[Dnsmasq-discuss] SRV/SOA/NS (aka Active Directory) patch for dnsmasq

Robert Kean rkean@keanconsulting.com
Sat, 25 Dec 2004 23:22:47 -0500


I'm building a new linux box and wanted to take the opportunity to learn 
the ins and outs of Samba and Active Directory.
This led to the need to get a better understanding of ldap and kerberos.
This led to the need for a DNS server that supported SRV records.

Well, I had already decided on dnsmasq for my DNS server...  DOH!

So, rather than send the traditional, "hey, can you add this feature?" 
email, I decided that it was time to for me to contribute...

features added:
   support for SRV records
          option accepts service, port, and target; if target is not 
set, it defaults to host that dnsmasq is running on
   support for NS records
            if not set, defaults to host that dnsmasq is running on
   support for SOA records
            SOA record returned based on the settings for the --domain 
and new --nameserver options
            if no --domain option is set, then, obviously, there is no 
way to return a SOA record

changes made:
   replaced add_text_record function() with add_resource_record() and 
add_dname() functions
   replaced all add_text_record() with add_resource_record() calls
   replaced inline build of T_A and T_AAAA records with a call to 
add_resource_record()
   updated dnsmasq.8, dnsmasq.conf.example and `dnsmasq --help` to 
reflect new -W(--srv-host) and -y(--nameserver) options

to apply:
   cd <distribution directory>  (probably something like 
/usr/src/dnsmasq-2.18)
   patch -p1 < dnsmasq-2.18_AD.patch

notes:
   I have also tested this patch with dnsmasq-2.19 successfully.
   This patch only adds 283 bytes to the 2.18 and 2.19 binaries

   add_resource_record takes a variable argument list
       all types pass *header, nameoffset, *ansp, ttl, *offset, and type
       for each type, the arguments after type vary as follows:
            T_A & T_AAAA:                     struct crec *  (pointer to 
the cache record struct)
            T_NS, T_CNAME, & T_PTR:                    char * (pointer 
to dname string)
            T_SOA:                    char *   (pointer to ns dname string)
                   char *    (pointer to contact dname string)
                   long      (long value for serial)
                   long      (long value fo refresh)
                   long      (long value for retry)
                   long      (long value for retry)
                   long      (long value for ttl)

            T_MX:
                   short      (short for pref)
                   char *      (point to target dname)

            T_HINFO & T_TXT:
                  not implemented (breaks out of  case statement)

            T_SRV:
               int      (priority)
               int      (weight)
               int      (port)
               char *   (pointer to target dname string)

------------------------------------------------------------------------

diff -rup dnsmasq-2.18/dnsmasq.8 dnsmasq-2.18-patch/dnsmasq.8
--- dnsmasq-2.18/dnsmasq.8	2004-11-15 15:15:53.000000000 -0500
+++ dnsmasq-2.18-patch/dnsmasq.8	2004-12-24 15:46:23.000000000 -0500
@@ -281,6 +281,14 @@ machine on which dnsmasq is running) for
 local machine. Local machines are those in /etc/hosts or with DHCP
 leases.
 .TP
+.B \-W, --srv-host=<_service._prot.domain-name>,<port>
+Define an SRV record in the for of _service._tcp.domain-name,port
+(ex:srv-host=ldap._tcp.example.com,389)
+.TP
+.B \-y, -nameserver=<hostname>
+Define the NS record for this server.  If not defined, it will default to the hostname that dnamasq is
+running on.  This is also used to respond to SOA requests.
+.TP
 .B \-c, --cache-size=<cachesize>
 Set the size of dnsmasq's cache. The default is 150 names. Setting the cache size to zero disables caching.
 .TP
diff -rup dnsmasq-2.18/dnsmasq.conf.example dnsmasq-2.18-patch/dnsmasq.conf.example
--- dnsmasq-2.18/dnsmasq.conf.example	2004-11-15 15:15:53.000000000 -0500
+++ dnsmasq-2.18-patch/dnsmasq.conf.example	2004-12-24 15:46:23.000000000 -0500
@@ -12,6 +12,24 @@
 #selfmx
 #localmx
 
