Add filtering of arbitrary RR-types.
authorSimon Kelley <simon@thekelleys.org.uk>
Wed, 29 Mar 2023 21:43:21 +0000 (22:43 +0100)
committerSimon Kelley <simon@thekelleys.org.uk>
Wed, 29 Mar 2023 21:43:21 +0000 (22:43 +0100)
man/dnsmasq.8
src/dbus.c
src/dnsmasq.c
src/dnsmasq.h
src/forward.c
src/option.c
src/rfc1035.c
src/rrfilter.c

index 6d844bf..acb78df 100644 (file)
@@ -376,6 +376,9 @@ Remove A records from answers. No IPv4 addresses will be returned.
 .B --filter-AAAA
 Remove AAAA records from answers. No IPv6 addresses will be returned.
 .TP
+.B --filter-rr=<rrtype>[,<rrtype>...]
+Remove records of the specified type(s) from answers.
+.TP
 .B --cache-rr=<rrtype>[,<rrtype>...]
 By default, dnsmasq caches A, AAAA, CNAME and SRV DNS record types.
 This option adds other record types to the cache. The RR-type can be given
index 4366b7e..4512b8e 100644 (file)
@@ -825,11 +825,25 @@ DBusHandlerResult message_handler(DBusConnection *connection,
     }
   else if (strcmp(method, "SetFilterA") == 0)
     {
-      reply = dbus_set_bool(message, OPT_FILTER_A, "filter-A");
+      static int done = 0;
+      static struct rrlist list = { T_A, NULL };
+
+      if (!done)
+       {
+         list.next = daemon->filter_rr;
+         daemon->filter_rr = &list;
+       }
     }
   else if (strcmp(method, "SetFilterAAAA") == 0)
     {
-      reply = dbus_set_bool(message, OPT_FILTER_AAAA, "filter-AAAA");
+      static int done = 0;
+      static struct rrlist list = { T_AAAA, NULL };
+
+      if (!done)
+       {
+         list.next = daemon->filter_rr;
+         daemon->filter_rr = &list;
+       }
     }
   else if (strcmp(method, "SetLocaliseQueriesOption") == 0)
     {
index 6fb9571..c77d9c3 100644 (file)
@@ -140,7 +140,7 @@ int main (int argc, char **argv)
   /* CONNTRACK UBUS code uses this buffer, so if not allocated above,
      we need to allocate it here. */
   if (option_bool(OPT_CMARK_ALST_EN) && !daemon->workspacename)
-    daemon->workspacename = safe_malloc(MAXDNAME);
+    daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
 #endif
   
 #ifdef HAVE_DHCP
index 7a00ece..9ee5e39 100644 (file)
@@ -276,14 +276,12 @@ struct event_desc {
 #define OPT_UMBRELLA_DEVID 64
 #define OPT_CMARK_ALST_EN  65
 #define OPT_QUIET_TFTP     66
-#define OPT_FILTER_A       67
-#define OPT_FILTER_AAAA    68
-#define OPT_STRIP_ECS      69
-#define OPT_STRIP_MAC      70
-#define OPT_NORR           71
-#define OPT_NO_IDENT       72
-#define OPT_CACHE_RR       73
-#define OPT_LAST           74
+#define OPT_STRIP_ECS      67
+#define OPT_STRIP_MAC      68
+#define OPT_NORR           69
+#define OPT_NO_IDENT       70
+#define OPT_CACHE_RR       71
+#define OPT_LAST           72
 
 #define OPTION_BITS (sizeof(unsigned int)*8)
 #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -1130,7 +1128,7 @@ extern struct daemon {
   struct naptr *naptr;
   struct txt_record *txt, *rr;
   struct ptr_record *ptr;
-  struct rrlist *cache_rr, filter_rr;
+  struct rrlist *cache_rr, *filter_rr;
   struct host_record *host_records, *host_records_tail;
   struct cname *cnames;
   struct auth_zone *auth_zones;
@@ -1831,8 +1829,7 @@ void from_wire(char *name);
 /* modes. */
 #define RRFILTER_EDNS0   0
 #define RRFILTER_DNSSEC  1
-#define RRFILTER_A       2
-#define RRFILTER_AAAA    3
+#define RRFILTER_CONF    2
 
 /* edns0.c */
 unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
index d79cc56..5c1bad3 100644 (file)
@@ -829,19 +829,8 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
          break;
        }
 
-      if (rcode == NOERROR)
-       {
-         size_t modified = 0;
-
-         if (option_bool(OPT_FILTER_A))
-           modified = rrfilter(header, &n, RRFILTER_A);
-         
-         if (option_bool(OPT_FILTER_AAAA))
-           modified += rrfilter(header, &n, RRFILTER_AAAA);
-
-         if (modified > 0)
-           ede = EDE_FILTERED;
-       }
+      if (rcode == NOERROR && rrfilter(header, &n, RRFILTER_CONF) > 0) 
+       ede = EDE_FILTERED;
       
       if (doctored)
        cache_secure = 0;
index 0f70932..ac3b24d 100644 (file)
@@ -187,6 +187,7 @@ struct myoption {
 #define LOPT_NORR          378
 #define LOPT_NO_IDENT      379
 #define LOPT_CACHE_RR      380
+#define LOPT_FILTER_RR     381
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -226,6 +227,7 @@ static const struct myoption opts[] =
     { "filterwin2k", 0, 0, 'f' },
     { "filter-A", 0, 0, LOPT_FILTER_A },
     { "filter-AAAA", 0, 0, LOPT_FILTER_AAAA },
+    { "filter-rr", 1, 0, LOPT_FILTER_RR },
     { "pid-file", 2, 0, 'x' },
     { "strict-order", 0, 0, 'o' },
     { "server", 1, 0, 'S' },
@@ -405,8 +407,9 @@ static struct {
   { 'e', OPT_SELFMX, NULL, gettext_noop("Return self-pointing MX records for local hosts."), NULL },
   { 'E', OPT_EXPAND, NULL, gettext_noop("Expand simple names in /etc/hosts with domain-suffix."), NULL },
   { 'f', OPT_FILTER, NULL, gettext_noop("Don't forward spurious DNS requests from Windows hosts."), NULL },
-  { LOPT_FILTER_A, OPT_FILTER_A, NULL, gettext_noop("Don't include IPv4 addresses in DNS answers."), NULL },
-  { LOPT_FILTER_AAAA, OPT_FILTER_AAAA, NULL, gettext_noop("Don't include IPv6 addresses in DNS answers."), NULL },
+  { LOPT_FILTER_A, ARG_DUP, NULL, gettext_noop("Don't include IPv4 addresses in DNS answers."), NULL },
+  { LOPT_FILTER_AAAA, ARG_DUP, NULL, gettext_noop("Don't include IPv6 addresses in DNS answers."), NULL },
+  { LOPT_FILTER_RR, ARG_DUP, "<RR-type>", gettext_noop("Don't include resource records of the given type in DNS answers."), NULL },
   { 'F', ARG_DUP, "<ipaddr>,...", gettext_noop("Enable DHCP in the range given with lease duration."), NULL },
   { 'g', ARG_ONE, "<groupname>", gettext_noop("Change to this group after startup (defaults to %s)."), CHGRP },
   { 'G', ARG_DUP, "<hostspec>", gettext_noop("Set address or hostname for a specified machine."), NULL },
@@ -575,7 +578,7 @@ static struct {
   { LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL },
   { LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL },
   { LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL },
-  { LOPT_CACHE_RR, ARG_DUP, "RRtype", gettext_noop("Cache this DNS resource record type."), NULL },
+  { LOPT_CACHE_RR, ARG_DUP, "<RR-type>", gettext_noop("Cache this DNS resource record type."), NULL },
   { 0, 0, NULL, NULL, NULL }
 }; 
 
@@ -3470,19 +3473,39 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
       break;
 
     case LOPT_CACHE_RR: /* --cache-rr */
+    case LOPT_FILTER_RR: /* --filter-rr */
+    case LOPT_FILTER_A: /* --filter-A */
+    case LOPT_FILTER_AAAA: /* --filter-AAAA */
       while (1) {
        int type;
        struct rrlist *new;
-       
-       comma = split(arg);
-       if (!atoi_check(arg, &type) && (type = rrtype(arg)) == 0)
-         ret_err(_("bad RR type"));
 
+       comma = NULL;
+
+       if (option == LOPT_FILTER_A)
+         type = T_A;
+       else if (option == LOPT_FILTER_AAAA)
+         type = T_AAAA;
+       else
+         {
+           comma = split(arg);
+           if (!atoi_check(arg, &type) && (type = rrtype(arg)) == 0)
+             ret_err(_("bad RR type"));
+         }
+       
        new = opt_malloc(sizeof(struct rrlist));
        new->rr = type;
 
-       new->next = daemon->cache_rr;
-       daemon->cache_rr = new;
+       if (option == LOPT_CACHE_RR)
+         {
+           new->next = daemon->cache_rr;
+           daemon->cache_rr = new;
+         }
+       else
+         {
+           new->next = daemon->filter_rr;
+           daemon->filter_rr = new;
+         }
        
        if (!comma) break;
        arg = comma;
index 63aff8a..8c8b73a 100644 (file)
@@ -916,7 +916,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
                     returned packet in process_reply() but gets cached here anyway
                     and will be filtered again on the way out of the cache. Here,
                     we just need to alter the logging. */
-                 if (((flags & F_IPV4) && option_bool(OPT_FILTER_A)) || ((flags & F_IPV6) && option_bool(OPT_FILTER_AAAA)))
+                 if (rr_on_list(daemon->filter_rr, qtype))
                    negflag = F_NEG | F_CONFIG;
                  
                  log_query(negflag | flags | F_FORWARD | secflag, name, &addr, NULL, aqtype);
@@ -1912,7 +1912,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                        if (!(crecp->flags & (F_HOSTS | F_DHCP)))
                          auth = 0;
 
-                       if ((((flag & F_IPV4) && option_bool(OPT_FILTER_A)) || ((flag & F_IPV6) && option_bool(OPT_FILTER_AAAA))) &&
+                       if (rr_on_list(daemon->filter_rr, qtype) &&
                            !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | F_NEG)))
                          {
                            /* We have a cached answer but we're filtering it. */
@@ -1921,7 +1921,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                            
                            if (!dryrun)
                              log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0);
-
+                           
                            if (filtered)
                              *filtered = 1;
                          }
@@ -1968,27 +1968,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                        anscount++;
                    }
                }
