]> git.ipfire.org Git - people/ms/dnsmasq.git/commitdiff
Generalise RR-filtering code, for use with EDNS0.
authorSimon Kelley <simon@thekelleys.org.uk>
Tue, 15 Dec 2015 17:25:21 +0000 (17:25 +0000)
committerSimon Kelley <simon@thekelleys.org.uk>
Tue, 15 Dec 2015 17:25:21 +0000 (17:25 +0000)
Makefile
bld/Android.mk
src/dnsmasq.h
src/dnssec.c
src/forward.c
src/rrfilter.c [new file with mode: 0644]

index 4c87ea9c1a0af56bcaab8a17fe67145e7a6b5a6c..b664160736c8aedfb0881f8bf7252d1f6ddce2d2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -73,7 +73,8 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \
        dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
        helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
        dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
-       domain.o dnssec.o blockdata.o tables.o loop.o inotify.o poll.o
+       domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \
+       poll.o rrfilter.o
 
 hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
        dns-protocol.h radv-protocol.h ip6addr.h
index 5364ee72d7f322d3b36cde803b36c68259b86dc7..67b9c4b359e90afbfb0b14f8368b9dcf4d254a52 100644 (file)
@@ -10,7 +10,7 @@ LOCAL_SRC_FILES :=  bpf.c cache.c dbus.c dhcp.c dnsmasq.c \
                    dhcp6.c rfc3315.c dhcp-common.c outpacket.c \
                    radv.c slaac.c auth.c ipset.c domain.c \
                    dnssec.c dnssec-openssl.c blockdata.c tables.c \
-                   loop.c inotify.c poll.c
+                   loop.c inotify.c poll.c rrfilter.c
 
 LOCAL_MODULE := dnsmasq
 
index 4344cae2061891483a318818a4718ffc5958c4dc..39a930c9abaf3932ff5dc639d5d2779c823a177e 100644 (file)
@@ -1513,3 +1513,8 @@ int poll_check(int fd, short event);
 void poll_listen(int fd, short event);
 int do_poll(int timeout);
 
+/* rrfilter.c */
+size_t rrfilter(struct dns_header *header, size_t plen, int mode);
+u16 *rrfilter_desc(int type);
+int expand_workspace(unsigned char ***wkspc, int *szp, int new);
+
index 359231f5dabc8d411c2e59013524835709cc3e41..fa3eb81089926921684dac6ae5ff6c39921a8fcd 100644 (file)
@@ -507,50 +507,6 @@ static int check_date_range(unsigned long date_start, unsigned long date_end)
     && serial_compare_32(curtime, date_end) == SERIAL_LT;
 }
 
-static u16 *get_desc(int type)
-{
-  /* List of RRtypes which include domains in the data.
-     0 -> domain
-     integer -> no of plain bytes
-     -1 -> end
-
-     zero is not a valid RRtype, so the final entry is returned for
-     anything which needs no mangling.
-  */
-  
-  static u16 rr_desc[] = 
-    { 
-      T_NS, 0, -1, 
-      T_MD, 0, -1,
-      T_MF, 0, -1,
-      T_CNAME, 0, -1,
-      T_SOA, 0, 0, -1,
-      T_MB, 0, -1,
-      T_MG, 0, -1,
-      T_MR, 0, -1,
-      T_PTR, 0, -1,
-      T_MINFO, 0, 0, -1,
-      T_MX, 2, 0, -1,
-      T_RP, 0, 0, -1,
-      T_AFSDB, 2, 0, -1,
-      T_RT, 2, 0, -1,
-      T_SIG, 18, 0, -1,
-      T_PX, 2, 0, 0, -1,
-      T_NXT, 0, -1,
-      T_KX, 2, 0, -1,
-      T_SRV, 6, 0, -1,
-      T_DNAME, 0, -1,
-      0, -1 /* wildcard/catchall */
-    }; 
-  
-  u16 *p = rr_desc;
-  
-  while (*p != type && *p != 0)
-    while (*p++ != (u16)-1);
-
-  return p+1;
-}
-
 /* Return bytes of canonicalised rdata, when the return value is zero, the remaining 
    data, pointed to by *p, should be used raw. */
 static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
@@ -594,34 +550,6 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end,
     }
 }
 
