]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: dns: don't call connect to dest socket for AF_INET*
authorValentine Krasnobaeva <vkrasnobaeva@haproxy.com>
Wed, 13 Aug 2025 13:48:47 +0000 (15:48 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 19 Aug 2025 09:26:02 +0000 (11:26 +0200)
When we perform connect call for a datagram socket, used to send DNS requests,
we set for it the default destination address to some given nameserver. Then we
simply use send(), as the destination address is already set. In some usecases
described in GitHub issues #3001 and #2654, this approach becames inefficient,
nameservers change its IP addresses dynamically, this triggers DNS resolution
errors.

To fix this, let's perform the bind() on the wildcard address for the datagram
AF_INET* client socket. Like this we will allocate a port for it. Then let's
use sendto() instead of send().

If the nameserver is local and is listening on the UNIX domain socket, we
continue to use the existed approach (connect() and then send()).

This fixes issues #3001 and #2654.
This may be backported in all stable versions.

src/dns.c

index 054f90a0c2db4a77a3ca54bad60208b767aad524..e02eb1bdc295f93b4669235cafe46d6f5258081d 100644 (file)
--- a/src/dns.c
+++ b/src/dns.c
@@ -65,12 +65,52 @@ static int dns_connect_nameserver(struct dns_nameserver *ns)
                         ns->counters->pid, ns->id);
                return -1;
        }
-       if (connect(fd, (struct sockaddr*)&dgram->addr.to, get_addr_len(&dgram->addr.to)) == -1) {
-               send_log(NULL, LOG_WARNING,
-                        "DNS : section '%s': can't connect socket for nameserver '%s'.\n",
-                        ns->counters->id, ns->id);
-               close(fd);
-               return -1;
+
+       switch (proto->fam->sock_domain) {
+       case AF_INET: {
+               struct sockaddr_in address = {
+                       .sin_family = AF_INET,
+                       .sin_port = 0,
+                       .sin_addr = { .s_addr = INADDR_ANY }
+               };
+               if (bind(fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
+                       send_log(NULL, LOG_WARNING,
+                                "DNS : section '%s': can't bind socket for nameserver '%s' on 0.0.0.0:0.\n",
+                                ns->counters->pid, ns->id);
+                       return -1;
+               }
+               break;
+       }
+       case AF_INET6: {
+               struct sockaddr_in6 address6 = {
+                       .sin6_family = AF_INET6,
+                       .sin6_port = 0,
+                       .sin6_addr = in6addr_any,
+                       .sin6_flowinfo = 0,
+                       .sin6_scope_id = 0
+               };
+               if (bind(fd, (struct sockaddr *)&address6, sizeof(address6)) < 0) {
+                       send_log(NULL, LOG_WARNING,
+                                "DNS : section '%s': can't bind socket for nameserver '%s' on :::0.\n",
+                                ns->counters->pid, ns->id);
+                       return -1;
+               }
+               break;
+       }
+       case AF_UNIX:
+               /* if IPC is used via local domain sockets, we don't expect that
+                * the path to DNS server socket can change dynamically.
+                */
+               if (connect(fd, (struct sockaddr*)&dgram->addr.to, get_addr_len(&dgram->addr.to)) == -1) {
+                       send_log(NULL, LOG_WARNING,
+                                "DNS : section '%s': can't connect socket for nameserver '%s'.\n",
+                                ns->counters->id, ns->id);
+                       close(fd);
+                       return -1;
+               }
+               break;
+       default:
+               BUG_ON(1, "DNS: Unsupported address family.");
        }
 
        /* Make the socket non blocking */
@@ -106,7 +146,17 @@ int dns_send_nameserver(struct dns_nameserver *ns, void *buf, size_t len)
                        fd = dgram->t.sock.fd;
                }
 
-               ret = send(fd, buf, len, 0);
+               if (dgram->addr.to.ss_family == AF_UNIX) {
+                       /* we do connect for AF_UNIX sockets and from the man
+                        * sendto: "If sendto() is  used on a connection-mode
+                        * (SOCK_STREAM, SOCK_SEQPACKET) socket, the arguments
+                        * dest_addr and addrlen are ignored (and the error
+                        * EISCONN may be returned when they are not NULL and 0)..."
+                        */
+                       ret = send(fd, buf, len, 0);
+               } else
+                       ret = sendto(fd, buf, len, 0, (struct sockaddr*)&dgram->addr.to, get_addr_len(&dgram->addr.to));
+
                if (ret < 0) {
                        if (errno == EAGAIN || errno == EWOULDBLOCK) {
                                struct ist myist;