Imported Upstream version 2.88
[platform/upstream/dnsmasq.git] / src / domain-match.c
index f8e4796..fe8e25a 100644 (file)
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2022 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
@@ -207,16 +207,20 @@ int lookup_domain(char *domain, int flags, int *lowout, int *highout)
                }
            }
          
-         if (found)
+         if (found && filter_servers(try, flags, &nlow, &nhigh))
+           /* We have a match, but it may only be (say) an IPv6 address, and
+              if the query wasn't for an AAAA record, it's no good, and we need
+              to continue generalising */
            {
              /* We've matched a setting which says to use servers without a domain.
-                Continue the search with empty query */
-             if (daemon->serverarray[try]->flags & SERV_USE_RESOLV)
-               crop_query = qlen;
-             else if (filter_servers(try, flags, &nlow, &nhigh))
-               /* We have a match, but it may only be (say) an IPv6 address, and
-                  if the query wasn't for an AAAA record, it's no good, and we need
-                  to continue generalising */
+                Continue the search with empty query. We set the F_SERVER flag
+                so that --address=/#/... doesn't match. */
+             if (daemon->serverarray[nlow]->flags & SERV_USE_RESOLV)
+               {
+                 crop_query = qlen;
+                 flags |= F_SERVER;
+               }
+             else
                break;
            }
        }
@@ -273,7 +277,7 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
     nlow--;
   
   while (nhigh < daemon->serverarraysz-1 && order_servers(daemon->serverarray[nhigh], daemon->serverarray[nhigh+1]) == 0)
-       nhigh++;
+    nhigh++;
   
   nhigh++;
   
@@ -293,13 +297,13 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
   else
     {
       /* Now the servers are on order between low and high, in the order
-        IPv6 addr, IPv4 addr, return zero for both, send upstream, no-data return.
+        IPv6 addr, IPv4 addr, return zero for both, resolvconf servers, send upstream, no-data return.
         
         See which of those match our query in that priority order and narrow (low, high) */
-
+      
       for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
       
-      if (i != nlow && (flags & F_IPV6))
+      if (!(flags & F_SERVER) && i != nlow && (flags & F_IPV6))
        nhigh = i;
       else
        {
@@ -307,7 +311,7 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
          
          for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++);
          
-         if (i != nlow && (flags & F_IPV4))
+         if (!(flags & F_SERVER) && i != nlow && (flags & F_IPV4))
            nhigh = i;
          else
            {
@@ -315,38 +319,46 @@ int filter_servers(int seed, int flags, int *lowout, int *highout)
              
              for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_ALL_ZEROS); i++);
              
-             if (i != nlow && (flags & (F_IPV4 | F_IPV6)))
+             if (!(flags & F_SERVER) && i != nlow && (flags & (F_IPV4 | F_IPV6)))
                nhigh = i;
              else
                {
                  nlow = i;
                  
-                 /* now look for a server */
-                 for (i = nlow; i < nhigh && !(daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
-
+                 /* Short to resolv.conf servers */
+                 for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_USE_RESOLV); i++);
+                 
                  if (i != nlow)
-                   {
-                     /* If we want a server that can do DNSSEC, and this one can't, 
-                        return nothing, similarly if were looking only for a server
-                        for a particular domain. */
-                     if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
-                       nlow = nhigh;
-                     else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
-                       nlow = nhigh;
-                     else
-                       nhigh = i;
-                   }
+                   nhigh = i;
                  else
                    {
-                     /* --local=/domain/, only return if we don't need a server. */
-                     if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER))
-                       nhigh = i;
+                     /* now look for a server */
+                     for (i = nlow; i < nhigh && !(daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
+                     
+                     if (i != nlow)
+                       {
+                         /* If we want a server that can do DNSSEC, and this one can't, 
+                            return nothing, similarly if were looking only for a server
+                            for a particular domain. */
+                         if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
+                           nlow = nhigh;
+                         else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
+                           nlow = nhigh;
+                         else
+                           nhigh = i;
+                       }
+                     else
+                       {
+                         /* --local=/domain/, only return if we don't need a server. */
+                         if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER))
+                           nhigh = i;
+                       }
                    }
                }
            }
        }
     }
-  
+
   *lowout = nlow;
   *highout = nhigh;
   
@@ -387,13 +399,13 @@ int is_local_answer(time_t now, int first, char *name)
 
 size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last, int ede)
 {
-  int trunc = 0;
+  int trunc = 0, anscount = 0;
   unsigned char *p;
   int start;
   union all_addr addr;
   
   if (flags & (F_NXDOMAIN | F_NOERR))
-    log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL);
+    log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL, 0);
          
   setup_reply(header, flags, ede);
          
