Commit 918c30df authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag '5.19-rc3-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6

Pull cifs client fixes from Steve French:
 "Fixes addressing important multichannel, and reconnect issues.

  Multichannel mounts when the server network interfaces changed, or ip
  addresses changed, uncovered problems, especially in reconnect, but
  the patches for this were held up until recently due to some lock
  conflicts that are now addressed.

  Included in this set of fixes:

   - three fixes relating to multichannel reconnect, dynamically
     adjusting the list of server interfaces to avoid problems during
     reconnect

   - a lock conflict fix related to the above

   - two important fixes for negotiate on secondary channels (null
     netname can unintentionally cause multichannel to be disabled to
     some servers)

   - a reconnect fix (reporting incorrect IP address in some cases)"

* tag '5.19-rc3-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6:
  cifs: update cifs_ses::ip_addr after failover
  cifs: avoid deadlocks while updating iface
  cifs: periodically query network interfaces from server
  cifs: during reconnect, update interface if necessary
  cifs: change iface_list from array to sorted linked list
  smb3: use netname when available on secondary channels
  smb3: fix empty netname context on secondary channels
parents 0840a791 af3a6d10
Loading
Loading
Loading
Loading
+7 −5
Original line number Diff line number Diff line
@@ -162,6 +162,8 @@ cifs_dump_iface(struct seq_file *m, struct cifs_server_iface *iface)
		seq_printf(m, "\t\tIPv4: %pI4\n", &ipv4->sin_addr);
	else if (iface->sockaddr.ss_family == AF_INET6)
		seq_printf(m, "\t\tIPv6: %pI6\n", &ipv6->sin6_addr);
	if (!iface->is_active)
		seq_puts(m, "\t\t[for-cleanup]\n");
}

