summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/smb/client/cifsglob.h37
-rw-r--r--fs/smb/client/connect.c115
-rw-r--r--fs/smb/client/fs_context.c55
-rw-r--r--fs/smb/client/fs_context.h2
-rw-r--r--fs/smb/client/sess.c6
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(&param->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;