Blob Blame History Raw
From: Paulo Alcantara <pc@cjr.nz>
Date: Thu, 15 Jul 2021 21:53:53 -0300
Subject: [PATCH] cifs: do not share tcp sessions of dfs connections
Git-commit: cdc3363065aba2711e51019b3d5787f044f8a133
References: bsc#1185902
Patch-mainline: v5.14-rc2

Make sure that we do not share tcp sessions of dfs mounts when
mounting regular shares that connect to same server.  DFS connections
rely on a single instance of tcp in order to do failover properly in
cifs_reconnect().

Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
Signed-off-by: Steve French <stfrench@microsoft.com>
Acked-by: Paulo Alcantara <palcantara@suse.de>
---
 fs/cifs/cifsglob.h |    3 +++
 fs/cifs/connect.c  |   39 +++++++++++++++++++++++++++++++++++----
 2 files changed, 38 insertions(+), 4 deletions(-)

--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -773,6 +773,9 @@ struct TCP_Server_Info {
 	 * reconnect.
 	 */
 	int nr_targets;
+#ifdef CONFIG_CIFS_DFS_UPCALL
+	bool is_dfs_conn; /* if a dfs connection */
+#endif
 };
 
 struct cifs_credits {
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -2639,6 +2639,16 @@ cifs_find_tcp_session(struct smb_vol *vo
 
 	spin_lock(&cifs_tcp_ses_lock);
 	list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
+#ifdef CONFIG_CIFS_DFS_UPCALL
+		/*
+		 * DFS failover implementation in cifs_reconnect() requires unique tcp sessions for
+		 * DFS connections to do failover properly, so avoid sharing them with regular
+		 * shares or even links that may connect to same server but having completely
+		 * different failover targets.
+		 */
+		if (server->is_dfs_conn)
+			continue;
+#endif
 		if (!match_server(server, vol))
 			continue;
 
@@ -4292,6 +4302,23 @@ static int mount_setup_tlink(struct cifs
 }
 
 #ifdef CONFIG_CIFS_DFS_UPCALL
+static int mount_get_dfs_conns(struct smb_vol *vol, struct cifs_sb_info *cifs_sb,
+			       unsigned int *xid, struct TCP_Server_Info **nserver,
+			       struct cifs_ses **nses, struct cifs_tcon **ntcon)
+{
+	int rc;
+
+	vol->nosharesock = true;
+	rc = mount_get_conns(vol, cifs_sb, xid, nserver, nses, ntcon);
+	if (*nserver) {
+		cifs_dbg(FYI, "%s: marking tcp session as a dfs connection\n", __func__);
+		spin_lock(&cifs_tcp_ses_lock);
+		(*nserver)->is_dfs_conn = true;
+		spin_unlock(&cifs_tcp_ses_lock);
+	}
+	return rc;
+}
+
 /*
  * cifs_build_path_to_root returns full path to root when we do not have an
  * exiting connection (tcon)
@@ -4453,8 +4480,7 @@ static int setup_dfs_tgt_conn(const char
 		 * targets.
 		 */
 		mount_put_conns(cifs_sb, *xid, *server, *ses, *tcon);
-		rc = mount_get_conns(&fake_vol, cifs_sb, xid, server, ses,
-				     tcon);
+		rc = mount_get_dfs_conns(&fake_vol, cifs_sb, xid, server, ses, tcon);
 		if (!rc || (*server && *ses)) {
 			/*
 			 * We were able to connect to new target server.
@@ -4797,7 +4823,12 @@ int cifs_mount(struct cifs_sb_info *cifs
 			goto error;
 	}
 
-	vol->nosharesock = true;
+	mount_put_conns(cifs_sb, xid, server, ses, tcon);
+	/*
+	 * Ignore error check here because we may failover to other targets from cached a
+	 * referral.
+	 */
+	(void)mount_get_dfs_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
 
 	/* Get path of DFS root */
 	ref_path = build_unc_path_to_root(vol, cifs_sb, false);
@@ -4826,7 +4857,7 @@ int cifs_mount(struct cifs_sb_info *cifs
 		/* Connect to new DFS target only if we were redirected */
 		if (oldmnt != cifs_sb->mountdata) {
 			mount_put_conns(cifs_sb, xid, server, ses, tcon);
-			rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
+			rc = mount_get_dfs_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
 		}
 		if (rc && !server && !ses) {
 			/* Failed to connect. Try to connect to other targets in the referral. */