mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 18:28:25 +00:00
--add-subnet option.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, ...);
|
||||
|
||||
@@ -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,7 +476,9 @@ 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)))
|
||||
{
|
||||
if (!is_sign)
|
||||
{
|
||||
unsigned short udpsz;
|
||||
unsigned char *psave = sizep;
|
||||
@@ -475,6 +488,14 @@ static size_t process_reply(struct dns_header *header, time_t now,
|
||||
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;
|
||||
@@ -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;
|
||||
|
||||
@@ -956,6 +980,16 @@ 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;
|
||||
}
|
||||
|
||||
14
src/option.c
14
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)
|
||||
|
||||
229
src/rfc1035.c
229
src/rfc1035.c
@@ -514,13 +514,79 @@ struct macparm {
|
||||
union mysockaddr *l3;
|
||||
};
|
||||
|
||||
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, plen)) ||
|
||||
!(p = skip_section(p,
|
||||
ntohs(header->ancount) + ntohs(header->nscount),
|
||||
header, plen)))
|
||||
return plen;
|
||||
*p++ = 0; /* empty name */
|
||||
PUTSHORT(T_OPT, p);
|
||||
PUTSHORT(daemon->edns_pktsz, p); /* max packet length */
|
||||
PUTLONG(0, p); /* extended RCODE */
|
||||
lenp = p;
|
||||
PUTSHORT(0, p); /* RDLEN */
|
||||
rdlen = 0;
|
||||
if (((ssize_t)optlen) > (limit - (p + 4)))
|
||||
return plen; /* Too big */
|
||||
header->arcount = htons(1);
|
||||
datap = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
int i, is_sign;
|
||||
unsigned short code, len;
|
||||
|
||||
if (ntohs(header->arcount) != 1 ||
|
||||
!(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign)) ||
|
||||
is_sign ||
|
||||
(!(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, plen, rdlen))
|
||||
return plen; /* bad packet */
|
||||
datap = p;
|
||||
|
||||
/* check if option already there */
|
||||
for (i = 0; i + 4 < rdlen; i += len + 4)
|
||||
{
|
||||
GETSHORT(code, p);
|
||||
GETSHORT(len, p);
|
||||
if (code == optno)
|
||||
return plen;
|
||||
p += len;
|
||||
}
|
||||
|
||||
if (((ssize_t)optlen) > (limit - (p + 4)))
|
||||
return plen; /* Too big */
|
||||
}
|
||||
|
||||
PUTSHORT(optno, p);
|
||||
PUTSHORT(optlen, p);
|
||||
memcpy(p, opt, optlen);
|
||||
p += optlen;
|
||||
|
||||
PUTSHORT(p - datap, lenp);
|
||||
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;
|
||||
unsigned short rdlen;
|
||||
struct dns_header *header = parm->header;
|
||||
unsigned char *lenp, *datap, *p;
|
||||
|
||||
if (family == parm->l3->sa.sa_family)
|
||||
{
|
||||
@@ -536,71 +602,11 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p
|
||||
if (!match)
|
||||
return 1; /* continue */
|
||||
|
||||
if (ntohs(header->arcount) == 0)
|
||||
{
|
||||
/* We are adding the pseudoheader */
|
||||
if (!(p = skip_questions(header, parm->plen)) ||
|
||||
!(p = skip_section(p,
|
||||
ntohs(header->ancount) + ntohs(header->nscount),
|
||||
header, parm->plen)))
|
||||
return 0;
|
||||
*p++ = 0; /* empty name */
|
||||
PUTSHORT(T_OPT, p);
|
||||
PUTSHORT(PACKETSZ, p); /* max packet length - is 512 suitable default for non-EDNS0 resolvers? */
|
||||
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 */
|
||||
header->arcount = htons(1);
|
||||
datap = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
int i, is_sign;
|
||||
unsigned short code, len;
|
||||
|
||||
if (ntohs(header->arcount) != 1 ||
|
||||
!(p = find_pseudoheader(header, parm->plen, NULL, NULL, &is_sign)) ||
|
||||
is_sign ||
|
||||
(!(p = skip_name(p, header, parm->plen, 10))))
|
||||
return 0;
|
||||
|
||||
p += 8; /* skip UDP length and RCODE */
|
||||
|
||||
lenp = p;
|
||||
GETSHORT(rdlen, p);
|
||||
if (!CHECK_LEN(header, p, parm->plen, rdlen))
|
||||
return 0; /* bad packet */
|
||||
datap = p;
|
||||
|
||||
/* check if option already there */
|
||||
for (i = 0; i + 4 < rdlen; i += len + 4)
|
||||
{
|
||||
GETSHORT(code, p);
|
||||
GETSHORT(len, p);
|
||||
if (code == EDNS0_OPTION_MAC)
|
||||
return 0;
|
||||
p += len;
|
||||
}
|
||||
|
||||
if (((ssize_t)maclen) > (parm->limit - (p + 4)))
|
||||
return 0; /* Too big */
|
||||
}
|
||||
|
||||
PUTSHORT(EDNS0_OPTION_MAC, p);
|
||||
PUTSHORT(maclen, p);
|
||||
memcpy(p, mac, maclen);
|
||||
p += maclen;
|
||||
|
||||
PUTSHORT(p - datap, lenp);
|
||||
parm->plen = p - (unsigned char *)header;
|
||||
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,6 +627,101 @@ 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)
|
||||
|
||||
Reference in New Issue
Block a user