File Coverage

blib/lib/Net/DRI/Transport/Socket.pm
Criterion Covered Total %
statement 27 232 11.6
branch 0 92 0.0
condition 0 65 0.0
subroutine 9 27 33.3
pod 1 13 7.6
total 37 429 8.6


line stmt bran cond sub pod time code
1             ## Domain Registry Interface, TCP/SSL Socket Transport
2             ##
3             ## Copyright (c) 2005-2013 Patrick Mevzek . All rights reserved.
4             ##
5             ## This file is part of Net::DRI
6             ##
7             ## Net::DRI is free software; you can redistribute it and/or modify
8             ## it under the terms of the GNU General Public License as published by
9             ## the Free Software Foundation; either version 2 of the License, or
10             ## (at your option) any later version.
11             ##
12             ## See the LICENSE file that comes with this distribution for more details.
13             ####################################################################################################
14              
15             package Net::DRI::Transport::Socket;
16              
17 1     1   859 use base qw(Net::DRI::Transport);
  1         2  
  1         76  
18              
19 1     1   5 use strict;
  1         1  
  1         21  
20 1     1   9 use warnings;
  1         2  
  1         21  
21              
22 1     1   5 use Time::HiRes ();
  1         2  
  1         11  
23 1     1   4 use IO::Socket::INET;
  1         2  
  1         14  
24             ## At least this version is needed, to have getline()
25 1     1   1363 use IO::Socket::SSL 0.90;
  1         42283  
  1         9  
26              
27 1     1   222 use Net::DRI::Exception;
  1         2  
  1         21  
28 1     1   4 use Net::DRI::Util;
  1         3  
  1         16  
29 1     1   5 use Net::DRI::Data::Raw;
  1         2  
  1         10  
