Add --bind-dynamic v2.63test1
authorSimon Kelley <simon@thekelleys.org.uk>
Wed, 20 Jun 2012 10:23:38 +0000 (11:23 +0100)
committerSimon Kelley <simon@thekelleys.org.uk>
Wed, 20 Jun 2012 10:23:38 +0000 (11:23 +0100)
CHANGELOG
man/dnsmasq.8
src/dhcp.c
src/dnsmasq.c
src/dnsmasq.h
src/forward.c
src/netlink.c
src/option.c

index 702a865..d67404c 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,6 +7,10 @@ version 2.63
            Allow more than one --tfp-root flag. The per-interface
            stuff is pointless without that.
 
+           Add --bind-dynamic. A hybrid mode between the default and
+           --bind-interfaces which copes with dynamically created
+           interfaces. 
+
 
 version 2.62
             Update German translation. Thanks to Conrad Kostecki.
index 3c51f78..fd61c4b 100644 (file)
@@ -204,6 +204,17 @@ running another nameserver (or another instance of dnsmasq) on the
 same machine. Setting this option also enables multiple instances of
 dnsmasq which provide DHCP service to run in the same machine.
 .TP
+.B --bind-dynamic
+Enable a network mode which is a hybrid between 
+.B --bind-interfaces
+and the default. Dnsmasq binds the address of indivdual interfaces,
+allowing multiple dnsmasq instances, but if new interfaces or
+addresses appear, it automatically listens on those (subject to any
+access-control configuration). This makes dynamically created
+interfaces work in the same way as the default. Implementing this
+option requires non-standard networking APIs and it is only availble
+under Linux. 
+.TP
 .B \-y, --localise-queries
 Return answers to DNS queries from /etc/hosts which depend on the interface over which the query was
 received. If a name in /etc/hosts has more than one address associated with
index 045eb3a..9496ba2 100644 (file)
@@ -66,7 +66,7 @@ static int make_fd(int port)
   /* When bind-interfaces is set, there might be more than one dnmsasq
      instance binding port 67. That's OK if they serve different networks.
      Need to set REUSEADDR to make this posible, or REUSEPORT on *BSD. */
-  if (option_bool(OPT_NOWILD))
+  if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
     {
 #ifdef SO_REUSEPORT
       int rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt));
index e48424b..0845d76 100644 (file)
@@ -114,6 +114,9 @@ int main (int argc, char **argv)
       set_option_bool(OPT_NOWILD);
     }
 #  endif
+
+  if (option_bool(OPT_CLEVERBIND))
+    die(_("--bind-dynamic not available on this platform"), NULL, EC_BADCONF);
 #endif
 
 #ifndef HAVE_TFTP
@@ -185,6 +188,9 @@ int main (int argc, char **argv)
 #ifdef HAVE_LINUX_NETWORK
   /* After lease_init */
   netlink_init();
+
+  if (option_bool(OPT_NOWILD) && option_bool(OPT_CLEVERBIND))
+    die(_("cannot set --bind-interfaces and --bind-dynamic"), NULL, EC_BADCONF);
 #endif
 
 #ifdef HAVE_DHCP6
@@ -202,13 +208,14 @@ int main (int argc, char **argv)
   if (!enumerate_interfaces())
     die(_("failed to find list of interfaces: %s"), NULL, EC_MISC);
   
