File Coverage

blib/lib/SRS/EPP/Proxy/Listener.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1              
2             package SRS::EPP::Proxy::Listener;
3              
4 2     2   101897 use 5.010; # for (?| alternation feature
  2         8  
  2         64  
5              
6 2     2   372 use Moose;
  0            
  0            
7             use MooseX::Method::Signatures;
8              
9             with 'MooseX::Log::Log4perl::Easy';
10              
11             use IO::Select;
12             use Net::SSLeay::OO;
13             use Socket;
14             use IO::Socket::INET;
15              
16             our ($HAVE_V6, @SOCKET_TYPES);
17             BEGIN {
18             my $sock = eval {
19             require Socket6;
20             Socket6->import;
21             require IO::Socket::INET6;
22             IO::Socket::INET6->new(
23             Listen => 1,
24             LocalAddr => '::1',
25             LocalPort => int(rand(60000)+1024),
26             Proto => 'tcp',
27             );
28             };
29             if ( $sock or $!{EADDRINUSE} ) {
30             $HAVE_V6 = 1;
31             @SOCKET_TYPES = ("IO::Socket::INET6");
32             }
33             push @SOCKET_TYPES, "IO::Socket::INET";
34             }
35              
36             sub resolve {
37             my $hostname = shift;
38             my @addr;
39             $DB::single = 1;
40             if ( $HAVE_V6 ) {
41             my @res = getaddrinfo($hostname, "", AF_UNSPEC);
42             while ( my ($family, $socktype, $proto, $address,
43             $canonical) = splice @res, 0, 5 ) {
44             my ($addr) = getnameinfo($address, &NI_NUMERICHOST);
45             push @addr, $addr unless grep { $_ eq $addr }
46             @addr;
47             }
48             }
49             else {
50             my $packed_ip = gethostbyname($hostname)
51             or die "fail to resolve host '$hostname'; $!";
52             my $ip_address = inet_ntoa($packed_ip);
53             push @addr, $ip_address;
54             }
55             @addr;
56             }
57              
58             has 'listen' =>
59             is => "ro",
60             isa => "ArrayRef[Str]",
61             required => 1,
62             default => sub { [ ($HAVE_V6 ? "[::]" : "0.0.0.0") ] },
63             ;
64              
65             has 'sockets' =>
66             is => "ro",
67             isa => "ArrayRef[IO::Socket]",
68             default => sub { [] },
69             ;
70              
71             use constant EPP_DEFAULT_TCP_PORT => 700;
72             use constant EPP_DEFAULT_LOCAL_PORT => "epp(".EPP_DEFAULT_TCP_PORT.")";
73              
74             method init() {
75              
76             my @sockets;
77             for my $addr ( @{ $self->listen } ) {
78              
79             # parse out the hostname and port; I can't see another
80             # way to supply a default port number.
81             my ($hostname, $port) = $addr =~
82             m{^(?|\[([^]]+)\]|([^:]+))(?::(\d+))?$}
83             or die "bad listen address: $addr";
84             $port ||= EPP_DEFAULT_LOCAL_PORT;
85              
86             my @addr = resolve($hostname);
87             $self->log_debug("$hostname resolved to @addr");
88              
89             for my $addr ( @addr ) {
90             my $SOCKET_TYPE = "IO::Socket::INET";
91             if ( $addr =~ /:/ ) {
92             $SOCKET_TYPE .= "6";
93             }
94             my $socket = $SOCKET_TYPE->new(
95             Listen => 5,
96             LocalAddr => $addr,
97             LocalPort => $port,
98             Proto => "tcp",
99             ReuseAddr => 1,
100             );
101              
102             if ( !$socket ) {
103             $self->log_error("Failed to listen on $addr:$port; $!");
104             }
105             else {
106             $self->log_info("Listening on $addr port $port");
107             push @sockets, $socket;
108             }
109             }
110             }
111              
112             if ( !@sockets ) {
113             die "No listening sockets; aborting";
114             }
115              
116             @{ $self->sockets } = @sockets;
117             }
118              
119             method accept( Int $timeout? ) {
120             my $select = IO::Select->new();
121             $select->add($_) for @{$self->sockets};
122             my @ready = $select->can_read( $timeout )
123             or return;
124             while ( @ready > 1 ) {
125             if ( rand(1) > 0.5 ) {
126             shift @ready;
127             }
128             else {
129             pop @ready;
130             }
131             }
132             my $socket = $ready[0]->accept;
133             if ( !$socket ) {
134             die "accept lost a socket; exiting";
135             }
136             $socket;
137             }
138              
139             method close() {
140             for my $socket ( @{ $self->sockets } ) {
141             $socket->close if $socket;
142             }
143             @{ $self->sockets } = ();
144             }
145              
146             1;
147              
148             __END__
149              
150             =head1 NAME
151              
152             SRS::EPP::Proxy::Listener - socket factory class
153              
154             =head1 SYNOPSIS
155              
156             my $listener = SRS::EPP::Proxy::Listener->new(
157             listen => [ "hostname:port", "address:port" ],
158             );
159              
160             # this does the listen part
161             $listener->init;
162              
163             # this normally blocks, and returns a socket.
164             # it might return undef, if you pass it a timeout.
165             my $socket = $listener->accept;
166              
167             =head1 DESCRIPTION
168              
169             This class is a TCP/IP listener. It listens on the configured ports
170             for TCP connections and returns sockets when there are incoming
171             connections waiting.
172              
173             You don't actually need to supply the port or listen addresses; the
174             defaults are to listen on INADDR_ANY (0.0.0.0) or IN6ADDR_ANY (::) on
175             port C<epp(700)>.
176              
177             If the L<IO::Socket::INET6> module is installed, then at load time the
178             module tries to listen on a random port on the IPv6 loopback address.
179             If that works (or fails with a particular plausible error, if
180             something else happened to be using that port), then IPv6 is
181             considered to be available. This means that the RFC3493-style
182             I<getaddrinfo> and such are used instead of C<gethostbyname>. You
183             will end up with a socket for every distinct address returned by
184             C<getaddrinfo> on the passed-in list.
185              
186             IPv6 addresses (not names) must be passed in square brackets, such as
187             C<[2404:130:0::42]>.
188              
189             In general these rules should make this listener behave like any
190             normal IPv6-aware daemon.
191              
192             =head1 SEE ALSO
193              
194             L<IO::Socket::INET>, L<Socket6>, L<IO::Socket::INET6>
195              
196             =head1 AUTHOR AND LICENCE
197              
198             Development commissioned by NZ Registry Services, and carried out by
199             Catalyst IT - L<http://www.catalyst.net.nz/>
200              
201             Copyright 2009, 2010, NZ Registry Services. This module is licensed
202             under the Artistic License v2.0, which permits relicensing under other
203             Free Software licenses.
204              
205             =cut
206