cifs: handle when server stops supporting multichannel
authorShyam Prasad N <sprasad@microsoft.com>
Fri, 13 Oct 2023 11:40:09 +0000 (11:40 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 1 Feb 2024 00:18:48 +0000 (16:18 -0800)
[ Upstream commit ee1d21794e55ab76505745d24101331552182002 ]

When a server stops supporting multichannel, we will
keep attempting reconnects to the secondary channels today.
Avoid this by freeing extra channels when negotiate
returns no multichannel support.

Signed-off-by: Shyam Prasad N <sprasad@microsoft.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
Stable-dep-of: 78e727e58e54 ("cifs: update iface_last_update on each query-and-update")
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/smb/client/cifsglob.h
fs/smb/client/cifsproto.h
fs/smb/client/connect.c
fs/smb/client/sess.c
fs/smb/client/smb2pdu.c
fs/smb/client/transport.c

index e3ef8eee68d116c51a5c4825102c7319283db2c0..5e32c79f03a74c262c9e35c3f2a70d87cee17fa2 100644 (file)
@@ -659,6 +659,7 @@ struct TCP_Server_Info {
        bool noautotune;                /* do not autotune send buf sizes */
        bool nosharesock;
        bool tcp_nodelay;
+       bool terminate;
        unsigned int credits;  /* send no more requests at once */
        unsigned int max_credits; /* can override large 32000 default at mnt */
        unsigned int in_flight;  /* number of requests on the wire to server */
index 4a28cff870387cf02b85f4d777f9d9a2928bdc0e..c00f844205590f38e32e8cdd62ce729c9f3c5e6c 100644 (file)
@@ -647,6 +647,8 @@ cifs_chan_needs_reconnect(struct cifs_ses *ses,
 bool
 cifs_chan_is_iface_active(struct cifs_ses *ses,
                          struct TCP_Server_Info *server);
+void
+cifs_disable_secondary_channels(struct cifs_ses *ses);
 int
 cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server);
 int
index c0b1f30eecd7626cb99eee899c0b3a88de9e3ca1..f43f51f2d1c14fb429a4949393bbb23cc267e6a6 100644 (file)
@@ -215,6 +215,14 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
 
        spin_lock(&cifs_tcp_ses_lock);
        list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) {
+               /*
+                * if channel has been marked for termination, nothing to do
+                * for the channel. in fact, we cannot find the channel for the
+                * server. So safe to exit here
+                */
+               if (server->terminate)
+                       break;
+
                /* check if iface is still active */
                spin_lock(&ses->chan_lock);
                if (!cifs_chan_is_iface_active(ses, server)) {
@@ -252,6 +260,8 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
                        spin_lock(&tcon->tc_lock);
                        tcon->status = TID_NEED_RECON;
                        spin_unlock(&tcon->tc_lock);
+
+                       cancel_delayed_work(&tcon->query_interfaces);
                }
                if (ses->tcon_ipc) {
                        ses->tcon_ipc->need_reconnect = true;
index 650a3ec9e6e52c48f1163048f24a08f8229ad112..2ce1b7571371410b58042b8a93eb11e3d6fa3504 100644 (file)
@@ -290,6 +290,60 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
        return new_chan_count - old_chan_count;
 }
 
+/*
+ * called when multichannel is disabled by the server.
+ * this always gets called from smb2_reconnect
+ * and cannot get called in parallel threads.
+ */
+void
+cifs_disable_secondary_channels(struct cifs_ses *ses)
+{
+       int i, chan_count;
+       struct TCP_Server_Info *server;
+       struct cifs_server_iface *iface;
+
+       spin_lock(&ses->chan_lock);
+       chan_count = ses->chan_count;
+       if (chan_count == 1)
+               goto done;
+
+       ses->chan_count = 1;
+
+       /* for all secondary channels reset the need reconnect bit */
+       ses->chans_need_reconnect &= 1;
+
+       for (i = 1; i < chan_count; i++) {
+               iface = ses->chans[i].iface;
+               server = ses->chans[i].server;
+
+               if (iface) {
+                       spin_lock(&ses->iface_lock);
+                       kref_put(&iface->refcount, release_iface);
+                       ses->chans[i].iface = NULL;
+                       iface->num_channels--;
+                       if (iface->weight_fulfilled)
+                               iface->weight_fulfilled--;
+                       spin_unlock(&ses->iface_lock);
+               }
+
+               spin_unlock(&ses->chan_lock);
+               if (server && !server->terminate) {
+                       server->terminate = true;
+                       cifs_signal_cifsd_for_reconnect(server, false);
+               }
+               spin_lock(&ses->chan_lock);
+
+               if (server) {
+                       ses->chans[i].server = NULL;
+                       cifs_put_tcp_session(server, false);
+               }
+
+       }
+
+done:
+       spin_unlock(&ses->chan_lock);
+}
+
 /*
  * update the iface for the channel if necessary.
  * will return 0 when iface is updated, 1 if removed, 2 otherwise
@@ -589,14 +643,10 @@ cifs_ses_add_channel(struct cifs_ses *ses,
 
 out:
        if (rc && chan->server) {
-               /*
-                * we should avoid race with these delayed works before we
-                * remove this channel
-                */
-               cancel_delayed_work_sync(&chan->server->echo);
-               cancel_delayed_work_sync(&chan->server->reconnect);
+               cifs_put_tcp_session(chan->server, 0);
 
                spin_lock(&ses->chan_lock);
+
                /* we rely on all bits beyond chan_count to be clear */
                cifs_chan_clear_need_reconnect(ses, chan->server);
                ses->chan_count--;
@@ -606,8 +656,6 @@ out:
                 */
                WARN_ON(ses->chan_count < 1);
                spin_unlock(&ses->chan_lock);
-
-               cifs_put_tcp_session(chan->server, 0);
        }
 
        kfree(ctx->UNC);
index 288f22050c20b908150ad26e778f3f9b3e06bb94..f1977987ae7434b9dd386cb0e86c8909f95dd5ef 100644 (file)
@@ -164,6 +164,8 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
        struct nls_table *nls_codepage = NULL;
        struct cifs_ses *ses;
        int xid;
+       struct TCP_Server_Info *pserver;
+       unsigned int chan_index;
 
        /*
         * SMB2s NegProt, SessSetup, Logoff do not have tcon yet so
@@ -224,6 +226,12 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
                        return -EAGAIN;
                }
        }
+
+       /* if server is marked for termination, cifsd will cleanup */
+       if (server->terminate) {
+               spin_unlock(&server->srv_lock);
+               return -EHOSTDOWN;
+       }
        spin_unlock(&server->srv_lock);
 
 again:
@@ -242,12 +250,24 @@ again:
                 tcon->need_reconnect);
 
        mutex_lock(&ses->session_mutex);
