ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/MP/Transport.pm
Revision: 1.21
Committed: Tue Aug 4 23:35:51 2009 UTC (14 years, 9 months ago) by root
Branch: MAIN
Changes since 1.20: +2 -1 lines
Log Message:
*** empty log message ***

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     use Scalar::Util;
28     use MIME::Base64 ();
29     use Storable ();
30 root 1.2 use JSON::XS ();
31 root 1.1
32 root 1.19 use Digest::MD6 ();
33     use Digest::HMAC_MD6 ();
34    
35 root 1.1 use AE ();
36     use AnyEvent::Socket ();
37     use AnyEvent::Handle ();
38 root 1.2
39 root 1.1 use base Exporter::;
40    
41     our $VERSION = '0.0';
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.7 our @FRAMINGS = qw(json storable); # the framing types we accept and send, in order of preference
118     our @AUTH_SND = qw(hmac_md6_64_256); # auth types we send
119 root 1.13 our @AUTH_RCV = (@AUTH_SND, qw(cleartext)); # auth types we accept
120 root 1.7
121     #AnyEvent::Handle::register_write_type mp_record => sub {
122     #};
123 root 1.4
124 root 1.1 sub new {
125     my ($class, %arg) = @_;
126    
127     my $self = bless \%arg, $class;
128    
129     $self->{queue} = [];
130    
131     {
132     Scalar::Util::weaken (my $self = $self);
133    
134 root 1.5 $arg{secret} = AnyEvent::MP::Base::default_secret ()
135 root 1.2 unless exists $arg{secret};
136    
137 root 1.19 my $secret = $arg{secret};
138    
139     if ($secret =~ /-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----/s) {
140     # assume TLS mode
141     $arg{tls_ctx} = {
142     sslv2 => 0,
143     sslv3 => 0,
144     tlsv1 => 1,
145     verify => 1,
146     cert => $secret,
147     ca_cert => $secret,
148     verify_require_client_cert => 1,
149     };
150     }
151    
152 root 1.1 $self->{hdl} = new AnyEvent::Handle
153 root 1.2 fh => delete $arg{fh},
154 root 1.4 autocork => 1,
155     no_delay => 1,
156 root 1.1 on_error => sub {
157     $self->error ($_[2]);
158     },
159     peername => delete $arg{peername},
160     ;
161    
162 root 1.2 my $greeting_kv = $self->{greeting} ||= {};
163 root 1.8 $greeting_kv->{"tls"} = "1.0"
164     if $arg{tls_ctx};
165 root 1.2 $greeting_kv->{provider} = "AE-$VERSION";
166 root 1.7 $greeting_kv->{peeraddr} = AnyEvent::Socket::format_hostport $self->{peerhost}, $self->{peerport};
167 root 1.1
168     # send greeting
169 root 1.12 my $lgreeting1 = "aemp;$PROTOCOL_VERSION"
170 root 1.7 . ";$AnyEvent::MP::Base::UNIQ"
171     . ";$AnyEvent::MP::Base::NODE"
172     . ";" . (join ",", @AUTH_RCV)
173     . ";" . (join ",", @FRAMINGS)
174     . (join "", map ";$_=$greeting_kv->{$_}", keys %$greeting_kv);
175 root 1.12
176 root 1.7 my $lgreeting2 = MIME::Base64::encode_base64 AnyEvent::MP::Base::nonce (33), "";
177 root 1.1
178 root 1.7 $self->{hdl}->push_write ("$lgreeting1\012$lgreeting2\012");
179 root 1.1
180     # expect greeting
181 root 1.12 $self->{hdl}->rbuf_max (4 * 1024);
182 root 1.1 $self->{hdl}->push_read (line => sub {
183 root 1.7 my $rgreeting1 = $_[1];
184 root 1.1
185 root 1.12 my ($aemp, $version, $uniq, $rnode, $auths, $framings, @kv) = split /;/, $rgreeting1;
186 root 1.1
187     if ($aemp ne "aemp") {
188     return $self->error ("unparsable greeting");
189 root 1.12 } elsif ($version != $PROTOCOL_VERSION) {
190     return $self->error ("version mismatch (we: $PROTOCOL_VERSION, they: $version)");
191 root 1.1 }
192    
193 root 1.7 my $s_auth;
194     for my $auth_ (split /,/, $auths) {
195     if (grep $auth_ eq $_, @AUTH_SND) {
196     $s_auth = $auth_;
197     last;
198     }
199     }
200    
201     defined $s_auth
202     or return $self->error ("$auths: no common auth type supported");
203    
204     die unless $s_auth eq "hmac_md6_64_256"; # hardcoded atm.
205    
206     my $s_framing;
207     for my $framing_ (split /,/, $framings) {
208     if (grep $framing_ eq $_, @FRAMINGS) {
209     $s_framing = $framing_;
210     last;
211     }
212     }
213    
214     defined $s_framing
215     or return $self->error ("$framings: no common framing method supported");
216    
217 root 1.2 $self->{remote_uniq} = $uniq;
218     $self->{remote_node} = $rnode;
219 root 1.1
220 root 1.2 $self->{remote_greeting} = {
221     map /^([^=]+)(?:=(.*))?/ ? ($1 => $2) : (),
222     @kv
223 root 1.1 };
224    
225 root 1.7 # read nonce
226     $self->{hdl}->push_read (line => sub {
227     my $rgreeting2 = $_[1];
228    
229 root 1.19 "$lgreeting1\012$lgreeting2" ne "$rgreeting1\012$rgreeting2" # echo attack?
230     or return $self->error ("authentication error, echo attack?");
231    
232     my $key = Digest::MD6::md6 $secret;
233     my $lauth;
234    
235 root 1.10 if ($self->{tls_ctx} and 1 == int $self->{remote_greeting}{tls}) {
236 root 1.8 $self->{tls} = $lgreeting2 lt $rgreeting2 ? "connect" : "accept";
237     $self->{hdl}->starttls ($self->{tls}, $self->{tls_ctx});
238 root 1.19 $s_auth = "tls";
239     $lauth = "";
240     } else {
241     # we currently only support hmac_md6_64_256
242     $lauth = Digest::HMAC_MD6::hmac_md6_hex $key, "$lgreeting1\012$lgreeting2\012$rgreeting1\012$rgreeting2\012", 64, 256;
243 root 1.8 }
244 root 1.2
245 root 1.7 $self->{hdl}->push_write ("$s_auth;$lauth;$s_framing\012");
246 root 1.2
247 root 1.19 # read the authentication response
248 root 1.7 $self->{hdl}->push_read (line => sub {
249     my ($hdl, $rline) = @_;
250 root 1.2
251 root 1.7 my ($auth_method, $rauth2, $r_framing) = split /;/, $rline;
252 root 1.1
253 root 1.19 my $rauth =
254     $auth_method eq "hmac_md6_64_256" ? Digest::HMAC_MD6::hmac_md6_hex $key, "$rgreeting1\012$rgreeting2\012$lgreeting1\012$lgreeting2\012", 64, 256
255     : $auth_method eq "cleartext" ? unpack "H*", $secret
256     : $auth_method eq "tls" ? ($self->{tls} ? "" : "\012\012") # \012\012 never matches
257     : return $self->error ("$auth_method: fatal, selected unsupported auth method");
258    
259 root 1.7 if ($rauth2 ne $rauth) {
260     return $self->error ("authentication failure/shared secret mismatch");
261     }
262 root 1.1
263 root 1.7 $self->{s_framing} = $s_framing;
264 root 1.2
265 root 1.7 $hdl->rbuf_max (undef);
266     my $queue = delete $self->{queue}; # we are connected
267 root 1.1
268 root 1.7 $self->connected;
269 root 1.1
270 root 1.12 my $src_node = $self->{node};
271    
272 root 1.7 $hdl->push_write ($self->{s_framing} => $_)
273     for @$queue;
274 root 1.1
275 root 1.7 my $rmsg; $rmsg = sub {
276     $_[0]->push_read ($r_framing => $rmsg);
277 root 1.1
278 root 1.12 local $AnyEvent::MP::Base::SRCNODE = $src_node;
279     AnyEvent::MP::Base::_inject (@{ $_[1] });
280 root 1.7 };
281     $hdl->push_read ($r_framing => $rmsg);
282     });
283 root 1.1 });
284     });
285     }
286    
287     $self
288     }
289    
290     sub error {
291     my ($self, $msg) = @_;
292    
293 root 1.4 if ($self->{node} && $self->{node}{transport} == $self) {
294 root 1.21 #TODO: store error, but do not instantly fail
295     $self->{node}->fail (transport_error => $self->{node}{noderef}, $msg);
296 root 1.4 $self->{node}->clr_transport;
297     }
298 root 1.7 $AnyEvent::MP::Base::WARN->("$self->{peerhost}:$self->{peerport}: $msg");
299 root 1.4 $self->destroy;
300 root 1.1 }
301    
302 root 1.2 sub connected {
303     my ($self) = @_;
304    
305 root 1.5 my $node = AnyEvent::MP::Base::add_node ($self->{remote_node});
306 root 1.4 Scalar::Util::weaken ($self->{node} = $node);
307     $node->set_transport ($self);
308 root 1.2 }
309    
310 root 1.1 sub send {
311 root 1.2 $_[0]{hdl}->push_write ($_[0]{s_framing} => $_[1]);
312 root 1.1 }
313    
314     sub destroy {
315     my ($self) = @_;
316    
317 root 1.2 $self->{hdl}->destroy
318     if $self->{hdl};
319 root 1.1 }
320    
321     sub DESTROY {
322     my ($self) = @_;
323    
324     $self->destroy;
325     }
326    
327     =back
328    
329 root 1.7 =head1 PROTOCOL
330    
331     The protocol is relatively simple, and consists of three phases which are
332     symmetrical for both sides: greeting (followed by optionally switching to
333     TLS mode), authentication and packet exchange.
334    
335     the protocol is designed to allow both full-text and binary streams.
336    
337     The greeting consists of two text lines that are ended by either an ASCII
338     CR LF pair, or a single ASCII LF (recommended).
339    
340     =head2 GREETING
341    
342 root 1.15 All the lines until after authentication must not exceed 4kb in length,
343     including delimiter. Afterwards there is no limit on the packet size that
344     can be received.
345    
346     =head3 First Greeting Line
347 root 1.12
348 root 1.16 Example:
349    
350     aemp;0;fec.4a7720fc;127.0.0.1:1235,[::1]:1235;hmac_md6_64_256;json,storable;provider=AE-0.0
351    
352     The first line contains strings separated (not ended) by C<;>
353     characters. The first even ixtrings are fixed by the protocol, the
354     remaining strings are C<KEY=VALUE> pairs. None of them may contain C<;>
355     characters themselves.
356    
357 root 1.12 The fixed strings are:
358 root 1.7
359     =over 4
360    
361 root 1.18 =item protocol identification
362 root 1.7
363     The constant C<aemp> to identify the protocol.
364    
365     =item protocol version
366    
367 root 1.12 The protocol version supported by this end, currently C<0>. If the
368     versions don't match then no communication is possible. Minor extensions
369 root 1.18 are supposed to be handled through additional key-value pairs.
370 root 1.7
371     =item a token uniquely identifying the current node instance
372    
373     This is a string that must change between restarts. It usually contains
374     things like the current time, the (OS) process id or similar values, but
375     no meaning of the contents are assumed.
376    
377     =item the node endpoint descriptors
378    
379     for public nodes, this is a comma-separated list of protocol endpoints,
380     i.e., the noderef. For slave nodes, this is a unique identifier.
381    
382     =item the acceptable authentication methods
383    
384     A comma-separated list of authentication methods supported by the
385     node. Note that AnyEvent::MP supports a C<hex_secret> authentication
386     method that accepts a cleartext password (hex-encoded), but will not use
387     this auth method itself.
388    
389     The receiving side should choose the first auth method it supports.
390    
391     =item the acceptable framing formats
392    
393     A comma-separated list of packet encoding/framign formats understood. The
394     receiving side should choose the first framing format it supports for
395     sending packets (which might be different from the format it has to accept).
396    
397 root 1.10 =back
398 root 1.8
399     The remaining arguments are C<KEY=VALUE> pairs. The following key-value
400     pairs are known at this time:
401    
402     =over 4
403    
404     =item provider=<module-version>
405    
406     The software provider for this implementation. For AnyEvent::MP, this is
407     C<AE-0.0> or whatever version it currently is at.
408    
409     =item peeraddr=<host>:<port>
410    
411     The peer address (socket address of the other side) as seen locally, in the same format
412     as noderef endpoints.
413    
414     =item tls=<major>.<minor>
415    
416     Indicates that the other side supports TLS (version should be 1.0) and
417     wishes to do a TLS handshake.
418    
419     =back
420    
421 root 1.15 =head3 Second Greeting Line
422    
423 root 1.8 After this greeting line there will be a second line containing a
424     cryptographic nonce, i.e. random data of high quality. To keep the
425     protocol text-only, these are usually 32 base64-encoded octets, but
426     it could be anything that doesn't contain any ASCII CR or ASCII LF
427     characters.
428    
429 root 1.14 I<< The two nonces B<must> be different, and an aemp implementation
430     B<must> check and fail when they are identical >>.
431    
432 root 1.16 Example of a nonce line:
433 root 1.8
434 root 1.12 p/I122ql7kJR8lumW3lXlXCeBnyDAvz8NQo3x5IFowE4
435 root 1.8
436     =head2 TLS handshake
437    
438 root 1.14 I<< If, after the handshake, both sides indicate interest in TLS, then the
439 root 1.20 connection B<must> use TLS, or fail. >>
440 root 1.8
441     Both sides compare their nonces, and the side who sent the lower nonce
442     value ("string" comparison on the raw octet values) becomes the client,
443     and the one with the higher nonce the server.
444    
445     =head2 AUTHENTICATION PHASE
446    
447     After the greeting is received (and the optional TLS handshake),
448     the authentication phase begins, which consists of sending a single
449     C<;>-separated line with three fixed strings and any number of
450     C<KEY=VALUE> pairs.
451    
452     The three fixed strings are:
453    
454     =over 4
455    
456     =item the authentication method chosen
457    
458     This must be one of the methods offered by the other side in the greeting.
459    
460 root 1.13 The currently supported authentication methods are:
461    
462     =over 4
463    
464     =item cleartext
465    
466     This is simply the shared secret, lowercase-hex-encoded. This method is of
467     course very insecure, unless TLS is used, which is why this module will
468     accept, but not generate, cleartext auth replies.
469    
470     =item hmac_md6_64_256
471    
472     This method uses an MD6 HMAC with 64 bit blocksize and 256 bit hash. First, the shared secret
473     is hashed with MD6:
474    
475     key = MD6 (secret)
476    
477     This secret is then used to generate the "local auth reply", by taking
478     the two local greeting lines and the two remote greeting lines (without
479     line endings), appending \012 to all of them, concatenating them and
480     calculating the MD6 HMAC with the key.
481    
482     lauth = HMAC_MD6 key, "lgreeting1\012lgreeting2\012rgreeting1\012rgreeting2\012"
483    
484     This authentication token is then lowercase-hex-encoded and sent to the
485     other side.
486    
487     Then the remote auth reply is generated using the same method, but local
488     and remote greeting lines swapped:
489    
490     rauth = HMAC_MD6 key, "rgreeting1\012rgreeting2\012lgreeting1\012lgreeting2\012"
491    
492     This is the token that is expected from the other side.
493    
494 root 1.19 =item tls
495    
496     This type is only valid iff TLS was enabled and the TLS handshake
497     was successful. It has no authentication data, as the server/client
498     certificate was successfully verified.
499    
500     Implementations supporting TLS I<must> accept this authentication type.
501    
502 root 1.13 =back
503    
504 root 1.8 =item the authentication data
505    
506 root 1.13 The authentication data itself, usually base64 or hex-encoded data, see
507     above.
508 root 1.8
509     =item the framing protocol chosen
510    
511     This must be one of the framing protocols offered by the other side in the
512     greeting. Each side must accept the choice of the other side.
513    
514     =back
515    
516 root 1.16 Example of an authentication reply:
517 root 1.9
518 root 1.13 hmac_md6_64_256;363d5175df38bd9eaddd3f6ca18aa1c0c4aa22f0da245ac638d048398c26b8d3;json
519 root 1.9
520 root 1.8 =head2 DATA PHASE
521    
522     After this, packets get exchanged using the chosen framing protocol. It is
523     quite possible that both sides use a different framing protocol.
524    
525 root 1.16 =head2 FULL EXAMPLE
526    
527 root 1.17 This is an actual protocol dump of a handshake, followed by a single data
528 root 1.16 packet. The greater than/less than lines indicate the direction of the
529     transfer only.
530    
531     > 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
532     > sRG8bbc4TDbkpvH8FTP4HBs87OhepH6VuApoZqXXskuG
533     < 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
534     < dCEUcL/LJVSTJcx8byEsOzrwhzJYOq+L3YcopA5T6EAo
535     > hmac_md6_64_256;9513d4b258975accfcb2ab7532b83690e9c119a502c612203332a591c7237788;json
536     < hmac_md6_64_256;0298d6ba2240faabb2b2e881cf86b97d70a113ca74a87dc006f9f1e9d3010f90;json
537 root 1.18 > ["","lookup","pinger","10.0.0.1:4040#nndKd+gn.a","resolved"]
538 root 1.16
539 root 1.1 =head1 SEE ALSO
540    
541     L<AnyEvent>.
542    
543     =head1 AUTHOR
544    
545     Marc Lehmann <schmorp@schmorp.de>
546     http://home.schmorp.de/
547    
548     =cut
549    
550     1
551