[v1,11/14] cifs: Add support for failover in cifs_reconnect()

Message ID 20181115142103.24617-12-aaptel@suse.com
State New
Headers show
Series
  • DFS failover
Related show

Commit Message

Aurélien Aptel Nov. 15, 2018, 2:21 p.m.
From: Paulo Alcantara <paulo@paulo.ac>

After failing to reconnect to original target, it will retry any
target available from DFS cache.

Signed-off-by: Paulo Alcantara <palcantara@suse.de>
Signed-off-by: Aurelien Aptel <aaptel@suse.com>
---
 fs/cifs/cifs_fs_sb.h |   9 +++
 fs/cifs/cifsglob.h   |   7 ++
 fs/cifs/connect.c    | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 204 insertions(+), 1 deletion(-)

Comments

Pavel Shilovsky Jan. 10, 2019, 11:32 p.m. | #1
чт, 15 нояб. 2018 г. в 06:22, Aurelien Aptel <aaptel@suse.com>:
>
> From: Paulo Alcantara <paulo@paulo.ac>
>
> After failing to reconnect to original target, it will retry any
> target available from DFS cache.

Hi Paulo, Aurelien,

Please find my comments below. I re-arranged the diff files to follow
the code execution.

>
> Signed-off-by: Paulo Alcantara <palcantara@suse.de>
> Signed-off-by: Aurelien Aptel <aaptel@suse.com>
> ---
>  fs/cifs/cifs_fs_sb.h |   9 +++
>  fs/cifs/cifsglob.h   |   7 ++
>  fs/cifs/connect.c    | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++-
>  3 files changed, 204 insertions(+), 1 deletion(-)
>
...
> +#endif
>
>  /*
>   * cifs tcp session reconnection
> @@ -339,8 +449,34 @@ cifs_reconnect(struct TCP_Server_Info *server)
>         struct cifs_tcon *tcon;
>         struct mid_q_entry *mid_entry;
>         struct list_head retry_list;
> +#ifdef CONFIG_CIFS_DFS_UPCALL
> +       struct cifs_sb_info *cifs_sb;
> +       struct dfs_cache_tgt_list tgt_list;
> +       struct dfs_cache_tgt_iterator *tgt_it = NULL;
> +#endif
>
>         spin_lock(&GlobalMid_Lock);
> +#ifdef CONFIG_CIFS_DFS_UPCALL
> +       cifs_sb = find_super_by_tcp(server);
> +       if (IS_ERR(cifs_sb)) {
> +               rc = PTR_ERR(cifs_sb);

Let's suppose we didn't find anything, tgt_it is still NULL and
tgt_list is empty...

> +       } else {
> +               rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list, &tgt_it);
> +               if (rc) {
> +                       cifs_dbg(VFS, "%s: no target servers for DFS failover\n",
> +                                __func__);
> +               }
> +       }
> +       if (!rc) {
> +               server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list);
> +       } else {
> +               server->nr_targets = 1;
> +               cifs_dbg(VFS, "%s: will not do DFS failover: rc = %d\n",
> +                        __func__, rc);
> +       }
> +       cifs_dbg(FYI, "%s: will retry %d target(s)\n", __func__,
> +                server->nr_targets);
> +#endif
>         if (server->tcpStatus == CifsExiting) {
>                 /* the demux thread will exit normally
>                 next time through the loop */
> @@ -414,14 +550,22 @@ cifs_reconnect(struct TCP_Server_Info *server)
>         do {
>                 try_to_freeze();
>
> -               /* we should try only the port we connected to before */
>                 mutex_lock(&server->srv_mutex);
> +               /*
> +                * Set up next DFS target server (if any) for reconnect. If DFS
> +                * feature is disabled, then we will retry last server we
> +                * connected to before.
> +                */
>                 if (cifs_rdma_enabled(server))
>                         rc = smbd_reconnect(server);
>                 else
>                         rc = generic_ip_connect(server);
>                 if (rc) {
>                         cifs_dbg(FYI, "reconnect error %d\n", rc);
> +#ifdef CONFIG_CIFS_DFS_UPCALL
> +                       reconn_inval_dfs_target(server, cifs_sb, &tgt_list,
> +                                               &tgt_it);

now we call reconn_inval_dfs_target()...

> +#endif
>                         mutex_unlock(&server->srv_mutex);
>                         msleep(3000);
>                 } else {
> @@ -434,6 +578,22 @@ cifs_reconnect(struct TCP_Server_Info *server)
>                 }
>         } while (server->tcpStatus == CifsNeedReconnect);
>
> +#ifdef CONFIG_CIFS_DFS_UPCALL
> +       if (tgt_it) {
> +               rc = dfs_cache_noreq_update_tgthint(cifs_sb->origin_fullpath + 1,
> +                                                   tgt_it);
> +               if (rc) {
> +                       cifs_dbg(VFS, "%s: failed to update DFS target hint: rc = %d\n",
> +                                __func__, rc);
> +               }
> +               rc = dfs_cache_update_vol(cifs_sb->origin_fullpath, server);
> +               if (rc) {
> +                       cifs_dbg(VFS, "%s: failed to update vol info in DFS cache: rc = %d\n",
> +                                __func__, rc);
> +               }
> +       }
> +       dfs_cache_free_tgts(&tgt_list);
> +#endif
>         if (server->tcpStatus == CifsNeedNegotiate)
>                 mod_delayed_work(cifsiod_wq, &server->echo, 0);
>
> @@ -2470,6 +2630,8 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
>         }
>         tcp_ses->tcpStatus = CifsNeedNegotiate;
>
> +       tcp_ses->nr_targets = 1;
> +
>         /* thread spawned, put it on the list */
>         spin_lock(&cifs_tcp_ses_lock);
>         list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);
> @@ -4291,6 +4453,12 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
>                 goto error;
>         }
>
> +       full_path = build_unc_path_to_root(vol, cifs_sb, true);
> +       if (IS_ERR(full_path)) {
> +               rc = PTR_ERR(full_path);
> +               full_path = NULL;
> +               goto error;
> +       }
>         /*
>          * Perform an unconditional check for whether there are DFS
>          * referrals for this path without prefix, to provide support
> @@ -4397,6 +4565,22 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
>         if (rc)
>                 goto error;
>
> +       spin_lock(&cifs_tcp_ses_lock);
> +       if (!tcon->dfs_path) {
> +               /* Save full path in new tcon to do failover when reconnecting tcons */
> +               tcon->dfs_path = full_path;
> +               full_path = NULL;
> +               tcon->remap = cifs_remap(cifs_sb);
> +       }
> +       cifs_sb->origin_fullpath = kstrndup(tcon->dfs_path,
> +                                           strlen(tcon->dfs_path), GFP_KERNEL);
> +       if (!cifs_sb->origin_fullpath) {
> +               spin_unlock(&cifs_tcp_ses_lock);
> +               rc = -ENOMEM;
> +               goto error;
> +       }
> +       spin_unlock(&cifs_tcp_ses_lock);
> +
>         /*
>          * After reconnecting to a different server, unique ids won't
>          * match anymore, so we disable serverino. This prevents
> @@ -4638,6 +4822,9 @@ cifs_umount(struct cifs_sb_info *cifs_sb)
>
>         kfree(cifs_sb->mountdata);
>         kfree(cifs_sb->prepath);
> +#ifdef CONFIG_CIFS_DFS_UPCALL
> +       kfree(cifs_sb->origin_fullpath);
> +#endif
>         call_rcu(&cifs_sb->rcu, delayed_free);
>  }
>
> --
> 2.13.7
>
 ...
> diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h
> index 63d7530f2e1d..42f0d67f1054 100644
> --- a/fs/cifs/cifs_fs_sb.h
> +++ b/fs/cifs/cifs_fs_sb.h
> @@ -72,6 +72,15 @@ struct cifs_sb_info {
>         char   *mountdata; /* options received at mount time or via DFS refs */
>         struct delayed_work prune_tlinks;
>         struct rcu_head rcu;
> +
> +       /* only used when CIFS_MOUNT_USE_PREFIX_PATH is set */
>         char *prepath;
> +
> +       /*
> +        * Path initially provided by the mount call. We might connect
> +        * to something different via DFS but we want to keep it to do
> +        * failover properly.
> +        */
> +       char *origin_fullpath; /* \\HOST\SHARE\[OPTIONAL PATH] */
>  };
>  #endif                         /* _CIFS_FS_SB_H */
> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> index 60c202bbcdfc..643633a3dc09 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -701,6 +701,13 @@ struct TCP_Server_Info {
>         struct delayed_work reconnect; /* reconnect workqueue job */
>         struct mutex reconnect_mutex; /* prevent simultaneous reconnects */
>         unsigned long echo_interval;
> +
> +       /*
> +        * Number of targets available for reconnect. The more targets
> +        * the more tasks have to wait to let the demultiplex thread
> +        * reconnect.
> +        */
> +       int nr_targets;
>  };
>
>  static inline unsigned int
> diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
> index c4ce52818ed6..52d837d7bf57 100644
> --- a/fs/cifs/connect.c
> +++ b/fs/cifs/connect.c
> @@ -57,6 +57,7 @@
>  #include "smb2proto.h"
>  #include "smbdirect.h"
>  #include "dns_resolve.h"
> +#include "cifsfs.h"
>  #ifdef CONFIG_CIFS_DFS_UPCALL
>  #include "dfs_cache.h"
>  #endif
> @@ -321,6 +322,115 @@ static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink);
>  static void cifs_prune_tlinks(struct work_struct *work);
>  static int cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data,
>                                         const char *devname, bool is_smb3);
> +static char *extract_hostname(const char *unc);
> +
> +#ifdef CONFIG_CIFS_DFS_UPCALL
> +struct super_cb_data {
> +       struct TCP_Server_Info *server;
> +       struct cifs_sb_info *cifs_sb;
> +};
> +
> +/* These functions must be called with server->srv_mutex held */
> +
> +static void super_cb(struct super_block *sb, void *arg)
> +{
> +       struct super_cb_data *d = arg;
> +       struct cifs_sb_info *cifs_sb;
> +       struct cifs_tcon *tcon;
> +
> +       if (d->cifs_sb)
> +               return;
> +
> +       cifs_sb = CIFS_SB(sb);
> +       tcon = cifs_sb_master_tcon(cifs_sb);
> +       if (tcon->ses->server == d->server)
> +               d->cifs_sb = cifs_sb;
> +}
> +
> +static inline struct cifs_sb_info *
> +find_super_by_tcp(struct TCP_Server_Info *server)
> +{
> +       struct super_cb_data d = {
> +               .server = server,
> +               .cifs_sb = NULL,
> +       };
> +
> +       iterate_supers_type(&cifs_fs_type, super_cb, &d);
> +       return d.cifs_sb ? d.cifs_sb : ERR_PTR(-ENOENT);
> +}
> +
> +static void reconn_inval_dfs_target(struct TCP_Server_Info *server,
> +                                   struct cifs_sb_info *cifs_sb,
> +                                   struct dfs_cache_tgt_list *tgt_list,
> +                                   struct dfs_cache_tgt_iterator **tgt_it)
> +{
> +       const char *name;
> +       int rc;
> +       char *ipaddr = NULL;
> +       char *unc;
> +       int len;
> +
> +       if (!cifs_sb->origin_fullpath || !tgt_list || !server->nr_targets)
> +               return;
> +
> +       if (!*tgt_it) {
> +               *tgt_it = dfs_cache_get_tgt_iterator(tgt_list);

here tgt_list is still empty, so *tgt_it will be NULL after the line above.


> +       } else {
> +               *tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it);
> +               if (!*tgt_it)
> +                       *tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
> +       }
> +
> +       cifs_dbg(FYI, "%s: UNC: %s\n", __func__, cifs_sb->origin_fullpath);
> +
> +       name = dfs_cache_get_tgt_name(*tgt_it);

