-
Notifications
You must be signed in to change notification settings - Fork 582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Close handle when closing the stream #1875
base: main
Are you sure you want to change the base?
Conversation
Your patch will prevent the stream->on_close event to be handled properly ! (IMHO) |
I'm not sure what you mean. Are you suggesting I should change the order? The following also works for my purpose: sub close {
my $self = shift;
return unless my $reactor = $self->reactor;
return unless my $handle = delete $self->timeout(0)->{handle};
$reactor->remove($handle);
$self->emit('close');
$handle->close; # ✨ new ✨
} I'll be happy to change it, if that's what you mean.
As Mojolicious comes with built-in self-signed certificates, this is not a problem, and the Gemini protocol explicitly supports self-signed certificates. The code works as-is, as a Gemini server (not as a HTTPS server, of course). My actual project is Phoebe, where all of this is taken care of, of course. |
I missed the API doc referencing the builtin cert.
Why would i stop the stream at the read event, when it can be done by using the emitted "close" event ? |
Does it work for you? use Mojo::IOLoop;
Mojo::IOLoop->server(
{port => 1965, tls => 1} =>
sub {
my ($loop, $stream) = @_;
$stream->on(
read => sub {
my ($stream, $bytes) = @_;
# $stream->write("HTTP/1.1 200 OK\r\n\r\n");
$stream->write("20 text/plain\r\n");
$stream->write("Hello\n");
$stream->close_gracefully();
});
# ✨ new ✨
$stream->on(
close => sub {
my ($stream) = @_;
$stream->stop();
});
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; Test: (echo; sleep 1) | gnutls-cli --insecure localhost:1965 Result:
I'm not sure how the reactor watch would end up calling close on the handle, though. I tried an alternative, which also doesn't work: use Mojo::IOLoop;
Mojo::IOLoop->server(
{port => 1965, tls => 1} =>
sub {
my ($loop, $stream) = @_;
$stream->on(
read => sub {
my ($stream, $bytes) = @_;
# $stream->write("HTTP/1.1 200 OK\r\n\r\n");
$stream->write("20 text/plain\r\n");
$stream->write("Hello\n");
$stream->close_gracefully();
});
# ✨ new ✨
$stream->on(
close => sub {
my ($stream) = @_;
$stream->handle->close();
});
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; Test: (echo; sleep 1) | gnutls-cli --insecure localhost:1965 Result:
And on the server:
By the time the close call runs, the handle is already gone. |
As an aside, I think the default behaviour is simply wrong. If there is a different fix to the problem, then that's great. But I think the issue needs a fix because the RFC demands correct closing of TLS connections, and IO::Socket::SSL has the capability of doing that. You could argue that perhaps IO::Socket::SSL is the correct place to fix this issue? That I do not know. |
Seems like something that could be tested with a unit test. |
It appears that IO::Socket::SSL explicitly stops the normal shutdown handshake happening if ->close gets called implicitly via DESTROY rather than directly via user code: https://metacpan.org/dist/IO-Socket-SSL/source/lib/IO/Socket/SSL.pm#L2123 I'm not sure whether that makes sense but it looks like to do graceful shutdown either IO::Socket::SSL needs to change or Mojo needs to call the close method itself. ... thinking about it this feels kinda like a DBI InactiveDestroy type thing except with an excessively big hammer applied to achieve the goal ... which, if so, probably means the current SSL.pm behaviour can't be changed without an extra flag but doesn't leave me any less sure what would be "right" here. |
use strict;
use Mojo::IOLoop;
Mojo::IOLoop->server(
{port => 3000, tls => 1} =>
sub {
my ($loop, $stream) = @_;
$stream->on(
read => sub {
my ($stream, $bytes) = @_;
$stream->write("Hello\n");
$stream->close_gracefully();
});
});
my $handle;
Mojo::IOLoop->client(
{port => 3000, tls => 1, tls_options => { SSL_verify_mode => 0x00 }} =>
sub {
my ($loop, $err, $stream) = @_;
$stream->on(
read =>
sub {
my ($stream, $bytes) = @_;
print "$bytes";
# keep a reference the IO::Socket::SSL
$handle = $stream->{handle};
});
$stream->on(
close =>
sub {
my ($stream) = @_;
print "Closed\n";
print $handle . "\n";
my $ssl = ${*$handle}{_SSL_object};
print "$ssl\n";
# Returns the shutdown mode of $ssl.
# to decode the return value (bitmask) use:
# 0 - No shutdown setting, yet
# 1 - SSL_SENT_SHUTDOWN
# 2 - SSL_RECEIVED_SHUTDOWN
# See <http://www.openssl.org/docs/ssl/SSL_set_shutdown.html>
print "Shutdown: " . ((Net::SSLeay::get_shutdown($ssl) & 2) ? "yes" : "no") . "\n";
});
$stream->write("\r\n");
});
Mojo::IOLoop->timer(3 => sub { Mojo::IOLoop->stop });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running; When I run it:
When I close the handle before emitting the close event, this doesn't work, of course, so using this code in Stream.pm: sub close {
my $self = shift;
return unless my $reactor = $self->reactor;
return unless my $handle = delete $self->timeout(0)->{handle};
$reactor->remove($handle);
$self->emit('close');
$handle->close;
} I get the following result:
|
009eba7
to
12cc32a
Compare
I'm still interested in getting this merged. |
The PR is still missing a unit test and needs to be rebased. |
At the end of the connection, each side sends a close_notify alert to inform the peer that the connection is closed. IO::Socket::SSL does that, if its close method is called.
I think I need help running the existing tests. I'm assuming this test should go into perl Makefile.PL
make test
# Result: PASS
TEST_TLS=1 make test TEST_FILES=t/mojo/ioloop_tls.t I'm getting: "Can't call method "write" on an undefined value at t/mojo/ioloop_tls.t line 98." (And then it hangs.) Regenerating the certs in |
I can only run the beginning of the test, so bear with me. I'm running: TEST_TLS=1 make test TEST_FILES=t/mojo/ioloop_tls.t TEST_VERBOSE=1 Without the change to
With the change to
|
Summary
At the end of the connection, each side sends a close_notify alert to inform the peer that the connection is closed. IO::Socket::SSL does that, if its close method is called. This change to Mojo::IOLoop::Stream makes sure close() is called.
Motivation
I'm maintaining a Gemini server. The spec was recently updated to make the close_notify call at the end of a TLS connection mandatory:
RFC 5246 section 7.2.1:
RFC 8446 section 6.1:
My server kept failing the test.
Here's a server using Mojo::IOLoop that fails the test:
Run test using gnutls-cli:
Here is a IO::Socket::SSL server that passes the test:
Run test using gnutls-cli:
The key is the close call at the end. If I remove it, the server still works, but the test for client_notify now fails.
The Mojo::IOLoop based server passes the test if I make the change I'm proposing to merge.
References
None