This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: ssl unclean shutdown
Type: behavior Stage: patch review
Components: Library (Lib) Versions: Python 3.3, Python 3.4, Python 2.7
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: Hiroaki.Kawai, neologix, pitrou
Priority: normal Keywords: patch

Created on 2013-04-09 04:45 by Hiroaki.Kawai, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
python27.patch Hiroaki.Kawai, 2013-04-09 04:45 review
Messages (15)
msg186372 - (view) Author: Hiroaki Kawai (Hiroaki.Kawai) Date: 2013-04-09 04:45
When using ssl module, I sometimes get unexpected error. The observed error varies in different situations. After the investigation, I found the reason was that ssl shutdown was not performed and sometimes RST was sent over the network instead of FIN.

I created a patch against 2.7 branch.
msg186399 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-04-09 13:15
I don't think your patch is right:

- calling unwrap() already shuts down the SSL layer; this is the right way to do it and is documented as such: "Performs the SSL shutdown handshake, which removes the TLS layer from the underlying socket, and returns the underlying socket object"

- shutdown() right now isn't blocking; if you add a call to SSL shutdown, it can either block or fail with EAGAIN or similar, which is something people won't expect

- close() should simply close the file descriptor, like on a regular socket (if you call socket.close(), it won't shutdown the TCP connection, especially if there's another file descriptor referencing the same connection)

As for Modules/_ssl.c, the case where SSL_shutdown() returns 0 is already handled:

        if (err == 0) {
            /* Don't loop endlessly; instead preserve legacy
               behaviour of trying SSL_shutdown() only twice.
               This looks necessary for OpenSSL < 0.9.8m */
            if (++zeros > 1)
                break;
            /* Shutdown was sent, now try receiving */
            self->shutdown_seen_zero = 1;
            continue;
        }

... so I don't think anything more is necessary.

So I think things are fine right now and your patch shouldn't be applied.
msg186415 - (view) Author: Hiroaki Kawai (Hiroaki.Kawai) Date: 2013-04-09 14:36
Please run the test so that you'll see the problem.

2013/4/9 Antoine Pitrou <report@bugs.python.org>

>
> Antoine Pitrou added the comment:
>
> I don't think your patch is right:
>
> - calling unwrap() already shuts down the SSL layer; this is the right way
> to do it and is documented as such: "Performs the SSL shutdown handshake,
> which removes the TLS layer from the underlying socket, and returns the
> underlying socket object"
>
> - shutdown() right now isn't blocking; if you add a call to SSL shutdown,
> it can either block or fail with EAGAIN or similar, which is something
> people won't expect
>
> - close() should simply close the file descriptor, like on a regular
> socket (if you call socket.close(), it won't shutdown the TCP connection,
> especially if there's another file descriptor referencing the same
> connection)
>
> As for Modules/_ssl.c, the case where SSL_shutdown() returns 0 is already
> handled:
>
>         if (err == 0) {
>             /* Don't loop endlessly; instead preserve legacy
>                behaviour of trying SSL_shutdown() only twice.
>                This looks necessary for OpenSSL < 0.9.8m */
>             if (++zeros > 1)
>                 break;
>             /* Shutdown was sent, now try receiving */
>             self->shutdown_seen_zero = 1;
>             continue;
>         }
>
> ... so I don't think anything more is necessary.
>
> So I think things are fine right now and your patch shouldn't be applied.
>
> ----------
> nosy: +pitrou
> stage:  -> patch review
> versions:  -Python 2.6, Python 3.1, Python 3.2, Python 3.5
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <http://bugs.python.org/issue17672>
> _______________________________________
>
msg186417 - (view) Author: Charles-François Natali (neologix) * (Python committer) Date: 2013-04-09 14:45
> sometimes RST was sent over the network instead of FIN

