App-HTTP_Proxy_IMP

 view release on metacpan or  search on metacpan

lib/App/HTTP_Proxy_IMP/Relay.pm  view on Meta::CPAN

}

# fatal problem - close connection
sub fatal {
    my ($self,$reason) = @_;
    warn "[fatal] ".( $self->{conn} && $self->{conn}->id || 'noid')." $reason\n";
    $self->close;
    return 0;
}

sub connect:method {
    my ($self,$to,$host,$port,$callback,$reconnect) = @_;
    my $fo = $self->{fds}[$to] ||= App::HTTP_Proxy_IMP::Relay::FD->new($to,undef,$self);
    $fo->connect($host,$port,$callback,$reconnect);
}

# masks/unmasks fd for dir, rw = r|w
sub mask {
    my ($self,$dir,$rw,$v) = @_;
    my $fd = $self->{fds}[$dir] or do {
	warn "fd dir=$dir does not exists\n";
	return;
    };
    $fd->mask($rw,$v);
}

sub fd {
    my ($self,$dir) = @_;
    return $self->{fds}[$dir];
}

# send some data via fd dir
sub forward {
    my ($self,$from,$to,$data) = @_;
    my $fo = $self->{fds}[$to] or return
	$self->fatal("cannot write to $to - no such fo");
    $self->xdebug("$from>$to - forward %d bytes",length($data));
    $fo->write($data,$from);
}

# ssl interception, e.g. upgrade both client and server to SSL sockets,
# where I can read/write unencrypted data
sub sslify {
    my ($self,$from,$to,$hostname,$callback) = @_;
    my $conn = $self->{conn} or return;
    my $mitm = $conn->{mitm} or return; # no MITM needed

    # destroy the current connection object and create a new obne
    $conn = $self->{conn} = $conn->clone;
    $conn->{intunnel} = 1;
    
    my $sfo = $self->{fds}[$from] or return
	$self->fatal("cannot startssl $from - no such fo");

    # stop handling all data
    $self->mask($to,r=>0);
    $self->mask($from,r=>0);
    weaken( my $wself = $self );

    my %sslargs = (
	SSL_verifycn_name => $hostname,
	SSL_verifycn_schema => 'http',
	SSL_hostname => $hostname, # SNI
	$conn->{capath} ? (
	    SSL_verify_mode => SSL_VERIFY_PEER,
	    ( -d $conn->{capath} ? 'SSL_ca_path' : 'SSL_ca_file' ), 
	    $conn->{capath}
	):( 
	    SSL_verify_mode => SSL_VERIFY_NONE 
	)
    );
    $sfo->startssl( %sslargs, sub {
	my $sfo = shift;
	my ($cert,$key) = $mitm->clone_cert($sfo->{fd}->peer_certificate);
	my $cfo = $wself->{fds}[$to] or return
	    $wself->fatal("cannot startssl $to - no such fo");
	$cfo->startssl(
	    SSL_server => 1,
	    SSL_cert => $cert,
	    SSL_key  => $key,
	    sub {
		# allow data again
		$self->mask($to,r=>1);
		$self->mask($from,r=>1);
		$callback->() if $callback;
	    }
	);
    });
}

# closes relay
sub close:method {
    my $self = shift;
    #debug("close $self");
    undef $self->{conn};
    @relays = grep { !$_ or $_ != $self } @relays;
    $_ && $_->close for @{$self->{fds}};
    @{$self->{fds}} = ();
}

# shutdown part of relay
sub shutdown:method {
    my ($self,$dir,$rw,$force) = @_;
    my $fo = $self->{fds}[$dir] or return;
    $fo->shutdown($rw,$force);
}

