mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-07 07:37:02 +02:00
BUG/MINOR: reload: detect the OS's v6only status before choosing an old socket
The v4v6 and v6only options are passed as data during the socket transfer between processes so that the new process can decide whether it wants to reuse a socket or not. But this actually misses one point: if no such option is set and the OS defaults are changed between the reloads, then the socket will still be inherited and will never be rebound using the new options. This can be seen by starting the following config: global stats socket /tmp/haproxy.sock level admin expose-fd listeners frontend testme bind :::1234 timeout client 2000ms Having a look at the OS settins, v6only is disabled: $ cat /proc/sys/net/ipv6/bindv6only 0 A first check shows it's indeed bound to v4 and v6: $ ss -an -6|grep 1234 tcp LISTEN 0 2035 *:1234 *:* Reloading the process doesn't change anything (which is expected). Now let's set bindv6only: $ echo 1 | sudo tee /proc/sys/net/ipv6/bindv6only 1 $ cat /proc/sys/net/ipv6/bindv6only 1 Reloading gives the same state: $ ss -an -6|grep 1234 tcp LISTEN 0 2035 *:1234 *:* However a restart properly shows a correct bind: $ ss -an -6|grep 1234 tcp LISTEN 0 2035 [::]:1234 [::]:* This one doesn't change once bindv6only is reset, for the same reason. This patch attacks this problem differently. Instead of passing the two options at once for each listening fd, it ignores the options and reads the socket's current state for the IPV6_V6ONLY flag and sets it only. Then before looking for a compatible FD, it checks the OS's defaults before deciding which of the v4v6 and v6only needs to be kept on the listener. And the selection is only made on this. First, it addresses this issue. Second, it also ensures that if such options are changed between reloads to identical states, the socket can still be inherited. For example adding v4v6 when bindv6only is not set will allow the socket to still be usable. Third, it avoids an undesired dependency on the LI_O_* bit values between processes across a reload (for these ones at least). It might make sense to backport this to some recent stable versions, but quite frankly the likelyhood that anyone will ever notice it is extremely faint.
This commit is contained in:
parent
bbb284d675
commit
bca5a4e0a8
@ -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;
|
||||
|
@ -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 <local> to socket <fd>, unless <flags> is set, in which
|
||||
* case we try to bind <remote>. <flags> 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
|
||||
|
Loading…
Reference in New Issue
Block a user