-             else if (((flag & F_IPV4) && option_bool(OPT_FILTER_A)) || ((flag & F_IPV6) && option_bool(OPT_FILTER_AAAA)))
-               {
-                 /* We don't have a cached answer and when we get an answer from upstream we're going to
-                    filter it anyway. If we have a cached answer for the domain for another RRtype then
-                    that may be enough to tell us if the answer should be NODATA and save the round trip.
-                    Cached NXDOMAIN has already been handled, so here we look for any record for the domain,
-                    since its existence allows us to return a NODATA answer. Note that we never set the AD flag,
-                    since we didn't authentucate the record. */
-
-                 if (cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_RR))
-                   {
-                     ans = 1;
-                     sec_data = auth = 0;
-                     
-                     if (!dryrun)
-                       log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0);
-
-                     if (filtered)
-                       *filtered = 1;
-                   }
-               }
            }
 
          if (qtype == T_MX || qtype == T_ANY)
@@ -2103,30 +2082,32 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                log_query(F_CONFIG | F_NEG, name, &addr, NULL, 0);
            }
 
-         if (!ans && qtype != T_ANY)
+         if (!ans)
            {
               if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN | (dryrun ? F_NO_RR : 0))) &&
                   rd_bit && (!do_bit || cache_validated(crecp)))
                 do
                   {
-                    int stale_flag = 0;
+                    int flags = crecp->flags;
 
-                    if (crecp->addr.rr.rrtype == qtype)
+                    if ((flags & F_NXDOMAIN) || crecp->addr.rr.rrtype == qtype)
                       {
                         if (crec_isstale(crecp, now))
                           {
                             if (stale)
                               *stale = 1;
                             
-                            stale_flag = F_STALE;
+                            flags |= F_STALE;
                           }
                         
-                        if (!(crecp->flags & F_DNSSECOK))
+                        if (!(flags & F_DNSSECOK))
                           sec_data = 0;
 
-                        if (crecp->flags & F_NXDOMAIN)
+                        if (flags & F_NXDOMAIN)
                           nxdomain = 1;
-
+                        else if (rr_on_list(daemon->filter_rr, qtype))
+                          flags |=  F_NEG | F_CONFIG;
+                        
                         auth = 0;
                         ans = 1;
                         
@@ -2134,7 +2115,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                           {
                             char *rrdata = NULL;
 
-                            if (!(crecp->flags & F_NEG))
+                            if (!(flags & F_NEG))
                               {
                                 rrdata = blockdata_retrieve(crecp->addr.rr.rrdata, crecp->addr.rr.datalen, NULL);
                             
@@ -2148,38 +2129,46 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                             if (qtype == T_TXT && !(crecp->flags & F_NEG))
                               log_txt(name, (unsigned char *)rrdata, crecp->addr.rr.datalen, crecp->flags & F_DNSSECOK);
                             else
-                              log_query(stale_flag | crecp->flags, name, &crecp->addr, NULL, 0);
+                              log_query(flags, name, &crecp->addr, NULL, 0);
                           }
                       }
                   } while ((crecp = cache_find_by_name(crecp, name, now, F_RR)));
            }
