]> git.ipfire.org Git - people/ms/dnsmasq.git/commitdiff
Handle UDP packet loss when fragmentation of large packets is broken.
authorSimon Kelley <simon@thekelleys.org.uk>
Fri, 8 May 2015 15:25:38 +0000 (16:25 +0100)
committerSimon Kelley <simon@thekelleys.org.uk>
Fri, 8 May 2015 15:25:38 +0000 (16:25 +0100)
CHANGELOG
src/config.h
src/dnsmasq.h
src/dnssec.c
src/forward.c
src/network.c
src/option.c
src/rfc1035.c

index af2b22cf8f732f0f4e0183935f53b2c38c54ad23..d8fc57a418bb8e0be4e8496d0d9fb91245f9655a 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -109,6 +109,12 @@ version 2.73
            by quiet-dhcp6. Thanks to J. Pablo Abonia for 
            spotting the problem.
 
+           Try and handle net connections with broken fragmentation 
+           that lose large UDP packets. If a server times out, 
+            reduce the maximum UDP packet size field in the EDNS0
+           header to 1280 bytes. If it then answers, make that
+           change permanent.
+
        
 version 2.72
             Add ra-advrouter mode, for RFC-3775 mobile IPv6 support.
index 8def6f2004611c124f7ff6acfa3d7f8b92128d58..f75fe9db7081ce384b78f66bd71bd0412b8e62fc 100644 (file)
@@ -19,6 +19,7 @@
 #define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
 #define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */
 #define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */
+#define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */
 #define KEYBLOCK_LEN 40 /* choose to mininise fragmentation when storing DNSSEC keys */
 #define DNSSEC_WORK 50 /* Max number of queries to validate one question */
 #define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
index 824a86009439d4f7a185fb2473180e98043ceb52..ab16f79b3ec98cddf5896dc823ecd46152e025b8 100644 (file)
@@ -504,7 +504,7 @@ struct server {
   char interface[IF_NAMESIZE+1];
   struct serverfd *sfd; 
   char *domain; /* set if this server only handles a domain. */ 
-  int flags, tcpfd;
+  int flags, tcpfd, edns_pktsz;
   unsigned int queries, failed_queries;
 #ifdef HAVE_LOOP
   u32 uid;
@@ -594,6 +594,7 @@ struct hostsfile {
 #define FREC_DO_QUESTION       64
 #define FREC_ADDED_PHEADER    128
 #define FREC_CHECK_NOSIGN     256
+#define FREC_TEST_PKTSZ       512
 
 #ifdef HAVE_DNSSEC
 #define HASH_SIZE 20 /* SHA-1 digest size */
@@ -1148,7 +1149,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
 #endif
 
 /* dnssec.c */
-size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr);
+size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz);
 int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
 int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer, int *nons);
index a9e12153ccf2e48b298d69c5a05449870ce69198..e91d7c2cf040a01a4c2f0e931868f7e6899c7468 100644 (file)
@@ -2162,10 +2162,12 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
     }
 }
 
-size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr)
+size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, 
+                            int type, union mysockaddr *addr, int edns_pktsz)
 {
   unsigned char *p;
   char *types = querystr("dnssec-query", type);
+  size_t ret;
 
   if (addr->sa.sa_family == AF_INET) 
     log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);
@@ -2194,7 +2196,12 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i
   PUTSHORT(type, p);
   PUTSHORT(class, p);
 
-  return add_do_bit(header, p - (unsigned char *)header, end);
+  ret = add_do_bit(header, p - (unsigned char *)header, end);
+
+  if (find_pseudoheader(header, ret, NULL, &p, NULL))
+    PUTSHORT(edns_pktsz, p);
+
+  return ret;
 }
 
 /* Go through a domain name, find "pointers" and fix them up based on how many bytes
index a8e403c4b25e4db23c878dff2da74c3178e24e61..592243fd4d3542445e2033f25138113ab3025903 100644 (file)
@@ -253,6 +253,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
   void *hash = &crc;
 #endif
  unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
+ unsigned char *pheader;
 
  (void)do_bit;
 
@@ -261,19 +262,32 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
     forward = NULL;
   else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))))
     {
+      /* If we didn't get an answer advertising a maximal packet in EDNS,
+        fall back to 1280, which should work everywhere on IPv6.
+        If that generates an answer, it will become the new default
+        for this server */
+      forward->flags |= FREC_TEST_PKTSZ;
+      
 #ifdef HAVE_DNSSEC
       /* If we've already got an answer to this query, but we're awaiting keys for validation,
         there's no point retrying the query, retry the key query instead...... */
       if (forward->blocking_query)
        {
          int fd;
-
+         
+         forward->flags &= ~FREC_TEST_PKTSZ;
+         
          while (forward->blocking_query)
            forward = forward->blocking_query;
+          
+         forward->flags |= FREC_TEST_PKTSZ;
          
          blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
          plen = forward->stash_len;
          
+         if (find_pseudoheader(header, plen, NULL, &pheader, NULL))
+           PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : forward->sentto->edns_pktsz, pheader);
+
          if (forward->sentto->addr.sa.sa_family == AF_INET) 
            log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec");
 #ifdef HAVE_IPV6
@@ -417,7 +431,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
          plen = new_plen;
        }
 #endif