# check for condition, where we cannot transfer anymore data:
# - nowhere to read and no open requests
# - nowhere to write too
sub closeIfDone {
    my $self = shift;
    my $sink = my $drain = '';
    for my $fo (@{$self->{fds}}) {
	$fo && $fo->{fd} or next;
	return if $fo->{rbuf} ne ''; # has unprocessed data
	return if $fo->{wbuf} ne ''; # has unwritten data
	$drain .= $fo->{dir} if not $fo->{status} & 0b100; # not read-closed
	$sink  .= $fo->{dir} if not $fo->{status} & 0b010; # not write-closed
    }

    if ( $sink eq '' ) {      # nowhere to write
	$DEBUG && $self->xdebug( "close relay because all fd done sink='$sink' ");
	# close relay
	return $self->close;
    }

    if ( $drain ne '01' ) {  # no reading from both sides
	my $conn = $self->{conn};

lib/App/HTTP_Proxy_IMP/Relay.pm  view on Meta::CPAN

	$self->{relay} or return; # relay already closed
	if ( my $addr = shift ) {
	    tcp_connect($addr,$port, sub {
		if ( my $fd = shift ) {
		    $self->{relay} or return; # relay already closed
		    $self->{fd} = $fd;
		    $self->{status} = 0b001;
		    $self->{host} = "$host.$port";
		    $self->xdebug("connect done");
		    $self->mask( r => 1 );
		    $callback->();
		} else {
		    App::HTTP_Proxy_IMP::Relay::DNS::uncache($host,$addr);
		    $self->{relay} or return; # relay already closed
		    $self->{relay}->fatal("connect to $host.$port failed: $!");
		}
	    });
	} else {
	    $self->{relay}->fatal(
		"connect to $host.$port failed: no such host (DNS)");
	}
    });
    return -1;
}

sub startssl {
    my $self = shift;
    $self->{rbuf} eq '' or return 
	$self->{relay}->fatal("read buf $self->{dir} not empty before starting SSL: '$self->{rbuf}'");
    $self->{wbuf} eq '' or return 
	$self->{relay}->fatal("write buf $self->{dir} not empty before starting SSL: '$self->{wbuf}'");

    my $callback = @_%2 ? pop(@_):undef;
    my %sslargs = @_;
    IO::Socket::SSL->start_SSL( $self->{fd},
	%sslargs,
	SSL_startHandshake => 0,
    ) or die "failed to upgrade socket to SSL";
    my $sub = $sslargs{SSL_server} 
	? \&IO::Socket::SSL::accept_SSL
	: \&IO::Socket::SSL::connect_SSL;
    _ssl($self,$sub,$callback,\%sslargs);
}

sub _ssl {
    my ($self,$sub,$cb,$sslargs) = @_;
    if ( $sub->($self->{fd}) ) {
	$self->xdebug("ssl handshake success");
	$cb->($self) if $cb;
    } elsif ( $!{EAGAIN} ) {
	# retry
	my $dir = 
	    $SSL_ERROR == SSL_WANT_READ ? 'r' :
	    $SSL_ERROR == SSL_WANT_WRITE ? 'w' :
	    return $self->{relay}->fatal( "unhandled $SSL_ERROR on EAGAIN" );
	$self->mask( $dir => sub { _ssl($self,$sub,$cb,$sslargs) });
    } elsif ( $sslargs->{SSL_server} ) {
	return $self->{relay}->fatal( "error on accept_SSL: $SSL_ERROR|$!" );
    } else {
	return $self->{relay}->fatal( 
	    "error on connect_SSL to $sslargs->{SSL_verifycn_name}: $SSL_ERROR|$!" );
    }
}


############################################################################
# DNS cache
############################################################################

package App::HTTP_Proxy_IMP::Relay::DNS;
use AnyEvent::DNS;
use Socket qw(AF_INET AF_INET6 inet_pton);

my %cache;
sub uncache {
    my ($host,$addr) = @_;
    my $e = $cache{lc($host)} or return;
    @$e = grep { $_ ne $addr } @$e;
    delete $cache{lc($host)} if !@$e;
}

sub lookup {
    my ($host,$cb) = @_;
    $host = lc($host);

    if ( my $e = $cache{$host} ) {
	return $cb->(@$e);
    } elsif ( inet_pton(AF_INET,$host) || inet_pton(AF_INET6,$host) ) {
	return $cb->($host);
    }

    AnyEvent::DNS::a($host,sub {
	if ( @_ ) {
	    $cache{$host} = [ @_ ];
	    return $cb->(@_);
	}

	# try AAAA
	AnyEvent::DNS::aaaa($host,sub {
	    $cache{$host} = [ @_ ] if @_;
	    return $cb->(@_);
	});
    });
}

1;



( run in 0.436 second using v1.01-cache-2.11-cpan-39bf76dae61 )