diff --git a/src/haproxy.c b/src/haproxy.c index e891eb058..028ebcda0 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -1298,6 +1298,26 @@ static int get_old_sockets(const char *unixsocket) sizeof(xfer_sock->options)); curoff += sizeof(xfer_sock->options); + /* keep only the v6only flag depending on what's currently + * active on the socket, and always drop the v4v6 one. + */ + { + int val = 0; +#if defined(IPV6_V6ONLY) + socklen_t len = sizeof(val); + + if (xfer_sock->addr.ss_family == AF_INET6 && + getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, &len) != 0) + val = 0; +#endif + + if (val) + xfer_sock->options |= LI_O_V6ONLY; + else + xfer_sock->options &= ~LI_O_V6ONLY; + xfer_sock->options &= ~LI_O_V4V6; + } + xfer_sock->fd = fd; if (xfer_sock_list) xfer_sock_list->prev = xfer_sock; diff --git a/src/proto_tcp.c b/src/proto_tcp.c index eb0e6689f..3d56cf60b 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -115,6 +115,11 @@ static THREAD_LOCAL int default_tcp_maxseg = -1; static THREAD_LOCAL int default_tcp6_maxseg = -1; #endif +/* determine if the operating system uses IPV6_V6ONLY by default. + * -1=unknown, 0=no, 1=yes. + */ +static int v6only_default = -1; + /* Binds ipv4/ipv6 address to socket , unless is set, in which * case we try to bind . is a 2-bit field consisting of : * - 0 : ignore remote address (may even be a NULL pointer) @@ -676,22 +681,60 @@ static int compare_sockaddr(struct sockaddr_storage *a, struct sockaddr_storage } -#define LI_MANDATORY_FLAGS (LI_O_FOREIGN | LI_O_V6ONLY | LI_O_V4V6) +/* sets the v6only_default flag according to the OS' default settings; for + * simplicity it's set to zero if not supported. + */ +static inline void tcp_test_v6only_default() +{ + if (v6only_default == -1) { +#if defined(IPV6_V6ONLY) + int fd, val; + socklen_t len = sizeof(val); + + v6only_default = 0; + + fd = socket(AF_INET6, SOCK_STREAM, 0); + if (fd < 0) + return; + + if (getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, &len) == 0 && val > 0) + v6only_default = 1; + + close(fd); +#else + v6only_default = 0; +#endif + } +} + +#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; + tcp_test_v6only_default(); + + /* 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) || (v6only_default && !(options & LI_O_V4V6))) + options |= LI_O_V6ONLY; + else if ((options & LI_O_V4V6) || !v6only_default) + options &= ~LI_O_V6ONLY; + options &= ~LI_O_V4V6; + while (xfer_sock) { if (!compare_sockaddr(&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 ((l->options & LI_MANDATORY_FLAGS) == - (xfer_sock->options & LI_MANDATORY_FLAGS)) { + if (options == (xfer_sock->options & LI_MANDATORY_FLAGS)) { if ((xfer_sock->namespace == NULL && l->netns == NULL) #ifdef USE_NS