-  if (option_bool(OPT_NOWILD)) 
+  if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)
     {
       create_bound_listeners(1);
-
-      for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
-       if (if_tmp->name && !if_tmp->used)
-         die(_("unknown interface %s"), if_tmp->name, EC_BADNET);
+      
+      if (!option_bool(OPT_CLEVERBIND))
+       for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+         if (if_tmp->name && !if_tmp->used)
+           die(_("unknown interface %s"), if_tmp->name, EC_BADNET);
 
 #if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP)
       /* after enumerate_interfaces()  */
@@ -425,8 +432,9 @@ int main (int argc, char **argv)
 #if defined(HAVE_LINUX_NETWORK)          
          /* On linux, we keep CAP_NETADMIN (for ARP-injection) and
             CAP_NET_RAW (for icmp) if we're doing dhcp. If we have yet to bind 
-            ports because of DAD, we need CAP_NET_BIND_SERVICE too. */
-         if (is_dad_listeners())
+            ports because of DAD, or we're doing it dynamically,
+            we need CAP_NET_BIND_SERVICE too. */
+         if (is_dad_listeners() || option_bool(OPT_CLEVERBIND))
            data->effective = data->permitted = data->inheritable =
              (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | 
              (1 << CAP_SETUID) | (1 << CAP_NET_BIND_SERVICE);
@@ -474,7 +482,7 @@ int main (int argc, char **argv)
            }     
 
 #ifdef HAVE_LINUX_NETWORK
-        if (is_dad_listeners())
+         if (is_dad_listeners() || option_bool(OPT_CLEVERBIND))
           data->effective = data->permitted =
             (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_NET_BIND_SERVICE);
         else
@@ -1352,7 +1360,7 @@ static void check_dns_listeners(fd_set *set, time_t now)
              getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
            continue;
          
-         if (option_bool(OPT_NOWILD))
+         if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
            iface = listener->iface; /* May be NULL */
          else
            {
@@ -1369,7 +1377,7 @@ static void check_dns_listeners(fd_set *set, time_t now)
                    break;
            }
          
-         if (!iface && !option_bool(OPT_NOWILD))
+         if (!iface && !(option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)))
            {
              shutdown(confd, SHUT_RDWR);
              close(confd);
index dc2d294..c00f029 100644 (file)
@@ -219,7 +219,8 @@ struct event_desc {
 #define OPT_FQDN_UPDATE    36
 #define OPT_RA             37
 #define OPT_TFTP_LC        38
-#define OPT_LAST           39
+#define OPT_CLEVERBIND     39
+#define OPT_LAST           40
 
 /* extra flags for my_syslog, we use a couple of facilities since they are known 
    not to occupy the same bits as priorities, no matter how syslog.h is set up. */
index 17f098f..f672194 100644 (file)
@@ -436,7 +436,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
   if (udpfd != -1)
     {
       plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl);
-      send_from(udpfd, option_bool(OPT_NOWILD), (char *)header, plen, udpaddr, dst_addr, dst_iface);
+      send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface);
     }
 
   return 0;
@@ -624,7 +624,7 @@ void reply_query(int fd, int family, time_t now)
        {
          header->id = htons(forward->orig_id);
          header->hb4 |= HB4_RA; /* recursion if available */
-         send_from(forward->fd, option_bool(OPT_NOWILD), daemon->packet, nn, 
+         send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, 
                    &forward->source, &forward->dest, forward->iface);
        }
       free_frec(forward); /* cancel */
@@ -819,8 +819,8 @@ void receive_query(struct listener *listen, time_t now)
                      dst_addr_4, netmask, now);
   if (m >= 1)
     {
-      send_from(listen->fd, option_bool(OPT_NOWILD), (char *)header, 
-               m, &source_addr, &dst_addr, if_index);
+      send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
+               (char *)header, m, &source_addr, &dst_addr, if_index);
       daemon->local_answer++;
     }
   else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index,
index d20654d..3659222 100644 (file)
@@ -38,7 +38,7 @@
 static struct iovec iov;
 static u32 netlink_pid;
 
-static void nl_err(struct nlmsghdr *h);
+static int nl_async(struct nlmsghdr *h);
 static void nl_routechange(struct nlmsghdr *h);
 
 void netlink_init(void)
@@ -49,10 +49,13 @@ void netlink_init(void)
   addr.nl_family = AF_NETLINK;
   addr.nl_pad = 0;
   addr.nl_pid = 0; /* autobind */