+# Change the following line if you want dnsmasq to serve SRV 
+# records.  These are useful if you want to serve ldap requests for
+# Active Directory and other windows-originated DNS requests.
+# You may add multiple srv-host lines.
+# Leaving the host parameter empty will default the the host of dnsmasq.
+# example: srv-host=_ldap._tcp.example.com,389
+#          srv-host=_gc._tcp.example.com,3268,adserver.example.com
+#          srv-host=_kerberos._tcp.example.com,88,krb5.example.com
+#          srv-host=_kpasswd._tcp.example.com,464,krb5.example.com
+
+#srv-host=
+
+# Change the following line if you want dnsmasq to respond to
+# NS and SOA records with something other than the hostname
+# of the server that it is running on.
+# example: nameserver=ns.example.com
+#nameserver=
+
 # The following two options make you a better netizen, since they 
 # tell dnsmasq to filter out queries which the public DNS cannot
 # answer, and which load the servers (especially the root servers) 
@@ -28,6 +46,8 @@ bogus-priv
 # which can trigger dial-on-demand links needlessly.
 # Note that (amongst other things) this blocks all SRV requests, 
 # so don't use it if you use eg Kerberos.
+# Note: enabling this option will prevent dnsmasq from responding
+#   to request for any srv-host options you may set.
 #filterwin2k
 
 # Change this line if you want dns to get its upstream servers from
diff -rup dnsmasq-2.18/src/dnsmasq.h dnsmasq-2.18-patch/src/dnsmasq.h
--- dnsmasq-2.18/src/dnsmasq.h	2004-11-21 10:09:41.000000000 -0500
+++ dnsmasq-2.18-patch/src/dnsmasq.h	2004-12-24 15:46:23.000000000 -0500
@@ -57,6 +57,7 @@
 #include <errno.h>
 #include <pwd.h>
 #include <grp.h>
+#include <stdarg.h>
 #if defined(__OpenBSD__) || defined(__NetBSD__)
 #  include <netinet/if_ether.h>
 #else
@@ -122,6 +123,12 @@ struct mx_record {
   struct mx_record *next;
 };
 
