]> git.ipfire.org Git - people/ms/dnsmasq.git/commitdiff
Add --dhcp-hostsdir config option.
authorSimon Kelley <simon@thekelleys.org.uk>
Tue, 20 Jan 2015 20:51:02 +0000 (20:51 +0000)
committerSimon Kelley <simon@thekelleys.org.uk>
Tue, 20 Jan 2015 20:51:02 +0000 (20:51 +0000)
CHANGELOG
man/dnsmasq.8
src/dnsmasq.c
src/dnsmasq.h
src/inotify.c
src/option.c

index bbd7e66196896869c4c47222e1d2279e1a6acedc..0076b557e95edaed61e1d0af1e1e84e4cc35e3d6 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -53,6 +53,11 @@ version 2.73
            Cope with multiple interfaces with the same link-local 
            address. (IPv6 addresses are scoped, so this is allowed.)
            Thanks to Cory Benfield for help with this.
+
+           Add --dhcp-hostsdir. This allows addition of new host
+           configurations to a running dnsmasq instance much more 
+           cheaply than having dnsmasq re-read all its existing
+           configuration each time. 
        
        
 version 2.72
index 5cfa355dea4a092ad5e6868c7d8e7dfe9488e869..005b5cca8d1f12b5d487e61a43c58897266557f5 100644 (file)
@@ -977,6 +977,15 @@ is given, then read all the files contained in that directory. The advantage of
 using this option is the same as for --dhcp-hostsfile: the
 dhcp-optsfile will be re-read when dnsmasq receives SIGHUP. Note that
 it is possible to encode the information in a
+.TP
+.B --dhcp-hostsdir=<path>
+This is exactly equivalent to dhcp-hostfile, except for the following. The path MUST be a
+directory, and not an individual file. Changed or new files within
+the directory are read automatically, without the need to send SIGHUP.
+If a file is deleted for changed after it has been read by dnsmasq, then the
+host record it contained will remain until dnsmasq recieves a SIGHUP, or 
+is restarted; ie host records are only added dynamically.
+.TP
 .B --dhcp-boot
 flag as DHCP options, using the options names bootfile-name,
 server-ip-address and tftp-server. This allows these to be included
index c0c0589d4ce18731b01633d324dae178dd7323e1..04cc98278f62b0cbce89820389bdc3866457b0d8 100644 (file)
@@ -142,6 +142,9 @@ int main (int argc, char **argv)
       set_option_bool(OPT_NOWILD);
       reset_option_bool(OPT_CLEVERBIND);
     }
+
+  if (daemon->inotify_hosts)
+    die(_("dhcp-hostsdir not supported on this platform"), NULL, EC_BADCONF);
 #endif
   
   if (option_bool(OPT_DNSSEC_VALID))
@@ -316,13 +319,16 @@ int main (int argc, char **argv)
 #ifdef HAVE_DNSSEC
       blockdata_init();
 #endif
+    }
 
 #ifdef HAVE_LINUX_NETWORK
-      if (!option_bool(OPT_NO_POLL))
-       inotify_dnsmasq_init();
+  if ((!option_bool(OPT_NO_POLL) && daemon->port != 0) ||
+      daemon->dhcp || daemon->doing_dhcp6)
+    inotify_dnsmasq_init();
+  else
+    daemon->inotifyfd = -1;
 #endif