-
+      
       while (1)
        { 
          /* only send to servers dealing with our domain.
@@ -464,6 +478,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
                    }
 #endif
                }
+
+             if (find_pseudoheader(header, plen, NULL, &pheader, NULL))
+               PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : start->edns_pktsz, pheader);
              
              if (retry_send(sendto(fd, (char *)header, plen, 0,
                                    &start->addr.sa,
@@ -760,7 +777,6 @@ void reply_query(int fd, int family, time_t now)
     }   
    
   server = forward->sentto;
-  
   if ((forward->sentto->flags & SERV_TYPE) == 0)
     {
       if (RCODE(header) == REFUSED)
@@ -781,7 +797,12 @@ void reply_query(int fd, int family, time_t now)
       if (!option_bool(OPT_ALL_SERVERS))
        daemon->last_server = server;
     }
-
+  /* We tried resending to this server with a smaller maximum size and got an answer.
+     Make that permanent. */
+  if (server && (forward->flags & FREC_TEST_PKTSZ))
+    server->edns_pktsz = SAFE_PKTSZ;
+  
   /* 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
@@ -890,7 +911,7 @@ void reply_query(int fd, int family, time_t now)
                    {
                      new->flags |= FREC_DNSKEY_QUERY; 
                      nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
-                                                daemon->keyname, forward->class, T_DNSKEY, &server->addr);
+                                                daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz);
                    }
                  else 
                    {
@@ -899,7 +920,7 @@ void reply_query(int fd, int family, time_t now)
                      else
                        new->flags |= FREC_DS_QUERY;
                      nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
-                                                daemon->keyname, forward->class, T_DS, &server->addr);
+                                                daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz);
                    }
                  if ((hash = hash_questions(header, nn, daemon->namebuff)))
                    memcpy(new->hash, hash, HASH_SIZE);
@@ -1526,7 +1547,7 @@ static int  tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, s
       
       /* Can't find it in the cache, have to send a query */
 
-      m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr);
+      m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz);
       
       *length = htons(m);
       
@@ -1638,7 +1659,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
 
     another_tcp_key:
       m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class, 
-                               new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr);
+                               new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz);
       
       *length = htons(m);
       
index 992f023c31de67887c94c8bda40b87d76a3451b3..a1d90c876fc17a349226d7b66c3dbc0db994f021 100644 (file)
@@ -1396,6 +1396,7 @@ void add_update_server(int flags,
       serv->domain = domain_str;
       serv->next = next;
       serv->queries = serv->failed_queries = 0;
+      serv->edns_pktsz = daemon->edns_pktsz;
 #ifdef HAVE_LOOP
       serv->uid = rand32();
 #endif      
index f91cfbb1aa545f62bebaaa72f34249b77de83339..c7add88de7ac8a1f77667edcc27182b2381f6e0c 100644 (file)
@@ -4498,15 +4498,19 @@ void read_opts(int argc, char **argv, char *compile_opts)
     {
       struct server *tmp;
       for (tmp = daemon->servers; tmp; tmp = tmp->next)
-       if (!(tmp->flags & SERV_HAS_SOURCE))
-         {
-           if (tmp->source_addr.sa.sa_family == AF_INET)
-             tmp->source_addr.in.sin_port = htons(daemon->query_port);
+       {
+         tmp->edns_pktsz = daemon->edns_pktsz;
+        
+         if (!(tmp->flags & SERV_HAS_SOURCE))
+           {
+             if (tmp->source_addr.sa.sa_family == AF_INET)
+               tmp->source_addr.in.sin_port = htons(daemon->query_port);
 #ifdef HAVE_IPV6
-           else if (tmp->source_addr.sa.sa_family == AF_INET6)
-             tmp->source_addr.in6.sin6_port = htons(daemon->query_port);
+             else if (tmp->source_addr.sa.sa_family == AF_INET6)
+               tmp->source_addr.in6.sin6_port = htons(daemon->query_port);
 #endif 
-         } 
+           }
+       } 
     }
   
   if (daemon->if_addrs)
index 5828055caa5db86f00919d43a2aeb9a47d312e2d..8b1709dd3495c7ded6fdf34da7651f9f88a62fa6 100644 (file)
@@ -552,7 +552,7 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
        return plen;
       *p++ = 0; /* empty name */
       PUTSHORT(T_OPT, p);
-      PUTSHORT(daemon->edns_pktsz, p); /* max packet length */
+      PUTSHORT(SAFE_PKTSZ, p); /* max packet length, this will be overwritten */
       PUTSHORT(0, p);    /* extended RCODE and version */
       PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */
       lenp = p;
@@ -1537,7 +1537,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
   unsigned short flag;
   int q, ans, anscount = 0, addncount = 0;
   int dryrun = 0, sec_reqd = 0, have_pseudoheader = 0;
-  int is_sign;
   struct crec *crecp;
   int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
   struct mx_srv_record *rec;
@@ -1557,28 +1556,19 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
      forward rather than answering from the cache, which doesn't include
      security information, unless we're in DNSSEC validation mode. */
 
-  if (find_pseudoheader(header, qlen, NULL, &pheader, &is_sign))
+  if (find_pseudoheader(header, qlen, NULL, &pheader, NULL))
     { 
-      unsigned short udpsz, flags;
-      unsigned char *psave = pheader;
-
+      unsigned short flags;
+      
       have_pseudoheader = 1;
 
-      GETSHORT(udpsz, pheader);
-      pheader += 2; /* ext_rcode */
+      pheader += 4; /* udp size, ext_rcode */
       GETSHORT(flags, pheader);
       
       if ((sec_reqd = flags & 0x8000))
        *do_bit = 1;/* do bit */ 
-      *ad_reqd = 1;
-
-      /* If our client is advertising a larger UDP packet size
-        than we allow, trim it so that we don't get an overlarge
-        response from upstream */
-
-      if (!is_sign && (udpsz > daemon->edns_pktsz))
-       PUTSHORT(daemon->edns_pktsz, psave); 
 
+      *ad_reqd = 1;
       dryrun = 1;
     }