Now we call dfs_cache_get_tgt_name which sets name to NULL too.

> +
> +       kfree(server->hostname);
> +
> +       server->hostname = extract_hostname(name);

And here we we call extract_hostname(NULL) which calls strlen ->
NULL-ptr dereference. It seems like we don't need to kfree the
existing server->hostname if name is NULL here few lines above.

> +       if (!server->hostname) {
> +               cifs_dbg(FYI, "%s: failed to extract hostname from target: %d\n",
> +                        __func__, -ENOMEM);
> +               return;
> +       }
> +
> +       len = strlen(server->hostname) + 3;
> +
> +       unc = kmalloc(len, GFP_KERNEL);
> +       if (!unc) {
> +               cifs_dbg(FYI, "%s: failed to create UNC path\n", __func__);
> +               return;
> +       }
> +       snprintf(unc, len, "\\\\%s", server->hostname);
> +
> +       rc = dns_resolve_server_name_to_ip(unc, &ipaddr);
> +       kfree(unc);
> +
> +       if (rc < 0) {
> +               cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n",
> +                        __func__, server->hostname, rc);
> +               return;
> +       }
> +
> +       rc = cifs_convert_address((struct sockaddr *)&server->dstaddr, ipaddr,
> +                                 strlen(ipaddr));
> +       kfree(ipaddr);
> +
> +       if (!rc) {
> +               cifs_dbg(FYI, "%s: failed to get ipaddr out of hostname\n",
> +                        __func__);
> +       }
> +}
> +
> +static inline int reconn_setup_dfs_targets(struct cifs_sb_info *cifs_sb,
> +                                          struct dfs_cache_tgt_list *tl,
> +                                          struct dfs_cache_tgt_iterator **it)
> +{
> +       if (!cifs_sb->origin_fullpath)
> +               return -EOPNOTSUPP;
> +       return dfs_cache_noreq_find(cifs_sb->origin_fullpath + 1, NULL, tl);
> +}

