summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2014-07-29 16:34:14 +0100
committerSimon Kelley <simon@thekelleys.org.uk>2014-07-29 16:34:14 +0100
commitb5ea1cc2550226a3c19d169acf5d84d8b3a132fb (patch)
tree0787a9e37cbcf2c52c4e7cd5b4026401fd34be99
parent6d8e8ac0fa82c9cb771a3b54275589bc197e777a (diff)
downloaddnsmasq-b5ea1cc2550226a3c19d169acf5d84d8b3a132fb.tar.gz
Add --dns-loop-detect feature.
-rw-r--r--CHANGELOG7
-rw-r--r--Makefile2
-rw-r--r--bld/Android.mk3
-rw-r--r--man/dnsmasq.810
-rw-r--r--src/config.h17
-rw-r--r--src/dnsmasq.c9
-rw-r--r--src/dnsmasq.h17
-rw-r--r--src/forward.c29
-rw-r--r--src/loop.c116
-rw-r--r--src/network.c22
-rw-r--r--src/option.c7
-rw-r--r--src/util.c12
12 files changed, 233 insertions, 18 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 5b7dfb8..62f2e4a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -20,6 +20,13 @@ version 2.72
longer prefix length.) Thanks to Lung-Pin Chang for the
patch.
+ Add a mode which detects and removes DNS forwarding loops, ie
+ a query sent to an upstream server returns as a new query to
+ dnsmasq, and would therefore be forwarded again, resulting in
+ a query which loops many times before being dropped. Upstream
+ servers which loop back are disabled and this event is logged.
+ Thanks to Smoothwall for their sponsorship of this feature.
+
version 2.71
Subtle change to error handling to help DNSSEC validation
diff --git a/Makefile b/Makefile
index 17eeb27..58a7975 100644
--- a/Makefile
+++ b/Makefile
@@ -69,7 +69,7 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \
dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
- domain.o dnssec.o blockdata.o tables.o
+ domain.o dnssec.o blockdata.o tables.o loop.o
hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
dns-protocol.h radv-protocol.h ip6addr.h
diff --git a/bld/Android.mk b/bld/Android.mk
index 5255ec9..d855094 100644
--- a/bld/Android.mk
+++ b/bld/Android.mk
@@ -9,7 +9,8 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \
rfc2131.c tftp.c util.c conntrack.c \
dhcp6.c rfc3315.c dhcp-common.c outpacket.c \
radv.c slaac.c auth.c ipset.c domain.c \
- dnssec.c dnssec-openssl.c blockdata.c tables.c
+ dnssec.c dnssec-openssl.c blockdata.c tables.c \
+ loop.c
LOCAL_MODULE := dnsmasq
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 0530a19..7b4cc98 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -334,6 +334,16 @@ it will send queries to just one server. Setting this flag forces
dnsmasq to send all queries to all available servers. The reply from
the server which answers first will be returned to the original requester.
.TP
+.B --dns-loop-detect
+Enable code to detect DNS forwarding loops; ie the situation where a query sent to one
+of the upstream server eventually returns as a new query to the dnsmasq instance. The
+process works by generating TXT queries of the form <hex>.test and sending them to
+each upstream server. The hex is a UID which encodes the instance of dnsmasq sending the query
+and the upstream server to which it was sent. If the query returns to the server which sent it, then
+the upstream server through which it was sent is disabled and this event is logged. Each time the
+set of upstream servers changes, the test is re-run on all of them, including ones which
+were previously disabled.
+.TP
.B --stop-dns-rebind
Reject (and log) addresses from upstream nameservers which are in the
private IP ranges. This blocks an attack where a browser behind a
diff --git a/src/config.h b/src/config.h
index 87f8f8a..145820a 100644
--- a/src/config.h
+++ b/src/config.h
@@ -47,6 +47,8 @@
#define SOA_REFRESH 1200 /* SOA refresh default */
#define SOA_RETRY 180 /* SOA retry default */
#define SOA_EXPIRY 1209600 /* SOA expiry default */
+#define LOOP_TEST_DOMAIN "test" /* domain for loop testing, "test" is reserved by RFC 2606 and won't therefore clash */
+#define LOOP_TEST_TYPE T_TXT
/* compile-time options: uncomment below to enable or do eg.
make COPTS=-DHAVE_BROKEN_RTC
@@ -108,6 +110,10 @@ HAVE_AUTH
HAVE_DNSSEC
include DNSSEC validator.
+HAVE_LOOP
+ include functionality to probe for and remove DNS forwarding loops.
+
+
NO_IPV6
NO_TFTP
NO_DHCP
@@ -148,6 +154,7 @@ RESOLVFILE
#define HAVE_SCRIPT
#define HAVE_AUTH
#define HAVE_IPSET
+#define HAVE_LOOP
/* Build options which require external libraries.
@@ -342,6 +349,10 @@ HAVE_SOCKADDR_SA_LEN
#undef HAVE_IPSET
#endif
+#ifdef NO_LOOP
+#undef HAVE_LOOP
+#endif
+
/* Define a string indicating which options are in use.
DNSMASQP_COMPILE_OPTS is only defined in dnsmasq.c */
@@ -411,7 +422,11 @@ static char *compile_opts =
#ifndef HAVE_DNSSEC
"no-"
#endif
-"DNSSEC";
+"DNSSEC "
+#ifndef HAVE_LOOP
+"no-"
+#endif
+"loop-detect";
#endif
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 8b375de..f4a89fc 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -80,7 +80,9 @@ int main (int argc, char **argv)
sigaction(SIGPIPE, &sigact, NULL);
umask(022); /* known umask, create leases and pid files as 0644 */
-
+
+ rand_init(); /* Must precede read_opts() */
+
read_opts(argc, argv, compile_opts);
if (daemon->edns_pktsz < PACKETSZ)
@@ -186,7 +188,10 @@ int main (int argc, char **argv)
die(_("authoritative DNS not available: set HAVE_AUTH in src/config.h"), NULL, EC_BADCONF);
#endif
- rand_init();
+#ifndef HAVE_LOOP
+ if (option_bool(OPT_LOOP_DETECT))
+ die(_("Loop detection not available: set HAVE_LOOP in src/config.h"), NULL, EC_BADCONF);
+#endif
now = dnsmasq_time();
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index e70d10a..a1ac1d1 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -237,7 +237,8 @@ struct event_desc {
#define OPT_DNSSEC_DEBUG 47
#define OPT_DNSSEC_NO_SIGN 48
#define OPT_LOCAL_SERVICE 49
-#define OPT_LAST 50
+#define OPT_LOOP_DETECT 50
+#define OPT_LAST 51
/* 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. */
@@ -478,6 +479,7 @@ union mysockaddr {
#define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */
#define SERV_NO_REBIND 2048 /* inhibit dns-rebind protection */
#define SERV_FROM_FILE 4096 /* read from --servers-file */
+#define SERV_LOOP 8192 /* server causes forwarding loop */
struct serverfd {
int fd;
@@ -498,6 +500,9 @@ struct server {
char *domain; /* set if this server only handles a domain. */
int flags, tcpfd;
unsigned int queries, failed_queries;
+#ifdef HAVE_LOOP
+ u32 uid;
+#endif
struct server *next;
};
@@ -1123,6 +1128,7 @@ unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name
/* util.c */
void rand_init(void);
unsigned short rand16(void);
+u32 rand32(void);
u64 rand64(void);
int legal_hostname(char *c);
char *canonicalise(char *s, int *nomem);
@@ -1188,6 +1194,8 @@ int send_from(int fd, int nowild, char *packet, size_t len,
union mysockaddr *to, struct all_addr *source,
unsigned int iface);
void resend_query();
+struct randfd *allocate_rfd(int family);
+void free_rfd(struct randfd *rfd);
/* network.c */
int indextoname(int fd, int index, char *name);
@@ -1453,3 +1461,10 @@ void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force);
time_t periodic_slaac(time_t now, struct dhcp_lease *leases);
void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases);
#endif
+
+/* loop.c */
+#ifdef HAVE_LOOP
+void loop_send_probes();
+int detect_loop(char *query, int type);
+#endif
+
diff --git a/src/forward.c b/src/forward.c
index 1a657bb..3afd1b1 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -22,7 +22,6 @@ static struct frec *lookup_frec_by_sender(unsigned short id,
void *hash);
static unsigned short get_id(void);
static void free_frec(struct frec *f);
-static struct randfd *allocate_rfd(int family);
#ifdef HAVE_DNSSEC
static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
@@ -427,7 +426,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (type == (start->flags & SERV_TYPE) &&
(type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) &&
- !(start->flags & SERV_LITERAL_ADDRESS))
+ !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
{
int fd;
@@ -1271,6 +1270,12 @@ void receive_query(struct listener *listen, time_t now)
break;
}
#endif
+
+#ifdef HAVE_LOOP
+ /* Check for forwarding loop */
+ if (detect_loop(daemon->namebuff, type))
+ return;
+#endif
}
#ifdef HAVE_AUTH
@@ -1782,7 +1787,8 @@ unsigned char *tcp_request(int confd, time_t now,
/* server for wrong domain */
if (type != (last_server->flags & SERV_TYPE) ||
- (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)))
+ (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)) ||
+ (last_server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
continue;
if (last_server->tcpfd == -1)
@@ -1958,7 +1964,7 @@ static struct frec *allocate_frec(time_t now)
return f;
}
-static struct randfd *allocate_rfd(int family)
+struct randfd *allocate_rfd(int family)
{
static int finger = 0;
int i;
@@ -1993,19 +1999,22 @@ static struct randfd *allocate_rfd(int family)
return NULL; /* doom */
}
+
+void free_rfd(struct randfd *rfd)
+{
+ if (rfd && --(rfd->refcount) == 0)
+ close(rfd->fd);
+}
+
static void free_frec(struct frec *f)
{
- if (f->rfd4 && --(f->rfd4->refcount) == 0)
- close(f->rfd4->fd);
-
+ free_rfd(f->rfd4);
f->rfd4 = NULL;
f->sentto = NULL;
f->flags = 0;
#ifdef HAVE_IPV6
- if (f->rfd6 && --(f->rfd6->refcount) == 0)
- close(f->rfd6->fd);
-
+ free_rfd(f->rfd6);
f->rfd6 = NULL;
#endif
diff --git a/src/loop.c b/src/loop.c
new file mode 100644
index 0000000..bb377ad
--- /dev/null
+++ b/src/loop.c
@@ -0,0 +1,116 @@
+/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_LOOP
+static ssize_t loop_make_probe(u32 uid);
+
+void loop_send_probes()
+{
+ struct server *serv;
+
+ if (!option_bool(OPT_LOOP_DETECT))
+ return;
+
+ /* Loop through all upstream servers not for particular domains, and send a query to that server which is
+ identifiable, via the uid. If we see that query back again, then the server is looping, and we should not use it. */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (!(serv->flags &
+ (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_LOOP)))
+ {
+ ssize_t len = loop_make_probe(serv->uid);
+ int fd;
+ struct randfd *rfd = NULL;
+
+ if (serv->sfd)
+ fd = serv->sfd->fd;
+ else
+ {
+ if (!(rfd = allocate_rfd(serv->addr.sa.sa_family)))
+ continue;
+ fd = rfd->fd;
+ }
+
+ while (sendto(fd, daemon->packet, len, 0, &serv->addr.sa, sa_len(&serv->addr)) == -1 && retry_send());
+
+ free_rfd(rfd);
+ }
+}
+
+static ssize_t loop_make_probe(u32 uid)
+{
+ struct dns_header *header = (struct dns_header *)daemon->packet;
+ unsigned char *p = (unsigned char *)(header+1);
+
+ /* packet buffer overwritten */
+ daemon->srv_save = NULL;
+
+ header->id = rand16();
+ header->ancount = header->nscount = header->arcount = htons(0);
+ header->qdcount = htons(1);
+ header->hb3 = HB3_RD;
+ header->hb4 = 0;
+ SET_OPCODE(header, QUERY);
+
+ *p++ = 8;
+ sprintf((char *)p, "%.8x", uid);
+ p += 8;
+ *p++ = strlen(LOOP_TEST_DOMAIN);
+ strcpy((char *)p, LOOP_TEST_DOMAIN); /* Add terminating zero */
+ p += strlen(LOOP_TEST_DOMAIN) + 1;
+
+ PUTSHORT(LOOP_TEST_TYPE, p);
+ PUTSHORT(C_IN, p);
+
+ return p - (unsigned char *)header;
+}
+
+
+int detect_loop(char *query, int type)
+{
+ int i;
+ u32 uid;
+ struct server *serv;
+
+ if (!option_bool(OPT_LOOP_DETECT))
+ return 0;
+
+ if (type != LOOP_TEST_TYPE ||
+ strlen(LOOP_TEST_DOMAIN) + 9 != strlen(query) ||
+ strstr(query, LOOP_TEST_DOMAIN) != query + 9)
+ return 0;
+
+ for (i = 0; i < 8; i++)
+ if (!isxdigit(query[i]))
+ return 0;
+
+ uid = strtol(query, NULL, 16);
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (!(serv->flags &
+ (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_LOOP)) &&
+ uid == serv->uid)
+ {
+ serv->flags |= SERV_LOOP;
+ check_servers(); /* log new state */
+ return 1;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/src/network.c b/src/network.c
index b188f50..9c46b8a 100644
--- a/src/network.c
+++ b/src/network.c
@@ -1298,7 +1298,13 @@ void mark_servers(int flag)
/* mark everything with argument flag */
for (serv = daemon->servers; serv; serv = serv->next)
if (serv->flags & flag)
- serv->flags |= SERV_MARK;
+ {
+ serv->flags |= SERV_MARK;
+#ifdef HAVE_LOOP
+ /* Give looped servers another chance */
+ serv->flags &= ~SERV_LOOP;
+#endif
+ }
}
void cleanup_servers(void)
@@ -1320,6 +1326,11 @@ void cleanup_servers(void)
else
up = &serv->next;
}
+
+#ifdef HAVE_LOOP
+ /* Now we have a new set of servers, test for loops. */
+ loop_send_probes();
+#endif
}
void add_update_server(int flags,
@@ -1385,7 +1396,10 @@ void add_update_server(int flags,
serv->domain = domain_str;
serv->next = next;
serv->queries = serv->failed_queries = 0;
-
+#ifdef HAVE_LOOP
+ serv->uid = rand32();
+#endif
+
if (domain)
serv->flags |= SERV_HAS_DOMAIN;
@@ -1464,6 +1478,10 @@ void check_servers(void)
else if (!(serv->flags & SERV_LITERAL_ADDRESS))
my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s"), daemon->namebuff, port, s1, s2);
}
+#ifdef HAVE_LOOP
+ else if (serv->flags & SERV_LOOP)
+ my_syslog(LOG_INFO, _("NOT using nameserver %s#%d - query loop detected"), daemon->namebuff, port);
+#endif
else if (serv->interface[0] != 0)
my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface);
else
diff --git a/src/option.c b/src/option.c
index a0a6e46..07e7d36 100644
--- a/src/option.c
+++ b/src/option.c
@@ -146,6 +146,7 @@ struct myoption {
#define LOPT_DNSSEC_CHECK 334
#define LOPT_LOCAL_SERVICE 335
#define LOPT_DNSSEC_TIME 336
+#define LOPT_LOOP_DETECT 337
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -297,6 +298,7 @@ static const struct myoption opts[] =
{ "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP },
{ "quiet-dhcp6", 0, 0, LOPT_QUIET_DHCP6 },
{ "quiet-ra", 0, 0, LOPT_QUIET_RA },
+ { "dns-loop-detect", 0, 0, LOPT_LOOP_DETECT },
{ NULL, 0, 0, 0 }
};
@@ -454,6 +456,7 @@ static struct {
{ LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
{ LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL },
{ LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks"), NULL },
+ { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops"), NULL },
{ 0, 0, NULL, NULL, NULL }
};
@@ -2171,6 +2174,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
{
newlist = opt_malloc(sizeof(struct server));
memset(newlist, 0, sizeof(struct server));
+#ifdef HAVE_LOOP
+ newlist->uid = rand32();
+#endif
}
if (servers_only && option == 'S')
@@ -4269,6 +4275,7 @@ void read_opts(int argc, char **argv, char *compile_opts)
daemon->soa_refresh = SOA_REFRESH;
daemon->soa_retry = SOA_RETRY;
daemon->soa_expiry = SOA_EXPIRY;
+
add_txt("version.bind", "dnsmasq-" VERSION, 0 );
add_txt("authors.bind", "Simon Kelley", 0);
add_txt("copyright.bind", COPYRIGHT, 0);
diff --git a/src/util.c b/src/util.c
index 660347f..df751c7 100644
--- a/src/util.c
+++ b/src/util.c
@@ -81,6 +81,18 @@ unsigned short rand16(void)
return (unsigned short) out[--outleft];
}
+u32 rand32(void)
+{
+ if (!outleft)
+ {
+ if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3];
+ surf();
+ outleft = 8;
+ }
+
+ return out[--outleft];
+}
+
u64 rand64(void)
{
static int outleft = 0;