Skip to content

Commit ee1d217

Browse files
sprasad-microsoftSteve French
authored andcommitted
cifs: handle when server stops supporting multichannel
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 <[email protected]> Signed-off-by: Steve French <[email protected]>
1 parent 705fc52 commit ee1d217

File tree

6 files changed

+145
-10
lines changed

6 files changed

+145
-10
lines changed

fs/smb/client/cifsglob.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ struct TCP_Server_Info {
650650
bool noautotune; /* do not autotune send buf sizes */
651651
bool nosharesock;
652652
bool tcp_nodelay;
653+
bool terminate;
653654
unsigned int credits; /* send no more requests at once */
654655
unsigned int max_credits; /* can override large 32000 default at mnt */
655656
unsigned int in_flight; /* number of requests on the wire to server */

fs/smb/client/cifsproto.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,8 @@ cifs_chan_needs_reconnect(struct cifs_ses *ses,
641641
bool
642642
cifs_chan_is_iface_active(struct cifs_ses *ses,
643643
struct TCP_Server_Info *server);
644+
void
645+
cifs_disable_secondary_channels(struct cifs_ses *ses);
644646
int
645647
cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server);
646648
int

fs/smb/client/connect.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,14 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
219219

220220
spin_lock(&cifs_tcp_ses_lock);
221221
list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) {
222+
/*
223+
* if channel has been marked for termination, nothing to do
224+
* for the channel. in fact, we cannot find the channel for the
225+
* server. So safe to exit here
226+
*/
227+
if (server->terminate)
228+
break;
229+
222230
/* check if iface is still active */
223231
if (!cifs_chan_is_iface_active(ses, server))
224232
cifs_chan_update_iface(ses, server);
@@ -253,6 +261,8 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
253261
spin_lock(&tcon->tc_lock);
254262
tcon->status = TID_NEED_RECON;
255263
spin_unlock(&tcon->tc_lock);
264+
265+
cancel_delayed_work(&tcon->query_interfaces);
256266
}
257267
if (ses->tcon_ipc) {
258268
ses->tcon_ipc->need_reconnect = true;

fs/smb/client/sess.c

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,60 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
296296
return new_chan_count - old_chan_count;
297297
}
298298

299+
/*
300+
* called when multichannel is disabled by the server.
301+
* this always gets called from smb2_reconnect
302+
* and cannot get called in parallel threads.
303+
*/
304+
void
305+
cifs_disable_secondary_channels(struct cifs_ses *ses)
306+
{
307+
int i, chan_count;
308+
struct TCP_Server_Info *server;
309+
struct cifs_server_iface *iface;
310+
311+
spin_lock(&ses->chan_lock);
312+
chan_count = ses->chan_count;
313+
if (chan_count == 1)
314+
goto done;
315+
316+
ses->chan_count = 1;
317+
318+
/* for all secondary channels reset the need reconnect bit */
319+
ses->chans_need_reconnect &= 1;
320+
321+
for (i = 1; i < chan_count; i++) {
322+
iface = ses->chans[i].iface;
323+
server = ses->chans[i].server;
324+
325+
if (iface) {
326+
spin_lock(&ses->iface_lock);
327+
kref_put(&iface->refcount, release_iface);
328+
ses->chans[i].iface = NULL;
329+
iface->num_channels--;
330+
if (iface->weight_fulfilled)
331+
iface->weight_fulfilled--;
332+
spin_unlock(&ses->iface_lock);
333+
}
334+
335+
spin_unlock(&ses->chan_lock);
336+
if (server && !server->terminate) {
337+
server->terminate = true;
338+
cifs_signal_cifsd_for_reconnect(server, false);
339+
}
340+
spin_lock(&ses->chan_lock);
341+
342+
if (server) {
343+
ses->chans[i].server = NULL;
344+
cifs_put_tcp_session(server, false);
345+
}
346+
347+
}
348+
349+
done:
350+
spin_unlock(&ses->chan_lock);
351+
}
352+
299353
/*
300354
* update the iface for the channel if necessary.
301355
* will return 0 when iface is updated, 1 if removed, 2 otherwise
@@ -595,14 +649,10 @@ cifs_ses_add_channel(struct cifs_ses *ses,
595649

596650
out:
597651
if (rc && chan->server) {
598-
/*
599-
* we should avoid race with these delayed works before we
600-
* remove this channel
601-
*/
602-
cancel_delayed_work_sync(&chan->server->echo);
603-
cancel_delayed_work_sync(&chan->server->reconnect);
652+
cifs_put_tcp_session(chan->server, 0);
604653

605654
spin_lock(&ses->chan_lock);
655+
606656
/* we rely on all bits beyond chan_count to be clear */
607657
cifs_chan_clear_need_reconnect(ses, chan->server);
608658
ses->chan_count--;
@@ -612,8 +662,6 @@ cifs_ses_add_channel(struct cifs_ses *ses,
612662
*/
613663
WARN_ON(ses->chan_count < 1);
614664
spin_unlock(&ses->chan_lock);
615-
616-
cifs_put_tcp_session(chan->server, 0);
617665
}
618666