-       }
-      
-      
-      if (!ans)
-       {
-         if (option_bool(OPT_FILTER) && (qtype == T_SRV || (qtype == T_ANY && strchr(name, '_'))))
+         
+         if (!ans && option_bool(OPT_FILTER) && (qtype == T_SRV || (qtype == T_ANY && strchr(name, '_'))))
            {
              ans = 1;
              sec_data = 0;
              if (!dryrun)
                log_query(F_CONFIG | F_NEG, name, NULL, NULL, 0);
            }
-         else if ((crecp = cache_find_by_name(NULL, name, now, F_NXDOMAIN)))
+         
+         
+         if (!ans && rr_on_list(daemon->filter_rr, qtype))
            {
-             /* We may know that the domain doesn't exist for any RRtype. */
-             ans = nxdomain = 1;
-             auth = 0;
-
-             if (!(crecp->flags & F_DNSSECOK)) 
-               sec_data = 0;
+             /* We don't have a cached answer and when we get an answer from upstream we're going to
+                filter it anyway. If we have a cached answer for the domain for another RRtype then
+                that may be enough to tell us if the answer should be NODATA and save the round trip.
+                Cached NXDOMAIN has already been handled, so here we look for any record for the domain,
+                since its existence allows us to return a NODATA answer. Note that we never set the AD flag,
+                since we didn't authenticate the record. */
              
-             if (!dryrun)
-               log_query(F_NXDOMAIN | F_NEG, name, NULL, NULL, 0);
+             if (cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_RR | F_CNAME))
+               {
+                 ans = 1;
+                 sec_data = auth = 0;
+                 
+                 if (!dryrun)
+                   log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0);
+                 
+                 if (filtered)
+                   *filtered = 1;
+               }
            }
-         else
-           return 0; /* failed to answer a question */
        }
+      
+      if (!ans)
+       return 0; /* failed to answer a question */
     }
   
   if (dryrun)
index e4c56cb..d98236e 100644 (file)
@@ -219,10 +219,7 @@ size_t rrfilter(struct dns_header *header, size_t *plen, int mode)
          if (class != C_IN)
            continue;
          
-         if (mode == RRFILTER_A && type != T_A)
-           continue;
-
-         if (mode == RRFILTER_AAAA && type != T_AAAA)
+         if (!rr_on_list(daemon->filter_rr, type))
            continue;
        }