+struct srv_record {
+  char *srvname, *srvtarget;
+  int srvport;
+  struct srv_record *next;
+};
+
 union bigname {
   char name[MAXDNAME];
   union bigname *next; /* freelist */
@@ -360,6 +367,8 @@ struct daemon {
   char *lease_file; 
   char *username, *groupname;
   char *domain_suffix;
+  struct srv_record *srvnames;
+  char *nameserver;
   char *runfile; 
   struct iname *if_names, *if_addrs, *if_except;
   struct bogus_addr *bogus_addr;
diff -rup dnsmasq-2.18/src/option.c dnsmasq-2.18-patch/src/option.c
--- dnsmasq-2.18/src/option.c	2004-11-21 14:20:10.000000000 -0500
+++ dnsmasq-2.18-patch/src/option.c	2004-12-25 00:38:57.180696056 -0500
@@ -21,7 +21,7 @@ struct myoption {
   int val;
 };
 
-#define OPTSTRING "ZDNLERKzowefnbvhdkqr:m:p:c:l:s:i:t:u:g:a:x:S:C:A:T:H:Q:I:B:F:G:O:M:X:V:U:j:P:J:"
+#define OPTSTRING "ZDNLERKzowefnbvhdkqr:m:p:c:l:s:i:t:u:g:a:x:S:C:A:T:H:Q:I:B:F:G:O:M:X:V:U:j:P:J:W:y:"
 
 static struct myoption opts[] = { 
   {"version", 0, 0, 'v'},
@@ -76,6 +76,8 @@ static struct myoption opts[] = { 
   {"edns-packet-max", 1, 0, 'P'},
   {"keep-in-foreground", 0, 0, 'k'},
   {"dhcp-authoritative", 0, 0, 'K'},
+  {"srv-host", 1, 0, 'W'},
+  {"nameserver", 1, 0, 'y'},
   {0, 0, 0, 0}
 };
 
@@ -158,9 +160,11 @@ static char *usage =
 "-U, --dhcp-vendorclass=<id>,<class> Map DHCP vendor class to option set.\n"
 "-v, --version                       Display dnsmasq version and copyright information.\n"
 "-V, --alias=addr,addr,mask          Translate IPv4 addresses from upstream servers.\n"
+"-W, --srv-host=_service._prot,port  Specify a service, protocol, & port for an SRV record.\n"
 "-w, --help                          Display this message.\n"
 "-x, --pid-file=path                 Specify path of PID file. (defaults to " RUNFILE ").\n"
 "-X, --dhcp-lease-max=number         Specify maximum number of DHCP leases (defaults to %d).\n"
+"-y, --nameserver=nameserver         Specify default nameserver.\n"
 "-z, --bind-interfaces               Bind only to interfaces in use.\n"
 "-Z, --read-ethers                   Read DHCP static host information from " ETHERSFILE ".\n"
 "\n";
@@ -175,6 +179,8 @@ struct daemon *read_opts (int argc, char
   char *comma, *file_name_save = NULL, *conffile = CONFFILE;
   int hosts_index = 1, conffile_set = 0;
   int line_save = 0, lineno = 0;
+  struct srv_record *srv = NULL;
+  char *hostname = safe_malloc(MAXDNAME);
   opterr = 0;
   
   memset(daemon, 0, sizeof(struct daemon));
@@ -389,7 +395,7 @@ struct daemon *read_opts (int argc, char
 		  }
 		break;
 	      }
-	      
+
 	    case 't':
 	      if (!canonicalise(optarg))
 		{
@@ -1363,6 +1369,57 @@ struct daemon *read_opts (int argc, char
 		
 		break;
 	      }
+
+	    case 'W':
+	      {
+		int port;
+		char *tmp;
+		if ((tmp = strchr(optarg, ',')))
+		  *(tmp++) = 0;
+		else
+		  {
+		    option = '?';
+		    problem = "no port number defined";
+		    break;
+		  }
+
+		if ((comma = strchr(tmp, ',')))
+		  *(comma++) = 0;
+		if (!atoi_check(tmp, &port))
+		  {
+		    option = '?';
+		    problem = "invalid port number";
+		    break;
+	          }
+
+		if (!canonicalise(optarg) || (comma && !canonicalise(comma)))
+		  {
+		    option = '?';
+		    problem = "bad SRV record";
+		  }
+		else
+		  {
+		    struct srv_record *new = safe_malloc(sizeof(struct srv_record));
+		    new->next = daemon->srvnames;
+		    daemon->srvnames = new;
+		    new->srvname = safe_string_alloc(optarg);
+		    new->srvport = (int)port;
+		    new->srvtarget = safe_string_alloc(comma); /* may be NULL */
+		  }
+		break;
+	      }
+
+	    case 'y':
+	      {
+	        if (!canonicalise(optarg))
+		  {
+		    option = '?';
+		    problem = "bad ns target";
+		  }
+		else
+		  daemon->nameserver = safe_string_alloc(optarg);
+		break;
+	      }
 	    }
 	}
       
@@ -1411,23 +1468,52 @@ struct daemon *read_opts (int argc, char
 #endif /* IPv6 */
     }
 		      
+  /* get hostname for defaulting mx, ns, and soa records */
+  if (gethostname(hostname, MAXDNAME) == -1)
+    die("cannot get host-name: %s", NULL);
+
+  if (daemon->domain_suffix)
+    {
+      if (!strstr(hostname, daemon->domain_suffix))
+        {
+	  int j;
+          j = strlen(hostname);
+          *(hostname+j++) = '.';
+          *(hostname+j) = '\0';
+          strncat(hostname, daemon->domain_suffix, MAXDNAME - strlen(hostname) - 1);
+	}
+    }
+
   /* only one of these need be specified: the other defaults to the
      host-name */
   if ((daemon->options & OPT_LOCALMX) || daemon->mxnames || daemon->mxtarget)
     {
-      if (gethostname(buff, MAXDNAME) == -1)
-	die("cannot get host-name: %s", NULL);
-	      
       if (!daemon->mxnames)
 	{
 	  daemon->mxnames = safe_malloc(sizeof(struct mx_record));
 	  daemon->mxnames->next = NULL;
 	  daemon->mxnames->mxtarget = NULL;
-	  daemon->mxnames->mxname = safe_string_alloc(buff);
-}
+	  daemon->mxnames->mxname = safe_string_alloc(hostname);
+	}
       
       if (!daemon->mxtarget)
-	daemon->mxtarget = safe_string_alloc(buff);
+	daemon->mxtarget = safe_string_alloc(hostname);
+    }
+
+  /* set default host for any srv-hosts that don't have a target */
+  if (daemon->srvnames)
+    {
+      for (srv = daemon->srvnames; srv; srv = srv->next)
+        {
+	  if (!srv->srvtarget)
+	    srv->srvtarget = safe_string_alloc(hostname);
+	}
+    }
+
+  /* set dafault ns if none set with options */
+  if (!daemon->nameserver)
+    {
+      daemon->nameserver = safe_string_alloc(hostname);
     }
   
   if (daemon->options & OPT_NO_RESOLV)
diff -rup dnsmasq-2.18/src/rfc1035.c dnsmasq-2.18-patch/src/rfc1035.c
--- dnsmasq-2.18/src/rfc1035.c	2004-11-15 15:15:53.000000000 -0500
+++ dnsmasq-2.18-patch/src/rfc1035.c	2004-12-25 00:40:03.573602800 -0500
@@ -420,43 +420,151 @@ static int private_net(struct all_addr *
     return 0;
 }
  
-static unsigned char *add_text_record(HEADER *header, unsigned int nameoffset, unsigned char *p, 
-				      unsigned long ttl, unsigned short pref, 
-				      unsigned short type, char *name, int *offset)
+static unsigned char *add_dname(char *dname, unsigned char **p)
+  {
+    unsigned char *cp;
+    int j;
+    
+    while (*dname)
+      {
+	cp = (*p)++;
+	for (j=0; *dname && (*dname != '.'); dname++, j++)
+	*(*p)++ = *dname;
+	*cp = j;
+	if (*dname)
+	  dname++;
+      }
+    *(*p)++ = 0;
+
+    return *p;
+  }
+
+static unsigned char *add_resource_record(HEADER *header, unsigned int nameoffset, unsigned char *p,
+					unsigned long ttl, int *offset, unsigned short type, ...)
 {
-  unsigned char *sav, *cp;
+  va_list ap;
+  unsigned char *sav;
   int j;
-  
-  PUTSHORT(nameoffset | 0xc000, p); 
+  int addrsz = INADDRSZ;
+  unsigned short usval;
+  long lval;
+  char *sval;
+  struct crec *pval;
+
+  PUTSHORT(nameoffset | 0xc000, p);
   PUTSHORT(type, p);
   PUTSHORT(C_IN, p);
-  PUTLONG(ttl, p); /* TTL */
-  
-  sav = p;
-  PUTSHORT(0, p); /* dummy RDLENGTH */
+  PUTLONG(ttl, p);      /* TTL */
 
-  if (pref)
-    PUTSHORT(pref, p);
+  sav = p;              /* Save pointer to RDLength field */
+  PUTSHORT(0, p);       /* Placeholder RDLength */
 
-  while (*name) 
+  va_start(ap, type);   /* make ap point to 1st unamed argument */
+  switch (type)
     {
-      cp = p++;
-      for (j=0; *name && (*name != '.'); name++, j++)
-	*p++ = *name;
-      *cp = j;
-      if (*name)
-	name++;
+      case T_AAAA:
+        addrsz = IN6ADDRSZ;
+      case T_A:
+        {
+	  pval = va_arg(ap, struct crec *); 
+	  memcpy(p, &pval->addr, addrsz);
+	  p += addrsz;
+	  break;
+	}
+
+      case T_NS:
+      case T_CNAME:
+      case T_PTR:
+	{
+	  /* get domain-name answer arg and store it in RDATA field */
+	  sval = va_arg(ap, char *);
+	  add_dname(sval, &p);
+
+	  break;
+	}
+
+      case T_SOA:
+	{
+	  /* get nameserver arg */
+	  sval = va_arg(ap, char *);
+	  add_dname(sval, &p);
+
+	  /* get contact name arg */
+	  sval = va_arg(ap, char *);
+	  add_dname(sval, &p);
+
+	  /* get serial long */
+	  lval = va_arg(ap, long);
+	  PUTLONG(lval, p);
+
+	  /* get refresh long */
+	  lval = va_arg(ap, long);
+	  PUTLONG(lval, p);
+	  
+	  /* get retry long */
+	  lval = va_arg(ap, long);
+	  PUTLONG(lval, p);
+
+	  /* get expire long */
+	  lval = va_arg(ap, long);
+	  PUTLONG(lval, p);
+
+	  /* use TTL for minimum long */
+	  PUTLONG(ttl, p);
+
+	  break;
+	}
+
+      case T_MX:
+	{
+	  /* get MX PREF Arg and store it in RDATA field */
+	  usval = va_arg(ap, int);
+	  PUTSHORT(usval, p);
+
+	  /* get domain-name answer Arg and store it in RDATA field */
+	  sval = va_arg(ap, char *);
+	  add_dname(sval, &p);
+
+	  break;
+	}
+
+      case T_HINFO:
+      case T_TXT:
+	{
+	  break;
+	}
+
+      case T_SRV:
+	{
+	  /* get Priority Arg and store it in RDATA */
+	  usval = va_arg(ap, int);
+	  PUTSHORT(usval, p);
+
+	  /* get Weight Arg and store it in RDATA */
+	  usval = va_arg(ap, int);
+	  PUTSHORT(usval, p);
+
+	  /* get Port Arg and store it in RDATA */
+	  usval = va_arg(ap, int);
+	  PUTSHORT(usval, p);
+
+	  /* get domain-name answer Arg and store it in RDATA field */
+	  sval = va_arg(ap, char *);
+	  add_dname(sval, &p);
+
+          break;
+        }
     }
-  *p++ = 0;
+  va_end(ap);	/* clean up variable argument pointer */
+
   j = p - sav - 2;
-  PUTSHORT(j, sav); /* Real RDLENGTH */
-  
+  PUTSHORT(j, sav);     /* Now, store real RDLength */
+
   if (offset)
     *offset = sav - (unsigned char *)header;
 
   return p;
 }
-
 static void dns_doctor(HEADER *header, struct doctor *doctor, struct in_addr *addr)
 {
   for (; doctor; doctor = doctor->next)
@@ -1019,8 +1127,8 @@ int answer_request(HEADER *header, char 
 			      if (!(crecp->flags & (F_HOSTS | F_DHCP)))
 				auth = 0;
 			      
-			      ansp = add_text_record(header, nameoffset, ansp, ttl, 0, T_PTR, 
-						     cache_get_name(crecp), NULL);
+			      ansp = add_resource_record(header, nameoffset, ansp, ttl, NULL,
+						     T_PTR, cache_get_name(crecp));
 			      
 			      log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr,
 					0, daemon->addn_hosts, crecp->uid);
@@ -1034,6 +1142,38 @@ int answer_request(HEADER *header, char 
 		    } while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa)));
 		}
 	      
