Commit c8a80487 authored by Simon Kelley's avatar Simon Kelley

--local-service. Default protection from DNS amplification attacks.

parent 4ea8e80d
...@@ -67,7 +67,16 @@ version 2.69 ...@@ -67,7 +67,16 @@ version 2.69
Add --servers-file. Allows dynamic update of upstream servers Add --servers-file. Allows dynamic update of upstream servers
full access to configuration. 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 version 2.68
Use random addresses for DHCPv6 temporary address Use random addresses for DHCPv6 temporary address
allocations, instead of algorithmically determined stable allocations, instead of algorithmically determined stable
......
...@@ -208,6 +208,14 @@ resolve in the global DNS to a A and/or AAAA record which points to ...@@ -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, 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 it may be qualified with "/4" or "/6" to specify only the IPv4 or IPv6
addresses associated with the interface. 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 .TP
.B \-2, --no-dhcp-interface=<interface name> .B \-2, --no-dhcp-interface=<interface name>
Do not provide DHCP or TFTP on the specified interface, but do provide DNS service. Do not provide DHCP or TFTP on the specified interface, but do provide DNS service.
......
...@@ -233,7 +233,8 @@ struct event_desc { ...@@ -233,7 +233,8 @@ struct event_desc {
#define OPT_DNSSEC_PERMISS 46 #define OPT_DNSSEC_PERMISS 46
#define OPT_DNSSEC_DEBUG 47 #define OPT_DNSSEC_DEBUG 47
#define OPT_DNSSEC_NO_SIGN 48 #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 /* 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. */ not to occupy the same bits as priorities, no matter how syslog.h is set up. */
...@@ -966,6 +967,7 @@ extern struct daemon { ...@@ -966,6 +967,7 @@ extern struct daemon {
pid_t tcp_pids[MAX_PROCS]; pid_t tcp_pids[MAX_PROCS];
struct randfd randomsocks[RANDOM_SOCKS]; struct randfd randomsocks[RANDOM_SOCKS];
int v6pktinfo; int v6pktinfo;
struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */
/* DHCP state */ /* DHCP state */
int dhcpfd, helperfd, pxefd; int dhcpfd, helperfd, pxefd;
......
...@@ -1081,6 +1081,37 @@ void receive_query(struct listener *listen, time_t now) ...@@ -1081,6 +1081,37 @@ void receive_query(struct listener *listen, time_t now)
source_addr.in6.sin6_flowinfo = 0; source_addr.in6.sin6_flowinfo = 0;
#endif #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) if (check_dst)
{ {
struct ifreq ifr; struct ifreq ifr;
...@@ -1544,6 +1575,37 @@ unsigned char *tcp_request(int confd, time_t now, ...@@ -1544,6 +1575,37 @@ unsigned char *tcp_request(int confd, time_t now,
if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1) if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
return packet; 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) while (1)
{ {
......
...@@ -268,7 +268,40 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, ...@@ -268,7 +268,40 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label,
if (!label) if (!label)
label = ifr.ifr_name; 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 #ifdef HAVE_IPV6
if (addr->sa.sa_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr)) if (addr->sa.sa_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr))
...@@ -565,6 +598,15 @@ int enumerate_interfaces(int reset) ...@@ -565,6 +598,15 @@ int enumerate_interfaces(int reset)
intname->addr = NULL; 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 #ifdef HAVE_AUTH
/* remove addresses stored against auth_zone subnets, but not /* remove addresses stored against auth_zone subnets, but not
ones configured as address literals */ ones configured as address literals */
......
...@@ -144,6 +144,7 @@ struct myoption { ...@@ -144,6 +144,7 @@ struct myoption {
#define LOPT_REV_SERV 332 #define LOPT_REV_SERV 332
#define LOPT_SERVERS_FILE 333 #define LOPT_SERVERS_FILE 333
#define LOPT_DNSSEC_CHECK 334 #define LOPT_DNSSEC_CHECK 334
#define LOPT_LOCAL_SERVICE 335
#ifdef HAVE_GETOPT_LONG #ifdef HAVE_GETOPT_LONG
static const struct option opts[] = static const struct option opts[] =
...@@ -175,6 +176,7 @@ static const struct myoption opts[] = ...@@ -175,6 +176,7 @@ static const struct myoption opts[] =
{ "domain-suffix", 1, 0, 's' }, { "domain-suffix", 1, 0, 's' },
{ "interface", 1, 0, 'i' }, { "interface", 1, 0, 'i' },
{ "listen-address", 1, 0, 'a' }, { "listen-address", 1, 0, 'a' },
{ "local-service", 0, 0, LOPT_LOCAL_SERVICE },
{ "bogus-priv", 0, 0, 'b' }, { "bogus-priv", 0, 0, 'b' },
{ "bogus-nxdomain", 1, 0, 'B' }, { "bogus-nxdomain", 1, 0, 'B' },
{ "selfmx", 0, 0, 'e' }, { "selfmx", 0, 0, 'e' },
...@@ -448,6 +450,7 @@ static struct { ...@@ -448,6 +450,7 @@ static struct {
{ LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL }, { 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_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_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 } { 0, 0, NULL, NULL, NULL }
}; };
...@@ -4457,6 +4460,11 @@ void read_opts(int argc, char **argv, char *compile_opts) ...@@ -4457,6 +4460,11 @@ void read_opts(int argc, char **argv, char *compile_opts)
else if (option_bool(OPT_DHCP_FQDN)) else if (option_bool(OPT_DHCP_FQDN))
die(_("there must be a default domain when --dhcp-fqdn is set"), NULL, EC_BADCONF); 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) if (testmode)
{ {
fprintf(stderr, "dnsmasq: %s.\n", _("syntax check OK")); fprintf(stderr, "dnsmasq: %s.\n", _("syntax check OK"));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment