[MEDIUM] add support for source interface binding

Specifying "interface <name>" after the "source" statement allows
one to bind to a specific interface for proxy<->server traffic.

This makes it possible to use multiple links to reach multiple
servers, and to force traffic to pass via an interface different
from the one the system would have chosen based on the routing
table.
This commit is contained in:
Willy Tarreau 2009-02-04 18:46:54 +01:00
parent 4e30ed73f4
commit d53f96b3f0
5 changed files with 85 additions and 31 deletions

View File

@ -2839,6 +2839,7 @@ server <name> <address>[:port] [param*]
source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | client | clientip } ]
source <addr>[:<port>] [interface <name>]
Set the source address for outgoing connections
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
@ -2864,6 +2865,13 @@ source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | client | clientip } ]
The default value of zero means the system will select a free
port.
<name> is an optional interface name to which to bind to for outgoing
traffic. On systems supporting this features (currently, only
Linux), this allows one to bind all traffic to the server to
this interface even if it is not the one the system would select
based on routing tables. This should be used with extreme care.
Note that using this option requires root privileges.
The "source" keyword is useful in complex environments where a specific
address only is allowed to connect to the servers. It may be needed when a
private address must be used through a public gateway for instance, and it is

View File

@ -233,6 +233,8 @@ struct proxy {
#if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY)
struct sockaddr_in tproxy_addr; /* non-local address we want to bind to for connect() */
#endif
int iface_len; /* bind interface name length */
char *iface_name; /* bind interface name or NULL */
struct proxy *next;
struct logsrv logsrv1, logsrv2; /* 2 syslog servers */
signed char logfac1, logfac2; /* log facility for both servers. -1 = disabled */

View File

@ -1757,6 +1757,11 @@ int connect_server(struct session *s)
remote = (struct sockaddr_in *)&s->cli_addr;
break;
}
#endif
#ifdef SO_BINDTODEVICE
/* Note: this might fail if not CAP_NET_RAW */
if (s->be->iface_name)
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, s->be->iface_name, s->be->iface_len);
#endif
ret = tcpv4_bind_socket(fd, flags, &s->be->source_addr, remote);
if (ret) {

View File

@ -705,6 +705,10 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
if (defproxy.url_param_name)
curproxy->url_param_name = strdup(defproxy.url_param_name);
curproxy->url_param_len = defproxy.url_param_len;
if (defproxy.iface_name)
curproxy->iface_name = strdup(defproxy.iface_name);
curproxy->iface_len = defproxy.iface_len;
}
if (curproxy->cap & PR_CAP_RS) {
@ -761,6 +765,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
free(defproxy.capture_name);
free(defproxy.monitor_uri);
free(defproxy.defbe.name);
free(defproxy.iface_name);
free(defproxy.fwdfor_hdr_name);
defproxy.fwdfor_hdr_len = 0;
@ -2123,54 +2128,82 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
}
}
else if (!strcmp(args[0], "source")) { /* address to which we bind when connecting */
int cur_arg;
if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
return 0;
if (!*args[1]) {
#if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY)
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], and optional '%s' <addr> as argument.\n",
file, linenum, "source", "usesrc");
#else
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>] as argument.\n",
file, linenum, "source");
#endif
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], and optionally '%s' <addr>, and '%s' <name>.\n",
file, linenum, "source", "usesrc", "interface");
return -1;
}
curproxy->source_addr = *str2sa(args[1]);
curproxy->options |= PR_O_BIND_SRC;
if (!strcmp(args[2], "usesrc")) { /* address to use outside */
cur_arg = 2;
while (*(args[cur_arg])) {
if (!strcmp(args[cur_arg], "usesrc")) { /* address to use outside */
#if defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY)
#if !defined(CONFIG_HAP_LINUX_TPROXY)
if (curproxy->source_addr.sin_addr.s_addr == INADDR_ANY) {
Alert("parsing [%s:%d] : '%s' requires an explicit 'source' address.\n",
file, linenum, "usesrc");
return -1;
}
if (curproxy->source_addr.sin_addr.s_addr == INADDR_ANY) {
Alert("parsing [%s:%d] : '%s' requires an explicit 'source' address.\n",
file, linenum, "usesrc");
return -1;
}
#endif
if (!*args[3]) {
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], 'client', or 'clientip' as argument.\n",
file, linenum, "usesrc");
return -1;
}
if (!*args[cur_arg + 1]) {
Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], 'client', or 'clientip' as argument.\n",
file, linenum, "usesrc");
return -1;
}
if (!strcmp(args[3], "client")) {
curproxy->options |= PR_O_TPXY_CLI;
} else if (!strcmp(args[3], "clientip")) {
curproxy->options |= PR_O_TPXY_CIP;
} else {
curproxy->options |= PR_O_TPXY_ADDR;
curproxy->tproxy_addr = *str2sa(args[3]);
}
global.last_checks |= LSTCHK_NETADM;
if (!strcmp(args[cur_arg + 1], "client")) {
curproxy->options |= PR_O_TPXY_CLI;
} else if (!strcmp(args[cur_arg + 1], "clientip")) {
curproxy->options |= PR_O_TPXY_CIP;
} else {
curproxy->options |= PR_O_TPXY_ADDR;
curproxy->tproxy_addr = *str2sa(args[cur_arg + 1]);
}
global.last_checks |= LSTCHK_NETADM;
#if !defined(CONFIG_HAP_LINUX_TPROXY)
global.last_checks |= LSTCHK_CTTPROXY;
global.last_checks |= LSTCHK_CTTPROXY;
#endif
#else /* no TPROXY support */
Alert("parsing [%s:%d] : '%s' not allowed here because support for TPROXY was not compiled in.\n",
file, linenum, "usesrc");
return -1;
Alert("parsing [%s:%d] : '%s' not allowed here because support for TPROXY was not compiled in.\n",
file, linenum, "usesrc");
return -1;
#endif
cur_arg += 2;
continue;
}
if (!strcmp(args[cur_arg], "interface")) { /* specifically bind to this interface */
#ifdef SO_BINDTODEVICE
if (!*args[cur_arg + 1]) {
Alert("parsing [%s:%d] : '%s' : missing interface name.\n",
file, linenum, args[0]);
return -1;
}
if (curproxy->iface_name)
free(curproxy->iface_name);
curproxy->iface_name = strdup(args[cur_arg + 1]);
curproxy->iface_len = strlen(curproxy->iface_name);
global.last_checks |= LSTCHK_NETADM;
#else
Alert("parsing [%s:%d] : '%s' : '%s' option not implemented.\n",
file, linenum, args[0], args[cur_arg]);
return -1;
#endif
cur_arg += 2;
continue;
}
Alert("parsing [%s:%d] : '%s' only supports optional keywords '%s' and '%s'.\n",
file, linenum, args[0], "inteface", "usesrc");
return -1;
}
}
else if (!strcmp(args[0], "usesrc")) { /* address to use outside: needs "source" first */

View File

@ -614,6 +614,12 @@ void process_chk(struct task *t, int *next)
remote = (struct sockaddr_in *)&s->proxy->tproxy_addr;
flags = 3;
}
#endif
#ifdef SO_BINDTODEVICE
/* Note: this might fail if not CAP_NET_RAW */
if (s->proxy->iface_name)
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
s->proxy->iface_name, s->proxy->iface_len);
#endif
ret = tcpv4_bind_socket(fd, flags, &s->proxy->source_addr, remote);
if (ret) {