root/trunk/lib/Perlbal/SocketSSL.pm @ 708

Revision 708, 4.0 kB (checked in by marksmith, 2 years ago)

* make SSL non-blocking

New Perlbal::SocketSSL class that manages the SSL_WANT_READ/WRITE states
and doing the proper thing when the handshake is complete. Some rather
nasty work had to be done to make it all play well together.

Still not recommended to put Perlbal SSL into production anywhere. But it
would be great to get some eyes on this and some more testing.

Line 
1# Base class for SSL sockets.
2#
3# This is a simple class that extends Danga::Socket and contains an IO::Socket::SSL
4# for the purpose of allowing non-blocking SSL in Perlbal.
5#
6# WARNING: this code will break IO::Socket::SSL if you use it in any plugins or
7# have custom Perlbal modifications that use it.  you will run into issues.  This
8# is because we override the close method to prevent premature closure of the socket,
9# so you will end up with the socket not closing properly.
10#
11# Copyright 2007, Mark Smith <mark@plogs.net>.
12#
13# This file is licensed under the same terms as Perl itself.
14
15package Perlbal::SocketSSL;
16
17use strict;
18use warnings;
19no  warnings qw(deprecated);
20
21use Danga::Socket 1.44;
22use IO::Socket::SSL;
23use Errno qw( EAGAIN );
24
25use base 'Danga::Socket';
26use fields qw( listener create_time );
27
28# magic IO::Socket::SSL crap to make it play nice with us
29{
30    no strict 'refs';
31    no warnings 'redefine';
32
33    # replace IO::Socket::SSL::close with our own code...
34    my $orig = *IO::Socket::SSL::close{CODE};
35    *IO::Socket::SSL::close = sub {
36        my $self = shift()
37            or return IO::Socket::SSL::_invalid_object();
38
39        # if we have args, close ourselves (second call!), else don't
40        if (exists ${*$self}->{__close_args}) {
41            $orig->($self, @{${*$self}->{__close_args}});
42        } else {
43            ${*$self}->{__close_args} = [ @_ ];
44            ${*$self}->{_danga_socket}->close('intercepted_ssl_close');
45        }
46    };
47}
48
49# called: CLASS->new( $sock, $tcplistener )
50sub new {
51    my Perlbal::SocketSSL $self = shift;
52    $self = fields::new( $self ) unless ref $self;
53
54    Perlbal::objctor($self);
55
56    my ($sock, $listener) = @_;
57
58    ${*$sock}->{_danga_socket} = $self;
59    $self->{listener} = $listener;
60    $self->{create_time} = time;
61
62    $self->SUPER::new($sock);
63
64    # TODO: would be good to have an overall timeout so that we can
65    # kill sockets that are open and just sitting ethere.  "ssl_handshake_timeout"
66    # or something like that...
67
68    return $self;
69}
70
71# this is nonblocking, it attempts to setup SSL and if it can't then
72# it returns whether it needs to read or write.  we then setup to wait
73# for the event it indicates and then wait.  when that event fires, we
74# call down again, and repeat the process until we have setup the
75# SSL connection.
76sub try_accept {
77    my Perlbal::SocketSSL $self = shift;
78
79    my $sock = $self->{sock}->accept_SSL;
80
81    if (defined $sock) {
82        # looks like we got it!  let's steal it from ourselves
83        # so Danga::Socket gives up on it and we can send
84        # it out to someone else.  (we discard the return value
85        # as we already have it in $sock)
86        #
87        # of course, life isn't as simple as that.  we have to do
88        # some trickery with the ordering here to ensure that we
89        # don't setup the new class until after the Perlbal::SocketSSL
90        # goes away according to Danga::Socket.
91        #
92        # if we don't do it this way, we get nasty errors because
93        # we (this object) still exists in the DescriptorMap of
94        # Danga::Socket when the new Perlbal::ClientXX tries to
95        # insert itself there.
96
97        # removes us from the active polling, closes up shop, but
98        # save our fd first!
99        my $fd = $self->{fd};
100        $self->steal_socket;
101
102        # finish blowing us away
103        my $ref = Danga::Socket->DescriptorMap();
104        delete $ref->{$fd};
105
106        # now stick the new one in
107        $self->{listener}->class_new_socket($sock);
108        return;
109    }
110
111    # nope, let's see if we can continue the process
112    if ($! == EAGAIN) {
113        if ($SSL_ERROR == SSL_WANT_READ) {
114            $self->watch_read(1);
115        } elsif ($SSL_ERROR == SSL_WANT_WRITE) {
116            $self->watch_write(1);
117        } else {
118            $self->close('invalid_ssl_state');
119        }
120    } else {
121        $self->close('invalid_ssl_error');
122    }
123}
124
125sub event_read {
126    $_[0]->watch_read(0);
127    $_[0]->try_accept;
128}
129
130sub event_write {
131    $_[0]->watch_write(0);
132    $_[0]->try_accept;
133}
134
1351;
Note: See TracBrowser for help on using the browser.