ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/MP/Transport.pm
(Generate patch)

Comparing AnyEvent-MP/MP/Transport.pm (file contents):
Revision 1.5 by root, Sun Aug 2 14:44:37 2009 UTC vs.
Revision 1.23 by root, Wed Aug 5 22:40:51 2009 UTC

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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines