[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.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 59a8322..a804d23 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2839,6 +2839,7 @@
 
 
 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 @@
               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
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 5d0869c..cc50690 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -233,6 +233,8 @@
 #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 be3dcf6..a6a0351 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1758,6 +1758,11 @@
 			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) {
 			close(fd);
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 10f7b2a..6507bff 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -705,6 +705,10 @@
 			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 @@
 		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 @@
 		}
 	}
 	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[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[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;
 #endif
-			if (!*args[3]) {
-				Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], 'client', or 'clientip' as argument.\n",
+#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;
+#endif
+				cur_arg += 2;
+				continue;
 			}
 
-			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 !defined(CONFIG_HAP_LINUX_TPROXY)
-			global.last_checks |= LSTCHK_CTTPROXY;
+			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
-#else	/* no TPROXY support */
-			Alert("parsing [%s:%d] : '%s' not allowed here because support for TPROXY was not compiled in.\n",
-			      file, linenum, "usesrc");
+				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;
-#endif
 		}
 	}
 	else if (!strcmp(args[0], "usesrc")) {  /* address to use outside: needs "source" first */
diff --git a/src/checks.c b/src/checks.c
index d6ce335..aad1643 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -615,6 +615,12 @@
 						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) {
 						s->result |= SRV_CHK_ERROR;