Merge pull request #1061 from silveraignacio/fix-broken-pipe-handling

fix: improve broken pipe (EPIPE) error handling in socket operations
This commit is contained in:
Brian May
2025-08-02 09:07:33 +10:00
committed by GitHub
2 changed files with 15 additions and 8 deletions

View File

@ -616,7 +616,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
except socket.error as e: except socket.error as e:
if e.args[0] == errno.EPIPE: if e.args[0] == errno.EPIPE:
debug3('Error: EPIPE: ' + repr(e)) debug3('Error: EPIPE: ' + repr(e))
raise Fatal("failed to establish ssh session (1)") raise Fatal("SSH connection lost: broken pipe (server may have terminated unexpectedly)")
else: else:
raise raise
mux = Mux(rfile, wfile) mux = Mux(rfile, wfile)

View File

@ -80,11 +80,13 @@ def _nb_clean(func, *args):
except (OSError, socket.error): except (OSError, socket.error):
# Note: In python2 socket.error != OSError (In python3, they are same) # Note: In python2 socket.error != OSError (In python3, they are same)
_, e = sys.exc_info()[:2] _, e = sys.exc_info()[:2]
if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN): if e.errno in (errno.EWOULDBLOCK, errno.EAGAIN):
raise
else:
debug3('%s: err was: %s' % (func.__name__, e)) debug3('%s: err was: %s' % (func.__name__, e))
return None return None
else:
# Re-raise other errors (including EPIPE) so they can be handled
# by the calling function appropriately
raise
def _try_peername(sock): def _try_peername(sock):
@ -220,7 +222,7 @@ class SockWrapper:
self.wsock.setblocking(False) self.wsock.setblocking(False)
try: try:
return _nb_clean(self.wsock.send, buf) return _nb_clean(self.wsock.send, buf)
except OSError: except (OSError, socket.error):
_, e = sys.exc_info()[:2] _, e = sys.exc_info()[:2]
if e.errno == errno.EPIPE: if e.errno == errno.EPIPE:
debug1('%r: uwrite: got EPIPE' % self) debug1('%r: uwrite: got EPIPE' % self)
@ -243,10 +245,15 @@ class SockWrapper:
self.rsock.setblocking(False) self.rsock.setblocking(False)
try: try:
return _nb_clean(self.rsock.recv, 65536) return _nb_clean(self.rsock.recv, 65536)
except OSError: except (OSError, socket.error):
_, e = sys.exc_info()[:2] _, e = sys.exc_info()[:2]
self.seterr('uread: %s' % e) if e.errno == errno.EPIPE:
return b('') # unexpected error... we'll call it EOF debug1('%r: uread: got EPIPE' % self)
self.noread()
return b('') # treat broken pipe as EOF
else:
self.seterr('uread: %s' % e)
return b('') # unexpected error... we'll call it EOF
def fill(self): def fill(self):
if self.buf: if self.buf: