ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/MP/Transport.pm
(Generate patch)

Comparing AnyEvent-MP/MP/Transport.pm (file contents):
Revision 1.11 by root, Mon Aug 3 15:40:53 2009 UTC vs.
Revision 1.28 by root, Sun Aug 9 16:08:16 2009 UTC

1=head1 NAME 1=head1 NAME
2 2
3AnyEvent::MP::Transport - actual transport protocol 3AnyEvent::MP::Transport - actual transport protocol handler
4 4
5=head1 SYNOPSIS 5=head1 SYNOPSIS
6 6
7 use AnyEvent::MP::Transport; 7 use AnyEvent::MP::Transport;
8 8
9=head1 DESCRIPTION 9=head1 DESCRIPTION
10 10
11This is the superclass for MP transports, most of which is considered an 11This implements the actual transport protocol for MP (it represents a
12implementation detail. 12single link), most of which is considered an implementation detail.
13 13
14See the "PROTOCOL" section below if you want to write another client for 14See the "PROTOCOL" section below if you want to write another client for
15this protocol. 15this protocol.
16 16
17=head1 FUNCTIONS/METHODS 17=head1 FUNCTIONS/METHODS
22 22
23package AnyEvent::MP::Transport; 23package AnyEvent::MP::Transport;
24 24
25use common::sense; 25use common::sense;
26 26
27use Scalar::Util; 27use Scalar::Util ();
28use List::Util ();
28use MIME::Base64 (); 29use MIME::Base64 ();
29use Storable (); 30use Storable ();
30use JSON::XS (); 31use JSON::XS ();
31 32
33use Digest::MD6 ();
34use Digest::HMAC_MD6 ();
35
32use AE (); 36use AE ();
33use AnyEvent::Socket (); 37use AnyEvent::Socket ();
34use AnyEvent::Handle (); 38use AnyEvent::Handle 4.92 ();
35 39
36use base Exporter::; 40use base Exporter::;
37 41
38our $VERSION = '0.0';
39our $PROTOCOL_VERSION = 0; 42our $PROTOCOL_VERSION = 0;
40 43
41=item $listener = mp_listener $host, $port, <constructor-args>, $cb->($transport) 44=item $listener = mp_listener $host, $port, <constructor-args>, $cb->($transport)
42 45
43Creates a listener on the given host/port using 46Creates a listener on the given host/port using
109 peername => $peername, # for verification 112 peername => $peername, # for verification
110 ; 113 ;
111 114
112=cut 115=cut
113 116
117sub LATENCY() { 3 } # assumed max. network latency
118
114our @FRAMINGS = qw(json storable); # the framing types we accept and send, in order of preference 119our @FRAMINGS = qw(json storable); # the framing types we accept and send, in order of preference
115our @AUTH_SND = qw(hmac_md6_64_256); # auth types we send 120our @AUTH_SND = qw(hmac_md6_64_256); # auth types we send
116our @AUTH_RCV = (@AUTH_SND, qw(hex_secret)); # auth types we accept 121our @AUTH_RCV = (@AUTH_SND, qw(cleartext)); # auth types we accept
117 122
118#AnyEvent::Handle::register_write_type mp_record => sub { 123#AnyEvent::Handle::register_write_type mp_record => sub {
119#}; 124#};
120 125
121sub new { 126sub new {
126 $self->{queue} = []; 131 $self->{queue} = [];
127 132
128 { 133 {
129 Scalar::Util::weaken (my $self = $self); 134 Scalar::Util::weaken (my $self = $self);
130 135
131 $arg{tls_ctx_disabled} ||= {
132 sslv2 => 0,
133 sslv3 => 0,
134 tlsv1 => 1,
135 verify => 1,
136 cert_file => "secret.pem",
137 ca_file => "secret.pem",
138 verify_require_client_cert => 1,
139 };
140
141 $arg{secret} = AnyEvent::MP::Base::default_secret () 136 $arg{secret} = AnyEvent::MP::Base::default_secret ()
142 unless exists $arg{secret}; 137 unless exists $arg{secret};
143 138
139 $arg{timeout} = 30
140 unless exists $arg{timeout};
141
142 $arg{timeout} = 1 + LATENCY
143 if $arg{timeout} < 1 + LATENCY;
144
145 my $secret = $arg{secret};
146
147 if ($secret =~ /-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----/s) {
148 # assume TLS mode
149 $arg{tls_ctx} = {
150 sslv2 => 0,
151 sslv3 => 0,
152 tlsv1 => 1,
153 verify => 1,
154 cert => $secret,
155 ca_cert => $secret,
156 verify_require_client_cert => 1,
157 };
158 }
159
144 $self->{hdl} = new AnyEvent::Handle 160 $self->{hdl} = new AnyEvent::Handle
145 fh => delete $arg{fh}, 161 fh => delete $arg{fh},
146 rbuf_max => 64 * 1024,
147 autocork => 1, 162 autocork => 1,
148 no_delay => 1, 163 no_delay => 1,
149 on_error => sub { 164 on_error => sub {
150 $self->error ($_[2]); 165 $self->error ($_[2]);
151 }, 166 },
167 rtimeout => $AnyEvent::MP::Base::CONNECT_TIMEOUT,
152 peername => delete $arg{peername}, 168 peername => delete $arg{peername},
153 ; 169 ;
154 170
155 my $secret = $arg{secret};
156 my $greeting_kv = $self->{greeting} ||= {}; 171 my $greeting_kv = $self->{greeting} ||= {};
172
173 $self->{local_node} = $AnyEvent::MP::Base::NODE;
174
157 $greeting_kv->{"tls"} = "1.0" 175 $greeting_kv->{"tls"} = "1.0" if $arg{tls_ctx};
158 if $arg{tls_ctx};
159 $greeting_kv->{provider} = "AE-$VERSION"; 176 $greeting_kv->{provider} = "AE-$AnyEvent::MP::Base::VERSION";
160 $greeting_kv->{peeraddr} = AnyEvent::Socket::format_hostport $self->{peerhost}, $self->{peerport}; 177 $greeting_kv->{peeraddr} = AnyEvent::Socket::format_hostport $self->{peerhost}, $self->{peerport};
178 $greeting_kv->{timeout} = $arg{timeout};
161 179
162 # send greeting 180 # send greeting
163 my $lgreeting1 = "aemp;$PROTOCOL_VERSION;$PROTOCOL_VERSION" # version, min 181 my $lgreeting1 = "aemp;$PROTOCOL_VERSION"
164 . ";$AnyEvent::MP::Base::UNIQ" 182 . ";$self->{local_node}"
165 . ";$AnyEvent::MP::Base::NODE"
166 . ";" . (join ",", @AUTH_RCV) 183 . ";" . (join ",", @AUTH_RCV)
167 . ";" . (join ",", @FRAMINGS) 184 . ";" . (join ",", @FRAMINGS)
168 . (join "", map ";$_=$greeting_kv->{$_}", keys %$greeting_kv); 185 . (join "", map ";$_=$greeting_kv->{$_}", keys %$greeting_kv);
186
169 my $lgreeting2 = MIME::Base64::encode_base64 AnyEvent::MP::Base::nonce (33), ""; 187 my $lgreeting2 = MIME::Base64::encode_base64 AnyEvent::MP::Base::nonce (33), "";
170 188
171 $self->{hdl}->push_write ("$lgreeting1\012$lgreeting2\012"); 189 $self->{hdl}->push_write ("$lgreeting1\012$lgreeting2\012");
172 190
173 # expect greeting 191 # expect greeting
192 $self->{hdl}->rbuf_max (4 * 1024);
174 $self->{hdl}->push_read (line => sub { 193 $self->{hdl}->push_read (line => sub {
175 my $rgreeting1 = $_[1]; 194 my $rgreeting1 = $_[1];
176 195
177 my ($aemp, $version, $version_min, $uniq, $rnode, $auths, $framings, @kv) = split /;/, $rgreeting1; 196 my ($aemp, $version, $rnode, $auths, $framings, @kv) = split /;/, $rgreeting1;
178 197
179 if ($aemp ne "aemp") { 198 if ($aemp ne "aemp") {
180 return $self->error ("unparsable greeting"); 199 return $self->error ("unparsable greeting");
181 } elsif ($version_min > $PROTOCOL_VERSION) { 200 } elsif ($version != $PROTOCOL_VERSION) {
182 return $self->error ("version mismatch (we: $PROTOCOL_VERSION, they: $version_min .. $version)"); 201 return $self->error ("version mismatch (we: $PROTOCOL_VERSION, they: $version)");
183 } 202 }
184 203
185 my $s_auth; 204 my $s_auth;
186 for my $auth_ (split /,/, $auths) { 205 for my $auth_ (split /,/, $auths) {
187 if (grep $auth_ eq $_, @AUTH_SND) { 206 if (grep $auth_ eq $_, @AUTH_SND) {
204 } 223 }
205 224
206 defined $s_framing 225 defined $s_framing
207 or return $self->error ("$framings: no common framing method supported"); 226 or return $self->error ("$framings: no common framing method supported");
208 227
209 $self->{remote_uniq} = $uniq;
210 $self->{remote_node} = $rnode; 228 $self->{remote_node} = $rnode;
211 229
212 $self->{remote_greeting} = { 230 $self->{remote_greeting} = {
213 map /^([^=]+)(?:=(.*))?/ ? ($1 => $2) : (), 231 map /^([^=]+)(?:=(.*))?/ ? ($1 => $2) : (),
214 @kv 232 @kv
216 234
217 # read nonce 235 # read nonce
218 $self->{hdl}->push_read (line => sub { 236 $self->{hdl}->push_read (line => sub {
219 my $rgreeting2 = $_[1]; 237 my $rgreeting2 = $_[1];
220 238
239 "$lgreeting1\012$lgreeting2" ne "$rgreeting1\012$rgreeting2" # echo attack?
240 or return $self->error ("authentication error, echo attack?");
241
242 my $key = Digest::MD6::md6 $secret;
243 my $lauth;
244
221 if ($self->{tls_ctx} and 1 == int $self->{remote_greeting}{tls}) { 245 if ($self->{tls_ctx} and 1 == int $self->{remote_greeting}{tls}) {
222 $self->{tls} = $lgreeting2 lt $rgreeting2 ? "connect" : "accept"; 246 $self->{tls} = $lgreeting2 lt $rgreeting2 ? "connect" : "accept";
223 $self->{hdl}->starttls ($self->{tls}, $self->{tls_ctx}); 247 $self->{hdl}->starttls ($self->{tls}, $self->{tls_ctx});
248 $s_auth = "tls";
249 $lauth = "";
250 } else {
251 # we currently only support hmac_md6_64_256
252 $lauth = Digest::HMAC_MD6::hmac_md6_hex $key, "$lgreeting1\012$lgreeting2\012$rgreeting1\012$rgreeting2\012", 64, 256;
224 } 253 }
225
226 # auth
227 require Digest::MD6;
228 require Digest::HMAC_MD6;
229
230 my $key = Digest::MD6::md6 ($secret);
231 my $lauth = Digest::HMAC_MD6::hmac_md6_base64 ($key, "$lgreeting1\012$lgreeting2\012$rgreeting1\012$rgreeting2\012", 64, 256);
232
233 my $rauth =
234 $s_auth eq "hmac_md6_64_256" ? Digest::HMAC_MD6::hmac_md6_base64 ($key, "$rgreeting1\012$rgreeting2\012$lgreeting1\012$lgreeting2\012", 64, 256)
235 : $s_auth eq "hex_secret" ? unpack "H*", $secret
236 : die;
237
238 $lauth ne $rauth # echo attack?
239 or return $self->error ("authentication error");
240 254
241 $self->{hdl}->push_write ("$s_auth;$lauth;$s_framing\012"); 255 $self->{hdl}->push_write ("$s_auth;$lauth;$s_framing\012");
242 256
243 $self->{hdl}->rbuf_max (64); # enough for 44 reply bytes or so 257 # read the authentication response
244 $self->{hdl}->push_read (line => sub { 258 $self->{hdl}->push_read (line => sub {
245 my ($hdl, $rline) = @_; 259 my ($hdl, $rline) = @_;
246 260
247 my ($auth_method, $rauth2, $r_framing) = split /;/, $rline; 261 my ($auth_method, $rauth2, $r_framing) = split /;/, $rline;
262
263 my $rauth =
264 $auth_method eq "hmac_md6_64_256" ? Digest::HMAC_MD6::hmac_md6_hex $key, "$rgreeting1\012$rgreeting2\012$lgreeting1\012$lgreeting2\012", 64, 256
265 : $auth_method eq "cleartext" ? unpack "H*", $secret
266 : $auth_method eq "tls" ? ($self->{tls} ? "" : "\012\012") # \012\012 never matches
267 : return $self->error ("$auth_method: fatal, selected unsupported auth method");
248 268
249 if ($rauth2 ne $rauth) { 269 if ($rauth2 ne $rauth) {
250 return $self->error ("authentication failure/shared secret mismatch"); 270 return $self->error ("authentication failure/shared secret mismatch");
251 } 271 }
252 272
253 $self->{s_framing} = $s_framing; 273 $self->{s_framing} = $s_framing;
254 274
255 $hdl->rbuf_max (undef); 275 $hdl->rbuf_max (undef);
256 my $queue = delete $self->{queue}; # we are connected 276 my $queue = delete $self->{queue}; # we are connected
257 277
278 $self->{hdl}->rtimeout ($self->{remote_greeting}{timeout});
279 $self->{hdl}->wtimeout ($arg{timeout} - LATENCY);
280 $self->{hdl}->on_wtimeout (sub { $self->send (["", "devnull"]) });
281
258 $self->connected; 282 $self->connected;
259 283
260 $hdl->push_write ($self->{s_framing} => $_) 284 # send queued messages
285 $self->send ($_)
261 for @$queue; 286 for @$queue;
262 287
288 # receive handling
289 my $src_node = $self->{node};
290
263 my $rmsg; $rmsg = sub { 291 my $rmsg; $rmsg = sub {
264 $_[0]->push_read ($r_framing => $rmsg); 292 $_[0]->push_read ($r_framing => $rmsg);
265 293
294 local $AnyEvent::MP::Base::SRCNODE = $src_node;
266 AnyEvent::MP::Base::_inject ($_[1]); 295 AnyEvent::MP::Base::_inject (@{ $_[1] });
267 }; 296 };
268 $hdl->push_read ($r_framing => $rmsg); 297 $hdl->push_read ($r_framing => $rmsg);
269 }); 298 });
270 }); 299 });
271 }); 300 });
276 305
277sub error { 306sub error {
278 my ($self, $msg) = @_; 307 my ($self, $msg) = @_;
279 308
280 if ($self->{node} && $self->{node}{transport} == $self) { 309 if ($self->{node} && $self->{node}{transport} == $self) {
310 #TODO: store error, but do not instantly fail
311 $self->{node}->fail (transport_error => $self->{node}{noderef}, $msg);
281 $self->{node}->clr_transport; 312 $self->{node}->clr_transport;
282 } 313 }
283 $AnyEvent::MP::Base::WARN->("$self->{peerhost}:$self->{peerport}: $msg"); 314 $AnyEvent::MP::Base::WARN->("$self->{peerhost}:$self->{peerport}: $msg");
284 $self->destroy; 315 $self->destroy;
285} 316}
286 317
287sub connected { 318sub connected {
288 my ($self) = @_; 319 my ($self) = @_;
289 320
321 if (ref $AnyEvent::MP::Base::SLAVE) {
322 # first connect with a master node
323 my $via = $self->{remote_node};
324 $via =~ s/,/!/g;
325 $AnyEvent::MP::Base::NODE .= "\@$via";
326 $AnyEvent::MP::Base::NODE{$AnyEvent::MP::Base::NODE} = $AnyEvent::MP::Base::NODE{""};
327 $AnyEvent::MP::Base::SLAVE->();
328 }
329
330 if ($self->{local_node} ne $AnyEvent::MP::Base::NODE) {
331 # node changed its name since first greeting
332 $self->send (["", iam => $AnyEvent::MP::Base::NODE]);
333 }
334
290 my $node = AnyEvent::MP::Base::add_node ($self->{remote_node}); 335 my $node = AnyEvent::MP::Base::add_node ($self->{remote_node});
291 Scalar::Util::weaken ($self->{node} = $node); 336 Scalar::Util::weaken ($self->{node} = $node);
292 $node->set_transport ($self); 337 $node->set_transport ($self);
293} 338}
294 339
322The greeting consists of two text lines that are ended by either an ASCII 367The greeting consists of two text lines that are ended by either an ASCII
323CR LF pair, or a single ASCII LF (recommended). 368CR LF pair, or a single ASCII LF (recommended).
324 369
325=head2 GREETING 370=head2 GREETING
326 371
372All the lines until after authentication must not exceed 4kb in length,
373including delimiter. Afterwards there is no limit on the packet size that
374can be received.
375
376=head3 First Greeting Line
377
378Example:
379
380 aemp;0;fec.4a7720fc;127.0.0.1:1235,[::1]:1235;hmac_md6_64_256;json,storable;provider=AE-0.0
381
327The first line contains strings separated (not ended) by C<;> 382The first line contains strings separated (not ended) by C<;>
328characters. The first seven strings are fixed by the protocol, the 383characters. The first even ixtrings are fixed by the protocol, the
329remaining strings are C<KEY=VALUE> pairs. None of them may contain C<;> 384remaining strings are C<KEY=VALUE> pairs. None of them may contain C<;>
330characters themselves. 385characters themselves.
331 386
332The seven fixed strings are: 387The fixed strings are:
333 388
334=over 4 389=over 4
335 390
336=item C<aemp> 391=item protocol identification
337 392
338The constant C<aemp> to identify the protocol. 393The constant C<aemp> to identify the protocol.
339 394
340=item protocol version 395=item protocol version
341 396
342The (maximum) protocol version supported by this end, currently C<0>.
343
344=item minimum protocol version
345
346The minimum protocol version supported by this end, currently C<0>. 397The protocol version supported by this end, currently C<0>. If the
347 398versions don't match then no communication is possible. Minor extensions
348=item a token uniquely identifying the current node instance 399are supposed to be handled through additional key-value pairs.
349
350This is a string that must change between restarts. It usually contains
351things like the current time, the (OS) process id or similar values, but
352no meaning of the contents are assumed.
353 400
354=item the node endpoint descriptors 401=item the node endpoint descriptors
355 402
356for public nodes, this is a comma-separated list of protocol endpoints, 403for public nodes, this is a comma-separated list of protocol endpoints,
357i.e., the noderef. For slave nodes, this is a unique identifier. 404i.e., the noderef. For slave nodes, this is a unique identifier of the
405form C<slave/nonce>.
358 406
359=item the acceptable authentication methods 407=item the acceptable authentication methods
360 408
361A comma-separated list of authentication methods supported by the 409A comma-separated list of authentication methods supported by the
362node. Note that AnyEvent::MP supports a C<hex_secret> authentication 410node. Note that AnyEvent::MP supports a C<hex_secret> authentication
391=item tls=<major>.<minor> 439=item tls=<major>.<minor>
392 440
393Indicates that the other side supports TLS (version should be 1.0) and 441Indicates that the other side supports TLS (version should be 1.0) and
394wishes to do a TLS handshake. 442wishes to do a TLS handshake.
395 443
444=item timeout=<seconds>
445
446The amount of time after which this node should be detected as dead unless
447some data has been received. The node is responsible to send traffic
448reasonably more often than this interval (such as every timeout minus five
449seconds).
450
396=back 451=back
452
453=head3 Second Greeting Line
397 454
398After this greeting line there will be a second line containing a 455After this greeting line there will be a second line containing a
399cryptographic nonce, i.e. random data of high quality. To keep the 456cryptographic nonce, i.e. random data of high quality. To keep the
400protocol text-only, these are usually 32 base64-encoded octets, but 457protocol text-only, these are usually 32 base64-encoded octets, but
401it could be anything that doesn't contain any ASCII CR or ASCII LF 458it could be anything that doesn't contain any ASCII CR or ASCII LF
402characters. 459characters.
403 460
404Example of the two lines of greeting: 461I<< The two nonces B<must> be different, and an aemp implementation
462B<must> check and fail when they are identical >>.
405 463
406 aemp;0;0;e7d.4a76f48f;10.0.0.1:4040;hmac_md6_64_256,hex_secret;json,storable;provider=AE-0.0;peeraddr=127.0.0.1:1235 464Example of a nonce line:
407 XntegV2Guvss0qNn7phCPnoU87xqxV+4Mqm/5y4iQm6a 465
466 p/I122ql7kJR8lumW3lXlXCeBnyDAvz8NQo3x5IFowE4
408 467
409=head2 TLS handshake 468=head2 TLS handshake
410 469
411If, after the handshake, both sides indicate interest in TLS, then the 470I<< If, after the handshake, both sides indicate interest in TLS, then the
412connection I<must> use TLS, or fail. 471connection B<must> use TLS, or fail. >>
413 472
414Both sides compare their nonces, and the side who sent the lower nonce 473Both sides compare their nonces, and the side who sent the lower nonce
415value ("string" comparison on the raw octet values) becomes the client, 474value ("string" comparison on the raw octet values) becomes the client,
416and the one with the higher nonce the server. 475and the one with the higher nonce the server.
417 476
428 487
429=item the authentication method chosen 488=item the authentication method chosen
430 489
431This must be one of the methods offered by the other side in the greeting. 490This must be one of the methods offered by the other side in the greeting.
432 491
492The currently supported authentication methods are:
493
494=over 4
495
496=item cleartext
497
498This is simply the shared secret, lowercase-hex-encoded. This method is of
499course very insecure, unless TLS is used, which is why this module will
500accept, but not generate, cleartext auth replies.
501
502=item hmac_md6_64_256
503
504This method uses an MD6 HMAC with 64 bit blocksize and 256 bit hash. First, the shared secret
505is hashed with MD6:
506
507 key = MD6 (secret)
508
509This secret is then used to generate the "local auth reply", by taking
510the two local greeting lines and the two remote greeting lines (without
511line endings), appending \012 to all of them, concatenating them and
512calculating the MD6 HMAC with the key.
513
514 lauth = HMAC_MD6 key, "lgreeting1\012lgreeting2\012rgreeting1\012rgreeting2\012"
515
516This authentication token is then lowercase-hex-encoded and sent to the
517other side.
518
519Then the remote auth reply is generated using the same method, but local
520and remote greeting lines swapped:
521
522 rauth = HMAC_MD6 key, "rgreeting1\012rgreeting2\012lgreeting1\012lgreeting2\012"
523
524This is the token that is expected from the other side.
525
526=item tls
527
528This type is only valid iff TLS was enabled and the TLS handshake
529was successful. It has no authentication data, as the server/client
530certificate was successfully verified.
531
532Implementations supporting TLS I<must> accept this authentication type.
533
534=back
535
433=item the authentication data 536=item the authentication data
434 537
435The authentication data itself, usually base64 or hex-encoded data. 538The authentication data itself, usually base64 or hex-encoded data, see
539above.
436 540
437=item the framing protocol chosen 541=item the framing protocol chosen
438 542
439This must be one of the framing protocols offered by the other side in the 543This must be one of the framing protocols offered by the other side in the
440greeting. Each side must accept the choice of the other side. 544greeting. Each side must accept the choice of the other side.
441 545
442=back 546=back
443 547
444Example (the actual reply matching the previous example): 548Example of an authentication reply:
445 549
446 hmac_md6_64_256;wIlLedBY956UCGSISG9mBZRDTG8xUi73/sVse2DSQp0;json 550 hmac_md6_64_256;363d5175df38bd9eaddd3f6ca18aa1c0c4aa22f0da245ac638d048398c26b8d3;json
447 551
448=head2 DATA PHASE 552=head2 DATA PHASE
449 553
450After this, packets get exchanged using the chosen framing protocol. It is 554After this, packets get exchanged using the chosen framing protocol. It is
451quite possible that both sides use a different framing protocol. 555quite possible that both sides use a different framing protocol.
452 556
557=head2 FULL EXAMPLE
558
559This is an actual protocol dump of a handshake, followed by a single data
560packet. The greater than/less than lines indicate the direction of the
561transfer only.
562
563 > aemp;0;nndKd+gn;10.0.0.1:4040;hmac_md6_64_256,cleartext;json,storable;provider=AE-0.0;peeraddr=127.0.0.1:1235
564 > sRG8bbc4TDbkpvH8FTP4HBs87OhepH6VuApoZqXXskuG
565 < aemp;0;nmpKd+gh;127.0.0.1:1235,[::1]:1235;hmac_md6_64_256,cleartext;json,storable;provider=AE-0.0;peeraddr=127.0.0.1:58760
566 < dCEUcL/LJVSTJcx8byEsOzrwhzJYOq+L3YcopA5T6EAo
567 > hmac_md6_64_256;9513d4b258975accfcb2ab7532b83690e9c119a502c612203332a591c7237788;json
568 < hmac_md6_64_256;0298d6ba2240faabb2b2e881cf86b97d70a113ca74a87dc006f9f1e9d3010f90;json
569 > ["","lookup","pinger","10.0.0.1:4040#nndKd+gn.a","resolved"]
570
453=head1 SEE ALSO 571=head1 SEE ALSO
454 572
455L<AnyEvent>. 573L<AnyEvent>.
456 574
457=head1 AUTHOR 575=head1 AUTHOR

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines