I have been trying to construct an SSL client which works in non-blocking mode on a variety of platforms (i.e. it needs to run under UNIX (or Linux) and Windows). It uses a custom select dispatcher, which I am at this point unwilling to change (it would affect too much existing code).
I have looked at the following modules for implementing this client:
Non-blocking and timeouts (which are based on non-blocking) are not supported on Win32, because the underlying IO::Socket::INET does not support non-blocking on this platform.( I'm not sure what they're getting at, I use non-blocking IO::Socket::INET objects all the time in Win32, and they seem to work (although they exhibit behavior which is slightly different from UNIX). )
Being primarily a UNIX guy, I usually implement first on UNIX (in this case snow leopard), and then port to Windows. After much weeping and gnashing of teeth, I was able to get such a client working on my Mac using Net::SSLeay. The weeping and gnashing of teeth occured primarily not because Net::SSLeay is bad, or difficult, but because it isn't really well documented. In the process of implementation, here's what I arrived at:
use Net::SSLeay qw( die_now die_if_ssl_error ); Net::SSLeay::load_error_strings(); eval 'no warnings "redefine"; sub Net::SSLeay::load_error_strings () {} '; die $@ if $@; Net::SSLeay::SSLeay_add_ssl_algorithms(); eval 'no warnings "redefine"; sub Net::SSLeay::SSLeay_add_ssl_algorithms () {} '; die $@ if $@; Net::SSLeay::ENGINE_load_builtin_engines(); eval 'no warnings "redefine"; sub Net::SSLeay::ENGINE_load_builtin_engines () {} '; die $@ if $@; Net::SSLeay::ENGINE_register_all_complete(); eval 'no warnings "redefine"; sub Net::SSLeay::ENGINE_register_all_complete () {} '; die $@ if $@; Net::SSLeay::randomize(); eval 'no warnings "redefine"; sub Net::SSLeay::randomize (;$$) {} '; die $@ if $@;
my $socket = IO::Socket::INET->new( PeerAddr => $host, PeerPort => 'https(443)', AutoFlush => 1, Blocking => 0, Proto => 'tcp' ) or die "can't connect to https server";
my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_ALL) and ssl_check_die("ssl ctx set options"); my $ssl = Net::SSLeay::new($ctx) or die_now("Failed to create SSL $!");
sub ssl_get_error { my $errors = ""; my $errnos = []; while(my $errno = Net::SSLeay::ERR_get_error()) { push @$errnos, $errno; $errors .= Net::SSLeay::ERR_error_string($errno) . "\n"; } return $errors, $errnos if wantarray; return $errors; } sub ssl_check_die { my ($message) = @_; my ($errors, $errnos) = ssl_get_error(); die "${message}: ${errors}" if @$errnos; return; }
Net::SSLeay::set_fd($ssl, fileno($socket)); my $res = Net::SSLeay::connect($ssl) and Net::SSLeay::die_if_ssl_error("ssl connect"); if($res < 0){ # Here the connect failed because the exchange is not complete; # continue to select on this socket and retry every time the # socket selects true until it succeeds. return; } else { # Here the connect is complete. This is where to but the code # that changes the "select" handler for the socket for the next # phase (writing the data). }
my $written = $ssl->write(substr($cur_buff, $cur_count)); if($written <= 0){ unless($! eq "Resource temporarily unavailable"){ ## An error has occurred - recovery code goes here } return; } $cur_count += $written; if($cur_count == length($cur_buff)){ $cur_count = 0; $cur_buff = undef; ### We have emptied the current buffer ### Here is where code specific to select dispatcher ### must determine whether we have written all the data... }
my $res = CORE::shutdown $socket, 1;
my $rb = $ssl->read(16384); ssl_check_die("SSL read"); if(undefined($rb) or length($rb) <= 0){ unless($! eq "Resource temporarily unavailable"){ ## Here we are done - do whatever is necessary to ## shutdown select dispatcher and close sockets, release ## ssl and context } if($rb){ ## you have read some data in $rb -- do something with it } }
Net::SSLeay::free($ssl); Net::SSLeay::CTX_free($ctx); $socket->close();
The code which I "stole" off the web, came for the following source:
http://devpit.org/wiki/OpenSSL_with_nonblocking_sockets_(in_Perl)
Since I was writing a client and his sample code was for a server, I didn't use much of his code, but the magical incantations were useful. I didn't implement his "drain the socket on read strategy", because the server always closes when its finished writing and "read" always selects true on a closed socket, so I don't have to worry about orphan data in the SSL buffer.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Quest for the Elusive Non-Blocking SSL Client
by ambrus (Abbot) on Dec 19, 2010 at 14:45 UTC | |
by bill.bbennett (Novice) on Dec 20, 2010 at 14:16 UTC | |
|
Re: Quest for the Elusive Non-Blocking SSL Client
by syphilis (Archbishop) on Dec 18, 2010 at 22:23 UTC | |
by bill.bbennett (Novice) on Dec 20, 2010 at 14:52 UTC | |
by andal (Hermit) on Dec 21, 2010 at 10:47 UTC |