]> git.ipfire.org Git - people/ms/dnsmasq.git/commitdiff
--add-subnet option.
authorSimon Kelley <simon@thekelleys.org.uk>
Tue, 8 Oct 2013 19:46:34 +0000 (20:46 +0100)
committerSimon Kelley <simon@thekelleys.org.uk>
Tue, 8 Oct 2013 19:46:34 +0000 (20:46 +0100)
CHANGELOG
man/dnsmasq.8
src/config.h
src/dns-protocol.h
src/dnsmasq.h
src/forward.c
src/option.c
src/rfc1035.c

index d76eb7a1f720d2d7fd00b856c8f52843da4197a7..d400d164c5baf726496e86212af47e0512c07a0c 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -132,6 +132,9 @@ version 2.67
 
            Update Spanish transalation. Thanks to Vicente Soriano.
 
+           Add --add-subnet configuration, to tell upstream DNS
+           servers where the original client is. 
+
  
 version 2.66
             Add the ability to act as an authoritative DNS
index 2b1570a0af5fd168f3e9b268542c4c89510cb98b..a0f903f99e660c20194982424266096693565f20 100644 (file)
@@ -543,7 +543,20 @@ server. The MAC address can only be added if the requestor is on the same
 subnet as the dnsmasq server. Note that the mechanism used to achieve this (an EDNS0 option)
 is not yet standardised, so this should be considered
 experimental. Also note that exposing MAC addresses in this way may
-have security and privacy implications. 
+have security and privacy implications. The warning about caching
+given for --add-subnet applies to --add-mac too.
+.TP 
+.B --add-subnet[[=<IPv4 prefix length>],<IPv6 prefix length>]
+Add the subnet address of the requestor to the DNS queries which are
+forwarded upstream. The amount of the address forwarded depends on the
+prefix length parameter: 32 (128 for IPv6) forwards the whole address,
+zero forwards none of it but still marks the request so that no
+upstream nameserver will add client address information either. The
+default is zero for both IPv4 and IPv6. Note that upstream nameservers
+may be configured to return different results based on this
+information, but the dnsmasq cache does not take account. If a dnsmasq
+instance is configured such that different results may be encountered,
+caching should be disabled.
 .TP
 .B \-c, --cache-size=<cachesize>
 Set the size of dnsmasq's cache. The default is 150 names. Setting the cache size to zero disables caching.
index 5224adf8e33c912d49394b77d31809a9725c7ebd..31ae1cb574798677fb7eff68cb35d018e755635e 100644 (file)
@@ -39,7 +39,6 @@
 #define TFTP_MAX_CONNECTIONS 50 /* max simultaneous connections */
 #define LOG_MAX 5 /* log-queue length */
 #define RANDFILE "/dev/urandom"
-#define EDNS0_OPTION_MAC 5 /* dyndns.org temporary assignment */
 #define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" /* Default - may be overridden by config */
 #define DNSMASQ_PATH "/uk/org/thekelleys/dnsmasq"
 #define AUTH_TTL 600 /* default TTL for auth DNS */
index 80eff720a9dcc2f78f8ed9c790bb1e243e995ce7..51ca6b937ac1658a49c194c06d83517a8be363af 100644 (file)
 #define T_MAILB                253     
 #define T_ANY          255
 