-#ifdef HAVE_IPV6
-  addr.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
-#else
   addr.nl_groups = RTMGRP_IPV4_ROUTE;
+  if (option_bool(OPT_CLEVERBIND))
+    addr.nl_groups |= RTMGRP_IPV4_IFADDR;
+#ifdef HAVE_IPV6
+  addr.nl_groups |= RTMGRP_IPV6_ROUTE;
+  if (daemon->ra_contexts || option_bool(OPT_CLEVERBIND))
+    addr.nl_groups |= RTMGRP_IPV6_IFADDR;
 #endif
   
   /* May not be able to have permission to set multicast groups don't die in that case */
@@ -136,7 +139,7 @@ int iface_enumerate(int family, void *parm, int (*callback)())
   struct nlmsghdr *h;
   ssize_t len;
   static unsigned int seq = 0;
-  int callback_ok = 1;
+  int callback_ok = 1, newaddr = 0;
 
   struct {
     struct nlmsghdr nlh;
@@ -182,12 +185,24 @@ int iface_enumerate(int family, void *parm, int (*callback)())
        }
 
       for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len))
-       if (h->nlmsg_seq != seq || h->nlmsg_pid != netlink_pid)
-         nl_routechange(h); /* May be multicast arriving async */
-       else if (h->nlmsg_type == NLMSG_ERROR)
-         nl_err(h);
+       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; 
+         }
        else if (h->nlmsg_type == NLMSG_DONE)
-         return callback_ok;
+         {
+           /* handle async new interface address arrivals, these have to be done
+              after we complete as we're not re-entrant */
+           if (newaddr) 
+             {
+               enumerate_interfaces();
+               create_bound_listeners(0);
+             }
+           
+           return callback_ok;
+         }
        else if (h->nlmsg_type == RTM_NEWADDR && family != AF_UNSPEC && family != AF_LOCAL)
          {
            struct ifaddrmsg *ifa = NLMSG_DATA(h);  
@@ -295,7 +310,7 @@ void netlink_multicast(void)
 {
   ssize_t len;
   struct nlmsghdr *h;
-  int flags;
+  int flags, newaddr = 0;
   
   /* don't risk blocking reading netlink messages here. */
   if ((flags = fcntl(daemon->netlinkfd, F_GETFL)) == -1 ||
@@ -303,71 +318,83 @@ void netlink_multicast(void)
     return;
   
   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;
+  
+  /* restore non-blocking status */
+  fcntl(daemon->netlinkfd, F_SETFL, flags);
+
+  if (newaddr) 
     {
-      for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len))
-       if (h->nlmsg_type == NLMSG_ERROR)
-         nl_err(h);
-       else
-         nl_routechange(h);
+      enumerate_interfaces();
+      create_bound_listeners(0);
     }
-
-  /* restore non-blocking status */
-  fcntl(daemon->netlinkfd, F_SETFL, flags); 
 }
 
-static void nl_err(struct nlmsghdr *h)
+static int nl_async(struct nlmsghdr *h)
 {
-  struct nlmsgerr *err = NLMSG_DATA(h);
-  
-  if (err->error != 0)
-    my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error)));
-}
+  if (h->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err = NLMSG_DATA(h);
+      my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error)));
+      return 0;
+    }
+  else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE) 
+    {
+      nl_routechange(h);
+      return 0;
+    }
+  else if (h->nlmsg_type == RTM_NEWADDR) 
+       {
+#ifdef HAVE_DHCP6
+         /* force RAs to sync new network and pick up new interfaces.  */
+         if (daemon->ra_contexts)
+           {
+             schedule_subnet_map();
+             ra_start_unsolicted(dnsmasq_time(), NULL);
+             /* cause lease_update_file to run after we return, in case we were called from
+                iface_enumerate and can't re-enter it now */
+             send_alarm(0, 0);
+           }
+         return !!option_bool(OPT_CLEVERBIND); /* clever bind mode - rescan */
+       }
+#endif  
 
