ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/MP/Transport.pm
Revision: 1.14
Committed: Tue Aug 4 07:46:33 2009 UTC (14 years, 9 months ago) by root
Branch: MAIN
Changes since 1.13: +5 -2 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 AE ();
33 use AnyEvent::Socket ();
34 use AnyEvent::Handle ();
35
36 use base Exporter::;
37
38 our $VERSION = '0.0';
39 our $PROTOCOL_VERSION = 0;
40
41 =item $listener = mp_listener $host, $port, <constructor-args>, $cb->($transport)
42
43 Creates a listener on the given host/port using
44 C<AnyEvent::Socket::tcp_server>.
45
46 See C<new>, below, for constructor arguments.
47
48 Defaults for peerhost, peerport and fh are provided.
49
50 =cut
51
52 sub mp_server($$@) {
53 my $cb = pop;
54 my ($host, $port, @args) = @_;
55
56 AnyEvent::Socket::tcp_server $host, $port, sub {
57 my ($fh, $host, $port) = @_;
58
59 $cb->(new AnyEvent::MP::Transport
60 fh => $fh,
61 peerhost => $host,
62 peerport => $port,
63 @args,
64 );
65 }
66 }
67
68 =item $guard = mp_connect $host, $port, <constructor-args>, $cb->($transport)
69
70 =cut
71
72 sub mp_connect {
73 my $cb = pop;
74 my ($host, $port, @args) = @_;
75
76 AnyEvent::Socket::tcp_connect $host, $port, sub {
77 my ($fh, $nhost, $nport) = @_;
78
79 return $cb->() unless $fh;
80
81 $cb->(new AnyEvent::MP::Transport
82 fh => $fh,
83 peername => $host,
84 peerhost => $nhost,
85 peerport => $nport,
86 @args,
87 );
88 }
89 }
90
91 =item new AnyEvent::MP::Transport
92
93 # immediately starts negotiation
94 my $transport = new AnyEvent::MP::Transport
95 # mandatory
96 fh => $filehandle,
97 local_id => $identifier,
98 on_recv => sub { receive-callback },
99 on_error => sub { error-callback },
100
101 # optional
102 secret => "shared secret",
103 on_eof => sub { clean-close-callback },
104 on_connect => sub { successful-connect-callback },
105 greeting => { key => value },
106
107 # tls support
108 tls_ctx => AnyEvent::TLS,
109 peername => $peername, # for verification
110 ;
111
112 =cut
113
114 our @FRAMINGS = qw(json storable); # the framing types we accept and send, in order of preference
115 our @AUTH_SND = qw(hmac_md6_64_256); # auth types we send
116 our @AUTH_RCV = (@AUTH_SND, qw(cleartext)); # auth types we accept
117
118 #AnyEvent::Handle::register_write_type mp_record => sub {
119 #};
120
121 sub new {
122 my ($class, %arg) = @_;
123
124 my $self = bless \%arg, $class;
125
126 $self->{queue} = [];
127
128 {
129 Scalar::Util::weaken (my $self = $self);
130
131 $arg{tls_ctx_disabled} ||= {
132 sslv2 => 0,
133 sslv3 => 0,
134 tlsv1 => 1,
135 verify => 1,
136 cert_file => "secret.pem",
137 ca_file => "secret.pem",
138 verify_require_client_cert => 1,
139 };
140
141 $arg{secret} = AnyEvent::MP::Base::default_secret ()
142 unless exists $arg{secret};
143
144 $self->{hdl} = new AnyEvent::Handle
145 fh => delete $arg{fh},
146 autocork => 1,
147 no_delay => 1,
148 on_error => sub {
149 $self->error ($_[2]);
150 },
151 peername => delete $arg{peername},
152 ;
153
154 my $secret = $arg{secret};
155 my $greeting_kv = $self->{greeting} ||= {};
156 $greeting_kv->{"tls"} = "1.0"
157 if $arg{tls_ctx};
158 $greeting_kv->{provider} = "AE-$VERSION";
159 $greeting_kv->{peeraddr} = AnyEvent::Socket::format_hostport $self->{peerhost}, $self->{peerport};
160
161 # send greeting
162 my $lgreeting1 = "aemp;$PROTOCOL_VERSION"
163 . ";$AnyEvent::MP::Base::UNIQ"
164 . ";$AnyEvent::MP::Base::NODE"
165 . ";" . (join ",", @AUTH_RCV)
166 . ";" . (join ",", @FRAMINGS)
167 . (join "", map ";$_=$greeting_kv->{$_}", keys %$greeting_kv);
168
169 my $lgreeting2 = MIME::Base64::encode_base64 AnyEvent::MP::Base::nonce (33), "";
170
171 $self->{hdl}->push_write ("$lgreeting1\012$lgreeting2\012");
172
173 # expect greeting
174 $self->{hdl}->rbuf_max (4 * 1024);
175 $self->{hdl}->push_read (line => sub {
176 my $rgreeting1 = $_[1];
177
178 my ($aemp, $version, $uniq, $rnode, $auths, $framings, @kv) = split /;/, $rgreeting1;
179
180 if ($aemp ne "aemp") {
181 return $self->error ("unparsable greeting");
182 } elsif ($version != $PROTOCOL_VERSION) {
183 return $self->error ("version mismatch (we: $PROTOCOL_VERSION, they: $version)");
184 }
185
186 my $s_auth;
187 for my $auth_ (split /,/, $auths) {
188 if (grep $auth_ eq $_, @AUTH_SND) {
189 $s_auth = $auth_;
190 last;
191 }
192 }
193
194 defined $s_auth
195 or return $self->error ("$auths: no common auth type supported");
196
197 die unless $s_auth eq "hmac_md6_64_256"; # hardcoded atm.
198
199 my $s_framing;
200 for my $framing_ (split /,/, $framings) {
201 if (grep $framing_ eq $_, @FRAMINGS) {
202 $s_framing = $framing_;
203 last;
204 }
205 }
206
207 defined $s_framing
208 or return $self->error ("$framings: no common framing method supported");
209
210 $self->{remote_uniq} = $uniq;
211 $self->{remote_node} = $rnode;
212
213 $self->{remote_greeting} = {
214 map /^([^=]+)(?:=(.*))?/ ? ($1 => $2) : (),
215 @kv
216 };
217
218 # read nonce
219 $self->{hdl}->push_read (line => sub {
220 my $rgreeting2 = $_[1];
221
222 if ($self->{tls_ctx} and 1 == int $self->{remote_greeting}{tls}) {
223 $self->{tls} = $lgreeting2 lt $rgreeting2 ? "connect" : "accept";
224 $self->{hdl}->starttls ($self->{tls}, $self->{tls_ctx});
225 }
226
227 # auth
228 require Digest::MD6;
229 require Digest::HMAC_MD6;
230
231 my $key = Digest::MD6::md6 ($secret);
232 my $lauth = Digest::HMAC_MD6::hmac_md6_hex ($key, "$lgreeting1\012$lgreeting2\012$rgreeting1\012$rgreeting2\012", 64, 256);
233
234 my $rauth =
235 $s_auth eq "hmac_md6_64_256" ? Digest::HMAC_MD6::hmac_md6_hex ($key, "$rgreeting1\012$rgreeting2\012$lgreeting1\012$lgreeting2\012", 64, 256)
236 : $s_auth eq "cleartext" ? unpack "H*", $secret
237 : die;
238
239 $lauth ne $rauth # echo attack?
240 or return $self->error ("authentication error");
241
242 $self->{hdl}->push_write ("$s_auth;$lauth;$s_framing\012");
243
244 # reasd the authentication response
245 $self->{hdl}->push_read (line => sub {
246 my ($hdl, $rline) = @_;
247
248 my ($auth_method, $rauth2, $r_framing) = split /;/, $rline;
249
250 if ($rauth2 ne $rauth) {
251 return $self->error ("authentication failure/shared secret mismatch");
252 }
253
254 $self->{s_framing} = $s_framing;
255
256 $hdl->rbuf_max (undef);
257 my $queue = delete $self->{queue}; # we are connected
258
259 $self->connected;
260
261 my $src_node = $self->{node};
262
263 $hdl->push_write ($self->{s_framing} => $_)
264 for @$queue;
265
266 my $rmsg; $rmsg = sub {
267 $_[0]->push_read ($r_framing => $rmsg);
268
269 local $AnyEvent::MP::Base::SRCNODE = $src_node;
270 AnyEvent::MP::Base::_inject (@{ $_[1] });
271 };
272 $hdl->push_read ($r_framing => $rmsg);
273 });
274 });
275 });
276 }
277
278 $self
279 }
280
281 sub error {
282 my ($self, $msg) = @_;
283
284 if ($self->{node} && $self->{node}{transport} == $self) {
285 $self->{node}->clr_transport;
286 }
287 $AnyEvent::MP::Base::WARN->("$self->{peerhost}:$self->{peerport}: $msg");
288 $self->destroy;
289 }
290
291 sub connected {
292 my ($self) = @_;
293
294 my $node = AnyEvent::MP::Base::add_node ($self->{remote_node});
295 Scalar::Util::weaken ($self->{node} = $node);
296 $node->set_transport ($self);
297 }
298
299 sub send {
300 $_[0]{hdl}->push_write ($_[0]{s_framing} => $_[1]);
301 }
302
303 sub destroy {
304 my ($self) = @_;
305
306 $self->{hdl}->destroy
307 if $self->{hdl};
308 }
309
310 sub DESTROY {
311 my ($self) = @_;
312
313 $self->destroy;
314 }
315
316 =back
317
318 =head1 PROTOCOL
319
320 The protocol is relatively simple, and consists of three phases which are
321 symmetrical for both sides: greeting (followed by optionally switching to
322 TLS mode), authentication and packet exchange.
323
324 the protocol is designed to allow both full-text and binary streams.
325
326 The greeting consists of two text lines that are ended by either an ASCII
327 CR LF pair, or a single ASCII LF (recommended).
328
329 =head2 GREETING
330
331 The first line contains strings separated (not ended) by C<;>
332 characters. The first even ixtrings are fixed by the protocol, the
333 remaining strings are C<KEY=VALUE> pairs. None of them may contain C<;>
334 characters themselves.
335
336 All the lines until after authentication must not exceed 4kb in length, including delimiter.
337
338 The fixed strings are:
339
340 =over 4
341
342 =item C<aemp>
343
344 The constant C<aemp> to identify the protocol.
345
346 =item protocol version
347
348 The protocol version supported by this end, currently C<0>. If the
349 versions don't match then no communication is possible. Minor extensions
350 are supposed to be handled by addign additional key-value pairs.
351
352 =item a token uniquely identifying the current node instance
353
354 This is a string that must change between restarts. It usually contains
355 things like the current time, the (OS) process id or similar values, but
356 no meaning of the contents are assumed.
357
358 =item the node endpoint descriptors
359
360 for public nodes, this is a comma-separated list of protocol endpoints,
361 i.e., the noderef. For slave nodes, this is a unique identifier.
362
363 =item the acceptable authentication methods
364
365 A comma-separated list of authentication methods supported by the
366 node. Note that AnyEvent::MP supports a C<hex_secret> authentication
367 method that accepts a cleartext password (hex-encoded), but will not use
368 this auth method itself.
369
370 The receiving side should choose the first auth method it supports.
371
372 =item the acceptable framing formats
373
374 A comma-separated list of packet encoding/framign formats understood. The
375 receiving side should choose the first framing format it supports for
376 sending packets (which might be different from the format it has to accept).
377
378 =back
379
380 The remaining arguments are C<KEY=VALUE> pairs. The following key-value
381 pairs are known at this time:
382
383 =over 4
384
385 =item provider=<module-version>
386
387 The software provider for this implementation. For AnyEvent::MP, this is
388 C<AE-0.0> or whatever version it currently is at.
389
390 =item peeraddr=<host>:<port>
391
392 The peer address (socket address of the other side) as seen locally, in the same format
393 as noderef endpoints.
394
395 =item tls=<major>.<minor>
396
397 Indicates that the other side supports TLS (version should be 1.0) and
398 wishes to do a TLS handshake.
399
400 =back
401
402 After this greeting line there will be a second line containing a
403 cryptographic nonce, i.e. random data of high quality. To keep the
404 protocol text-only, these are usually 32 base64-encoded octets, but
405 it could be anything that doesn't contain any ASCII CR or ASCII LF
406 characters.
407
408 I<< The two nonces B<must> be different, and an aemp implementation
409 B<must> check and fail when they are identical >>.
410
411 Example of the two lines of greeting:
412
413 aemp;0;fec.4a7720fc;127.0.0.1:1235,[::1]:1235;hmac_md6_64_256;json,storable;provider=AE-0.0
414 p/I122ql7kJR8lumW3lXlXCeBnyDAvz8NQo3x5IFowE4
415
416 =head2 TLS handshake
417
418 I<< If, after the handshake, both sides indicate interest in TLS, then the
419 connection B<must> use TLS, or fail.>>
420
421 Both sides compare their nonces, and the side who sent the lower nonce
422 value ("string" comparison on the raw octet values) becomes the client,
423 and the one with the higher nonce the server.
424
425 =head2 AUTHENTICATION PHASE
426
427 After the greeting is received (and the optional TLS handshake),
428 the authentication phase begins, which consists of sending a single
429 C<;>-separated line with three fixed strings and any number of
430 C<KEY=VALUE> pairs.
431
432 The three fixed strings are:
433
434 =over 4
435
436 =item the authentication method chosen
437
438 This must be one of the methods offered by the other side in the greeting.
439
440 The currently supported authentication methods are:
441
442 =over 4
443
444 =item cleartext
445
446 This is simply the shared secret, lowercase-hex-encoded. This method is of
447 course very insecure, unless TLS is used, which is why this module will
448 accept, but not generate, cleartext auth replies.
449
450 =item hmac_md6_64_256
451
452 This method uses an MD6 HMAC with 64 bit blocksize and 256 bit hash. First, the shared secret
453 is hashed with MD6:
454
455 key = MD6 (secret)
456
457 This secret is then used to generate the "local auth reply", by taking
458 the two local greeting lines and the two remote greeting lines (without
459 line endings), appending \012 to all of them, concatenating them and
460 calculating the MD6 HMAC with the key.
461
462 lauth = HMAC_MD6 key, "lgreeting1\012lgreeting2\012rgreeting1\012rgreeting2\012"
463
464 This authentication token is then lowercase-hex-encoded and sent to the
465 other side.
466
467 Then the remote auth reply is generated using the same method, but local
468 and remote greeting lines swapped:
469
470 rauth = HMAC_MD6 key, "rgreeting1\012rgreeting2\012lgreeting1\012lgreeting2\012"
471
472 This is the token that is expected from the other side.
473
474 =back
475
476 =item the authentication data
477
478 The authentication data itself, usually base64 or hex-encoded data, see
479 above.
480
481 =item the framing protocol chosen
482
483 This must be one of the framing protocols offered by the other side in the
484 greeting. Each side must accept the choice of the other side.
485
486 =back
487
488 Example:
489
490 hmac_md6_64_256;363d5175df38bd9eaddd3f6ca18aa1c0c4aa22f0da245ac638d048398c26b8d3;json
491
492 =head2 DATA PHASE
493
494 After this, packets get exchanged using the chosen framing protocol. It is
495 quite possible that both sides use a different framing protocol.
496
497 =head1 SEE ALSO
498
499 L<AnyEvent>.
500
501 =head1 AUTHOR
502
503 Marc Lehmann <schmorp@schmorp.de>
504 http://home.schmorp.de/
505
506 =cut
507
508 1
509