From a0358e5ddbc1ef3dec791f11f95f5dbe56087a5e Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Sat, 7 Jun 2014 13:38:48 +0100
Subject: [PATCH] Handle async notification of address changes using the event
 system.

---
 CHANGELOG     |  4 ++++
 src/bpf.c     |  6 +++---
 src/dhcp6.c   | 10 ----------
 src/dnsmasq.c | 13 +++++++++++--
 src/dnsmasq.h |  6 ++++--
 src/netlink.c | 39 ++++++++++-----------------------------
 src/network.c | 11 +++--------
 7 files changed, 35 insertions(+), 54 deletions(-)

--- a/CHANGELOG
+++ b/CHANGELOG
@@ -15,6 +15,10 @@ version 2.71
 	    regression introduced in 2.69. Thanks to James Hunt and
 	    the Ubuntu crowd for assistance in fixing this.
 
+	    Fix race condition which could lock up dnsmasq when an 
+	    interface goes down and up rapidly. Thanks to Conrad 
+	    Kostecki for helping to chase this down.
+	    
 
 version 2.70
             Fix crash, introduced in 2.69, on TCP request when dnsmasq
--- a/src/bpf.c
+++ b/src/bpf.c
@@ -376,7 +376,7 @@ void route_init(void)
     die(_("cannot create PF_ROUTE socket: %s"), NULL, EC_BADNET);
 }
 
-void route_sock(time_t now)
+void route_sock(void)
 {
   struct if_msghdr *msg;
   int rc = recv(daemon->routefd, daemon->packet, daemon->packet_buff_sz, 0);
@@ -401,7 +401,7 @@ void route_sock(time_t now)
    else if (msg->ifm_type == RTM_NEWADDR)
      {
        del_family = 0;
-       newaddress(now);
+       send_newaddr();
      }
    else if (msg->ifm_type == RTM_DELADDR)
      {
@@ -439,7 +439,7 @@ void route_sock(time_t now)
 	       of += sizeof(long) - (diff & (sizeof(long) - 1));
 	   }
        
-       newaddress(now);
+       send_newaddr();
      }
 }
 
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -917,10 +917,10 @@ int main (int argc, char **argv)
 
 #if defined(HAVE_LINUX_NETWORK)
       if (FD_ISSET(daemon->netlinkfd, &rset))
-	netlink_multicast(now);
+	netlink_multicast();
 #elif defined(HAVE_BSD_NETWORK)
       if (FD_ISSET(daemon->routefd, &rset))
-	route_sock(now);
+	route_sock();
 #endif
 
       /* Check for changes to resolv files once per second max. */
@@ -1037,6 +1037,11 @@ void send_alarm(time_t event, time_t now
     }
 }
 