--
Best regards,
Pavel Shilovsky

Patch

diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h
index 63d7530f2e1d..42f0d67f1054 100644
--- a/fs/cifs/cifs_fs_sb.h
+++ b/fs/cifs/cifs_fs_sb.h
@@ -72,6 +72,15 @@  struct cifs_sb_info {
 	char   *mountdata; /* options received at mount time or via DFS refs */
 	struct delayed_work prune_tlinks;
 	struct rcu_head rcu;
+
+	/* only used when CIFS_MOUNT_USE_PREFIX_PATH is set */
 	char *prepath;
+
+	/*
+	 * Path initially provided by the mount call. We might connect
+	 * to something different via DFS but we want to keep it to do
+	 * failover properly.
+	 */
+	char *origin_fullpath; /* \\HOST\SHARE\[OPTIONAL PATH] */
 };
 #endif				/* _CIFS_FS_SB_H */
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 60c202bbcdfc..643633a3dc09 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -701,6 +701,13 @@  struct TCP_Server_Info {
 	struct delayed_work reconnect; /* reconnect workqueue job */
 	struct mutex reconnect_mutex; /* prevent simultaneous reconnects */
 	unsigned long echo_interval;
+
+	/*
+	 * Number of targets available for reconnect. The more targets
+	 * the more tasks have to wait to let the demultiplex thread
+	 * reconnect.
+	 */
+	int nr_targets;
 };
 
 static inline unsigned int
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index c4ce52818ed6..52d837d7bf57 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -57,6 +57,7 @@ 
 #include "smb2proto.h"
 #include "smbdirect.h"
 #include "dns_resolve.h"
