Update Spanish transalation. Thanks to Vicente Soriano.
+ Add --add-subnet configuration, to tell upstream DNS
+ servers where the original client is.
+
version 2.66
Add the ability to act as an authoritative DNS
subnet as the dnsmasq server. Note that the mechanism used to achieve this (an EDNS0 option)
is not yet standardised, so this should be considered
experimental. Also note that exposing MAC addresses in this way may
-have security and privacy implications.
+have security and privacy implications. The warning about caching
+given for --add-subnet applies to --add-mac too.
+.TP
+.B --add-subnet[[=<IPv4 prefix length>],<IPv6 prefix length>]
+Add the subnet address of the requestor to the DNS queries which are
+forwarded upstream. The amount of the address forwarded depends on the
+prefix length parameter: 32 (128 for IPv6) forwards the whole address,
+zero forwards none of it but still marks the request so that no
+upstream nameserver will add client address information either. The
+default is zero for both IPv4 and IPv6. Note that upstream nameservers
+may be configured to return different results based on this
+information, but the dnsmasq cache does not take account. If a dnsmasq
+instance is configured such that different results may be encountered,
+caching should be disabled.
.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.
#define TFTP_MAX_CONNECTIONS 50 /* max simultaneous connections */
#define LOG_MAX 5 /* log-queue length */
#define RANDFILE "/dev/urandom"
-#define EDNS0_OPTION_MAC 5 /* dyndns.org temporary assignment */
#define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" /* Default - may be overridden by config */
#define DNSMASQ_PATH "/uk/org/thekelleys/dnsmasq"
#define AUTH_TTL 600 /* default TTL for auth DNS */
#define T_MAILB 253
#define T_ANY 255
+#define EDNS0_OPTION_MAC 65001 /* dyndns.org temporary assignment */
+#define EDNS0_OPTION_CLIENT_SUBNET 5 /* IANA */
+
+
struct dns_header {
u16 id;
u8 hb3,hb4;
#define OPT_CLEVERBIND 39
#define OPT_TFTP 40
#define OPT_FAST_RA 41
-#define OPT_LAST 42
+#define OPT_CLIENT_SUBNET 42
+#define OPT_LAST 43
/* 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. */
#define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2
+#define FREC_HAS_SUBNET 4
struct frec {
union mysockaddr source;
struct auth_zone *auth_zones;
struct interface_name *int_names;
char *mxtarget;
+ int addr4_netmask;
+ int addr6_netmask;
char *lease_file;
char *username, *groupname, *scriptuser;
char *luascript;
size_t resize_packet(struct dns_header *header, size_t plen,
unsigned char *pheader, size_t hlen);
size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3);
+size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source);
+int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
int add_resource_record(struct dns_header *header, char *limit, int *truncp,
int nameoffset, unsigned char **pp, unsigned long ttl,
int *offset, unsigned short type, unsigned short class, char *format, ...);
forward->fd = udpfd;
forward->crc = crc;
forward->forwardall = 0;
+ forward->flags = 0;
if (norebind)
forward->flags |= FREC_NOREBIND;
if (header->hb4 & HB4_CD)
if (option_bool(OPT_ADD_MAC))
plen = add_mac(header, plen, ((char *) header) + PACKETSZ, &forward->source);
+ if (option_bool(OPT_CLIENT_SUBNET))
+ {
+ size_t new = add_source_addr(header, plen, ((char *) header) + PACKETSZ, &forward->source);
+ if (new != plen)
+ {
+ plen = new;
+ forward->flags |= FREC_HAS_SUBNET;
+ }
+ }
+
while (1)
{
/* only send to servers dealing with our domain.
return 0;
}
-static size_t process_reply(struct dns_header *header, time_t now,
- struct server *server, size_t n, int check_rebind, int checking_disabled)
+static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind,
+ int checking_disabled, int check_subnet, union mysockaddr *query_source)
{
unsigned char *pheader, *sizep;
char **sets = 0;
than we allow, trim it so that we don't get overlarge
requests for the client. We can't do this for signed packets. */
- if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)) && !is_sign)
+ if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)))
{
- unsigned short udpsz;
- unsigned char *psave = sizep;
+ if (!is_sign)
+ {
+ unsigned short udpsz;
+ unsigned char *psave = sizep;
+
+ GETSHORT(udpsz, sizep);
+ if (udpsz > daemon->edns_pktsz)
+ PUTSHORT(daemon->edns_pktsz, psave);
+ }
- GETSHORT(udpsz, sizep);
- if (udpsz > daemon->edns_pktsz)
- PUTSHORT(daemon->edns_pktsz, psave);
+ if (check_subnet && !check_source(header, plen, pheader, query_source))
+ {
+ my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch"));
+ return 0;
+ }
}
+
/* RFC 4035 sect 4.6 para 3 */
if (!is_sign && !option_bool(OPT_DNSSEC))
- header->hb4 &= ~HB4_AD;
+ header->hb4 &= ~HB4_AD;
if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN))
return n;
if (!option_bool(OPT_NO_REBIND))
check_rebind = 0;
- if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED)))
+ if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED,
+ forward->flags & FREC_HAS_SUBNET, &forward->source)))
{
header->id = htons(forward->orig_id);
header->hb4 |= HB4_RA; /* recursion if available */
{
size_t size = 0;
int norebind = 0;
- int checking_disabled;
+ int checking_disabled, check_subnet;
size_t m;
unsigned short qtype;
unsigned int gotname;
if (size < (int)sizeof(struct dns_header))
continue;
+ check_subnet = 0;
+
/* save state of "cd" flag in query */
checking_disabled = header->hb4 & HB4_CD;
if (option_bool(OPT_ADD_MAC))
size = add_mac(header, size, ((char *) header) + 65536, &peer_addr);
-
+
+ if (option_bool(OPT_CLIENT_SUBNET))
+ {
+ size_t new = add_source_addr(header, size, ((char *) header) + 65536, &peer_addr);
+ if (size != new)
+ {
+ size = new;
+ check_subnet = 1;
+ }
+ }
+
if (gotname)
flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
sending replies containing questions and bogus answers. */
if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff))
m = process_reply(header, now, last_server, (unsigned int)m,
- option_bool(OPT_NO_REBIND) && !norebind, checking_disabled);
+ option_bool(OPT_NO_REBIND) && !norebind, checking_disabled,
+ check_subnet, &peer_addr);
break;
}
#endif
#define LOPT_FAST_RA 322
#define LOPT_RELAY 323
+#define LOPT_ADD_SBNET 324
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
{ "dhcp-generate-names", 2, 0, LOPT_GEN_NAMES },
{ "rebind-localhost-ok", 0, 0, LOPT_LOC_REBND },
{ "add-mac", 0, 0, LOPT_ADD_MAC },
+ { "add-subnet", 2, 0, LOPT_ADD_SBNET },
{ "proxy-dnssec", 0, 0, LOPT_DNSSEC },
{ "dhcp-sequential-ip", 0, 0, LOPT_INCR_ADDR },
{ "conntrack", 0, 0, LOPT_CONNTRACK },
{ LOPT_PXE_SERV, ARG_DUP, "<service>", gettext_noop("Boot service for PXE menu."), NULL },
{ LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL },
{ LOPT_ADD_MAC, OPT_ADD_MAC, NULL, gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL },
+ { LOPT_ADD_SBNET, ARG_ONE, "<v4 pref>[,<v6 pref>]", gettext_noop("Add requestor's IP subnet to forwarded DNS queries."), NULL },
{ LOPT_DNSSEC, OPT_DNSSEC, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL },
{ LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL },
{ LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL },
break;
}
+ case LOPT_ADD_SBNET: /* --add-subnet */
+ set_option_bool(OPT_CLIENT_SUBNET);
+ if (arg)
+ {
+ comma = split(arg);
+ if (!atoi_check(arg, &daemon->addr4_netmask) ||
+ (comma && !atoi_check(comma, &daemon->addr6_netmask)))
+ ret_err(gen_err);
+ }
+ break;
+
case '1': /* --enable-dbus */
set_option_bool(OPT_DBUS);
if (arg)
size_t plen;
union mysockaddr *l3;
};
-
-static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv)
-{
- struct macparm *parm = parmv;
- int match = 0;
- unsigned short rdlen;
- struct dns_header *header = parm->header;
- unsigned char *lenp, *datap, *p;
-
- if (family == parm->l3->sa.sa_family)
- {
- if (family == AF_INET && memcmp (&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0)
- match = 1;
-#ifdef HAVE_IPV6
- else
- if (family == AF_INET6 && memcmp (&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0)
- match = 1;
-#endif
- }
- if (!match)
- return 1; /* continue */
+static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
+ int optno, unsigned char *opt, size_t optlen)
+{
+ unsigned char *lenp, *datap, *p;
+ int rdlen;
if (ntohs(header->arcount) == 0)
{
/* We are adding the pseudoheader */
- if (!(p = skip_questions(header, parm->plen)) ||
+ if (!(p = skip_questions(header, plen)) ||
!(p = skip_section(p,
ntohs(header->ancount) + ntohs(header->nscount),
- header, parm->plen)))
- return 0;
+ header, plen)))
+ return plen;
*p++ = 0; /* empty name */
PUTSHORT(T_OPT, p);
- PUTSHORT(PACKETSZ, p); /* max packet length - is 512 suitable default for non-EDNS0 resolvers? */
+ PUTSHORT(daemon->edns_pktsz, p); /* max packet length */
PUTLONG(0, p); /* extended RCODE */
lenp = p;
PUTSHORT(0, p); /* RDLEN */
rdlen = 0;
- if (((ssize_t)maclen) > (parm->limit - (p + 4)))
- return 0; /* Too big */
+ if (((ssize_t)optlen) > (limit - (p + 4)))
+ return plen; /* Too big */
header->arcount = htons(1);
datap = p;
}
unsigned short code, len;
if (ntohs(header->arcount) != 1 ||
- !(p = find_pseudoheader(header, parm->plen, NULL, NULL, &is_sign)) ||
+ !(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign)) ||
is_sign ||
- (!(p = skip_name(p, header, parm->plen, 10))))
- return 0;
+ (!(p = skip_name(p, header, plen, 10))))
+ return plen;
p += 8; /* skip UDP length and RCODE */
lenp = p;
GETSHORT(rdlen, p);
- if (!CHECK_LEN(header, p, parm->plen, rdlen))
- return 0; /* bad packet */
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return plen; /* bad packet */
datap = p;
/* check if option already there */
{
GETSHORT(code, p);
GETSHORT(len, p);
- if (code == EDNS0_OPTION_MAC)
- return 0;
+ if (code == optno)
+ return plen;
p += len;
}
- if (((ssize_t)maclen) > (parm->limit - (p + 4)))
- return 0; /* Too big */
+ if (((ssize_t)optlen) > (limit - (p + 4)))
+ return plen; /* Too big */
}
- PUTSHORT(EDNS0_OPTION_MAC, p);
- PUTSHORT(maclen, p);
- memcpy(p, mac, maclen);
- p += maclen;
+ PUTSHORT(optno, p);
+ PUTSHORT(optlen, p);
+ memcpy(p, opt, optlen);
+ p += optlen;
PUTSHORT(p - datap, lenp);
- parm->plen = p - (unsigned char *)header;
+ return p - (unsigned char *)header;
+
+}
+
+static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv)
+{
+ struct macparm *parm = parmv;
+ int match = 0;
+
+ if (family == parm->l3->sa.sa_family)
+ {
+ if (family == AF_INET && memcmp (&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0)
+ match = 1;
+#ifdef HAVE_IPV6
+ else
+ if (family == AF_INET6 && memcmp (&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0)
+ match = 1;
+#endif
+ }
+
+ if (!match)
+ return 1; /* continue */
+
+ parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen);
return 0; /* done */
}
-
size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3)
{
struct macparm parm;
return parm.plen;
}
-
+struct subnet_opt {
+ u16 family;
+ u8 source_netmask, scope_netmask;
+#ifdef HAVE_IPV6
+ u8 addr[IN6ADDRSZ];
+#else
+ u8 addr[INADDRSZ];
+#endif
+};
+
+size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
+{
+ /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
+
+ int len;
+ void *addrp;
+
+ if (source->sa.sa_family == AF_INET)
+ {
+ opt->family = htons(1);
+ opt->source_netmask = daemon->addr4_netmask;
+ addrp = &source->in.sin_addr;
+ }
+#ifdef HAVE_IPV6
+ else
+ {
+ opt->family = htons(2);
+ opt->source_netmask = daemon->addr6_netmask;
+ addrp = &source->in6.sin6_addr;
+ }
+#endif
+
+ opt->scope_netmask = 0;
+ len = 0;
+
+ if (opt->source_netmask != 0)
+ {
+ len = ((opt->source_netmask - 1) >> 3) + 1;
+ memcpy(opt->addr, addrp, len);
+ if (opt->source_netmask & 7)
+ opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7));
+ }
+
+ return len + 4;
+}
+
+size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source)
+{
+ /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
+
+ int len;
+ struct subnet_opt opt;
+
+ len = calc_subnet_opt(&opt, source);
+ return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len);
+}
+
+int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer)
+{
+ /* Section 9.2, Check that subnet option in reply matches. */
+
+
+ int len, calc_len;
+ struct subnet_opt opt;
+ unsigned char *p;
+ int code, i, rdlen;
+
+ calc_len = calc_subnet_opt(&opt, peer);
+
+ if (!(p = skip_name(pseudoheader, header, plen, 10)))
+ return 1;
+
+ p += 8; /* skip UDP length and RCODE */
+
+ GETSHORT(rdlen, p);
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return 1; /* bad packet */
+
+ /* check if option there */
+ for (i = 0; i + 4 < rdlen; i += len + 4)
+ {
+ GETSHORT(code, p);
+ GETSHORT(len, p);
+ if (code == EDNS0_OPTION_CLIENT_SUBNET)
+ {
+ /* make sure this doesn't mismatch. */
+ opt.scope_netmask = p[3];
+ if (len != calc_len || memcmp(p, &opt, len) != 0)
+ return 0;
+ }
+ p += len;
+ }
+
+ return 1;
+}
+
/* is addr in the non-globally-routed IP space? */
static int private_net(struct in_addr addr, int ban_localhost)
{