[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++;
}
}