File Coverage

blib/lib/App/HTTP_Proxy_IMP/Relay.pm
Criterion Covered Total %
statement 48 342 14.0
branch 0 188 0.0
condition 0 60 0.0
subroutine 16 55 29.0
pod 0 17 0.0
total 64 662 9.6


line stmt bran cond sub pod time code
1 1     1   7 use strict;
  1         2  
  1         31  
2 1     1   5 use warnings;
  1         3  
  1         41  
3              
4             package App::HTTP_Proxy_IMP::Relay;
5             use fields (
6 1         5 'fds', # file descriptors
7             'conn', # App::HTTP_Proxy_IMP::HTTPConn object
8             'acct', # collect accounting
9 1     1   5 );
  1         3  
10              
11 1     1   69 use App::HTTP_Proxy_IMP::Debug;
  1         2  
  1         83  
12 1     1   6 use Scalar::Util 'weaken';
  1         3  
  1         40  
13 1     1   844 use IO::Socket::SSL;
  1         78134  
  1         7  
14 1     1   1234 use AnyEvent;
  1         5487  
  1         37  
15 1     1   7 use POSIX '_exit';
  1         2  
  1         7  
16              
17             # set if the child should destroy itself after last connection closed
18             my $exit_if_no_relays;
19 0     0 0   sub exit_if_no_relays { $exit_if_no_relays = pop; }
20              
21             # active relay, inserted in new, removed in $idlet timer
22             my @relays;
23 0     0 0   sub relays { return grep { $_ } @relays }
  0            