+  return 0;
+}
+  
 /* We arrange to receive netlink multicast messages whenever the network route is added.
    If this happens and we still have a DNS packet in the buffer, we re-send it.
    This helps on DoD links, where frequently the packet which triggers dialling is
    a DNS query, which then gets lost. By re-sending, we can avoid the lookup
-   failing. Note that we only accept these messages from the kernel (pid == 0) */ 
+   failing. */ 
 static void nl_routechange(struct nlmsghdr *h)
 {
-  if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE)
+  struct rtmsg *rtm = NLMSG_DATA(h);
+  int fd;
+  
+  if (rtm->rtm_type != RTN_UNICAST || rtm->rtm_scope != RT_SCOPE_LINK)
+    return;
+  
+  /* Force re-reading resolv file right now, for luck. */
+  daemon->last_resolv = 0;
+  
+  if (daemon->srv_save)
     {
-      struct rtmsg *rtm = NLMSG_DATA(h);
-      int fd;
-
-      if (rtm->rtm_type != RTN_UNICAST || rtm->rtm_scope != RT_SCOPE_LINK)
+      if (daemon->srv_save->sfd)
+       fd = daemon->srv_save->sfd->fd;
+      else if (daemon->rfd_save && daemon->rfd_save->refcount != 0)
+       fd = daemon->rfd_save->fd;
+      else
        return;
-
-      /* Force re-reading resolv file right now, for luck. */
-      daemon->last_resolv = 0;
       
-#ifdef HAVE_DHCP6
-      /* force RAs to sync new network and pick up new interfaces.  */
-      if (daemon->ra_contexts)
-       {
-         schedule_subnet_map();
-         ra_start_unsolicted(dnsmasq_time(), NULL);
-         /* cause lease_update_file to run after we return, in case we were called from
-            iface_enumerate and can't re-enter it now */
-         send_alarm(0, 0);
-       }
-#endif
-
-      if (daemon->srv_save)
-       {
-         if (daemon->srv_save->sfd)
-           fd = daemon->srv_save->sfd->fd;
-         else if (daemon->rfd_save && daemon->rfd_save->refcount != 0)
-           fd = daemon->rfd_save->fd;
-         else
-           return;
-         
-         while(sendto(fd, daemon->packet, daemon->packet_len, 0,
-                      &daemon->srv_save->addr.sa, sa_len(&daemon->srv_save->addr)) == -1 && retry_send()); 
-       }
+      while(sendto(fd, daemon->packet, daemon->packet_len, 0,
+                  &daemon->srv_save->addr.sa, sa_len(&daemon->srv_save->addr)) == -1 && retry_send()); 
     }
 }
 
+
 #endif
 
       
index 77c0d5a..47dca0d 100644 (file)
@@ -119,6 +119,7 @@ struct myoption {
 #define LOPT_HOST_REC  308
 #define LOPT_TFTP_LC   309
 #define LOPT_RR        310
+#define LOPT_CLVERBIND 311
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -243,6 +244,7 @@ static const struct myoption opts[] =
     { "enable-ra", 0, 0, LOPT_RA },
     { "dhcp-duid", 1, 0, LOPT_DUID },
     { "host-record", 1, 0, LOPT_HOST_REC },
+    { "bind-dynamic", 0, 0, LOPT_CLVERBIND },
     { NULL, 0, 0, 0 }
   };
 
@@ -374,6 +376,7 @@ static struct {
   { LOPT_DUID, ARG_ONE, "<enterprise>,<duid>", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL },
   { LOPT_HOST_REC, ARG_DUP, "<name>,<address>", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL },
   { LOPT_RR, ARG_DUP, "<name>,<RR-number>,[<data>]", gettext_noop("Specify arbitrary DNS resource record"), NULL },
+  { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL},
   { 0, 0, NULL, NULL, NULL }
 };