root/trunk/lib/Perlbal/TCPListener.pm

Revision 806, 6.1 kB (checked in by bradfitz, 12 months ago)

Beginnings of IPv6 support.

  • 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 ('service',
15            'hostport',
16            'sslopts',
17            'v6',  # bool: IPv6 libraries are available
18            );
19use Socket qw(IPPROTO_TCP SOL_SOCKET SO_SNDBUF);
20
21BEGIN {
22    eval { require Perlbal::SocketSSL };
23    if (Perlbal::DEBUG > 0 && $@) { warn "SSL support failed on load: $@\n" }
24}
25
26# TCPListener
27sub new {
28    my Perlbal::TCPListener $self = shift;
29    my ($hostport, $service, $opts) = @_;
30
31    $self = fields::new($self) unless ref $self;
32    $opts ||= {};
33
34    # Were ipv4 or ipv6 explicitly mentioned by syntax?
35    my $force_v4 = 0;
36    my $force_v6 = 0;
37
38    my @args;
39    if ($hostport =~ /^\d+$/) {
40        @args = ('LocalPort' => $hostport);
41    } elsif ($hostport =~ /^\d+\.\d+\.\d+\.\d+:/) {
42        $force_v4 = 1;
43        @args = ('LocalAddr' => $hostport);
44    }
45
46    my $v6_errors = "";
47
48    my $can_v6 = 0;
49    if (!$force_v4) {
50        eval "use Danga::Socket 1.61; 1; ";
51        if ($@) {
52            $v6_errors = "Danga::Socket 1.61 required for IPv6 support.";
53        } elsif (!eval { require IO::Socket::INET6; 1 }) {
54            $v6_errors = "IO::Socket::INET6 required for IPv6 support.";
55        } else {
56            $can_v6 = 1;
57        }
58    }
59
60    my $socket_class = $can_v6 ? "IO::Socket::INET6" : "IO::Socket::INET";
61    $self->{v6} = $can_v6;
62
63    my $sock = $socket_class->new(
64                                  @args,
65                                  Proto => IPPROTO_TCP,
66                                  Listen => 1024,
67                                  ReuseAddr => 1,
68                                  );
69
70    return Perlbal::error("Error creating listening socket: " . ($@ || $!))
71        unless $sock;
72
73    if ($^O eq 'MSWin32') {
74        # On Windows, we have to do this a bit differently.
75        # IO::Socket should really do this for us, but whatever.
76        my $do = 1;
77        ioctl($sock, 0x8004667E, \$do) or return Perlbal::error("Unable to make listener on $hostport non-blocking: $!");
78    }
79    else {
80        # IO::Socket::INET's Blocking => 0 just doesn't seem to work
81        # on lots of perls.  who knows why.
82        IO::Handle::blocking($sock, 0) or return Perlbal::error("Unable to make listener on $hostport non-blocking: $!");
83    }
84
85    $self->SUPER::new($sock);
86    $self->{service} = $service;
87    $self->{hostport} = $hostport;
88    $self->{sslopts} = $opts->{ssl};
89    $self->watch_read(1);
90    return $self;
91}
92
93# TCPListener: accepts a new client connection
94sub event_read {
95    my Perlbal::TCPListener $self = shift;
96
97    # accept as many connections as we can
98    while (my ($psock, $peeraddr) = $self->{sock}->accept) {
99        IO::Handle::blocking($psock, 0);
100
101        if (my $sndbuf = $self->{service}->{client_sndbuf_size}) {
102            my $rv = setsockopt($psock, SOL_SOCKET, SO_SNDBUF, pack("L", $sndbuf));
103        }
104
105        if (Perlbal::DEBUG >= 1) {
106            my ($pport, $pipr) = $self->{v6} ?
107                Socket6::unpack_sockaddr_in6($peeraddr) :
108                Socket::sockaddr_in($peeraddr);
109            my $pip = $self->{v6} ?
110                "[" . Socket6::inet_ntop(Socket6::AF_INET6(), $pipr) . "]" :
111                Socket::inet_ntoa($pipr);
112            print "Got new conn: $psock ($pip:$pport) for " . $self->{service}->role . "\n";
113        }
114
115        # SSL promotion if necessary
116        if ($self->{sslopts}) {
117            # try to upgrade to SSL, this does no IO it just re-blesses
118            # and prepares the SSL engine for handling us later
119            IO::Socket::SSL->start_SSL(
120                                       $psock,
121                                       SSL_server => 1,
122                                       SSL_startHandshake => 0,
123                                       %{ $self->{sslopts} },
124                                       );
125            print "  .. socket upgraded to SSL!\n" if Perlbal::DEBUG >= 1;
126
127            # safety checking to ensure we got upgraded
128            return $psock->close
129                unless ref $psock eq 'IO::Socket::SSL';
130
131            # class into new package and run with it
132            my $sslsock = new Perlbal::SocketSSL($psock, $self);
133            $sslsock->try_accept;
134
135            # all done from our point of view
136            next;
137        }
138
139        # puts this socket into the right class
140        $self->class_new_socket($psock);
141    }
142}
143
144sub class_new_socket {
145    my Perlbal::TCPListener $self = shift;
146    my $psock = shift;
147
148    my $service_role = $self->{service}->role;
149    if ($service_role eq "reverse_proxy") {
150        Perlbal::ClientProxy->new($self->{service}, $psock);
151    } elsif ($service_role eq "management") {
152        Perlbal::ClientManage->new($self->{service}, $psock);
153    } elsif ($service_role eq "web_server") {
154        Perlbal::ClientHTTP->new($self->{service}, $psock);
155    } elsif ($service_role eq "selector") {
156        # will be cast to a more specific class later...
157        Perlbal::ClientHTTPBase->new($self->{service}, $psock, $self->{service});
158    } elsif (my $creator = Perlbal::Service::get_role_creator($service_role)) {
159        # was defined by a plugin, so we want to return one of these
160        $creator->($self->{service}, $psock);
161    }
162}
163
164sub as_string {
165    my Perlbal::TCPListener $self = shift;
166    my $ret = $self->SUPER::as_string;
167    my Perlbal::Service $svc = $self->{service};
168    $ret .= ": listening on $self->{hostport} for service '$svc->{name}'";
169    return $ret;
170}
171
172sub as_string_html {
173    my Perlbal::TCPListener $self = shift;
174    my $ret = $self->SUPER::as_string_html;
175    my Perlbal::Service $svc = $self->{service};
176    $ret .= ": listening on $self->{hostport} for service <b>$svc->{name}</b>";
177    return $ret;
178}
179
180sub die_gracefully {
181    # die off so we stop waiting for new connections
182    my $self = shift;
183    $self->close('graceful_death');
184}
185
1861;
187
188
189# Local Variables:
190# mode: perl
191# c-basic-indent: 4
192# indent-tabs-mode: nil
193# End:
Note: See TracBrowser for help on using the browser.