619667
kfree(ctx->UNC);

fs/smb/client/smb2pdu.c

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
164164
struct nls_table *nls_codepage = NULL;
165165
struct cifs_ses *ses;
166166
int xid;
167+
struct TCP_Server_Info *pserver;
168+
unsigned int chan_index;
167169

168170
/*
169171
* SMB2s NegProt, SessSetup, Logoff do not have tcon yet so
@@ -224,6 +226,12 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
224226
return -EAGAIN;
225227
}
226228
}
229+
230+
/* if server is marked for termination, cifsd will cleanup */
231+
if (server->terminate) {
232+
spin_unlock(&server->srv_lock);
233+
return -EHOSTDOWN;
234+
}
227235
spin_unlock(&server->srv_lock);
228236

229237
again:
@@ -242,12 +250,24 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
242250
tcon->need_reconnect);
243251

244252
mutex_lock(&ses->session_mutex);
253+
/*
254+
* if this is called by delayed work, and the channel has been disabled
255+
* in parallel, the delayed work can continue to execute in parallel
256+
* there's a chance that this channel may not exist anymore
257+
*/
258+
spin_lock(&server->srv_lock);
259+
if (server->tcpStatus == CifsExiting) {
260+
spin_unlock(&server->srv_lock);
261+
mutex_unlock(&ses->session_mutex);
262+
rc = -EHOSTDOWN;
263+
goto out;
264+
}
265+
245266
/*
246267
* Recheck after acquire mutex. If another thread is negotiating
247268
* and the server never sends an answer the socket will be closed
248269
* and tcpStatus set to reconnect.
249270
*/
250-
spin_lock(&server->srv_lock);
251271
if (server->tcpStatus == CifsNeedReconnect) {
252272
spin_unlock(&server->srv_lock);
253273
mutex_unlock(&ses->session_mutex);
@@ -284,6 +304,53 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
284304

285305
rc = cifs_negotiate_protocol(0, ses, server);
286306
if (!rc) {
307+
/*
308+
* if server stopped supporting multichannel
309+
* and the first channel reconnected, disable all the others.
310+
*/
311+
if (ses->chan_count > 1 &&
312+
!(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
313+
if (SERVER_IS_CHAN(server)) {
314+
cifs_dbg(VFS, "server %s does not support " \
315+
"multichannel anymore. skipping secondary channel\n",
316+
ses->server->hostname);
317+
318+
spin_lock(&ses->chan_lock);
319+
chan_index = cifs_ses_get_chan_index(ses, server);
320+
if (chan_index == CIFS_INVAL_CHAN_INDEX) {
321+
spin_unlock(&ses->chan_lock);
322+
goto skip_terminate;
323+
}
324+
325+
ses->chans[chan_index].server = NULL;
326+
spin_unlock(&ses->chan_lock);
327+
328+
/*
329+
* the above reference of server by channel
330+
* needs to be dropped without holding chan_lock
331+
* as cifs_put_tcp_session takes a higher lock
332+
* i.e. cifs_tcp_ses_lock
333+
*/
334+
cifs_put_tcp_session(server, 1);
335+
336+
server->terminate = true;
337+
cifs_signal_cifsd_for_reconnect(server, false);
338+
339+
/* mark primary server as needing reconnect */
340+
pserver = server->primary_server;
341+
cifs_signal_cifsd_for_reconnect(pserver, false);
342+
343+
skip_terminate:
344+
mutex_unlock(&ses->session_mutex);
345+
rc = -EHOSTDOWN;
346+
goto out;
347+
} else {
348+
cifs_server_dbg(VFS, "does not support " \
349+
"multichannel anymore. disabling all other channels\n");
350+
cifs_disable_secondary_channels(ses);
351+
}
352+
}
353+
287354
rc = cifs_setup_session(0, ses, server, nls_codepage);
288355
if ((rc == -EACCES) && !tcon->retry) {
289356
mutex_unlock(&ses->session_mutex);
@@ -3836,6 +3903,13 @@ void smb2_reconnect_server(struct work_struct *work)
38363903
/* Prevent simultaneous reconnects that can corrupt tcon->rlist list */
38373904
mutex_lock(&pserver->reconnect_mutex);
38383905

3906+
/* if the server is marked for termination, drop the ref count here */
3907+
if (server->terminate) {
3908+
cifs_put_tcp_session(server, true);
3909+
mutex_unlock(&pserver->reconnect_mutex);
3910+
return;
3911+
}
3912+
38393913
INIT_LIST_HEAD(&tmp_list);
38403914
INIT_LIST_HEAD(&tmp_ses_list);
38413915
cifs_dbg(FYI, "Reconnecting tcons and channels\n");

fs/smb/client/transport.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,7 @@ struct TCP_Server_Info *cifs_pick_channel(struct cifs_ses *ses)
10231023
spin_lock(&ses->chan_lock);
10241024
for (i = 0; i < ses->chan_count; i++) {
10251025
server = ses->chans[i].server;
1026-
if (!server)
1026+
if (!server || server->terminate)
10271027
continue;
10281028

10291029
/*

0 commit comments

Comments
 (0)