+#include "cifsfs.h"
 #ifdef CONFIG_CIFS_DFS_UPCALL
 #include "dfs_cache.h"
 #endif
@@ -321,6 +322,115 @@  static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink);
 static void cifs_prune_tlinks(struct work_struct *work);
 static int cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data,
 					const char *devname, bool is_smb3);
+static char *extract_hostname(const char *unc);
+
+#ifdef CONFIG_CIFS_DFS_UPCALL
+struct super_cb_data {
+	struct TCP_Server_Info *server;
+	struct cifs_sb_info *cifs_sb;
+};
+
+/* These functions must be called with server->srv_mutex held */
+
+static void super_cb(struct super_block *sb, void *arg)
+{
+	struct super_cb_data *d = arg;
+	struct cifs_sb_info *cifs_sb;
+	struct cifs_tcon *tcon;
+
+	if (d->cifs_sb)
+		return;
+
+	cifs_sb = CIFS_SB(sb);
+	tcon = cifs_sb_master_tcon(cifs_sb);
+	if (tcon->ses->server == d->server)
+		d->cifs_sb = cifs_sb;
+}
+
+static inline struct cifs_sb_info *
+find_super_by_tcp(struct TCP_Server_Info *server)
+{
+	struct super_cb_data d = {
+		.server = server,
+		.cifs_sb = NULL,
+	};
+
+	iterate_supers_type(&cifs_fs_type, super_cb, &d);
+	return d.cifs_sb ? d.cifs_sb : ERR_PTR(-ENOENT);
+}
+
+static void reconn_inval_dfs_target(struct TCP_Server_Info *server,
+				    struct cifs_sb_info *cifs_sb,
+				    struct dfs_cache_tgt_list *tgt_list,
+				    struct dfs_cache_tgt_iterator **tgt_it)
+{
+	const char *name;
+	int rc;
+	char *ipaddr = NULL;
+	char *unc;
+	int len;
+
+	if (!cifs_sb->origin_fullpath || !tgt_list || !server->nr_targets)
+		return;
+
+	if (!*tgt_it) {
+		*tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
+	} else {
+		*tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it);
+		if (!*tgt_it)
+			*tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
+	}
+
+	cifs_dbg(FYI, "%s: UNC: %s\n", __func__, cifs_sb->origin_fullpath);
+
+	name = dfs_cache_get_tgt_name(*tgt_it);
+
+	kfree(server->hostname);
+
+	server->hostname = extract_hostname(name);
+	if (!server->hostname) {
+		cifs_dbg(FYI, "%s: failed to extract hostname from target: %d\n",
+			 __func__, -ENOMEM);
+		return;
+	}
+
+	len = strlen(server->hostname) + 3;
+
+	unc = kmalloc(len, GFP_KERNEL);
+	if (!unc) {
+		cifs_dbg(FYI, "%s: failed to create UNC path\n", __func__);
+		return;
+	}
+	snprintf(unc, len, "\\\\%s", server->hostname);
+
+	rc = dns_resolve_server_name_to_ip(unc, &ipaddr);
+	kfree(unc);
+
+	if (rc < 0) {
+		cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n",
+			 __func__, server->hostname, rc);
+		return;
+	}
+
+	rc = cifs_convert_address((struct sockaddr *)&server->dstaddr, ipaddr,
+				  strlen(ipaddr));
+	kfree(ipaddr);
+
+	if (!rc) {
+		cifs_dbg(FYI, "%s: failed to get ipaddr out of hostname\n",
+			 __func__);
+	}
+}
+
+static inline int reconn_setup_dfs_targets(struct cifs_sb_info *cifs_sb,
+					   struct dfs_cache_tgt_list *tl,
+					   struct dfs_cache_tgt_iterator **it)
+{
+	if (!cifs_sb->origin_fullpath)
+		return -EOPNOTSUPP;
+	return dfs_cache_noreq_find(cifs_sb->origin_fullpath + 1, NULL, tl);
+}
+#endif
 
 /*
  * cifs tcp session reconnection
@@ -339,8 +449,34 @@  cifs_reconnect(struct TCP_Server_Info *server)
 	struct cifs_tcon *tcon;
 	struct mid_q_entry *mid_entry;
 	struct list_head retry_list;
+#ifdef CONFIG_CIFS_DFS_UPCALL
+	struct cifs_sb_info *cifs_sb;
+	struct dfs_cache_tgt_list tgt_list;
+	struct dfs_cache_tgt_iterator *tgt_it = NULL;
+#endif
 
 	spin_lock(&GlobalMid_Lock);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+	cifs_sb = find_super_by_tcp(server);
+	if (IS_ERR(cifs_sb)) {
+		rc = PTR_ERR(cifs_sb);
+	} else {
+		rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list, &tgt_it);
+		if (rc) {
+			cifs_dbg(VFS, "%s: no target servers for DFS failover\n",
+				 __func__);
+		}
+	}
+	if (!rc) {
+		server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list);
+	} else {
+		server->nr_targets = 1;
+		cifs_dbg(VFS, "%s: will not do DFS failover: rc = %d\n",
+			 __func__, rc);
+	}
+	cifs_dbg(FYI, "%s: will retry %d target(s)\n", __func__,
+		 server->nr_targets);
+#endif
 	if (server->tcpStatus == CifsExiting) {
 		/* the demux thread will exit normally
 		next time through the loop */
