From: Stefan Metzmacher Date: Thu, 15 May 2025 11:48:20 +0000 (+0200) Subject: libcli/smb: add smbXcli_conn_monitor_{send,recv,once}() X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8e58f090709b7f88760c618ebc53d50c552a426f;p=thirdparty%2Fsamba.git libcli/smb: add smbXcli_conn_monitor_{send,recv,once}() 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 Reviewed-by: Volker Lendecke --- diff --git a/libcli/smb/smbXcli_base.c b/libcli/smb/smbXcli_base.c index 2d49bb1294b..36693905f67 100644 --- a/libcli/smb/smbXcli_base.c +++ b/libcli/smb/smbXcli_base.c @@ -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(). diff --git a/libcli/smb/smbXcli_base.h b/libcli/smb/smbXcli_base.h index ec173ae7533..6460ac6eba0 100644 --- a/libcli/smb/smbXcli_base.h +++ b/libcli/smb/smbXcli_base.h @@ -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);