+#define EDNS0_OPTION_MAC            65001 /* dyndns.org temporary assignment */
+#define EDNS0_OPTION_CLIENT_SUBNET  5     /* IANA */
+
+
 struct dns_header {
   u16 id;
   u8  hb3,hb4;
index b652a9121ebcb77f0d914bfe8c5cf97097f8dac0..ed6f92f9d21264c595410bcefb5638c3bfec9267 100644 (file)
@@ -222,7 +222,8 @@ struct event_desc {
 #define OPT_CLEVERBIND     39
 #define OPT_TFTP           40
 #define OPT_FAST_RA        41
-#define OPT_LAST           42
+#define OPT_CLIENT_SUBNET  42
+#define OPT_LAST           43
 
 /* 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. */
@@ -487,6 +488,7 @@ struct hostsfile {
 
 #define FREC_NOREBIND           1
 #define FREC_CHECKING_DISABLED  2
+#define FREC_HAS_SUBNET         4
 
 struct frec {
   union mysockaddr source;
@@ -802,6 +804,8 @@ extern struct daemon {
   struct auth_zone *auth_zones;
   struct interface_name *int_names;
   char *mxtarget;
+  int addr4_netmask;
+  int addr6_netmask;
   char *lease_file; 
   char *username, *groupname, *scriptuser;
   char *luascript;
@@ -962,6 +966,8 @@ unsigned int questions_crc(struct dns_header *header, size_t plen, char *buff);
 size_t resize_packet(struct dns_header *header, size_t plen, 
                  unsigned char *pheader, size_t hlen);
 size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3);
+size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source);
+int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
 int add_resource_record(struct dns_header *header, char *limit, int *truncp,
                        int nameoffset, unsigned char **pp, unsigned long ttl, 
                        int *offset, unsigned short type, unsigned short class, char *format, ...);
index 6c9f64601e880c4b8184925622a8dd0035ac76e3..adc4a0fc7d4398799a850c8c415b27614f1a5567 100644 (file)
@@ -284,6 +284,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
          forward->fd = udpfd;
          forward->crc = crc;
          forward->forwardall = 0;
+         forward->flags = 0;
          if (norebind)
            forward->flags |= FREC_NOREBIND;
          if (header->hb4 & HB4_CD)
@@ -331,6 +332,16 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
       if (option_bool(OPT_ADD_MAC))
        plen = add_mac(header, plen, ((char *) header) + PACKETSZ, &forward->source);
       
+      if (option_bool(OPT_CLIENT_SUBNET))
+       {
+         size_t new = add_source_addr(header, plen, ((char *) header) + PACKETSZ, &forward->source); 
+         if (new != plen)
+           {
+             plen = new;
+             forward->flags |= FREC_HAS_SUBNET;
+           }
+       }
+
       while (1)
        { 
          /* only send to servers dealing with our domain.
@@ -435,8 +446,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
   return 0;
 }
 
-static size_t process_reply(struct dns_header *header, time_t now, 
-                           struct server *server, size_t n, int check_rebind, int checking_disabled)
+static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind, 
+                           int checking_disabled, int check_subnet, union mysockaddr *query_source)
 {
   unsigned char *pheader, *sizep;
   char **sets = 0;
@@ -465,19 +476,29 @@ static size_t process_reply(struct dns_header *header, time_t now,
      than we allow, trim it so that we don't get overlarge
      requests for the client. We can't do this for signed packets. */
 
-  if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)) && !is_sign)
+  if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)))
     {
-      unsigned short udpsz;
-      unsigned char *psave = sizep;
+      if (!is_sign)
+       {
+         unsigned short udpsz;
+         unsigned char *psave = sizep;
+         
+         GETSHORT(udpsz, sizep);
+         if (udpsz > daemon->edns_pktsz)
+           PUTSHORT(daemon->edns_pktsz, psave);
+       }
       
-      GETSHORT(udpsz, sizep);
-      if (udpsz > daemon->edns_pktsz)
-       PUTSHORT(daemon->edns_pktsz, psave);
+      if (check_subnet && !check_source(header, plen, pheader, query_source))
+       {
+         my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch"));
+         return 0;
+       }
     }
+      
 
   /* RFC 4035 sect 4.6 para 3 */
   if (!is_sign && !option_bool(OPT_DNSSEC))
-     header->hb4 &= ~HB4_AD;
+    header->hb4 &= ~HB4_AD;
 
   if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN))
     return n;
@@ -632,7 +653,8 @@ void reply_query(int fd, int family, time_t now)
       if (!option_bool(OPT_NO_REBIND))
        check_rebind = 0;
       
