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

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
...@@ -162,6 +162,8 @@ cifs_dump_iface(struct seq_file *m, struct cifs_server_iface *iface) ...@@ -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); seq_printf(m, "\t\tIPv4: %pI4\n", &ipv4->sin_addr);
else if (iface->sockaddr.ss_family == AF_INET6) else if (iface->sockaddr.ss_family == AF_INET6)
seq_printf(m, "\t\tIPv6: %pI6\n", &ipv6->sin6_addr); 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) 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) ...@@ -221,6 +223,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
struct TCP_Server_Info *server; struct TCP_Server_Info *server;
struct cifs_ses *ses; struct cifs_ses *ses;
struct cifs_tcon *tcon; struct cifs_tcon *tcon;
struct cifs_server_iface *iface;
int c, i, j; int c, i, j;
seq_puts(m, seq_puts(m,
...@@ -456,11 +459,10 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) ...@@ -456,11 +459,10 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
if (ses->iface_count) if (ses->iface_count)
seq_printf(m, "\n\n\tServer interfaces: %zu", seq_printf(m, "\n\n\tServer interfaces: %zu",
ses->iface_count); ses->iface_count);
for (j = 0; j < ses->iface_count; j++) { j = 0;
struct cifs_server_iface *iface; list_for_each_entry(iface, &ses->iface_list,
iface_head) {
iface = &ses->iface_list[j]; seq_printf(m, "\n\t%d)", ++j);
seq_printf(m, "\n\t%d)", j+1);
cifs_dump_iface(m, iface); cifs_dump_iface(m, iface);
if (is_ses_using_iface(ses, iface)) if (is_ses_using_iface(ses, iface))
seq_puts(m, "\t\t[CONNECTED]\n"); seq_puts(m, "\t\t[CONNECTED]\n");
......
...@@ -80,6 +80,9 @@ ...@@ -80,6 +80,9 @@
#define SMB_DNS_RESOLVE_INTERVAL_MIN 120 #define SMB_DNS_RESOLVE_INTERVAL_MIN 120
#define SMB_DNS_RESOLVE_INTERVAL_DEFAULT 600 #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 */ /* maximum number of PDUs in one compound */
#define MAX_COMPOUND 5 #define MAX_COMPOUND 5
...@@ -933,15 +936,67 @@ static inline void cifs_set_net_ns(struct TCP_Server_Info *srv, struct net *net) ...@@ -933,15 +936,67 @@ static inline void cifs_set_net_ns(struct TCP_Server_Info *srv, struct net *net)
#endif #endif
struct cifs_server_iface { struct cifs_server_iface {
struct list_head iface_head;
struct kref refcount;
size_t speed; size_t speed;
unsigned int rdma_capable : 1; unsigned int rdma_capable : 1;
unsigned int rss_capable : 1; unsigned int rss_capable : 1;
unsigned int is_active : 1; /* unset if non existent */
struct sockaddr_storage sockaddr; 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 { struct cifs_chan {
unsigned int in_reconnect : 1; /* if session setup in progress for this channel */ unsigned int in_reconnect : 1; /* if session setup in progress for this channel */
struct TCP_Server_Info *server; struct TCP_Server_Info *server;
struct cifs_server_iface *iface; /* interface in use */
__u8 signkey[SMB3_SIGN_KEY_SIZE]; __u8 signkey[SMB3_SIGN_KEY_SIZE];
}; };
...@@ -993,7 +1048,7 @@ struct cifs_ses { ...@@ -993,7 +1048,7 @@ struct cifs_ses {
*/ */
spinlock_t iface_lock; spinlock_t iface_lock;
/* ========= begin: protected by iface_lock ======== */ /* ========= begin: protected by iface_lock ======== */
struct cifs_server_iface *iface_list; struct list_head iface_list;
size_t iface_count; size_t iface_count;
unsigned long iface_last_update; /* jiffies */ unsigned long iface_last_update; /* jiffies */
/* ========= end: protected by iface_lock ======== */ /* ========= end: protected by iface_lock ======== */
...@@ -1203,6 +1258,7 @@ struct cifs_tcon { ...@@ -1203,6 +1258,7 @@ struct cifs_tcon {
#ifdef CONFIG_CIFS_DFS_UPCALL #ifdef CONFIG_CIFS_DFS_UPCALL
struct list_head ulist; /* cache update list */ struct list_head ulist; /* cache update list */
#endif #endif
struct delayed_work query_interfaces; /* query interfaces workqueue job */
}; };
/* /*
......
...@@ -636,6 +636,13 @@ cifs_chan_clear_need_reconnect(struct cifs_ses *ses, ...@@ -636,6 +636,13 @@ cifs_chan_clear_need_reconnect(struct cifs_ses *ses,
bool bool
cifs_chan_needs_reconnect(struct cifs_ses *ses, cifs_chan_needs_reconnect(struct cifs_ses *ses,
struct TCP_Server_Info *server); 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); void extract_unc_hostname(const char *unc, const char **h, size_t *len);
int copy_path_name(char *dst, const char *src); int copy_path_name(char *dst, const char *src);
......
...@@ -145,6 +145,25 @@ static int reconn_set_ipaddr_from_hostname(struct TCP_Server_Info *server) ...@@ -145,6 +145,25 @@ static int reconn_set_ipaddr_from_hostname(struct TCP_Server_Info *server)
return rc; 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) 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, ...@@ -217,7 +236,7 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
bool mark_smb_session) bool mark_smb_session)
{ {
struct TCP_Server_Info *pserver; struct TCP_Server_Info *pserver;
struct cifs_ses *ses; struct cifs_ses *ses, *nses;
struct cifs_tcon *tcon; struct cifs_tcon *tcon;
/* /*
...@@ -231,7 +250,20 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server, ...@@ -231,7 +250,20 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
spin_lock(&cifs_tcp_ses_lock); 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); spin_lock(&ses->chan_lock);
if (!mark_smb_session && cifs_chan_needs_reconnect(ses, server)) if (!mark_smb_session && cifs_chan_needs_reconnect(ses, server))
goto next_session; goto next_session;
...@@ -1894,9 +1926,11 @@ void cifs_put_smb_ses(struct cifs_ses *ses) ...@@ -1894,9 +1926,11 @@ void cifs_put_smb_ses(struct cifs_ses *ses)
int i; int i;
for (i = 1; i < chan_count; 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); cifs_put_tcp_session(ses->chans[i].server, 0);
spin_lock(&ses->chan_lock);
ses->chans[i].server = NULL; ses->chans[i].server = NULL;
} }
} }
...@@ -2270,6 +2304,9 @@ cifs_put_tcon(struct cifs_tcon *tcon) ...@@ -2270,6 +2304,9 @@ cifs_put_tcon(struct cifs_tcon *tcon)
list_del_init(&tcon->tcon_list); list_del_init(&tcon->tcon_list);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
/* cancel polling of interfaces */
cancel_delayed_work_sync(&tcon->query_interfaces);
if (tcon->use_witness) { if (tcon->use_witness) {
int rc; int rc;
...@@ -2507,6 +2544,12 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) ...@@ -2507,6 +2544,12 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx)
tcon->local_lease = ctx->local_lease; tcon->local_lease = ctx->local_lease;
INIT_LIST_HEAD(&tcon->pending_opens); 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); spin_lock(&cifs_tcp_ses_lock);
list_add(&tcon->tcon_list, &ses->tcon_list); list_add(&tcon->tcon_list, &ses->tcon_list);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
...@@ -3982,10 +4025,16 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, ...@@ -3982,10 +4025,16 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses,
struct nls_table *nls_info) struct nls_table *nls_info)
{ {
int rc = -ENOSYS; 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; bool is_binding = false;
spin_lock(&cifs_tcp_ses_lock); 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 && if (ses->ses_status != SES_GOOD &&
ses->ses_status != SES_NEW && ses->ses_status != SES_NEW &&
ses->ses_status != SES_NEED_RECON) { ses->ses_status != SES_NEED_RECON) {
......
...@@ -75,6 +75,7 @@ sesInfoAlloc(void) ...@@ -75,6 +75,7 @@ sesInfoAlloc(void)
INIT_LIST_HEAD(&ret_buf->tcon_list); INIT_LIST_HEAD(&ret_buf->tcon_list);
mutex_init(&ret_buf->session_mutex); mutex_init(&ret_buf->session_mutex);
spin_lock_init(&ret_buf->iface_lock); spin_lock_init(&ret_buf->iface_lock);
INIT_LIST_HEAD(&ret_buf->iface_list);
spin_lock_init(&ret_buf->chan_lock); spin_lock_init(&ret_buf->chan_lock);
} }
return ret_buf; return ret_buf;
...@@ -83,6 +84,8 @@ sesInfoAlloc(void) ...@@ -83,6 +84,8 @@ sesInfoAlloc(void)
void void
sesInfoFree(struct cifs_ses *buf_to_free) sesInfoFree(struct cifs_ses *buf_to_free)
{ {
struct cifs_server_iface *iface = NULL, *niface = NULL;
if (buf_to_free == NULL) { if (buf_to_free == NULL) {
cifs_dbg(FYI, "Null buffer passed to sesInfoFree\n"); cifs_dbg(FYI, "Null buffer passed to sesInfoFree\n");
return; return;
...@@ -96,7 +99,11 @@ sesInfoFree(struct cifs_ses *buf_to_free) ...@@ -96,7 +99,11 @@ sesInfoFree(struct cifs_ses *buf_to_free)
kfree(buf_to_free->user_name); kfree(buf_to_free->user_name);
kfree(buf_to_free->domainName); kfree(buf_to_free->domainName);
kfree_sensitive(buf_to_free->auth_key.response); 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); kfree_sensitive(buf_to_free);
} }
......
...@@ -58,7 +58,7 @@ bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface) ...@@ -58,7 +58,7 @@ bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface)
spin_lock(&ses->chan_lock); spin_lock(&ses->chan_lock);
for (i = 0; i < ses->chan_count; i++) { for (i = 0; i < ses->chan_count; i++) {
if (is_server_using_iface(ses->chans[i].server, iface)) { if (ses->chans[i].iface == iface) {
spin_unlock(&ses->chan_lock); spin_unlock(&ses->chan_lock);
return true; return true;
} }
...@@ -146,16 +146,24 @@ cifs_chan_needs_reconnect(struct cifs_ses *ses, ...@@ -146,16 +146,24 @@ cifs_chan_needs_reconnect(struct cifs_ses *ses,
return CIFS_CHAN_NEEDS_RECONNECT(ses, chan_index); return CIFS_CHAN_NEEDS_RECONNECT(ses, chan_index);
} }
bool
cifs_chan_is_iface_active(struct cifs_ses *ses,
struct TCP_Server_Info *server)
{
unsigned int chan_index = cifs_ses_get_chan_index(ses, server);
return ses->chans[chan_index].iface &&
ses->chans[chan_index].iface->is_active;
}
/* returns number of channels added */ /* returns number of channels added */
int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
{ {
int old_chan_count, new_chan_count; int old_chan_count, new_chan_count;
int left; int left;
int i = 0;
int rc = 0; int rc = 0;
int tries = 0; int tries = 0;
struct cifs_server_iface *ifaces = NULL; struct cifs_server_iface *iface = NULL, *niface = NULL;
size_t iface_count;
spin_lock(&ses->chan_lock); spin_lock(&ses->chan_lock);
...@@ -184,33 +192,17 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) ...@@ -184,33 +192,17 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
} }
spin_unlock(&ses->chan_lock); spin_unlock(&ses->chan_lock);
/*
* Make a copy of the iface list at the time and use that
* instead so as to not hold the iface spinlock for opening
* channels
*/
spin_lock(&ses->iface_lock);
iface_count = ses->iface_count;
if (iface_count <= 0) {
spin_unlock(&ses->iface_lock);
cifs_dbg(VFS, "no iface list available to open channels\n");
return 0;
}
ifaces = kmemdup(ses->iface_list, iface_count*sizeof(*ifaces),
GFP_ATOMIC);
if (!ifaces) {
spin_unlock(&ses->iface_lock);
return 0;
}
spin_unlock(&ses->iface_lock);
/* /*
* Keep connecting to same, fastest, iface for all channels as * Keep connecting to same, fastest, iface for all channels as
* long as its RSS. Try next fastest one if not RSS or channel * long as its RSS. Try next fastest one if not RSS or channel
* creation fails. * creation fails.
*/ */
spin_lock(&ses->iface_lock);
iface = list_first_entry(&ses->iface_list, struct cifs_server_iface,
iface_head);
spin_unlock(&ses->iface_lock);
while (left > 0) { while (left > 0) {
struct cifs_server_iface *iface;
tries++; tries++;
if (tries > 3*ses->chan_max) { if (tries > 3*ses->chan_max) {
...@@ -219,30 +211,127 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) ...@@ -219,30 +211,127 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
break; break;
} }
iface = &ifaces[i]; spin_lock(&ses->iface_lock);
if (is_ses_using_iface(ses, iface) && !iface->rss_capable) { if (!ses->iface_count) {
i = (i+1) % iface_count; spin_unlock(&ses->iface_lock);
continue; break;
} }
rc = cifs_ses_add_channel(cifs_sb, ses, iface); list_for_each_entry_safe_from(iface, niface, &ses->iface_list,
if (rc) { iface_head) {
cifs_dbg(FYI, "failed to open extra channel on iface#%d rc=%d\n", /* skip ifaces that are unusable */
i, rc); if (!iface->is_active ||
i = (i+1) % iface_count; (is_ses_using_iface(ses, iface) &&
continue; !iface->rss_capable)) {
continue;
}
/* take ref before unlock */
kref_get(&iface->refcount);
spin_unlock(&ses->iface_lock);
rc = cifs_ses_add_channel(cifs_sb, ses, iface);
spin_lock(&ses->iface_lock);
if (rc) {
cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n",
&iface->sockaddr,
rc);
kref_put(&iface->refcount, release_iface);
continue;
}
cifs_dbg(FYI, "successfully opened new channel on iface:%pIS\n",
&iface->sockaddr);
break;
} }
spin_unlock(&ses->iface_lock);
cifs_dbg(FYI, "successfully opened new channel on iface#%d\n",
i);
left--; left--;
new_chan_count++; new_chan_count++;
} }
kfree(ifaces);
return new_chan_count - old_chan_count; return new_chan_count - old_chan_count;
} }
/*
* update the iface for the channel if necessary.
* will return 0 when iface is updated, 1 if removed, 2 otherwise
* Must be called with chan_lock held.
*/
int
cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server)
{
unsigned int chan_index;
struct cifs_server_iface *iface = NULL;
struct cifs_server_iface *old_iface = NULL;
int rc = 0;
spin_lock(&ses->chan_lock);
chan_index = cifs_ses_get_chan_index(ses, server);
if (!chan_index) {
spin_unlock(&ses->chan_lock);
return 0;
}
if (ses->chans[chan_index].iface) {
old_iface = ses->chans[chan_index].iface;
if (old_iface->is_active) {
spin_unlock(&ses->chan_lock);
return 1;
}
}
spin_unlock(&ses->chan_lock);
spin_lock(&ses->iface_lock);
/* then look for a new one */
list_for_each_entry(iface, &ses->iface_list, iface_head) {
if (!iface->is_active ||
(is_ses_using_iface(ses, iface) &&
!iface->rss_capable)) {
continue;
}
kref_get(&iface->refcount);
}
if (!list_entry_is_head(iface, &ses->iface_list, iface_head)) {
rc = 1;
iface = NULL;
cifs_dbg(FYI, "unable to find a suitable iface\n");
}
/* now drop the ref to the current iface */
if (old_iface && iface) {
kref_put(&old_iface->refcount, release_iface);
cifs_dbg(FYI, "replacing iface: %pIS with %pIS\n",
&old_iface->sockaddr,
&iface->sockaddr);
} else if (old_iface) {
kref_put(&old_iface->refcount, release_iface);
cifs_dbg(FYI, "releasing ref to iface: %pIS\n",
&old_iface->sockaddr);
} else {
WARN_ON(!iface);
cifs_dbg(FYI, "adding new iface: %pIS\n", &iface->sockaddr);
}
spin_unlock(&ses->iface_lock);
spin_lock(&ses->chan_lock);
chan_index = cifs_ses_get_chan_index(ses, server);
ses->chans[chan_index].iface = iface;
/* No iface is found. if secondary chan, drop connection */
if (!iface && CIFS_SERVER_IS_CHAN(server))
ses->chans[chan_index].server = NULL;
spin_unlock(&ses->chan_lock);
if (!iface && CIFS_SERVER_IS_CHAN(server))
cifs_put_tcp_session(server, false);
return rc;
}
/* /*
* If server is a channel of ses, return the corresponding enclosing * If server is a channel of ses, return the corresponding enclosing
* cifs_chan otherwise return NULL. * cifs_chan otherwise return NULL.
...@@ -355,6 +444,7 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, ...@@ -355,6 +444,7 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
spin_unlock(&ses->chan_lock); spin_unlock(&ses->chan_lock);
goto out; goto out;
} }
chan->iface = iface;
ses->chan_count++; ses->chan_count++;
atomic_set(&ses->chan_seq, 0); atomic_set(&ses->chan_seq, 0);
......
...@@ -512,73 +512,41 @@ smb3_negotiate_rsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx) ...@@ -512,73 +512,41 @@ smb3_negotiate_rsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
static int static int
parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
size_t buf_len, size_t buf_len,
struct cifs_server_iface **iface_list, struct cifs_ses *ses)
size_t *iface_count)
{ {
struct network_interface_info_ioctl_rsp *p; struct network_interface_info_ioctl_rsp *p;
struct sockaddr_in *addr4; struct sockaddr_in *addr4;
struct sockaddr_in6 *addr6; struct sockaddr_in6 *addr6;
struct iface_info_ipv4 *p4; struct iface_info_ipv4 *p4;
struct iface_info_ipv6 *p6; struct iface_info_ipv6 *p6;
struct cifs_server_iface *info; struct cifs_server_iface *info = NULL, *iface = NULL, *niface = NULL;
struct cifs_server_iface tmp_iface;
ssize_t bytes_left; ssize_t bytes_left;
size_t next = 0; size_t next = 0;
int nb_iface = 0; int nb_iface = 0;
int rc = 0; int rc = 0, ret = 0;
*iface_list = NULL;
*iface_count = 0;
/*
* Fist pass: count and sanity check
*/
bytes_left = buf_len; bytes_left = buf_len;
p = buf; p = buf;
while (bytes_left >= sizeof(*p)) {
nb_iface++;
next = le32_to_cpu(p->Next);
if (!next) {
bytes_left -= sizeof(*p);
break;
}
p = (struct network_interface_info_ioctl_rsp *)((u8 *)p+next);
bytes_left -= next;
}
if (!nb_iface) {
cifs_dbg(VFS, "%s: malformed interface info\n", __func__);
rc = -EINVAL;
goto out;
}
/* Azure rounds the buffer size up 8, to a 16 byte boundary */
if ((bytes_left > 8) || p->Next)
cifs_dbg(VFS, "%s: incomplete interface info\n", __func__);
spin_lock(&ses->iface_lock);
/* /*
* Second pass: extract info to internal structure * Go through iface_list and do kref_put to remove
* any unused ifaces. ifaces in use will be removed
* when the last user calls a kref_put on it
*/ */
list_for_each_entry_safe(iface, niface, &ses->iface_list,
*iface_list = kcalloc(nb_iface, sizeof(**iface_list), GFP_KERNEL); iface_head) {
if (!*iface_list) { iface->is_active = 0;
rc = -ENOMEM; kref_put(&iface->refcount, release_iface);
goto out;
} }
spin_unlock(&ses->iface_lock);
info = *iface_list;
bytes_left = buf_len;
p = buf;
while (bytes_left >= sizeof(*p)) { while (bytes_left >= sizeof(*p)) {
info->speed = le64_to_cpu(p->LinkSpeed); memset(&tmp_iface, 0, sizeof(tmp_iface));
info->rdma_capable = le32_to_cpu(p->Capability & RDMA_CAPABLE) ? 1 : 0; tmp_iface.speed = le64_to_cpu(p->LinkSpeed);
info->rss_capable = le32_to_cpu(p->Capability & RSS_CAPABLE) ? 1 : 0; tmp_iface.rdma_capable = le32_to_cpu(p->Capability & RDMA_CAPABLE) ? 1 : 0;
tmp_iface.rss_capable = le32_to_cpu(p->Capability & RSS_CAPABLE) ? 1 : 0;
cifs_dbg(FYI, "%s: adding iface %zu\n", __func__, *iface_count);
cifs_dbg(FYI, "%s: speed %zu bps\n", __func__, info->speed);
cifs_dbg(FYI, "%s: capabilities 0x%08x\n", __func__,
le32_to_cpu(p->Capability));
switch (p->Family) { switch (p->Family) {
/* /*
...@@ -587,7 +555,7 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, ...@@ -587,7 +555,7 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
* conversion explicit in case either one changes. * conversion explicit in case either one changes.
*/ */
case INTERNETWORK: case INTERNETWORK:
addr4 = (struct sockaddr_in *)&info->sockaddr; addr4 = (struct sockaddr_in *)&tmp_iface.sockaddr;
p4 = (struct iface_info_ipv4 *)p->Buffer; p4 = (struct iface_info_ipv4 *)p->Buffer;
addr4->sin_family = AF_INET; addr4->sin_family = AF_INET;
memcpy(&addr4->sin_addr, &p4->IPv4Address, 4); memcpy(&addr4->sin_addr, &p4->IPv4Address, 4);
...@@ -599,7 +567,7 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, ...@@ -599,7 +567,7 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
&addr4->sin_addr); &addr4->sin_addr);
break; break;
case INTERNETWORKV6: case INTERNETWORKV6:
addr6 = (struct sockaddr_in6 *)&info->sockaddr; addr6 = (struct sockaddr_in6 *)&tmp_iface.sockaddr;
p6 = (struct iface_info_ipv6 *)p->Buffer; p6 = (struct iface_info_ipv6 *)p->Buffer;
addr6->sin6_family = AF_INET6; addr6->sin6_family = AF_INET6;
memcpy(&addr6->sin6_addr, &p6->IPv6Address, 16); memcpy(&addr6->sin6_addr, &p6->IPv6Address, 16);
...@@ -619,46 +587,96 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, ...@@ -619,46 +587,96 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf,
goto next_iface; goto next_iface;
} }
(*iface_count)++; /*
info++; * The iface_list is assumed to be sorted by speed.
* Check if the new interface exists in that list.
* NEVER change iface. it could be in use.
* Add a new one instead
*/
spin_lock(&ses->iface_lock);
iface = niface = NULL;
list_for_each_entry_safe(iface, niface, &ses->iface_list,
iface_head) {
ret = iface_cmp(iface, &tmp_iface);
if (!ret) {
/* just get a ref so that it doesn't get picked/freed */
iface->is_active = 1;
kref_get(&iface->refcount);
spin_unlock(&ses->iface_lock);
goto next_iface;
} else if (ret < 0) {
/* all remaining ifaces are slower */
kref_get(&iface->refcount);
break;
}
}
spin_unlock(&ses->iface_lock);
/* no match. insert the entry in the list */
info = kmalloc(sizeof(struct cifs_server_iface),
GFP_KERNEL);
if (!info) {
rc = -ENOMEM;
goto out;
}
memcpy(info, &tmp_iface, sizeof(tmp_iface));
/* add this new entry to the list */
kref_init(&info->refcount);
info->is_active = 1;
cifs_dbg(FYI, "%s: adding iface %zu\n", __func__, ses->iface_count);
cifs_dbg(FYI, "%s: speed %zu bps\n", __func__, info->speed);
cifs_dbg(FYI, "%s: capabilities 0x%08x\n", __func__,
le32_to_cpu(p->Capability));
spin_lock(&ses->iface_lock);
if (!list_entry_is_head(iface, &ses->iface_list, iface_head)) {
list_add_tail(&info->iface_head, &iface->iface_head);
kref_put(&iface->refcount, release_iface);
} else
list_add_tail(&info->iface_head, &ses->iface_list);
spin_unlock(&ses->iface_lock);
ses->iface_count++;
ses->iface_last_update = jiffies;
next_iface: next_iface:
nb_iface++;
next = le32_to_cpu(p->Next); next = le32_to_cpu(p->Next);
if (!next) if (!next) {
bytes_left -= sizeof(*p);
break; break;
}
p = (struct network_interface_info_ioctl_rsp *)((u8 *)p+next); p = (struct network_interface_info_ioctl_rsp *)((u8 *)p+next);
bytes_left -= next; bytes_left -= next;
} }
if (!*iface_count) { if (!nb_iface) {
cifs_dbg(VFS, "%s: malformed interface info\n", __func__);
rc = -EINVAL; rc = -EINVAL;
goto out; goto out;
} }
out: /* Azure rounds the buffer size up 8, to a 16 byte boundary */
if (rc) { if ((bytes_left > 8) || p->Next)
kfree(*iface_list); cifs_dbg(VFS, "%s: incomplete interface info\n", __func__);
*iface_count = 0;
*iface_list = NULL;
}
return rc;
}
static int compare_iface(const void *ia, const void *ib)
{
const struct cifs_server_iface *a = (struct cifs_server_iface *)ia;
const struct cifs_server_iface *b = (struct cifs_server_iface *)ib;
return a->speed == b->speed ? 0 : (a->speed > b->speed ? -1 : 1); if (!ses->iface_count) {
rc = -EINVAL;
goto out;
}
out:
return rc;
} }
static int int
SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon) SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon)
{ {
int rc; int rc;
unsigned int ret_data_len = 0; unsigned int ret_data_len = 0;
struct network_interface_info_ioctl_rsp *out_buf = NULL; struct network_interface_info_ioctl_rsp *out_buf = NULL;
struct cifs_server_iface *iface_list;
size_t iface_count;
struct cifs_ses *ses = tcon->ses; struct cifs_ses *ses = tcon->ses;
rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID, rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID,
...@@ -674,21 +692,10 @@ SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon) ...@@ -674,21 +692,10 @@ SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon)
goto out; goto out;
} }
rc = parse_server_interfaces(out_buf, ret_data_len, rc = parse_server_interfaces(out_buf, ret_data_len, ses);
&iface_list, &iface_count);
if (rc) if (rc)
goto out; goto out;
/* sort interfaces from fastest to slowest */
sort(iface_list, iface_count, sizeof(*iface_list), compare_iface, NULL);
spin_lock(&ses->iface_lock);
kfree(ses->iface_list);
ses->iface_list = iface_list;
ses->iface_count = iface_count;
ses->iface_last_update = jiffies;
spin_unlock(&ses->iface_lock);
out: out:
kfree(out_buf); kfree(out_buf);
return rc; return rc;
......
...@@ -543,6 +543,7 @@ assemble_neg_contexts(struct smb2_negotiate_req *req, ...@@ -543,6 +543,7 @@ assemble_neg_contexts(struct smb2_negotiate_req *req,
struct TCP_Server_Info *server, unsigned int *total_len) struct TCP_Server_Info *server, unsigned int *total_len)
{ {
char *pneg_ctxt; char *pneg_ctxt;
char *hostname = NULL;
unsigned int ctxt_len, neg_context_count; unsigned int ctxt_len, neg_context_count;
if (*total_len > 200) { if (*total_len > 200) {
...@@ -570,16 +571,24 @@ assemble_neg_contexts(struct smb2_negotiate_req *req, ...@@ -570,16 +571,24 @@ assemble_neg_contexts(struct smb2_negotiate_req *req,
*total_len += ctxt_len; *total_len += ctxt_len;
pneg_ctxt += ctxt_len; pneg_ctxt += ctxt_len;
ctxt_len = build_netname_ctxt((struct smb2_netname_neg_context *)pneg_ctxt,
server->hostname);
*total_len += ctxt_len;
pneg_ctxt += ctxt_len;
build_posix_ctxt((struct smb2_posix_neg_context *)pneg_ctxt); build_posix_ctxt((struct smb2_posix_neg_context *)pneg_ctxt);
*total_len += sizeof(struct smb2_posix_neg_context); *total_len += sizeof(struct smb2_posix_neg_context);
pneg_ctxt += sizeof(struct smb2_posix_neg_context); pneg_ctxt += sizeof(struct smb2_posix_neg_context);
neg_context_count = 4; /*
* secondary channels don't have the hostname field populated
* use the hostname field in the primary channel instead
*/
hostname = CIFS_SERVER_IS_CHAN(server) ?
server->primary_server->hostname : server->hostname;
if (hostname && (hostname[0] != 0)) {
ctxt_len = build_netname_ctxt((struct smb2_netname_neg_context *)pneg_ctxt,
hostname);
*total_len += ctxt_len;
pneg_ctxt += ctxt_len;
neg_context_count = 4;
} else /* second channels do not have a hostname */
neg_context_count = 3;
if (server->compress_algorithm) { if (server->compress_algorithm) {
build_compression_ctxt((struct smb2_compression_capabilities_context *) build_compression_ctxt((struct smb2_compression_capabilities_context *)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment