diff mbox series

[2/3] cifs: handle multiple ip addresses per hostname

Message ID 20210511163609.11019-3-pc@cjr.nz
State New
Headers show
Series Support multiple ips per hostname | expand

Commit Message

Paulo Alcantara May 11, 2021, 4:36 p.m. UTC
Define a maximum number of 16 (CIFS_MAX_ADDR_COUNT) ip addresses the
client can handle per hostname at mount and reconnect time.
mount.cifs(8) will be responsible for passing multiple 'ip=' options
in order to tell what ip addresses the given hostname resolved to.

cifs.ko will now connect to all resolved ip addresses and pick up the
one that got connected first. In case there is no upcall or it failed
for some reason, the client will reuse the resolved ip addresses that
came from userspace mount.cifs(8).

Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
---
 fs/cifs/cifs_dfs_ref.c |   48 +-
 fs/cifs/cifsglob.h     |    8 +-
 fs/cifs/connect.c      | 1042 ++++++++++++++++++++++++----------------
 fs/cifs/dfs_cache.c    |    6 +-
 fs/cifs/dns_resolve.c  |   72 +--
 fs/cifs/dns_resolve.h  |    5 +-
 fs/cifs/fs_context.c   |   22 +-
 fs/cifs/fs_context.h   |    4 +-
 fs/cifs/misc.c         |   47 +-
 9 files changed, 758 insertions(+), 496 deletions(-)

Comments

kernel test robot May 11, 2021, 6:47 p.m. UTC | #1
Hi Paulo,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on cifs/for-next]
[also build test ERROR on v5.13-rc1 next-20210511]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Paulo-Alcantara/Support-multiple-ips-per-hostname/20210512-003751
base:   git://git.samba.org/sfrench/cifs-2.6.git for-next
config: microblaze-randconfig-s032-20210511 (attached as .config)
compiler: microblaze-linux-gcc (GCC) 9.3.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # apt-get install sparse
        # sparse version: v0.6.3-341-g8af24329-dirty
        # https://github.com/0day-ci/linux/commit/210f8e08a6bb153136929af6da6e0a7289ba5931
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Paulo-Alcantara/Support-multiple-ips-per-hostname/20210512-003751
        git checkout 210f8e08a6bb153136929af6da6e0a7289ba5931
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' W=1 ARCH=microblaze 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All error/warnings (new ones prefixed by >>):

   fs/cifs/connect.c: In function 'cifs_create_socket':
>> fs/cifs/connect.c:177:6: warning: variable 'slen' set but not used [-Wunused-but-set-variable]
     177 |  int slen, sfamily;
         |      ^~~~
   fs/cifs/connect.c: In function 'cifs_reconnect':
>> fs/cifs/connect.c:726:46: error: 'cifs_sb' undeclared (first use in this function); did you mean 'cifs_ses'?
     726 |    if (IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) && cifs_sb &&
         |                                              ^~~~~~~
         |                                              cifs_ses
   fs/cifs/connect.c:726:46: note: each undeclared identifier is reported only once for each function it appears in
>> fs/cifs/connect.c:733:5: error: implicit declaration of function 'reconn_set_next_dfs_target' [-Werror=implicit-function-declaration]
     733 |     reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
         |     ^~~~~~~~~~~~~~~~~~~~~~~~~~
>> fs/cifs/connect.c:733:50: error: 'tgt_list' undeclared (first use in this function)
     733 |     reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
         |                                                  ^~~~~~~~
>> fs/cifs/connect.c:733:61: error: 'tgt_it' undeclared (first use in this function)
     733 |     reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
         |                                                             ^~~~~~
   cc1: some warnings being treated as errors
--
   fs/cifs/sess.c: In function 'cifs_ses_add_channel':
>> fs/cifs/sess.c:310:1: warning: the frame size of 2608 bytes is larger than 1024 bytes [-Wframe-larger-than=]
     310 | }
         | ^


vim +726 fs/cifs/connect.c

   577	
   578	/*
   579	 * cifs tcp session reconnection
   580	 *
   581	 * mark tcp session as reconnecting so temporarily locked
   582	 * mark all smb sessions as reconnecting for tcp session
   583	 * reconnect tcp session
   584	 * wake up waiters on reconnection? - (not needed currently)
   585	 */
   586	int
   587	cifs_reconnect(struct TCP_Server_Info *server)
   588	{
   589		int rc = 0;
   590		struct list_head *tmp, *tmp2;
   591		struct cifs_ses *ses;
   592		struct cifs_tcon *tcon;
   593		struct mid_q_entry *mid_entry;
   594		struct list_head retry_list;
   595	#ifdef CONFIG_CIFS_DFS_UPCALL
   596		struct super_block *sb = NULL;
   597		struct cifs_sb_info *cifs_sb = NULL;
   598		struct dfs_cache_tgt_list tgt_list = {0};
   599		struct dfs_cache_tgt_iterator *tgt_it = NULL;
   600	#endif
   601		struct sockaddr_storage *addrs = NULL;
   602		unsigned int numaddrs;
   603	
   604		addrs = kmalloc(sizeof(*addrs) * CIFS_MAX_ADDR_COUNT, GFP_KERNEL);
   605		if (!addrs) {
   606			rc = -ENOMEM;
   607			goto out;
   608		}
   609	
   610		spin_lock(&GlobalMid_Lock);
   611		server->nr_targets = 1;
   612	#ifdef CONFIG_CIFS_DFS_UPCALL
   613		spin_unlock(&GlobalMid_Lock);
   614		sb = cifs_get_tcp_super(server);
   615		if (IS_ERR(sb)) {
   616			rc = PTR_ERR(sb);
   617			cifs_dbg(FYI, "%s: will not do DFS failover: rc = %d\n",
   618				 __func__, rc);
   619			sb = NULL;
   620		} else {
   621			cifs_sb = CIFS_SB(sb);
   622			rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list);
   623			if (rc) {
   624				cifs_sb = NULL;
   625				if (rc != -EOPNOTSUPP) {
   626					cifs_server_dbg(VFS, "%s: no target servers for DFS failover\n",
   627							__func__);
   628				}
   629			} else {
   630				server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list);
   631			}
   632		}
   633		cifs_dbg(FYI, "%s: will retry %d target(s)\n", __func__,
   634			 server->nr_targets);
   635		spin_lock(&GlobalMid_Lock);
   636	#endif
   637		if (server->tcpStatus == CifsExiting) {
   638			/* the demux thread will exit normally
   639			next time through the loop */
   640			spin_unlock(&GlobalMid_Lock);
   641	#ifdef CONFIG_CIFS_DFS_UPCALL
   642			dfs_cache_free_tgts(&tgt_list);
   643			cifs_put_tcp_super(sb);
   644	#endif
   645			goto out;
   646		} else
   647			server->tcpStatus = CifsNeedReconnect;
   648		spin_unlock(&GlobalMid_Lock);
   649		server->maxBuf = 0;
   650		server->max_read = 0;
   651	
   652		cifs_dbg(FYI, "Mark tcp session as need reconnect\n");
   653		trace_smb3_reconnect(server->CurrentMid, server->conn_id, server->hostname);
   654	
   655		/* before reconnecting the tcp session, mark the smb session (uid)
   656			and the tid bad so they are not used until reconnected */
   657		cifs_dbg(FYI, "%s: marking sessions and tcons for reconnect\n",
   658			 __func__);
   659		spin_lock(&cifs_tcp_ses_lock);
   660		list_for_each(tmp, &server->smb_ses_list) {
   661			ses = list_entry(tmp, struct cifs_ses, smb_ses_list);
   662			ses->need_reconnect = true;
   663			list_for_each(tmp2, &ses->tcon_list) {
   664				tcon = list_entry(tmp2, struct cifs_tcon, tcon_list);
   665				tcon->need_reconnect = true;
   666			}
   667			if (ses->tcon_ipc)
   668				ses->tcon_ipc->need_reconnect = true;
   669		}
   670		spin_unlock(&cifs_tcp_ses_lock);
   671	
   672		/* do not want to be sending data on a socket we are freeing */
   673		cifs_dbg(FYI, "%s: tearing down socket\n", __func__);
   674		mutex_lock(&server->srv_mutex);
   675		if (server->ssocket) {
   676			cifs_dbg(FYI, "State: 0x%x Flags: 0x%lx\n",
   677				 server->ssocket->state, server->ssocket->flags);
   678			kernel_sock_shutdown(server->ssocket, SHUT_WR);
   679			cifs_dbg(FYI, "Post shutdown state: 0x%x Flags: 0x%lx\n",
   680				 server->ssocket->state, server->ssocket->flags);
   681			sock_release(server->ssocket);
   682			server->ssocket = NULL;
   683		}
   684		server->sequence_number = 0;
   685		server->session_estab = false;
   686		kfree(server->session_key.response);
   687		server->session_key.response = NULL;
   688		server->session_key.len = 0;
   689		server->lstrp = jiffies;
   690	
   691		/* mark submitted MIDs for retry and issue callback */
   692		INIT_LIST_HEAD(&retry_list);
   693		cifs_dbg(FYI, "%s: moving mids to private list\n", __func__);
   694		spin_lock(&GlobalMid_Lock);
   695		list_for_each_safe(tmp, tmp2, &server->pending_mid_q) {
   696			mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
   697			kref_get(&mid_entry->refcount);
   698			if (mid_entry->mid_state == MID_REQUEST_SUBMITTED)
   699				mid_entry->mid_state = MID_RETRY_NEEDED;
   700			list_move(&mid_entry->qhead, &retry_list);
   701			mid_entry->mid_flags |= MID_DELETED;
   702		}
   703		spin_unlock(&GlobalMid_Lock);
   704		mutex_unlock(&server->srv_mutex);
   705	
   706		cifs_dbg(FYI, "%s: issuing mid callbacks\n", __func__);
   707		list_for_each_safe(tmp, tmp2, &retry_list) {
   708			mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
   709			list_del_init(&mid_entry->qhead);
   710			mid_entry->callback(mid_entry);
   711			cifs_mid_q_entry_release(mid_entry);
   712		}
   713	
   714		if (cifs_rdma_enabled(server)) {
   715			mutex_lock(&server->srv_mutex);
   716			smbd_destroy(server);
   717			mutex_unlock(&server->srv_mutex);
   718		}
   719	
   720		do {
   721			try_to_freeze();
   722	
   723			mutex_lock(&server->srv_mutex);
   724	
   725			if (!cifs_swn_set_server_dstaddr(server)) {
 > 726				if (IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) && cifs_sb &&
   727				    cifs_sb->origin_fullpath) {
   728					/*
   729					 * Set up next DFS target server (if any) for reconnect. If DFS
   730					 * feature is disabled, then we will retry last server we
   731					 * connected to before.
   732					 */
 > 733					reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
   734				}
   735				/*
   736				 * Resolve the hostname again to make sure that IP address is up-to-date.
   737				 */
   738				numaddrs = CIFS_MAX_ADDR_COUNT;
   739				reconn_resolve_hostname(server, addrs, &numaddrs);
   740	
   741				if (cifs_rdma_enabled(server)) {
   742					/* FIXME: handle multiple ips for RDMA */
   743					server->dst_addr_list[0] = server->dstaddr = addrs[0];
   744					server->dst_addr_count = 1;
   745				}
   746			} else {
   747				addrs[0] = server->dstaddr;
   748				numaddrs = 1;
   749			}
   750	
   751			if (cifs_rdma_enabled(server)) {
   752				rc = smbd_reconnect(server);
   753			} else {
   754				struct socket **socks, *sock;
   755	
   756				socks = connect_all_ips(server, addrs, numaddrs);
   757				if (IS_ERR(socks)) {
   758					rc = PTR_ERR(socks);
   759					cifs_server_dbg(VFS, "%s: connect_all_ips() failed: %d\n", __func__, rc);
   760				} else {
   761					mutex_unlock(&server->srv_mutex);
   762					sock = get_first_connected_socket(socks, addrs, numaddrs, true);
   763					release_sockets(socks, numaddrs);
   764					mutex_lock(&server->srv_mutex);
   765	
   766					if (IS_ERR(sock)) {
   767						rc = PTR_ERR(sock);
   768						cifs_server_dbg(FYI, "%s: couldn't find a connected socket: %d\n", __func__, rc);
   769					} else {
   770						rc = kernel_getpeername(sock, (struct sockaddr *)&server->dstaddr);
   771						if (rc < 0) {
   772							cifs_server_dbg(VFS, "%s: getpeername() failed: %d\n", __func__, rc);
   773							sock_release(sock);
   774						} else
   775							rc = 0;
   776					}
   777					if (!rc) {
   778						memcpy(server->dst_addr_list, addrs,
   779						       sizeof(addrs[0]) * numaddrs);
   780						server->dst_addr_count = numaddrs;
   781						server->ssocket = sock;
   782					}
   783				}
   784			}
   785	
   786			if (rc) {
   787				mutex_unlock(&server->srv_mutex);
   788				cifs_server_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
   789				msleep(3000);
   790			} else {
   791				atomic_inc(&tcpSesReconnectCount);
   792				set_credits(server, 1);
   793				spin_lock(&GlobalMid_Lock);
   794				if (server->tcpStatus != CifsExiting)
   795					server->tcpStatus = CifsNeedNegotiate;
   796				spin_unlock(&GlobalMid_Lock);
   797				cifs_swn_reset_server_dstaddr(server);
   798				mutex_unlock(&server->srv_mutex);
   799			}
   800		} while (server->tcpStatus == CifsNeedReconnect);
   801	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