-    }
-    
+       
   if (option_bool(OPT_DBUS))
 #ifdef HAVE_DBUS
     {
@@ -745,7 +751,7 @@ int main (int argc, char **argv)
 #endif
 
 #ifdef HAVE_TFTP
-       if (option_bool(OPT_TFTP))
+  if (option_bool(OPT_TFTP))
     {
 #ifdef FD_SETSIZE
       if (FD_SETSIZE < (unsigned)max_fd)
@@ -870,7 +876,7 @@ int main (int argc, char **argv)
 #if defined(HAVE_LINUX_NETWORK)
       FD_SET(daemon->netlinkfd, &rset);
       bump_maxfd(daemon->netlinkfd, &maxfd);
-      if (daemon->port != 0 && !option_bool(OPT_NO_POLL))
+      if (daemon->inotifyfd != -1)
        {
          FD_SET(daemon->inotifyfd, &rset);
          bump_maxfd(daemon->inotifyfd, &maxfd);
@@ -943,8 +949,11 @@ int main (int argc, char **argv)
 #endif
 
 #ifdef HAVE_LINUX_NETWORK
-      if (daemon->port != 0 && !option_bool(OPT_NO_POLL) && FD_ISSET(daemon->inotifyfd, &rset) && inotify_check())
-       poll_resolv(1, 1, now);           
+      if  (daemon->inotifyfd != -1 && FD_ISSET(daemon->inotifyfd, &rset) && inotify_check(now))
+       {
+         if (daemon->port != 0 && !option_bool(OPT_NO_POLL))
+           poll_resolv(1, 1, now);
+       }         
 #else
       /* Check for changes to resolv files once per second max. */
       /* Don't go silent for long periods if the clock goes backwards. */
@@ -1385,6 +1394,9 @@ void clear_cache_and_reload(time_t now)
       if (option_bool(OPT_ETHERS))
        dhcp_read_ethers();
       reread_dhcp();
+#ifdef HAVE_LINUX_NETWORK
+      set_dhcp_inotify();
+#endif
       dhcp_update_configs(daemon->dhcp_conf);
       lease_update_from_configs(); 
       lease_update_file(now); 
index f8275e3ac47950a16ad6551eae091797ba70c5d5..d841fdc064adbe86e40b293479ac9d3c926dcc9f 100644 (file)
@@ -550,13 +550,17 @@ struct resolvc {
 #endif
 };
 
-/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile */
+/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
 #define AH_DIR      1
 #define AH_INACTIVE 2
+#define AH_WD_DONE  4
 struct hostsfile {
   struct hostsfile *next;
   int flags;
   char *fname;
+#ifdef HAVE_LINUX_NETWORK
+  int wd; /* inotify watch descriptor */
+#endif
   unsigned int index; /* matches to cache entries for logging */
 };
 
@@ -961,7 +965,7 @@ extern struct daemon {
   int doing_ra, doing_dhcp6;
   struct dhcp_netid_list *dhcp_ignore, *dhcp_ignore_names, *dhcp_gen_names; 
   struct dhcp_netid_list *force_broadcast, *bootp_dynamic;
-  struct hostsfile *dhcp_hosts_file, *dhcp_opts_file;
+  struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *inotify_hosts;
   int dhcp_max, tftp_max;
   int dhcp_server_port, dhcp_client_port;
   int start_tftp_port, end_tftp_port; 
@@ -1197,7 +1201,7 @@ void reset_option_bool(unsigned int opt);
 struct hostsfile *expand_filelist(struct hostsfile *list);
 char *parse_server(char *arg, union mysockaddr *addr, 
                   union mysockaddr *source_addr, char *interface, int *flags);
-
+int option_read_hostsfile(char *file);
 /* forward.c */
 void reply_query(int fd, int family, time_t now);
 void receive_query(struct listener *listen, time_t now);
@@ -1486,5 +1490,8 @@ int detect_loop(char *query, int type);
 /* inotify.c */
 #ifdef HAVE_LINUX_NETWORK
 void inotify_dnsmasq_init();
-int inotify_check(void);
+int inotify_check(time_t now);
+#  ifdef HAVE_DHCP
+void set_dhcp_inotify(void);
+#  endif
 #endif
index 83730008c11b33c18a77d6f9b349f19b2f1bf7d0..52a30d7f44db79e55f01f987dde782a7b64b410e 100644 (file)
 
 #include <sys/inotify.h>
 
+#ifdef HAVE_DHCP
+static void check_for_dhcp_inotify(struct inotify_event *in, time_t now);
+#endif
+
+
 /* the strategy is to set a inotify on the directories containing
    resolv files, for any files in the directory which are close-write 
    or moved into the directory.
@@ -40,8 +45,6 @@ void inotify_dnsmasq_init()
   struct resolvc *res;
 
   inotify_buffer = safe_malloc(INOTIFY_SZ);
-
-
   daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
   
   if (daemon->inotifyfd == -1)
@@ -66,6 +69,7 @@ void inotify_dnsmasq_init()
        {
          *d = 0; /* make path just directory */
          res->wd = inotify_add_watch(daemon->inotifyfd, path, IN_CLOSE_WRITE | IN_MOVED_TO);
+
          res->file = d+1; /* pointer to filename */
          *d = '/';
          
@@ -78,7 +82,7 @@ void inotify_dnsmasq_init()
     }
 }
 
-int inotify_check(void)
+int inotify_check(time_t now)
 {
   int hit = 0;
   
@@ -101,13 +105,116 @@ int inotify_check(void)
          for (res = daemon->resolv_files; res; res = res->next)
            if (res->wd == in->wd && in->len != 0 && strcmp(res->file, in->name) == 0)
              hit = 1;
+
+#ifdef HAVE_DHCP
+         if (daemon->dhcp || daemon->doing_dhcp6)
+           check_for_dhcp_inotify(in, now);
+#endif
        }
     }
-
   return hit;
 }
 
