<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml" xmlns="http://www.w3.org/TR/REC-html40"><head><meta http-equiv=Content-Type content="text/html; charset=utf-8"><meta name=Generator content="Microsoft Word 15 (filtered medium)"><style><!--
/* Font Definitions */
@font-face
        {font-family:"Cambria Math";
        panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
        {font-family:DengXian;
        panose-1:2 1 6 0 3 1 1 1 1 1;}
@font-face
        {font-family:DengXian;
        panose-1:2 1 6 0 3 1 1 1 1 1;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
        {margin:0cm;
        text-align:justify;
        text-justify:inter-ideograph;
        font-size:10.5pt;
        font-family:DengXian;}
.MsoChpDefault
        {mso-style-type:export-only;}
/* Page Definitions */
@page WordSection1
        {size:612.0pt 792.0pt;
        margin:72.0pt 90.0pt 72.0pt 90.0pt;}
div.WordSection1
        {page:WordSection1;}
--></style></head><body lang=ZH-CN link=blue vlink="#954F72" style='word-wrap:break-word'><div class=WordSection1><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>Hi Simon,<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>I'd like to submit a patch to fix an EADDRINUSE issue when dnsmasq is<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>running with --bind-dynamic (OPT_CLEVERBIND) and a network interface is<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>removed and re-added while a TCP child process is still alive.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>== Bug description ==<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>When dnsmasq forks a child process to handle a TCP DNS query<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>(do_tcp_connection) or a DNSSEC UDP-to-TCP fallback (swap_to_tcp), the<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>child inherits all of the parent's listening socket file descriptors.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>These sockets are never used by the child, but the kernel keeps the<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>underlying socket objects alive as long as any process holds a reference.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>If a network interface goes away and comes back while the child is still<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>running (waiting for a slow upstream response, for example), the parent<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>calls release_listener() which closes its own fd, then<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>create_bound_listeners() tries to bind() a new socket to the same<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>address:port.  This bind() fails with EADDRINUSE because the child's<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>inherited copy of the old listening socket still exists in the kernel.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>SO_REUSEADDR does not help here — it only permits binding over sockets<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>in TIME_WAIT state, not over a socket that is still in LISTEN state<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>(held open by the child).<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>SO_REUSEPORT would allow the bind() to succeed, but it is not a suitable<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>fix: with SO_REUSEPORT the kernel load-balances incoming connections<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>across all sockets bound to the same address.  The child process never<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>calls accept() on the inherited listening socket, so any connection<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>dispatched there is silently black-holed until the child exits.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>== When does this happen ==<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>The race window is open for the entire lifetime of the TCP child process<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>(bounded by CHILD_LIFETIME).  It is most likely to trigger when:<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>  1. The upstream DNS server is slow or unresponsive, so the child<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>     process lives for a long time waiting for a reply.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>  2. The network environment is dynamic — interfaces are frequently<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>     removed and re-created (e.g. USB NIC unplug/replug, mobile data<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>     PDN reconnect, Android tethering toggle).<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>Both conditions together (slow upstream + dynamic interfaces) make this<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>reliably reproducible.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>== Fix ==<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>Close all inherited listener fds in the child immediately after fork,<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>in both do_tcp_connection() and swap_to_tcp().  This mirrors the<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>existing pattern of closing the netlink fd in the child.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>== How to test ==<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>A simple test using /proc/<pid>/fd to verify the fix:<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>  1. Start a fake upstream that accepts connections but never replies:<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>       nc -l -k -s 127.0.0.1 -p 15354 &<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>  2. Start dnsmasq with --bind-dynamic pointing to the fake upstream:<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>       dnsmasq --keep-in-foreground --no-resolv \<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>         --server=127.0.0.1#15354 \<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>         --listen-address=127.0.0.1 --port=15353 \<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>         --log-queries --log-facility=-<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>  3. Send a TCP query to trigger a fork:<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>       dig @127.0.0.1 -p 15353 +tcp example.com<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>  4. Find the child process and inspect its file descriptors:<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>       PARENT=$(pgrep -f "dnsmasq.*15353" | head -1)<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>       CHILD=$(pgrep -P $PARENT)<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>       ls -l /proc/$PARENT/fd | grep socket<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>       ls -l /proc/$CHILD/fd | grep socket<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>  Without the fix: the child holds copies of all parent listening<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>  socket inodes (EADDRINUSE will occur on rebind).<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>  With the fix: the child only holds its own connection socket and<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>  the pipe to parent — no listening socket inodes are shared.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>---<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'> src/dnsmasq.c | 21 ++++++++++++++++++++-<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'> 1 file changed, 20 insertions(+), 1 deletion(-)<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>diff --git a/src/dnsmasq.c b/src/dnsmasq.c<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>index adaa692..9af3e09 100644<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>--- a/src/dnsmasq.c<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+++ b/src/dnsmasq.c<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>@@ -2015,7 +2015,8 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>   pid_t p;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>   union mysockaddr tcp_addr;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>   socklen_t tcp_len = sizeof(union mysockaddr);<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>-  struct server *s;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+  struct server *s;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+  struct listener *l;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>   int flags, auth_dns = 0;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>   struct in_addr netmask;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>   int pipefd[2];<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>@@ -2186,6 +2187,16 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>       alarm(CHILD_LIFETIME);<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>       close(pipefd[0]); /* close read end in child. */<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>       daemon->pipe_to_parent = pipefd[1];<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+      /* Close inherited listening sockets in the child process.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+         These are not needed here and holding them prevents the<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+         parent from re-binding if an interface is removed and<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+         re-added (the child's copy causes EADDRINUSE). */<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+      for (l = daemon->listeners; l; l = l->next)<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+        {<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+          if (l->fd != -1) close(l->fd);<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+          if (l->tcpfd != -1) close(l->tcpfd);<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+        }<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>     }<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>   /* The connected socket inherits non-blocking<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>@@ -2232,6 +2243,7 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>      ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount)<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'> {<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>   struct server *s;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+  struct listener *l;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>   if (!option_bool(OPT_DEBUG))<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>     {<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>@@ -2304,6 +2316,13 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>     daemon->forward_to_tcp = forward;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>     daemon->header_to_tcp = header;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>     daemon->plen_to_tcp = *plen;<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+   /* Close inherited listening sockets in the child process. */<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+   for (l = daemon->listeners; l; l = l->next)<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+     {<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+       if (l->fd != -1) close(l->fd);<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+       if (l->tcpfd != -1) close(l->tcpfd);<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>+     }<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>   }<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>     }<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>--<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>2.25.1<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>The patch is attached below.<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>Thanks<o:p></o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'><o:p> </o:p></span></p><p class=MsoNormal><span lang=EN-US style='font-size:12.0pt'>Zhou Yuefu<o:p></o:p></span></p></div></body></html>