diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a8f6a57a44c26a2b28061e586ca591f547e3b55..9fcfe30a2b2803959704fd94e91c58d8a2624de8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Added `smbclient.ClientConfig()` to set global default options on new connections * Moved the SMB Header structures to `smbprotocol.header` * Added `null_terminated` option for a `TextField` value +* Fix broken pipe errors that occur on long running connections by sending a echo request for each connection session every 10 minutes ## 1.1.0 - 2020-08-14 diff --git a/smbprotocol/connection.py b/smbprotocol/connection.py index d7f3947d76072aa6034aa7593952e17f5b98e434..30f7f089a2a82e32f713be358d95b827f0b1a9de 100644 --- a/smbprotocol/connection.py +++ b/smbprotocol/connection.py @@ -89,9 +89,9 @@ from smbprotocol.transport import ( ) try: - from queue import Queue + from queue import Queue, Empty except ImportError: # pragma: no cover - from Queue import Queue + from Queue import Queue, Empty log = logging.getLogger(__name__) @@ -1107,7 +1107,21 @@ class Connection(object): def _process_message_thread(self, msg_queue): while True: - b_msg = msg_queue.get() + # Wait for a max of 10 minutes before sending an echo that tells the SMB server the client is still + # available. This stops the server from closing the connection and the associated sessions on a long lived + # connection. A brief test shows Windows kills a connection at ~16 minutes so 10 minutes is a safe choice. + # https://github.com/jborean93/smbprotocol/issues/31 + try: + b_msg = msg_queue.get(timeout=600) + except Empty: + log.debug("Sending SMB2 Echo to keep connection alive") + for sid in self.session_table.keys(): + req = self.send(SMB2Echo(), sid=sid) + # Set this reserved field to 1 as we use that internally to check whether the outstanding requests + # queue should be cleared in this thread or not. + req.message['reserved'] = 1 + + continue # The socket will put None in the queue if it is closed, signalling the end of the connection. if b_msg is None: @@ -1163,6 +1177,11 @@ class Connection(object): request.response = header request.response_event.set() + + # When we send a ping in this thread we want to make sure it doesn't linger in the outstanding + # request queue. + if request.message['reserved'].get_value() == 1: + del self.outstanding_requests[message_id] except Exception as exc: # The exception is raised in _check_worker_running by the main thread when send/receive is called next. self._t_exc = exc