+       /*
+        * if this is called by delayed work, and the channel has been disabled
+        * in parallel, the delayed work can continue to execute in parallel
+        * there's a chance that this channel may not exist anymore
+        */
+       spin_lock(&server->srv_lock);
+       if (server->tcpStatus == CifsExiting) {
+               spin_unlock(&server->srv_lock);
+               mutex_unlock(&ses->session_mutex);
+               rc = -EHOSTDOWN;
+               goto out;
+       }
+
        /*
         * Recheck after acquire mutex. If another thread is negotiating
         * and the server never sends an answer the socket will be closed
         * and tcpStatus set to reconnect.
         */
-       spin_lock(&server->srv_lock);
        if (server->tcpStatus == CifsNeedReconnect) {
                spin_unlock(&server->srv_lock);
                mutex_unlock(&ses->session_mutex);
@@ -284,6 +304,53 @@ again:
 
        rc = cifs_negotiate_protocol(0, ses, server);
        if (!rc) {
+               /*
+                * if server stopped supporting multichannel
+                * and the first channel reconnected, disable all the others.
+                */
+               if (ses->chan_count > 1 &&
+                   !(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
+                       if (SERVER_IS_CHAN(server)) {
+                               cifs_dbg(VFS, "server %s does not support " \
+                                        "multichannel anymore. skipping secondary channel\n",
+                                        ses->server->hostname);
+
+                               spin_lock(&ses->chan_lock);
+                               chan_index = cifs_ses_get_chan_index(ses, server);
+                               if (chan_index == CIFS_INVAL_CHAN_INDEX) {
+                                       spin_unlock(&ses->chan_lock);
+                                       goto skip_terminate;
+                               }
+
+                               ses->chans[chan_index].server = NULL;
+                               spin_unlock(&ses->chan_lock);
+
+                               /*
+                                * the above reference of server by channel
+                                * needs to be dropped without holding chan_lock
+                                * as cifs_put_tcp_session takes a higher lock
+                                * i.e. cifs_tcp_ses_lock
+                                */
+                               cifs_put_tcp_session(server, 1);
+
+                               server->terminate = true;
+                               cifs_signal_cifsd_for_reconnect(server, false);
+
+                               /* mark primary server as needing reconnect */
+                               pserver = server->primary_server;
+                               cifs_signal_cifsd_for_reconnect(pserver, false);
+
+skip_terminate:
+                               mutex_unlock(&ses->session_mutex);
+                               rc = -EHOSTDOWN;
+                               goto out;
+                       } else {
+                               cifs_server_dbg(VFS, "does not support " \
+                                        "multichannel anymore. disabling all other channels\n");
+                               cifs_disable_secondary_channels(ses);
+                       }
+               }
+
                rc = cifs_setup_session(0, ses, server, nls_codepage);
                if ((rc == -EACCES) && !tcon->retry) {
                        mutex_unlock(&ses->session_mutex);
@@ -3863,6 +3930,13 @@ void smb2_reconnect_server(struct work_struct *work)
        /* Prevent simultaneous reconnects that can corrupt tcon->rlist list */
        mutex_lock(&pserver->reconnect_mutex);
 
+       /* if the server is marked for termination, drop the ref count here */
+       if (server->terminate) {
+               cifs_put_tcp_session(server, true);
+               mutex_unlock(&pserver->reconnect_mutex);
+               return;
+       }
+
        INIT_LIST_HEAD(&tmp_list);
        INIT_LIST_HEAD(&tmp_ses_list);
        cifs_dbg(FYI, "Reconnecting tcons and channels\n");
index d553b7a54621bc0c77d98d55e151805900b09ddd..4f717ad7c21b424d45f785fdbb94be941c1d7f14 100644 (file)
@@ -1023,7 +1023,7 @@ struct TCP_Server_Info *cifs_pick_channel(struct cifs_ses *ses)
        spin_lock(&ses->chan_lock);
        for (i = 0; i < ses->chan_count; i++) {
                server = ses->chans[i].server;
-               if (!server)
+               if (!server || server->terminate)
                        continue;
 
                /*