Handle caching SOA for negative PTR queries.
authorSimon Kelley <simon@thekelleys.org.uk>
Fri, 2 Feb 2024 23:07:57 +0000 (23:07 +0000)
committerSimon Kelley <simon@thekelleys.org.uk>
Sat, 3 Feb 2024 20:46:23 +0000 (20:46 +0000)
Also deal with the fact that a root SOA is a thing.

src/cache.c
src/rfc1035.c

index e00c922..69afeca 100644 (file)
@@ -802,32 +802,28 @@ void cache_end_insert(void)
              read_write(daemon->pipe_to_parent, (unsigned char *)name, m, 0);
              read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0);
              read_write(daemon->pipe_to_parent, (unsigned  char *)&flags, sizeof(flags), 0);
-
-             if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_RR))
+             read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
+             
+             if (flags & F_RR)
                {
-                 read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
-
-                 if (flags & F_RR)
-                   {
-                     /* A negative RR entry is possible and has no data, obviously. */
-                     if (!(flags & F_NEG) && (flags & F_KEYTAG))
-                       blockdata_write(new_chain->addr.rrblock.rrdata, new_chain->addr.rrblock.datalen, daemon->pipe_to_parent);
-                   }
+                 /* A negative RR entry is possible and has no data, obviously. */
+                 if (!(flags & F_NEG) && (flags & F_KEYTAG))
+                   blockdata_write(new_chain->addr.rrblock.rrdata, new_chain->addr.rrblock.datalen, daemon->pipe_to_parent);
+               }
 #ifdef HAVE_DNSSEC
-                 if (flags & F_DNSKEY)
-                   {
-                     read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
-                     blockdata_write(new_chain->addr.key.keydata, new_chain->addr.key.keylen, daemon->pipe_to_parent);
-                   }
-                 else if (flags & F_DS)
-                   {
-                     read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
-                     /* A negative DS entry is possible and has no data, obviously. */
-                     if (!(flags & F_NEG))
-                       blockdata_write(new_chain->addr.ds.keydata, new_chain->addr.ds.keylen, daemon->pipe_to_parent);
-                   }
-#endif
+             if (flags & F_DNSKEY)
+               {
+                 read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
+                 blockdata_write(new_chain->addr.key.keydata, new_chain->addr.key.keylen, daemon->pipe_to_parent);
                }
+             else if (flags & F_DS)
+               {
+                 read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
+                 /* A negative DS entry is possible and has no data, obviously. */
+                 if (!(flags & F_NEG))
+                   blockdata_write(new_chain->addr.ds.keydata, new_chain->addr.ds.keylen, daemon->pipe_to_parent);
+               }
+#endif
            }
        }
       
@@ -871,7 +867,8 @@ int cache_recv_insert(time_t now, int fd)
 
       if (!read_write(fd, (unsigned char *)daemon->namebuff, m, 1) ||
          !read_write(fd, (unsigned char *)&ttd, sizeof(ttd), 1) ||
-         !read_write(fd, (unsigned char *)&flags, sizeof(flags), 1))
+         !read_write(fd, (unsigned char *)&flags, sizeof(flags), 1) ||
+         !read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
        return 0;
 
       daemon->namebuff[m] = 0;
@@ -902,30 +899,23 @@ int cache_recv_insert(time_t now, int fd)
        {
          unsigned short class = C_IN;
 
-         if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_RR))
+         if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
+             && !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
+           return 0;
+#ifdef HAVE_DNSSEC
+         if (flags & F_DNSKEY)
            {
-             if (!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
+             if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
+                 !(addr.key.keydata = blockdata_read(fd, addr.key.keylen)))
                return 0;
-             
-             if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
-                 && !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
+           }
+         else  if (flags & F_DS)
+           {
+             if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
+                 (!(flags & F_NEG) && !(addr.key.keydata = blockdata_read(fd, addr.key.keylen))))
                return 0;
-#ifdef HAVE_DNSSEC
-             if (flags & F_DNSKEY)
-               {
-                 if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
-                     !(addr.key.keydata = blockdata_read(fd, addr.key.keylen)))
-                   return 0;
-               }
-             else  if (flags & F_DS)
-               {
-                 if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
-                     (!(flags & F_NEG) && !(addr.key.keydata = blockdata_read(fd, addr.key.keylen))))
-                   return 0;
-               }
-#endif
            }
-         
+#endif
          crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags);
        }
     }
@@ -1809,8 +1799,18 @@ static void dump_cache_entry(struct crec *cache, time_t now)
   p = buff;
   
   *a = 0;
-  if (strlen(n) == 0 && !(cache->flags & F_REVERSE))
-    n = "<Root>";
+
+  if (cache->flags & F_REVERSE)
+    {
+      if ((cache->flags & F_NEG))
+       n = "";
+    }
+  else
+    {
+      if (strlen(n) == 0)
+       n = "<Root>";
+    }
+  
   p += sprintf(p, "%-30.30s ", sanitise(n));
   if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
     a = sanitise(cache_get_cname_target(cache));
index 0d0cdb8..a83d9c1 100644 (file)
@@ -454,7 +454,7 @@ int do_doctor(struct dns_header *header, size_t qlen)
    Cache said SOA and return the difference in length between name and the name of the 
    SOA RR so we can look it up again.
 */