30              
31              
32             =pod
33              
34             =head1 NAME
35              
36             Net::DRI::Transport::Socket - TCP/TLS Socket connection for Net::DRI
37              
38             =head1 DESCRIPTION
39              
40             This module implements a socket (tcp or tls) for establishing connections in Net::DRI
41              
42             =head1 METHODS
43              
44             At creation (see Net::DRI C) you pass a reference to an hash, with the following available keys:
45              
46             =head2 socktype
47              
48             ssl, tcp or udp
49              
50             =head2 ssl_key_file ssl_cert_file ssl_ca_file ssl_ca_path ssl_cipher_list ssl_version ssl_passwd_cb ssl_hostname
51              
52             if C is 'ssl', all key materials, see IO::Socket::SSL documentation for corresponding options
53              
54             =head2 ssl_verify
55              
56             see IO::Socket::SSL documentation about verify_mode (by default 0x00 here)
57              
58             =head2 ssl_verify_callback
59              
60             see IO::Socket::SSL documentation about verify_callback, it gets here as first parameter the transport object
61             then all parameter given by IO::Socket::SSL; it is explicitly verified that the subroutine returns a true value,
62             and if not the connection is aborted.
63              
64             =head2 remote_host remote_port
65              
66             hostname (or IP address) & port number of endpoint
67              
68             =head2 client_login client_password
69              
70             protocol login & password
71              
72             =head2 client_newpassword
73              
74             (optional) new password if you want to change password on login for registries handling that at connection
75              
76             =head2 protocol_connection
77              
78             Net::DRI class handling protocol connection details. (Ex: C or C)
79              
80             =head2 protocol_data
81              
82             (optional) opaque data given to protocol_connection class.
83             For EPP, a key login_service_filter may exist, whose value is a code ref. It will be given an array of services, and should give back a
84             similar array; it can be used to filter out some services from those given by the registry.
85              
86             =head2 close_after
87              
88             number of protocol commands to send to server (we will automatically close and re-open connection if needed)
89              
90             =head2 local_host
91              
92             (optional) the local address (hostname or IP) you want to use to connect
93              
94             =head1 SUPPORT
95              
96             For now, support questions should be sent to:
97              
98             Enetdri@dotandco.comE
99              
100             Please also see the SUPPORT file in the distribution.
101              
102             =head1 SEE ALSO
103              
104             http://www.dotandco.com/services/software/Net-DRI/
105              
106             =head1 AUTHOR
107              
108             Patrick Mevzek, Enetdri@dotandco.comE
109              
110             =head1 COPYRIGHT
111              
112             Copyright (c) 2005-2013 Patrick Mevzek .
113             All rights reserved.
114              
115             This program is free software; you can redistribute it and/or modify
116             it under the terms of the GNU General Public License as published by
117             the Free Software Foundation; either version 2 of the License, or
118             (at your option) any later version.
119              
120             See the LICENSE file that comes with this distribution for more details.
121              
122             =cut
123              
124             ####################################################################################################
125              
126             sub new
127             {
128 0     0 1   my ($class,$ctx,$rp)=@_;
129 0           my %opts=%$rp;
130 0           my $po=$ctx->{protocol};
131              
132 0           my %t=(message_factory => $po->factories()->{message});
133 0 0 0       Net::DRI::Exception::usererr_insufficient_parameters('protocol_connection') unless (exists($opts{protocol_connection}) && $opts{protocol_connection});
134 0           $t{pc}=$opts{protocol_connection};
135 0           Net::DRI::Util::load_module($t{pc},'transport/socket');
136 0 0         if ($t{pc}->can('transport_default'))
137             {
138 0           %opts=($t{pc}->transport_default('socket_inet'),%opts);
139             }
140              
141 0           my $self=$class->SUPER::new($ctx,\%opts); ## We are now officially a Net::DRI::Transport instance
142 0 0         $self->has_state(exists $opts{has_state}? $opts{has_state} : 1);
143 0           $self->is_sync(1);
144 0           $self->name('socket_inet');
145 0           $self->version('0.8');
146             ##delete($ctx->{protocol}); ## TODO : double check it is ok
147 0           delete($ctx->{registry});
148 0           delete($ctx->{profile});
149              
150 0 0         Net::DRI::Exception::usererr_insufficient_parameters('socktype must be defined') unless (exists($opts{socktype}));
151 0 0         Net::DRI::Exception::usererr_invalid_parameters('socktype must be ssl, tcp or udp') unless ($opts{socktype}=~m/^(ssl|tcp|udp)$/);
152 0           $t{socktype}=$opts{socktype};
153 0           $t{client_login}=$opts{client_login};
154 0           $t{client_password}=$opts{client_password};
155 0 0 0       $t{client_newpassword}=$opts{client_newpassword} if (exists($opts{client_newpassword}) && $opts{client_newpassword});
156              
157 0 0 0       $t{protocol_data}=$opts{protocol_data} if (exists($opts{protocol_data}) && $opts{protocol_data});
158 0           my @need=qw/read_data write_message/;
159 0 0         Net::DRI::Exception::usererr_invalid_parameters('protocol_connection class ('.$t{pc}.') must have: '.join(' ',@need)) if (grep { ! $t{pc}->can($_) } @need);
  0            
160              
161 0 0 0       if (exists($opts{find_remote_server}) && defined($opts{find_remote_server}) && $t{pc}->can('find_remote_server'))
      0        
162             {
163 0           ($opts{remote_host},$opts{remote_port})=$t{pc}->find_remote_server($self,$opts{find_remote_server});
164 0           $self->log_output('notice','transport',$ctx,{phase=>'opening',message=>'Found the following remote_host:remote_port = '.$opts{remote_host}.':'.$opts{remote_port}});
165             }
166 0           foreach my $p ('remote_host','remote_port','protocol_version')
167             {
168 0 0 0       Net::DRI::Exception::usererr_insufficient_parameters($p.' must be defined') unless (exists($opts{$p}) && $opts{$p});
169 0           $t{$p}=$opts{$p};
170             }
171              
172 0 0 0       Net::DRI::Exception::usererr_invalid_parameters('close_after must be an integer') if ($opts{close_after} && !Net::DRI::Util::isint($opts{close_after}));
173 0   0       $t{close_after}=$opts{close_after} || 0;
174              
175 0 0         if ($t{socktype} eq 'ssl')
176             {
177 0           $t{ssl_context}=$self->parse_ssl_options(\%opts);
178             }
179              
180 0 0 0       $t{local_host}=$opts{local_host} if (exists($opts{local_host}) && $opts{local_host});
181 0           $t{remote_uri}=sprintf('%s://%s:%d',$t{socktype},$t{remote_host},$t{remote_port}); ## handy shortcut only used for error messages
182 0           $self->{transport}=\%t;
183              
184 0           my $rc;
185 0 0         if ($self->defer()) ## we will open, but later
186             {
187 0           $self->current_state(0);
188             } else ## we will open NOW
189             {
190 0           $rc=$self->open_connection($ctx);
191 0           $self->current_state(1);
192             }
193              
194 0           return ($self,$rc);
195             }
196              
197 0 0   0 0   sub sock { my ($self,$v)=@_; $self->transport_data()->{sock}=$v if defined($v); return $self->transport_data()->{sock}; }
  0            
  0            
