Commit 3eb9a889 authored by Ben Greear's avatar Ben Greear Committed by Steve French

cifs: Allow binding to local IP address.

When using multi-homed machines, it's nice to be able to specify
the local IP to use for outbound connections.  This patch gives
cifs the ability to bind to a particular IP address.

   Usage:  mount -t cifs -o srcaddr=192.168.1.50,user=foo, ...
   Usage:  mount -t cifs -o srcaddr=2002::100:1,user=foo, ...
Acked-by: default avatarJeff Layton <jlayton@redhat.com>
Acked-by: default avatarDr. David Holder <david.holder@erion.co.uk>
Signed-off-by: default avatarBen Greear <greearb@candelatech.com>
Signed-off-by: default avatarSteve French <sfrench@us.ibm.com>
parent 2b149f11
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include <linux/kthread.h> #include <linux/kthread.h>
#include <linux/freezer.h> #include <linux/freezer.h>
#include <linux/smp_lock.h> #include <linux/smp_lock.h>
#include <net/ipv6.h>
#include "cifsfs.h" #include "cifsfs.h"
#include "cifspdu.h" #include "cifspdu.h"
#define DECLARE_GLOBALS_HERE #define DECLARE_GLOBALS_HERE
...@@ -367,6 +368,8 @@ cifs_show_options(struct seq_file *s, struct vfsmount *m) ...@@ -367,6 +368,8 @@ cifs_show_options(struct seq_file *s, struct vfsmount *m)
{ {
struct cifs_sb_info *cifs_sb = CIFS_SB(m->mnt_sb); struct cifs_sb_info *cifs_sb = CIFS_SB(m->mnt_sb);
struct cifsTconInfo *tcon = cifs_sb->tcon; struct cifsTconInfo *tcon = cifs_sb->tcon;
struct sockaddr *srcaddr;
srcaddr = (struct sockaddr *)&tcon->ses->server->srcaddr;
seq_printf(s, ",unc=%s", tcon->treeName); seq_printf(s, ",unc=%s", tcon->treeName);
if (tcon->ses->userName) if (tcon->ses->userName)
...@@ -374,6 +377,22 @@ cifs_show_options(struct seq_file *s, struct vfsmount *m) ...@@ -374,6 +377,22 @@ cifs_show_options(struct seq_file *s, struct vfsmount *m)
if (tcon->ses->domainName) if (tcon->ses->domainName)
seq_printf(s, ",domain=%s", tcon->ses->domainName); seq_printf(s, ",domain=%s", tcon->ses->domainName);
if (srcaddr->sa_family != AF_UNSPEC) {
struct sockaddr_in *saddr4;
struct sockaddr_in6 *saddr6;
saddr4 = (struct sockaddr_in *)srcaddr;
saddr6 = (struct sockaddr_in6 *)srcaddr;
if (srcaddr->sa_family == AF_INET6)
seq_printf(s, ",srcaddr=%pI6c",
&saddr6->sin6_addr);
else if (srcaddr->sa_family == AF_INET)
seq_printf(s, ",srcaddr=%pI4",
&saddr4->sin_addr.s_addr);
else
seq_printf(s, ",srcaddr=BAD-AF:%i",
(int)(srcaddr->sa_family));
}
seq_printf(s, ",uid=%d", cifs_sb->mnt_uid); seq_printf(s, ",uid=%d", cifs_sb->mnt_uid);
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_OVERR_UID) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_OVERR_UID)
seq_printf(s, ",forceuid"); seq_printf(s, ",forceuid");
......
...@@ -139,6 +139,7 @@ struct TCP_Server_Info { ...@@ -139,6 +139,7 @@ struct TCP_Server_Info {
struct sockaddr_in sockAddr; struct sockaddr_in sockAddr;
struct sockaddr_in6 sockAddr6; struct sockaddr_in6 sockAddr6;
} addr; } addr;
struct sockaddr_storage srcaddr; /* locally bind to this IP */
wait_queue_head_t response_q; wait_queue_head_t response_q;
wait_queue_head_t request_q; /* if more than maxmpx to srvr must block*/ wait_queue_head_t request_q; /* if more than maxmpx to srvr must block*/
struct list_head pending_mid_q; struct list_head pending_mid_q;
......
...@@ -105,6 +105,7 @@ struct smb_vol { ...@@ -105,6 +105,7 @@ struct smb_vol {
bool sockopt_tcp_nodelay:1; bool sockopt_tcp_nodelay:1;
unsigned short int port; unsigned short int port;
char *prepath; char *prepath;
struct sockaddr_storage srcaddr; /* allow binding to a local IP */
struct nls_table *local_nls; struct nls_table *local_nls;
}; };
...@@ -1046,6 +1047,22 @@ cifs_parse_mount_options(char *options, const char *devname, ...@@ -1046,6 +1047,22 @@ cifs_parse_mount_options(char *options, const char *devname,
"long\n"); "long\n");
return 1; return 1;
} }
} else if (strnicmp(data, "srcaddr", 7) == 0) {
vol->srcaddr.ss_family = AF_UNSPEC;
if (!value || !*value) {
printk(KERN_WARNING "CIFS: srcaddr value"
" not specified.\n");
return 1; /* needs_arg; */
}
i = cifs_convert_address((struct sockaddr *)&vol->srcaddr,
value, strlen(value));
if (i < 0) {
printk(KERN_WARNING "CIFS: Could not parse"
" srcaddr: %s\n",
value);
return 1;
}
} else if (strnicmp(data, "prefixpath", 10) == 0) { } else if (strnicmp(data, "prefixpath", 10) == 0) {
if (!value || !*value) { if (!value || !*value) {
printk(KERN_WARNING printk(KERN_WARNING
...@@ -1374,8 +1391,36 @@ cifs_parse_mount_options(char *options, const char *devname, ...@@ -1374,8 +1391,36 @@ cifs_parse_mount_options(char *options, const char *devname,
return 0; return 0;
} }
/** Returns true if srcaddr isn't specified and rhs isn't
* specified, or if srcaddr is specified and
* matches the IP address of the rhs argument.
*/
static bool
srcip_matches(struct sockaddr *srcaddr, struct sockaddr *rhs)
{
switch (srcaddr->sa_family) {
case AF_UNSPEC:
return (rhs->sa_family == AF_UNSPEC);
case AF_INET: {
struct sockaddr_in *saddr4 = (struct sockaddr_in *)srcaddr;
struct sockaddr_in *vaddr4 = (struct sockaddr_in *)rhs;
return (saddr4->sin_addr.s_addr == vaddr4->sin_addr.s_addr);
}
case AF_INET6: {
struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)srcaddr;
struct sockaddr_in6 *vaddr6 = (struct sockaddr_in6 *)&rhs;
return ipv6_addr_equal(&saddr6->sin6_addr, &vaddr6->sin6_addr);
}
default:
WARN_ON(1);
return false; /* don't expect to be here */
}
}
static bool static bool
match_address(struct TCP_Server_Info *server, struct sockaddr *addr) match_address(struct TCP_Server_Info *server, struct sockaddr *addr,
struct sockaddr *srcaddr)
{ {
struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; struct sockaddr_in *addr4 = (struct sockaddr_in *)addr;
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
...@@ -1402,6 +1447,9 @@ match_address(struct TCP_Server_Info *server, struct sockaddr *addr) ...@@ -1402,6 +1447,9 @@ match_address(struct TCP_Server_Info *server, struct sockaddr *addr)
break; break;
} }
if (!srcip_matches(srcaddr, (struct sockaddr *)&server->srcaddr))
return false;
return true; return true;
} }
...@@ -1469,7 +1517,8 @@ cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol) ...@@ -1469,7 +1517,8 @@ cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol)
if (server->tcpStatus == CifsNew) if (server->tcpStatus == CifsNew)
continue; continue;
if (!match_address(server, addr)) if (!match_address(server, addr,
(struct sockaddr *)&vol->srcaddr))
continue; continue;
if (!match_security(server, vol)) if (!match_security(server, vol))
...@@ -1584,6 +1633,8 @@ cifs_get_tcp_session(struct smb_vol *volume_info) ...@@ -1584,6 +1633,8 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
* no need to spinlock this init of tcpStatus or srv_count * no need to spinlock this init of tcpStatus or srv_count
*/ */
tcp_ses->tcpStatus = CifsNew; tcp_ses->tcpStatus = CifsNew;
memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr,
sizeof(tcp_ses->srcaddr));
++tcp_ses->srv_count; ++tcp_ses->srv_count;
if (addr.ss_family == AF_INET6) { if (addr.ss_family == AF_INET6) {
...@@ -1999,6 +2050,33 @@ static void rfc1002mangle(char *target, char *source, unsigned int length) ...@@ -1999,6 +2050,33 @@ static void rfc1002mangle(char *target, char *source, unsigned int length)
} }
static int
bind_socket(struct TCP_Server_Info *server)
{
int rc = 0;
if (server->srcaddr.ss_family != AF_UNSPEC) {
/* Bind to the specified local IP address */
struct socket *socket = server->ssocket;
rc = socket->ops->bind(socket,
(struct sockaddr *) &server->srcaddr,
sizeof(server->srcaddr));
if (rc < 0) {
struct sockaddr_in *saddr4;
struct sockaddr_in6 *saddr6;
saddr4 = (struct sockaddr_in *)&server->srcaddr;
saddr6 = (struct sockaddr_in6 *)&server->srcaddr;
if (saddr6->sin6_family == AF_INET6)
cERROR(1, "cifs: "
"Failed to bind to: %pI6c, error: %d\n",
&saddr6->sin6_addr, rc);
else
cERROR(1, "cifs: "
"Failed to bind to: %pI4, error: %d\n",
&saddr4->sin_addr.s_addr, rc);
}
}
return rc;
}
static int static int
ipv4_connect(struct TCP_Server_Info *server) ipv4_connect(struct TCP_Server_Info *server)
...@@ -2024,6 +2102,10 @@ ipv4_connect(struct TCP_Server_Info *server) ...@@ -2024,6 +2102,10 @@ ipv4_connect(struct TCP_Server_Info *server)
cifs_reclassify_socket4(socket); cifs_reclassify_socket4(socket);
} }
rc = bind_socket(server);
if (rc < 0)
return rc;
/* user overrode default port */ /* user overrode default port */
if (server->addr.sockAddr.sin_port) { if (server->addr.sockAddr.sin_port) {
rc = socket->ops->connect(socket, (struct sockaddr *) rc = socket->ops->connect(socket, (struct sockaddr *)
...@@ -2186,6 +2268,10 @@ ipv6_connect(struct TCP_Server_Info *server) ...@@ -2186,6 +2268,10 @@ ipv6_connect(struct TCP_Server_Info *server)
cifs_reclassify_socket6(socket); cifs_reclassify_socket6(socket);
} }
rc = bind_socket(server);
if (rc < 0)
return rc;
/* user overrode default port */ /* user overrode default port */
if (server->addr.sockAddr6.sin6_port) { if (server->addr.sockAddr6.sin6_port) {
rc = socket->ops->connect(socket, rc = socket->ops->connect(socket,
......
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