@@ -410,9 +422,9 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
        else
          addr.addr4 = srv->addr;
        
-       header->ancount = htons(ntohs(header->ancount) + 1);
-       add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr);
-       log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL);
+       if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr))
+         anscount++;
+       log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL, 0);
       }
   
   if (flags & gotname & F_IPV6)
@@ -425,14 +437,15 @@ size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header
        else
          addr.addr6 = srv->addr;
        
-       header->ancount = htons(ntohs(header->ancount) + 1);
-       add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr);
-       log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL);
+       if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr))
+         anscount++;
+       log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL, 0);
       }
 
   if (trunc)
     header->hb3 |= HB3_TC;
-
+  header->ancount = htons(anscount);
+  
   return p - (unsigned char *)header;
 }
 
@@ -485,7 +498,7 @@ static int order(char *qdomain, size_t qlen, struct server *serv)
   if (qlen > dlen)
     return -1;
 
-  return strcmp(qdomain, serv->domain);
+  return hostname_order(qdomain, serv->domain);
 }
 
 static int order_servers(struct server *s1, struct server *s2)
@@ -520,10 +533,10 @@ static int order_qsort(const void *a, const void *b)
   /* Sort all literal NODATA and local IPV4 or IPV6 responses together,
      in a very specific order. We flip the SERV_LITERAL_ADDRESS bit
      so the order is IPv6 literal, IPv4 literal, all-zero literal, 
-     upstream server, NXDOMAIN literal. */
+     unqualified servers, upstream server, NXDOMAIN literal. */
   if (rc == 0)
-    rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS) -
-      ((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS);
+    rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_USE_RESOLV | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS) -
+      ((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_USE_RESOLV | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS);
 
   /* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */
   if (rc == 0)
@@ -533,22 +546,53 @@ static int order_qsort(const void *a, const void *b)
   return rc;
 }
 
+
+/* When loading large numbers of server=.... lines during startup,
+   there's no possibility that there will be server records that can be reused, but
+   searching a long list for each server added grows as O(n^2) and slows things down.
+   This flag is set only if is known there may be free server records that can be reused.
+   There's a call to mark_servers(0) in read_opts() to reset the flag before
+   main config read. */
+
+static int maybe_free_servers = 0;
+
+/* Must be called before  add_update_server() to set daemon->servers_tail */
 void mark_servers(int flag)
 {
-  struct server *serv;
+  struct server *serv, *next, **up;
 
+  maybe_free_servers = !!flag;
+  
+  daemon->servers_tail = NULL;
+  
   /* mark everything with argument flag */
   for (serv = daemon->servers; serv; serv = serv->next)
-    if (serv->flags & flag)
-      serv->flags |= SERV_MARK;
-    else
-      serv->flags &= ~SERV_MARK;
+    {
+      if (serv->flags & flag)
+       serv->flags |= SERV_MARK;
+      else
+       serv->flags &= ~SERV_MARK;
 
-  for (serv = daemon->local_domains; serv; serv = serv->next)
-    if (serv->flags & flag)
-      serv->flags |= SERV_MARK;
-    else
-      serv->flags &= ~SERV_MARK;
+      daemon->servers_tail = serv;
+    }
+  
+  /* --address etc is different: since they are expected to be 
+     1) numerous and 2) not reloaded often. We just delete 
+     and recreate. */
+  if (flag)
+    for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = next)
+      {
+       next = serv->next;
+
+       if (serv->flags & flag)
+         {
+           *up = next;
+           free(serv->domain);
+           free(serv);
+         }
+       else 
+         up = &serv->next;
+      }
 }
 
 void cleanup_servers(void)
@@ -556,7 +600,7 @@ void cleanup_servers(void)
   struct server *serv, *tmp, **up;
 
   /* unlink and free anything still marked. */
-  for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp) 
+  for (serv = daemon->servers, up = &daemon->servers, daemon->servers_tail = NULL; serv; serv = tmp) 
     {
       tmp = serv->next;
       if (serv->flags & SERV_MARK)
@@ -567,21 +611,11 @@ void cleanup_servers(void)
         free(serv);
        }
       else 
-       up = &serv->next;
+       {
+         up = &serv->next;
+         daemon->servers_tail = serv;
+       }
     }