198              
199             ## TODO (for IRIS DCHK1 + NAPTR/SRV)
200             ## Wrap in an eval to handle timeout (see if outer eval already for that ?)
201             ## Handle remote_host/port being ref array of ordered strings to try (in which case defer should be 0 probably as the list of things to try have been determined now, not later)
202             ## Or specify a callback to call when doing socket open to find the correct host+ports to use at that time
203             sub open_socket
204             {
205 0     0 0   my ($self,$ctx)=@_;
206 0           my $t=$self->transport_data();
207 0           my $type=$t->{socktype};
208 0           my $sock;
209              
210 0 0         my %n=( PeerAddr => $t->{remote_host},
211             PeerPort => $t->{remote_port},
212             Proto => $t->{socktype} eq 'udp'? 'udp' : 'tcp',
213             Blocking => 1,
214             MultiHomed => 1,
215             );
216 0 0         $n{LocalAddr}=$t->{local_host} if exists($t->{local_host});
217              
218 0 0 0       if ($type eq 'ssl')
    0          
219             {
220 0           $sock=IO::Socket::SSL->new(%{$t->{ssl_context}},
  0            
221             %n,
222             );
223             } elsif ($type eq 'tcp' || $type eq 'udp')
224             {
225 0           $sock=IO::Socket::INET->new(%n);
226             }
227              
228 0 0         Net::DRI::Exception->die(1,'transport/socket',6,'Unable to setup the socket for '.$t->{remote_uri}.' with error: "'.$!.($type eq 'ssl'? '" and SSL error: "'.IO::Socket::SSL::errstr().'"' : '"')) unless defined $sock;
    0          
229 0           $sock->autoflush(1);
230 0           $self->sock($sock);
231 0           $self->log_output('notice','transport',$ctx,{phase=>'opening',message=>'Successfully opened socket to '.$t->{remote_uri}});
232 0           return;
233             }
234              
235             sub send_login
236             {
237 0     0 0   my ($self,$ctx)=@_;
238 0           my $t=$self->transport_data();
239 0           my $sock=$self->sock();
240 0           my $pc=$t->{pc};
241 0           my $dr;
242 0           my $cltrid=$self->generate_trid($self->{logging_ctx}->{registry});
243 0           my @rs;
244              
245             ## Get server greeting, if needed
246 0 0         if ($ctx->{protocol}->has_action('session','connect'))
247             {
248 0           my $t1=Time::HiRes::time();
249 0           $dr=$pc->read_data($self,$sock);
250 0           my $t2=Time::HiRes::time();
251 0           $self->log_output('notice','transport',$ctx,{trid=>$cltrid,phase=>'opening',direction=>'in',message=>$dr});
252 0           push @rs,$self->protocol_parse($ctx->{protocol},'session','connect',$dr,$cltrid,$t2-$t1);
253 0 0         return Net::DRI::Util::link_rs(@rs) unless $rs[-1]->is_success();
254             }
255              
256 0 0         return unless $ctx->{protocol}->has_action('session','login');
257              
258 0           foreach my $p (qw/client_login client_password/)
259             {
260 0 0 0       Net::DRI::Exception::usererr_insufficient_parameters($p.' must be defined') unless (exists($t->{$p}) && $t->{$p});
261             }
262              
263 0           $cltrid=$self->generate_trid($self->{logging_ctx}->{registry});
264              
265 0 0         my $login=$ctx->{protocol}->action('session','login',$cltrid,$t->{client_login},$t->{client_password},{ client_newpassword => $t->{client_newpassword}, %{$t->{protocol_data} || {}}}); ## TODO: fix last hash ref
  0            
266 0           $self->log_output('notice','transport',$ctx,{otype=>'session',oaction=>'login',trid=>$cltrid,phase=>'opening',direction=>'out',message=>$login});
267 0           my $t1=Time::HiRes::time();
268 0 0 0       Net::DRI::Exception->die(0,'transport/socket',4,'Unable to send login message to '.$t->{remote_uri}) unless ($sock->connected() && $sock->print($pc->write_message($self,$login)));
269              
270             ## Verify login successful
271 0           $dr=$pc->read_data($self,$sock);
272 0           my $t2=Time::HiRes::time();
273 0           $self->log_output('notice','transport',$ctx,{otype=>'session',oaction=>'login',trid=>$cltrid,phase=>'opening',direction=>'in',message=>$dr});
274 0           push @rs,$self->protocol_parse($ctx->{protocol},'session','login',$dr,$cltrid,$t2-$t1,$login);
275              
276 0           return Net::DRI::Util::link_rs(@rs);
277             }
278              
279             sub send_logout
280             {
281 0     0 0   my ($self,$ctx)=@_;
282 0           my $t=$self->transport_data();
283 0           my $sock=$self->sock();
284 0           my $pc=$t->{pc};
285              
286 0 0         return unless $ctx->{protocol}->has_action('session','logout');
287              
288 0           my $cltrid=$self->generate_trid($self->{logging_ctx}->{registry});
289 0           my $logout=$ctx->{protocol}->action('session','logout',$cltrid);
290 0           $self->log_output('notice','transport',$ctx,{otype=>'session',oaction=>'logout',trid=>$cltrid,phase=>'closing',direction=>'out',message=>$logout});
291 0           my $t1=Time::HiRes::time();
292 0 0 0       Net::DRI::Exception->die(0,'transport/socket',4,'Unable to send logout message to '.$t->{remote_uri}) unless ($sock->connected() && $sock->print($pc->write_message($self,$logout)));
293 0           my $dr=$pc->read_data($self,$sock); ## We expect this to throw an exception, since the server will probably cut the connection
294 0           my $t2=Time::HiRes::time();
295 0           $self->time_used(time());
296 0           $t->{exchanges_done}++;
297 0           $self->log_output('notice','transport',$ctx,{otype=>'session',oaction=>'logout',trid=>$cltrid,phase=>'closing',direction=>'in',message=>$dr});
298 0           my $rc1=$self->protocol_parse($ctx->{protocol},'session','logout',$dr,$cltrid,$t2-$t1,$logout);
299 0 0         die $rc1 unless $rc1->is_success();
300 0           return $rc1;
301             }
302              
303             sub open_connection
304             {
305 0     0 0   my ($self,$ctx)=@_;
306 0           $self->open_socket($ctx);
307 0           my $rc=$self->send_login($ctx);
308 0           $self->current_state(1);
309 0           $self->time_open(time());
310 0           $self->time_used(time());
311 0           $self->transport_data()->{exchanges_done}=0;
312 0           return $rc;
313             }
314              
315             sub ping
316             {
317 0     0 0   my ($self,$ctx,$autorecon)=@_;
318 0 0         $autorecon=0 unless defined $autorecon;
319 0           my $t=$self->transport_data();
320 0           my $pc=$t->{pc};
321 0           my $sock=$self->sock();
322              
323 0 0         return 0 unless $self->has_state();
324 0 0         return 0 unless $ctx->{protocol}->has_action('session','noop');
325              
326 0           my $rc1;
327 0           my $cltrid=$self->generate_trid($self->{logging_ctx}->{registry});
328             my $ok=eval
329 0           {
330 0     0     local $SIG{ALRM}=sub { die 'timeout' };
  0            
331 0           alarm 10;
332 0           my $noop=$ctx->{protocol}->action('session','noop',$cltrid);
333 0           $self->log_output('notice','transport',$ctx,{otype=>'session',oaction=>'keepalive',trid=>$cltrid,phase=>'keepalive',direction=>'out',message=>$noop});
334 0           my $t1=Time::HiRes::time();
335 0 0 0       Net::DRI::Exception->die(0,'transport/socket',4,'Unable to send keepalive message to '.$t->{remote_uri}) unless ($sock->connected() && $sock->print($pc->write_message($self,$noop)));
336 0           my $dr=$pc->read_data($self,$sock);
337 0           my $t2=Time::HiRes::time();
338 0           $self->time_used(time());
339 0           $t->{exchanges_done}++;
340 0           $self->log_output('notice','transport',$ctx,{otype=>'session',oaction=>'keepalive',trid=>$cltrid,phase=>'keepalive',direction=>'in',message=>$dr});
341 0           $rc1=$self->protocol_parse($ctx->{protocol},'session','noop',$dr,$cltrid,$t2-$t1,$noop);
342 0 0         die $rc1 unless $rc1->is_success();
343 0           1;
344             };
345 0           my $err=$@;
346              
347 0           alarm 0;
348 0 0 0       if (defined $ok && $ok==1)
349             {
350 0           $self->current_state(1);
351             } else
352             {
353 0           $self->current_state(0);
354 0 0 0       $rc1=$err if defined $err && Net::DRI::Util::is_class($err,'Net::DRI::Protocol::ResultStatus');
355 0 0         if ($autorecon)
356             {
357 0           $self->log_output('notice','transport',{},{phase=>'keepalive',message=>'Reopening connection to '.$t->{remote_uri}.' because ping failed and asked to auto-reconnect'});
358 0           my $rc2=$self->open_connection($ctx);
359 0 0         $rc1=defined $rc1 ? Net::DRI::Util::link_rs($rc1,$rc2) : $rc2;
360             }
361             }
362              
363 0 0         return defined $rc1 ? $rc1 : Net::DRI::Protocol::ResultStatus->new_error('COMMAND_FAILED_CLOSING','ping failed, no auto-reconnect');
364             }
365              
366             sub close_socket
367             {
368 0     0 0   my ($self)=@_;
369 0           my $t=$self->transport_data();
370 0           $self->sock()->close();
371 0           $self->log_output('notice','transport',{},{phase=>'closing',message=>'Successfully closed socket for '.$t->{remote_uri}});
372 0           $self->sock(undef);
373 0           return;
374             }
375              
376             sub close_connection
377             {
378 0     0 0   my ($self,$ctx)=@_;
379 0           $self->send_logout($ctx);
380 0           $self->close_socket();
381 0           $self->current_state(0);
382 0           return;
383             }
384              
385             sub end
386             {
387 0     0 0   my ($self,$ctx)=@_;
388 0 0         if ($self->current_state())
389             {
390             eval
391 0           {
392 0     0     local $SIG{ALRM}=sub { die 'timeout' };
  0            
393 0           alarm 10;
394 0           $self->close_connection($ctx);
395             };
396 0           alarm 0; ## since close_connection may die, this must be outside of eval to be executed in all cases
397             }
398 0           return;
399             }
400              
401             ####################################################################################################
402              
403             sub send ## no critic (Subroutines::ProhibitBuiltinHomonyms)
404             {
405 0     0 0   my ($self,$ctx,$tosend,$count)=@_;
406             ## We do a very crude error handling : if first send fails, we reset connection.
407             ## Thus if you put retry=>2 when creating this object, the connection will be re-established and the message resent
408 0     0     return $self->SUPER::send($ctx,$tosend,\&_print,sub { shift->current_state(0) },$count);
  0            
409             }
410              
411             sub _print ## here we are sure open_connection() was called before
412             {
413 0     0     my ($self,$count,$tosend,$ctx)=@_;
414 0           my $pc=$self->transport_data('pc');
415 0           my $sock=$self->sock();
416 0 0         my $m=($self->transport_data('socktype') eq 'udp')? 'send' : 'print';
417 0 0 0       Net::DRI::Exception->die(0,'transport/socket',4,'Unable to send message to '.$self->transport_data('remote_uri').' because of error: '.$!) unless (($m ne 'print' || $sock->connected()) && $sock->$m($pc->write_message($self,$tosend)));
      0        
418 0           return 1; ## very important
419             }
420              
421             sub receive
422             {
423 0     0 0   my ($self,$ctx,$count)=@_;
424 0           return $self->SUPER::receive($ctx,\&_get,undef,$count);
425             }
426              
427             sub _get
428             {
429 0     0     my ($self,$count,$ctx)=@_;
430 0           my $t=$self->transport_data();
431 0           my $sock=$self->sock();
432 0           my $pc=$t->{pc};
433              
434             ## Answer
435 0           my $dr=$pc->read_data($self,$sock);
436 0           $t->{exchanges_done}++;
437 0 0 0       if ($t->{exchanges_done}==$t->{close_after} && $self->has_state() && $self->current_state())
      0        
438             {
439 0           $self->log_output('notice','transport',$ctx,{phase=>'closing',message=>'Due to maximum number of exchanges reached, closing connection to '.$t->{remote_uri}});
440 0           $self->close_connection($ctx);
441             }
442 0           return $dr;
443             }
444              
445             sub try_again
446             {
447 0     0 0   my ($self,$ctx,$po,$err,$count,$istimeout,$step,$rpause,$rtimeout)=@_;
448 0 0         if ($step==0) ## sending not already done, hence error during send
449             {
450 0           $self->current_state(0);
451 0           return 1;
452             }
453              
454             ## We do a more agressive retry procedure in case of udp (that is IRIS basically)
455             ## See RFC4993 section 4
456 0 0 0       if ($step==1 && $istimeout==1 && $self->transport_data()->{socktype} eq 'udp')
      0        
457             {
458 0           $self->log_output('debug','transport',$ctx,{phase=>'active',message=>sprintf('In try_again, currently: pause=%f timeout=%f',$$rpause,$$rtimeout)});
459 0           $$rtimeout=2*$$rtimeout;
460 0           $$rpause+=rand(1+int($$rpause/2));
461 0           $self->log_output('debug','transport',$ctx,{phase=>'active',message=>sprintf('In try_again, new values: pause=%f timeout=%f',$$rpause,$$rtimeout)});
462 0           return 1; ## we will retry
463             }
464              
465 0           return 0; ## we do not handle other cases, hence no retry and fatal error
466             }
467              
468              
469             ####################################################################################################
470             1;