From d53f96b3f07a2e51b0a86355c190962ec6c677bc Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Wed, 4 Feb 2009 18:46:54 +0100 Subject: [PATCH] [MEDIUM] add support for source interface binding Specifying "interface " 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. --- doc/configuration.txt | 8 ++++ include/types/proxy.h | 2 + src/backend.c | 5 +++ src/cfgparse.c | 95 +++++++++++++++++++++++++++++-------------- src/checks.c | 6 +++ 5 files changed, 85 insertions(+), 31 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 59a832286..a804d23bd 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2839,6 +2839,7 @@ server
[:port] [param*] source [:] [usesrc { [:] | client | clientip } ] +source [:] [interface ] 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 [:] [usesrc { [:] | client | clientip } ] The default value of zero means the system will select a free port. + 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 diff --git a/include/types/proxy.h b/include/types/proxy.h index 5d0869cb5..cc5069007 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -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 */ diff --git a/src/backend.c b/src/backend.c index be3dcf6a8..a6a0351af 100644 --- a/src/backend.c +++ b/src/backend.c @@ -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) { diff --git a/src/cfgparse.c b/src/cfgparse.c index 10f7b2acc..6507bff09 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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 [:], and optional '%s' as argument.\n", - file, linenum, "source", "usesrc"); -#else - Alert("parsing [%s:%d] : '%s' expects [:] as argument.\n", - file, linenum, "source"); -#endif + Alert("parsing [%s:%d] : '%s' expects [:], and optionally '%s' , and '%s' .\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 [:], 'client', or 'clientip' as argument.\n", - file, linenum, "usesrc"); - return -1; - } + if (!*args[cur_arg + 1]) { + Alert("parsing [%s:%d] : '%s' expects [:], '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 */ diff --git a/src/checks.c b/src/checks.c index d6ce3355e..aad164389 100644 --- a/src/checks.c +++ b/src/checks.c @@ -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) {