-static int expand_workspace(unsigned char ***wkspc, int *szp, int new)
-{
-  unsigned char **p;
-  int old = *szp;
-
-  if (old >= new+1)
-    return 1;
-
-  if (new >= 100)
-    return 0;
-
-  new += 5;
-  
-  if (!(p = whine_malloc(new * sizeof(unsigned char **))))
-    return 0;  
-  
-  if (old != 0 && *wkspc)
-    {
-      memcpy(p, *wkspc, old * sizeof(unsigned char **));
-      free(*wkspc);
-    }
-  
-  *wkspc = p;
-  *szp = new;
-
-  return 1;
-}
-
 /* Bubble sort the RRset into the canonical order. 
    Note that the byte-streams from two RRs may get unsynced: consider 
    RRs which have two domain-names at the start and then other data.
@@ -849,7 +777,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
   int rdlen, j, name_labels;
   struct crec *crecp = NULL;
   int algo, labels, orig_ttl, key_tag;
-  u16 *rr_desc = get_desc(type);
+  u16 *rr_desc = rrfilter_desc(type);
  
   if (wildcard_out)
     *wildcard_out = NULL;
@@ -2266,239 +2194,6 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i
   return ret;
 }
 
-/* Go through a domain name, find "pointers" and fix them up based on how many bytes
-   we've chopped out of the packet, or check they don't point into an elided part.  */
-static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
-{
-  unsigned char *ansp = *namep;
-
-  while(1)
-    {
-      unsigned int label_type;
-      
-      if (!CHECK_LEN(header, ansp, plen, 1))
-       return 0;
-      
-      label_type = (*ansp) & 0xc0;
-
-      if (label_type == 0xc0)
-       {
-         /* pointer for compression. */
-         unsigned int offset;
-         int i;
-         unsigned char *p;
-         
-         if (!CHECK_LEN(header, ansp, plen, 2))
-           return 0;
-
-         offset = ((*ansp++) & 0x3f) << 8;
-         offset |= *ansp++;
-
-         p = offset + (unsigned char *)header;
-         
-         for (i = 0; i < rr_count; i++)
-           if (p < rrs[i])
-             break;
-           else
-             if (i & 1)
-               offset -= rrs[i] - rrs[i-1];
-
-         /* does the pointer end up in an elided RR? */
-         if (i & 1)
-           return 0;
-
-         /* No, scale the pointer */
-         if (fixup)
-           {
-             ansp -= 2;
-             *ansp++ = (offset >> 8) | 0xc0;
-             *ansp++ = offset & 0xff;
-           }
-         break;
-       }
-      else if (label_type == 0x80)
-       return 0; /* reserved */
-      else if (label_type == 0x40)
-       {
-         /* Extended label type */
-         unsigned int count;
-         
-         if (!CHECK_LEN(header, ansp, plen, 2))
-           return 0;
-         
-         if (((*ansp++) & 0x3f) != 1)
-           return 0; /* we only understand bitstrings */
-         
-         count = *(ansp++); /* Bits in bitstring */
-         
-         if (count == 0) /* count == 0 means 256 bits */
-           ansp += 32;
-         else
-           ansp += ((count-1)>>3)+1;
-       }
-      else
-       { /* label type == 0 Bottom six bits is length */
-         unsigned int len = (*ansp++) & 0x3f;
-         
-         if (!ADD_RDLEN(header, ansp, plen, len))
-           return 0;
-
-         if (len == 0)
-           break; /* zero length label marks the end. */
-       }
-    }
-
-  *namep = ansp;
-
-  return 1;
-}
-
-/* Go through RRs and check or fixup the domain names contained within */
-static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
-{
-  int i, type, class, rdlen;
-  unsigned char *pp;
-  
-  for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++)
-    {
-      pp = p;
-
-      if (!(p = skip_name(p, header, plen, 10)))
-       return 0;
-      
-      GETSHORT(type, p); 
-      GETSHORT(class, p);
-      p += 4; /* TTL */
-      GETSHORT(rdlen, p);
-
-      if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
-       {
-         /* fixup name of RR */
-         if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
-           return 0;
-         
-         if (class == C_IN)
-           {
-             u16 *d;
-             for (pp = p, d = get_desc(type); *d != (u16)-1; d++)
-               {
-                 if (*d != 0)
-                   pp += *d;
-                 else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
-                   return 0;
-               }
-           }
-       }
-      
-      if (!ADD_RDLEN(header, p, plen, rdlen))
-       return 0;
-    }
-  
-  return 1;
-}
-       
-
-size_t filter_rrsigs(struct dns_header *header, size_t plen)
-{
-  static unsigned char **rrs;
-  static int rr_sz = 0;
-  
-  unsigned char *p = (unsigned char *)(header+1);
-  int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar;
-
-  if (ntohs(header->qdcount) != 1 ||
-      !(p = skip_name(p, header, plen, 4)))
-    return plen;
-  
-  GETSHORT(qtype, p);
-  GETSHORT(qclass, p);
-
-  /* First pass, find pointers to start and end of all the records we wish to elide:
-     records added for DNSSEC, unless explicity queried for */
-  for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; 
-       i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount);
-       i++)
-    {
-      unsigned char *pstart = p;
-      int type, class;
-
-      if (!(p = skip_name(p, header, plen, 10)))
-       return plen;
-      
-      GETSHORT(type, p); 
-      GETSHORT(class, p);
-      p += 4; /* TTL */
-      GETSHORT(rdlen, p);
-      
-      if ((type == T_NSEC || type == T_NSEC3 || type == T_RRSIG) && 
-         (type != qtype || class != qclass))
-       {
-         if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
-           return plen; 
-         
-         rrs[rr_found++] = pstart;
-
-         if (!ADD_RDLEN(header, p, plen, rdlen))
-           return plen;
-         
-         rrs[rr_found++] = p;
-         
-         if (i < ntohs(header->ancount))
-           chop_an++;
-         else if (i < (ntohs(header->nscount) + ntohs(header->ancount)))
-           chop_ns++;
-         else
-           chop_ar++;
-       }
-      else if (!ADD_RDLEN(header, p, plen, rdlen))
-       return plen;
-    }
-  
-  /* Nothing to do. */
-  if (rr_found == 0)
-    return plen;
-
-  /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
-     point to records we're going to elide. This is theoretically possible, but unlikely. If
-     it happens, we give up and leave the answer unchanged. */
-  p = (unsigned char *)(header+1);
-  
-  /* question first */
-  if (!check_name(&p, header, plen, 0, rrs, rr_found))
-    return plen;
-  p += 4; /* qclass, qtype */
-  
-  /* Now answers and NS */
-  if (!check_rrs(p, header, plen, 0, rrs, rr_found))
-    return plen;
-  
-  /* Third pass, elide records */
-  for (p = rrs[0], i = 1; i < rr_found; i += 2)
-    {
-      unsigned char *start = rrs[i];
-      unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen;
-      
-      memmove(p, start, end-start);
-      p += end-start;
-    }
-     
-  plen = p - (unsigned char *)header;
-  header->ancount = htons(ntohs(header->ancount) - chop_an);
-  header->nscount = htons(ntohs(header->nscount) - chop_ns);
-  header->arcount = htons(ntohs(header->arcount) - chop_ar);
-
-  /* Fourth pass, fix up pointers in the remaining records */
-  p = (unsigned char *)(header+1);
-  
-  check_name(&p, header, plen, 1, rrs, rr_found);
-  p += 4; /* qclass, qtype */
-  
-  check_rrs(p, header, plen, 1, rrs, rr_found);
-  
-  return plen;
-}
-
 unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)
 {
   int q;
index dd22a6233f35c498411876c3c0e44559284c458c..3e801c86045fce43029c8c307d36b7f1bd35f095 100644 (file)
@@ -662,7 +662,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
 
   /* If the requestor didn't set the DO bit, don't return DNSSEC info. */
   if (!do_bit)
-    n = filter_rrsigs(header, n);
+    n = rrfilter(header, n, 1);
 #endif
 
   /* do this after extract_addresses. Ensure NODATA reply and remove
diff --git a/src/rrfilter.c b/src/rrfilter.c
new file mode 100644 (file)
index 0000000..ae12261
--- /dev/null
@@ -0,0 +1,339 @@
+/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; version 2 dated June, 1991, or
+   (at your option) version 3 dated 29 June, 2007.
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+     
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Code to safely remove RRs from an DNS answer */ 
+
+#include "dnsmasq.h"
+
+/* Go through a domain name, find "pointers" and fix them up based on how many bytes
+   we've chopped out of the packet, or check they don't point into an elided part.  */
+static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+{
+  unsigned char *ansp = *namep;
+
+  while(1)
+    {
+      unsigned int label_type;
+      
+      if (!CHECK_LEN(header, ansp, plen, 1))
+       return 0;
+      
+      label_type = (*ansp) & 0xc0;
+
+      if (label_type == 0xc0)
+       {
+         /* pointer for compression. */
+         unsigned int offset;
+         int i;
+         unsigned char *p;
+         
+         if (!CHECK_LEN(header, ansp, plen, 2))
+           return 0;
+
+         offset = ((*ansp++) & 0x3f) << 8;
+         offset |= *ansp++;
+
+         p = offset + (unsigned char *)header;
+         
+         for (i = 0; i < rr_count; i++)
+           if (p < rrs[i])
+             break;
+           else
+             if (i & 1)
+               offset -= rrs[i] - rrs[i-1];
+
+         /* does the pointer end up in an elided RR? */
+         if (i & 1)
+           return 0;
+
+         /* No, scale the pointer */
+         if (fixup)
+           {
+             ansp -= 2;
+             *ansp++ = (offset >> 8) | 0xc0;
+             *ansp++ = offset & 0xff;
+           }
+         break;
+       }
+      else if (label_type == 0x80)
+       return 0; /* reserved */
+      else if (label_type == 0x40)
+       {
+         /* Extended label type */
+         unsigned int count;
+         
+         if (!CHECK_LEN(header, ansp, plen, 2))
+           return 0;
+         
+         if (((*ansp++) & 0x3f) != 1)
+           return 0; /* we only understand bitstrings */
+         
+         count = *(ansp++); /* Bits in bitstring */
+         
+         if (count == 0) /* count == 0 means 256 bits */
+           ansp += 32;
+         else
+           ansp += ((count-1)>>3)+1;
+       }
+      else
+       { /* label type == 0 Bottom six bits is length */
+         unsigned int len = (*ansp++) & 0x3f;
+         
+         if (!ADD_RDLEN(header, ansp, plen, len))
+           return 0;
+
+         if (len == 0)
+           break; /* zero length label marks the end. */
+       }
+    }
+
+  *namep = ansp;
+
+  return 1;
+}
+
+/* Go through RRs and check or fixup the domain names contained within */
+static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+{
+  int i, j, type, class, rdlen;
+  unsigned char *pp;
+  
+  for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++)
+    {
+      pp = p;
+
+      if (!(p = skip_name(p, header, plen, 10)))
+       return 0;
+      
+      GETSHORT(type, p); 
+      GETSHORT(class, p);
+      p += 4; /* TTL */
+      GETSHORT(rdlen, p);
+
+      /* If this RR is to be elided, don't fix up its contents */
+      for (j = 0; j < rr_count; j += 2)
+       if (rrs[j] == pp)
+         break;
+
+      if (j >= rr_count)
+       {
+         /* fixup name of RR */
+         if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+           return 0;
+         
+         if (class == C_IN)
+           {
+             u16 *d;
+             for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++)
+               {
+                 if (*d != 0)
+                   pp += *d;
+                 else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+                   return 0;
+               }
+           }
+       }
+      
+      if (!ADD_RDLEN(header, p, plen, rdlen))
+       return 0;
+    }
+  
+  return 1;
+}
+       
+
+/* mode is 0 to remove EDNS0, 1 to filter DNSSEC RRs */
+size_t rrfilter(struct dns_header *header, size_t plen, int mode)
+{
+  static unsigned char **rrs;
+  static int rr_sz = 0;
+
+  unsigned char *p = (unsigned char *)(header+1);
+  int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar;
+
+  if (ntohs(header->qdcount) != 1 ||
+      !(p = skip_name(p, header, plen, 4)))
+    return plen;
+  
+  GETSHORT(qtype, p);
+  GETSHORT(qclass, p);
+
+  /* First pass, find pointers to start and end of all the records we wish to elide:
+     records added for DNSSEC, unless explicity queried for */
+  for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; 
+       i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount);
+       i++)
+    {
+      unsigned char *pstart = p;
+      int type, class;
+
+      if (!(p = skip_name(p, header, plen, 10)))
+       return plen;
+      
+      GETSHORT(type, p); 
+      GETSHORT(class, p);
+      p += 4; /* TTL */
+      GETSHORT(rdlen, p);
+        
+      if (!ADD_RDLEN(header, p, plen, rdlen))
+       return plen;
+
+      /* Don't remove the answer. */
+      if (i < ntohs(header->ancount) && type == qtype && class == qclass)
+       continue;
+      
+      if (mode == 0) /* EDNS */
+       {
+         /* EDNS mode, remove T_OPT from additional section only */
+         if (i < (ntohs(header->nscount) + ntohs(header->ancount)) || type != T_OPT)
+           continue;
+       }
+      else if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
+       /* DNSSEC mode, remove SIGs and NSECs from all three sections. */
+       continue;
+      
+      
+      if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
+       return plen; 
+      
+      rrs[rr_found++] = pstart;
+      rrs[rr_found++] = p;
+      
+      if (i < ntohs(header->ancount))
+       chop_an++;
+      else if (i < (ntohs(header->nscount) + ntohs(header->ancount)))
+       chop_ns++;
+      else
+       chop_ar++;
+    }
+  
+  /* Nothing to do. */
+  if (rr_found == 0)
+    return plen;
+
+  /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
+     point to records we're going to elide. This is theoretically possible, but unlikely. If
+     it happens, we give up and leave the answer unchanged. */
+  p = (unsigned char *)(header+1);
+  
+  /* question first */
+  if (!check_name(&p, header, plen, 0, rrs, rr_found))
+    return plen;
+  p += 4; /* qclass, qtype */
+  
+  /* Now answers and NS */
+  if (!check_rrs(p, header, plen, 0, rrs, rr_found))
+    return plen;
+  
+  /* Third pass, elide records */
+  for (p = rrs[0], i = 1; i < rr_found; i += 2)
+    {
+      unsigned char *start = rrs[i];
+      unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen;
+      
+      memmove(p, start, end-start);
+      p += end-start;
+    }
+     
+  plen = p - (unsigned char *)header;
+  header->ancount = htons(ntohs(header->ancount) - chop_an);
+  header->nscount = htons(ntohs(header->nscount) - chop_ns);
+  header->arcount = htons(ntohs(header->arcount) - chop_ar);
+
+  /* Fourth pass, fix up pointers in the remaining records */
+  p = (unsigned char *)(header+1);
+  
+  check_name(&p, header, plen, 1, rrs, rr_found);
+  p += 4; /* qclass, qtype */
+  
+  check_rrs(p, header, plen, 1, rrs, rr_found);
+  
+  return plen;
+}
+
+/* This is used in the DNSSEC code too, hence it's exported */
+u16 *rrfilter_desc(int type)
+{
+  /* List of RRtypes which include domains in the data.
+     0 -> domain
+     integer -> no of plain bytes
+     -1 -> end
+
+     zero is not a valid RRtype, so the final entry is returned for
+     anything which needs no mangling.
+  */
+  
+  static u16 rr_desc[] = 
+    { 
+      T_NS, 0, -1, 
+      T_MD, 0, -1,
+      T_MF, 0, -1,
+      T_CNAME, 0, -1,
+      T_SOA, 0, 0, -1,
+      T_MB, 0, -1,
+      T_MG, 0, -1,
+      T_MR, 0, -1,
+      T_PTR, 0, -1,
+      T_MINFO, 0, 0, -1,
+      T_MX, 2, 0, -1,
+      T_RP, 0, 0, -1,
+      T_AFSDB, 2, 0, -1,
+      T_RT, 2, 0, -1,
+      T_SIG, 18, 0, -1,
+      T_PX, 2, 0, 0, -1,
+      T_NXT, 0, -1,
+      T_KX, 2, 0, -1,
+      T_SRV, 6, 0, -1,
+      T_DNAME, 0, -1,
+      0, -1 /* wildcard/catchall */
+    }; 
+  
+  u16 *p = rr_desc;
+  
+  while (*p != type && *p != 0)
+    while (*p++ != (u16)-1);
+
+  return p+1;
+}
+
+int expand_workspace(unsigned char ***wkspc, int *szp, int new)
+{
+  unsigned char **p;
+  int old = *szp;
+
+  if (old >= new+1)
+    return 1;
+
+  if (new >= 100)
+    return 0;
+
+  new += 5;
+  
+  if (!(p = whine_malloc(new * sizeof(unsigned char **))))
+    return 0;  
+  
+  if (old != 0 && *wkspc)
+    {
+      memcpy(p, *wkspc, old * sizeof(unsigned char **));
+      free(*wkspc);
+    }
+  
+  *wkspc = p;
+  *szp = new;
+
+  return 1;
+}