kernel test robot May 11, 2021, 6:47 p.m. UTC | #2
Hi Paulo,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on cifs/for-next]
[also build test WARNING on v5.13-rc1 next-20210511]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Paulo-Alcantara/Support-multiple-ips-per-hostname/20210512-003751
base:   git://git.samba.org/sfrench/cifs-2.6.git for-next
config: arc-allyesconfig (attached as .config)
compiler: arceb-elf-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/210f8e08a6bb153136929af6da6e0a7289ba5931
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Paulo-Alcantara/Support-multiple-ips-per-hostname/20210512-003751
        git checkout 210f8e08a6bb153136929af6da6e0a7289ba5931
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross W=1 ARCH=arc 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

   fs/cifs/connect.c: In function 'cifs_create_socket':
   fs/cifs/connect.c:177:6: warning: variable 'slen' set but not used [-Wunused-but-set-variable]
     177 |  int slen, sfamily;
         |      ^~~~
   fs/cifs/connect.c: In function 'next_dfs_prepath':
>> fs/cifs/connect.c:3540:1: warning: the frame size of 2516 bytes is larger than 1024 bytes [-Wframe-larger-than=]
    3540 | }
         | ^
   fs/cifs/connect.c: In function 'do_dfs_failover':
   fs/cifs/connect.c:3313:1: warning: the frame size of 2584 bytes is larger than 1024 bytes [-Wframe-larger-than=]
    3313 | }
         | ^
--
   fs/cifs/dfs_cache.c: In function 'find_root_ses.isra.0':
>> fs/cifs/dfs_cache.c:1483:1: warning: the frame size of 2548 bytes is larger than 1024 bytes [-Wframe-larger-than=]
    1483 | }
         | ^


vim +3540 fs/cifs/connect.c

7efd081582619e Paulo Alcantara        2020-07-21  3497  
ff2c54a04097de Paulo Alcantara        2021-02-24  3498  /* Set up next dfs prefix path in @dfs_path */
ff2c54a04097de Paulo Alcantara        2021-02-24  3499  static int next_dfs_prepath(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx,
7efd081582619e Paulo Alcantara        2020-07-21  3500  			    const unsigned int xid, struct TCP_Server_Info *server,
7efd081582619e Paulo Alcantara        2020-07-21  3501  			    struct cifs_tcon *tcon, char **dfs_path)
56c762eb9bee33 Paulo Alcantara        2018-11-14  3502  {
ff2c54a04097de Paulo Alcantara        2021-02-24  3503  	char *path, *npath;
ff2c54a04097de Paulo Alcantara        2021-02-24  3504  	int added_treename = is_tcon_dfs(tcon);
ff2c54a04097de Paulo Alcantara        2021-02-24  3505  	int rc;
7efd081582619e Paulo Alcantara        2020-07-21  3506  
3fa1c6d1b8f5c3 Ronnie Sahlberg        2020-12-09  3507  	path = cifs_build_path_to_root(ctx, cifs_sb, tcon, added_treename);
7efd081582619e Paulo Alcantara        2020-07-21  3508  	if (!path)
7efd081582619e Paulo Alcantara        2020-07-21  3509  		return -ENOMEM;
ce558b0e17f8a6 Steve French           2018-05-31  3510  
ff2c54a04097de Paulo Alcantara        2021-02-24  3511  	rc = is_path_remote(cifs_sb, ctx, xid, server, tcon);
ff2c54a04097de Paulo Alcantara        2021-02-24  3512  	if (rc == -EREMOTE) {
3fa1c6d1b8f5c3 Ronnie Sahlberg        2020-12-09  3513  		struct smb3_fs_context v = {NULL};
7efd081582619e Paulo Alcantara        2020-07-21  3514  		/* if @path contains a tree name, skip it in the prefix path */
7efd081582619e Paulo Alcantara        2020-07-21  3515  		if (added_treename) {
66e7b09c731175 Ronnie Sahlberg        2020-11-05  3516  			rc = smb3_parse_devname(path, &v);
7efd081582619e Paulo Alcantara        2020-07-21  3517  			if (rc)
ff2c54a04097de Paulo Alcantara        2021-02-24  3518  				goto out;
7efd081582619e Paulo Alcantara        2020-07-21  3519  			npath = build_unc_path_to_root(&v, cifs_sb, true);
c741cba2cd1d14 Ronnie Sahlberg        2020-12-14  3520  			smb3_cleanup_fs_context_contents(&v);
7efd081582619e Paulo Alcantara        2020-07-21  3521  		} else {
3fa1c6d1b8f5c3 Ronnie Sahlberg        2020-12-09  3522  			v.UNC = ctx->UNC;
7efd081582619e Paulo Alcantara        2020-07-21  3523  			v.prepath = path + 1;
7efd081582619e Paulo Alcantara        2020-07-21  3524  			npath = build_unc_path_to_root(&v, cifs_sb, true);
5072010ccf0592 Paulo Alcantara (SUSE  2019-03-19  3525) 		}
ff2c54a04097de Paulo Alcantara        2021-02-24  3526  
7efd081582619e Paulo Alcantara        2020-07-21  3527  		if (IS_ERR(npath)) {
7efd081582619e Paulo Alcantara        2020-07-21  3528  			rc = PTR_ERR(npath);
ff2c54a04097de Paulo Alcantara        2021-02-24  3529  			goto out;
a6b5058fafdf50 Aurelien Aptel         2016-05-25  3530  		}
ff2c54a04097de Paulo Alcantara        2021-02-24  3531  
7efd081582619e Paulo Alcantara        2020-07-21  3532  		kfree(*dfs_path);
7efd081582619e Paulo Alcantara        2020-07-21  3533  		*dfs_path = npath;
ff2c54a04097de Paulo Alcantara        2021-02-24  3534  		rc = -EREMOTE;
1bfe73c258addc Igor Mammedov          2009-04-01  3535  	}
1bfe73c258addc Igor Mammedov          2009-04-01  3536  
ff2c54a04097de Paulo Alcantara        2021-02-24  3537  out:
7efd081582619e Paulo Alcantara        2020-07-21  3538  	kfree(path);
7efd081582619e Paulo Alcantara        2020-07-21  3539  	return rc;
4a367dc0443566 Paulo Alcantara        2018-11-14 @3540  }
4a367dc0443566 Paulo Alcantara        2018-11-14  3541  

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c
index c87c37cf2914..4ca718721bf9 100644
--- a/fs/cifs/cifs_dfs_ref.c
+++ b/fs/cifs/cifs_dfs_ref.c
@@ -143,8 +143,10 @@  char *cifs_compose_mount_options(const char *sb_mountdata,
 	const char *prepath = NULL;
 	int md_len;
 	char *tkn_e;
-	char *srvIP = NULL;
 	char sep = ',';
+	struct sockaddr_storage *addrs = NULL;
+	char ip[INET6_ADDRSTRLEN + 1];
+	int numaddrs, i;
 	int off, noff;
 
 	if (sb_mountdata == NULL)
@@ -173,21 +175,28 @@  char *cifs_compose_mount_options(const char *sb_mountdata,
 		}
 	}
 
