]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
libcli/smb: add smbXcli_conn_monitor_{send,recv,once}()
authorStefan Metzmacher <metze@samba.org>
Thu, 15 May 2025 11:48:20 +0000 (13:48 +0200)
committerStefan Metzmacher <metze@samba.org>
Wed, 18 Jun 2025 17:52:37 +0000 (17:52 +0000)
smbXcli_conn_monitor_{send,recv} can be used to monitor
a connection over a long time. It will only come back
if there's a connection error.

smbXcli_conn_monitor_once() will be used by sync callers
without a long term tevent context and needs to be called
multiple times per second in order to work correctly.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Volker Lendecke <vl@samba.org>
libcli/smb/smbXcli_base.c
libcli/smb/smbXcli_base.h

index 2d49bb1294b08e5e3550bb447ba72f76a28b26e2..36693905f673e62cb97784f111fef15d3700099a 100644 (file)
@@ -60,6 +60,7 @@ struct smbXcli_conn {
        struct tevent_queue *outgoing;
        struct tevent_req **pending;
        struct tevent_req *read_smb_req;
+       struct tevent_req *monitor_req;
        struct tevent_req *suicide_req;
 
        enum protocol_types min_protocol;
@@ -1291,6 +1292,17 @@ void smbXcli_conn_disconnect(struct smbXcli_conn *conn, NTSTATUS status)
                smb2cli_session_increment_channel_sequence(session);
        }
 
