MINOR: sock: implement sock_find_compatible_fd()

This is essentially a merge from tcp_find_compatible_fd() and
uxst_find_compatible_fd() that relies on a listener's address and
compare function and still checks for other variations. For AF_INET6
it compares a few of the listener's bind options. A minor change for
UNIX sockets is that transparent mode, interface and namespace used
to be ignored when trying to pick a previous socket while now if they
are changed, the socket will not be reused. This could be refined but
it's still better this way as there is no more risk of using a
differently bound socket by accident.

Eventually we should not pass a listener there but a set of binding
parameters (address, interface, namespace etc...) which ultimately will
be grouped into a receiver. For now this still doesn't exist so let's
stick to the listener to break dependencies in the rest of the code.
diff --git a/include/haproxy/sock.h b/include/haproxy/sock.h
index b3690d7..e405bb3 100644
--- a/include/haproxy/sock.h
+++ b/include/haproxy/sock.h
@@ -27,6 +27,7 @@
 
 #include <haproxy/api.h>
 #include <haproxy/connection-t.h>
+#include <haproxy/listener-t.h>
 #include <haproxy/sock-t.h>
 
 extern struct xfer_sock_list *xfer_sock_list;
@@ -34,6 +35,7 @@
 int sock_create_server_socket(struct connection *conn);
 int sock_get_src(int fd, struct sockaddr *sa, socklen_t salen, int dir);
 int sock_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir);
+int sock_find_compatible_fd(const struct listener *l);
 
 #endif /* _HAPROXY_SOCK_H */
 
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 44b11e4..4ae4a48 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -570,65 +570,6 @@
 	return SF_ERR_NONE;  /* connection is OK */
 }
 
-#define LI_MANDATORY_FLAGS	(LI_O_FOREIGN | LI_O_V6ONLY)
-/* When binding the listeners, check if a socket has been sent to us by the
- * previous process that we could reuse, instead of creating a new one.
- */
-static int tcp_find_compatible_fd(struct listener *l)
-{
-	struct xfer_sock_list *xfer_sock = xfer_sock_list;
-	int options = l->options & (LI_MANDATORY_FLAGS | LI_O_V4V6);
-	int ret = -1;
-
-	/* Prepare to match the v6only option against what we really want. Note
-	 * that sadly the two options are not exclusive to each other and that
-	 * v6only is stronger than v4v6.
-	 */
-	if ((options & LI_O_V6ONLY) || (sock_inet6_v6only_default && !(options & LI_O_V4V6)))
-		options |= LI_O_V6ONLY;
-	else if ((options & LI_O_V4V6) || !sock_inet6_v6only_default)
-		options &= ~LI_O_V6ONLY;
-	options &= ~LI_O_V4V6;
-
-	while (xfer_sock) {
-		if (!l->proto->addrcmp(&xfer_sock->addr, &l->addr)) {
-			if ((l->interface == NULL && xfer_sock->iface == NULL) ||
-			    (l->interface != NULL && xfer_sock->iface != NULL &&
-			     !strcmp(l->interface, xfer_sock->iface))) {
-				if (options == (xfer_sock->options & LI_MANDATORY_FLAGS)) {
-					if ((xfer_sock->namespace == NULL &&
-					    l->netns == NULL)
-#ifdef USE_NS
-					    || (xfer_sock->namespace != NULL &&
-					    l->netns != NULL &&
-					    !strcmp(xfer_sock->namespace,
-					    l->netns->node.key))
-#endif
-					   ) {
-						break;
-					}
-
-				}
-			}
-		}
-		xfer_sock = xfer_sock->next;
-	}
-	if (xfer_sock != NULL) {
-		ret = xfer_sock->fd;
-		if (xfer_sock == xfer_sock_list)
-			xfer_sock_list = xfer_sock->next;
-		if (xfer_sock->prev)
-			xfer_sock->prev->next = xfer_sock->next;
-		if (xfer_sock->next)
-			xfer_sock->next->prev = xfer_sock->prev;
-		free(xfer_sock->iface);
-		free(xfer_sock->namespace);
-		free(xfer_sock);
-	}
-	return ret;
-}
-#undef L1_MANDATORY_FLAGS
-
 /* This function tries to bind a TCPv4/v6 listener. It may return a warning or
  * an error message in <errmsg> if the message is at most <errlen> bytes long
  * (including '\0'). Note that <errmsg> may be NULL if <errlen> is also zero.
@@ -660,7 +601,7 @@
 	err = ERR_NONE;
 
 	if (listener->fd == -1)
-		listener->fd = tcp_find_compatible_fd(listener);
+		listener->fd = sock_find_compatible_fd(listener);
 
 	/* if the listener already has an fd assigned, then we were offered the
 	 * fd by an external process (most likely the parent), and we don't want
diff --git a/src/proto_uxst.c b/src/proto_uxst.c
index f0cf999..2c3e202 100644
--- a/src/proto_uxst.c
+++ b/src/proto_uxst.c
@@ -83,36 +83,6 @@
  * 2) listener-oriented functions
  ********************************/
 
