[MEDIUM] add support for source interface binding at the server level

Add support for "interface <name>" after the "source" statement on
the server line.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index a804d23..fd62ef1 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4294,6 +4294,7 @@
   seen as failed.
 
 source <addr>[:<port>] [usesrc { <addr2>[:<port2>] | client | clientip } ]
+source <addr>[:<port>] [interface <name>] ...
   The "source" parameter sets the source address which will be used when
   connecting to the server. It follows the exact same parameters and principle
   as the backend "source" keyword, except that it only applies to the server
diff --git a/include/types/server.h b/include/types/server.h
index 09eb1ba..5c0186a 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -91,6 +91,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 server *tracknext, *tracked;	/* next server in a tracking list, tracked server */
 	char *trackit;				/* temporary variable to make assignment deferrable */
diff --git a/src/backend.c b/src/backend.c
index a6a0351..e1911c5 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1719,6 +1719,11 @@
 			break;
 		}
 #endif
+#ifdef SO_BINDTODEVICE
+		/* Note: this might fail if not CAP_NET_RAW */
+		if (s->srv->iface_name)
+			setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, s->srv->iface_name, s->srv->iface_len);
+#endif
 		ret = tcpv4_bind_socket(fd, flags, &s->srv->source_addr, remote);
 		if (ret) {
 			close(fd);
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 6507bff..5136e94 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1976,39 +1976,66 @@
 				newsrv->state |= SRV_BIND_SRC;
 				newsrv->source_addr = *str2sa(args[cur_arg + 1]);
 				cur_arg += 2;
-				if (!strcmp(args[cur_arg], "usesrc")) {  /* address to use outside */
+				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 (newsrv->source_addr.sin_addr.s_addr == INADDR_ANY) {
-						Alert("parsing [%s:%d] : '%s' requires an explicit '%s' address.\n",
-						      file, linenum, "usesrc", "source");
-						return -1;
-					}
+						if (newsrv->source_addr.sin_addr.s_addr == INADDR_ANY) {
+							Alert("parsing [%s:%d] : '%s' requires an explicit '%s' address.\n",
+							      file, linenum, "usesrc", "source");
+							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")) {
-						newsrv->state |= SRV_TPROXY_CLI;
-					} else if (!strcmp(args[cur_arg + 1], "clientip")) {
-						newsrv->state |= SRV_TPROXY_CIP;
-					} else {
-						newsrv->state |= SRV_TPROXY_ADDR;
-						newsrv->tproxy_addr = *str2sa(args[cur_arg + 1]);
-					}
-					global.last_checks |= LSTCHK_NETADM;
+						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")) {
+							newsrv->state |= SRV_TPROXY_CLI;
+						} else if (!strcmp(args[cur_arg + 1], "clientip")) {
+							newsrv->state |= SRV_TPROXY_CIP;
+						} else {
+							newsrv->state |= SRV_TPROXY_ADDR;
+							newsrv->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
-					cur_arg += 2;
+						cur_arg += 2;
+						continue;
 #else	/* no TPROXY support */
-					Alert("parsing [%s:%d] : '%s' not allowed here because support for TPROXY was not compiled in.\n",
+						Alert("parsing [%s:%d] : '%s' not allowed here because support for TPROXY was not compiled in.\n",
 						      file, linenum, "usesrc");
 						return -1;
+#endif /* defined(CONFIG_HAP_CTTPROXY) || defined(CONFIG_HAP_LINUX_TPROXY) */
+					} /* "usesrc" */
+
+					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 (newsrv->iface_name)
+							free(newsrv->iface_name);
+
+						newsrv->iface_name = strdup(args[cur_arg + 1]);
+						newsrv->iface_len  = strlen(newsrv->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;
+					}
+					/* this keyword in not an option of "source" */
+					break;
+				} /* while */
 			}
 			else if (!strcmp(args[cur_arg], "usesrc")) {  /* address to use outside: needs "source" first */
 				Alert("parsing [%s:%d] : '%s' only allowed after a '%s' statement.\n",
diff --git a/src/checks.c b/src/checks.c
index aad1643..eb316cb 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -590,6 +590,12 @@
 						flags  = 3;
 					}
 #endif
+#ifdef SO_BINDTODEVICE
+					/* Note: this might fail if not CAP_NET_RAW */
+					if (s->iface_name)
+						setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
+							   s->iface_name, s->iface_len);
+#endif
 					ret = tcpv4_bind_socket(fd, flags, &s->source_addr, remote);
 					if (ret) {
 						s->result |= SRV_CHK_ERROR;