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, 10 months ago) by root
Branch: MAIN
CVS Tags: rel-0_4
Changes since 1.27: +1 -2 lines
Log Message:
0.4

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 implements the actual transport protocol for MP (it represents a
12 single link), most of which is considered an implementation detail.
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 base Exporter::;
41
42 our $PROTOCOL_VERSION = 0;
43
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 Defaults for peerhost, peerport and fh are provided.
52
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 =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 =item new AnyEvent::MP::Transport
95
96 # immediately starts negotiation
97 my $transport = new AnyEvent::MP::Transport
98 # mandatory
99 fh => $filehandle,
100 local_id => $identifier,
101 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 greeting => { key => value },
109
110 # tls support
111 tls_ctx => AnyEvent::TLS,
112 peername => $peername, # for verification
113 ;
114
115 =cut
116
117 sub LATENCY() { 3 } # assumed max. network latency
118
119 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 our @AUTH_RCV = (@AUTH_SND, qw(cleartext)); # auth types we accept
122
123 #AnyEvent::Handle::register_write_type mp_record => sub {
124 #};
125
126 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 $arg{secret} = AnyEvent::MP::Base::default_secret ()
137 unless exists $arg{secret};
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
160 $self->{hdl} = new AnyEvent::Handle
161 fh => delete $arg{fh},
162 autocork => 1,
163 no_delay => 1,
164 on_error => sub {
165 $self->error ($_[2]);
166 },
167 rtimeout => $AnyEvent::MP::Base::CONNECT_TIMEOUT,
168 peername => delete $arg{peername},
169 ;
170
171 my $greeting_kv = $self->{greeting} ||= {};
172
173 $self->{local_node} = $AnyEvent::MP::Base::NODE;
174
175 $greeting_kv->{"tls"} = "1.0" if $arg{tls_ctx};
176 $greeting_kv->{provider} = "AE-$AnyEvent::MP::Base::VERSION";
177 $greeting_kv->{peeraddr} = AnyEvent::Socket::format_hostport $self->{peerhost}, $self->{peerport};
178 $greeting_kv->{timeout} = $arg{timeout};
179
180 # send greeting
181 my $lgreeting1 = "aemp;$PROTOCOL_VERSION"
182 . ";$self->{local_node}"
183 . ";" . (join ",", @AUTH_RCV)
184 . ";" . (join ",", @FRAMINGS)
185 . (join "", map ";$_=$greeting_kv->{$_}", keys %$greeting_kv);
186
187 my $lgreeting2 = MIME::Base64::encode_base64 AnyEvent::MP::Base::nonce (33), "";
188
189 $self->{hdl}->push_write ("$lgreeting1\012$lgreeting2\012");
190
191 # expect greeting
192 $self->{hdl}->rbuf_max (4 * 1024);
193 $self->{hdl}->push_read (line => sub {
194 my $rgreeting1 = $_[1];
195
196 my ($aemp, $version, $rnode, $auths, $framings, @kv) = split /;/, $rgreeting1;
197
198 if ($aemp ne "aemp") {
199 return $self->error ("unparsable greeting");
200 } elsif ($version != $PROTOCOL_VERSION) {
201 return $self->error ("version mismatch (we: $PROTOCOL_VERSION, they: $version)");
202 }
203
204 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 $self->{remote_node} = $rnode;
229
230 $self->{remote_greeting} = {
231 map /^([^=]+)(?:=(.*))?/ ? ($1 => $2) : (),
232 @kv
233 };
234
235 # read nonce
236 $self->{hdl}->push_read (line => sub {
237 my $rgreeting2 = $_[1];
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
245 if ($self->{tls_ctx} and 1 == int $self->{remote_greeting}{tls}) {
246 $self->{tls} = $lgreeting2 lt $rgreeting2 ? "connect" : "accept";
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;
253 }
254
255 $self->{hdl}->push_write ("$s_auth;$lauth;$s_framing\012");
256
257 # read the authentication response
258 $self->{hdl}->push_read (line => sub {
259 my ($hdl, $rline) = @_;
260
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");
268
269 if ($rauth2 ne $rauth) {
270 return $self->error ("authentication failure/shared secret mismatch");
271 }
272
273 $self->{s_framing} = $s_framing;
274
275 $hdl->rbuf_max (undef);
276 my $queue = delete $self->{queue}; # we are connected
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
282 $self->connected;
283
284 # send queued messages
285 $self->send ($_)
286 for @$queue;
287
288 # receive handling
289 my $src_node = $self->{node};
290
291 my $rmsg; $rmsg = sub {
292 $_[0]->push_read ($r_framing => $rmsg);
293
294 local $AnyEvent::MP::Base::SRCNODE = $src_node;
295 AnyEvent::MP::Base::_inject (@{ $_[1] });
296 };
297 $hdl->push_read ($r_framing => $rmsg);
298 });
299 });
300 });
301 }
302
303 $self
304 }
305
306 sub error {
307 my ($self, $msg) = @_;
308
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);
312 $self->{node}->clr_transport;
313 }
314 $AnyEvent::MP::Base::WARN->("$self->{peerhost}:$self->{peerport}: $msg");
315 $self->destroy;
316 }
317
318 sub connected {
319 my ($self) = @_;
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
335 my $node = AnyEvent::MP::Base::add_node ($self->{remote_node});
336 Scalar::Util::weaken ($self->{node} = $node);
337 $node->set_transport ($self);
338 }
339
340 sub send {
341 $_[0]{hdl}->push_write ($_[0]{s_framing} => $_[1]);
342 }
343
344 sub destroy {
345 my ($self) = @_;
346
347 $self->{hdl}->destroy
348 if $self->{hdl};
349 }
350
351 sub DESTROY {
352 my ($self) = @_;
353
354 $self->destroy;
355 }
356
357 =back
358
359 =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 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
378 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 The fixed strings are:
388
389 =over 4
390
391 =item protocol identification
392
393 The constant C<aemp> to identify the protocol.
394
395 =item protocol version
396
397 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 are supposed to be handled through additional key-value pairs.
400
401 =item the node endpoint descriptors
402
403 for public nodes, this is a comma-separated list of protocol endpoints,
404 i.e., the noderef. For slave nodes, this is a unique identifier of the
405 form C<slave/nonce>.
406
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 =back
423
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 =item timeout=<seconds>
445
446 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
451 =back
452
453 =head3 Second Greeting Line
454
455 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 I<< The two nonces B<must> be different, and an aemp implementation
462 B<must> check and fail when they are identical >>.
463
464 Example of a nonce line:
465
466 p/I122ql7kJR8lumW3lXlXCeBnyDAvz8NQo3x5IFowE4
467
468 =head2 TLS handshake
469
470 I<< If, after the handshake, both sides indicate interest in TLS, then the
471 connection B<must> use TLS, or fail. >>
472
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 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 =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 =back
535
536 =item the authentication data
537
538 The authentication data itself, usually base64 or hex-encoded data, see
539 above.
540
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 Example of an authentication reply:
549
550 hmac_md6_64_256;363d5175df38bd9eaddd3f6ca18aa1c0c4aa22f0da245ac638d048398c26b8d3;json
551
552 =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 =head2 FULL EXAMPLE
558
559 This is an actual protocol dump of a handshake, followed by a single data
560 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 > ["","lookup","pinger","10.0.0.1:4040#nndKd+gn.a","resolved"]
570
571 =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