-  
- for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = tmp) 
-   {
-     tmp = serv->next;
-      if (serv->flags & SERV_MARK)
-       {
-        *up = serv->next;
-        free(serv->domain);
-        free(serv);
-       }
-      else 
-       up = &serv->next;
-   }
 }
 
 int add_update_server(int flags,
@@ -609,82 +643,95 @@ int add_update_server(int flags,
   
   if (*domain == 0)
     alloc_domain = whine_malloc(1);
-  else if (!(alloc_domain = canonicalise((char *)domain, NULL)))
+  else
+    alloc_domain = canonicalise((char *)domain, NULL);
+
+  if (!alloc_domain)
     return 0;
-  
-  /* See if there is a suitable candidate, and unmark
-     only do this for forwarding servers, not 
-     address or local, to avoid delays on large numbers. */
+
   if (flags & SERV_IS_LOCAL)
-    for (serv = daemon->servers; serv; serv = serv->next)
-      if ((serv->flags & SERV_MARK) &&
-         hostname_isequal(alloc_domain, serv->domain))
-       break;
-  
-  if (serv)
-    {
-      free(alloc_domain);
-      alloc_domain = serv->domain;
-    }
-  else
     {
       size_t size;
-
-      if (flags & SERV_LITERAL_ADDRESS)
-       {
-         if (flags & SERV_6ADDR)
-           size = sizeof(struct serv_addr6);
-         else if (flags & SERV_4ADDR)
-           size = sizeof(struct serv_addr4);
-         else
-           size = sizeof(struct serv_local);
-       }
+      
+      if (flags & SERV_6ADDR)
+       size = sizeof(struct serv_addr6);
+      else if (flags & SERV_4ADDR)
+       size = sizeof(struct serv_addr4);
       else
-       size = sizeof(struct server);
+       size = sizeof(struct serv_local);
       
       if (!(serv = whine_malloc(size)))
-       return 0;
+       {
+         free(alloc_domain);
+         return 0;
+       }
+      
+      serv->next = daemon->local_domains;
+      daemon->local_domains = serv;
       
-      if (flags & SERV_IS_LOCAL)
+      if (flags & SERV_4ADDR)
+       ((struct serv_addr4*)serv)->addr = local_addr->addr4;
+      
+      if (flags & SERV_6ADDR)
+       ((struct serv_addr6*)serv)->addr = local_addr->addr6;
+    }
+  else
+    { 
+      /* Upstream servers. See if there is a suitable candidate, if so unmark
+        and move to the end of the list, for order. The entry found may already
+        be at the end. */
+      struct server **up, *tmp;
+
+      serv = NULL;
+      
+      if (maybe_free_servers)
+       for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp)
+         {
+           tmp = serv->next;
+           if ((serv->flags & SERV_MARK) &&
+               hostname_isequal(alloc_domain, serv->domain))
+             {
+               /* Need to move down? */
+               if (serv->next)
+                 {
+                   *up = serv->next;
+                   daemon->servers_tail->next = serv;
+                   daemon->servers_tail = serv;
+                   serv->next = NULL;
+                 }
+               break;
+             }
+           else
+             up = &serv->next;
+         }
+      
+      if (serv)
        {
-         serv->next = daemon->local_domains;
-         daemon->local_domains = serv;
+         free(alloc_domain);
+         alloc_domain = serv->domain;
        }
       else
        {
-         struct server *s;
-         /* Add to the end of the chain, for order */
-         if (!daemon->servers)
-           daemon->servers = serv;
-         else
+         if (!(serv = whine_malloc(sizeof(struct server))))
            {
-             for (s = daemon->servers; s->next; s = s->next);
-             s->next = serv;
+             free(alloc_domain);
+             return 0;
            }
          
-         serv->next = NULL;
+         memset(serv, 0, sizeof(struct server));
+         
+         /* Add to the end of the chain, for order */
+         if (daemon->servers_tail)
+           daemon->servers_tail->next = serv;
+         else
+           daemon->servers = serv;
+         daemon->servers_tail = serv;
        }
-    }
-  
-  if (!(flags & SERV_IS_LOCAL))
-    memset(serv, 0, sizeof(struct server));
-  
-  serv->flags = flags;
-  serv->domain = alloc_domain;
-  serv->domain_len = strlen(alloc_domain);
-  
-  if (flags & SERV_4ADDR)
-    ((struct serv_addr4*)serv)->addr = local_addr->addr4;
-
-  if (flags & SERV_6ADDR)
-    ((struct serv_addr6*)serv)->addr = local_addr->addr6;
-  
-  if (!(flags & SERV_IS_LOCAL))
-    {
+      
 #ifdef HAVE_LOOP
       serv->uid = rand32();
 #endif      
-      
+         
       if (interface)
        safe_strncpy(serv->interface, interface, sizeof(serv->interface));
       if (addr)
@@ -692,7 +739,11 @@ int add_update_server(int flags,
       if (source_addr)
        serv->source_addr = *source_addr;
     }
-
+    
+  serv->flags = flags;
+  serv->domain = alloc_domain;
+  serv->domain_len = strlen(alloc_domain);
+  
   return 1;
 }