-
-static int uxst_find_compatible_fd(struct listener *l)
-{
-	struct xfer_sock_list *xfer_sock = xfer_sock_list;
-	int ret = -1;
-
-	while (xfer_sock) {
-		/*
-		 * The bound socket's path as returned by getsockaddr
-		 * will be the temporary name <sockname>.XXXXX.tmp,
-		 * so we can't just compare the two names
-		 */
-		if (!l->proto->addrcmp(&xfer_sock->addr, &l->addr))
-				break;
-		xfer_sock = xfer_sock->next;
-	}
-	if (xfer_sock != NULL) {
-		ret = xfer_sock->fd;
-		if (xfer_sock == xfer_sock_list)
-			xfer_sock_list = xfer_sock->next;
-		if (xfer_sock->prev)
-			xfer_sock->prev->next = xfer_sock->next;
-		if (xfer_sock->next)
-			xfer_sock->next->prev = xfer_sock->prev;
-		free(xfer_sock);
-	}
-	return ret;
-
-}
-
 /* This function creates a UNIX socket associated to the listener. It changes
  * the state from ASSIGNED to LISTEN. The socket is NOT enabled for polling.
  * The return value is composed from ERR_NONE, ERR_RETRYABLE and ERR_FATAL. It
@@ -144,7 +114,8 @@
 		return ERR_NONE; /* already bound */
 		
 	if (listener->fd == -1)
-		listener->fd = uxst_find_compatible_fd(listener);
+		listener->fd = sock_find_compatible_fd(listener);
+
 	path = ((struct sockaddr_un *)&listener->addr)->sun_path;
 
 	maxpathlen = MIN(MAXPATHLEN, sizeof(addr.sun_path));
diff --git a/src/sock.c b/src/sock.c
index 56a4a5c..5bb6e13 100644
--- a/src/sock.c
+++ b/src/sock.c
@@ -27,6 +27,7 @@
 #include <haproxy/listener-t.h>
 #include <haproxy/namespace.h>
 #include <haproxy/sock.h>
+#include <haproxy/sock_inet.h>
 #include <haproxy/tools.h>
 
 /* the list of remaining sockets transferred from an older process */
@@ -80,6 +81,72 @@
 		return getsockname(fd, sa, &salen);
 }
 
+/* When binding the listeners, check if a socket has been sent to us by the
+ * previous process that we could reuse, instead of creating a new one. Note
+ * that some address family-specific options are checked on the listener and
+ * on the socket. Typically for AF_INET and AF_INET6, we check for transparent
+ * mode, and for AF_INET6 we also check for "v4v6" or "v6only". The reused
+ * socket is automatically removed from the list so that it's not proposed
+ * anymore.
+ */
+int sock_find_compatible_fd(const struct listener *l)
+{
+	struct xfer_sock_list *xfer_sock = xfer_sock_list;
+	int options = l->options & (LI_O_FOREIGN | LI_O_V6ONLY | LI_O_V4V6);
+	int if_namelen = 0;
+	int ns_namelen = 0;
+	int ret = -1;
+
+	if (!l->proto->addrcmp)
+		return -1;
+
+	if (l->addr.ss_family == AF_INET6) {
+		/* Prepare to match the v6only option against what we really want. Note
+		 * that sadly the two options are not exclusive to each other and that
+		 * v6only is stronger than v4v6.
+		 */
+		if ((options & LI_O_V6ONLY) || (sock_inet6_v6only_default && !(options & LI_O_V4V6)))
+			options |= LI_O_V6ONLY;
+		else if ((options & LI_O_V4V6) || !sock_inet6_v6only_default)
+			options &= ~LI_O_V6ONLY;
+	}
+	options &= ~LI_O_V4V6;
+
+	if (l->interface)
+		if_namelen = strlen(l->interface);
+#ifdef USE_NS
+	if (l->netns)
+		ns_namelen = l->netns->name_len;
+#endif
+
+	while (xfer_sock) {
+		if (((options ^ xfer_sock->options) & (LI_O_FOREIGN | LI_O_V6ONLY)) == 0 &&
+		    (if_namelen == xfer_sock->if_namelen) &&
+		    (ns_namelen == xfer_sock->ns_namelen) &&
+		    (!if_namelen || strcmp(l->interface, xfer_sock->iface) == 0) &&
+#ifdef USE_NS
+		    (!ns_namelen || strcmp(l->netns->node.key, xfer_sock->namespace) == 0) &&
+#endif
+		    l->proto->addrcmp(&xfer_sock->addr, &l->addr) == 0)
+			break;
+		xfer_sock = xfer_sock->next;
+	}
+
+	if (xfer_sock != NULL) {
+		ret = xfer_sock->fd;
+		if (xfer_sock == xfer_sock_list)
+			xfer_sock_list = xfer_sock->next;
+		if (xfer_sock->prev)
+			xfer_sock->prev->next = xfer_sock->next;
+		if (xfer_sock->next)
+			xfer_sock->next->prev = xfer_sock->prev;
+		free(xfer_sock->iface);
+		free(xfer_sock->namespace);
+		free(xfer_sock);
+	}
+	return ret;
+}
+
 /*
  * Local variables:
  *  c-indent-level: 8