Commit ee1d2179 authored by Shyam Prasad N's avatar Shyam Prasad N Committed by Steve French
Browse files

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: default avatarShyam Prasad N <sprasad@microsoft.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 705fc522
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -650,6 +650,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 */
+2 −0
Original line number Diff line number Diff line
@@ -641,6 +641,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
+10 −0
Original line number Diff line number Diff line
@@ -219,6 +219,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 */
		if (!cifs_chan_is_iface_active(ses, server))
			cifs_chan_update_iface(ses, server);
@@ -253,6 +261,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;
+56 −8
Original line number Diff line number Diff line
@@ -296,6 +296,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
@@ -595,14 +649,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--;
@@ -612,8 +662,6 @@ cifs_ses_add_channel(struct cifs_ses *ses,
		 */
		WARN_ON(ses->chan_count < 1);
		spin_unlock(&ses->chan_lock);

		cifs_put_tcp_session(chan->server, 0);
	}

	kfree(ctx->UNC);
+75 −1
Original line number Diff line number Diff line
@@ -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 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
		 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 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,

	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);
@@ -3836,6 +3903,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");
Loading