24              
25             # creates new relay and puts it into @relays as weak reference
26             sub new {
27 0     0 0   my ($class,$cfd,$upstream,$conn) = @_;
28 0           my $self = fields::new($class);
29 0           debug("create relay $self");
30              
31 0 0 0       if ( $upstream && ! ref($upstream)) {
32 0 0         $upstream =~m{\A(?:\[([a-f\d:.]+)\]|([\da-z_\-.]+)):(\d+)\Z} or
33             die "invalid upstream specification: $upstream";
34 0   0       $upstream = [ $1||$2, $3 ];
35             }
36              
37 0           my $cobj = $conn->new_connection({
38             daddr => $cfd->sockhost,
39             dport => $cfd->sockport,
40             saddr => $cfd->peerhost,
41             sport => $cfd->peerport,
42             upstream => $upstream,
43             },$self);
44              
45             #debug("create connection $cobj");
46 0           $self->{conn} = $cobj;
47 0           my $cfo = $self->{fds}[0] = App::HTTP_Proxy_IMP::Relay::FD->new(0,$cfd,$self,1);
48 0           $cfo->mask( r => 1 ); # enable read
49              
50 0           push @relays, $self;
51 0           weaken($relays[-1]);
52              
53 0           return $self;
54             }
55              
56             sub DESTROY {
57 0     0     my $self = shift;
58 0           $self->account('destroy');
59 0           $self->xdebug("destroy relay $self");
60 0 0 0       if ( $exit_if_no_relays && ! $self->relays ) {
61             # der letzte macht das Licht aus
62 0           debug("exit child $$ after last connection");
63 0           _exit(0)
64             }
65             }
66              
67             sub acctinfo {
68 0     0 0   my ($self,$acct) = @_;
69 0           $self->{acct} = $acct;
70             }
71             sub account {
72 0     0 0   my ($self,$what,%args) = @_;
73 0           my $acct = $self->{acct};
74 0 0         $acct = $acct ? { %$acct,%args } : \%args if %args;
    0          
75 0 0         $acct or return;
76 0           $self->{acct} = undef;
77 0 0         if ( my $t = delete $acct->{start} ) {
78 0           $acct->{duration} = AnyEvent->now - $t;
79             }
80 0           my @msg;
81 0           for( sort keys %$acct ) {
82 0           my $t;
83 0           my $v = $acct->{$_};
84 0 0         if ( ! defined $v ) {
    0          
    0          
85 0           next;
86             } elsif ( ref($v) eq 'ARRAY') {
87 0           $t = "$_=[".join(',',map { _quote($_) } @$v)."]";
  0            
88             } elsif ( defined $v ) {
89 0           $t = "$_="._quote($v);
90             }
91 0           push @msg,$t;
92             }
93 0           print STDERR "ACCT @msg\n";
94             }
95              
96             sub _quote {
97 0     0     my $text = shift;
98 0           $text =~s{([\000-\037\\"\377-\777])}{ sprintf("\\%03o",ord($1)) }eg;
  0            
99 0 0         return $text =~m{ } ? qq["$text"]:$text;
100             }
101              
102             sub xdebug {
103 0     0 0   my $self = shift;
104 0           my $conn = $self->{conn};
105 0 0         if ( my $xdebug = UNIVERSAL::can($conn,'xdebug') ) {
106 0           unshift @_,$conn;
107 0           goto &$xdebug;
108             } else {
109 0           goto &debug;
110             }
111             }
112              
113              
114             # non-fatal problem
115             sub error {
116 0     0 0   my ($self,$reason) = @_;
117 0   0       warn "[error] ".( $self->{conn} && $self->{conn}->id || 'noid')." $reason\n";
118 0           return 0;
119             }
120              
121             # fatal problem - close connection
122             sub fatal {
123 0     0 0   my ($self,$reason) = @_;
124 0   0       warn "[fatal] ".( $self->{conn} && $self->{conn}->id || 'noid')." $reason\n";
125 0           $self->close;
126 0           return 0;
127             }
128              
129             sub connect:method {
130 0     0 0   my ($self,$to,$host,$port,$callback,$reconnect) = @_;
131 0   0       my $fo = $self->{fds}[$to] ||= App::HTTP_Proxy_IMP::Relay::FD->new($to,undef,$self);
132 0           $fo->connect($host,$port,$callback,$reconnect);
133             }
134              
135             # masks/unmasks fd for dir, rw = r|w
136             sub mask {
137 0     0 0   my ($self,$dir,$rw,$v) = @_;
138 0 0         my $fd = $self->{fds}[$dir] or do {
139 0           warn "fd dir=$dir does not exists\n";
140 0           return;
141             };
142 0           $fd->mask($rw,$v);
143             }
144              
145             sub fd {
146 0     0 0   my ($self,$dir) = @_;
147 0           return $self->{fds}[$dir];
148             }
149              
150             # send some data via fd dir
151             sub forward {
152 0     0 0   my ($self,$from,$to,$data) = @_;
153 0 0         my $fo = $self->{fds}[$to] or return
154             $self->fatal("cannot write to $to - no such fo");
155 0           $self->xdebug("$from>$to - forward %d bytes",length($data));
156 0           $fo->write($data,$from);
157             }
158              
159             # ssl interception, e.g. upgrade both client and server to SSL sockets,
160             # where I can read/write unencrypted data
161             sub sslify {
162 0     0 0   my ($self,$from,$to,$hostname,$callback) = @_;
163 0 0         my $conn = $self->{conn} or return;
164 0 0         my $mitm = $conn->{mitm} or return; # no MITM needed
165              
166             # destroy the current connection object and create a new obne
167 0           $conn = $self->{conn} = $conn->clone;
168 0           $conn->{intunnel} = 1;
169            
170 0 0         my $sfo = $self->{fds}[$from] or return
171             $self->fatal("cannot startssl $from - no such fo");
172              
173             # stop handling all data
174 0           $self->mask($to,r=>0);
175 0           $self->mask($from,r=>0);
176 0           weaken( my $wself = $self );
177              
178             my %sslargs = (
179             SSL_verifycn_name => $hostname,
180             SSL_verifycn_schema => 'http',
181             SSL_hostname => $hostname, # SNI
182             $conn->{capath} ? (
183             SSL_verify_mode => SSL_VERIFY_PEER,
184             ( -d $conn->{capath} ? 'SSL_ca_path' : 'SSL_ca_file' ),
185             $conn->{capath}
186 0 0         ):(
    0          
187             SSL_verify_mode => SSL_VERIFY_NONE
188             )
189             );
190             $sfo->startssl( %sslargs, sub {
191 0     0     my $sfo = shift;
192 0           my ($cert,$key) = $mitm->clone_cert($sfo->{fd}->peer_certificate);
193 0 0         my $cfo = $wself->{fds}[$to] or return
194             $wself->fatal("cannot startssl $to - no such fo");
195             $cfo->startssl(
196             SSL_server => 1,
197             SSL_cert => $cert,
198             SSL_key => $key,
199             sub {
200             # allow data again
201 0           $self->mask($to,r=>1);
202 0           $self->mask($from,r=>1);
203 0 0         $callback->() if $callback;
204             }
205 0           );
206 0           });
207             }
208              
209             # closes relay
210             sub close:method {
211 0     0 0   my $self = shift;
212             #debug("close $self");
213 0           undef $self->{conn};
214 0 0         @relays = grep { !$_ or $_ != $self } @relays;
  0            
215 0   0       $_ && $_->close for @{$self->{fds}};
  0            
216 0           @{$self->{fds}} = ();
  0            
217             }
218              
219             # shutdown part of relay
220             sub shutdown:method {
221 0     0 0   my ($self,$dir,$rw,$force) = @_;
222 0 0         my $fo = $self->{fds}[$dir] or return;
223 0           $fo->shutdown($rw,$force);
224             }
225              
226             # check for condition, where we cannot transfer anymore data:
227             # - nowhere to read and no open requests
228             # - nowhere to write too
229             sub closeIfDone {
230 0     0 0   my $self = shift;
231 0           my $sink = my $drain = '';
232 0           for my $fo (@{$self->{fds}}) {
  0            
233 0 0 0       $fo && $fo->{fd} or next;
234 0 0         return if $fo->{rbuf} ne ''; # has unprocessed data
235 0 0         return if $fo->{wbuf} ne ''; # has unwritten data
236 0 0         $drain .= $fo->{dir} if not $fo->{status} & 0b100; # not read-closed
237 0 0         $sink .= $fo->{dir} if not $fo->{status} & 0b010; # not write-closed
238             }
239              
240 0 0         if ( $sink eq '' ) { # nowhere to write
241 0 0         $DEBUG && $self->xdebug( "close relay because all fd done sink='$sink' ");
242             # close relay
243 0           return $self->close;
244             }
245              
246 0 0         if ( $drain ne '01' ) { # no reading from both sides
247 0           my $conn = $self->{conn};
248 0 0 0       if ( ! $conn or ! $conn->open_requests ) {
249             # close relay
250 0 0         $DEBUG && $self->xdebug( "close relay because nothing to read and all done");
251 0           return $self->close;
252             }
253             }
254              
255 0 0         $DEBUG && $self->xdebug("drain=$drain sink=$sink rq=".$self->{conn}->open_requests." - keeping open");
256 0           return;
257             }
258              
259              
260             # dump state to debug
261             sub dump_state {
262 0     0 0   my $self = shift;
263 0           my $conn = $self->{conn};
264 0           my $msg = '';
265 0 0         if ( my $fds = $self->{fds} ) {
266 0           my @st;
267 0           for( my $i=0;$i<@$fds;$i++) {
268 0   0       push @st, sprintf("%d=%03b",$i,$fds->[$i]{status} || 0);
269             }
270 0           $msg .= " fd:".join(',',@st);
271             }
272 0           $msg = $conn->dump_state().$msg;
273 0 0         return $msg if defined wantarray;
274 0           debug($msg);
275             }
276              
277              
278             my $idlet = AnyEvent->timer(
279             after => 5,
280             interval => 5, cb => sub {
281             @relays = grep { $_ } @relays or return;
282             #debug("check timeouts for %d conn",+@relays);
283             my $now = AnyEvent->now;
284             RELAY: for my $r (@relays) {
285             # timeout depends on the state of the relay and child
286             # if there are active requests set it to 60, if not (e.g.
287             # idle keep-alive connections) to 30. If this is a forked
288             # child with no listener which should close after all
289             # requests are done close idle keep-alive connections faster,
290             # e.g. set timeout to 1
291             my $idle = ! $r->{conn}->open_requests;
292             my $timeout =
293             ! $idle ? 60 :
294             $exit_if_no_relays ? 1 :
295             30;
296             for my $fo (@{$r->{fds}}) {
297             next RELAY if $_->{didit} + $timeout > $now;
298             }
299             $r->xdebug("close because of timeout");
300             $r->close
301             }
302             }
303             );
304              
305             ############################################################################
306             # Filehandle
307             ############################################################################
308              
309             package App::HTTP_Proxy_IMP::Relay::FD;
310 1     1   2381 use Carp 'croak';
  1         4  
  1         72  
311 1     1   7 use Scalar::Util 'weaken';
  1         2  
  1         81  
312 1     1   7 use App::HTTP_Proxy_IMP::Debug;
  1         2  
  1         93  
313 1     1   613 use AnyEvent::Socket qw(tcp_connect format_address);
  1         21565  
  1         67  
314 1     1   8 use IO::Socket::SSL;
  1         2  
  1         10  
315              
316             use fields (
317 1         7 'dir', # direction 0,1
318             'fd', # file descriptor
319             'host', # destination hostname
320             'status', # bitmap of read_shutdown|write_shutdown|connected
321             'relay', # weak link to relay
322             'didit', # time of last activity (read/write)
323             'rbuf', # read buffer (read but not processed)
324             'rsub', # read handler
325             'rwatch', # AnyEvent watcher - undef if read is disabled
326             'wbuf', # write buffer (not yet written to socket)
327             'wsub', # write handler
328             'wwatch', # AnyEvent watcher - undef if write is disabled
329             'wsrc', # source of writes for stalled handling
330 1     1   158 );
  1         2  
331              
332             sub new {
333 0     0     my ($class,$dir,$fd,$relay,$connected) = @_;
334 0           my $self = fields::new($class);
335 0           $self->{dir} = $dir;
336 0           $self->{fd} = $fd;
337 0 0         $self->{status} = $connected ? 0b001 : 0;
338             #weaken( $self->{relay} = $relay );
339 0           $self->{relay} = $relay;
340 0           $self->{rbuf} = $self->{wbuf} = '';
341 0           return $self;
342             }
343              
344             sub xdebug {
345 0     0     my $self = shift;
346 0           my $conn = $self->{relay}{conn};
347 0 0         if ( my $xdebug = UNIVERSAL::can($conn,'xdebug') ) {
348 0           my $msg = "[$self->{dir}] ".shift(@_);
349 0           unshift @_,$conn,$msg;
350 0           goto &$xdebug;
351             } else {
352 0           goto &debug;
353             }
354             }
355              
356             sub close:method {
357 0     0     my $self = shift;
358 0           $self->xdebug("close");
359 0 0         if ( $self->{fd} ) {
360 0           $self->{fd} = undef;
361 0           delete $self->{relay}{fds}[$self->{dir}];
362 0           $self->{relay}->closeIfDone;
363             }
364 0           %$self = ();
365             }
366              
367             sub reset {
368 0     0     my $self = shift;
369 0           $self->xdebug("reset");
370 0 0         close($self->{fd}) if $self->{fd};
371             $self->{fd} =
372             $self->{rwatch} = $self->{rsub} =
373             $self->{wwatch} = $self->{wsub} =
374             $self->{host} =
375             $self->{wsrc} =
376 0           undef;
377 0           $self->{status} = $self->{didit} = 0;
378 0           $self->{rbuf} = $self->{wbuf} = '';
379 0           return 1;
380             }
381              
382             # attempt to shutdown fd.
383             # don't shutdown(1) if wbuf ne '' && ! $force
384             sub shutdown:method {
385 0     0     my ($self,$rw,$force) = @_;
386 0 0         my $write = $rw eq 'r' ? 0 : $rw eq 'w' ? 1 : $rw;
    0          
387 0 0         my $stat = $write ? 0b010 : 0b100;
388 0 0 0       return if $self->{status} & $stat && ! $force; # no change
389              
390 0           $self->{status} |= $stat;
391 0 0 0       if ( $write && $self->{wbuf} ne '' ) {
392             $self->xdebug("called shutdown $rw fn=".fileno($self->{fd}).
393 0           " wbuf.len=".length($self->{wbuf}));
394 0 0         return if ! $force; # will shutdown once all is written
395 0           $self->{wbuf} = ''; # drop rest
396 0           undef $self->{wsrc}; # don't re-enable, unclear state
397 0           undef $self->{wwatch};
398             }
399            
400 0           $self->xdebug("shutdown $rw fn=".fileno($self->{fd}));
401 0           shutdown($self->{fd},$write);
402             # shutdown on both sides -> close
403 0 0         if (( $self->{status} & 0b110 ) == 0b110 ) {
    0          
404 0           $self->xdebug( "close fn=".fileno($self->{fd})." because status $self->{status} done");
405 0           $self->close;
406             } elsif ( $write ) {
407 0           undef $self->{wwatch};
408             } else {
409 0           undef $self->{rwatch};
410             }
411              
412             # if all fd are closed, close the relay too
413 0           $self->{relay}->closeIfDone;
414              
415 0           return 1;
416             }
417              
418              
419             sub mask {
420 0     0     my ($self,$rw,$val) = @_;
421             #debug("$self->{dir} $self->{fd} fn=".fileno($self->{fd})." $rw=>$val");
422 0 0         if ( $rw eq 'r' ) {
    0          
423 0 0         if ( ! $val ) {
424             # disable read
425 0           undef $self->{rwatch};
426             } else {
427 0 0         $self->{status} & 0b100 and return 0; # read shutdown already
428 0   0 0     $self->{rsub} ||= sub { _read($self) };
  0            
429             $self->{rwatch} = AnyEvent->io(
430             fh => $self->{fd},
431             poll => 'r',
432             cb => ref($val) ? $val : $self->{rsub}
433 0 0         );
434             }
435             } elsif ( $rw eq 'w' ) {
436 0 0         if ( ! $val ) {
437             # disable write
438 0           undef $self->{wwatch};
439             } else {
440 0 0         $self->{status} & 0b010 and return 0; # write shutdown already
441 0   0 0     $self->{wsub} ||= sub { _writebuf($self) };
  0            
442             $self->{wwatch} = AnyEvent->io(
443             fh => $self->{fd},
444             poll => 'w',
445             cb => ref($val) ? $val : $self->{wsub}
446 0 0         );
447             }
448             } else {
449 0           croak("cannot set mask for $rw");
450             }
451 0           return 1;
452             }
453              
454             # write data, gets written from relay->send
455             sub write:method {
456 0     0     my ($self,$data,$from) = @_;
457 0           my $n = 0;
458 0 0         if ( $self->{wbuf} eq '' ) {
459             # no buffered data, set as buffer and try to write immediately
460 0           $self->{wbuf} = $data;
461 0   0       $n = _writebuf($self,$from) // return; # fatal?
462             } else {
463             # only append to buffer, will be written on write ready
464 0           $self->{wbuf} .= $data;
465             }
466              
467 0 0 0       if ( $self->{wbuf} ne ''
468             && ! $self->{wsrc}{$from}++ ) {
469             # newly stalled, disable reads on $from for now
470 0           $self->{relay}->mask($from, r=>0);
471             }
472 0           return $n;
473             }
474              
475             # gets called if wbuf is not empty, either from write or from callback
476             # when fd is writable again
477             sub _writebuf {
478 0     0     my $self = shift;
479             #debug("write $self fn=".fileno($self->{fd}));
480 0           my $n = syswrite($self->{fd},$self->{wbuf});
481             #debug("write(%s,%d) -> %s", $self->{dir},length($self->{wbuf}), (defined $n ? $n : $!));
482 0 0         if ( ! defined $n ) {
483             $self->{relay}->fatal("write($self->{dir}) failed: $!")
484 0 0 0       unless $!{EINTR} or $!{EAGAIN};
485 0           return;
486             }
487              
488 0           substr($self->{wbuf},0,$n,'');
489 0           $self->{didit} = AnyEvent->now;
490              
491 0 0         if ( $self->{wbuf} eq '' ) {
492             # wrote everything
493             #debug("all written to $self->{dir}");
494 0           undef $self->{wwatch};
495              
496 0 0         if ( $self->{status} & 0b100 ) {
497             # was marked for shutdown
498 0           shutdown($self->{fd},1);
499             # if all fd are closed, close the relay too
500 0           $self->{relay}->closeIfDone;
501             }
502             # enable read again on stalled fd
503 0 0         if ( my $src = $self->{wsrc} ) {
504 0           $self->{relay}->mask($_, r=>1) for (keys %$src);
505             }
506             } else {
507             # need to write more later
508             #debug("need to write more");
509 0           mask($self,w=>1);
510             }
511 0           return $n;
512             }
513              
514             # gets called if data are available on the socket
515             # but only, if we don't have unsent data in wbuf
516             # reads data into rbuf and calls connection->in
517             sub _read:method {
518 0     0     my $self = shift;
519             #debug("read $self fn=".fileno($self->{fd}));
520 0           my $n = sysread($self->{fd},$self->{rbuf},2**15,length($self->{rbuf}));
521             #debug("read done: ". (defined $n ? $n : $!));
522 0 0         if ( ! defined $n ) {
523 0 0 0       if ( ! $!{EINTR} and ! $!{EAGAIN} ) {
524             # complain only if we are inside a request
525             # timeouts after inactivity are normal
526             return $self->{relay}->fatal("read($self->{dir}) failed: $!")
527 0 0         if $self->{relay}{conn}->open_requests;
528              
529             # close connection
530 0           $self->xdebug("closing relay because of read error on $self->{dir}");
531 0           return $self->{relay}->close;
532             }
533 0           return;
534             }
535              
536 0           $self->{didit} = AnyEvent->now;
537             my $bytes = $self->{relay}{conn}
538 0           ->in($self->{dir},$self->{rbuf},!$n,$self->{didit});
539              
540             # fd/relay closed from within in() ?
541 0 0         defined $self->{fd} or return;
542              
543 0 0         if ( $bytes ) {
544             # connection accepted $bytes
545 0           substr($self->{rbuf},0,$bytes,'');
546             }
547              
548             return $self->{relay}->fatal(
549             "connection should have taken all remaining bytes on eof")
550 0 0 0       if !$n && $self->{rbuf} ne '';
551              
552 0 0         $self->shutdown('r') if ! $n;
553             }
554              
555             sub connect:method {
556 0     0     my ($self,$host,$port,$callback,$reconnect) = @_;
557              
558             # down existing connection if we should connect to another host
559             $self->reset if $self->{fd} and
560 0 0 0       ( $reconnect or $self->{host}||'' ne "$host.$port" );
      0        
561              
562             # if we have a connection already, keep it
563 0 0         if ( $self->{status} & 0b001 ) { # already connected
564 0           $callback->();
565 0           return 1;
566             }
567              
568             # (re)connect
569 0           $self->xdebug("connecting to $host.$port");
570             # async dns lookup + connect
571             App::HTTP_Proxy_IMP::Relay::DNS::lookup($host, sub {
572 0 0   0     $self->{relay} or return; # relay already closed
573 0 0         if ( my $addr = shift ) {
574             tcp_connect($addr,$port, sub {
575 0 0         if ( my $fd = shift ) {
576 0 0         $self->{relay} or return; # relay already closed
577 0           $self->{fd} = $fd;
578 0           $self->{status} = 0b001;
579 0           $self->{host} = "$host.$port";
580 0           $self->xdebug("connect done");
581 0           $self->mask( r => 1 );
582 0           $callback->();
583             } else {
584 0           App::HTTP_Proxy_IMP::Relay::DNS::uncache($host,$addr);
585 0 0         $self->{relay} or return; # relay already closed
586 0           $self->{relay}->fatal("connect to $host.$port failed: $!");
587             }
588 0           });
589             } else {
590             $self->{relay}->fatal(
591 0           "connect to $host.$port failed: no such host (DNS)");
592             }
593 0           });
594 0           return -1;
595             }
596              
597             sub startssl {
598 0     0     my $self = shift;
599             $self->{rbuf} eq '' or return
600 0 0         $self->{relay}->fatal("read buf $self->{dir} not empty before starting SSL: '$self->{rbuf}'");
601             $self->{wbuf} eq '' or return
602 0 0         $self->{relay}->fatal("write buf $self->{dir} not empty before starting SSL: '$self->{wbuf}'");
603              
604 0 0         my $callback = @_%2 ? pop(@_):undef;
605 0           my %sslargs = @_;
606             IO::Socket::SSL->start_SSL( $self->{fd},
607 0 0         %sslargs,
608             SSL_startHandshake => 0,
609             ) or die "failed to upgrade socket to SSL";
610             my $sub = $sslargs{SSL_server}
611 0 0         ? \&IO::Socket::SSL::accept_SSL
612             : \&IO::Socket::SSL::connect_SSL;
613 0           _ssl($self,$sub,$callback,\%sslargs);
614             }
615              
616             sub _ssl {
617 0     0     my ($self,$sub,$cb,$sslargs) = @_;
618 0 0         if ( $sub->($self->{fd}) ) {
    0          
    0          
619 0           $self->xdebug("ssl handshake success");
620 0 0         $cb->($self) if $cb;
621             } elsif ( $!{EAGAIN} ) {
622             # retry
623             my $dir =
624             $SSL_ERROR == SSL_WANT_READ ? 'r' :
625             $SSL_ERROR == SSL_WANT_WRITE ? 'w' :
626 0 0         return $self->{relay}->fatal( "unhandled $SSL_ERROR on EAGAIN" );
    0          
627 0     0     $self->mask( $dir => sub { _ssl($self,$sub,$cb,$sslargs) });
  0            
628             } elsif ( $sslargs->{SSL_server} ) {
629 0           return $self->{relay}->fatal( "error on accept_SSL: $SSL_ERROR|$!" );
630             } else {
631             return $self->{relay}->fatal(
632 0           "error on connect_SSL to $sslargs->{SSL_verifycn_name}: $SSL_ERROR|$!" );
633             }
634             }
635              
636              
637             ############################################################################
638             # DNS cache
639             ############################################################################
640              
641             package App::HTTP_Proxy_IMP::Relay::DNS;
642 1     1   2285 use AnyEvent::DNS;
  1         2  
  1         28  
643 1     1   5 use Socket qw(AF_INET AF_INET6 inet_pton);
  1         3  
  1         327  
644              
645             my %cache;
646             sub uncache {
647 0     0     my ($host,$addr) = @_;
648 0 0         my $e = $cache{lc($host)} or return;
649 0           @$e = grep { $_ ne $addr } @$e;
  0            
650 0 0         delete $cache{lc($host)} if !@$e;
651             }
652              
653             sub lookup {
654 0     0     my ($host,$cb) = @_;
655 0           $host = lc($host);
656              
657 0 0 0       if ( my $e = $cache{$host} ) {
    0          
658 0           return $cb->(@$e);
659             } elsif ( inet_pton(AF_INET,$host) || inet_pton(AF_INET6,$host) ) {
660 0           return $cb->($host);
661             }
662              
663             AnyEvent::DNS::a($host,sub {
664 0 0   0     if ( @_ ) {
665 0           $cache{$host} = [ @_ ];
666 0           return $cb->(@_);
667             }
668              
669             # try AAAA
670             AnyEvent::DNS::aaaa($host,sub {
671 0 0         $cache{$host} = [ @_ ] if @_;
672 0           return $cb->(@_);
673 0           });
674 0           });
675             }
676              
677             1;