@@ -414,14 +550,22 @@  cifs_reconnect(struct TCP_Server_Info *server)
 	do {
 		try_to_freeze();
 
-		/* we should try only the port we connected to before */
 		mutex_lock(&server->srv_mutex);
+		/*
+		 * Set up next DFS target server (if any) for reconnect. If DFS
+		 * feature is disabled, then we will retry last server we
+		 * connected to before.
+		 */
 		if (cifs_rdma_enabled(server))
 			rc = smbd_reconnect(server);
 		else
 			rc = generic_ip_connect(server);
 		if (rc) {
 			cifs_dbg(FYI, "reconnect error %d\n", rc);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+			reconn_inval_dfs_target(server, cifs_sb, &tgt_list,
+						&tgt_it);
+#endif
 			mutex_unlock(&server->srv_mutex);
 			msleep(3000);
 		} else {
@@ -434,6 +578,22 @@  cifs_reconnect(struct TCP_Server_Info *server)
 		}
 	} while (server->tcpStatus == CifsNeedReconnect);
 
+#ifdef CONFIG_CIFS_DFS_UPCALL
+	if (tgt_it) {
+		rc = dfs_cache_noreq_update_tgthint(cifs_sb->origin_fullpath + 1,
+						    tgt_it);
+		if (rc) {
+			cifs_dbg(VFS, "%s: failed to update DFS target hint: rc = %d\n",
+				 __func__, rc);
+		}
+		rc = dfs_cache_update_vol(cifs_sb->origin_fullpath, server);
+		if (rc) {
+			cifs_dbg(VFS, "%s: failed to update vol info in DFS cache: rc = %d\n",
+				 __func__, rc);
+		}
+	}
+	dfs_cache_free_tgts(&tgt_list);
+#endif
 	if (server->tcpStatus == CifsNeedNegotiate)
 		mod_delayed_work(cifsiod_wq, &server->echo, 0);
 