-	rc = dns_resolve_server_name_to_ip(name, &srvIP);
-	if (rc < 0) {
+	addrs = kmalloc(CIFS_MAX_ADDR_COUNT * sizeof(*addrs), GFP_KERNEL);
+	if (!addrs) {
+		rc = -ENOMEM;
+		goto compose_mount_options_err;
+	}
+
+	rc = numaddrs = dns_resolve_server_name_to_addrs(name, addrs, CIFS_MAX_ADDR_COUNT);
+	if (rc <= 0) {
 		cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n",
 			 __func__, name, rc);
+		rc = rc == 0 ? -EINVAL : rc;
 		goto compose_mount_options_err;
 	}
 
 	/*
-	 * In most cases, we'll be building a shorter string than the original,
-	 * but we do have to assume that the address in the ip= option may be
-	 * much longer than the original. Add the max length of an address
-	 * string to the length of the original string to allow for worst case.
+	 * In most cases, we'll be building a shorter string than the original, but we do have to
+	 * assume that the address in the ip= option may be much longer than the original.
+	 * Add the max length of CIFS_MAX_ADDR_COUNT * an address string to the length of the
+	 * original string to allow for worst case.
 	 */
-	md_len = strlen(sb_mountdata) + INET6_ADDRSTRLEN;
-	mountdata = kzalloc(md_len + sizeof("ip=") + 1, GFP_KERNEL);
+	md_len = strlen(sb_mountdata) + CIFS_MAX_ADDR_COUNT * (INET6_ADDRSTRLEN + sizeof(",ip="));
+	mountdata = kzalloc(md_len + 1, GFP_KERNEL);
 	if (mountdata == NULL) {
 		rc = -ENOMEM;
 		goto compose_mount_options_err;
@@ -227,10 +236,23 @@  char *cifs_compose_mount_options(const char *sb_mountdata,
 	mountdata[md_len] = '\0';
 
 	/* copy new IP and ref share name */
-	if (mountdata[strlen(mountdata) - 1] != sep)
+	if (mountdata[strlen(mountdata) - 1] == sep)
+		mountdata[strlen(mountdata) - 1] = '\0';
+
+	for (i = 0; i < numaddrs; i++) {
+		struct sockaddr_storage *addr = &addrs[i];
+
+		if (addr->ss_family == AF_INET6) {
+			scnprintf(ip, sizeof(ip), "%pI6",
+				  &((struct sockaddr_in6 *)addr)->sin6_addr);
+		} else
+			scnprintf(ip, sizeof(ip), "%pI4", &((struct sockaddr_in *)addr)->sin_addr);
 		strncat(mountdata, &sep, 1);
-	strcat(mountdata, "ip=");
-	strcat(mountdata, srvIP);
+		strcat(mountdata, "ip=");
+		strcat(mountdata, ip);
+	}
+
+	kfree(addrs);
 
 	if (devname)
 		*devname = name;
@@ -241,13 +263,13 @@  char *cifs_compose_mount_options(const char *sb_mountdata,
 	/*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/
 
 compose_mount_options_out:
-	kfree(srvIP);
 	return mountdata;
 
 compose_mount_options_err:
 	kfree(mountdata);
 	mountdata = ERR_PTR(rc);
 	kfree(name);
+	kfree(addrs);
 	goto compose_mount_options_out;
 }
 
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 6c65e39f0509..ccc5917c2ff3 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -87,6 +87,9 @@ 
 /* maximum number of PDUs in one compound */
 #define MAX_COMPOUND 5
 
+/* maximum number of addresses we can handle from a resolved hostname */
+#define CIFS_MAX_ADDR_COUNT 16
+
 /*
  * Default number of credits to keep available for SMB3.
  * This value is chosen somewhat arbitrarily. The Windows client
@@ -589,8 +592,11 @@  struct TCP_Server_Info {
 	enum statusEnum tcpStatus; /* what we think the status is */
 	char *hostname; /* hostname portion of UNC string */
 	struct socket *ssocket;
-	struct sockaddr_storage dstaddr;
+	struct sockaddr_storage dst_addr_list[CIFS_MAX_ADDR_COUNT];
+	unsigned int dst_addr_count;
+	struct sockaddr_storage dstaddr; /* current destination address */
 	struct sockaddr_storage srcaddr; /* locally bind to this IP */
+	unsigned short port_num;
 #ifdef CONFIG_NET_NS
 	struct net *net;
 #endif
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 495c395f9def..43315a0365d6 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -74,51 +74,462 @@  extern bool disable_legacy_dialects;
 /* Drop the connection to not overload the server */
 #define NUM_STATUS_IO_TIMEOUT   5
 
-static int ip_connect(struct TCP_Server_Info *server);
-static int generic_ip_connect(struct TCP_Server_Info *server);
 static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink);
 static void cifs_prune_tlinks(struct work_struct *work);
 
-/*
- * Resolve hostname and set ip addr in tcp ses. Useful for hostnames that may
- * get their ip addresses changed at some point.
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+static struct lock_class_key cifs_key[2];
+static struct lock_class_key cifs_slock_key[2];
+
+static inline void
+cifs_reclassify_socket4(struct socket *sock)
+{
+	struct sock *sk = sock->sk;
+
+	BUG_ON(!sock_allow_reclassification(sk));
+	sock_lock_init_class_and_name(sk, "slock-AF_INET-CIFS",
+		&cifs_slock_key[0], "sk_lock-AF_INET-CIFS", &cifs_key[0]);
+}
+
+static inline void
+cifs_reclassify_socket6(struct socket *sock)
+{
+	struct sock *sk = sock->sk;
+
+	BUG_ON(!sock_allow_reclassification(sk));
+	sock_lock_init_class_and_name(sk, "slock-AF_INET6-CIFS",
+		&cifs_slock_key[1], "sk_lock-AF_INET6-CIFS", &cifs_key[1]);
+}
+#else
+static inline void
+cifs_reclassify_socket4(struct socket *sock)
+{
+}
+
+static inline void
+cifs_reclassify_socket6(struct socket *sock)
+{
+}
+#endif
+
+/* See RFC1001 section 14 on representation of Netbios names */
+static void rfc1002mangle(char *target, char *source, unsigned int length)
+{
+	unsigned int i, j;
+
+	for (i = 0, j = 0; i < (length); i++) {
+		/* mask a nibble at a time and encode */
+		target[j] = 'A' + (0x0F & (source[i] >> 4));
+		target[j+1] = 'A' + (0x0F & source[i]);
+		j += 2;
+	}
+
+}
+
+static void set_ipaddr_port(struct sockaddr_storage *addrs, unsigned int numaddrs,
+			    unsigned short port)
+{
+	unsigned int i;
+
+	for (i = 0; i < numaddrs; i++)
+		cifs_set_port((struct sockaddr *)&addrs[i], port);
+}
+
+static void release_sockets(struct socket **socks, unsigned int numsocks)
+{
+	unsigned int i;
+
+	for (i = 0; i < numsocks; i++) {
+		if (socks[i])
+			sock_release(socks[i]);
+	}
+	kfree(socks);
+}
+
+static int cifs_bind_socket(struct TCP_Server_Info *server, struct socket *socket)
+{
+	int rc = 0;
+
+	if (server->srcaddr.ss_family != AF_UNSPEC) {
+		/* Bind to the specified local IP address */
+		rc = socket->ops->bind(socket, (struct sockaddr *)&server->srcaddr,
+				       sizeof(server->srcaddr));
+		if (rc < 0) {
+			struct sockaddr_in *saddr4;
+			struct sockaddr_in6 *saddr6;
+
+			saddr4 = (struct sockaddr_in *)&server->srcaddr;
+			saddr6 = (struct sockaddr_in6 *)&server->srcaddr;
+			if (saddr6->sin6_family == AF_INET6)
+				cifs_server_dbg(VFS, "Failed to bind to: %pI6c, error: %d\n", &saddr6->sin6_addr, rc);
+			else
+				cifs_server_dbg(VFS, "Failed to bind to: %pI4, error: %d\n", &saddr4->sin_addr.s_addr, rc);
+		}
+	}
+	return rc;
+}
+
+static struct socket *cifs_create_socket(struct TCP_Server_Info *server, int stype,
+					 struct sockaddr_storage *dstaddr)
+{
+	int rc = 0;
+	__be16 *sport;
+	int slen, sfamily;
+	struct socket *socket = NULL;
+
+	if (dstaddr->ss_family == AF_INET6) {
+		struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)dstaddr;
+
+		sport = &ipv6->sin6_port;
+		slen = sizeof(struct sockaddr_in6);
+		sfamily = AF_INET6;
+		cifs_dbg(FYI, "%s: connecting to [%pI6]:%d\n", __func__, &ipv6->sin6_addr,
+				ntohs(*sport));
+	} else {
+		struct sockaddr_in *ipv4 = (struct sockaddr_in *)dstaddr;
+
+		sport = &ipv4->sin_port;
+		slen = sizeof(struct sockaddr_in);
+		sfamily = AF_INET;
+		cifs_dbg(FYI, "%s: connecting to %pI4:%d\n", __func__, &ipv4->sin_addr,
+				ntohs(*sport));
+	}
+
+	rc = __sock_create(cifs_net_ns(server), sfamily, stype, IPPROTO_TCP, &socket, 1);
+	if (rc < 0) {
+		cifs_dbg(VFS, "%s: Error %d creating socket\n", __func__, rc);
+		return ERR_PTR(rc);
+	}
+
+	/* BB other socket options to set KEEPALIVE, NODELAY? */
+	cifs_dbg(FYI, "Socket created\n");
+	socket->sk->sk_allocation = GFP_NOFS;
+	if (sfamily == AF_INET6)
+		cifs_reclassify_socket6(socket);
+	else
+		cifs_reclassify_socket4(socket);
+
+	rc = cifs_bind_socket(server, socket);
+	if (rc < 0)
+		return ERR_PTR(rc);
+
+	/*
+	 * Eventually check for other socket options to change from
+	 * the default. sock_setsockopt not used because it expects
+	 * user space buffer
+	 */
+	socket->sk->sk_rcvtimeo = 7 * HZ;
+	socket->sk->sk_sndtimeo = 5 * HZ;
+
+	/* make the bufsizes depend on wsize/rsize and max requests */
+	if (server->noautotune) {
+		if (socket->sk->sk_sndbuf < (200 * 1024))
+			socket->sk->sk_sndbuf = 200 * 1024;
+		if (socket->sk->sk_rcvbuf < (140 * 1024))
+			socket->sk->sk_rcvbuf = 140 * 1024;
+	}
+
+	if (server->tcp_nodelay)
+		tcp_sock_set_nodelay(socket->sk);
+
+	cifs_dbg(FYI, "sndbuf %d rcvbuf %d rcvtimeo 0x%lx\n",
+		 socket->sk->sk_sndbuf,
+		 socket->sk->sk_rcvbuf, socket->sk->sk_rcvtimeo);
+
+	return socket;
+}
+
+static struct socket **connect_all_ips(struct TCP_Server_Info *server,
+				       struct sockaddr_storage *addrs, unsigned int numaddrs)
+{
+	unsigned int i;
+	struct socket **socks;
+	int rc = 0;
+
+	socks = kcalloc(numaddrs, sizeof(struct socket *), GFP_KERNEL);
+	if (!socks)
+		return ERR_PTR(-ENOMEM);
+
+	for (i = 0; i < numaddrs; i++) {
+		struct sockaddr_storage *addr = &addrs[i];
+		int slen = addr->ss_family == AF_INET6 ? sizeof(struct sockaddr_in6) :
+			sizeof(struct sockaddr_in);
+
+		socks[i] = cifs_create_socket(server, SOCK_STREAM, addr);
+		if (IS_ERR(socks[i])) {
+			rc = PTR_ERR(socks[i]);
+			socks[i] = NULL;
+			break;
+		}
+
+		rc = socks[i]->ops->connect(socks[i], (struct sockaddr *)addr, slen, O_NONBLOCK);
+#ifdef CONFIG_CIFS_DEBUG2
+		if (rc != 0 && rc != -EINPROGRESS)
+			cifs_dbg(FYI, "%s: socket connect() error: %d\n", __func__, rc);
+#endif
+		rc = 0;
+	}
+
+	if (rc) {
+		release_sockets(socks, numaddrs);
+		socks = ERR_PTR(rc);
+	}
+
+	return socks;
+}
+
+static struct socket *__get_first_connected_socket(struct socket **socks,
+						   struct sockaddr_storage *addrs,
+						   unsigned int numaddrs)
+{
+	unsigned int i, k;
+	struct socket *sock = ERR_PTR(-EHOSTUNREACH);
+	int rc;
+
+	for (i = 0; i < numaddrs; i++) {
+		struct sockaddr_storage naddr;
+
+		if (!socks[i])
+			continue;
+
+		rc = kernel_getpeername(socks[i], (struct sockaddr *)&naddr);
+		if (rc >= 0) {
+#ifdef CONFIG_CIFS_DEBUG2
+			struct sockaddr_in *ip4;
+			struct sockaddr_in6 *ip6;
+			__be16 *sport;
+
+			if (naddr.ss_family == AF_INET6) {
+				ip6 = (struct sockaddr_in6 *)&naddr;
+				sport = &ip6->sin6_port;
+				cifs_dbg(FYI, "%s: found a connected socket ([%pI6]:%d)\n", __func__,
+					 &ip6->sin6_addr, ntohs(*sport));
+			} else {
+				ip4 = (struct sockaddr_in *)&naddr;
+				sport = &ip4->sin_port;
+				cifs_dbg(FYI, "%s: found a connected socket (%pI4:%d)\n", __func__,
+					 &ip4->sin_addr, ntohs(*sport));
+			}
+
+#endif
+			for (k = 0; k < numaddrs; k++) {
+				struct sockaddr_storage *addr = &addrs[k];
+				if (cifs_match_ipaddr((struct sockaddr *)&naddr,
+						      (struct sockaddr *)addr)) {
+					swap(addrs[0], *addr);
+					break;
+				}
+			}
+			sock = socks[i];
+			socks[i] = NULL;
+			break;
+		}
+	}
+	return sock;
+}
+
+static struct socket *get_first_connected_socket(struct socket **socks,
+						 struct sockaddr_storage *addrs,
+						 unsigned int numaddrs, bool canwait)
+{
+	struct socket *sock;
+	int retries = 0;
+
+	if (!canwait)
+		return __get_first_connected_socket(socks, addrs, numaddrs);
+
+	do {
+		msleep(1 << retries);
+		sock = __get_first_connected_socket(socks, addrs, numaddrs);
+	} while (IS_ERR(sock) && ++retries < 13);
+	return sock;
+}
+
+static int
+ip_rfc1001_connect(struct TCP_Server_Info *server)
+{
+	int rc = 0;
+	/*
+	 * some servers require RFC1001 sessinit before sending
+	 * negprot - BB check reconnection in case where second
+	 * sessinit is sent but no second negprot
+	 */
+	struct rfc1002_session_packet *ses_init_buf;
+	struct smb_hdr *smb_buf;
+
+	ses_init_buf = kzalloc(sizeof(struct rfc1002_session_packet),
+			       GFP_KERNEL);
+	if (ses_init_buf) {
+		ses_init_buf->trailer.session_req.called_len = 32;
+
+		if (server->server_RFC1001_name[0] != 0)
+			rfc1002mangle(ses_init_buf->trailer.
+				      session_req.called_name,
+				      server->server_RFC1001_name,
+				      RFC1001_NAME_LEN_WITH_NULL);
+		else
+			rfc1002mangle(ses_init_buf->trailer.
+				      session_req.called_name,
+				      DEFAULT_CIFS_CALLED_NAME,
+				      RFC1001_NAME_LEN_WITH_NULL);
+
+		ses_init_buf->trailer.session_req.calling_len = 32;
+
+		/*
+		 * calling name ends in null (byte 16) from old smb
+		 * convention.
+		 */
+		if (server->workstation_RFC1001_name[0] != 0)
+			rfc1002mangle(ses_init_buf->trailer.
+				      session_req.calling_name,
+				      server->workstation_RFC1001_name,
+				      RFC1001_NAME_LEN_WITH_NULL);
+		else
+			rfc1002mangle(ses_init_buf->trailer.
+				      session_req.calling_name,
+				      "LINUX_CIFS_CLNT",
+				      RFC1001_NAME_LEN_WITH_NULL);
+
+		ses_init_buf->trailer.session_req.scope1 = 0;
+		ses_init_buf->trailer.session_req.scope2 = 0;
+		smb_buf = (struct smb_hdr *)ses_init_buf;
+
+		/* sizeof RFC1002_SESSION_REQUEST with no scope */
+		smb_buf->smb_buf_length = cpu_to_be32(0x81000044);
+		rc = smb_send(server, smb_buf, 0x44);
+		kfree(ses_init_buf);
+		/*
+		 * RFC1001 layer in at least one server
+		 * requires very short break before negprot
+		 * presumably because not expecting negprot
+		 * to follow so fast.  This is a simple
+		 * solution that works without
+		 * complicating the code and causes no
+		 * significant slowing down on mount
+		 * for everyone else
+		 */
+		usleep_range(1000, 2000);
+	}
+	/*
+	 * else the negprot may still work without this
+	 * even though malloc failed
+	 */
+
+	return rc;
+}
+
+static int cifs_ip_connect(struct TCP_Server_Info *server)
+{
+	struct socket **socks, *sock;
+	unsigned short port = server->port_num;
+	int rc;
+	bool rootfs = server->noblockcnt;
+
+	if (port == 0) {
+		/* try with 445 port at first */
+		port = CIFS_PORT;
+
+		set_ipaddr_port(server->dst_addr_list, server->dst_addr_count, port);
+		socks = connect_all_ips(server, server->dst_addr_list, server->dst_addr_count);
+		if (IS_ERR(socks)) {
+			rc = PTR_ERR(socks);
+			cifs_server_dbg(VFS, "%s: connect_all_ips() failed: %d\n", __func__, rc);
+			return rc;
+		}
+
+		/* if it failed, try with 139 port */
+		sock = get_first_connected_socket(socks, server->dst_addr_list,
+						  server->dst_addr_count, !rootfs);
+		release_sockets(socks, server->dst_addr_count);
+
+		if (IS_ERR(sock)) {
+			port = RFC1001_PORT;
+
+			set_ipaddr_port(server->dst_addr_list, server->dst_addr_count, port);
+			socks = connect_all_ips(server, server->dst_addr_list,
+						server->dst_addr_count);
+			if (IS_ERR(socks)) {
+				rc = PTR_ERR(socks);
+				cifs_server_dbg(VFS, "%s: connect_all_ips() failed: %d\n", __func__, rc);
+				return rc;
+			}
+			sock = get_first_connected_socket(socks, server->dst_addr_list,
+							  server->dst_addr_count, !rootfs);
+			release_sockets(socks, server->dst_addr_count);
+		}
+	} else {
+		set_ipaddr_port(server->dst_addr_list, server->dst_addr_count, port);
+		socks = connect_all_ips(server, server->dst_addr_list, server->dst_addr_count);
+		if (IS_ERR(socks)) {
+			cifs_server_dbg(VFS, "%s: connect_all_ips() failed: %d\n", __func__, rc);
+			return PTR_ERR(socks);
+		}
+		sock = get_first_connected_socket(socks, server->dst_addr_list,
+						  server->dst_addr_count, !rootfs);
+		release_sockets(socks, server->dst_addr_count);
+	}
+
+	if (IS_ERR(sock))
+		return PTR_ERR(sock);
+
+	rc = kernel_getpeername(sock, (struct sockaddr *)&server->dstaddr);
+	if (rc < 0) {
+		cifs_server_dbg(VFS, "%s: getpeername() failed with rc=%d\n",
+				__func__, rc);
+		sock_release(sock);
+		return rc;
+	}
+	server->port_num = port;
+	server->ssocket = sock;
+
+	return server->port_num == RFC1001_PORT ? ip_rfc1001_connect(server) : 0;
+}
+
+/**
+ * Set resolved address list in @addrs and number of addresses in @numaddrs.
+ *
+ * Useful for hostnames that may get their ip addresses changed at some point.
  *
- * This should be called with server->srv_mutex held.
  */
-static int reconn_set_ipaddr_from_hostname(struct TCP_Server_Info *server)
+static void reconn_resolve_hostname(struct TCP_Server_Info *server, struct sockaddr_storage *addrs,
+				    unsigned int *numaddrs)
 {
 	int rc;
 	int len;
-	char *unc, *ipaddr = NULL;
+	char *unc = NULL;
 
-	if (!server->hostname)
-		return -EINVAL;
-
-	len = strlen(server->hostname) + 3;
-
-	unc = kmalloc(len, GFP_KERNEL);
-	if (!unc) {
-		cifs_dbg(FYI, "%s: failed to create UNC path\n", __func__);
-		return -ENOMEM;
+	if (server->hostname) {
+		len = strlen(server->hostname) + 3;
+		unc = kmalloc(len, GFP_KERNEL);
+		if (!unc) {
+			cifs_server_dbg(VFS, "%s: failed to allocate UNC path\n", __func__);
+			cifs_server_dbg(FYI, "%s: reusing current list of ip addresses\n", __func__);
+		}
 	}
-	scnprintf(unc, len, "\\\\%s", server->hostname);
 
-	rc = dns_resolve_server_name_to_ip(unc, &ipaddr);
-	kfree(unc);
+	if (unc) {
+		scnprintf(unc, len, "\\\\%s", server->hostname);
 
-	if (rc < 0) {
-		cifs_dbg(FYI, "%s: failed to resolve server part of %s to IP: %d\n",
-			 __func__, server->hostname, rc);
-		return rc;
-	}
+		rc = dns_resolve_server_name_to_addrs(unc, addrs, *numaddrs);
+		kfree(unc);
 
-	spin_lock(&cifs_tcp_ses_lock);
-	rc = cifs_convert_address((struct sockaddr *)&server->dstaddr, ipaddr,
-				  strlen(ipaddr));
-	spin_unlock(&cifs_tcp_ses_lock);
-	kfree(ipaddr);
-
-	return !rc ? -1 : 0;
+		if (rc <= 0) {
+			cifs_server_dbg(FYI, "%s: couldn't resolve server hostname to IP(s): %d\n", __func__, rc);
+			cifs_server_dbg(FYI, "%s: reusing current list of ip addresses\n", __func__);
+			spin_lock(&cifs_tcp_ses_lock);
+			memcpy(addrs, server->dst_addr_list,
+			       sizeof(*addrs) * server->dst_addr_count);
+			*numaddrs = server->dst_addr_count;
+			spin_unlock(&cifs_tcp_ses_lock);
+		} else
+			*numaddrs = rc;
+	} else {
+		spin_lock(&cifs_tcp_ses_lock);
+		memcpy(addrs, server->dst_addr_list, sizeof(*addrs) * server->dst_addr_count);
+		*numaddrs = server->dst_addr_count;
+		spin_unlock(&cifs_tcp_ses_lock);
+	}
+	set_ipaddr_port(addrs, *numaddrs, server->port_num);
 }
 
 #ifdef CONFIG_CIFS_DFS_UPCALL
@@ -129,7 +540,6 @@  static void reconn_set_next_dfs_target(struct TCP_Server_Info *server,
 				       struct dfs_cache_tgt_iterator **tgt_it)
 {
 	const char *name;
-	int rc;
 
 	if (!cifs_sb || !cifs_sb->origin_fullpath)
 		return;
@@ -150,16 +560,9 @@  static void reconn_set_next_dfs_target(struct TCP_Server_Info *server,
 
 	server->hostname = extract_hostname(name);
 	if (IS_ERR(server->hostname)) {
-		cifs_dbg(FYI,
-			 "%s: failed to extract hostname from target: %ld\n",
-			 __func__, PTR_ERR(server->hostname));
-		return;
-	}
-
-	rc = reconn_set_ipaddr_from_hostname(server);
-	if (rc) {
-		cifs_dbg(FYI, "%s: failed to resolve hostname: %d\n",
-			 __func__, rc);
+		cifs_dbg(VFS, "%s: failed to extract hostname from target: %ld\n", __func__,
+			 PTR_ERR(server->hostname));
+		server->hostname = NULL;
 	}
 }
 
@@ -195,6 +598,14 @@  cifs_reconnect(struct TCP_Server_Info *server)
 	struct dfs_cache_tgt_list tgt_list = {0};
 	struct dfs_cache_tgt_iterator *tgt_it = NULL;
 #endif
+	struct sockaddr_storage *addrs = NULL;
+	unsigned int numaddrs;
+
+	addrs = kmalloc(sizeof(*addrs) * CIFS_MAX_ADDR_COUNT, GFP_KERNEL);
+	if (!addrs) {
+		rc = -ENOMEM;
+		goto out;
+	}
 
 	spin_lock(&GlobalMid_Lock);
 	server->nr_targets = 1;
@@ -231,8 +642,7 @@  cifs_reconnect(struct TCP_Server_Info *server)
 		dfs_cache_free_tgts(&tgt_list);
 		cifs_put_tcp_super(sb);
 #endif
-		wake_up(&server->response_q);
-		return rc;
+		goto out;
 	} else
 		server->tcpStatus = CifsNeedReconnect;
 	spin_unlock(&GlobalMid_Lock);
@@ -312,41 +722,70 @@  cifs_reconnect(struct TCP_Server_Info *server)
 
 		mutex_lock(&server->srv_mutex);
 
-
 		if (!cifs_swn_set_server_dstaddr(server)) {
-#ifdef CONFIG_CIFS_DFS_UPCALL
-		if (cifs_sb && cifs_sb->origin_fullpath)
-			/*
-			 * 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.
-			 */
-			reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
-		else {
-#endif
+			if (IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) && cifs_sb &&
+			    cifs_sb->origin_fullpath) {
+				/*
+				 * 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.
+				 */
+				reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
+			}
 			/*
 			 * Resolve the hostname again to make sure that IP address is up-to-date.
 			 */
-			rc = reconn_set_ipaddr_from_hostname(server);
-			if (rc) {
-				cifs_dbg(FYI, "%s: failed to resolve hostname: %d\n",
-						__func__, rc);
+			numaddrs = CIFS_MAX_ADDR_COUNT;
+			reconn_resolve_hostname(server, addrs, &numaddrs);
+
+			if (cifs_rdma_enabled(server)) {
+				/* FIXME: handle multiple ips for RDMA */
+				server->dst_addr_list[0] = server->dstaddr = addrs[0];
+				server->dst_addr_count = 1;
 			}
-
-#ifdef CONFIG_CIFS_DFS_UPCALL
-		}
-#endif
-
-
+		} else {
+			addrs[0] = server->dstaddr;
+			numaddrs = 1;
 		}
 
-		if (cifs_rdma_enabled(server))
+		if (cifs_rdma_enabled(server)) {
 			rc = smbd_reconnect(server);
-		else
-			rc = generic_ip_connect(server);
+		} else {
+			struct socket **socks, *sock;
+
+			socks = connect_all_ips(server, addrs, numaddrs);
+			if (IS_ERR(socks)) {
+				rc = PTR_ERR(socks);
+				cifs_server_dbg(VFS, "%s: connect_all_ips() failed: %d\n", __func__, rc);
+			} else {
+				mutex_unlock(&server->srv_mutex);
+				sock = get_first_connected_socket(socks, addrs, numaddrs, true);
+				release_sockets(socks, numaddrs);
+				mutex_lock(&server->srv_mutex);
+
+				if (IS_ERR(sock)) {
+					rc = PTR_ERR(sock);
+					cifs_server_dbg(FYI, "%s: couldn't find a connected socket: %d\n", __func__, rc);
+				} else {
+					rc = kernel_getpeername(sock, (struct sockaddr *)&server->dstaddr);
+					if (rc < 0) {
+						cifs_server_dbg(VFS, "%s: getpeername() failed: %d\n", __func__, rc);
+						sock_release(sock);
+					} else
+						rc = 0;
+				}
+				if (!rc) {
+					memcpy(server->dst_addr_list, addrs,
+					       sizeof(addrs[0]) * numaddrs);
+					server->dst_addr_count = numaddrs;
+					server->ssocket = sock;
+				}
+			}
+		}
+
 		if (rc) {
-			cifs_dbg(FYI, "reconnect error %d\n", rc);
 			mutex_unlock(&server->srv_mutex);
+			cifs_server_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
 			msleep(3000);
 		} else {
 			atomic_inc(&tcpSesReconnectCount);
@@ -382,7 +821,9 @@  cifs_reconnect(struct TCP_Server_Info *server)
 	if (server->tcpStatus == CifsNeedNegotiate)
 		mod_delayed_work(cifsiod_wq, &server->echo, 0);
 
+out:
 	wake_up(&server->response_q);
+	kfree(addrs);
 	return rc;
 }
 
@@ -1093,80 +1534,77 @@  cifs_match_ipaddr(struct sockaddr *srcaddr, struct sockaddr *rhs)
 	}
 }
 
