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