-      if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED)))
+      if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED,
+                             forward->flags & FREC_HAS_SUBNET, &forward->source)))
        {
          header->id = htons(forward->orig_id);
          header->hb4 |= HB4_RA; /* recursion if available */
@@ -876,7 +898,7 @@ unsigned char *tcp_request(int confd, time_t now,
 {
   size_t size = 0;
   int norebind = 0;
-  int checking_disabled;
+  int checking_disabled, check_subnet;
   size_t m;
   unsigned short qtype;
   unsigned int gotname;
@@ -906,6 +928,8 @@ unsigned char *tcp_request(int confd, time_t now,
       if (size < (int)sizeof(struct dns_header))
        continue;
       
+      check_subnet = 0;
+
       /* save state of "cd" flag in query */
       checking_disabled = header->hb4 & HB4_CD;
        
@@ -955,7 +979,17 @@ unsigned char *tcp_request(int confd, time_t now,
              
              if (option_bool(OPT_ADD_MAC))
                size = add_mac(header, size, ((char *) header) + 65536, &peer_addr);
-             
+               
+             if (option_bool(OPT_CLIENT_SUBNET))
+               {
+                 size_t new = add_source_addr(header, size, ((char *) header) + 65536, &peer_addr);
+                 if (size != new)
+                   {
+                     size = new;
+                     check_subnet = 1;
+                   }
+               }
+
              if (gotname)
                flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
              
@@ -1056,7 +1090,8 @@ unsigned char *tcp_request(int confd, time_t now,
                         sending replies containing questions and bogus answers. */
                      if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff))
                        m = process_reply(header, now, last_server, (unsigned int)m, 
-                                         option_bool(OPT_NO_REBIND) && !norebind, checking_disabled);
+                                         option_bool(OPT_NO_REBIND) && !norebind, checking_disabled,
+                                         check_subnet, &peer_addr);
                      
                      break;
                    }
index 9b128cfd3a991454212721e7e8ceb303d05e8c51..b8edf42c993046872ec8b2ce71eeadaf97347a0b 100644 (file)
@@ -134,6 +134,7 @@ struct myoption {
 #endif
 #define LOPT_FAST_RA   322
 #define LOPT_RELAY     323
+#define LOPT_ADD_SBNET 324
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -251,6 +252,7 @@ static const struct myoption opts[] =
     { "dhcp-generate-names", 2, 0, LOPT_GEN_NAMES },
     { "rebind-localhost-ok", 0, 0,  LOPT_LOC_REBND },
     { "add-mac", 0, 0, LOPT_ADD_MAC },
+    { "add-subnet", 2, 0, LOPT_ADD_SBNET },
     { "proxy-dnssec", 0, 0, LOPT_DNSSEC },
     { "dhcp-sequential-ip", 0, 0,  LOPT_INCR_ADDR },
     { "conntrack", 0, 0, LOPT_CONNTRACK },
@@ -397,6 +399,7 @@ static struct {
   { LOPT_PXE_SERV, ARG_DUP, "<service>", gettext_noop("Boot service for PXE menu."), NULL },
   { LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL },
   { LOPT_ADD_MAC, OPT_ADD_MAC, NULL, gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL },
+  { LOPT_ADD_SBNET, ARG_ONE, "<v4 pref>[,<v6 pref>]", gettext_noop("Add requestor's IP subnet to forwarded DNS queries."), NULL },
   { LOPT_DNSSEC, OPT_DNSSEC, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL },
   { LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL },
   { LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL },
@@ -1424,6 +1427,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
        break;
       }
 
+    case LOPT_ADD_SBNET: /* --add-subnet */
+      set_option_bool(OPT_CLIENT_SUBNET);
+      if (arg)
+       {
+         comma = split(arg);
+         if (!atoi_check(arg, &daemon->addr4_netmask) || 
+             (comma && !atoi_check(comma, &daemon->addr6_netmask)))
+            ret_err(gen_err);
+       }
+      break;
+
     case '1': /* --enable-dbus */
       set_option_bool(OPT_DBUS);
       if (arg)
index 60ed068fa6cf3b7f63d043f039ddddd27a7cb57e..5e7fe95f8eb27ecfd648e2a3719f5e98b9a31739 100644 (file)
@@ -513,46 +513,30 @@ struct macparm {
   size_t plen;
   union mysockaddr *l3;
 };
-
-static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv)
-{
-  struct macparm *parm = parmv;
-  int match = 0;
-  unsigned short rdlen;
-  struct dns_header *header = parm->header;
-  unsigned char *lenp, *datap, *p;
-  
-  if (family == parm->l3->sa.sa_family)
-    {
-      if (family == AF_INET && memcmp (&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0)
-       match = 1;
-#ifdef HAVE_IPV6
-      else
-       if (family == AF_INET6 && memcmp (&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0)
-         match = 1;
-#endif
-    }
  
-  if (!match)
-    return 1; /* continue */
+static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit, 
+                              int optno, unsigned char *opt, size_t optlen)
+{ 
+  unsigned char *lenp, *datap, *p;
+  int rdlen;
   
   if (ntohs(header->arcount) == 0)
     {
       /* We are adding the pseudoheader */
-      if (!(p = skip_questions(header, parm->plen)) ||
+      if (!(p = skip_questions(header, plen)) ||
          !(p = skip_section(p, 
                             ntohs(header->ancount) + ntohs(header->nscount), 
-                            header, parm->plen)))
-       return 0;
+                            header, plen)))
+       return plen;
       *p++ = 0; /* empty name */
       PUTSHORT(T_OPT, p);
-      PUTSHORT(PACKETSZ, p); /* max packet length - is 512 suitable default for non-EDNS0 resolvers? */
+      PUTSHORT(daemon->edns_pktsz, p); /* max packet length */
       PUTLONG(0, p);    /* extended RCODE */
       lenp = p;
       PUTSHORT(0, p);    /* RDLEN */
       rdlen = 0;
-      if (((ssize_t)maclen) > (parm->limit - (p + 4)))
-       return 0; /* Too big */
+      if (((ssize_t)optlen) > (limit - (p + 4)))
+       return plen; /* Too big */
       header->arcount = htons(1);
       datap = p;
     }
@@ -562,17 +546,17 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p
       unsigned short code, len;
       
       if (ntohs(header->arcount) != 1 ||
-         !(p = find_pseudoheader(header, parm->plen, NULL, NULL, &is_sign)) ||
+         !(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign)) ||
          is_sign ||
-         (!(p = skip_name(p, header, parm->plen, 10))))
-       return 0;
+         (!(p = skip_name(p, header, plen, 10))))
+       return plen;
       
       p += 8; /* skip UDP length and RCODE */
       
       lenp = p;
       GETSHORT(rdlen, p);
-      if (!CHECK_LEN(header, p, parm->plen, rdlen))
-       return 0; /* bad packet */
+      if (!CHECK_LEN(header, p, plen, rdlen))
+       return plen; /* bad packet */
       datap = p;
 
       /* check if option already there */
@@ -580,27 +564,49 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p
        {
          GETSHORT(code, p);
          GETSHORT(len, p);
-         if (code == EDNS0_OPTION_MAC)
-           return 0;
+         if (code == optno)
+           return plen;
          p += len;
        }
       
-      if (((ssize_t)maclen) > (parm->limit - (p + 4)))
-       return 0; /* Too big */
+      if (((ssize_t)optlen) > (limit - (p + 4)))
+       return plen; /* Too big */
     }
   
