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) {