@@ -2470,6 +2630,8 @@  cifs_get_tcp_session(struct smb_vol *volume_info)
 	}
 	tcp_ses->tcpStatus = CifsNeedNegotiate;
 
+	tcp_ses->nr_targets = 1;
+
 	/* thread spawned, put it on the list */
 	spin_lock(&cifs_tcp_ses_lock);
 	list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);
@@ -4291,6 +4453,12 @@  int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
 		goto error;
 	}
 
+	full_path = build_unc_path_to_root(vol, cifs_sb, true);
+	if (IS_ERR(full_path)) {
+		rc = PTR_ERR(full_path);
+		full_path = NULL;
+		goto error;
+	}
 	/*
 	 * Perform an unconditional check for whether there are DFS
 	 * referrals for this path without prefix, to provide support
@@ -4397,6 +4565,22 @@  int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
 	if (rc)
 		goto error;
 
+	spin_lock(&cifs_tcp_ses_lock);
+	if (!tcon->dfs_path) {
+		/* Save full path in new tcon to do failover when reconnecting tcons */
+		tcon->dfs_path = full_path;
+		full_path = NULL;
+		tcon->remap = cifs_remap(cifs_sb);
+	}
+	cifs_sb->origin_fullpath = kstrndup(tcon->dfs_path,
+					    strlen(tcon->dfs_path), GFP_KERNEL);
+	if (!cifs_sb->origin_fullpath) {
+		spin_unlock(&cifs_tcp_ses_lock);
+		rc = -ENOMEM;
+		goto error;
+	}
+	spin_unlock(&cifs_tcp_ses_lock);
+
 	/*
 	 * After reconnecting to a different server, unique ids won't
 	 * match anymore, so we disable serverino. This prevents
@@ -4638,6 +4822,9 @@  cifs_umount(struct cifs_sb_info *cifs_sb)
 
 	kfree(cifs_sb->mountdata);
 	kfree(cifs_sb->prepath);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+	kfree(cifs_sb->origin_fullpath);
+#endif
 	call_rcu(&cifs_sb->rcu, delayed_free);
 }