summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEnzo Matsumiya <ematsumiya@suse.de>2025-05-21 16:25:53 +0200
committerEnzo Matsumiya <ematsumiya@suse.de>2025-05-22 14:02:48 +0200
commit192a0f9ef43af2d520ed4ac016595992660d24e0 (patch)
treec9cc854c84eecf159b8605d9dfc6ad269c89cbea
parenta87cf769d6b0be1a0e733e33945f3d6ba468240b (diff)
downloadlinux-multichannel-fixes-v2.tar.gz
linux-multichannel-fixes-v2.tar.bz2
linux-multichannel-fixes-v2.zip
smb: client: set interface for primary channelmultichannel-fixes-v2
On multichannel mounts, 2 sockets are created for the primary channel, as the interface it's connected to is received and parsed/added again. So compare ifaces IP with primary channel server IP (server->dstaddr) and assign the matching iface to ses->chans[0].iface. Add a ->is_primary flag to cifs_server_iface to allow skipping session setup on the primary channel, and for pretty-printing on cifs_debug.c as well. Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
-rw-r--r--fs/smb/client/cifs_debug.c17
-rw-r--r--fs/smb/client/cifsglob.h1
-rw-r--r--fs/smb/client/sess.c14
-rw-r--r--fs/smb/client/smb2ops.c133
4 files changed, 90 insertions, 75 deletions
diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index 0c2acc4e2cbb..34166040569c 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -212,22 +212,16 @@ static inline const char *smb_speed_to_str(size_t bps)
static void
cifs_dump_iface(struct seq_file *m, struct cifs_server_iface *iface)
{
- struct sockaddr_in *ipv4 = (struct sockaddr_in *)&iface->sockaddr;
- struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)&iface->sockaddr;
-
seq_printf(m, "\tSpeed: %s\n", smb_speed_to_str(iface->speed));
seq_puts(m, "\t\tCapabilities: ");
if (iface->rdma_capable)
- seq_puts(m, "rdma ");
+ seq_puts(m, "RDMA ");
if (iface->rss_capable)
- seq_puts(m, "rss ");
+ seq_puts(m, "RSS ");
if (!iface->rdma_capable && !iface->rss_capable)
- seq_puts(m, "None");
+ seq_puts(m, "NONE");
seq_putc(m, '\n');
- if (iface->sockaddr.ss_family == AF_INET)
- 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);
+ seq_printf(m, "\t\tIP: %pIS\n", (struct sockaddr *)&iface->sockaddr);
if (!iface->is_active)
seq_puts(m, "\t\t[for-cleanup]\n");
}
@@ -602,7 +596,8 @@ skip_rdma:
iface->num_channels);
if (is_ses_using_iface(ses, iface))
- seq_puts(m, "\t\t[CONNECTED]\n");
+ seq_printf(m, "\t\t[CONNECTED]%s\n",
+ iface->is_primary ? " (PRIMARY)" : "");
}
spin_unlock(&server->iface_lock);
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 8631f3b9eaf7..282684316b87 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -1083,6 +1083,7 @@ struct cifs_server_iface {
unsigned int rdma_capable : 1;
unsigned int rss_capable : 1;
unsigned int is_active : 1; /* unset if non existent */
+ unsigned int is_primary : 1;
struct sockaddr_storage sockaddr;
};
diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c
index af39c819822f..5c2725eee933 100644
--- a/fs/smb/client/sess.c
+++ b/fs/smb/client/sess.c
@@ -170,8 +170,6 @@ static int try_adding_channels(struct cifs_ses *ses, struct list_head *ifaces)
spin_unlock(&pserver->iface_lock);
list_for_each_entry_safe(iface, niface, ifaces, iface_head) {
- rc = 0;
-
/* do not mix rdma and non-rdma interfaces */
if (iface->rdma_capable != ses->server->rdma)
continue;
@@ -185,7 +183,15 @@ static int try_adding_channels(struct cifs_ses *ses, struct list_head *ifaces)
if (iface->weight_fulfilled >= weight)
continue;
- rc = cifs_ses_add_channel(ses, iface);
+ rc = 0;
+ if (!iface->is_primary) {
+ rc = cifs_ses_add_channel(ses, iface);
+ } else {
+ spin_lock(&ses->chan_lock);
+ ses->chans[0].iface = iface;
+ spin_unlock(&ses->chan_lock);
+ }
+
if (!rc) {
cifs_info("successfully opened new channel on iface:%pIS\n", &iface->sockaddr);
iface->is_active = 1;
@@ -195,7 +201,7 @@ static int try_adding_channels(struct cifs_ses *ses, struct list_head *ifaces)
spin_lock(&pserver->iface_lock);
pserver->iface_count++;
- list_move(&iface->iface_head, &pserver->iface_list);
+ list_move_tail(&iface->iface_head, &pserver->iface_list);
pserver->iface_last_update = jiffies;
spin_unlock(&pserver->iface_lock);
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index f3efafa23fa4..827cdec61b12 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -593,9 +593,12 @@ static int __iface_cmp(struct cifs_server_iface *a, struct cifs_server_iface *b)
return -1;
if (a->rdma_capable == b->rdma_capable) {
if (a->rss_capable == b->rss_capable) {
- if (a->speed == b->speed)
- return cifs_ipaddr_cmp((struct sockaddr *)&a->sockaddr,
- (struct sockaddr *)&b->sockaddr);
+ if (a->speed == b->speed) {
+ if (cifs_ipaddr_cmp((struct sockaddr *)&a->sockaddr,
+ (struct sockaddr *)&b->sockaddr))
+ return -1;
+ return 0;
+ }
if (a->speed > b->speed)
return -1;
return 1;
@@ -623,17 +626,70 @@ static inline bool ifaces_need_update(unsigned long last_update)
time_before(jiffies, last_update + (SMB_INTERFACE_POLL_INTERVAL * HZ)));
}
-static int
-parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, size_t buf_len,
- struct TCP_Server_Info *pserver, struct list_head *ifaces)
+static inline bool check_iface(struct network_interface_info_ioctl_rsp *info, bool server_rdma,
+ struct cifs_server_iface *tmp)
{
- struct network_interface_info_ioctl_rsp *p;
struct sockaddr_in *addr4;
struct sockaddr_in6 *addr6;
struct iface_info_ipv4 *p4;
struct iface_info_ipv6 *p6;
- struct cifs_server_iface *info = NULL, *iface = NULL, *niface = NULL;
- struct cifs_server_iface tmp_iface;
+
+ memset(tmp, 0, sizeof(*tmp));
+
+ /* default to 1Gbps when link speed is unset */
+ tmp->speed = le64_to_cpu(info->LinkSpeed) ?: 1000000000;
+ tmp->rdma_capable = le32_to_cpu(info->Capability & RDMA_CAPABLE) ? 1 : 0;
+ tmp->rss_capable = le32_to_cpu(info->Capability & RSS_CAPABLE) ? 1 : 0;
+
+ if (!tmp->rss_capable)
+ return false;
+
+ if (tmp->rdma_capable != server_rdma)
+ return false;
+
+ /*
+ * The kernel and wire socket structures have the same
+ * layout and use network byte order but make the
+ * conversion explicit in case either one changes.
+ */
+ if (info->Family == INTERNETWORK) {
+ addr4 = (struct sockaddr_in *)&tmp->sockaddr;
+ p4 = (struct iface_info_ipv4 *)info->Buffer;
+ addr4->sin_family = AF_INET;
+ memcpy(&addr4->sin_addr, &p4->IPv4Address, 4);
+
+ /* [MS-SMB2] 2.2.32.5.1.1 Clients MUST ignore these */
+ addr4->sin_port = cpu_to_be16(CIFS_PORT);
+
+ cifs_dbg(FYI, "%s: ipv4 %pI4\n", __func__, &addr4->sin_addr);
+ return true;
+ }
+
+ if (info->Family == INTERNETWORKV6) {
+ addr6 = (struct sockaddr_in6 *)&tmp->sockaddr;
+ p6 = (struct iface_info_ipv6 *)info->Buffer;
+ addr6->sin6_family = AF_INET6;
+ memcpy(&addr6->sin6_addr, &p6->IPv6Address, 16);
+
+ /* [MS-SMB2] 2.2.32.5.1.2 Clients MUST ignore these */
+ addr6->sin6_flowinfo = 0;
+ addr6->sin6_scope_id = 0;
+ addr6->sin6_port = cpu_to_be16(CIFS_PORT);
+
+ cifs_dbg(FYI, "%s: ipv6 %pI6\n", __func__, &addr6->sin6_addr);
+ return true;
+ }
+
+ cifs_dbg(VFS, "%s: skipping unsupported socket family\n", __func__);
+ return false;
+}
+
+static int
+parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, size_t buf_len,
+ struct TCP_Server_Info *pserver, struct list_head *ifaces)
+{
+ struct network_interface_info_ioctl_rsp *p;
+ struct cifs_server_iface *info = NULL, *iface = NULL, *niface = NULL, tmp_iface = {};
ssize_t bytes_left;
size_t next = 0;
int nb_iface = 0;
@@ -653,57 +709,9 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, size_t buf
}
while (bytes_left >= (ssize_t)sizeof(*p)) {
- memset(&tmp_iface, 0, sizeof(tmp_iface));
- /* default to 1Gbps when link speed is unset */
- tmp_iface.speed = le64_to_cpu(p->LinkSpeed) ?: 1000000000;
- 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;
-
- if (!tmp_iface.rss_capable)
- goto next_iface;
-
- if (tmp_iface.rdma_capable != pserver->rdma)
+ if (!check_iface(p, pserver->rdma, &tmp_iface))
goto next_iface;
- switch (p->Family) {
- /*
- * The kernel and wire socket structures have the same
- * layout and use network byte order but make the
- * conversion explicit in case either one changes.
- */
- case INTERNETWORK:
- addr4 = (struct sockaddr_in *)&tmp_iface.sockaddr;
- p4 = (struct iface_info_ipv4 *)p->Buffer;
- addr4->sin_family = AF_INET;
- memcpy(&addr4->sin_addr, &p4->IPv4Address, 4);
-
- /* [MS-SMB2] 2.2.32.5.1.1 Clients MUST ignore these */
- addr4->sin_port = cpu_to_be16(CIFS_PORT);
-
- cifs_dbg(FYI, "%s: ipv4 %pI4\n", __func__,
- &addr4->sin_addr);
- break;
- case INTERNETWORKV6:
- addr6 = (struct sockaddr_in6 *)&tmp_iface.sockaddr;
- p6 = (struct iface_info_ipv6 *)p->Buffer;
- addr6->sin6_family = AF_INET6;
- memcpy(&addr6->sin6_addr, &p6->IPv6Address, 16);
-
- /* [MS-SMB2] 2.2.32.5.1.2 Clients MUST ignore these */
- addr6->sin6_flowinfo = 0;
- addr6->sin6_scope_id = 0;
- addr6->sin6_port = cpu_to_be16(CIFS_PORT);
-
- cifs_dbg(FYI, "%s: ipv6 %pI6\n", __func__,
- &addr6->sin6_addr);
- break;
- default:
- cifs_dbg(VFS,
- "%s: skipping unsupported socket family\n",
- __func__);
- goto next_iface;
- }
-
/*
* iface_list is sorted by speed.
* Check if the new interface exists in that list.
@@ -737,13 +745,18 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, size_t buf
/* add this new entry to the list */
kref_init(&info->refcount);
- cifs_dbg(FYI, "%s: adding iface[%zu]: %pIS\n", __func__, pserver->iface_count,
- &info->sockaddr);
+ cifs_dbg(FYI, "%s: adding iface: %pIS\n", __func__, &info->sockaddr);
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));
+ /* mark primary channel to avoid duplicate sockets */
+ if (unlikely(!cifs_ipaddr_cmp((struct sockaddr *)&info->sockaddr,
+ (struct sockaddr *)&pserver->dstaddr)))
+ info->is_primary = 1;
+
list_add_tail(&info->iface_head, ifaces);
+
next_iface:
nb_iface++;
next = le32_to_cpu(p->Next);