static int cifs_debug_files_proc_show(struct seq_file *m, void *v)
@@ -221,6 +223,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
	struct TCP_Server_Info *server;
	struct cifs_ses *ses;
	struct cifs_tcon *tcon;
	struct cifs_server_iface *iface;
	int c, i, j;

	seq_puts(m,
@@ -456,11 +459,10 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
			if (ses->iface_count)
				seq_printf(m, "\n\n\tServer interfaces: %zu",
					   ses->iface_count);
			for (j = 0; j < ses->iface_count; j++) {
				struct cifs_server_iface *iface;

				iface = &ses->iface_list[j];
				seq_printf(m, "\n\t%d)", j+1);
			j = 0;
			list_for_each_entry(iface, &ses->iface_list,
						 iface_head) {
				seq_printf(m, "\n\t%d)", ++j);
				cifs_dump_iface(m, iface);
				if (is_ses_using_iface(ses, iface))
					seq_puts(m, "\t\t[CONNECTED]\n");
+57 −1
Original line number Diff line number Diff line
@@ -80,6 +80,9 @@
#define SMB_DNS_RESOLVE_INTERVAL_MIN     120
#define SMB_DNS_RESOLVE_INTERVAL_DEFAULT 600

/* smb multichannel query server interfaces interval in seconds */
#define SMB_INTERFACE_POLL_INTERVAL	600

/* maximum number of PDUs in one compound */
#define MAX_COMPOUND 5

@@ -933,15 +936,67 @@ static inline void cifs_set_net_ns(struct TCP_Server_Info *srv, struct net *net)
#endif

struct cifs_server_iface {
	struct list_head iface_head;
	struct kref refcount;
	size_t speed;
	unsigned int rdma_capable : 1;
	unsigned int rss_capable : 1;
	unsigned int is_active : 1; /* unset if non existent */
	struct sockaddr_storage sockaddr;
};

/* release iface when last ref is dropped */
static inline void
release_iface(struct kref *ref)
{
	struct cifs_server_iface *iface = container_of(ref,
						       struct cifs_server_iface,
						       refcount);
	list_del_init(&iface->iface_head);
	kfree(iface);
}

/*
 * compare two interfaces a and b
 * return 0 if everything matches.
 * return 1 if a has higher link speed, or rdma capable, or rss capable
 * return -1 otherwise.
 */
static inline int
iface_cmp(struct cifs_server_iface *a, struct cifs_server_iface *b)
{
	int cmp_ret = 0;

	WARN_ON(!a || !b);
	if (a->speed == b->speed) {
		if (a->rdma_capable == b->rdma_capable) {
			if (a->rss_capable == b->rss_capable) {
				cmp_ret = memcmp(&a->sockaddr, &b->sockaddr,
						 sizeof(a->sockaddr));
				if (!cmp_ret)
					return 0;
				else if (cmp_ret > 0)
					return 1;
				else
					return -1;
			} else if (a->rss_capable > b->rss_capable)
				return 1;
			else
				return -1;
		} else if (a->rdma_capable > b->rdma_capable)
			return 1;
		else
			return -1;
	} else if (a->speed > b->speed)
		return 1;
	else
		return -1;
}

struct cifs_chan {
	unsigned int in_reconnect : 1; /* if session setup in progress for this channel */
	struct TCP_Server_Info *server;
	struct cifs_server_iface *iface; /* interface in use */
	__u8 signkey[SMB3_SIGN_KEY_SIZE];
};

@@ -993,7 +1048,7 @@ struct cifs_ses {
	 */
	spinlock_t iface_lock;
	/* ========= begin: protected by iface_lock ======== */
	struct cifs_server_iface *iface_list;
	struct list_head iface_list;
	size_t iface_count;
	unsigned long iface_last_update; /* jiffies */
	/* ========= end: protected by iface_lock ======== */
@@ -1203,6 +1258,7 @@ struct cifs_tcon {
#ifdef CONFIG_CIFS_DFS_UPCALL
	struct list_head ulist; /* cache update list */
#endif
	struct delayed_work	query_interfaces; /* query interfaces workqueue job */
};

/*
+7 −0
Original line number Diff line number Diff line
@@ -636,6 +636,13 @@ cifs_chan_clear_need_reconnect(struct cifs_ses *ses,
bool
cifs_chan_needs_reconnect(struct cifs_ses *ses,
			  struct TCP_Server_Info *server);
bool
cifs_chan_is_iface_active(struct cifs_ses *ses,
			  struct TCP_Server_Info *server);
int
cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server);
int
SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon);

void extract_unc_hostname(const char *unc, const char **h, size_t *len);
int copy_path_name(char *dst, const char *src);
+54 −5
Original line number Diff line number Diff line
@@ -145,6 +145,25 @@ static int reconn_set_ipaddr_from_hostname(struct TCP_Server_Info *server)
	return rc;
}

static void smb2_query_server_interfaces(struct work_struct *work)
{
	int rc;
	struct cifs_tcon *tcon = container_of(work,
					struct cifs_tcon,
					query_interfaces.work);

	/*
	 * query server network interfaces, in case they change
	 */
	rc = SMB3_request_interfaces(0, tcon);
	if (rc) {
		cifs_dbg(FYI, "%s: failed to query server interfaces: %d\n",
				__func__, rc);
	}

	queue_delayed_work(cifsiod_wq, &tcon->query_interfaces,
			   (SMB_INTERFACE_POLL_INTERVAL * HZ));
}

static void cifs_resolve_server(struct work_struct *work)
{
@@ -217,7 +236,7 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
				      bool mark_smb_session)
{
	struct TCP_Server_Info *pserver;
	struct cifs_ses *ses;
	struct cifs_ses *ses, *nses;
	struct cifs_tcon *tcon;

	/*
@@ -231,7 +250,20 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,


	spin_lock(&cifs_tcp_ses_lock);
	list_for_each_entry(ses, &pserver->smb_ses_list, smb_ses_list) {
	list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) {
		/* check if iface is still active */
		if (!cifs_chan_is_iface_active(ses, server)) {
			/*
			 * HACK: drop the lock before calling
			 * cifs_chan_update_iface to avoid deadlock
			 */
			ses->ses_count++;
			spin_unlock(&cifs_tcp_ses_lock);
			cifs_chan_update_iface(ses, server);
			spin_lock(&cifs_tcp_ses_lock);
			ses->ses_count--;
		}

		spin_lock(&ses->chan_lock);
		if (!mark_smb_session && cifs_chan_needs_reconnect(ses, server))
			goto next_session;
@@ -1894,9 +1926,11 @@ void cifs_put_smb_ses(struct cifs_ses *ses)
		int i;

		for (i = 1; i < chan_count; i++) {
			spin_unlock(&ses->chan_lock);
			if (ses->chans[i].iface) {
				kref_put(&ses->chans[i].iface->refcount, release_iface);
				ses->chans[i].iface = NULL;
			}
			cifs_put_tcp_session(ses->chans[i].server, 0);
			spin_lock(&ses->chan_lock);
			ses->chans[i].server = NULL;
		}
	}
@@ -2270,6 +2304,9 @@ cifs_put_tcon(struct cifs_tcon *tcon)
	list_del_init(&tcon->tcon_list);
	spin_unlock(&cifs_tcp_ses_lock);

	/* cancel polling of interfaces */
	cancel_delayed_work_sync(&tcon->query_interfaces);

	if (tcon->use_witness) {
		int rc;

@@ -2507,6 +2544,12 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx)
	tcon->local_lease = ctx->local_lease;
	INIT_LIST_HEAD(&tcon->pending_opens);

	/* schedule query interfaces poll */
	INIT_DELAYED_WORK(&tcon->query_interfaces,
			  smb2_query_server_interfaces);
	queue_delayed_work(cifsiod_wq, &tcon->query_interfaces,
			   (SMB_INTERFACE_POLL_INTERVAL * HZ));

	spin_lock(&cifs_tcp_ses_lock);
	list_add(&tcon->tcon_list, &ses->tcon_list);
	spin_unlock(&cifs_tcp_ses_lock);
@@ -3982,10 +4025,16 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses,
		   struct nls_table *nls_info)
{
	int rc = -ENOSYS;
	struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr;
	struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr;
	bool is_binding = false;


	spin_lock(&cifs_tcp_ses_lock);
	if (server->dstaddr.ss_family == AF_INET6)
		scnprintf(ses->ip_addr, sizeof(ses->ip_addr), "%pI6", &addr6->sin6_addr);
	else
		scnprintf(ses->ip_addr, sizeof(ses->ip_addr), "%pI4", &addr->sin_addr);

	if (ses->ses_status != SES_GOOD &&
	    ses->ses_status != SES_NEW &&
	    ses->ses_status != SES_NEED_RECON) {
+8 −1
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ sesInfoAlloc(void)
		INIT_LIST_HEAD(&ret_buf->tcon_list);
		mutex_init(&ret_buf->session_mutex);
		spin_lock_init(&ret_buf->iface_lock);
		INIT_LIST_HEAD(&ret_buf->iface_list);
		spin_lock_init(&ret_buf->chan_lock);
	}
	return ret_buf;
@@ -83,6 +84,8 @@ sesInfoAlloc(void)
void
sesInfoFree(struct cifs_ses *buf_to_free)
{
	struct cifs_server_iface *iface = NULL, *niface = NULL;

	if (buf_to_free == NULL) {
		cifs_dbg(FYI, "Null buffer passed to sesInfoFree\n");
		return;
@@ -96,7 +99,11 @@ sesInfoFree(struct cifs_ses *buf_to_free)
	kfree(buf_to_free->user_name);
	kfree(buf_to_free->domainName);
	kfree_sensitive(buf_to_free->auth_key.response);
	kfree(buf_to_free->iface_list);
	spin_lock(&buf_to_free->iface_lock);
	list_for_each_entry_safe(iface, niface, &buf_to_free->iface_list,
				 iface_head)
		kref_put(&iface->refcount, release_iface);
	spin_unlock(&buf_to_free->iface_lock);
	kfree_sensitive(buf_to_free);
}

Loading