+       if (conn->monitor_req != NULL) {
+               /*
+                * smbXcli_conn_monitor_send()
+                * used tevent_req_defer_callback() already.
+                */
+               if (!NT_STATUS_IS_OK(status)) {
+                       tevent_req_nterror(conn->monitor_req, status);
+               }
+               conn->monitor_req = NULL;
+       }
+
        if (conn->suicide_req != NULL) {
                /*
                 * smbXcli_conn_samba_suicide_send()
@@ -1396,6 +1408,168 @@ void smbXcli_conn_disconnect(struct smbXcli_conn *conn, NTSTATUS status)
        }
 }
 
+struct smbXcli_conn_monitor_state {
+       struct smbXcli_conn *conn;
+       struct tevent_req *monitor_req;
+};
+
+static void smbXcli_conn_monitor_cleanup(struct tevent_req *req,
+                                        enum tevent_req_state req_state);
+static void smbXcli_conn_monitor_done(struct tevent_req *subreq);
+
+struct tevent_req *smbXcli_conn_monitor_send(TALLOC_CTX *mem_ctx,
+                                            struct tevent_context *ev,
+                                            struct smbXcli_conn *conn)
+{
+       struct tevent_req *req = NULL;
+       struct smbXcli_conn_monitor_state *state = NULL;
+       struct tevent_req *subreq = NULL;
+       bool ok;
+
+       req = tevent_req_create(mem_ctx, &state,
+                               struct smbXcli_conn_monitor_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       state->conn = conn;
+
+       if (conn->monitor_req != NULL) {
+               tevent_req_nterror(req, NT_STATUS_ALREADY_REGISTERED);
+               return tevent_req_post(req, ev);
+       }
+
+       tevent_req_set_cleanup_fn(req, smbXcli_conn_monitor_cleanup);
+
+       /*
+        * We need to use tevent_req_defer_callback()
+        * in order to allow smbXcli_conn_disconnect()
+        * to do a safe cleanup.
+        */
+       tevent_req_defer_callback(req, ev);
+       conn->monitor_req = req;
+
+       ok = smbXcli_conn_is_connected(conn);
+       if (!ok) {
+               smbXcli_conn_disconnect(conn,
+                               NT_STATUS_CONNECTION_DISCONNECTED);
+               return tevent_req_post(req, ev);
+       }
+
+       subreq = wait_for_error_send(state, ev, conn->transport->sock_fd);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, smbXcli_conn_monitor_done, req);
+       state->monitor_req = subreq;
+
+       return req;
+}
+
+static void smbXcli_conn_monitor_cleanup(struct tevent_req *req,
+                                        enum tevent_req_state req_state)
+{
+       struct smbXcli_conn_monitor_state *state = tevent_req_data(
+               req, struct smbXcli_conn_monitor_state);
+
+       TALLOC_FREE(state->monitor_req);
+
+       if (state->conn == NULL) {
+               return;
+       }
+
+       if (state->conn->monitor_req == req) {
+               state->conn->monitor_req = NULL;
+       }
+       state->conn = NULL;
+}
+
+static void smbXcli_conn_monitor_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                         struct tevent_req);
+       struct smbXcli_conn_monitor_state *state = tevent_req_data(
+               req, struct smbXcli_conn_monitor_state);
+       NTSTATUS status;
+       int err;
+
+       state->monitor_req = NULL;
+
+       err = wait_for_error_recv(subreq);
+       TALLOC_FREE(subreq);
+       /* here, we need to notify all pending requests */
+       status = map_nt_error_from_unix_common(err);
+       smbXcli_conn_disconnect(state->conn, status);
+}
+
+NTSTATUS smbXcli_conn_monitor_recv(struct tevent_req *req)
+{
+       return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS smbXcli_conn_monitor_once(struct smbXcli_conn *conn)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       struct tevent_context *ev;
+       struct tevent_req *req;
+       NTSTATUS status = NT_STATUS_NO_MEMORY;
+       bool ok;
+
+       if (smbXcli_conn_has_async_calls(conn)) {
+               /*
+                * Can't use sync call while an async call is in flight
+                */
+               status = NT_STATUS_INVALID_PARAMETER_MIX;
+               goto fail;
+       }
+       ev = samba_tevent_context_init(frame);
+       if (ev == NULL) {
+               goto fail;
+       }
+       /*
+        * We want tevent_req_poll()
+        * to return -1 and errno=EAGAIN
+        * instead of blocking in [e]poll waiting
+        * for external events to happen.
+        *
+        * So we use tevent_context_set_wait_timeout(0)
+        * that will cause tevent_loop_once() in
+        * tevent_req_poll() to break with EAGAIN.
+        * It also means that tevent_req_is_in_progress()
+        * would still return true.
+        */
+       tevent_context_set_wait_timeout(ev, 0);
+       req = smbXcli_conn_monitor_send(frame, ev, conn);
+       if (req == NULL) {
+               goto fail;
+       }
+       ok = tevent_req_poll(req, ev);
+       if (!ok) {
+               if (errno == EAGAIN) {
+                       /*
+                        * This is exactly what we want:
+                        * no existing events happened
+                        * and we avoid blocking in
+                        * [e]poll waiting.
+                        *
+                        * See also the comment above before
+                        * tevent_context_set_wait_timeout!
+                        *
+                        * We don't call smbXcli_conn_monitor_recv()
+                        * and let TALLOC_FREE(frame) destroy req
+                        * as well.
+                        */
+                       status = NT_STATUS_OK;
+                       goto fail;
+               }
+               status = map_nt_error_from_unix_common(errno);
+               goto fail;
+       }
+       status = smbXcli_conn_monitor_recv(req);
+fail:
+       TALLOC_FREE(frame);
+       return status;
+}
+
 /*
  * Fetch a smb request's mid. Only valid after the request has been sent by
  * smb1cli_req_send().
index ec173ae7533a6a6291634df96e520104f36441f5..6460ac6eba098dd8d87cd2d60a1de4c0a1f5139b 100644 (file)
@@ -58,6 +58,17 @@ struct smbXcli_conn *smbXcli_conn_create(TALLOC_CTX *mem_ctx,
 bool smbXcli_conn_is_connected(struct smbXcli_conn *conn);
 void smbXcli_conn_disconnect(struct smbXcli_conn *conn, NTSTATUS status);
 
+struct tevent_req *smbXcli_conn_monitor_send(TALLOC_CTX *mem_ctx,
+                                            struct tevent_context *ev,
+                                            struct smbXcli_conn *conn);
+NTSTATUS smbXcli_conn_monitor_recv(struct tevent_req *req);
+/*
+ * This needs to be called multiple times per second
+ * on an idle connection in order to let the transport
+ * do low level keepalive handling.
+ */
+NTSTATUS smbXcli_conn_monitor_once(struct smbXcli_conn *conn);
+
 struct tevent_queue *smbXcli_conn_send_queue(struct smbXcli_conn *conn);
 bool smbXcli_conn_has_async_calls(struct smbXcli_conn *conn);