Skip to content
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

Closing TLS connections #1876

Open
kensanata opened this issue Nov 9, 2021 · 4 comments
Open

Closing TLS connections #1876

kensanata opened this issue Nov 9, 2021 · 4 comments

Comments

@kensanata
Copy link

kensanata commented Nov 9, 2021

  • Mojolicious version: 9.22
  • IO::Socket::SSL version: 2.072
  • Perl version: 5.32.0
  • Operating system: Linux 4.19.0-14-amd64

Steps to reproduce the behavior

Here's a small server using TLS, assuming you have cert.pem and key.pem in the working directory:

use Mojo::IOLoop;
Mojo::IOLoop->server(
  {
    port => 3000, tls => 1,
    tls_cert => 'cert.pem',
    tls_key => 'key.pem'
  } =>
  sub {
    my ($loop, $stream) = @_;
    $stream->on(
      read => sub {
	my ($stream, $bytes) = @_;
	$stream->write("HTTP/1.1 200 OK\r\n");
	$stream->write("Content-Type: text/plain\r\n");
	$stream->write("\r\n");
	$stream->write("Hello\n");
	$stream->close_gracefully();
      });
  });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;

Test it using gnutls-cli, using --insecure because it's self-signed:

(echo; sleep 1) | gnutls-cli --insecure localhost:3000

Expected behavior

...
HTTP/1.1 200 OK
Content-Type: text/plain

Hello
- Peer has closed the GnuTLS connection

Actual behavior

...
HTTP/1.1 200 OK
Content-Type: text/plain

Hello
*** Fatal error: The TLS connection was non-properly terminated.
*** Server has terminated the connection abnormally.

Potential fix

I assume the connection is not closed correctly because the server is not sending a close_notify message; IO::Socket::SSL does that if you close it explicitly. We could explicitly close the handle in Mojo::IOLoop::Stream close, as suggested in #1875.

@gordon-fish
Copy link

It looks like the problem is that you're cutting off the whole connection when you call close_gracefully from the read handler. That handler is processing HTTP communication; TLS is outside of that scope, being handled by the framework that called this handler.

This cutting off of the stream is preventing the normal wrap-up of the TLS connection. The HTTP communication is encapsulated in TLS, so that needs to be handled correctly, and that is what Mojo is doing. You can't just ->close immediately after the HTTP response is sent like you might with naked HTTP.

@kensanata
Copy link
Author

kensanata commented Nov 12, 2021

I don't know. Imagine a client and server, messages flowing back and forth. This is not HTTP but some other protocol. What happens if the server decides that the connection needs to be closed? Here's a client and a server. As long as the server is getting a number, it sends back that number + 1 and the client prints it. But at some point the client sends a non-number and the server does what? My idea is that the server closes the connection. How else would you handle this?

use strict;
use Mojo::IOLoop;

# The +1 server
Mojo::IOLoop->server(
  {port => 3000, tls => 1} =>
  sub {
    my ($loop, $stream) = @_;
    $stream->on(
      read => sub {
	my ($stream, $bytes) = @_;
	if ($bytes =~ /^(\d+)/) {
	  $stream->write($1 + 1);
	  $stream->write("\n");
	} else {
	  warn "The +1 server did not get a number!\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";
	$handle = $stream->{handle};
	if ($bytes < 10) {
	  $stream->write($bytes);
	} else {
	  $stream->write("error!");
	}
	$stream->write("\r\n");
      });
    $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("1\r\n");
  });

Mojo::IOLoop->timer(3 => sub { Mojo::IOLoop->stop });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;

Output:

2
3
4
5
6
7
8
9
10
The +1 server did not get a number!
Closed
IO::Socket::SSL=GLOB(0x55e5460de8e8)
94443211232416
Shutdown: no

@gordon-fish
Copy link

@kensanata

Mojo::IOLoop::Stream has a ->stop method, that appears to be exactly for this purpose, which is documented as "Stop watching for new data on the stream."

So instead of $stream->close_gracefully;, you would want to use $stream->stop; This tells the loop that's managing the connection that this conversation is done and that it can wrap things up on the TLS side.

@kensanata
Copy link
Author

kensanata commented Nov 13, 2021

@gordon-fish Interesting, thank you. When I replace close_carefully with stop the client doesn't get a close event. I don't think I care about that, but it's something to keep in mind. Thus, the output is this:

2
3
4
5
6
7
8
9
10
The +1 server did not get a number!

So I rewrote that little +1 echo server:

use strict;
use Mojo::IOLoop;

# The +1 server
Mojo::IOLoop->server(
  {port => 3000, tls => 1} =>
  sub {
    my ($loop, $stream) = @_;
    $stream->on(
      read => sub {
	my ($stream, $bytes) = @_;
	if ($bytes =~ /^(\d+)/) {
	  $stream->write("> ");
	  $stream->write($1 + 1);
	  $stream->write("\n");
	} else {
	  warn "The +1 server did not get a number!\n";
	  $stream->stop;
	}
      });
  });
print("gnutls-cli --insecure 127.0.0.1:3000 and type lines starting with numbers\n");
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;

I start it, and it prints the line about using gnutls-cli, which I do. This is what the conversation looks like:

1
> 2
2
> 3
a
b
c
3
4
5
*** Fatal error: Error in the pull function.
*** Server has terminated the connection abnormally.

When I send a to the server, the server prints correctly:

The +1 server did not get a number!

But the client doesn't get notified. I can keep typing, except there is no effect. So it seems that something is still amiss.

And adding a close_carefully after the stop doesn't work, not surprisingly:

1
> 2
2
> 3
3
> 4
a
*** Fatal error: The TLS connection was non-properly terminated.
*** Server has terminated the connection abnormally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants