Reputation: 2697
(Edited to provide a reduced test case as per comments below)
I'm facing an odd situation, where if I fork a "WSS" connection to send a message, the socket gets closed when the child exits. However, when I fork to process a "WS" connection, the connection remains open when the child exits.
Server code:
use Net::WebSocket::Server;
use IO::Socket::SSL;
$SIG{CHLD}='IGNORE';
my $enable_ssl = 1; # If you make this one the problem reveals itself
# you need to point this to your own certs
my $ssl_cert_file = "/etc/letsencrypt/live/mydomain/fullchain.pem";
my $ssl_key_file = "/etc/letsencrypt/live/mydomain/privkey.pem";
# To show the problem, all I'm doing is I'm forking and sending current time
sub process {
my $serv = shift;
my $pid = fork();
if ($pid == 0 ) {
print ("fork start\n");
$_->send_utf8(time) for $serv->connections;
print ("fork end\n");
exit 0;
}
}
my $ssl_server;
if ($ssl_enable) {
$ssl_server = IO::Socket::SSL->new(
Listen => 10,
LocalPort => 9000,
Proto => 'tcp',
Reuse => 1,
ReuseAddr => 1,
SSL_cert_file => $ssl_cert_file,
SSL_key_file => $ssl_key_file
);
}
Net::WebSocket::Server->new(
listen => $enable_ssl? $ssl_server: 9000,
tick_period=>5,
on_tick=> sub {
my ($serv) = @_;
process($serv);
#$_->send_utf8(time) for $serv->connections;
},
)->start;
Here is the client code:
my $client = AnyEvent::WebSocket::Client->new;
# replace with your server
$client->connect("wss://myserver:9000")->cb(sub {
our $connection = eval { shift->recv };
if($@) {
print ("connection error");
warn $@;
return;
}
# recieve message from the websocket...
$connection->on(each_message => sub {
my($connection, $message) = @_;
my $msg = $message->body;
print ("GOT $msg\n");
});
});
AnyEvent->condvar->recv;
Expected behavior
The client will keep displaying timestamps
Observed behavior
The client gets the very first message and prints it. When the server exits its fork, the client stops getting any more messages and the connection terminates
How to make it work
We have two options:
Therefore, my conclusion is SSL+fork == problem.
Thoughts?
Upvotes: 1
Views: 701
Reputation: 123340
Therefore, my conclusion is SSL+fork == problem.
Yes, the problem is first doing the SSL handshake and then forking. This way a user space SSL state will be created in the parent and with the fork duplicated in the child and these two SSL states get out of sync on the first SSL data send or received. This means it is not possible to deal with the same SSL socket from two processes.
If it is really necessary that both parent and child process use the same SSL connection to the peer than the child must use the parent as a "proxy", i.e. the child does not communicate directly with the SSL peer but the child needs to communicate in plain with the parent (for example by using a socketpair) which then can forward the communication to the SSL peer. This way the SSL state is only maintained in the parent process.
But given that only a single message should be handled at a time for a single connection it might be possible instead to not fork for a single tick but fork a child for each connection which handles then all messages in this connection. In this case the SSL handshake can be done in full in the child by listening in the parent to a TCP and not SSL socket, forking on_connect
and then upgrading the connection to SSL in the client using IO::Socket::start_SSL
. This would also have the advantage that the blocking SSL handshake (which involves several round trips and thus takes some time) would be done in the forked child and would not make the parent block.
Upvotes: 3