Blob Blame History Raw
From: Paulo Alcantara <pc@cjr.nz>
Date: Tue, 19 May 2020 15:38:28 -0300
Subject: [PATCH] cifs: handle hostnames that resolve to same ip in failover
Git-commit: e4af35fa55b072190711c11e2bfff8326d313948
References: bsc#1144333 bsc#1161016
Patch-mainline: v5.8-rc1

In order to support reconnect to hostnames that resolve to same ip
address, besides relying on the currently set hostname to match DFS
targets, attempt to resolve the targets and then match their addresses
with the reconnected server ip address.

For instance, if we have two hostnames "FOO" and "BAR", and both
resolve to the same ip address, we would be able to handle failover in
DFS paths like

    \\FOO\dfs\link1 -> [ \BAZ\share2 (*), \BAR\share1 ]
    \\FOO\dfs\link2 -> [ \BAZ\share2 (*), \FOO\share1 ]

so when "BAZ" is no longer accessible, link1 and link2 would get
reconnected despite having different target hostnames.

Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
Reviewed-by: Aurelien Aptel <aaptel@suse.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
Acked-by: Paulo Alcantara <palcantara@suse.de>
---
 fs/cifs/cifsproto.h |  5 ++++
 fs/cifs/cifssmb.c   | 55 +++++++++++++++++++++++++++++---------------
 fs/cifs/connect.c   |  6 ++---
 fs/cifs/misc.c      | 48 ++++++++++++++++++++++++++++++++++++++
 fs/cifs/smb2pdu.c   | 56 ++++++++++++++++++++++++++++++---------------
 5 files changed, 131 insertions(+), 39 deletions(-)

diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index 12a895e02db4..311d8e86c5a8 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -89,6 +89,7 @@ extern void cifs_mid_q_entry_release(struct mid_q_entry *midEntry);
 extern void cifs_wake_up_task(struct mid_q_entry *mid);
 extern int cifs_handle_standard(struct TCP_Server_Info *server,
 				struct mid_q_entry *mid);
+extern bool cifs_match_ipaddr(struct sockaddr *srcaddr, struct sockaddr *rhs);
 extern int cifs_discard_remaining_data(struct TCP_Server_Info *server);
 extern int cifs_call_async(struct TCP_Server_Info *server,
 			struct smb_rqst *rqst,
@@ -616,6 +617,10 @@ static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses,
 	return dfs_cache_find(xid, ses, nls_codepage, remap, old_path,
 			      referral, NULL);
 }
+
+int match_target_ip(struct TCP_Server_Info *server,
+		    const char *share, size_t share_len,
+		    bool *result);
 #endif
 
 static inline int cifs_create_options(struct cifs_sb_info *cifs_sb, int options)
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index d62f9175c546..5a684f9ac883 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -129,6 +129,7 @@ static int __cifs_reconnect_tcon(const struct nls_table *nlsc,
 				 struct cifs_tcon *tcon)
 {
 	int rc;
+	struct TCP_Server_Info *server = tcon->ses->server;
 	struct dfs_cache_tgt_list tl;
 	struct dfs_cache_tgt_iterator *it = NULL;
 	char *tree;
@@ -141,15 +142,14 @@ static int __cifs_reconnect_tcon(const struct nls_table *nlsc,
 	if (!tree)
 		return -ENOMEM;
 
-	if (tcon->ipc) {
-		scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$",
-			  tcon->ses->server->hostname);
-		rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc);
-		goto out;
-	}
-
 	if (!tcon->dfs_path) {
-		rc = CIFSTCon(0, tcon->ses, tcon->treeName, tcon, nlsc);
+		if (tcon->ipc) {
+			scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$",
+				  server->hostname);
+			rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc);
+		} else {
+			rc = CIFSTCon(0, tcon->ses, tcon->treeName, tcon, nlsc);
+		}
 		goto out;
 	}
 
