summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2013-10-08 20:46:34 +0100
committerSimon Kelley <simon@thekelleys.org.uk>2013-10-08 20:46:34 +0100
commited4c0767b1f3a5182f5322adf33a5a460b500f98 (patch)
treeff64fec686c4daff38aee0285511dd5c9b3758fa
parent043c271f8a4bc7be157f938f5d581e79d9c1f4d2 (diff)
downloaddnsmasq-ed4c0767b1f3a5182f5322adf33a5a460b500f98.tar.gz
--add-subnet option.v2.67test18
-rw-r--r--CHANGELOG3
-rw-r--r--man/dnsmasq.815
-rw-r--r--src/config.h1
-rw-r--r--src/dns-protocol.h4
-rw-r--r--src/dnsmasq.h8
-rw-r--r--src/forward.c61
-rw-r--r--src/option.c14
-rw-r--r--src/rfc1035.c187
8 files changed, 234 insertions, 59 deletions
diff --git a/CHANGELOG b/CHANGELOG
index d76eb7a..d400d16 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -132,6 +132,9 @@ version 2.67
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
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 2b1570a..a0f903f 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -543,7 +543,20 @@ server. The MAC address can only be added if the requestor is on the same
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.
diff --git a/src/config.h b/src/config.h
index 5224adf..31ae1cb 100644
--- a/src/config.h
+++ b/src/config.h
@@ -39,7 +39,6 @@
#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 */
diff --git a/src/dns-protocol.h b/src/dns-protocol.h
index 80eff72..51ca6b9 100644
--- a/src/dns-protocol.h
+++ b/src/dns-protocol.h
@@ -56,6 +56,10 @@
#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;
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index b652a91..ed6f92f 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -222,7 +222,8 @@ struct event_desc {
#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. */
@@ -487,6 +488,7 @@ struct hostsfile {
#define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2
+#define FREC_HAS_SUBNET 4
struct frec {
union mysockaddr source;
@@ -802,6 +804,8 @@ extern struct daemon {
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;
@@ -962,6 +966,8 @@ unsigned int questions_crc(struct dns_header *header, size_t plen, char *buff);
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, ...);
diff --git a/src/forward.c b/src/forward.c
index 6c9f646..adc4a0f 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -284,6 +284,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
forward->fd = udpfd;
forward->crc = crc;
forward->forwardall = 0;
+ forward->flags = 0;
if (norebind)
forward->flags |= FREC_NOREBIND;
if (header->hb4 & HB4_CD)
@@ -331,6 +332,16 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
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.
@@ -435,8 +446,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
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;
@@ -465,19 +476,29 @@ static size_t process_reply(struct dns_header *header, time_t now,
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;
@@ -632,7 +653,8 @@ void reply_query(int fd, int family, time_t now)
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 */
@@ -876,7 +898,7 @@ unsigned char *tcp_request(int confd, time_t now,
{
size_t size = 0;
int norebind = 0;
- int checking_disabled;
+ int checking_disabled, check_subnet;
size_t m;
unsigned short qtype;
unsigned int gotname;
@@ -906,6 +928,8 @@ unsigned char *tcp_request(int confd, time_t now,
if (size < (int)sizeof(struct dns_header))
continue;
+ check_subnet = 0;
+
/* save state of "cd" flag in query */
checking_disabled = header->hb4 & HB4_CD;
@@ -955,7 +979,17 @@ unsigned char *tcp_request(int confd, time_t now,
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);
@@ -1056,7 +1090,8 @@ unsigned char *tcp_request(int confd, time_t now,
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;
}
diff --git a/src/option.c b/src/option.c
index 9b128cf..b8edf42 100644
--- a/src/option.c
+++ b/src/option.c
@@ -134,6 +134,7 @@ struct myoption {
#endif
#define LOPT_FAST_RA 322
#define LOPT_RELAY 323
+#define LOPT_ADD_SBNET 324
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -251,6 +252,7 @@ static const struct myoption 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 },
@@ -397,6 +399,7 @@ static struct {
{ 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 },
@@ -1424,6 +1427,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
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)
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 60ed068..5e7fe95 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -513,46 +513,30 @@ struct macparm {
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;
}
@@ -562,17 +546,17 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *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 */
@@ -580,27 +564,49 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p
{
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;
@@ -621,7 +627,102 @@ size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysock
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)
{