Your client sends data, but the server never reads it: when a TCP socket is closed while there's still data in the input socket buffer, a RST is sent instead of a FIN. That's normal behaviour.
msg186418 - (view) Author: Hiroaki Kawai (Hiroaki.Kawai) Date: 2013-04-09 14:53
Client gets an exception in reading the socket, not in writing. Please run
the test code and see what happens.

2013/4/9 Charles-François Natali <report@bugs.python.org>

>
> Charles-François Natali added the comment:
>
> > sometimes RST was sent over the network instead of FIN
>
> Your client sends data, but the server never reads it: when a TCP socket
> is closed while there's still data in the input socket buffer, a RST is
> sent instead of a FIN. That's normal behaviour.
>
> ----------
> nosy: +neologix
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <http://bugs.python.org/issue17672>
> _______________________________________
>
msg186420 - (view) Author: Charles-François Natali (neologix) * (Python committer) Date: 2013-04-09 14:58
> Client gets an exception in reading the socket, not in writing. Please run
> the test code and see what happens.

Of course it gets ECONNRESET on subsequent recv(), that's how TCP works.

Just make your handler read from the socket and it won't happen anymore.
msg186424 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-04-09 15:05
> Client gets an exception in reading the socket, not in writing.

Yes, it does, and the exception bears the error code SSL_ERROR_EOF (8), which is expected here.

The question is: why would you expect reading *not* to raise an exception while the remote end of the connection has been closed? TCPStreamHandler will only keep the connection alive as long as the handle() method is running, the connection is disposed of afterwards.
msg186425 - (view) Author: Hiroaki Kawai (Hiroaki.Kawai) Date: 2013-04-09 15:05
As an interface of ssl socket, server does not have to read, just write
some data.
The client side should be able to read the bytes that ther server sent. The
problem is that client will sometimes raise an unexpected SSLError in
reading the ssl socket because server side does not shutdown the ssl
session cleanly.

2013/4/9 Charles-François Natali <report@bugs.python.org>

>
> Charles-François Natali added the comment:
>
> > Client gets an exception in reading the socket, not in writing. Please
> run
> > the test code and see what happens.
>
> Of course it gets ECONNRESET on subsequent recv(), that's how TCP works.
>
> Just make your handler read from the socket and it won't happen anymore.
>
> ----------
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <http://bugs.python.org/issue17672>
> _______________________________________
>
msg186426 - (view) Author: Charles-François Natali (neologix) * (Python committer) Date: 2013-04-09 15:10
> As an interface of ssl socket, server does not have to read, just write
> some data.
> The client side should be able to read the bytes that ther server sent. The
> problem is that client will sometimes raise an unexpected SSLError in
> reading the ssl socket because server side does not shutdown the ssl
> session cleanly.

Once again, that's not how TCP works.
If your server doesn't read data that the client sent it, a RST will
be sent when the server closes its end.

Please do read the TCP spec, or use google:
http://cs.baylor.edu/~donahoo/practical/CSockets/TCPRST.pdf

I suggest closing this issue...
msg186427 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-04-09 15:13
> As an interface of ssl socket, server does not have to read, just
> write
> some data.
> The client side should be able to read the bytes that ther server
> sent.

Please re-read your own code. The server does:

+            def handle(self):
+                self.wfile.write("123")

and the client does:

+                        s.recv(3)

