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.
This commit is contained in:
Willy Tarreau 2020-08-28 16:49:41 +02:00
parent a6473ede5c
commit 2d34a710b1
4 changed files with 72 additions and 91 deletions

View File

@ -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 @@ extern struct xfer_sock_list *xfer_sock_list;
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 */

View File

@ -570,65 +570,6 @@ int tcp_connect_server(struct connection *conn, int flags)
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 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
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

View File

@ -83,36 +83,6 @@ INITCALL1(STG_REGISTER, protocol_register, &proto_unix);
* 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 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle
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));

View File

@ -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 @@ int sock_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir)
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