-  PUTSHORT(EDNS0_OPTION_MAC, p);
-  PUTSHORT(maclen, p);
-  memcpy(p, mac, maclen);
-  p += maclen;  
+  PUTSHORT(optno, p);
+  PUTSHORT(optlen, p);
+  memcpy(p, opt, optlen);
+  p += optlen;  
 
   PUTSHORT(p - datap, lenp);
-  parm->plen = p - (unsigned char *)header;
+  return p - (unsigned char *)header;
+  
+}
+
+static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv)
+{
+  struct macparm *parm = parmv;
+  int match = 0;
+    
+  if (family == parm->l3->sa.sa_family)
+    {
+      if (family == AF_INET && memcmp (&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0)
+       match = 1;
+#ifdef HAVE_IPV6
+      else
+       if (family == AF_INET6 && memcmp (&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0)
+         match = 1;
+#endif
+    }
+  if (!match)
+    return 1; /* continue */
+
+  parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit,  EDNS0_OPTION_MAC, (unsigned char *)mac, maclen);
   
   return 0; /* done */
 }            
      
-
 size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3)
 {
   struct macparm parm;
@@ -621,7 +627,102 @@ size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysock
   return parm.plen; 
 }
 
-    
+struct subnet_opt {
+  u16 family;
+  u8 source_netmask, scope_netmask;
+#ifdef HAVE_IPV6 
+  u8 addr[IN6ADDRSZ];
+#else
+  u8 addr[INADDRSZ];
+#endif
+};
+
+size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
+{
+  /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
+  
+  int len;
+  void *addrp;
+
+  if (source->sa.sa_family == AF_INET)
+    {
+      opt->family = htons(1);
+      opt->source_netmask = daemon->addr4_netmask;
+      addrp = &source->in.sin_addr;
+    }
+#ifdef HAVE_IPV6
+  else
+    {
+      opt->family = htons(2);
+      opt->source_netmask = daemon->addr6_netmask;
+      addrp = &source->in6.sin6_addr;
+    }
+#endif
+  
+  opt->scope_netmask = 0;
+  len = 0;
+  
+  if (opt->source_netmask != 0)
+    {
+      len = ((opt->source_netmask - 1) >> 3) + 1;
+      memcpy(opt->addr, addrp, len);
+      if (opt->source_netmask & 7)
+       opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7));
+    }
+
+  return len + 4;
+}
+size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source)
+{
+  /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
+  
+  int len;
+  struct subnet_opt opt;
+  
+  len = calc_subnet_opt(&opt, source);
+  return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len);
+}
+  
+int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer)
+{
+  /* Section 9.2, Check that subnet option in reply matches. */
+
+
+ int len, calc_len;
+  struct subnet_opt opt;
+  unsigned char *p;
+  int code, i, rdlen;
+  
+   calc_len = calc_subnet_opt(&opt, peer);
+   
+   if (!(p = skip_name(pseudoheader, header, plen, 10)))
+     return 1;
+   
+   p += 8; /* skip UDP length and RCODE */
+   
+   GETSHORT(rdlen, p);
+   if (!CHECK_LEN(header, p, plen, rdlen))
+     return 1; /* bad packet */
+   
+   /* check if option there */
+   for (i = 0; i + 4 < rdlen; i += len + 4)
+     {
+       GETSHORT(code, p);
+       GETSHORT(len, p);
+       if (code == EDNS0_OPTION_CLIENT_SUBNET)
+        {
+          /* make sure this doesn't mismatch. */
+          opt.scope_netmask = p[3];
+          if (len != calc_len || memcmp(p, &opt, len) != 0)
+            return 0;
+        }
+       p += len;
+     }
+   
+   return 1;
+}
+
 /* is addr in the non-globally-routed IP space? */ 
 static int private_net(struct in_addr addr, int ban_localhost) 
 {