@@ -157,13 +157,13 @@ static int __cifs_reconnect_tcon(const struct nls_table *nlsc,
 	if (rc)
 		goto out;
 
-	extract_unc_hostname(tcon->ses->server->hostname, &tcp_host,
-			     &tcp_host_len);
+	extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len);
 
 	for (it = dfs_cache_get_tgt_iterator(&tl); it;
 	     it = dfs_cache_get_next_tgt(&tl, it)) {
 		const char *share, *prefix;
 		size_t share_len, prefix_len;
+		bool target_match;
 
 		rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix,
 					     &prefix_len);
@@ -177,19 +177,38 @@ static int __cifs_reconnect_tcon(const struct nls_table *nlsc,
 
 		if (dfs_host_len != tcp_host_len
 		    || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
-			cifs_dbg(FYI, "%s: skipping %.*s, doesn't match %.*s",
+			cifs_dbg(FYI, "%s: %.*s doesn't match %.*s",
 				 __func__,
 				 (int)dfs_host_len, dfs_host,
 				 (int)tcp_host_len, tcp_host);
-			continue;
+
+			rc = match_target_ip(server, dfs_host, dfs_host_len,
+					     &target_match);
+			if (rc) {
+				cifs_dbg(VFS, "%s: failed to match target ip: %d\n",
+					 __func__, rc);
+				break;
+			}
+
+			if (!target_match) {
+				cifs_dbg(FYI, "%s: skipping target\n", __func__);
+				continue;
+			}
 		}
 
-		scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len, share);
-
-		rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc);
-		if (!rc) {
-			rc = update_super_prepath(tcon, prefix, prefix_len);
-			break;
+		if (tcon->ipc) {
+			scnprintf(tree, MAX_TREE_SIZE, "\\\\%.*s\\IPC$",
+				  (int)share_len, share);
+			rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc);
+		} else {
+			scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len,
+				  share);
+			rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc);
+			if (!rc) {
+				rc = update_super_prepath(tcon, prefix,
+							  prefix_len);
+				break;
+			}
 		}
 		if (rc == -EREMOTE)
 			break;
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index aa1173f3fb12..ea3189a6ae10 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -2496,8 +2496,8 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
  * specified, or if srcaddr is specified and
  * matches the IP address of the rhs argument.
  */
-static bool
-srcip_matches(struct sockaddr *srcaddr, struct sockaddr *rhs)
+bool
+cifs_match_ipaddr(struct sockaddr *srcaddr, struct sockaddr *rhs)
 {
 	switch (srcaddr->sa_family) {
 	case AF_UNSPEC:
@@ -2588,7 +2588,7 @@ match_address(struct TCP_Server_Info *server, struct sockaddr *addr,
 		return false; /* don't expect to be here */
 	}
 
-	if (!srcip_matches(srcaddr, (struct sockaddr *)&server->srcaddr))
+	if (!cifs_match_ipaddr(srcaddr, (struct sockaddr *)&server->srcaddr))
 		return false;
 
 	return true;
diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c
index 550ce9020a3e..1ec6a5543eda 100644
--- a/fs/cifs/misc.c
+++ b/fs/cifs/misc.c
@@ -32,6 +32,9 @@
 #include "cifs_unicode.h"
 #include "smb2pdu.h"
 #include "cifsfs.h"
+#ifdef CONFIG_CIFS_DFS_UPCALL
+#include "dns_resolve.h"
+#endif
 
 extern mempool_t *cifs_sm_req_poolp;
 extern mempool_t *cifs_req_poolp;
@@ -1083,6 +1086,51 @@ void cifs_put_tcp_super(struct super_block *sb)
 }
 
 #ifdef CONFIG_CIFS_DFS_UPCALL
+int match_target_ip(struct TCP_Server_Info *server,
+		    const char *share, size_t share_len,
+		    bool *result)
+{
+	int rc;
+	char *target, *tip = NULL;
+	struct sockaddr tipaddr;
+
+	*result = false;
+
+	target = kzalloc(share_len + 3, GFP_KERNEL);
+	if (!target) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	scnprintf(target, share_len + 3, "\\\\%.*s", (int)share_len, share);
+
+	cifs_dbg(FYI, "%s: target name: %s\n", __func__, target + 2);
+
+	rc = dns_resolve_server_name_to_ip(target, &tip);
+	if (rc < 0)
+		goto out;
+
+	cifs_dbg(FYI, "%s: target ip: %s\n", __func__, tip);
+
+	if (!cifs_convert_address(&tipaddr, tip, strlen(tip))) {
+		cifs_dbg(VFS, "%s: failed to convert target ip address\n",
+			 __func__);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	*result = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr,
+				    &tipaddr);
+	cifs_dbg(FYI, "%s: ip addresses match: %u\n", __func__, *result);
+	rc = 0;
+
+out:
+	kfree(target);
+	kfree(tip);
+
+	return rc;
+}
+
 static void tcon_super_cb(struct super_block *sb, void *arg)
 {
 	struct super_cb_data *sd = arg;
diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
index b30aa3cdd845..cabc19f404e6 100644
--- a/fs/cifs/smb2pdu.c
+++ b/fs/cifs/smb2pdu.c
@@ -160,6 +160,7 @@ static int __smb2_reconnect(const struct nls_table *nlsc,
 			    struct cifs_tcon *tcon)
 {
 	int rc;
+	struct TCP_Server_Info *server = tcon->ses->server;
 	struct dfs_cache_tgt_list tl;
 	struct dfs_cache_tgt_iterator *it = NULL;
 	char *tree;
@@ -172,15 +173,15 @@ static int __smb2_reconnect(const struct nls_table *nlsc,
 	if (!tree)
 		return -ENOMEM;
 
-	if (tcon->ipc) {
-		scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$",
-			  tcon->ses->server->hostname);
-		rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc);
-		goto out;
-	}
-
 	if (!tcon->dfs_path) {
-		rc = SMB2_tcon(0, tcon->ses, tcon->treeName, tcon, nlsc);
+		if (tcon->ipc) {
+			scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$",
+				  server->hostname);
+			rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc);
+		} else {
+			rc = SMB2_tcon(0, tcon->ses, tcon->treeName, tcon,
+				       nlsc);
+		}
 		goto out;
 	}
 