So, yes, the client is able to read the 3 bytes that the server sent.
It's only when you are trying to read an additional byte that an
exception is raised, because the TCP connection has been closed
by the server.
msg186428 - (view) Author: Hiroaki Kawai (Hiroaki.Kawai) Date: 2013-04-09 15:20
The error looks like : SSLError(8, '_ssl.c:1363: EOF occurred in violation
of protocol')
But why we see "in violation of protocol" here?

2013/4/10 Antoine Pitrou <report@bugs.python.org>

>
> Antoine Pitrou added the comment:
>
> > Client gets an exception in reading the socket, not in writing.
>
> Yes, it does, and the exception bears the error code SSL_ERROR_EOF (8),
> which is expected here.
>
> The question is: why would you expect reading *not* to raise an exception
> while the remote end of the connection has been closed? TCPStreamHandler
> will only keep the connection alive as long as the handle() method is
> running, the connection is disposed of afterwards.
>
> ----------
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <http://bugs.python.org/issue17672>
> _______________________________________
>
msg186434 - (view) Author: Hiroaki Kawai (Hiroaki.Kawai) Date: 2013-04-09 15:44
Ah, sorry I understood now.
msg186435 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-04-09 15:44
> The error looks like : SSLError(8, '_ssl.c:1363: EOF occurred in
> violation
> of protocol')
> But why we see "in violation of protocol" here?

Because the SSL layer wasn't shutdown cleanly: the TCP connection was
closed while the SSL layer was still active. You have three solutions
around this:

- you can call unwrap() for a clean SSL shutdown (the server has to call
unwrap() too).

- you can use suppress_ragged_eofs=True with wrap_socket()

- you can simply avoid reading past the server's data, which will
solve the problem altogether
msg186437 - (view) Author: Hiroaki Kawai (Hiroaki.Kawai) Date: 2013-04-09 16:03
I think creating an ssl socket from existing socket from an instance
generated by library routine, and replace that socket with ssl socket is
very common usage. Injecting wrap_socket is very easy. But injecting unwrap
call is not easy.

In python 2.6, I got a plain socket.error of "connection reset" (not
SSLError) in client side in such situation without unwrap in server side.
The same code does not raise exception in python 2.7, which I don't know
why...

Any way, reading the data in server side will solve the problem, thanks.

2013/4/10 Antoine Pitrou <report@bugs.python.org>

>
> Antoine Pitrou added the comment:
>
> > The error looks like : SSLError(8, '_ssl.c:1363: EOF occurred in
> > violation
> > of protocol')
> > But why we see "in violation of protocol" here?
>
> Because the SSL layer wasn't shutdown cleanly: the TCP connection was
> closed while the SSL layer was still active. You have three solutions
> around this:
>
> - you can call unwrap() for a clean SSL shutdown (the server has to call
> unwrap() too).
>
> - you can use suppress_ragged_eofs=True with wrap_socket()
>
> - you can simply avoid reading past the server's data, which will
> solve the problem altogether
>
> ----------
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <http://bugs.python.org/issue17672>
> _______________________________________
>
msg186438 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-04-09 16:15
Ok, closing the issue then.
History
Date User Action Args
2022-04-11 14:57:44adminsetgithub: 61872
2013-04-09 16:15:29pitrousetstatus: open -> closed
resolution: rejected
messages: + msg186438
2013-04-09 16:03:53Hiroaki.Kawaisetmessages: + msg186437
2013-04-09 15:44:41pitrousetmessages: + msg186435
2013-04-09 15:44:31Hiroaki.Kawaisetmessages: + msg186434
2013-04-09 15:20:55Hiroaki.Kawaisetmessages: + msg186428
2013-04-09 15:13:21pitrousetmessages: + msg186427
2013-04-09 15:10:21neologixsetmessages: + msg186426
2013-04-09 15:05:38Hiroaki.Kawaisetmessages: + msg186425
2013-04-09 15:05:28pitrousetmessages: + msg186424
2013-04-09 14:58:17neologixsetmessages: + msg186420
2013-04-09 14:53:03Hiroaki.Kawaisetmessages: + msg186418
2013-04-09 14:45:15neologixsetnosy: + neologix
messages: + msg186417
2013-04-09 14:36:14Hiroaki.Kawaisetmessages: + msg186415
2013-04-09 13:15:20pitrousetversions: - Python 2.6, Python 3.1, Python 3.2, Python 3.5
nosy: + pitrou

messages: + msg186399

stage: patch review
2013-04-09 04:48:14Hiroaki.Kawaisettitle: ssl clean shutdown -> ssl unclean shutdown
2013-04-09 04:45:38Hiroaki.Kawaicreate