+void send_newaddr(void)
+{
+  send_event(pipewrite, EVENT_NEWADDR, 0, NULL);
+}
+
 void send_event(int fd, int event, int data, char *msg)
 {
   struct event_desc ev;
@@ -1230,6 +1235,10 @@ static void async_event(int pipe, time_t
 	if (daemon->log_file != NULL)
 	  log_reopen(daemon->log_file);
 	break;
+
+      case EVENT_NEWADDR:
+	newaddress(now);
+	break;
 	
       case EVENT_TERM:
 	/* Knock all our children on the head. */
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -165,6 +165,7 @@ struct event_desc {
 #define EVENT_LUA_ERR   19
 #define EVENT_TFTP_ERR  20
 #define EVENT_INIT      21
+#define EVENT_NEWADDR   22
 
 /* Exit codes. */
 #define EC_GOOD        0
@@ -1289,6 +1290,7 @@ unsigned char *extended_hwaddr(int hwtyp
 int make_icmp_sock(void);
 int icmp_ping(struct in_addr addr);
 #endif
+void send_newaddr(void);
 void send_alarm(time_t event, time_t now);
 void send_event(int fd, int event, int data, char *msg);
 void clear_cache_and_reload(time_t now);
@@ -1297,7 +1299,7 @@ void poll_resolv(int force, int do_reloa
 /* netlink.c */
 #ifdef HAVE_LINUX_NETWORK
 void netlink_init(void);
-void netlink_multicast(time_t now);
+void netlink_multicast(void);
 #endif
 
 /* bpf.c */
@@ -1306,7 +1308,7 @@ void init_bpf(void);
 void send_via_bpf(struct dhcp_packet *mess, size_t len,
 		  struct in_addr iface_addr, struct ifreq *ifr);
 void route_init(void);
-void route_sock(time_t now);
+void route_sock(void);
 #endif
 
 /* bpf.c or netlink.c */
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -38,7 +38,7 @@
 static struct iovec iov;
 static u32 netlink_pid;
 
-static int nl_async(struct nlmsghdr *h);
+static void nl_async(struct nlmsghdr *h);
 
 void netlink_init(void)
 {
@@ -142,7 +142,7 @@ int iface_enumerate(int family, void *pa
   struct nlmsghdr *h;
   ssize_t len;
   static unsigned int seq = 0;
-  int callback_ok = 1, newaddr = 0;
+  int callback_ok = 1;
 
   struct {
     struct nlmsghdr nlh;
@@ -191,21 +191,10 @@ int iface_enumerate(int family, void *pa
 	if (h->nlmsg_seq != seq || h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR)
 	  {
 	    /* May be multicast arriving async */
-	    if (nl_async(h))
-	      {
-		newaddr = 1; 
-		enumerate_interfaces(1); /* reset */
-	      }
+	    nl_async(h);
 	  }
 	else if (h->nlmsg_type == NLMSG_DONE)
-	  {
-	    /* handle async new interface address arrivals, these have to be done
-	       after we complete as we're not re-entrant */
-	    if (newaddr) 
-	      newaddress(dnsmasq_time());
-		
-	    return callback_ok;
-	  }
+	  return callback_ok;
 	else if (h->nlmsg_type == RTM_NEWADDR && family != AF_UNSPEC && family != AF_LOCAL)
 	  {
 	    struct ifaddrmsg *ifa = NLMSG_DATA(h);  
@@ -330,11 +319,11 @@ int iface_enumerate(int family, void *pa
     }
 }
 
-void netlink_multicast(time_t now)
+void netlink_multicast(void)
 {
   ssize_t len;
   struct nlmsghdr *h;
-  int flags, newaddr = 0;
+  int flags;
   
   /* don't risk blocking reading netlink messages here. */
   if ((flags = fcntl(daemon->netlinkfd, F_GETFL)) == -1 ||
@@ -343,24 +332,19 @@ void netlink_multicast(time_t now)
   
   if ((len = netlink_recv()) != -1)
     for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len))
-      if (nl_async(h))
-	newaddr = 1;
+      nl_async(h);
   
   /* restore non-blocking status */
   fcntl(daemon->netlinkfd, F_SETFL, flags);
-  
-  if (newaddr) 
-    newaddress(now);
 }
 
-static int nl_async(struct nlmsghdr *h)
+static void nl_async(struct nlmsghdr *h)
 {
   if (h->nlmsg_type == NLMSG_ERROR)
     {
       struct nlmsgerr *err = NLMSG_DATA(h);
       if (err->error != 0)
 	my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error)));
-      return 0;
     }
   else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE) 
     {
@@ -385,18 +369,15 @@ static int nl_async(struct nlmsghdr *h)
 	      else if (daemon->rfd_save && daemon->rfd_save->refcount != 0)
 		fd = daemon->rfd_save->fd;
 	      else
-		return 0;
+		return;
 	      
 	      while(sendto(fd, daemon->packet, daemon->packet_len, 0,
 			   &daemon->srv_save->addr.sa, sa_len(&daemon->srv_save->addr)) == -1 && retry_send()); 
 	    }
 	}
-      return 0;
     }
   else if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) 
-    return 1; /* clever bind mode - rescan */
-  
-  return 0;
+    send_newaddr();
 }
 #endif
 
--- a/src/network.c
+++ b/src/network.c
@@ -551,7 +551,7 @@ static int iface_allowed_v4(struct in_ad
 int enumerate_interfaces(int reset)
 {
   static struct addrlist *spare = NULL;
-  static int done = 0, active = 0;
+  static int done = 0;
   struct iface_param param;
   int errsave, ret = 1;
   struct addrlist *addr, *tmp;
@@ -570,14 +570,11 @@ int enumerate_interfaces(int reset)
       return 1;
     }
 
-  if (done || active)
+  if (done)
     return 1;
 
   done = 1;
 
-  /* protect against recusive calls from iface_enumerate(); */
-  active = 1;
-
   if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
     return 0;
  
@@ -677,10 +674,8 @@ int enumerate_interfaces(int reset)
     }
   
   errno = errsave;
-  
   spare = param.spare;
-  active = 0;
-  
+    
   return ret;
 }
 