-static int find_soa(struct dns_header *header, size_t qlen, char *name, int *substring, int no_cache, time_t now)
+static int find_soa(struct dns_header *header, size_t qlen, char *name, int *substring, unsigned long *ttlp, int no_cache, time_t now)
 {
   unsigned char *p, *psave;
   int qtype, qclass, rdlen;
@@ -472,6 +472,9 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
   
   if (substring)
     *substring = name_len;
+
+  if (ttlp)
+    *ttlp = daemon->neg_ttl;
   
   for (i = 0; i < ntohs(header->nscount); i++)
     {
@@ -571,7 +574,10 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
              if (substring)
                *substring = prefix;
              
-             return minttl;
+             if (ttlp)
+               *ttlp = minttl;
+
+             return 1;
            }
        }
 
@@ -581,7 +587,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
        return 0; /* bad packet */
     }
   
-  return daemon->neg_ttl;
+  return 0;
 }
 
 /* Print TXT reply to log */
@@ -747,14 +753,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
       
       if (!found && !option_bool(OPT_NO_NEG))
        {
-         /* don't cache SOAs for negative PTR records */
-         ttl = find_soa(header, qlen, name, NULL, 1, now);
+         /* For reverse records, we use the name field to store the SOA name. */
+         int substring, have_soa = find_soa(header, qlen, name, &substring, &ttl, no_cache_dnssec, now);
          
          flags |= F_NEG | (secure ?  F_DNSSECOK : 0);
          if (name_encoding && ttl)
            {
              flags |= F_REVERSE | name_encoding;
-             cache_insert(NULL, &addr, C_IN, now, ttl, flags);
+             if (!have_soa)
+               flags |= F_NO_RR; /* Marks no SOA found. */
+             cache_insert(name + substring, &addr, C_IN, now, ttl, flags);
            }
          
          log_query(flags | F_UPSTREAM, name, &addr, NULL, 0);
@@ -1038,7 +1046,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
       
       if (!found && (qtype != T_ANY || (flags & F_NXDOMAIN)))
        {
-         int substring;
+         int substring, have_soa;
 
          if (flags & F_NXDOMAIN)
            {
@@ -1050,15 +1058,23 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
          
          log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL, 0);
          
-         /* If there's no SOA to get the TTL from, but there is a CNAME 
-            pointing at this, inherit its TTL */
-         if (insert && !option_bool(OPT_NO_NEG) && ((ttl = find_soa(header, qlen, name, &substring, no_cache_dnssec, now)) || cpp))
+         if (insert && !option_bool(OPT_NO_NEG))
            {
-             addr.rrdata.datalen = substring;
-             addr.rrdata.rrtype = qtype;
+             int have_soa = find_soa(header, qlen, name, &substring, &ttl, no_cache_dnssec, now);
              
-             if (ttl == 0)
-               ttl = cttl;
+             /* If there's no SOA to get the TTL from, but there is a CNAME 
+                pointing at this, inherit its TTL */
+             if (ttl || cpp)
+               {
+                 if (!ttl)
+                   ttl = cttl;
+                 
+                 addr.rrdata.datalen = substring;
+                 addr.rrdata.rrtype = qtype;
+                 
+                 if (!have_soa)
+                   flags |= F_NO_RR; /* Marks no SOA found. */
+               }
              
              newc = cache_insert(name, &addr, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));  
              if (newc && cpp)
@@ -1627,6 +1643,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
            stale_flag = F_STALE;
          }
        
+       if (crecp->flags & F_NEG)
+         soa_lookup = crecp;
+         
        if (crecp->flags & F_NXDOMAIN)
          {
            if (qtype == T_CNAME)
@@ -1831,6 +1850,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                          if (crecp->flags & F_NXDOMAIN)
                            nxdomain = 1;
                          log_query(stale_flag | (crecp->flags & ~F_FORWARD), name, &addr, NULL, 0);
+                         soa_lookup = crecp;
                        }
                      else
                        {
@@ -2236,21 +2256,31 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
   if (!ans)
     return 0; /* failed to answer a question */
 
-  if (soa_lookup)
+  /* We found a negative record. See if we have an SOA record to 
+     return in the AUTH section. 
+     
+     For FORWARD NEG records, the addr.rrdata.datalen field of the othewise
+     empty addr is used to held an offset in to the name which yields the SOA
+     name. For REVERSE NEG records, the otherwise empty name field holds the
+     SOA name. If soa_name has zero length, then no SOA is known. soa_lookup
+     MUST be a neg record here.
+     
+     If the F_NO_RR flag is set, there was no SOA record supplied with the RR.  */
+  if (soa_lookup && !(soa_lookup->flags & F_NO_RR))
     {
-       /* We found a negative record. See if we have an SOA record to 
-        return in the AUTH section. */
-      char *rrdata;
-      int substring = soa_lookup->addr.rrdata.datalen;
+      char *soa_name = soa_lookup->flags & F_REVERSE ? cache_get_name(soa_lookup) : name + soa_lookup->addr.rrdata.datalen;
+      
       crecp = NULL;
-      while ((crecp = cache_find_by_name(crecp, name + substring, now, F_RR)))
+      while ((crecp = cache_find_by_name(crecp, soa_name, now, F_RR)))
        if (crecp->addr.rrblock.rrtype == T_SOA)
          {
+           char *rrdata;
+           
            if (!(crecp->flags & F_NEG) &&
                (rrdata = blockdata_retrieve(crecp->addr.rrblock.rrdata, crecp->addr.rrblock.datalen, NULL)) &&
                add_resource_record(header, limit, &trunc, 0, &ansp, 
                                    crec_ttl(crecp, now), NULL, T_SOA, C_IN, "t",
-                                   name + substring, crecp->addr.rrblock.datalen, rrdata))
+                                   soa_name, crecp->addr.rrblock.datalen, rrdata))
              {
                nscount++;