]> git.ipfire.org Git - people/ms/dnsmasq.git/blobdiff - src/forward.c
import of dnsmasq-2.36.tar.gz
[people/ms/dnsmasq.git] / src / forward.c
index 523a6e35eabcf859612ead1bc6dabbfb43332a68..85fcb4c5286093d0afedf7be8c905d96b0ef1dbf 100644 (file)
 
 static struct frec *frec_list = NULL;
 
-static struct frec *lookup_frec(unsigned short id);
+static struct frec *lookup_frec(unsigned short id, unsigned int crc);
 static struct frec *lookup_frec_by_sender(unsigned short id,
                                          union mysockaddr *addr,
                                          unsigned int crc);
-static unsigned short get_id(void);
+static unsigned short get_id(int force, unsigned short force_id, unsigned int crc);
 
 
 /* Send a UDP packet with it's source address set as "source" 
@@ -163,7 +163,7 @@ static unsigned short search_servers(struct daemon *daemon, time_t now, struct a
              {
                if ((sflag | F_QUERY ) & qtype)
                  {
-                   flags = qtype;
+                   flags = qtype & ~F_BIGNAME;
                    if (serv->addr.sa.sa_family == AF_INET) 
                      *addrpp = (struct all_addr *)&serv->addr.in.sin_addr;
 #ifdef HAVE_IPV6
@@ -184,7 +184,9 @@ static unsigned short search_servers(struct daemon *daemon, time_t now, struct a
       else
        log_query(F_CONFIG | F_FORWARD | flags, qdomain, *addrpp, 0, NULL, 0);
     }
-  else if (qtype && (daemon->options & OPT_NODOTS_LOCAL) && !strchr(qdomain, '.') && namelen != 0)
+  else if (qtype && !(qtype & F_BIGNAME) && 
+          (daemon->options & OPT_NODOTS_LOCAL) && !strchr(qdomain, '.') && namelen != 0)
+    /* don't forward simple names, make exception from NS queries and empty name. */
     flags = F_NXDOMAIN;
     
   if (flags == F_NXDOMAIN && check_for_local_domain(qdomain, now, daemon))
@@ -237,12 +239,16 @@ static void forward_query(struct daemon *daemon, int udpfd, union mysockaddr *ud
       
       if (forward)
        {
+         /* force unchanging id for signed packets */
+         int is_sign;
+         find_pseudoheader(header, plen, NULL, NULL, &is_sign);
+         
          forward->source = *udpaddr;
          forward->dest = *dst_addr;
          forward->iface = dst_iface;
-         forward->new_id = get_id();
-         forward->fd = udpfd;
          forward->orig_id = ntohs(header->id);
+         forward->new_id = get_id(is_sign, forward->orig_id, crc);
+         forward->fd = udpfd;
          forward->crc = crc;
          forward->forwardall = 0;
          header->id = htons(forward->new_id);
@@ -325,7 +331,7 @@ static void forward_query(struct daemon *daemon, int udpfd, union mysockaddr *ud
       
       /* could not send on, prepare to return */ 
       header->id = htons(forward->orig_id);
-      forward->new_id = 0; /* cancel */
+      forward->sentto = NULL; /* cancel */
     }    
   
   /* could not send on, return empty answer or address if known for whole domain */
@@ -339,17 +345,17 @@ static void forward_query(struct daemon *daemon, int udpfd, union mysockaddr *ud
 }
 
 static size_t process_reply(struct daemon *daemon, HEADER *header, time_t now, 
-                           unsigned int query_crc, struct server *server, size_t n)
+                           struct server *server, size_t n)
 {
   unsigned char *pheader, *sizep;
-  int munged = 0;
+  int munged = 0, is_sign;
   size_t plen; 
 
   /* If upstream is advertising a larger UDP packet size
         than we allow, trim it so that we don't get overlarge
-        requests for the client. */
+        requests for the client. We can't do this for signed packets. */
 
-  if ((pheader = find_pseudoheader(header, n, &plen, &sizep)))
+  if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)) && !is_sign)
     {
       unsigned short udpsz;
       unsigned char *psave = sizep;
@@ -359,7 +365,7 @@ static size_t process_reply(struct daemon *daemon, HEADER *header, time_t now,
        PUTSHORT(daemon->edns_pktsz, psave);
     }
 
-  if (header->opcode != QUERY || (header->rcode != NOERROR && header->rcode != NXDOMAIN))
+  if (is_sign || header->opcode != QUERY || (header->rcode != NOERROR && header->rcode != NXDOMAIN))
     return n;
   
   /* Complain loudly if the upstream server is non-recursive. */
@@ -392,12 +398,8 @@ static size_t process_reply(struct daemon *daemon, HEADER *header, time_t now,
          header->aa = 1;
          header->rcode = NOERROR;
        }
-  
-      /* If the crc of the question section doesn't match the crc we sent, then
-        someone might be attempting to insert bogus values into the cache by 
-        sending replies containing questions and bogus answers. */
-      if (query_crc == questions_crc(header, n, daemon->namebuff))
-       extract_addresses(header, n, daemon->namebuff, now, daemon);
+      
+      extract_addresses(header, n, daemon->namebuff, now, daemon);
     }
   
   /* do this after extract_addresses. Ensure NODATA reply and remove
@@ -421,16 +423,16 @@ void reply_query(struct serverfd *sfd, struct daemon *daemon, time_t now)
 {
   /* packet from peer server, extract data for cache, and send to
      original requester */
-  struct frec *forward;
   HEADER *header;
   union mysockaddr serveraddr;
+  struct frec *forward;
   socklen_t addrlen = sizeof(serveraddr);
   ssize_t n = recvfrom(sfd->fd, daemon->packet, daemon->edns_pktsz, 0, &serveraddr.sa, &addrlen);
   size_t nn;
 
   /* packet buffer overwritten */
   daemon->srv_save = NULL;
-
+  
   /* Determine the address of the server replying  so that we can mark that as good */
   serveraddr.sa.sa_family = sfd->source_addr.sa.sa_family;
 #ifdef HAVE_IPV6
@@ -439,51 +441,55 @@ void reply_query(struct serverfd *sfd, struct daemon *daemon, time_t now)
 #endif
   
   header = (HEADER *)daemon->packet;
-  forward = lookup_frec(ntohs(header->id));
   
-  if (n >= (int)sizeof(HEADER) && header->qr && forward)
+  if (n >= (int)sizeof(HEADER) && header->qr && 
+      (forward = lookup_frec(ntohs(header->id), questions_crc(header, n, daemon->namebuff))))
     {
-       struct server *server = forward->sentto;
-       
-       if ((header->rcode == SERVFAIL || header->rcode == REFUSED) && forward->forwardall == 0)
-        /* for broken servers, attempt to send to another one. */
-        {
-          unsigned char *pheader;
-          size_t plen;
-          
-          /* recreate query from reply */
-          pheader = find_pseudoheader(header, (size_t)n, &plen, NULL);
-          header->ancount = htons(0);
-          header->nscount = htons(0);
-          header->arcount = htons(0);
-          if ((nn = resize_packet(header, (size_t)n, pheader, plen)))
-            {
-              header->qr = 0;
-              header->tc = 0;
-              forward_query(daemon, -1, NULL, NULL, 0, header, nn, now, forward);
-              return;
-            }
-        }   
-
-       if ((forward->sentto->flags & SERV_TYPE) == 0)
-        {
-          if (header->rcode == SERVFAIL || header->rcode == REFUSED)
-            server = NULL;
-          else
-            {
-              struct server *last_server;
-              /* find good server by address if possible, otherwise assume the last one we sent to */ 
-              for (last_server = daemon->servers; last_server; last_server = last_server->next)
-                if (!(last_server->flags & (SERV_LITERAL_ADDRESS | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_NO_ADDR)) &&
-                    sockaddr_isequal(&last_server->addr, &serveraddr))
-                  {
-                    server = last_server;
-                    break;
-                  }
-            } 
-          daemon->last_server = server;
-        }
-                
+      struct server *server = forward->sentto;
+      
+      if ((header->rcode == SERVFAIL || header->rcode == REFUSED) && forward->forwardall == 0)
+       /* for broken servers, attempt to send to another one. */
+       {
+         unsigned char *pheader;
+         size_t plen;
+         int is_sign;
+         
+         /* recreate query from reply */
+         pheader = find_pseudoheader(header, (size_t)n, &plen, NULL, &is_sign);
+         if (!is_sign)
+           {
+             header->ancount = htons(0);
+             header->nscount = htons(0);
+             header->arcount = htons(0);
+             if ((nn = resize_packet(header, (size_t)n, pheader, plen)))
+               {
+                 header->qr = 0;
+                 header->tc = 0;
+                 forward_query(daemon, -1, NULL, NULL, 0, header, nn, now, forward);
+                 return;
+               }
+           }
+       }   
+      
+      if ((forward->sentto->flags & SERV_TYPE) == 0)
+       {
+         if (header->rcode == SERVFAIL || header->rcode == REFUSED)
+           server = NULL;
+         else
+           {
+             struct server *last_server;
+             /* find good server by address if possible, otherwise assume the last one we sent to */ 
+             for (last_server = daemon->servers; last_server; last_server = last_server->next)
+               if (!(last_server->flags & (SERV_LITERAL_ADDRESS | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_NO_ADDR)) &&
+                   sockaddr_isequal(&last_server->addr, &serveraddr))
+                 {
+                   server = last_server;
+                   break;
+                 }
+           } 
+         daemon->last_server = server;
+       }
+      
       /* If the answer is an error, keep the forward record in place in case
         we get a good reply from another server. Kill it when we've
         had replies from all to avoid filling the forwarding table when
@@ -491,14 +497,14 @@ void reply_query(struct serverfd *sfd, struct daemon *daemon, time_t now)
       if (forward->forwardall == 0 || --forward->forwardall == 1 || 
          (header->rcode != REFUSED && header->rcode != SERVFAIL))
        {
-         if ((nn = process_reply(daemon, header, now, forward->crc, server, (size_t)n)))
+         if ((nn = process_reply(daemon, header, now, server, (size_t)n)))
            {
              header->id = htons(forward->orig_id);
              header->ra = 1; /* recursion if available */
              send_from(forward->fd, daemon->options & OPT_NOWILD, daemon->packet, nn, 
                        &forward->source, &forward->dest, forward->iface);
            }
-         forward->new_id = 0; /* cancel */
+         forward->sentto = NULL; /* cancel */
        }
     }
 }
@@ -611,27 +617,24 @@ void receive_query(struct listener *listen, struct daemon *daemon, time_t now)
       if (if_index == 0)
        return;
       
-      if (daemon->if_except || daemon->if_names || (daemon->options & OPT_LOCALISE))
-       {
 #ifdef SIOCGIFNAME
-         ifr.ifr_ifindex = if_index;
-         if (ioctl(listen->fd, SIOCGIFNAME, &ifr) == -1)
-           return;
+      ifr.ifr_ifindex = if_index;
+      if (ioctl(listen->fd, SIOCGIFNAME, &ifr) == -1)
+       return;
 #else
-         if (!if_indextoname(if_index, ifr.ifr_name))
-           return;
+      if (!if_indextoname(if_index, ifr.ifr_name))
+       return;
 #endif
-
-         if (listen->family == AF_INET &&
-             (daemon->options & OPT_LOCALISE) &&
-             ioctl(listen->fd, SIOCGIFNETMASK, &ifr) == -1)
-           return;
-
-         netmask = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
-       }
-
-      if (!iface_check(daemon, listen->family, &dst_addr, ifr.ifr_name))
+      
+      if (!iface_check(daemon, listen->family, &dst_addr, &ifr, &if_index))
        return;
+      
+      if (listen->family == AF_INET &&
+         (daemon->options & OPT_LOCALISE) &&
+         ioctl(listen->fd, SIOCGIFNETMASK, &ifr) == -1)
+       return;
+      
+      netmask = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
     }
   
   if (extract_request(header, (size_t)n, daemon->namebuff, &type))
@@ -788,9 +791,13 @@ unsigned char *tcp_request(struct daemon *daemon, int confd, time_t now,
 #endif 
                  
                  /* There's no point in updating the cache, since this process will exit and
-                    lose the information after one query. We make this call for the alias and 
+                    lose the information after a few queries. We make this call for the alias and 
                     bogus-nxdomain side-effects. */
-                 m = process_reply(daemon, header, now, crc, last_server, (unsigned int)m);
+                 /* If the crc of the question section doesn't match the crc we sent, then
+                    someone might be attempting to insert bogus values into the cache by 
+                    sending replies containing questions and bogus answers. */
+                 if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff))
+                   m = process_reply(daemon, header, now, last_server, (unsigned int)m);
                  
                  break;
                }
@@ -818,7 +825,7 @@ static struct frec *allocate_frec(time_t now)
     {
       f->next = frec_list;
       f->time = now;
-      f->new_id = 0;
+      f->sentto = NULL;
       frec_list = f;
     }
 
@@ -837,7 +844,7 @@ struct frec *get_new_frec(struct daemon *daemon, time_t now, int *wait)
     *wait = 0;
 
   for (f = frec_list, oldest = NULL, count = 0; f; f = f->next, count++)
-    if (f->new_id == 0)
+    if (!f->sentto)
       {
        f->time = now;
        return f;
@@ -858,7 +865,7 @@ struct frec *get_new_frec(struct daemon *daemon, time_t now, int *wait)
 
       if (!wait)
        {
-         oldest->new_id = 0;
+         oldest->sentto = 0;
          oldest->time = now;
        }
       return oldest;
@@ -879,12 +886,14 @@ struct frec *get_new_frec(struct daemon *daemon, time_t now, int *wait)
   return f; /* OK if malloc fails and this is NULL */
 }
  
-static struct frec *lookup_frec(unsigned short id)
+/* crc is all-ones if not known. */
+static struct frec *lookup_frec(unsigned short id, unsigned int crc)
 {
   struct frec *f;
 
   for(f = frec_list; f; f = f->next)
-    if (f->new_id == id)
+    if (f->sentto && f->new_id == id && 
+       (f->crc == crc || crc == 0xffffffff))
       return f;
       
   return NULL;
@@ -897,7 +906,7 @@ static struct frec *lookup_frec_by_sender(unsigned short id,
   struct frec *f;
   
   for(f = frec_list; f; f = f->next)
-    if (f->new_id &&
+    if (f->sentto &&
        f->orig_id == id && 
        f->crc == crc &&
        sockaddr_isequal(&f->source, addr))
@@ -912,8 +921,8 @@ void server_gone(struct daemon *daemon, struct server *server)
   struct frec *f;
   
   for (f = frec_list; f; f = f->next)
-    if (f->new_id != 0 && f->sentto == server)
-      f->new_id = 0;
+    if (f->sentto && f->sentto == server)
+      f->sentto = NULL;
   
   if (daemon->last_server == server)
     daemon->last_server = NULL;
@@ -922,20 +931,25 @@ void server_gone(struct daemon *daemon, struct server *server)
     daemon->srv_save = NULL;
 }
 
-/* return unique random ids between 1 and 65535 */
-static unsigned short get_id(void)
+/* return unique random ids.
+   For signed packets we can't change the ID without breaking the
+   signing, so we keep the same one. In this case force is set, and this
+   routine degenerates into killing any conflicting forward record. */
+static unsigned short get_id(int force, unsigned short force_id, unsigned int crc)
 {
   unsigned short ret = 0;
-
-  while (ret == 0)
+  
+  if (force)
     {
-      ret = rand16();
-      
-      /* scrap ids already in use */
-      if ((ret != 0) && lookup_frec(ret))
-       ret = 0;
+      struct frec *f = lookup_frec(force_id, crc);
+      if (f)
+       f->sentto = NULL; /* free */
+      ret = force_id;
     }
-
+  else do 
+    ret = rand16();
+  while (lookup_frec(ret, crc));
+  
   return ret;
 }