diff options
-rw-r--r-- | fs/smb/client/cifsglob.h | 37 | ||||
-rw-r--r-- | fs/smb/client/connect.c | 115 | ||||
-rw-r--r-- | fs/smb/client/fs_context.c | 55 | ||||
-rw-r--r-- | fs/smb/client/fs_context.h | 2 | ||||
-rw-r--r-- | fs/smb/client/sess.c | 6 |
5 files changed, 206 insertions, 9 deletions
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index caeac04da97c..ee863b569734 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -106,6 +106,8 @@ #define CIFS_DFS_ROOT_SES(ses) ((ses)->dfs_root_ses ?: (ses)) +#define CIFS_MAX_CHANNELS 16 + /* * CIFS vfs client Status information (based on what we know.) */ @@ -621,6 +623,8 @@ struct TCP_Server_Info { struct socket *ssocket; struct sockaddr_storage dstaddr; struct sockaddr_storage srcaddr; /* locally bind to this IP */ + char *iface_names[CIFS_MAX_CHANNELS]; + int __iface_idx; /* do not use directly */ #ifdef CONFIG_NET_NS struct net *net; #endif @@ -757,6 +761,38 @@ struct TCP_Server_Info { char *origin_fullpath, *leaf_fullpath; }; +static inline char *smb_get_next_iface_name_rr(struct TCP_Server_Info *server) +{ + struct TCP_Server_Info *pserver = server->primary_server ?: server; + int idx = pserver->__iface_idx; + bool wrap = true; + char *iface; + + /* no interface names set */ + if (idx == -1) + return NULL; + + if (!pserver->iface_names[0]) { + pserver->__iface_idx = -1; + return NULL; + } + +next_iface: + if ((iface = pserver->iface_names[++idx])) { + pserver->__iface_idx = idx; + return iface; + } else if (wrap || idx >= CIFS_MAX_CHANNELS - 1) { + wrap = false; + idx = 0; + goto next_iface; + } + + /* no more interfaces to check -- unlikely to reach */ + pserver->__iface_idx = -1; + + return NULL; +} + static inline bool is_smb1(struct TCP_Server_Info *server) { return HEADER_PREAMBLE_SIZE(server) != 0; @@ -1072,7 +1108,6 @@ struct cifs_ses { spinlock_t chan_lock; /* ========= begin: protected by chan_lock ======== */ -#define CIFS_MAX_CHANNELS 16 #define CIFS_ALL_CHANNELS_SET(ses) \ ((1UL << (ses)->chan_count) - 1) #define CIFS_ALL_CHANS_GOOD(ses) \ diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index c808e2662857..a75db2dbd53f 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -28,6 +28,7 @@ #include <linux/uaccess.h> #include <asm/processor.h> #include <linux/inet.h> +#include <net/inet_common.h> #include <linux/module.h> #include <keys/user-type.h> #include <net/ipv6.h> @@ -1548,6 +1549,17 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect) server->session_key.len = 0; kfree(server->hostname); server->hostname = NULL; + if (server->iface_names[0]) { + int i; + + for (i = 0; i <= server->__iface_idx; i++) { + if (server->iface_names[i]) { + kfree(server->iface_names[i]); + server->iface_names[i] = NULL; + } + } + server->__iface_idx = -1; + } task = xchg(&server->tsk, NULL); if (task) @@ -1598,7 +1610,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, struct TCP_Server_Info *primary_server) { struct TCP_Server_Info *tcp_ses = NULL; - int rc; + int rc, i; cifs_dbg(FYI, "UNC: %s\n", ctx->UNC); @@ -1677,6 +1689,24 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, sizeof(tcp_ses->srcaddr)); memcpy(&tcp_ses->dstaddr, &ctx->dstaddr, sizeof(tcp_ses->dstaddr)); + + tcp_ses->__iface_idx = -1; + + if (ctx->iface_names[0]) { + int max = primary_server ? 1 : CIFS_MAX_CHANNELS; + char *iface_name; + + i = 0; + while (i < max && (iface_name = ctx->iface_names[i])) { + tcp_ses->iface_names[i] = kstrdup(iface_name, GFP_KERNEL); + if (unlikely(!tcp_ses->iface_names[i])) + goto out_err; + i++; + } + + tcp_ses->__iface_idx = i ? i - 1 : -1; + } + if (ctx->use_client_guid) memcpy(tcp_ses->client_guid, ctx->client_guid, SMB2_CLIENT_GUID_SIZE); @@ -1778,6 +1808,12 @@ out_err: kfree(tcp_ses->leaf_fullpath); if (tcp_ses->ssocket) sock_release(tcp_ses->ssocket); + for (i = 0; i <= tcp_ses->__iface_idx; i++) { + if (tcp_ses->iface_names[i]) { + kfree(tcp_ses->iface_names[i]); + tcp_ses->iface_names[i] = NULL; + } + } kfree(tcp_ses); } return ERR_PTR(rc); @@ -2849,11 +2885,80 @@ static void rfc1002mangle(char *target, char *source, unsigned int length) } +static inline int __smb_get_sock_addr(struct socket *sock, struct sockaddr *sa, bool peer) +{ + int addrlen; /* return value from kernel_getsockname() */ + + if (peer) + addrlen = kernel_getpeername(sock, sa); + else + addrlen = kernel_getsockname(sock, sa); + + if (addrlen != sizeof(struct sockaddr_in) && addrlen != sizeof(struct sockaddr_in6)) + return addrlen < 0 ? addrlen : -EAFNOSUPPORT; + + return 0; +} + +static inline int smb_get_srcaddr(struct socket *sock, struct sockaddr *srcaddr) +{ + int rc; + + rc = __smb_get_sock_addr(sock, srcaddr, false); + if (rc) + cifs_dbg(VFS, "%s: failed to get src address, rc=%d\n", __func__, rc); + + return rc; +} + +static inline int smb_get_dstaddr(struct socket *sock, struct sockaddr *dstaddr) +{ + int rc; + + rc = __smb_get_sock_addr(sock, dstaddr, true); + if (rc) + cifs_dbg(VFS, "%s: failed to get dst address, rc=%d\n", __func__, rc); + + return rc; +} + +static inline int smb_get_sock_addrs(struct socket *sock, struct sockaddr *srcaddr, + struct sockaddr *dstaddr) +{ + int rc; + + rc = smb_get_srcaddr(sock, srcaddr); + if (!rc) + rc = smb_get_dstaddr(sock, dstaddr); + if (rc) + return rc; + + cifs_dbg(VFS, "%s: socket @ 0x%p srcaddr=%pISc, dstaddr=%pISc\n", + __func__, sock, srcaddr, dstaddr); + + return 0; +} + +static inline int bind_iface_name(struct socket *sock, const char *iface_name) +{ + sockptr_t optval = KERNEL_SOCKPTR((char *)iface_name); + + return sock_setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, optval, strlen(iface_name)); +} + static int bind_socket(struct TCP_Server_Info *server) { + char *iface_name = server->iface_names[0]; int rc = 0; - if (server->srcaddr.ss_family != AF_UNSPEC) { + + if (iface_name) { + rc = bind_iface_name(server->ssocket, iface_name); + if (rc) { + cifs_dbg(VFS, "failed to bind to interface %s, rc=%d\n", iface_name, rc); + return rc; + } + } else if (server->srcaddr.ss_family != AF_UNSPEC) { /* Bind to the specified local IP address */ struct socket *socket = server->ssocket; rc = socket->ops->bind(socket, @@ -2872,6 +2977,7 @@ bind_socket(struct TCP_Server_Info *server) &saddr4->sin_addr.s_addr, rc); } } + return rc; } @@ -2937,7 +3043,7 @@ generic_ip_connect(struct TCP_Server_Info *server) __be16 sport; int slen, sfamily; struct socket *socket = server->ssocket; - struct sockaddr *saddr; + struct sockaddr *saddr, _src, _dst; saddr = (struct sockaddr *) &server->dstaddr; @@ -3022,6 +3128,9 @@ generic_ip_connect(struct TCP_Server_Info *server) server->ssocket = NULL; return rc; } + + (void) smb_get_sock_addrs(socket, &_src, &_dst); + trace_smb3_connect_done(server->hostname, server->conn_id, &server->dstaddr); if (sport == htons(RFC1001_PORT)) rc = ip_rfc1001_connect(server); diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c index 23dd35a9cefd..7cdce3c6916c 100644 --- a/fs/smb/client/fs_context.c +++ b/fs/smb/client/fs_context.c @@ -1242,14 +1242,51 @@ static int smb3_fs_context_parse_param(struct fs_context *fc, cifs_dbg(FYI, "Domain name set\n"); break; case Opt_srcaddr: - if (!cifs_convert_address( - (struct sockaddr *)&ctx->srcaddr, - param->string, strlen(param->string))) { - pr_warn("Could not parse srcaddr: %s\n", - param->string); + { + char *srcaddr_val = param->string; + int i = 0; + + /* check if it's an IP address */ + if (cifs_convert_address((struct sockaddr *)&ctx->srcaddr, + srcaddr_val, strlen(srcaddr_val))) + break; + + while ((srcaddr_val = strsep(¶m->string, ";")) && i < CIFS_MAX_CHANNELS) { + if (!strlen(srcaddr_val)) { + pr_warn("empty value passed to srcaddr=\n"); + continue; + } + + /* else, it's an interface name -- validate it */ + if (!dev_valid_name(srcaddr_val) || + !__dev_get_by_name(&init_net, srcaddr_val)) { + pr_warn("ignoring invalid interface name %s for srcaddr=\n", + srcaddr_val); + continue; + } + + ctx->iface_names[i] = kstrdup(srcaddr_val, GFP_KERNEL); + if (!ctx->iface_names[i]) { + /* cleanup any previously allocated interface name */ + while (--i >= 0) { + if (ctx->iface_names[i]) { + kfree(ctx->iface_names[i]); + ctx->iface_names[i] = NULL; + } + } + goto cifs_parse_mount_err; + } + + i++; + } + + if (i == 0) { + pr_warn("Could not parse srcaddr=: %s\n", param->string); goto cifs_parse_mount_err; } + break; + } case Opt_iocharset: if (strnlen(param->string, 1024) >= 65) { pr_warn("iocharset name too long\n"); @@ -1637,6 +1674,14 @@ smb3_cleanup_fs_context_contents(struct smb3_fs_context *ctx) ctx->prepath = NULL; kfree(ctx->leaf_fullpath); ctx->leaf_fullpath = NULL; + if (ctx->iface_names[0]) { + int i = 0; + while (ctx->iface_names[i] && i < CIFS_MAX_CHANNELS) { + kfree(ctx->iface_names[i]); + ctx->iface_names[i] = NULL; + i++; + } + } } void diff --git a/fs/smb/client/fs_context.h b/fs/smb/client/fs_context.h index 21bac6318c07..de177ea34f97 100644 --- a/fs/smb/client/fs_context.h +++ b/fs/smb/client/fs_context.h @@ -262,6 +262,8 @@ struct smb3_fs_context { char *prepath; struct sockaddr_storage dstaddr; /* destination address */ struct sockaddr_storage srcaddr; /* allow binding to a local IP */ + /* allow binding local interfaces by name */ + char *iface_names[CIFS_MAX_CHANNELS]; struct nls_table *local_nls; /* This is a copy of the pointer in cifs_sb */ unsigned int echo_interval; /* echo interval in secs */ __u64 snapshot_time; /* needed for timewarp tokens */ diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index 335c078c42fb..220e0a578cff 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -430,6 +430,10 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, ctx.rdma = iface->rdma_capable; memcpy(&ctx.dstaddr, &iface->sockaddr, sizeof(struct sockaddr_storage)); + spin_lock(&ses->server->srv_lock); + ctx.iface_names[0] = smb_get_next_iface_name_rr(ses->server); + spin_unlock(&ses->server->srv_lock); + /* reuse master con client guid */ memcpy(&ctx.client_guid, ses->server->client_guid, SMB2_CLIENT_GUID_SIZE); @@ -444,6 +448,8 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, rc = PTR_ERR(chan->server); chan->server = NULL; spin_unlock(&ses->chan_lock); + if (ctx.iface_names[0]) + kfree(ctx.iface_names[0]); goto out; } chan->iface = iface; |