-/*
- * If no port is specified in addr structure, we try to match with 445 port
- * and if it fails - with 139 ports. It should be called only if address
- * families of server and addr are equal.
- */
-static bool
-match_port(struct TCP_Server_Info *server, struct sockaddr *addr)
+static bool __match_address(struct TCP_Server_Info *server, struct sockaddr_storage *caddr)
 {
-	__be16 port, *sport;
+	unsigned int i;
+	bool match = false;
 
-	/* SMBDirect manages its own ports, don't match it here */
-	if (server->rdma)
-		return true;
+	for (i = 0; i < server->dst_addr_count && !match; i++) {
+		struct sockaddr *saddr = (struct sockaddr *)&server->dst_addr_list[i];
+		__be16 port, *sport;
 
-	switch (addr->sa_family) {
-	case AF_INET:
-		sport = &((struct sockaddr_in *) &server->dstaddr)->sin_port;
-		port = ((struct sockaddr_in *) addr)->sin_port;
-		break;
-	case AF_INET6:
-		sport = &((struct sockaddr_in6 *) &server->dstaddr)->sin6_port;
-		port = ((struct sockaddr_in6 *) addr)->sin6_port;
-		break;
-	default:
-		WARN_ON(1);
-		return false;
-	}
+		switch (saddr->sa_family) {
+		case AF_INET: {
+			struct sockaddr_in *addr4 = (struct sockaddr_in *)caddr;
+			struct sockaddr_in *srv_addr4 = (struct sockaddr_in *)saddr;
 
-	if (!port) {
-		port = htons(CIFS_PORT);
-		if (port == *sport)
+			if (addr4->sin_addr.s_addr != srv_addr4->sin_addr.s_addr)
+				continue;
+
+			sport = &srv_addr4->sin_port;
+			port = addr4->sin_port;
+			break;
+		}
+		case AF_INET6: {
+			struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)caddr;
+			struct sockaddr_in6 *srv_addr6 = (struct sockaddr_in6 *)saddr;
+
+			if (!ipv6_addr_equal(&addr6->sin6_addr, &srv_addr6->sin6_addr) ||
+			    addr6->sin6_scope_id != srv_addr6->sin6_scope_id)
+				continue;
+
+			sport = &srv_addr6->sin6_port;
+			port = addr6->sin6_port;
+			break;
+		}
+		default:
+			/* don't expect to be here */
+			WARN_ON(1);
+			return false;
+		}
+		/* SMBDirect manages its own ports, don't match it here */
+		if (cifs_rdma_enabled(server))
 			return true;
-
-		port = htons(RFC1001_PORT);
+		/*
+		 * If no port is specified in addr structure, we try to match with 445 port and if
+		 * it fails - with 139 ports. It should be called only if address families of server
+		 * and addr are equal.
+		 */
+		if (!port) {
+			port = htons(CIFS_PORT);
+			match |= port == *sport;
+			port = htons(RFC1001_PORT);
+		}
+		match |= port == *sport;
 	}
-
-	return port == *sport;
+	return match;
 }
 
