ferrency has asked for the wisdom of the Perl Monks concerning the following question:

Hello fellow monks.

I am currently working on a proxy server, which opens and maintains an SSL connection, accepts normal TCP connections, and then proxies information in both directions in a protocol-independant way (though it is limited to text line oriented and not binary protocols).

The technique I'm using for proxying is similar to a recipe in the Perl Cookbook: chapter 17.10. This recipe uses forking to build a bidirectional TCP client. When a connection is accepted, the process forks. The parent does a blocking read on the connecting socket, and blocking writes to the SSL socket. The child does a blocking read on the SSL socket and blocking writes to the connecting socket. To quote the cookbook, "To accomplish the same thing using just one process is remarkably more difficult."

Here is a (pseudo) code sample which demonstrates the technique for a single-connection proxy:

my $asock = new IO::Socket::INET (..., Listen => 5); # accepting socket my $sslsock = new IO::Socket::SSL(..., PeerAddr => ...,); # SSL socket while (1) { my $csock = $psock->accept(); # Accept $csock, connecting socket if (my $p = fork()) { my $line; $sslsock->print($line) while (defined($line = <$csock>)); # Now the connecting socket disconnected kill 1, $p; wait (); } else { my $line; $csock->print($line) while (defined($line = <$sslsock>)); # Lost SSL connection... this may cause our parent to block # forever, but this is just for demonstration of the Real prob +lem... exit; } }
I open one SSL socket, and keep it open for use with multiple connecting sockets. This cuts down on the overhead of establishing SSL connections, and is really the whole point of using this proxy server in the first place. So rebuilding the SSL socket for every connection isn't really an option :)

The technique above works, if $sslsock is an IO::Socket::INET instead of an IO::Socket::SSL. It also works for the first connection, using an SSL socket for the outgoing socket. But after the child process exits (or is killed by its parent), something deep within the SSL object is broken, and the $sslsock stops working correctly. This is not a problem with IO::Socket::SSL, but with Net::SSLeay or something deeper in the C libraries- I rewrote the server using Net::SSLeay instead of IO::Socket::SSL and the same problem exists there.

So, after all this explanation, my questions are:

  1. Does anyone have any ideas on how exactly the SSL connection is broken when the child dies?
  2. Is there a way to fix it or prevent it from breaking in the first place?
  3. In the latest IO::Socket::SSL I see support for an OpenSSL module instead of Net::SSLeay. Where is this module? Not in CPAN, it seems.
  4. Got any better ideas that Do work?
(Version notes: FreeBSD 4.1.1-stable, Perl 5.6.0, Net::SSLeay 1.12, IO::Socket::SSL 0.80, OpenSSL 0.96b)

Thanks in advance for any help you can give me on this!

Alan

Replies are listed 'Best First'.
Re: TCP - SSL proxy problems
by ferrency (Deacon) on Jan 19, 2002 at 01:29 UTC
    Hello,

    While waiting for answers to my earlier question, I implemented what I think is a working version of the "remarkably more difficult" solution, which is shown below. I'd like more eyes on this code, however, becuase "it has worked all afternoon" isn't enough to convince me there aren't potential race conditions or deadlocks lurking in there somewhere. Hopefully if it's not too broken, someone else can derive some value from it.

    The basic technique is as follows:

    • Open a listening socket and an SSL socket
    • Accept a connection
    • While both connecting and SSL socket are open and there are no error conditions:
      • select on all sockets for reading; select for writing on all sockets we have buffered data for.
      • read data and buffer it for all sockets which are readable; add write sockets to our select list for the destination socket for each read socket.
      • write data to all writable sockets which have buffered data waiting; remove these sockets from our write-select list.
    And now for the code:
    my $asock = new IO::Socket::INET (..., Listen => 5); # accepting socket my $sslsock = new IO::Socket::SSL(..., PeerAddr => ...,); # SSL socket $sslsock->blocking(0); while (1) { my $csock = $asock->accept(); # Accept $csock, connecting socket $csock->blocking(0); my $readsel = new IO::Select($csock, $sslsock); my $writesel = new IO::Select(); # For convenience: translation table for socket to proxy from/to my %proxy = ( $csock => $sslsock, $sslsock => $csock, ); my %buffer; OUTER: while (1) { my ($read, $write, $err) = IO::Select::select($readsel, $writesel, $readsel); foreach (@$read) { my $b; while (defined(my $line = <$_>)) { $b .= $line; } if (!defined($b) && ($_ == $csock or !$_->connected())) { # SSL socket seems to return undef sometimes even # while staying connected... INET socket returns undef + if # read and disconnected print "read undef: lost socket connection\n"; last OUTER; } $buffer{$proxy{$_}} .= $b; $writesel->add($proxy{$_}); } foreach (@$write) { if (exists($buffer{$_})) { # Do we need to handle partial writes here? $_->print($buffer{$_}); delete $buffer{$_}; } $writesel->remove($_); } if (@$err) { print "Error conditions: ", join (', ', @$err), "\n"; last OUTER; } } }
    Does anyone see anything wrong with this? If not, I'm good to go, thanks :) But I still think there should be a more simple perlish solution to attaching the two sockets together.

    Thanks,

    Alan