-
-
Notifications
You must be signed in to change notification settings - Fork 90
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
🐛 Suppress legit OS errors on socket shutdown #342
Conversation
3095268
to
b527eb2
Compare
cc @liamstask && @The-Compiler |
b527eb2
to
211581e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the change lgtm, not sure of the best way to test
|
||
acceptable_sock_shutdown_error_codes = { | ||
errno.ENOTCONN, | ||
errno.EPIPE, errno.ESHUTDOWN, # corresponds to BrokenPipeError in Python 3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do both of these map to BrokenPipeError
? maybe worth putting on their own line so the comment is unambiguous
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's why I've put them on the same line: https://docs.python.org/3/library/exceptions.html#BrokenPipeError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay! I set up a CI run which runs 10 Windows jobs resulting in 4 of them failing:
Then I tested this branch and all jobs pass again:
So I'm guessing this PR indeed fixes my issue - thanks for the quick fix! 👍
Thanks for the feedback! I'll see if I'll be able to come up with some regression tests soonish and if not, I'll just address the suggestions and merge the PR as is. |
When closing a socket we normally want to terminate the transport- level connection by sending a TCP FIN packet over the wire. This works great if the client is willing to perform a 3-way handshake but sometimes the client-side just drops the connection by sending an RST instead of a FIN. In this case, our server-side `socket.shutdown()` call will raise an OSError (socket.error on Python 2) or its derivatives. Which is perfectly fine and it's alright for us to ignore those. The kernel socket close helper rewrite introduced a behavior of only suppressing ENOTCONN in v8.4.8 (the case when the client is no longer with us) but it left own a few other cases that may be happening too. This change fixes that by extending the list of errors that are to be suppressed. Here's what's handled from now on: * ENOTCONN — client is no longer connected * EPIPE — write on a pipe while the other end has been closed * ESHUTDOWN — write on a socket which has been shutdown for writing * ECONNRESET — connection is reset by the peer Fixes #341 Refs: * https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Connection_termination * http://cs.baylor.edu/~donahoo/practical/CSockets/TCPRST.pdf * #341 (comment) Co-Authored-By: Liam Staskawicz <liam@stask.net>
211581e
to
384e6d0
Compare
This is how one can trigger RST (ConnectionResetError: [Errno 104] Connection reset by peer) https://stackoverflow.com/a/40779258/595220 |
I can only reproduce RST on |
CPython devs appear to be struggling with reproducers too: https://bugs.python.org/issue30319 / https://bugs.python.org/issue30329 / python/cpython@83a2c28 / https://github.com/python/cpython/blob/c39b52f/Lib/poplib.py#L297-L302 / https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-shutdown. I guess I'll just try to monkey-patch shutdown to raise states that are hard to reproduce until somebody with Windows can take a look. |
Hey @mxii-ca, I recall you performed an excellent debugging of TLS earlier this year so I thought you may be able to help us out here. Do you know of any ways to trigger TCP RST to reproduce an issue when the client sends RST while the server initiates a shutdown sequence with FIN? Anything Windows-specific maybe? |
Releases shouldn't be blocked on automation. I'll cut a manual release if needed. |
Since this change fixes an observable regression and as the user was able to confirm this change addresses that regression, I'd like to proceed with merging this fix and getting it out. I've filed #344 to capture the need to follow-up with a proper regression test. |
Oh, I had some local tests that I was about to push today. |
Great! Just push them as a separate PR (if you wish), or just push them to maint/8.x, and that'll close out #344. |
I'll work on it. I had an amended commit so I need to deal with that first. |
@The-Compiler This is released as v8.5.0 FYI |
Gah. I hadn't considered that contingency. Let me know if I can help. |
@webknjaz you can change the behavior of close from the graceful when import socket
s = socket.create_connection(("127.0.0.1", 5673))
linger = bytearray()
linger.extend((1).to_bytes(2, "little", signed=False)) # l_onoff
linger.extend((0).to_bytes(2, "little", signed=False)) # l_linger
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, bytes(linger))
# send RST
s.close() this should work cross-platform |
unfortunately, using however, reliably sending a note that using raw sockets requires running as an Administrator on Windows and the |
yeah.. that's what I've concluded after some experiments.
Woah, this adds a lot of complications. I'll have to postpone this idea and just simulate the exceptions with a P.S. Thanks for the feedback! I did have in mind some ideas that I wanted to emit RST in a controlled way but never thought about the raw sockets... I also wanted to see if mitmproxy would help me achieve the required result but it doesn't look like it'd be helpful. |
When closing a socket we normally want to terminate the transport-
level connection by sending a TCP FIN packet over the wire. This works
great if the client is willing to perform a 3-way handshake but
sometimes the client-side just drops the connection by sending an RST
instead of a FIN. In this case, our server-side
socket.shutdown()
call will raise an OSError (socket.error on Python 2) or its
derivatives. Which is perfectly fine and it's alright for us to ignore
those.
The kernel socket close helper rewrite introduced a behavior of only
suppressing ENOTCONN in v8.4.8 (the case when the client is no longer
with us) but it left own a few other cases that may be happening too.
This change fixes that by extending the list of errors that are to be
suppressed. Here's what's handled from now on:
❓ What kind of change does this PR introduce?
📋 What is the related issue number (starting with
#
)Fixes #341
❓ What is the current behavior? (You can also link to an open issue here)
Connection drop/reset/shutdown initiated by the client-side while the server attempts to send a TCP FIN triggers an unhandled OSError/socket.error during the socket shutdown and thus leaks into the logs.
❓ What is the new behavior (if this is a feature change)?
It's handled gracefully and suppressed.
📋 Other information:
Refs:
📋 Checklist:
and description in grammatically correct, complete sentences
This change is