+	      if (qtype == T_SOA || qtype == T_ANY)
+	        {
+		  if (daemon->domain_suffix && hostname_isequal(name, daemon->domain_suffix))
+		    {
+		      ans = 1;
+		      if (!dryrun)
+		        {
+  			  char *rname = safe_malloc(MAXDNAME);
+			  strcpy(rname, "hostmaster.");
+			  strncat(rname, daemon->domain_suffix, MAXDNAME - strlen(rname) - 1);
+			  ansp = add_resource_record(header, nameoffset, ansp, daemon->local_ttl,
+					  	     NULL, T_SOA, daemon->nameserver, rname, -1l,
+						     -1l, -1l, -1l);
+			  anscount++;
+			}
+		    }
+		}  
+	      
+	      if (qtype == T_NS || qtype == T_ANY)
+	        {
+		  if (hostname_isequal(name, daemon->domain_suffix))
+		    {
+		      ans = 1;
+		      if (!dryrun)
+		        {
+		          ansp = add_resource_record(header, nameoffset, ansp, daemon->local_ttl, 
+						     NULL, T_NS, daemon->nameserver);
+			  anscount++;
+			}
+		    }
+		}
+
 	      for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0)
 		{
 		  unsigned short type = T_A;
@@ -1065,8 +1205,8 @@ int answer_request(HEADER *header, char 
 			{
 			  if (!dryrun)
 			    {
-			      ansp = add_text_record(header, nameoffset, ansp, crecp->ttd - now, 0, T_CNAME, 
-						     cache_get_name(crecp->addr.cname.cache), &nameoffset);
+			      ansp = add_resource_record(header, nameoffset, ansp, crecp->ttd - now, &nameoffset,
+						     T_CNAME, cache_get_name(crecp));
 			      anscount++;
 			      log_query(crecp->flags, name, NULL, 0, daemon->addn_hosts, crecp->uid);
 			    }
@@ -1104,14 +1244,7 @@ int answer_request(HEADER *header, char 
 					0, daemon->addn_hosts, crecp->uid);
 			      
 			      /* copy question as first part of answer (use compression) */
-			      PUTSHORT(nameoffset | 0xc000, ansp); 
-			      PUTSHORT(type, ansp);
-			      PUTSHORT(C_IN, ansp);
-			      PUTLONG(ttl, ansp); /* TTL */
-			      
-			      PUTSHORT(addrsz, ansp);
-			      memcpy(ansp, &crecp->addr, addrsz);
-			      ansp += addrsz;
+			      ansp = add_resource_record(header, nameoffset, ansp, ttl, NULL, type, crecp);
 			      anscount++;
 			      
 			      if (((unsigned char *)limit - ansp) < 0)
@@ -1120,7 +1253,7 @@ int answer_request(HEADER *header, char 
 			}
 		    }
 		}
-	      
+
 	      if (qtype == T_MX || qtype == T_ANY)
 		{
 		  struct mx_record *mx;
@@ -1132,8 +1265,8 @@ int answer_request(HEADER *header, char 
 		      ans = 1;
 		      if (!dryrun)
 			{
-			  ansp = add_text_record(header, nameoffset, ansp, daemon->local_ttl, 1, T_MX, 
-						 mx->mxtarget ? mx->mxtarget : daemon->mxtarget, NULL);
+			  ansp = add_resource_record(header, nameoffset, ansp, daemon->local_ttl, NULL, T_MX, 1,
+					  	 mx->mxtarget ? mx->mxtarget : daemon->mxtarget);
 			  anscount++;
 			}
 		    }
@@ -1143,8 +1276,26 @@ int answer_request(HEADER *header, char 
 		      ans = 1;
 		      if (!dryrun)
 			{
-			  ansp = add_text_record(header, nameoffset, ansp, daemon->local_ttl, 1, T_MX,  
-						 (daemon->options & OPT_SELFMX) ? name : daemon->mxtarget, NULL);
+			  ansp = add_resource_record(header, nameoffset, ansp, daemon->local_ttl, NULL, T_MX,
+					  	 1, (daemon->options & OPT_SELFMX) ? name : daemon->mxtarget);
+			  anscount++;
+			}
+		    }
+		}
+
+	      if (qtype == T_SRV || qtype == T_ANY)
+		{
+		  struct srv_record *srv;
+		  for (srv = daemon->srvnames; srv; srv = srv->next)
+		    if (hostname_isequal(name, srv->srvname))
+		      break;
+		  if (srv)
+		    {
+		      ans = 1;
+		      if (!dryrun)
+			{
+			  ansp = add_resource_record(header, nameoffset, ansp, daemon->local_ttl, NULL,
+					  	T_SRV, 1, 1, srv->srvport, srv->srvtarget);
 			  anscount++;
 			}
 		    }