]> git.ipfire.org Git - people/ms/dnsmasq.git/commitdiff
--local-service. Default protection from DNS amplification attacks.
authorSimon Kelley <simon@thekelleys.org.uk>
Wed, 5 Mar 2014 14:29:54 +0000 (14:29 +0000)
committerSimon Kelley <simon@thekelleys.org.uk>
Wed, 5 Mar 2014 14:29:54 +0000 (14:29 +0000)
CHANGELOG
man/dnsmasq.8
src/dnsmasq.h
src/forward.c
src/network.c
src/option.c

index 806fc8e6e82915b17f017e2494094c183c057caa..4c89fa90acbcc53038b6730244a445839070dea1 100644 (file)
--- 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
index 975ccd4298d86cd7bae2508dd0134731190e5aba..87730df170054b12c7160a80640505b43fbd2c4d 100644 (file)
@@ -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.
index a00d95cfa288df921b7ec6ddfd6f67e76ca9e37c..6a0391d2556c74a6b738c4f5250831e7a1c2af01 100644 (file)
@@ -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; 
index 79167167c7854f3a3dd84f8076432a1d5b9e7c4a..b396aa4b28210dc67830e4ff609f628a3f32b3b9 100644 (file)
@@ -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)
     {
index a4380aee84d5e4175879fb954dd955f2d7285625..3cc5a4df8b49c4e6b7ba3293a778d42ae2070566 100644 (file)
@@ -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 */
index b8982319955da87f754bfaa4cf5a2473ff134e6e..e8ef5fabe2aebf1d4706dc40332bea65d9c9e7dc 100644 (file)
@@ -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"));