-static bool
-match_address(struct TCP_Server_Info *server, struct sockaddr *addr,
-	      struct sockaddr *srcaddr)
+static bool match_address(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
 {
-	switch (addr->sa_family) {
-	case AF_INET: {
-		struct sockaddr_in *addr4 = (struct sockaddr_in *)addr;
-		struct sockaddr_in *srv_addr4 =
-					(struct sockaddr_in *)&server->dstaddr;
+	unsigned int i;
 
-		if (addr4->sin_addr.s_addr != srv_addr4->sin_addr.s_addr)
-			return false;
-		break;
-	}
-	case AF_INET6: {
-		struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
-		struct sockaddr_in6 *srv_addr6 =
-					(struct sockaddr_in6 *)&server->dstaddr;
-
-		if (!ipv6_addr_equal(&addr6->sin6_addr,
-				     &srv_addr6->sin6_addr))
-			return false;
-		if (addr6->sin6_scope_id != srv_addr6->sin6_scope_id)
-			return false;
-		break;
-	}
-	default:
-		WARN_ON(1);
-		return false; /* don't expect to be here */
-	}
-
-	if (!cifs_match_ipaddr(srcaddr, (struct sockaddr *)&server->srcaddr))
+	if (ctx->dst_addr_count != server->dst_addr_count ||
+	    (!cifs_rdma_enabled(server) && ctx->port && ctx->port != server->port_num))
 		return false;
 
-	return true;
+	/* check if source and destination addresses match */
+	for (i = 0; i < ctx->dst_addr_count; i++) {
+		if (!__match_address(server, &ctx->dst_addr_list[i]))
+			return false;
+	}
+	return cifs_match_ipaddr((struct sockaddr *)&ctx->srcaddr,
+				 (struct sockaddr *)&server->srcaddr);
 }
 
 static bool
@@ -1194,8 +1632,6 @@  match_security(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
 
 static int match_server(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
 {
-	struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;
-
 	if (ctx->nosharesock)
 		return 0;
 
@@ -1213,11 +1649,7 @@  static int match_server(struct TCP_Server_Info *server, struct smb3_fs_context *
 	if (!net_eq(cifs_net_ns(server), current->nsproxy->net_ns))
 		return 0;
 
-	if (!match_address(server, addr,
-			   (struct sockaddr *)&ctx->srcaddr))
-		return 0;
-
-	if (!match_port(server, addr))
+	if (!match_address(server, ctx))
 		return 0;
 
 	if (!match_security(server, ctx))
@@ -1364,8 +1796,17 @@  cifs_get_tcp_session(struct smb3_fs_context *ctx)
 	mutex_init(&tcp_ses->reconnect_mutex);
 	memcpy(&tcp_ses->srcaddr, &ctx->srcaddr,
 	       sizeof(tcp_ses->srcaddr));
-	memcpy(&tcp_ses->dstaddr, &ctx->dstaddr,
-		sizeof(tcp_ses->dstaddr));
+
+	if (WARN_ON_ONCE(!ctx->dst_addr_count || ctx->dst_addr_count > CIFS_MAX_ADDR_COUNT)) {
+		rc = -EINVAL;
+		goto out_err_crypto_release;
+	}
+	memcpy(tcp_ses->dst_addr_list, ctx->dst_addr_list,
+	       ctx->dst_addr_count * sizeof(ctx->dst_addr_list[0]));
+	tcp_ses->dst_addr_count = ctx->dst_addr_count;
+	memcpy(&tcp_ses->dstaddr, &ctx->dstaddr, sizeof(tcp_ses->dstaddr));
+	tcp_ses->port_num = ctx->port;
+
 	if (ctx->use_client_guid)
 		memcpy(tcp_ses->client_guid, ctx->client_guid,
 		       SMB2_CLIENT_GUID_SIZE);
@@ -1401,7 +1842,7 @@  cifs_get_tcp_session(struct smb3_fs_context *ctx)
 			goto out_err_crypto_release;
 		}
 	}
-	rc = ip_connect(tcp_ses);
+	rc = cifs_ip_connect(tcp_ses);
 	if (rc < 0) {
 		cifs_dbg(VFS, "Error connecting to socket. Aborting operation.\n");
 		goto out_err_crypto_release;
@@ -2353,277 +2794,6 @@  cifs_match_super(struct super_block *sb, void *data)
 	return rc;
 }
 
-#ifdef CONFIG_DEBUG_LOCK_ALLOC
-static struct lock_class_key cifs_key[2];
-static struct lock_class_key cifs_slock_key[2];
-
-static inline void
-cifs_reclassify_socket4(struct socket *sock)
-{
-	struct sock *sk = sock->sk;
-	BUG_ON(!sock_allow_reclassification(sk));
-	sock_lock_init_class_and_name(sk, "slock-AF_INET-CIFS",
-		&cifs_slock_key[0], "sk_lock-AF_INET-CIFS", &cifs_key[0]);
-}
-
-static inline void
-cifs_reclassify_socket6(struct socket *sock)
-{
-	struct sock *sk = sock->sk;
-	BUG_ON(!sock_allow_reclassification(sk));
-	sock_lock_init_class_and_name(sk, "slock-AF_INET6-CIFS",
-		&cifs_slock_key[1], "sk_lock-AF_INET6-CIFS", &cifs_key[1]);
-}
-#else
-static inline void
-cifs_reclassify_socket4(struct socket *sock)
-{
-}
-
-static inline void
-cifs_reclassify_socket6(struct socket *sock)
-{
-}
-#endif
-
-/* See RFC1001 section 14 on representation of Netbios names */
-static void rfc1002mangle(char *target, char *source, unsigned int length)
-{
-	unsigned int i, j;
-
-	for (i = 0, j = 0; i < (length); i++) {
-		/* mask a nibble at a time and encode */
-		target[j] = 'A' + (0x0F & (source[i] >> 4));
-		target[j+1] = 'A' + (0x0F & source[i]);
-		j += 2;
-	}
-
-}
-
-static int
-bind_socket(struct TCP_Server_Info *server)
-{
-	int rc = 0;
-	if (server->srcaddr.ss_family != AF_UNSPEC) {
-		/* Bind to the specified local IP address */
-		struct socket *socket = server->ssocket;
-		rc = socket->ops->bind(socket,
-				       (struct sockaddr *) &server->srcaddr,
-				       sizeof(server->srcaddr));
-		if (rc < 0) {
-			struct sockaddr_in *saddr4;
-			struct sockaddr_in6 *saddr6;
-			saddr4 = (struct sockaddr_in *)&server->srcaddr;
-			saddr6 = (struct sockaddr_in6 *)&server->srcaddr;
-			if (saddr6->sin6_family == AF_INET6)
-				cifs_server_dbg(VFS, "Failed to bind to: %pI6c, error: %d\n",
-					 &saddr6->sin6_addr, rc);
-			else
-				cifs_server_dbg(VFS, "Failed to bind to: %pI4, error: %d\n",
-					 &saddr4->sin_addr.s_addr, rc);
-		}
-	}
-	return rc;
-}
-
-static int
-ip_rfc1001_connect(struct TCP_Server_Info *server)
-{
-	int rc = 0;
-	/*
-	 * some servers require RFC1001 sessinit before sending
-	 * negprot - BB check reconnection in case where second
-	 * sessinit is sent but no second negprot
-	 */
-	struct rfc1002_session_packet *ses_init_buf;
-	struct smb_hdr *smb_buf;
-	ses_init_buf = kzalloc(sizeof(struct rfc1002_session_packet),
-			       GFP_KERNEL);
-	if (ses_init_buf) {
-		ses_init_buf->trailer.session_req.called_len = 32;
-
-		if (server->server_RFC1001_name[0] != 0)
-			rfc1002mangle(ses_init_buf->trailer.
-				      session_req.called_name,
-				      server->server_RFC1001_name,
-				      RFC1001_NAME_LEN_WITH_NULL);
-		else
-			rfc1002mangle(ses_init_buf->trailer.
-				      session_req.called_name,
-				      DEFAULT_CIFS_CALLED_NAME,
-				      RFC1001_NAME_LEN_WITH_NULL);
-
-		ses_init_buf->trailer.session_req.calling_len = 32;
-
-		/*
-		 * calling name ends in null (byte 16) from old smb
-		 * convention.
-		 */
-		if (server->workstation_RFC1001_name[0] != 0)
-			rfc1002mangle(ses_init_buf->trailer.
-				      session_req.calling_name,
-				      server->workstation_RFC1001_name,
-				      RFC1001_NAME_LEN_WITH_NULL);
-		else
-			rfc1002mangle(ses_init_buf->trailer.
-				      session_req.calling_name,
-				      "LINUX_CIFS_CLNT",
-				      RFC1001_NAME_LEN_WITH_NULL);
-
-		ses_init_buf->trailer.session_req.scope1 = 0;
-		ses_init_buf->trailer.session_req.scope2 = 0;
-		smb_buf = (struct smb_hdr *)ses_init_buf;
-
-		/* sizeof RFC1002_SESSION_REQUEST with no scope */
-		smb_buf->smb_buf_length = cpu_to_be32(0x81000044);
-		rc = smb_send(server, smb_buf, 0x44);
-		kfree(ses_init_buf);
-		/*
-		 * RFC1001 layer in at least one server
-		 * requires very short break before negprot
-		 * presumably because not expecting negprot
-		 * to follow so fast.  This is a simple
-		 * solution that works without
-		 * complicating the code and causes no
-		 * significant slowing down on mount
-		 * for everyone else
-		 */
-		usleep_range(1000, 2000);
-	}
-	/*
-	 * else the negprot may still work without this
-	 * even though malloc failed
-	 */
-
-	return rc;
-}
-
-static int
-generic_ip_connect(struct TCP_Server_Info *server)
-{
-	int rc = 0;
-	__be16 sport;
-	int slen, sfamily;
-	struct socket *socket = server->ssocket;
-	struct sockaddr *saddr;
-
-	saddr = (struct sockaddr *) &server->dstaddr;
-
-	if (server->dstaddr.ss_family == AF_INET6) {
-		struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)&server->dstaddr;
-
-		sport = ipv6->sin6_port;
-		slen = sizeof(struct sockaddr_in6);
-		sfamily = AF_INET6;
-		cifs_dbg(FYI, "%s: connecting to [%pI6]:%d\n", __func__, &ipv6->sin6_addr,
-				ntohs(sport));
-	} else {
-		struct sockaddr_in *ipv4 = (struct sockaddr_in *)&server->dstaddr;
-
-		sport = ipv4->sin_port;
-		slen = sizeof(struct sockaddr_in);
-		sfamily = AF_INET;
-		cifs_dbg(FYI, "%s: connecting to %pI4:%d\n", __func__, &ipv4->sin_addr,
-				ntohs(sport));
-	}
-
-	if (socket == NULL) {
-		rc = __sock_create(cifs_net_ns(server), sfamily, SOCK_STREAM,
-				   IPPROTO_TCP, &socket, 1);
-		if (rc < 0) {
-			cifs_server_dbg(VFS, "Error %d creating socket\n", rc);
-			server->ssocket = NULL;
-			return rc;
-		}
-
-		/* BB other socket options to set KEEPALIVE, NODELAY? */
-		cifs_dbg(FYI, "Socket created\n");
-		server->ssocket = socket;
-		socket->sk->sk_allocation = GFP_NOFS;
-		if (sfamily == AF_INET6)
-			cifs_reclassify_socket6(socket);
-		else
-			cifs_reclassify_socket4(socket);
-	}
-
-	rc = bind_socket(server);
-	if (rc < 0)
-		return rc;
-
-	/*
-	 * Eventually check for other socket options to change from
-	 * the default. sock_setsockopt not used because it expects
-	 * user space buffer
-	 */
-	socket->sk->sk_rcvtimeo = 7 * HZ;
-	socket->sk->sk_sndtimeo = 5 * HZ;
-
-	/* make the bufsizes depend on wsize/rsize and max requests */
-	if (server->noautotune) {
-		if (socket->sk->sk_sndbuf < (200 * 1024))
-			socket->sk->sk_sndbuf = 200 * 1024;
-		if (socket->sk->sk_rcvbuf < (140 * 1024))
-			socket->sk->sk_rcvbuf = 140 * 1024;
-	}
-
-	if (server->tcp_nodelay)
-		tcp_sock_set_nodelay(socket->sk);
-
-	cifs_dbg(FYI, "sndbuf %d rcvbuf %d rcvtimeo 0x%lx\n",
-		 socket->sk->sk_sndbuf,
-		 socket->sk->sk_rcvbuf, socket->sk->sk_rcvtimeo);
-
-	rc = socket->ops->connect(socket, saddr, slen,
-				  server->noblockcnt ? O_NONBLOCK : 0);
-	/*
-	 * When mounting SMB root file systems, we do not want to block in
-	 * connect. Otherwise bail out and then let cifs_reconnect() perform
-	 * reconnect failover - if possible.
-	 */
-	if (server->noblockcnt && rc == -EINPROGRESS)
-		rc = 0;
-	if (rc < 0) {
-		cifs_dbg(FYI, "Error %d connecting to server\n", rc);
-		sock_release(socket);
-		server->ssocket = NULL;
-		return rc;
-	}
-
-	if (sport == htons(RFC1001_PORT))
-		rc = ip_rfc1001_connect(server);
-
-	return rc;
-}
-
-static int
-ip_connect(struct TCP_Server_Info *server)
-{
-	__be16 *sport;
-	struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr;
-	struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr;
-
-	if (server->dstaddr.ss_family == AF_INET6)
-		sport = &addr6->sin6_port;
-	else
-		sport = &addr->sin_port;
-
-	if (*sport == 0) {
-		int rc;
-
-		/* try with 445 port at first */
-		*sport = htons(CIFS_PORT);
-
-		rc = generic_ip_connect(server);
-		if (rc >= 0)
-			return rc;
-
-		/* if it failed, try with 139 port */
-		*sport = htons(RFC1001_PORT);
-	}
-
-	return generic_ip_connect(server);
-}
-
 void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon,
 			  struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
 {
@@ -3161,20 +3331,36 @@  cifs_setup_volume_info(struct smb3_fs_context *ctx, const char *mntopts, const c
 	}
 
 	if (mntopts) {
-		char *ip;
+		char *opts, *nopts, *key, *val, sep;
+		unsigned int count = 0;
 
-		rc = smb3_parse_opt(mntopts, "ip", &ip);
-		if (rc) {
-			cifs_dbg(VFS, "%s: failed to parse ip options: %d\n", __func__, rc);
+		opts = kstrdup(mntopts, GFP_KERNEL);
+		if (!opts)
+			return -ENOMEM;
+
+		smb3_options_for_each(opts, nopts, sep, key, val) {
+			if (strcasecmp(key, "ip"))
+				continue;
+			cifs_dbg(FYI, "%s: parsed ip address: %s\n", __func__, val);
+			if (!cifs_convert_address((struct sockaddr *)&ctx->dst_addr_list[count],
+						  val, strlen(val))) {
+				cifs_dbg(VFS, "%s: failed to convert ip address\n", __func__);
+				rc = -EINVAL;
+				break;
+			}
+			if (++count == CIFS_MAX_ADDR_COUNT)
+				break;
+		}
+		kfree(opts);
+
+		if (!rc && !count) {
+			cifs_dbg(VFS, "%s: could not find any ip address option\n", __func__);
+			rc = -EINVAL;
+		}
+		if (rc)
 			return rc;
-		}
 
-		rc = cifs_convert_address((struct sockaddr *)&ctx->dstaddr, ip, strlen(ip));
-		kfree(ip);
-		if (!rc) {
-			cifs_dbg(VFS, "%s: failed to convert ip address\n", __func__);
-			return -EINVAL;
-		}
+		ctx->dst_addr_count = count;
 	}
 
 	if (ctx->nullauth) {
@@ -3185,9 +3371,11 @@  cifs_setup_volume_info(struct smb3_fs_context *ctx, const char *mntopts, const c
 		/* BB fixme parse for domain name here */
 		cifs_dbg(FYI, "Username: %s\n", ctx->username);
 	} else {
+		/*
+		 * In userspace mount helper we can get user name from alternate locations such as
+		 * env variables and files on disk.
+		 */
 		cifs_dbg(VFS, "No username specified\n");
-	/* In userspace mount helper we can get user name from alternate
-	   locations such as env variables and files on disk */
 		return -EINVAL;
 	}
 
@@ -3391,6 +3579,8 @@  int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
 	char *mntdata = NULL;
 	bool ref_server = false;
 
+	cifs_dbg(FYI, "%s: mount_options=%s\n", __func__, cifs_sb->ctx->mount_options);
+
 	rc = mount_get_conns(ctx, cifs_sb, &xid, &server, &ses, &tcon);
 	/*
 	 * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally
diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c
index b1fa30fefe1f..5c3129d4af1d 100644
--- a/fs/cifs/dfs_cache.c
+++ b/fs/cifs/dfs_cache.c
@@ -1235,8 +1235,10 @@  int dfs_cache_update_vol(const char *fullpath, struct TCP_Server_Info *server)
 
 	cifs_dbg(FYI, "%s: updating volume info\n", __func__);
 	spin_lock(&vi->ctx_lock);
-	memcpy(&vi->ctx.dstaddr, &server->dstaddr,
-	       sizeof(vi->ctx.dstaddr));
+	memcpy(vi->ctx.dst_addr_list, server->dst_addr_list,
+	       server->dst_addr_count * sizeof(server->dst_addr_list[0]));
+	vi->ctx.dst_addr_count = server->dst_addr_count;
+	memcpy(&vi->ctx.dstaddr, &server->dstaddr, sizeof(vi->ctx.dstaddr));
 	spin_unlock(&vi->ctx_lock);
 
 	kref_put(&vi->refcnt, vol_release);
diff --git a/fs/cifs/dns_resolve.c b/fs/cifs/dns_resolve.c
index 534cbba72789..977ac4ba789b 100644
--- a/fs/cifs/dns_resolve.c
+++ b/fs/cifs/dns_resolve.c
@@ -32,25 +32,40 @@ 
 #include "cifsproto.h"
 #include "cifs_debug.h"
 
+static int iplist_to_addrs(char *ips, struct sockaddr_storage *addrs, int maxaddrs)
+{
+	char *ip;
+	int count = 0;
+
+	while ((ip = strsep(&ips, ",")) && count < maxaddrs) {
+		struct sockaddr_storage addr;
+
+		if (!*ip)
+			break;
+
+		cifs_dbg(FYI, "%s: add \'%s\' to the list of ip addresses\n", __func__, ip);
+		if (cifs_convert_address((struct sockaddr *)&addr, ip, strlen(ip)) > 0)
+			addrs[count++] = addr;
+	}
+	return count;
+}
+
 /**
- * dns_resolve_server_name_to_ip - Resolve UNC server name to ip address.
+ * dns_resolve_server_name_to_addrs - Resolve UNC server name to a list of addresses.
  * @unc: UNC path specifying the server (with '/' as delimiter)
- * @ip_addr: Where to return the IP address.
+ * @addrs: Where to return the list of addresses.
+ * @maxaddrs: Maximum number of addresses.
  *
- * The IP address will be returned in string form, and the caller is
- * responsible for freeing it.
- *
- * Returns length of result on success, -ve on error.
+ * Returns the number of resolved addresses, otherwise a negative error number.
  */
-int
-dns_resolve_server_name_to_ip(const char *unc, char **ip_addr)
+int dns_resolve_server_name_to_addrs(const char *unc, struct sockaddr_storage *addrs, int maxaddrs)
 {
 	struct sockaddr_storage ss;
 	const char *hostname, *sep;
-	char *name;
+	char *ips;
 	int len, rc;
 
-	if (!ip_addr || !unc)
+	if (!addrs || !maxaddrs || !unc)
 		return -EINVAL;
 
 	len = strlen(unc);
@@ -73,28 +88,23 @@  dns_resolve_server_name_to_ip(const char *unc, char **ip_addr)
 
 	/* Try to interpret hostname as an IPv4 or IPv6 address */
 	rc = cifs_convert_address((struct sockaddr *)&ss, hostname, len);
-	if (rc > 0)
-		goto name_is_IP_address;
+	if (rc > 0) {
+		cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %s\n", __func__, hostname);
+		addrs[0] = ss;
+		return 1;
+	}
 
 	/* Perform the upcall */
-	rc = dns_query(current->nsproxy->net_ns, NULL, hostname, len,
-		       NULL, ip_addr, NULL, false);
-	if (rc < 0)
-		cifs_dbg(FYI, "%s: unable to resolve: %*.*s\n",
-			 __func__, len, len, hostname);
-	else
-		cifs_dbg(FYI, "%s: resolved: %*.*s to %s\n",
-			 __func__, len, len, hostname, *ip_addr);
+	rc = dns_query(current->nsproxy->net_ns, NULL, hostname, len, "list", &ips, NULL, false);
+	if (rc < 0) {
+		cifs_dbg(FYI, "%s: unable to resolve: %*.*s\n", __func__, len, len, hostname);
+		return rc;
+	}
+	cifs_dbg(FYI, "%s: resolved: %*.*s to %s\n", __func__, len, len, hostname, ips);
+
+	rc = iplist_to_addrs(ips, addrs, maxaddrs);
+	cifs_dbg(FYI, "%s: num of resolved ips: %d\n", __func__, rc);
+	kfree(ips);
+
 	return rc;
-
-name_is_IP_address:
-	name = kmalloc(len + 1, GFP_KERNEL);
-	if (!name)
-		return -ENOMEM;
-	memcpy(name, hostname, len);
-	name[len] = 0;
-	cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %s\n",
-		 __func__, name);
-	*ip_addr = name;
-	return 0;
 }
diff --git a/fs/cifs/dns_resolve.h b/fs/cifs/dns_resolve.h
index d3f5d27f4d06..b6defe354278 100644
--- a/fs/cifs/dns_resolve.h
+++ b/fs/cifs/dns_resolve.h
@@ -23,8 +23,11 @@ 
 #ifndef _DNS_RESOLVE_H
 #define _DNS_RESOLVE_H
 
+#include <linux/net.h>
+
 #ifdef __KERNEL__
-extern int dns_resolve_server_name_to_ip(const char *unc, char **ip_addr);
+extern int dns_resolve_server_name_to_addrs(const char *unc, struct sockaddr_storage *addrs,
+					    int maxaddrs);
 #endif /* KERNEL */
 
 #endif /* _DNS_RESOLVE_H */
diff --git a/fs/cifs/fs_context.c b/fs/cifs/fs_context.c
index 5d21cd905315..a5b80bed2719 100644
--- a/fs/cifs/fs_context.c
+++ b/fs/cifs/fs_context.c
@@ -591,6 +591,7 @@  static int smb3_fs_context_parse_monolithic(struct fs_context *fc,
 static int smb3_fs_context_validate(struct fs_context *fc)
 {
 	struct smb3_fs_context *ctx = smb3_fc2context(fc);
+	unsigned int i;
 
 	if (ctx->rdma && ctx->vals->protocol_id < SMB30_PROT_ID) {
 		cifs_errorf(fc, "SMB Direct requires Version >=3.0\n");
@@ -633,9 +634,12 @@  static int smb3_fs_context_validate(struct fs_context *fc)
 			pr_err("Unable to determine destination address\n");
 			return -EHOSTUNREACH;
 		}
+		ctx->dst_addr_list[ctx->dst_addr_count++] = ctx->dstaddr;
 	}
 
 	/* set the port that we got earlier */
+	for (i = 0; i < ctx->dst_addr_count; i++)
+		cifs_set_port((struct sockaddr *)&ctx->dst_addr_list[i], ctx->port);
 	cifs_set_port((struct sockaddr *)&ctx->dstaddr, ctx->port);
 
 	if (ctx->override_uid && !ctx->uid_specified) {
@@ -792,6 +796,7 @@  static int smb3_fs_context_parse_param(struct fs_context *fc,
 	int i, opt;
 	bool is_smb3 = !strcmp(fc->fs_type->name, "smb3");
 	bool skip_parsing = false;
+	struct sockaddr_storage dstaddr;
 
 	cifs_dbg(FYI, "CIFS: parsing cifs mount option '%s'\n", param->key);
 
@@ -1096,12 +1101,25 @@  static int smb3_fs_context_parse_param(struct fs_context *fc,
 			ctx->got_ip = false;
 			break;
 		}
-		if (!cifs_convert_address((struct sockaddr *)&ctx->dstaddr,
-					  param->string,
+
+		if (ctx->dst_addr_count >= CIFS_MAX_ADDR_COUNT) {
+			cifs_errorf(fc, "reached max number (%u) of ip addresses.  ignoring ip=%s option.\n",
+				    CIFS_MAX_ADDR_COUNT, param->string);
+			break;
+		}
+
+		if (!cifs_convert_address((struct sockaddr *)&dstaddr, param->string,
 					  strlen(param->string))) {
 			pr_err("bad ip= option (%s)\n", param->string);
 			goto cifs_parse_mount_err;
 		}
+
+		if (ctx->dst_addr_count == 0)
+			ctx->dstaddr = dstaddr;
+
+		cifs_dbg(FYI, "%s: add \'%s\' to the list of destination addresses\n", __func__,
+			 param->string);
+		ctx->dst_addr_list[ctx->dst_addr_count++] = dstaddr;
 		ctx->got_ip = true;
 		break;
 	case Opt_domain:
diff --git a/fs/cifs/fs_context.h b/fs/cifs/fs_context.h
index 2a71c8e411ac..08e863b6c9b1 100644
--- a/fs/cifs/fs_context.h
+++ b/fs/cifs/fs_context.h
@@ -247,7 +247,9 @@  struct smb3_fs_context {
 	struct smb_version_operations *ops;
 	struct smb_version_values *vals;
 	char *prepath;
-	struct sockaddr_storage dstaddr; /* destination address */
+	struct sockaddr_storage dst_addr_list[CIFS_MAX_ADDR_COUNT]; /* list of dest addresses */
+	unsigned int dst_addr_count; /* number of dest addresses */
+	struct sockaddr_storage dstaddr; /* current destination address */
 	struct sockaddr_storage srcaddr; /* allow binding to a local IP */
 	struct nls_table *local_nls; /* This is a copy of the pointer in cifs_sb */
 	unsigned int echo_interval; /* echo interval in secs */
diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c
index 7b3b1ea76baf..cce3c6b709c1 100644
--- a/fs/cifs/misc.c
+++ b/fs/cifs/misc.c
@@ -1178,43 +1178,52 @@  int match_target_ip(struct TCP_Server_Info *server,
 		    bool *result)
 {
 	int rc;
-	char *target, *tip = NULL;
-	struct sockaddr tipaddr;
+	struct sockaddr_storage *addrs = NULL;
+	unsigned int numaddrs, i, j;
+	char *target = NULL;
 
 	*result = false;
 
 	target = kzalloc(share_len + 3, GFP_KERNEL);
-	if (!target) {
-		rc = -ENOMEM;
-		goto out;
-	}
+	if (!target)
+		return -ENOMEM;
 
 	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)
+	addrs = kcalloc(CIFS_MAX_ADDR_COUNT, sizeof(*addrs), GFP_KERNEL);
+	if (!addrs) {
+		rc = -ENOMEM;
 		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;
+	rc = numaddrs = dns_resolve_server_name_to_addrs(target, addrs, CIFS_MAX_ADDR_COUNT);
+	if (rc <= 0) {
+		cifs_dbg(FYI, "%s: failed to resolve server name: %d\n", __func__, rc);
+		rc = rc == 0 ? -EINVAL : rc;
 		goto out;
 	}
 
-	*result = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr,
-				    &tipaddr);
-	cifs_dbg(FYI, "%s: ip addresses match: %u\n", __func__, *result);
 	rc = 0;
 
+	if (server->dst_addr_count != numaddrs)
+		goto out;
+	for (i = 0; i < numaddrs; i++) {
+		bool match = false;
+
+		for (j = 0; j < server->dst_addr_count && !match; j++)
+			match |= cifs_match_ipaddr((struct sockaddr *)&server->dst_addr_list[j],
+						   (struct sockaddr *)&addrs[i]);
+		if (!match)
+			break;
+	}
+	*result = i == numaddrs;
+
 out:
+	cifs_dbg(FYI, "%s: ip addresses match: %u\n", __func__, *result);
 	kfree(target);
-	kfree(tip);
-
+	kfree(addrs);
 	return rc;
 }