CJDNS.pm 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package CJDNS;
  2. use Bencode qw(bencode bdecode);
  3. use Carp qw/croak/;
  4. use Digest::SHA qw(sha256_hex);
  5. use IO::Socket;
  6. # buffer size for reading from teh sawkets.
  7. use constant BUFFER_SIZE => 8192;
  8. our @ISA = qw();
  9. our $VERSION = '0.01';
  10. # turn on autoflush for this class.
  11. our $| = 1;
  12. sub new {
  13. my ($class, $addr, $port, $password) = @_;
  14. my $self = bless({
  15. connection => "$addr:$port",
  16. password => $password,
  17. }, $class);
  18. $self->{s} = IO::Socket::INET->new(
  19. PeerAddr => $addr,
  20. PeerPort => $port,
  21. Proto => 'udp',
  22. Type => SOCK_DGRAM
  23. );
  24. unless ($self->_ping) {
  25. die "Can't ping cjdns admin interface at udp://$addr:$port\n";
  26. }
  27. $self->_make_methods;
  28. return $self;
  29. }
  30. sub _make_methods {
  31. my ($self) = @_;
  32. my $s = $self->s;
  33. my $availableFunctions;
  34. my $page = 0;
  35. while (1) {
  36. my $to_decode;
  37. print $s "d1:q24:Admin_availableFunctions4:argsd4:pagei$page" . "eee";
  38. # grab the data and rock it out!
  39. recv($s, $to_decode, BUFFER_SIZE, undef);
  40. my $decoded = bdecode($to_decode);
  41. # copy the hash!
  42. foreach my $key (keys %{$decoded->{availableFunctions}}) {
  43. $availableFunctions->{$key} = $decoded->{availableFunctions}->{$key};
  44. }
  45. last unless exists $decoded->{more};
  46. # get the next page.
  47. $page++;
  48. }
  49. # first let's start by loading them as named into the cjdns namespace.
  50. foreach my $method_name (keys %$availableFunctions) {
  51. my $prototype = $availableFunctions->{$method_name};
  52. $self->{capabilities}->{$method_name} = $prototype;
  53. # This is the code that actually calls the function!
  54. my $method = sub {
  55. my ($self, %args) = @_;
  56. my $s = $self->s;
  57. my $to_decode;
  58. print $s "d1:q6:cookiee";
  59. recv($s, $to_decode, BUFFER_SIZE, undef);
  60. my $dec = bdecode($to_decode);
  61. my $cookie = $dec->{cookie};
  62. my $req = {
  63. q => 'auth',
  64. aq => $method_name,
  65. hash => sha256_hex($self->{password} . $cookie),
  66. cookie => " $cookie",
  67. args => \%args,
  68. };
  69. # replace $req->{hash} with a hash of the bencoded request.
  70. my $req_benc = bencode($req);
  71. $req->{hash} = sha256_hex($req_benc);
  72. # then re-encode thusly:
  73. $req_benc = bencode($req);
  74. print $s $req_benc;
  75. my $to_decode;
  76. recv($s, $to_decode, BUFFER_SIZE, undef);
  77. my $dec = bdecode($to_decode);
  78. if (ref($dec)) {
  79. # { error: "none" } is a success case
  80. if ($dec->{error} && $dec->{error} ne 'none') {
  81. croak "[error] CJDNS method '$method_name': $dec->{error}";
  82. }
  83. }
  84. return $dec;
  85. };
  86. # and now it's a method!
  87. my $full_name = "CJDNS::$method_name";
  88. *{$full_name} = $method;
  89. }
  90. }
  91. sub capabilities {
  92. my ($self) = @_;
  93. my $return = "Cjdns Administration Protocol Capabilities\n";
  94. $return .= "------------------------------------------\n";
  95. foreach my $func (keys %{$self->{capabilities}}) {
  96. $return .= " $func\n";
  97. foreach my $attr (keys %{$self->{capabilities}->{$func}}) {
  98. $return .= " + $attr: $self->{capabilities}->{$func}->{$attr}->{type} ";
  99. if ($self->{capabilities}->{$func}->{$attr}->{required}) {
  100. $return .= "[required]";
  101. }
  102. $return .= "\n";
  103. }
  104. $return .= "\n";
  105. }
  106. return $return;
  107. }
  108. sub _ping {
  109. my ($self) = @_;
  110. my $s = $self->s;
  111. my $data;
  112. print $s "d1:q4:pinge";
  113. recv($s, $data, BUFFER_SIZE, undef);
  114. if ($data eq "d1:q4:ponge") {
  115. return 1;
  116. } else {
  117. return undef;
  118. }
  119. }
  120. sub s {
  121. my ($self) = @_;
  122. return $self->{s};
  123. }
  124. 1;
  125. __END__
  126. =head1 NAME
  127. Cjdns - Perl interface to the Cjdns Administration Interface
  128. =head1 SYNOPSIS
  129. use CJDNS;
  130. my $cjdns = CJDNS->new('localhost', '12345', 'abc123');
  131. printf("Cjdns' routing table is using %d bytes of memory!\n", $cjdns->memory->{bytes});
  132. =head1 DESCRIPTION
  133. Perl interface to the cjdns Administration system
  134. =head1 SEE ALSO
  135. https://github.com/cjdelisle/cjdns
  136. =head1 AUTHOR
  137. Michael Gregorowicz, E<lt>mikei@mg2.orgE<gt>
  138. =head1 COPYRIGHT AND LICENSE
  139. Copyright (C) 2012 by Michael Gregorowicz
  140. This library is free software; you can redistribute it and/or modify
  141. it under the same terms as Perl itself, either Perl version 5.14.2 or,
  142. at your option, any later version of Perl 5 you may have available.
  143. =cut