-#endif
+#ifdef HAVE_DHCP 
+/* initialisation for dhcp-hostdir. Set inotify watch for each directory, and read pre-existing files */
+void set_dhcp_inotify(void)
+{
+  struct hostsfile *ah;
 
-  
+  for (ah = daemon->inotify_hosts; ah; ah = ah->next)
+    {
+       DIR *dir_stream = NULL;
+       struct dirent *ent;
+       struct stat buf;
+
+       if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode)))
+        {
+          my_syslog(LOG_ERR, _("bad directory in dhcp-hostsdir %s"), ah->fname);
+          continue;
+        }
+
+       if (!(ah->flags & AH_WD_DONE))
+        {
+          ah->wd = inotify_add_watch(daemon->inotifyfd, ah->fname, IN_CLOSE_WRITE | IN_MOVED_TO);
+          ah->flags |= AH_WD_DONE;
+        }
+       /* Read contents of dir _after_ calling add_watch, in the ho[e of avoiding
+         a race which misses files being added as we start */
+       if (ah->wd == -1 || !(dir_stream = opendir(ah->fname)))
+        {
+          my_syslog(LOG_ERR, _("failed to create inotify for %s"), ah->fname);
+          continue;
+        }
+
+       while ((ent = readdir(dir_stream)))
+        {
+          size_t lendir = strlen(ah->fname);
+          size_t lenfile = strlen(ent->d_name);
+          char *path;
+          
+          /* ignore emacs backups and dotfiles */
+          if (lenfile == 0 || 
+              ent->d_name[lenfile - 1] == '~' ||
+              (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') ||
+              ent->d_name[0] == '.')
+            continue;
+          
+          if ((path = whine_malloc(lendir + lenfile + 2)))
+            {
+              strcpy(path, ah->fname);
+              strcat(path, "/");
+              strcat(path, ent->d_name);
+              
+              /* ignore non-regular files */
+              if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode))
+                option_read_hostsfile(path);
+              
+              free(path);
+            }
+        }
+    }
+}
+
+static void check_for_dhcp_inotify(struct inotify_event *in, time_t now)
+{
+  struct hostsfile *ah;
+
+  /* ignore emacs backups and dotfiles */
+  if (in->len == 0 || 
+      in->name[in->len - 1] == '~' ||
+      (in->name[0] == '#' && in->name[in->len - 1] == '#') ||
+      in->name[0] == '.')
+    return;
+
+  for (ah = daemon->inotify_hosts; ah; ah = ah->next)
+    if (ah->wd == in->wd)
+      {
+       size_t lendir = strlen(ah->fname);
+       char *path;
+          
+       if ((path = whine_malloc(lendir + in->len + 2)))
+         {
+           strcpy(path, ah->fname);
+           strcat(path, "/");
+           strcat(path, in->name);
+           
+           if (option_read_hostsfile(path))
+             {
+               /* Propogate the consequences of loading a new dhcp-host */
+               dhcp_update_configs(daemon->dhcp_conf);
+               lease_update_from_configs(); 
+               lease_update_file(now); 
+               lease_update_dns(1);
+             }
+           
+           free(path);
+         }
+       
+       return;
+      }
+}
+
+#endif /* DHCP */
+
+#endif  /* LINUX_NETWORK */
   
