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.21 by root, Tue Aug 4 23:35: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};
154 167
155 # send greeting 168 # send greeting
156 my $lgreeting = "aemp;$PROTOCOL_VERSION;$PROTOCOL_VERSION" # version, min 169 my $lgreeting1 = "aemp;$PROTOCOL_VERSION"
157 . ";$AnyEvent::MP::Base::UNIQ" 170 . ";$AnyEvent::MP::Base::UNIQ"
158 . ";$AnyEvent::MP::Base::NODE" 171 . ";$AnyEvent::MP::Base::NODE"
159 . ";" . (MIME::Base64::encode_base64 AnyEvent::MP::Base::nonce (33), "") 172 . ";" . (join ",", @AUTH_RCV)
160 . ";hmac_md6_64_256" # hardcoded atm. 173 . ";" . (join ",", @FRAMINGS)
161 . ";json" # hardcoded atm.
162 . ";$self->{peerhost};$self->{peerport}"
163 . (join "", map ";$_=$greeting_kv->{$_}", keys %$greeting_kv); 174 . (join "", map ";$_=$greeting_kv->{$_}", keys %$greeting_kv);
164 175
176 my $lgreeting2 = MIME::Base64::encode_base64 AnyEvent::MP::Base::nonce (33), "";
177
165 $self->{hdl}->push_write ("$lgreeting\012"); 178 $self->{hdl}->push_write ("$lgreeting1\012$lgreeting2\012");
166 179
167 # expect greeting 180 # expect greeting
181 $self->{hdl}->rbuf_max (4 * 1024);
168 $self->{hdl}->push_read (line => sub { 182 $self->{hdl}->push_read (line => sub {
169 my $rgreeting = $_[1]; 183 my $rgreeting1 = $_[1];
170 184
171 my ($aemp, $version, $version_min, $uniq, $rnode, undef, $auth, $framing, $peerport, $peerhost, @kv) = split /;/, $rgreeting; 185 my ($aemp, $version, $uniq, $rnode, $auths, $framings, @kv) = split /;/, $rgreeting1;
172 186
173 if ($aemp ne "aemp") { 187 if ($aemp ne "aemp") {
174 return $self->error ("unparsable greeting"); 188 return $self->error ("unparsable greeting");
175 } elsif ($version_min > $PROTOCOL_VERSION) { 189 } elsif ($version != $PROTOCOL_VERSION) {
176 return $self->error ("version mismatch (we: $PROTOCOL_VERSION, they: $version_min .. $version)"); 190 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 } 191 }
192
193 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");
182 216
183 $self->{remote_uniq} = $uniq; 217 $self->{remote_uniq} = $uniq;
184 $self->{remote_node} = $rnode; 218 $self->{remote_node} = $rnode;
185 219
186 $self->{remote_greeting} = { 220 $self->{remote_greeting} = {
187 map /^([^=]+)(?:=(.*))?/ ? ($1 => $2) : (), 221 map /^([^=]+)(?:=(.*))?/ ? ($1 => $2) : (),
188 @kv 222 @kv
189 }; 223 };
190 224
191 if (exists $self->{tls} and $self->{tls_ctx} and exists $self->{remote_greeting}{"tls1.0"}) { 225 # read nonce
226 $self->{hdl}->push_read (line => sub {
227 my $rgreeting2 = $_[1];
228
229 "$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
192 if ($self->{tls} ne $self->{remote_greeting}{"tls1.0"}) { 235 if ($self->{tls_ctx} and 1 == int $self->{remote_greeting}{tls}) {
193 return $self->error ("TLS server/client mismatch"); 236 $self->{tls} = $lgreeting2 lt $rgreeting2 ? "connect" : "accept";
237 $self->{hdl}->starttls ($self->{tls}, $self->{tls_ctx});
238 $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;
194 } 243 }
195 $self->{hdl}->starttls ($self->{tls}, $self->{tls_ctx});
196 }
197 244
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"); 245 $self->{hdl}->push_write ("$s_auth;$lauth;$s_framing\012");
210 246
211 $self->{hdl}->rbuf_max (64); # enough for 44 reply bytes or so 247 # read the authentication response
212 $self->{hdl}->push_read (line => sub { 248 $self->{hdl}->push_read (line => sub {
213 my ($hdl, $rline) = @_; 249 my ($hdl, $rline) = @_;
214 250
215 my ($auth_method, $rauth2, $r_framing) = split /;/, $rline; 251 my ($auth_method, $rauth2, $r_framing) = split /;/, $rline;
216 252
253 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
217 if ($rauth2 ne $rauth) { 259 if ($rauth2 ne $rauth) {
218 return $self->error ("authentication failure/shared secret mismatch"); 260 return $self->error ("authentication failure/shared secret mismatch");
219 } 261 }
220 262
221 $self->{s_framing} = "json";#d# 263 $self->{s_framing} = $s_framing;
222 264
223 $hdl->rbuf_max (undef); 265 $hdl->rbuf_max (undef);
224 my $queue = delete $self->{queue}; # we are connected 266 my $queue = delete $self->{queue}; # we are connected
225 267
226 $self->connected; 268 $self->connected;
227 269
270 my $src_node = $self->{node};
271
228 $hdl->push_write ($self->{s_framing} => $_) 272 $hdl->push_write ($self->{s_framing} => $_)
229 for @$queue; 273 for @$queue;
230 274
231 my $rmsg; $rmsg = sub { 275 my $rmsg; $rmsg = sub {
232 $_[0]->push_read ($r_framing => $rmsg); 276 $_[0]->push_read ($r_framing => $rmsg);
233 277
278 local $AnyEvent::MP::Base::SRCNODE = $src_node;
234 AnyEvent::MP::Base::_inject ($_[1]); 279 AnyEvent::MP::Base::_inject (@{ $_[1] });
235 }; 280 };
236 $hdl->push_read ($r_framing => $rmsg); 281 $hdl->push_read ($r_framing => $rmsg);
282 });
237 }); 283 });
238 }); 284 });
239 } 285 }
240 286
241 $self 287 $self
243 289
244sub error { 290sub error {
245 my ($self, $msg) = @_; 291 my ($self, $msg) = @_;
246 292
247 if ($self->{node} && $self->{node}{transport} == $self) { 293 if ($self->{node} && $self->{node}{transport} == $self) {
294 #TODO: store error, but do not instantly fail
295 $self->{node}->fail (transport_error => $self->{node}{noderef}, $msg);
248 $self->{node}->clr_transport; 296 $self->{node}->clr_transport;
249 } 297 }
250# $self->{on_error}($self, $msg); 298 $AnyEvent::MP::Base::WARN->("$self->{peerhost}:$self->{peerport}: $msg");
251 $self->destroy; 299 $self->destroy;
252} 300}
253 301
254sub connected { 302sub connected {
255 my ($self) = @_; 303 my ($self) = @_;
276 $self->destroy; 324 $self->destroy;
277} 325}
278 326
279=back 327=back
280 328
329=head1 PROTOCOL
330
331The protocol is relatively simple, and consists of three phases which are
332symmetrical for both sides: greeting (followed by optionally switching to
333TLS mode), authentication and packet exchange.
334
335the protocol is designed to allow both full-text and binary streams.
336
337The greeting consists of two text lines that are ended by either an ASCII
338CR LF pair, or a single ASCII LF (recommended).
339
340=head2 GREETING
341
342All the lines until after authentication must not exceed 4kb in length,
343including delimiter. Afterwards there is no limit on the packet size that
344can be received.
345
346=head3 First Greeting Line
347
348Example:
349
350 aemp;0;fec.4a7720fc;127.0.0.1:1235,[::1]:1235;hmac_md6_64_256;json,storable;provider=AE-0.0
351
352The first line contains strings separated (not ended) by C<;>
353characters. The first even ixtrings are fixed by the protocol, the
354remaining strings are C<KEY=VALUE> pairs. None of them may contain C<;>
355characters themselves.
356
357The fixed strings are:
358
359=over 4
360
361=item protocol identification
362
363The constant C<aemp> to identify the protocol.
364
365=item protocol version
366
367The protocol version supported by this end, currently C<0>. If the
368versions don't match then no communication is possible. Minor extensions
369are supposed to be handled through additional key-value pairs.
370
371=item a token uniquely identifying the current node instance
372
373This is a string that must change between restarts. It usually contains
374things like the current time, the (OS) process id or similar values, but
375no meaning of the contents are assumed.
376
377=item the node endpoint descriptors
378
379for public nodes, this is a comma-separated list of protocol endpoints,
380i.e., the noderef. For slave nodes, this is a unique identifier.
381
382=item the acceptable authentication methods
383
384A comma-separated list of authentication methods supported by the
385node. Note that AnyEvent::MP supports a C<hex_secret> authentication
386method that accepts a cleartext password (hex-encoded), but will not use
387this auth method itself.
388
389The receiving side should choose the first auth method it supports.
390
391=item the acceptable framing formats
392
393A comma-separated list of packet encoding/framign formats understood. The
394receiving side should choose the first framing format it supports for
395sending packets (which might be different from the format it has to accept).
396
397=back
398
399The remaining arguments are C<KEY=VALUE> pairs. The following key-value
400pairs are known at this time:
401
402=over 4
403
404=item provider=<module-version>
405
406The software provider for this implementation. For AnyEvent::MP, this is
407C<AE-0.0> or whatever version it currently is at.
408
409=item peeraddr=<host>:<port>
410
411The peer address (socket address of the other side) as seen locally, in the same format
412as noderef endpoints.
413
414=item tls=<major>.<minor>
415
416Indicates that the other side supports TLS (version should be 1.0) and
417wishes to do a TLS handshake.
418
419=back
420
421=head3 Second Greeting Line
422
423After this greeting line there will be a second line containing a
424cryptographic nonce, i.e. random data of high quality. To keep the
425protocol text-only, these are usually 32 base64-encoded octets, but
426it could be anything that doesn't contain any ASCII CR or ASCII LF
427characters.
428
429I<< The two nonces B<must> be different, and an aemp implementation
430B<must> check and fail when they are identical >>.
431
432Example of a nonce line:
433
434 p/I122ql7kJR8lumW3lXlXCeBnyDAvz8NQo3x5IFowE4
435
436=head2 TLS handshake
437
438I<< If, after the handshake, both sides indicate interest in TLS, then the
439connection B<must> use TLS, or fail. >>
440
441Both sides compare their nonces, and the side who sent the lower nonce
442value ("string" comparison on the raw octet values) becomes the client,
443and the one with the higher nonce the server.
444
445=head2 AUTHENTICATION PHASE
446
447After the greeting is received (and the optional TLS handshake),
448the authentication phase begins, which consists of sending a single
449C<;>-separated line with three fixed strings and any number of
450C<KEY=VALUE> pairs.
451
452The three fixed strings are:
453
454=over 4
455
456=item the authentication method chosen
457
458This must be one of the methods offered by the other side in the greeting.
459
460The currently supported authentication methods are:
461
462=over 4
463
464=item cleartext
465
466This is simply the shared secret, lowercase-hex-encoded. This method is of
467course very insecure, unless TLS is used, which is why this module will
468accept, but not generate, cleartext auth replies.
469
470=item hmac_md6_64_256
471
472This method uses an MD6 HMAC with 64 bit blocksize and 256 bit hash. First, the shared secret
473is hashed with MD6:
474
475 key = MD6 (secret)
476
477This secret is then used to generate the "local auth reply", by taking
478the two local greeting lines and the two remote greeting lines (without
479line endings), appending \012 to all of them, concatenating them and
480calculating the MD6 HMAC with the key.
481
482 lauth = HMAC_MD6 key, "lgreeting1\012lgreeting2\012rgreeting1\012rgreeting2\012"
483
484This authentication token is then lowercase-hex-encoded and sent to the
485other side.
486
487Then the remote auth reply is generated using the same method, but local
488and remote greeting lines swapped:
489
490 rauth = HMAC_MD6 key, "rgreeting1\012rgreeting2\012lgreeting1\012lgreeting2\012"
491
492This is the token that is expected from the other side.
493
494=item tls
495
496This type is only valid iff TLS was enabled and the TLS handshake
497was successful. It has no authentication data, as the server/client
498certificate was successfully verified.
499
500Implementations supporting TLS I<must> accept this authentication type.
501
502=back
503
504=item the authentication data
505
506The authentication data itself, usually base64 or hex-encoded data, see
507above.
508
509=item the framing protocol chosen
510
511This must be one of the framing protocols offered by the other side in the
512greeting. Each side must accept the choice of the other side.
513
514=back
515
516Example of an authentication reply:
517
518 hmac_md6_64_256;363d5175df38bd9eaddd3f6ca18aa1c0c4aa22f0da245ac638d048398c26b8d3;json
519
520=head2 DATA PHASE
521
522After this, packets get exchanged using the chosen framing protocol. It is
523quite possible that both sides use a different framing protocol.
524
525=head2 FULL EXAMPLE
526
527This is an actual protocol dump of a handshake, followed by a single data
528packet. The greater than/less than lines indicate the direction of the
529transfer 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 > ["","lookup","pinger","10.0.0.1:4040#nndKd+gn.a","resolved"]
538
281=head1 SEE ALSO 539=head1 SEE ALSO
282 540
283L<AnyEvent>. 541L<AnyEvent>.
284 542
285=head1 AUTHOR 543=head1 AUTHOR

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines