summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2014-03-05 14:29:54 +0000
committerSimon Kelley <simon@thekelleys.org.uk>2014-03-05 14:29:54 +0000
commitc8a80487cd90d30b109bfdc66252ab87e25b1bd4 (patch)
tree50539aa68facf6a134dd4637fb24cb54f5bc3cca
parent4ea8e80dd9b852a2d3a9e44d00e46ec31184082e (diff)
downloaddnsmasq-c8a80487cd90d30b109bfdc66252ab87e25b1bd4.tar.gz
--local-service. Default protection from DNS amplification attacks.
-rw-r--r--CHANGELOG11
-rw-r--r--man/dnsmasq.88
-rw-r--r--src/dnsmasq.h4
-rw-r--r--src/forward.c62
-rw-r--r--src/network.c42
-rw-r--r--src/option.c8
6 files changed, 133 insertions, 2 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 806fc8e..4c89fa9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -67,7 +67,16 @@ version 2.69
Add --servers-file. Allows dynamic update of upstream servers
full access to configuration.
-
+ Add --local-service. Accept DNS queries only from hosts
+ whose address is on a local subnet, ie a subnet for which
+ an interface exists on the server. This option
+ only has effect is there are no --interface --except-interface,
+ --listen-address or --auth-server options. It is intended
+ to be set as a default on installation, to allow
+ unconfigured installations to be useful but also safe from
+ being used for DNS amplification attacks.
+
+
version 2.68
Use random addresses for DHCPv6 temporary address
allocations, instead of algorithmically determined stable
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 975ccd4..87730df 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -208,6 +208,14 @@ resolve in the global DNS to a A and/or AAAA record which points to
the address dnsmasq is listening on. When an interface is specified,
it may be qualified with "/4" or "/6" to specify only the IPv4 or IPv6
addresses associated with the interface.
+.TP
+.B --local-service
+Accept DNS queries only from hosts whose address is on a local subnet,
+ie a subnet for which an interface exists on the server. This option
+only has effect is there are no --interface --except-interface,
+--listen-address or --auth-server options. It is intended to be set as
+a default on installation, to allow unconfigured installations to be
+useful but also safe from being used for DNS amplification attacks.
.TP
.B \-2, --no-dhcp-interface=<interface name>
Do not provide DHCP or TFTP on the specified interface, but do provide DNS service.
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index a00d95c..6a0391d 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -233,7 +233,8 @@ struct event_desc {
#define OPT_DNSSEC_PERMISS 46
#define OPT_DNSSEC_DEBUG 47
#define OPT_DNSSEC_NO_SIGN 48
-#define OPT_LAST 49
+#define OPT_LOCAL_SERVICE 49
+#define OPT_LAST 50
/* 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. */
@@ -966,6 +967,7 @@ extern struct daemon {
pid_t tcp_pids[MAX_PROCS];
struct randfd randomsocks[RANDOM_SOCKS];
int v6pktinfo;
+ struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */
/* DHCP state */
int dhcpfd, helperfd, pxefd;
diff --git a/src/forward.c b/src/forward.c
index 7916716..b396aa4 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -1081,6 +1081,37 @@ void receive_query(struct listener *listen, time_t now)
source_addr.in6.sin6_flowinfo = 0;
#endif
+ /* We can be configured to only accept queries from at-most-one-hop-away addresses. */
+ if (option_bool(OPT_LOCAL_SERVICE))
+ {
+ struct addrlist *addr;
+#ifdef HAVE_IPV6
+ if (listen->family == AF_INET6)
+ {
+ for (addr = daemon->interface_addrs; addr; addr = addr->next)
+ if ((addr->flags & ADDRLIST_IPV6) &&
+ is_same_net6(&addr->addr.addr.addr6, &source_addr.in6.sin6_addr, addr->prefixlen))
+ break;
+ }
+ else
+#endif
+ {
+ struct in_addr netmask;
+ for (addr = daemon->interface_addrs; addr; addr = addr->next)
+ {
+ netmask.s_addr = 0xffffffff << (32 - addr->prefixlen);
+ if (!(addr->flags & ADDRLIST_IPV6) &&
+ is_same_net(addr->addr.addr.addr4, source_addr.in.sin_addr, netmask))
+ break;
+ }
+ }
+ if (!addr)
+ {
+ my_syslog(LOG_WARNING, _("Ignoring query from non-local network"));
+ return;
+ }
+ }
+
if (check_dst)
{
struct ifreq ifr;
@@ -1544,6 +1575,37 @@ unsigned char *tcp_request(int confd, time_t now,
if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
return packet;
+
+ /* We can be configured to only accept queries from at-most-one-hop-away addresses. */
+ if (option_bool(OPT_LOCAL_SERVICE))
+ {
+ struct addrlist *addr;
+#ifdef HAVE_IPV6
+ if (peer_addr.sa.sa_family == AF_INET6)
+ {
+ for (addr = daemon->interface_addrs; addr; addr = addr->next)
+ if ((addr->flags & ADDRLIST_IPV6) &&
+ is_same_net6(&addr->addr.addr.addr6, &peer_addr.in6.sin6_addr, addr->prefixlen))
+ break;
+ }
+ else
+#endif
+ {
+ struct in_addr netmask;
+ for (addr = daemon->interface_addrs; addr; addr = addr->next)
+ {
+ netmask.s_addr = 0xffffffff << (32 - addr->prefixlen);
+ if (!(addr->flags & ADDRLIST_IPV6) &&
+ is_same_net(addr->addr.addr.addr4, peer_addr.in.sin_addr, netmask))
+ break;
+ }
+ }
+ if (!addr)
+ {
+ my_syslog(LOG_WARNING, _("Ignoring query from non-local network"));
+ return packet;
+ }
+ }
while (1)
{
diff --git a/src/network.c b/src/network.c
index a4380ae..3cc5a4d 100644
--- a/src/network.c
+++ b/src/network.c
@@ -268,7 +268,40 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label,
if (!label)
label = ifr.ifr_name;
+
+ /* maintain a list of all addresses on all interfaces for --local-service option */
+ if (option_bool(OPT_LOCAL_SERVICE))
+ {
+ struct addrlist *al;
+ if (param->spare)
+ {
+ al = param->spare;
+ param->spare = al->next;
+ }
+ else
+ al = whine_malloc(sizeof(struct addrlist));
+
+ if (al)
+ {
+ al->next = daemon->interface_addrs;
+ daemon->interface_addrs = al;
+ al->prefixlen = prefixlen;
+
+ if (addr->sa.sa_family == AF_INET)
+ {
+ al->addr.addr.addr4 = addr->in.sin_addr;
+ al->flags = 0;
+ }
+#ifdef HAVE_IPV6
+ else
+ {
+ al->addr.addr.addr6 = addr->in6.sin6_addr;
+ al->flags = ADDRLIST_IPV6;
+ }
+#endif
+ }
+ }
#ifdef HAVE_IPV6
if (addr->sa.sa_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr))
@@ -565,6 +598,15 @@ int enumerate_interfaces(int reset)
intname->addr = NULL;
}
+ /* Remove list of addresses of local interfaces */
+ for (addr = daemon->interface_addrs; addr; addr = tmp)
+ {
+ tmp = addr->next;
+ addr->next = spare;
+ spare = addr;
+ }
+ daemon->interface_addrs = NULL;
+
#ifdef HAVE_AUTH
/* remove addresses stored against auth_zone subnets, but not
ones configured as address literals */
diff --git a/src/option.c b/src/option.c
index b898231..e8ef5fa 100644
--- a/src/option.c
+++ b/src/option.c
@@ -144,6 +144,7 @@ struct myoption {
#define LOPT_REV_SERV 332
#define LOPT_SERVERS_FILE 333
#define LOPT_DNSSEC_CHECK 334
+#define LOPT_LOCAL_SERVICE 335
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -175,6 +176,7 @@ static const struct myoption opts[] =
{ "domain-suffix", 1, 0, 's' },
{ "interface", 1, 0, 'i' },
{ "listen-address", 1, 0, 'a' },
+ { "local-service", 0, 0, LOPT_LOCAL_SERVICE },
{ "bogus-priv", 0, 0, 'b' },
{ "bogus-nxdomain", 1, 0, 'B' },
{ "selfmx", 0, 0, 'e' },
@@ -448,6 +450,7 @@ static struct {
{ LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL },
{ 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 },
{ 0, 0, NULL, NULL, NULL }
};
@@ -4457,6 +4460,11 @@ void read_opts(int argc, char **argv, char *compile_opts)
else if (option_bool(OPT_DHCP_FQDN))
die(_("there must be a default domain when --dhcp-fqdn is set"), NULL, EC_BADCONF);
+ /* If there's access-control config, then ignore --local-service, it's intended
+ as a system default to keep otherwise unconfigured installations safe. */
+ if (daemon->if_names || daemon->if_except || daemon->if_addrs || daemon->authserver)
+ reset_option_bool(OPT_LOCAL_SERVICE);
+
if (testmode)
{
fprintf(stderr, "dnsmasq: %s.\n", _("syntax check OK"));