index 8b994098cc9f4acf45a818d01794ff27c7105619..22e11c37d37496841284e886fc98420c779d0a97 100644 (file)
@@ -149,7 +149,7 @@ struct myoption {
 #define LOPT_LOOP_DETECT   337
 #define LOPT_IGNORE_ADDR   338
 #define LOPT_MINCTTL       339
-
+#define LOPT_DHCP_INOTIFY  340
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -248,6 +248,7 @@ static const struct myoption opts[] =
     { "interface-name", 1, 0, LOPT_INTNAME },
     { "dhcp-hostsfile", 1, 0, LOPT_DHCP_HOST },
     { "dhcp-optsfile", 1, 0, LOPT_DHCP_OPTS },
+    { "dhcp-hostsdir", 1, 0, LOPT_DHCP_INOTIFY },
     { "dhcp-no-override", 0, 0, LOPT_OVERRIDE },
     { "tftp-port-range", 1, 0, LOPT_TFTPPORTS },
     { "stop-dns-rebind", 0, 0, LOPT_REBIND },
@@ -336,6 +337,7 @@ static struct {
   { 'G', ARG_DUP, "<hostspec>", gettext_noop("Set address or hostname for a specified machine."), NULL },
   { LOPT_DHCP_HOST, ARG_DUP, "<path>", gettext_noop("Read DHCP host specs from file."), NULL },
   { LOPT_DHCP_OPTS, ARG_DUP, "<path>", gettext_noop("Read DHCP option specs from file."), NULL },
+  { LOPT_DHCP_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read DHCP host specs from a directory."), NULL }, 
   { LOPT_TAG_IF, ARG_DUP, "tag-expression", gettext_noop("Evaluate conditional tag expression."), NULL },
   { 'h', OPT_NO_HOSTS, NULL, gettext_noop("Do NOT load %s file."), HOSTSFILE },
   { 'H', ARG_DUP, "<path>", gettext_noop("Specify a hosts file to be read in addition to %s."), HOSTSFILE },
@@ -1710,8 +1712,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
       break;
 #endif /* HAVE_DHCP */
 
-    case LOPT_DHCP_HOST: /* --dhcp-hostfile */
+    case LOPT_DHCP_HOST: /* --dhcp-hostsfile */
     case LOPT_DHCP_OPTS: /* --dhcp-optsfile */
+    case LOPT_DHCP_INOTIFY: /* dhcp-hostsdir */
     case 'H': /* --addn-hosts */
       {
        struct hostsfile *new = opt_malloc(sizeof(struct hostsfile));
@@ -1734,6 +1737,12 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
            new->next = daemon->dhcp_opts_file;
            daemon->dhcp_opts_file = new;
          }       
+       else if (option == LOPT_DHCP_INOTIFY)
+         {
+           new->next = daemon->inotify_hosts;
+           daemon->inotify_hosts = new;
+         }
+       
        break;
       }
       
@@ -4042,6 +4051,13 @@ static void read_file(char *file, FILE *f, int hard_opt)
   fclose(f);
 }
 
+#ifdef HAVE_DHCP
+int option_read_hostsfile(char *file)
+{
+  return one_file(file, LOPT_BANK);
+}
+#endif
+
 static int one_file(char *file, int hard_opt)
 {
   FILE *f;
@@ -4139,7 +4155,7 @@ struct hostsfile *expand_filelist(struct hostsfile *list)
            
            /* don't read this as a file */
            ah->flags |= AH_INACTIVE;
-
+           
            if (!(dir_stream = opendir(ah->fname)))
              my_syslog(LOG_ERR, _("cannot access directory %s: %s"), 
                        ah->fname, strerror(errno));