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

Revision 708, 4.9 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.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1######################################################################
2# TCP listener on a given port
3#
4# Copyright 2004, Danga Interactive, Inc.
5# Copyright 2005-2007, Six Apart, Ltd.
6
7
8package Perlbal::TCPListener;
9use strict;
10use warnings;
11no  warnings qw(deprecated);
12
13use base "Perlbal::Socket";
14use fields qw(service hostport sslopts);
15use Socket qw(IPPROTO_TCP SOL_SOCKET SO_SNDBUF);
16use Perlbal::SocketSSL;
17
18# TCPListener
19sub new {
20    my ($class, $hostport, $service, $opts) = @_;
21    $opts ||= {};
22
23    my $sock = IO::Socket::INET->new(
24                                     LocalAddr => $hostport,
25                                     Proto => IPPROTO_TCP,
26                                     Listen => 1024,
27                                     ReuseAddr => 1,
28                                     );
29
30    return Perlbal::error("Error creating listening socket: " . ($@ || $!))
31        unless $sock;
32
33    if ($^O eq 'MSWin32') {
34        # On Windows, we have to do this a bit differently.
35        # IO::Socket should really do this for us, but whatever.
36        my $do = 1;
37        ioctl($sock, 0x8004667E, \$do) or return Perlbal::error("Unable to make listener on $hostport non-blocking: $!");
38    }
39    else {
40        # IO::Socket::INET's Blocking => 0 just doesn't seem to work
41        # on lots of perls.  who knows why.
42        IO::Handle::blocking($sock, 0) or return Perlbal::error("Unable to make listener on $hostport non-blocking: $!");
43    }
44
45    my $self = $class->SUPER::new($sock);
46    $self->{service} = $service;
47    $self->{hostport} = $hostport;
48    $self->{sslopts} = $opts->{ssl};
49    bless $self, ref $class || $class;
50    $self->watch_read(1);
51    return $self;
52}
53
54# TCPListener: accepts a new client connection
55sub event_read {
56    my Perlbal::TCPListener $self = shift;
57
58    # accept as many connections as we can
59    while (my ($psock, $peeraddr) = $self->{sock}->accept) {
60        IO::Handle::blocking($psock, 0);
61
62        if (my $sndbuf = $self->{service}->{client_sndbuf_size}) {
63            my $rv = setsockopt($psock, SOL_SOCKET, SO_SNDBUF, pack("L", $sndbuf));
64        }
65
66        if (Perlbal::DEBUG >= 1) {
67            my ($pport, $pipr) = Socket::sockaddr_in($peeraddr);
68            my $pip = Socket::inet_ntoa($pipr);
69            print "Got new conn: $psock ($pip:$pport) for " . $self->{service}->role . "\n";
70        }
71
72        # SSL promotion if necessary
73        if ($self->{sslopts}) {
74            # try to upgrade to SSL, this does no IO it just reblesses
75            # and prepares the SSL engine for handling us later
76            IO::Socket::SSL->start_SSL(
77                                       $psock,
78                                       SSL_server => 1,
79                                       SSL_startHandshake => 0,
80                                       %{ $self->{sslopts} },
81                                       );
82            print "  .. socket upgraded to SSL!\n" if Perlbal::DEBUG >= 1;
83
84            # safety checking to ensure we got upgraded
85            return $psock->close
86                unless ref $psock eq 'IO::Socket::SSL';
87
88            # class into new package and run with it
89            my $sslsock = new Perlbal::SocketSSL($psock, $self);
90            $sslsock->try_accept;
91
92            # all done from our point of view
93            next;
94        }
95
96        # puts this socket into the right class
97        $self->class_new_socket($psock);
98    }
99}
100
101sub class_new_socket {
102    my Perlbal::TCPListener $self = shift;
103    my $psock = shift;
104
105    my $service_role = $self->{service}->role;
106    if ($service_role eq "reverse_proxy") {
107        Perlbal::ClientProxy->new($self->{service}, $psock);
108    } elsif ($service_role eq "management") {
109        Perlbal::ClientManage->new($self->{service}, $psock);
110    } elsif ($service_role eq "web_server") {
111        Perlbal::ClientHTTP->new($self->{service}, $psock);
112    } elsif ($service_role eq "selector") {
113        # will be cast to a more specific class later...
114        Perlbal::ClientHTTPBase->new($self->{service}, $psock, $self->{service});
115    } elsif (my $creator = Perlbal::Service::get_role_creator($service_role)) {
116        # was defined by a plugin, so we want to return one of these
117        $creator->($self->{service}, $psock);
118    }
119}
120
121sub as_string {
122    my Perlbal::TCPListener $self = shift;
123    my $ret = $self->SUPER::as_string;
124    my Perlbal::Service $svc = $self->{service};
125    $ret .= ": listening on $self->{hostport} for service '$svc->{name}'";
126    return $ret;
127}
128
129sub as_string_html {
130    my Perlbal::TCPListener $self = shift;
131    my $ret = $self->SUPER::as_string_html;
132    my Perlbal::Service $svc = $self->{service};
133    $ret .= ": listening on $self->{hostport} for service <b>$svc->{name}</b>";
134    return $ret;
135}
136
137sub die_gracefully {
138    # die off so we stop waiting for new connections
139    my $self = shift;
140    $self->close('graceful_death');
141}
142
1431;
144
145
146# Local Variables:
147# mode: perl
148# c-basic-indent: 4
149# indent-tabs-mode: nil
150# End:
Note: See TracBrowser for help on using the browser.