@@ -188,13 +189,13 @@ static int __smb2_reconnect(const struct nls_table *nlsc,
 	if (rc)
 		goto out;
 
-	extract_unc_hostname(tcon->ses->server->hostname, &tcp_host,
-			     &tcp_host_len);
+	extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len);
 
 	for (it = dfs_cache_get_tgt_iterator(&tl); it;
 	     it = dfs_cache_get_next_tgt(&tl, it)) {
 		const char *share, *prefix;
 		size_t share_len, prefix_len;
+		bool target_match;
 
 		rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix,
 					     &prefix_len);
@@ -208,19 +209,38 @@ static int __smb2_reconnect(const struct nls_table *nlsc,
 
 		if (dfs_host_len != tcp_host_len
 		    || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
-			cifs_dbg(FYI, "%s: skipping %.*s, doesn't match %.*s",
+			cifs_dbg(FYI, "%s: %.*s doesn't match %.*s",
 				 __func__,
 				 (int)dfs_host_len, dfs_host,
 				 (int)tcp_host_len, tcp_host);
-			continue;
+
+			rc = match_target_ip(server, dfs_host, dfs_host_len,
+					     &target_match);
+			if (rc) {
+				cifs_dbg(VFS, "%s: failed to match target ip: %d\n",
+					 __func__, rc);
+				break;
+			}
+
+			if (!target_match) {
+				cifs_dbg(FYI, "%s: skipping target\n", __func__);
+				continue;
+			}
 		}
 
-		scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len, share);
-
-		rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc);
-		if (!rc) {
-			rc = update_super_prepath(tcon, prefix, prefix_len);
-			break;
+		if (tcon->ipc) {
+			scnprintf(tree, MAX_TREE_SIZE, "\\\\%.*s\\IPC$",
+				  (int)share_len, share);
+			rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc);
+		} else {
+			scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len,
+				  share);
+			rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc);
+			if (!rc) {
+				rc = update_super_prepath(tcon, prefix,
+							  prefix_len);
+				break;
+			}
 		}
 		if (rc == -EREMOTE)
 			break;
-- 
2.26.2