ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/MP/Transport.pm
Revision: 1.28
Committed: Sun Aug 9 16:08:16 2009 UTC (14 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-0_4
Changes since 1.27: +1 -2 lines
Log Message:
0.4

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3 root 1.13 AnyEvent::MP::Transport - actual transport protocol handler
4 root 1.1
5     =head1 SYNOPSIS
6    
7     use AnyEvent::MP::Transport;
8    
9     =head1 DESCRIPTION
10    
11 root 1.13 This implements the actual transport protocol for MP (it represents a
12     single link), most of which is considered an implementation detail.
13 root 1.1
14 root 1.7 See the "PROTOCOL" section below if you want to write another client for
15     this protocol.
16 root 1.1
17     =head1 FUNCTIONS/METHODS
18    
19     =over 4
20    
21     =cut
22    
23     package AnyEvent::MP::Transport;
24    
25     use common::sense;
26    
27 root 1.27 use Scalar::Util ();
28     use List::Util ();
29 root 1.1 use MIME::Base64 ();
30     use Storable ();
31 root 1.2 use JSON::XS ();
32 root 1.1
33 root 1.19 use Digest::MD6 ();
34     use Digest::HMAC_MD6 ();
35    
36 root 1.1 use AE ();
37     use AnyEvent::Socket ();
38 root 1.27 use AnyEvent::Handle 4.92 ();
39 root 1.2
40 root 1.1 use base Exporter::;
41    
42 root 1.2 our $PROTOCOL_VERSION = 0;
43 root 1.1
44     =item $listener = mp_listener $host, $port, <constructor-args>, $cb->($transport)
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 root 1.10 Defaults for peerhost, peerport and fh are provided.
52 root 1.1
53     =cut
54    
55     sub mp_server($$@) {
56     my $cb = pop;
57     my ($host, $port, @args) = @_;
58    
59     AnyEvent::Socket::tcp_server $host, $port, sub {
60     my ($fh, $host, $port) = @_;
61    
62     $cb->(new AnyEvent::MP::Transport
63     fh => $fh,
64     peerhost => $host,
65     peerport => $port,
66     @args,
67     );
68     }
69     }
70    
71 root 1.2 =item $guard = mp_connect $host, $port, <constructor-args>, $cb->($transport)
72    
73     =cut
74    
75     sub mp_connect {
76     my $cb = pop;
77     my ($host, $port, @args) = @_;
78    
79     AnyEvent::Socket::tcp_connect $host, $port, sub {
80     my ($fh, $nhost, $nport) = @_;
81    
82     return $cb->() unless $fh;
83    
84     $cb->(new AnyEvent::MP::Transport
85     fh => $fh,
86     peername => $host,
87     peerhost => $nhost,
88     peerport => $nport,
89     @args,
90     );
91     }
92     }
93    
94 root 1.1 =item new AnyEvent::MP::Transport
95    
96     # immediately starts negotiation
97     my $transport = new AnyEvent::MP::Transport
98 root 1.2 # mandatory
99 root 1.1 fh => $filehandle,
100 root 1.2 local_id => $identifier,
101 root 1.1 on_recv => sub { receive-callback },
102     on_error => sub { error-callback },
103    
104     # optional
105     secret => "shared secret",
106     on_eof => sub { clean-close-callback },
107     on_connect => sub { successful-connect-callback },
108 root 1.2 greeting => { key => value },
109 root 1.1
110     # tls support
111     tls_ctx => AnyEvent::TLS,
112     peername => $peername, # for verification
113     ;
114    
115     =cut
116    
117 root 1.27 sub LATENCY() { 3 } # assumed max. network latency
118    
119 root 1.7 our @FRAMINGS = qw(json storable); # the framing types we accept and send, in order of preference
120     our @AUTH_SND = qw(hmac_md6_64_256); # auth types we send
121 root 1.13 our @AUTH_RCV = (@AUTH_SND, qw(cleartext)); # auth types we accept
122 root 1.7
123     #AnyEvent::Handle::register_write_type mp_record => sub {
124     #};
125 root 1.4
126 root 1.1 sub new {
127     my ($class, %arg) = @_;
128    
129     my $self = bless \%arg, $class;
130    
131     $self->{queue} = [];
132    
133     {
134     Scalar::Util::weaken (my $self = $self);
135    
136 root 1.5 $arg{secret} = AnyEvent::MP::Base::default_secret ()
137 root 1.2 unless exists $arg{secret};
138    
139 root 1.27 $arg{timeout} = 30
140 root 1.24 unless exists $arg{timeout};
141    
142 root 1.27 $arg{timeout} = 1 + LATENCY
143     if $arg{timeout} < 1 + LATENCY;
144    
145 root 1.19 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    
160 root 1.1 $self->{hdl} = new AnyEvent::Handle
161 root 1.2 fh => delete $arg{fh},
162 root 1.4 autocork => 1,
163     no_delay => 1,
164 root 1.1 on_error => sub {
165     $self->error ($_[2]);
166     },
167 root 1.27 rtimeout => $AnyEvent::MP::Base::CONNECT_TIMEOUT,
168 root 1.1 peername => delete $arg{peername},
169     ;
170    
171 root 1.2 my $greeting_kv = $self->{greeting} ||= {};
172 root 1.24
173     $self->{local_node} = $AnyEvent::MP::Base::NODE;
174    
175     $greeting_kv->{"tls"} = "1.0" if $arg{tls_ctx};
176 root 1.28 $greeting_kv->{provider} = "AE-$AnyEvent::MP::Base::VERSION";
177 root 1.7 $greeting_kv->{peeraddr} = AnyEvent::Socket::format_hostport $self->{peerhost}, $self->{peerport};
178 root 1.26 $greeting_kv->{timeout} = $arg{timeout};
179 root 1.23
180 root 1.1 # send greeting
181 root 1.12 my $lgreeting1 = "aemp;$PROTOCOL_VERSION"
182 root 1.24 . ";$self->{local_node}"
183 root 1.7 . ";" . (join ",", @AUTH_RCV)
184     . ";" . (join ",", @FRAMINGS)
185     . (join "", map ";$_=$greeting_kv->{$_}", keys %$greeting_kv);
186 root 1.12
187 root 1.7 my $lgreeting2 = MIME::Base64::encode_base64 AnyEvent::MP::Base::nonce (33), "";
188 root 1.1
189 root 1.7 $self->{hdl}->push_write ("$lgreeting1\012$lgreeting2\012");
190 root 1.1
191     # expect greeting
192 root 1.12 $self->{hdl}->rbuf_max (4 * 1024);
193 root 1.1 $self->{hdl}->push_read (line => sub {
194 root 1.7 my $rgreeting1 = $_[1];
195 root 1.1
196 root 1.26 my ($aemp, $version, $rnode, $auths, $framings, @kv) = split /;/, $rgreeting1;
197 root 1.1
198     if ($aemp ne "aemp") {
199     return $self->error ("unparsable greeting");
200 root 1.12 } elsif ($version != $PROTOCOL_VERSION) {
201     return $self->error ("version mismatch (we: $PROTOCOL_VERSION, they: $version)");
202 root 1.1 }
203    
204 root 1.7 my $s_auth;
205     for my $auth_ (split /,/, $auths) {
206     if (grep $auth_ eq $_, @AUTH_SND) {
207     $s_auth = $auth_;
208     last;
209     }
210     }
211    
212     defined $s_auth
213     or return $self->error ("$auths: no common auth type supported");
214    
215     die unless $s_auth eq "hmac_md6_64_256"; # hardcoded atm.
216    
217     my $s_framing;
218     for my $framing_ (split /,/, $framings) {
219     if (grep $framing_ eq $_, @FRAMINGS) {
220     $s_framing = $framing_;
221     last;
222     }
223     }
224    
225     defined $s_framing
226     or return $self->error ("$framings: no common framing method supported");
227    
228 root 1.2 $self->{remote_node} = $rnode;
229 root 1.1
230 root 1.2 $self->{remote_greeting} = {
231     map /^([^=]+)(?:=(.*))?/ ? ($1 => $2) : (),
232     @kv
233 root 1.1 };
234    
235 root 1.7 # read nonce
236     $self->{hdl}->push_read (line => sub {
237     my $rgreeting2 = $_[1];
238    
239 root 1.19 "$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    
245 root 1.10 if ($self->{tls_ctx} and 1 == int $self->{remote_greeting}{tls}) {
246 root 1.8 $self->{tls} = $lgreeting2 lt $rgreeting2 ? "connect" : "accept";
247     $self->{hdl}->starttls ($self->{tls}, $self->{tls_ctx});
248 root 1.19 $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;
253 root 1.8 }
254 root 1.2
255 root 1.7 $self->{hdl}->push_write ("$s_auth;$lauth;$s_framing\012");
256 root 1.2
257 root 1.19 # read the authentication response
258 root 1.7 $self->{hdl}->push_read (line => sub {
259     my ($hdl, $rline) = @_;
260 root 1.2
261 root 1.7 my ($auth_method, $rauth2, $r_framing) = split /;/, $rline;
262 root 1.1
263 root 1.19 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");
268    
269 root 1.7 if ($rauth2 ne $rauth) {
270     return $self->error ("authentication failure/shared secret mismatch");
271     }
272 root 1.1
273 root 1.7 $self->{s_framing} = $s_framing;
274 root 1.2
275 root 1.7 $hdl->rbuf_max (undef);
276     my $queue = delete $self->{queue}; # we are connected
277 root 1.1
278 root 1.27 $self->{hdl}->rtimeout ($self->{remote_greeting}{timeout});
279     $self->{hdl}->wtimeout ($arg{timeout} - LATENCY);
280     $self->{hdl}->on_wtimeout (sub { $self->send (["", "devnull"]) });
281 root 1.24
282 root 1.7 $self->connected;
283 root 1.1
284 root 1.27 # send queued messages
285 root 1.23 $self->send ($_)
286 root 1.7 for @$queue;
287 root 1.1
288 root 1.27 # receive handling
289     my $src_node = $self->{node};
290    
291 root 1.22 my $rmsg; $rmsg = sub {
292 root 1.7 $_[0]->push_read ($r_framing => $rmsg);
293 root 1.1
294 root 1.12 local $AnyEvent::MP::Base::SRCNODE = $src_node;
295     AnyEvent::MP::Base::_inject (@{ $_[1] });
296 root 1.7 };
297     $hdl->push_read ($r_framing => $rmsg);
298     });
299 root 1.1 });
300     });
301     }
302    
303     $self
304     }
305    
306     sub error {
307     my ($self, $msg) = @_;
308    
309 root 1.4 if ($self->{node} && $self->{node}{transport} == $self) {
310 root 1.21 #TODO: store error, but do not instantly fail
311     $self->{node}->fail (transport_error => $self->{node}{noderef}, $msg);
312 root 1.4 $self->{node}->clr_transport;
313     }
314 root 1.7 $AnyEvent::MP::Base::WARN->("$self->{peerhost}:$self->{peerport}: $msg");
315 root 1.4 $self->destroy;
316 root 1.1 }
317    
318 root 1.2 sub connected {
319     my ($self) = @_;
320    
321 root 1.23 if (ref $AnyEvent::MP::Base::SLAVE) {
322     # first connect with a master node
323 root 1.24 my $via = $self->{remote_node};
324     $via =~ s/,/!/g;
325     $AnyEvent::MP::Base::NODE .= "\@$via";
326 root 1.23 $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    
335 root 1.5 my $node = AnyEvent::MP::Base::add_node ($self->{remote_node});
336 root 1.4 Scalar::Util::weaken ($self->{node} = $node);
337     $node->set_transport ($self);
338 root 1.2 }
339    
340 root 1.1 sub send {
341 root 1.2 $_[0]{hdl}->push_write ($_[0]{s_framing} => $_[1]);
342 root 1.1 }
343    
344     sub destroy {
345     my ($self) = @_;
346    
347 root 1.2 $self->{hdl}->destroy
348     if $self->{hdl};
349 root 1.1 }
350    
351     sub DESTROY {
352     my ($self) = @_;
353    
354     $self->destroy;
355     }
356    
357     =back
358    
359 root 1.7 =head1 PROTOCOL
360    
361     The protocol is relatively simple, and consists of three phases which are
362     symmetrical for both sides: greeting (followed by optionally switching to
363     TLS mode), authentication and packet exchange.
364    
365     the protocol is designed to allow both full-text and binary streams.
366    
367     The greeting consists of two text lines that are ended by either an ASCII
368     CR LF pair, or a single ASCII LF (recommended).
369    
370     =head2 GREETING
371    
372 root 1.15 All the lines until after authentication must not exceed 4kb in length,
373     including delimiter. Afterwards there is no limit on the packet size that
374     can be received.
375    
376     =head3 First Greeting Line
377 root 1.12
378 root 1.16 Example:
379    
380     aemp;0;fec.4a7720fc;127.0.0.1:1235,[::1]:1235;hmac_md6_64_256;json,storable;provider=AE-0.0
381    
382     The first line contains strings separated (not ended) by C<;>
383     characters. The first even ixtrings are fixed by the protocol, the
384     remaining strings are C<KEY=VALUE> pairs. None of them may contain C<;>
385     characters themselves.
386    
387 root 1.12 The fixed strings are:
388 root 1.7
389     =over 4
390    
391 root 1.18 =item protocol identification
392 root 1.7
393     The constant C<aemp> to identify the protocol.
394    
395     =item protocol version
396    
397 root 1.12 The protocol version supported by this end, currently C<0>. If the
398     versions don't match then no communication is possible. Minor extensions
399 root 1.18 are supposed to be handled through additional key-value pairs.
400 root 1.7
401     =item the node endpoint descriptors
402    
403     for public nodes, this is a comma-separated list of protocol endpoints,
404 root 1.26 i.e., the noderef. For slave nodes, this is a unique identifier of the
405     form C<slave/nonce>.
406 root 1.7
407     =item the acceptable authentication methods
408    
409     A comma-separated list of authentication methods supported by the
410     node. Note that AnyEvent::MP supports a C<hex_secret> authentication
411     method that accepts a cleartext password (hex-encoded), but will not use
412     this auth method itself.
413    
414     The receiving side should choose the first auth method it supports.
415    
416     =item the acceptable framing formats
417    
418     A comma-separated list of packet encoding/framign formats understood. The
419     receiving side should choose the first framing format it supports for
420     sending packets (which might be different from the format it has to accept).
421    
422 root 1.10 =back
423 root 1.8
424     The remaining arguments are C<KEY=VALUE> pairs. The following key-value
425     pairs are known at this time:
426    
427     =over 4
428    
429     =item provider=<module-version>
430    
431     The software provider for this implementation. For AnyEvent::MP, this is
432     C<AE-0.0> or whatever version it currently is at.
433    
434     =item peeraddr=<host>:<port>
435    
436     The peer address (socket address of the other side) as seen locally, in the same format
437     as noderef endpoints.
438    
439     =item tls=<major>.<minor>
440    
441     Indicates that the other side supports TLS (version should be 1.0) and
442     wishes to do a TLS handshake.
443    
444 root 1.26 =item timeout=<seconds>
445 root 1.24
446 root 1.26 The amount of time after which this node should be detected as dead unless
447     some data has been received. The node is responsible to send traffic
448     reasonably more often than this interval (such as every timeout minus five
449     seconds).
450 root 1.24
451 root 1.8 =back
452    
453 root 1.15 =head3 Second Greeting Line
454    
455 root 1.8 After this greeting line there will be a second line containing a
456     cryptographic nonce, i.e. random data of high quality. To keep the
457     protocol text-only, these are usually 32 base64-encoded octets, but
458     it could be anything that doesn't contain any ASCII CR or ASCII LF
459     characters.
460    
461 root 1.14 I<< The two nonces B<must> be different, and an aemp implementation
462     B<must> check and fail when they are identical >>.
463    
464 root 1.16 Example of a nonce line:
465 root 1.8
466 root 1.12 p/I122ql7kJR8lumW3lXlXCeBnyDAvz8NQo3x5IFowE4
467 root 1.8
468     =head2 TLS handshake
469    
470 root 1.14 I<< If, after the handshake, both sides indicate interest in TLS, then the
471 root 1.20 connection B<must> use TLS, or fail. >>
472 root 1.8
473     Both sides compare their nonces, and the side who sent the lower nonce
474     value ("string" comparison on the raw octet values) becomes the client,
475     and the one with the higher nonce the server.
476    
477     =head2 AUTHENTICATION PHASE
478    
479     After the greeting is received (and the optional TLS handshake),
480     the authentication phase begins, which consists of sending a single
481     C<;>-separated line with three fixed strings and any number of
482     C<KEY=VALUE> pairs.
483    
484     The three fixed strings are:
485    
486     =over 4
487    
488     =item the authentication method chosen
489    
490     This must be one of the methods offered by the other side in the greeting.
491    
492 root 1.13 The currently supported authentication methods are:
493    
494     =over 4
495    
496     =item cleartext
497    
498     This is simply the shared secret, lowercase-hex-encoded. This method is of
499     course very insecure, unless TLS is used, which is why this module will
500     accept, but not generate, cleartext auth replies.
501    
502     =item hmac_md6_64_256
503    
504     This method uses an MD6 HMAC with 64 bit blocksize and 256 bit hash. First, the shared secret
505     is hashed with MD6:
506    
507     key = MD6 (secret)
508    
509     This secret is then used to generate the "local auth reply", by taking
510     the two local greeting lines and the two remote greeting lines (without
511     line endings), appending \012 to all of them, concatenating them and
512     calculating the MD6 HMAC with the key.
513    
514     lauth = HMAC_MD6 key, "lgreeting1\012lgreeting2\012rgreeting1\012rgreeting2\012"
515    
516     This authentication token is then lowercase-hex-encoded and sent to the
517     other side.
518    
519     Then the remote auth reply is generated using the same method, but local
520     and remote greeting lines swapped:
521    
522     rauth = HMAC_MD6 key, "rgreeting1\012rgreeting2\012lgreeting1\012lgreeting2\012"
523    
524     This is the token that is expected from the other side.
525    
526 root 1.19 =item tls
527    
528     This type is only valid iff TLS was enabled and the TLS handshake
529     was successful. It has no authentication data, as the server/client
530     certificate was successfully verified.
531    
532     Implementations supporting TLS I<must> accept this authentication type.
533    
534 root 1.13 =back
535    
536 root 1.8 =item the authentication data
537    
538 root 1.13 The authentication data itself, usually base64 or hex-encoded data, see
539     above.
540 root 1.8
541     =item the framing protocol chosen
542    
543     This must be one of the framing protocols offered by the other side in the
544     greeting. Each side must accept the choice of the other side.
545    
546     =back
547    
548 root 1.16 Example of an authentication reply:
549 root 1.9
550 root 1.13 hmac_md6_64_256;363d5175df38bd9eaddd3f6ca18aa1c0c4aa22f0da245ac638d048398c26b8d3;json
551 root 1.9
552 root 1.8 =head2 DATA PHASE
553    
554     After this, packets get exchanged using the chosen framing protocol. It is
555     quite possible that both sides use a different framing protocol.
556    
557 root 1.16 =head2 FULL EXAMPLE
558    
559 root 1.17 This is an actual protocol dump of a handshake, followed by a single data
560 root 1.16 packet. The greater than/less than lines indicate the direction of the
561     transfer 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 root 1.18 > ["","lookup","pinger","10.0.0.1:4040#nndKd+gn.a","resolved"]
570 root 1.16
571 root 1.1 =head1 SEE ALSO
572    
573     L<AnyEvent>.
574    
575     =head1 AUTHOR
576    
577     Marc Lehmann <schmorp@schmorp.de>
578     http://home.schmorp.de/
579    
580     =cut
581    
582     1
583