ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/MP/Transport.pm
Revision: 1.44
Committed: Sun Aug 30 09:24:09 2009 UTC (14 years, 11 months ago) by root
Branch: MAIN
Changes since 1.43: +1 -0 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 AnyEvent::MP::Transport - actual transport protocol handler
4
5 =head1 SYNOPSIS
6
7 use AnyEvent::MP::Transport;
8
9 =head1 DESCRIPTION
10
11 This module implements (and documents) the actual transport protocol for
12 AEMP.
13
14 See the "PROTOCOL" section below if you want to write another client for
15 this protocol.
16
17 =head1 FUNCTIONS/METHODS
18
19 =over 4
20
21 =cut
22
23 package AnyEvent::MP::Transport;
24
25 use common::sense;
26
27 use Scalar::Util ();
28 use List::Util ();
29 use MIME::Base64 ();
30 use Storable ();
31 use JSON::XS ();
32
33 use Digest::MD6 ();
34 use Digest::HMAC_MD6 ();
35
36 use AE ();
37 use AnyEvent::Socket ();
38 use AnyEvent::Handle 4.92 ();
39
40 use AnyEvent::MP::Config ();
41
42 our $PROTOCOL_VERSION = 0;
43
44 =item $listener = mp_listener $host, $port, <constructor-args>
45
46 Creates a listener on the given host/port using
47 C<AnyEvent::Socket::tcp_server>.
48
49 See C<new>, below, for constructor arguments.
50
51 Defaults for peerhost, peerport and fh are provided.
52
53 =cut
54
55 sub mp_server($$@) {
56 my ($host, $port, @args) = @_;
57
58 AnyEvent::Socket::tcp_server $host, $port, sub {
59 my ($fh, $host, $port) = @_;
60
61 my $tp = new AnyEvent::MP::Transport
62 fh => $fh,
63 peerhost => $host,
64 peerport => $port,
65 @args,
66 ;
67 $tp->{keepalive} = $tp;
68 }
69 }
70
71 =item $guard = mp_connect $host, $port, <constructor-args>, $cb->($transport)
72
73 =cut
74
75 sub mp_connect {
76 my $release = pop;
77 my ($host, $port, @args) = @_;
78
79 my $state;
80
81 $state = AnyEvent::Socket::tcp_connect $host, $port, sub {
82 my ($fh, $nhost, $nport) = @_;
83
84 return $release->() unless $fh;
85
86 $state = new AnyEvent::MP::Transport
87 fh => $fh,
88 peername => $host,
89 peerhost => $nhost,
90 peerport => $nport,
91 release => $release,
92 @args,
93 ;
94 };
95
96 \$state
97 }
98
99 =item new AnyEvent::MP::Transport
100
101 # immediately starts negotiation
102 my $transport = new AnyEvent::MP::Transport
103 # mandatory
104 fh => $filehandle,
105 local_id => $identifier,
106 on_recv => sub { receive-callback },
107 on_error => sub { error-callback },
108
109 # optional
110 on_eof => sub { clean-close-callback },
111 on_connect => sub { successful-connect-callback },
112 greeting => { key => value },
113
114 # tls support
115 tls_ctx => AnyEvent::TLS,
116 peername => $peername, # for verification
117 ;
118
119 =cut
120
121 sub LATENCY() { 3 } # assumed max. network latency
122
123 our @FRAMINGS = qw(json storable); # the framing types we accept and send, in order of preference
124 our @AUTH_SND = qw(tls_md6_64_256 hmac_md6_64_256); # auth types we send
125 our @AUTH_RCV = (@AUTH_SND, qw(tls_anon cleartext)); # auth types we accept
126
127 #AnyEvent::Handle::register_write_type mp_record => sub {
128 #};
129
130 sub new {
131 my ($class, %arg) = @_;
132
133 my $self = bless \%arg, $class;
134
135 $self->{queue} = [];
136
137 {
138 Scalar::Util::weaken (my $self = $self);
139
140 my $config = AnyEvent::MP::Config::config;
141
142 my $latency = $config->{network_latency} || LATENCY;
143
144 $self->{secret} = $config->{secret}
145 unless exists $self->{secret};
146
147 $self->{timeout} = $config->{monitor_timeout} || $AnyEvent::MP::Kernel::MONITOR_TIMEOUT
148 unless exists $self->{timeout};
149
150 $self->{timeout} -= $latency;
151
152 $self->{timeout} = 1 + $latency
153 if $self->{timeout} < 1 + $latency;
154
155 my $secret = $self->{secret};
156
157 if (exists $config->{cert}) {
158 $self->{tls_ctx} = {
159 sslv2 => 0,
160 sslv3 => 0,
161 tlsv1 => 1,
162 verify => 1,
163 cert => $config->{cert},
164 ca_cert => $config->{cert},
165 verify_require_client_cert => 1,
166 };
167 }
168
169 $self->{hdl} = new AnyEvent::Handle
170 fh => delete $self->{fh},
171 autocork => 1,
172 no_delay => 1,
173 on_error => sub {
174 $self->error ($_[2]);
175 },
176 rtimeout => $latency,
177 peername => delete $self->{peername},
178 ;
179
180 my $greeting_kv = $self->{greeting} ||= {};
181
182 $self->{local_node} ||= $AnyEvent::MP::Kernel::NODE;
183
184 $greeting_kv->{tls} = "1.0" if $self->{tls_ctx};
185 $greeting_kv->{provider} = "AE-$AnyEvent::MP::Kernel::VERSION";
186 $greeting_kv->{peeraddr} = AnyEvent::Socket::format_hostport $self->{peerhost}, $self->{peerport};
187 $greeting_kv->{timeout} = $self->{timeout};
188
189 # send greeting
190 my $lgreeting1 = "aemp;$PROTOCOL_VERSION"
191 . ";$self->{local_node}"
192 . ";" . (join ",", @AUTH_RCV)
193 . ";" . (join ",", @FRAMINGS)
194 . (join "", map ";$_=$greeting_kv->{$_}", keys %$greeting_kv);
195
196 my $lgreeting2 = MIME::Base64::encode_base64 AnyEvent::MP::Kernel::nonce (66), "";
197
198 $self->{hdl}->push_write ("$lgreeting1\012$lgreeting2\012");
199
200 # expect greeting
201 $self->{hdl}->rbuf_max (4 * 1024);
202 $self->{hdl}->push_read (line => sub {
203 my $rgreeting1 = $_[1];
204
205 my ($aemp, $version, $rnode, $auths, $framings, @kv) = split /;/, $rgreeting1;
206
207 if ($aemp ne "aemp") {
208 return $self->error ("unparsable greeting");
209 } elsif ($version != $PROTOCOL_VERSION) {
210 return $self->error ("version mismatch (we: $PROTOCOL_VERSION, they: $version)");
211 } elsif ($rnode eq $self->{local_node}) {
212 AnyEvent::MP::Global::avoid_seed ($self->{seed})
213 if exists $self->{seed};
214
215 return $self->error ("I refuse to talk to myself");
216 } elsif ($AnyEvent::MP::Kernel::NODE{$rnode} && $AnyEvent::MP::Kernel::NODE{$rnode}{transport}) {
217 return $self->error ("$rnode already connected, not connecting again.");
218 }
219
220 $self->{remote_node} = $rnode;
221
222 $self->{remote_greeting} = {
223 map /^([^=]+)(?:=(.*))?/ ? ($1 => $2) : (),
224 @kv
225 };
226
227 # read nonce
228 $self->{hdl}->push_read (line => sub {
229 my $rgreeting2 = $_[1];
230
231 "$lgreeting1\012$lgreeting2" ne "$rgreeting1\012$rgreeting2" # echo attack?
232 or return $self->error ("authentication error, echo attack?");
233
234 my $tls = $self->{tls_ctx} && 1 == int $self->{remote_greeting}{tls};
235
236 my $s_auth;
237 for my $auth_ (split /,/, $auths) {
238 if (grep $auth_ eq $_, @AUTH_SND and ($auth_ !~ /^tls_/ or $tls)) {
239 $s_auth = $auth_;
240 last;
241 }
242 }
243
244 defined $s_auth
245 or return $self->error ("$auths: no common auth type supported");
246
247 my $s_framing;
248 for my $framing_ (split /,/, $framings) {
249 if (grep $framing_ eq $_, @FRAMINGS) {
250 $s_framing = $framing_;
251 last;
252 }
253 }
254
255 defined $s_framing
256 or return $self->error ("$framings: no common framing method supported");
257
258 my $key;
259 my $lauth;
260
261 if ($tls) {
262 $self->{tls} = $lgreeting2 lt $rgreeting2 ? "connect" : "accept";
263 $self->{hdl}->starttls ($self->{tls}, $self->{tls_ctx});
264
265 $lauth =
266 $s_auth eq "tls_anon" ? ""
267 : $s_auth eq "tls_md6_64_256" ? Digest::MD6::md6_hex "$lgreeting1\012$lgreeting2\012$rgreeting1\012$rgreeting2\012"
268 : return $self->error ("$s_auth: fatal, selected unsupported snd auth method");
269
270 } elsif (length $secret) {
271 return $self->error ("$s_auth: fatal, selected unsupported snd auth method")
272 unless $s_auth eq "hmac_md6_64_256"; # hardcoded atm.
273
274 $key = Digest::MD6::md6 $secret;
275 # we currently only support hmac_md6_64_256
276 $lauth = Digest::HMAC_MD6::hmac_md6_hex $key, "$lgreeting1\012$lgreeting2\012$rgreeting1\012$rgreeting2\012", 64, 256;
277
278 } else {
279 return $self->error ("unable to handshake TLS and no shared secret configured");
280 }
281
282 $self->{hdl}->push_write ("$s_auth;$lauth;$s_framing\012");
283
284 # read the authentication response
285 $self->{hdl}->push_read (line => sub {
286 my ($hdl, $rline) = @_;
287
288 my ($auth_method, $rauth2, $r_framing) = split /;/, $rline;
289
290 my $rauth =
291 $auth_method eq "hmac_md6_64_256" ? Digest::HMAC_MD6::hmac_md6_hex $key, "$rgreeting1\012$rgreeting2\012$lgreeting1\012$lgreeting2\012", 64, 256
292 : $auth_method eq "cleartext" ? unpack "H*", $secret
293 : $auth_method eq "tls_anon" ? ($tls ? "" : "\012\012") # \012\012 never matches
294 : $auth_method eq "tls_md6_64_256" ? ($tls ? Digest::MD6::md6_hex "$rgreeting1\012$rgreeting2\012$lgreeting1\012$lgreeting2\012" : "\012\012")
295 : return $self->error ("$auth_method: fatal, selected unsupported rcv auth method");
296
297 if ($rauth2 ne $rauth) {
298 return $self->error ("authentication failure/shared secret mismatch");
299 }
300
301 $self->{s_framing} = $s_framing;
302
303 $hdl->rbuf_max (undef);
304 my $queue = delete $self->{queue}; # we are connected
305
306 $self->{hdl}->rtimeout ($self->{remote_greeting}{timeout});
307 $self->{hdl}->wtimeout ($self->{timeout} - LATENCY);
308 $self->{hdl}->on_wtimeout (sub { $self->send ([]) });
309
310 $self->connected;
311
312 # send queued messages
313 $self->send ($_)
314 for @$queue;
315
316 # receive handling
317 my $src_node = $self->{node};
318 Scalar::Util::weaken $src_node;
319
320 my $rmsg; $rmsg = sub {
321 $_[0]->push_read ($r_framing => $rmsg);
322
323 local $AnyEvent::MP::Kernel::SRCNODE = $src_node;
324 AnyEvent::MP::Kernel::_inject (@{ $_[1] });
325 };
326 $hdl->push_read ($r_framing => $rmsg);
327 });
328 });
329 });
330 }
331
332 $self
333 }
334
335 sub error {
336 my ($self, $msg) = @_;
337
338 delete $self->{keepalive};
339
340 # $AnyEvent::MP::Kernel::WARN->(9, "$self->{peerhost}:$self->{peerport} $msg");#d#
341
342 $self->{node}->transport_error (transport_error => $self->{node}{id}, $msg)
343 if $self->{node} && $self->{node}{transport} == $self;
344
345 (delete $self->{release})->()
346 if exists $self->{release};
347
348 # $AnyEvent::MP::Kernel::WARN->(7, "$self->{peerhost}:$self->{peerport}: $msg");
349 $self->destroy;
350 }
351
352 sub connected {
353 my ($self) = @_;
354
355 delete $self->{keepalive};
356
357 (delete $self->{release})->()
358 if exists $self->{release};
359
360 $AnyEvent::MP::Kernel::WARN->(9, "$self->{peerhost}:$self->{peerport} connected as $self->{remote_node}");
361
362 my $node = AnyEvent::MP::Kernel::add_node ($self->{remote_node});
363 Scalar::Util::weaken ($self->{node} = $node);
364 $node->transport_connect ($self);
365 }
366
367 sub send {
368 $_[0]{hdl}->push_write ($_[0]{s_framing} => $_[1]);
369 }
370
371 sub destroy {
372 my ($self) = @_;
373
374 (delete $self->{release})->()
375 if exists $self->{release};
376
377 $self->{hdl}->destroy
378 if $self->{hdl};
379 }
380
381 sub DESTROY {
382 my ($self) = @_;
383
384 $self->destroy;
385 }
386
387 =back
388
389 =head1 PROTOCOL
390
391 The AEMP protocol is relatively simple, and consists of three phases which
392 are symmetrical for both sides: greeting (followed by optionally switching
393 to TLS mode), authentication and packet exchange.
394
395 The protocol is designed to allow both full-text and binary streams.
396
397 The greeting consists of two text lines that are ended by either an ASCII
398 CR LF pair, or a single ASCII LF (recommended).
399
400 =head2 GREETING
401
402 All the lines until after authentication must not exceed 4kb in length,
403 including line delimiter. Afterwards there is no limit on the packet size
404 that can be received.
405
406 =head3 First Greeting Line
407
408 Example:
409
410 aemp;0;rain;tls_md6_64_256,hmac_md6_64_256,tls_anon,cleartext;json,storable;timeout=12;peeraddr=10.0.0.1:48082
411
412 The first line contains strings separated (not ended) by C<;>
413 characters. The first five strings are fixed by the protocol, the
414 remaining strings are C<KEY=VALUE> pairs. None of them may contain C<;>
415 characters themselves (when escaping is needed, use C<%3b> to represent
416 C<;> and C<%25> to represent C<%>)-
417
418 The fixed strings are:
419
420 =over 4
421
422 =item protocol identification
423
424 The constant C<aemp> to identify this protocol.
425
426 =item protocol version
427
428 The protocol version supported by this end, currently C<0>. If the
429 versions don't match then no communication is possible. Minor extensions
430 are supposed to be handled through additional key-value pairs.
431
432 =item the node ID
433
434 This is the node ID of the connecting node.
435
436 =item the acceptable authentication methods
437
438 A comma-separated list of authentication methods supported by the
439 node. Note that AnyEvent::MP supports a C<hex_secret> authentication
440 method that accepts a clear-text password (hex-encoded), but will not use
441 this authentication method itself.
442
443 The receiving side should choose the first authentication method it
444 supports.
445
446 =item the acceptable framing formats
447
448 A comma-separated list of packet encoding/framing formats understood. The
449 receiving side should choose the first framing format it supports for
450 sending packets (which might be different from the format it has to accept).
451
452 =back
453
454 The remaining arguments are C<KEY=VALUE> pairs. The following key-value
455 pairs are known at this time:
456
457 =over 4
458
459 =item timeout=<seconds>
460
461 The amount of time after which this node should be detected as dead unless
462 some data has been received. The node is responsible to send traffic
463 reasonably more often than this interval (such as every timeout minus five
464 seconds).
465
466 =item provider=<module-version>
467
468 The software provider for this implementation. For AnyEvent::MP, this is
469 C<AE-0.0> or whatever version it currently is at.
470
471 =item peeraddr=<host>:<port>
472
473 The peer address (socket address of the other side) as seen locally.
474
475 =item tls=<major>.<minor>
476
477 Indicates that the other side supports TLS (version should be 1.0) and
478 wishes to do a TLS handshake.
479
480 =back
481
482 =head3 Second Greeting Line
483
484 After this greeting line there will be a second line containing a
485 cryptographic nonce, i.e. random data of high quality. To keep the
486 protocol text-only, these are usually 32 base64-encoded octets, but
487 it could be anything that doesn't contain any ASCII CR or ASCII LF
488 characters.
489
490 I<< The two nonces B<must> be different, and an aemp implementation
491 B<must> check and fail when they are identical >>.
492
493 Example of a nonce line (yes, it's random-looking because it is random
494 data):
495
496 2XYhdG7/O6epFa4wuP0ujAEx1rXYWRcOypjUYK7eF6yWAQr7gwIN9m/2+mVvBrTPXz5GJDgfGm9d8QRABAbmAP/s
497
498 =head2 TLS handshake
499
500 I<< If, after the handshake, both sides indicate interest in TLS, then the
501 connection B<must> use TLS, or fail to continue. >>
502
503 Both sides compare their nonces, and the side who sent the lower nonce
504 value ("string" comparison on the raw octet values) becomes the client,
505 and the one with the higher nonce the server.
506
507 =head2 AUTHENTICATION PHASE
508
509 After the greeting is received (and the optional TLS handshake),
510 the authentication phase begins, which consists of sending a single
511 C<;>-separated line with three fixed strings and any number of
512 C<KEY=VALUE> pairs.
513
514 The three fixed strings are:
515
516 =over 4
517
518 =item the authentication method chosen
519
520 This must be one of the methods offered by the other side in the greeting.
521
522 Note that all methods starting with C<tls_> are only valid I<iff> TLS was
523 successfully handshaked (and to be secure the implementation must enforce
524 this).
525
526 The currently supported authentication methods are:
527
528 =over 4
529
530 =item cleartext
531
532 This is simply the shared secret, lowercase-hex-encoded. This method is of
533 course very insecure if TLS is not used (and not completely secure even
534 if TLS is used), which is why this module will accept, but not generate,
535 cleartext auth replies.
536
537 =item hmac_md6_64_256
538
539 This method uses an MD6 HMAC with 64 bit blocksize and 256 bit hash, and
540 requires a shared secret. It is the preferred auth method when a shared
541 secret is available.
542
543 First, the shared secret is hashed with MD6:
544
545 key = MD6 (secret)
546
547 This secret is then used to generate the "local auth reply", by taking
548 the two local greeting lines and the two remote greeting lines (without
549 line endings), appending \012 to all of them, concatenating them and
550 calculating the MD6 HMAC with the key:
551
552 lauth = HMAC_MD6 key, "lgreeting1\012lgreeting2\012rgreeting1\012rgreeting2\012"
553
554 This authentication token is then lowercase-hex-encoded and sent to the
555 other side.
556
557 Then the remote auth reply is generated using the same method, but local
558 and remote greeting lines swapped:
559
560 rauth = HMAC_MD6 key, "rgreeting1\012rgreeting2\012lgreeting1\012lgreeting2\012"
561
562 This is the token that is expected from the other side.
563
564 =item tls_anon
565
566 This type is only valid I<iff> TLS was enabled and the TLS handshake
567 was successful. It has no authentication data, as the server/client
568 certificate was successfully verified.
569
570 This authentication type is somewhat insecure, as it allows a
571 man-in-the-middle attacker to change some of the connection parameters
572 (such as the framing format), although there is no known attack that
573 exploits this in a way that is worse than just denying the service.
574
575 By default, this implementation accepts but never generates this auth
576 reply.
577
578 =item tls_md6_64_256
579
580 This type is only valid I<iff> TLS was enabled and the TLS handshake was
581 successful.
582
583 This authentication type simply calculates:
584
585 lauth = MD6 "rgreeting1\012rgreeting2\012lgreeting1\012lgreeting2\012"
586
587 and lowercase-hex encodes the result and sends it as authentication
588 data. No shared secret is required (authentication is done by TLS). The
589 checksum exists only to make tinkering with the greeting hard.
590
591 =back
592
593 =item the authentication data
594
595 The authentication data itself, usually base64 or hex-encoded data, see
596 above.
597
598 =item the framing protocol chosen
599
600 This must be one of the framing protocols offered by the other side in the
601 greeting. Each side must accept the choice of the other side, and generate
602 packets in the format it chose itself.
603
604 =back
605
606 Example of an authentication reply:
607
608 hmac_md6_64_256;363d5175df38bd9eaddd3f6ca18aa1c0c4aa22f0da245ac638d048398c26b8d3;json
609
610 =head2 DATA PHASE
611
612 After this, packets get exchanged using the chosen framing protocol. It is
613 quite possible that both sides use a different framing protocol.
614
615 =head2 FULL EXAMPLE
616
617 This is an actual protocol dump of a handshake, followed by a single data
618 packet. The greater than/less than lines indicate the direction of the
619 transfer only.
620
621 > aemp;0;anon/57Cs1CggVJjzYaQp13XXg4;tls_md6_64_256,hmac_md6_64_256,tls_anon,cleartext;json,storable;provider=AE-0.8;timeout=12;peeraddr=10.0.0.17:4040
622 > yLgdG1ov/02shVkVQer3wzeuywZK+oraTdEQBmIqWHaegxSGDG4g+HqogLQbvdypFOsoDWJ1Sh4ImV4DMhvUBwTK
623
624 < aemp;0;ruth;tls_md6_64_256,hmac_md6_64_256,tls_anon,cleartext;json,storable;provider=AE-0.8;timeout=12;peeraddr=10.0.0.1:37108
625 < +xMQXP8ElfNmuvEhsmcp+s2wCJOuQAsPxSg3d2Ewhs6gBnJz+ypVdWJ/wAVrXqlIJfLeVS/CBy4gEGkyWHSuVb1L
626
627 > hmac_md6_64_256;5ad913855742ae5a03a5aeb7eafa4c78629de136bed6acd73eea36c9e98df44a;json
628
629 < hmac_md6_64_256;84cd590976f794914c2ca26dac3a207a57a6798b9171289c114de07cf0c20401;json
630 < ["","AnyEvent::MP::_spawn","57Cs1CggVJjzYaQp13XXg4.c","AnyEvent::MP::Global::connect",0,"anon/57Cs1CggVJjzYaQp13XXg4"]
631 ...
632
633 The shared secret in use was C<8ugxrtw6H5tKnfPWfaSr4HGhE8MoJXmzTT1BWq7sLutNcD0IbXprQlZjIbl7MBKoeklG3IEfY9GlJthC0pENzk>.
634
635 =head1 SEE ALSO
636
637 L<AnyEvent::MP>.
638
639 =head1 AUTHOR
640
641 Marc Lehmann <schmorp@schmorp.de>
642 http://home.schmorp.de/
643
644 =cut
645
646 1
647