From c8a80487cd90d30b109bfdc66252ab87e25b1bd4 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 5 Mar 2014 14:29:54 +0000 Subject: --local-service. Default protection from DNS amplification attacks. --- CHANGELOG | 11 ++++++++++- man/dnsmasq.8 | 8 ++++++++ src/dnsmasq.h | 4 +++- src/forward.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/network.c | 42 ++++++++++++++++++++++++++++++++++++++++ src/option.c | 8 ++++++++ 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= 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")); -- cgit v1.2.1