s3:smbd: add logic to retry break notifications on all available channels
authorStefan Metzmacher <metze@samba.org>
Tue, 2 Jun 2020 16:05:39 +0000 (18:05 +0200)
committerStefan Metzmacher <metze@samba.org>
Wed, 8 Jul 2020 15:54:40 +0000 (15:54 +0000)
For leases we need to use any available connection with the same
client_guid. That means all connections in the client->connections list.

We try the oldest connection first, as that's what windows is doing.

For oplocks we implement the same as that's what the specification
says. Windows behaves different and we have
'smb2 disable oplock break retry = yes' in order to behave like Windows.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=11897

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Günther Deschner <gd@samba.org>
source3/smbd/smb2_server.c

index 3291bac77f7933ff81c6ef250e467c006d320fa3..e8820afa796f44d3c08c605c0bf77f81735777dc 100644 (file)
@@ -3615,7 +3615,9 @@ static NTSTATUS smbd_smb2_break_recv(struct tevent_req *req)
 struct smbXsrv_pending_break {
        struct smbXsrv_pending_break *prev, *next;
        struct smbXsrv_client *client;
+       bool disable_oplock_break_retries;
        uint64_t session_id;
+       uint64_t last_channel_id;
        union {
                uint8_t generic[1];
                uint8_t oplock[0x18];
@@ -3638,6 +3640,7 @@ static struct smbXsrv_pending_break *smbXsrv_pending_break_create(
        }
        pb->client = client;
        pb->session_id = session_id;
+       pb->disable_oplock_break_retries = lp_smb2_disable_oplock_break_retry();
 
        return pb;
 }
@@ -3659,8 +3662,107 @@ static NTSTATUS smbXsrv_pending_break_schedule(struct smbXsrv_pending_break *pb)
 static NTSTATUS smbXsrv_pending_break_submit(struct smbXsrv_pending_break *pb)
 {
        struct smbXsrv_client *client = pb->client;
-       struct smbXsrv_connection *xconn = client->connections;
+       struct smbXsrv_session *session = NULL;
+       struct smbXsrv_connection *xconn = NULL;
+       struct smbXsrv_connection *oplock_xconn = NULL;
        struct tevent_req *subreq = NULL;
+       NTSTATUS status;
+
+       if (pb->session_id != 0) {
+               status = get_valid_smbXsrv_session(client,
+                                                  pb->session_id,
+                                                  &session);
+               if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+                       return NT_STATUS_ABANDONED;
+               }
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+
+               if (pb->last_channel_id != 0) {
+                       /*
+                        * This is what current Windows servers
+                        * do, they don't retry on all available
+                        * channels. They only use the last channel.
+                        *
+                        * But it doesn't match the specification in
+                        * [MS-SMB2] "3.3.4.6 Object Store Indicates an
+                        * Oplock Break"
+                        *
+                        * Per default disable_oplock_break_retries is false
+                        * and we behave like the specification.
+                        */
+                       if (pb->disable_oplock_break_retries) {
+                               return NT_STATUS_ABANDONED;
+                       }
+               }
+       }
+
+       for (xconn = client->connections; xconn != NULL; xconn = xconn->next) {
+               if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+                       continue;
+               }
+
+               if (xconn->channel_id == 0) {
+                       /*
+                        * non-multichannel case
+                        */
+                       break;
+               }
+
+               if (session != NULL) {
+                       struct smbXsrv_channel_global0 *c = NULL;
+
+                       /*
+                        * Having a session means we're handling
+                        * an oplock break and we only need to
+                        * use channels available on the
+                        * session.
+                        */
+                       status = smbXsrv_session_find_channel(session, xconn, &c);
+                       if (!NT_STATUS_IS_OK(status)) {
+                               continue;
+                       }
+
+                       /*
+                        * This is what current Windows servers
+                        * do, they don't retry on all available
+                        * channels. They only use the last channel.
+                        *
+                        * But it doesn't match the specification
+                        * in [MS-SMB2] "3.3.4.6 Object Store Indicates an
+                        * Oplock Break"
+                        *
+                        * Per default disable_oplock_break_retries is false
+                        * and we behave like the specification.
+                        */
+                       if (pb->disable_oplock_break_retries) {
+                               oplock_xconn = xconn;
+                               continue;
+                       }
+               }
+
+               if (xconn->channel_id > pb->last_channel_id) {
+                       /*
+                        * multichannel case
+                        */
+                       break;
+               }
+       }
+
+       if (xconn == NULL) {
+               xconn = oplock_xconn;
+       }
+
+       if (xconn == NULL) {
+               /*
+                * If there's no remaining connection available
+                * tell the caller to stop...
+                */
+               return NT_STATUS_ABANDONED;
+       }
+
+       pb->last_channel_id = xconn->channel_id;
 
        subreq = smbd_smb2_break_send(pb,
                                      client->raw_ev_ctx,
@@ -3689,10 +3791,22 @@ static void smbXsrv_pending_break_done(struct tevent_req *subreq)
        status = smbd_smb2_break_recv(subreq);
        TALLOC_FREE(subreq);
        if (!NT_STATUS_IS_OK(status)) {
-               smbd_server_disconnect_client(client, nt_errstr(status));
+               status = smbXsrv_pending_break_submit(pb);
+               if (NT_STATUS_EQUAL(status, NT_STATUS_ABANDONED)) {
+                       /*
+                        * If there's no remaing connection
+                        * there's no need to send a break again.
+                        */
+                       goto remove;
+               }
+               if (!NT_STATUS_IS_OK(status)) {
+                       smbd_server_disconnect_client(client, nt_errstr(status));
+                       return;
+               }
                return;
        }
 
+remove:
        TALLOC_FREE(pb);
 }