]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
EDNS fallback when timeout and multiple query rtt backoff.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Mon, 29 Sep 2008 14:50:35 +0000 (14:50 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Mon, 29 Sep 2008 14:50:35 +0000 (14:50 +0000)
git-svn-id: file:///svn/unbound/trunk@1272 be551aaa-1e26-0410-a405-d3ace91eadb9

doc/Changelog
services/cache/infra.c
services/cache/infra.h
services/outside_network.c
services/outside_network.h
testcode/ldns-testpkts.c
testcode/ldns-testpkts.h
testcode/unitmain.c
testdata/edns_lame.tpkg [new file with mode: 0644]
util/rtt.c
util/rtt.h

index 9f9a46c5bc50e519170ff46c04575b9245411504..da0d7c23e326c41ab4ecbdcfb7bd15902407c6d5 100644 (file)
@@ -1,3 +1,8 @@
+29 September 2008: Wouter
+       - EDNS lameness detection, if EDNS packets are dropped this is
+         detected, eventually.
+       - multiple query timeout rtt backoff does not backoff too much.
+
 26 September 2008: Wouter
        - tests for remote-control.
        - small memory leak in exception during remote control fixed.
index 2c366ace5684d64ed60efe1dbe092a2e29bd5d22..dca5080cc00d2d1a199d3d8e1ef34373c970e452 100644 (file)
@@ -208,13 +208,15 @@ new_host_entry(struct infra_cache* infra, struct sockaddr_storage* addr,
        data->ttl = tm + infra->host_ttl;
        data->lameness = NULL;
        data->edns_version = 0;
+       data->edns_lame_known = 0;
        rtt_init(&data->rtt);
        return &key->entry;
 }
 
 int 
 infra_host(struct infra_cache* infra, struct sockaddr_storage* addr,
-        socklen_t addrlen, uint32_t timenow, int* edns_vs, int* to)
+        socklen_t addrlen, uint32_t timenow, int* edns_vs, 
+       uint8_t* edns_lame_known, int* to)
 {
        struct lruhash_entry* e = infra_lookup_host_nottl(infra, addr, 
                addrlen, 0);
@@ -231,6 +233,7 @@ infra_host(struct infra_cache* infra, struct sockaddr_storage* addr,
                        rtt_init(&data->rtt);
                        /* do not touch lameness, it may be valid still */
                        data->edns_version = 0;
+                       data->edns_lame_known = 0;
                }
        }
        if(!e) {
@@ -240,6 +243,7 @@ infra_host(struct infra_cache* infra, struct sockaddr_storage* addr,
                data = (struct infra_host_data*)e->data;
                *to = rtt_timeout(&data->rtt);
                *edns_vs = data->edns_version;
+               *edns_lame_known = data->edns_lame_known;
                slabhash_insert(infra->hosts, e->hash, e, data, NULL);
                return 1;
        }
@@ -247,6 +251,7 @@ infra_host(struct infra_cache* infra, struct sockaddr_storage* addr,
        data = (struct infra_host_data*)e->data;
        *to = rtt_timeout(&data->rtt);
        *edns_vs = data->edns_version;
+       *edns_lame_known = data->edns_lame_known;
        lock_rw_unlock(&e->lock);
        return 1;
 }
@@ -438,7 +443,7 @@ infra_update_tcp_works(struct infra_cache* infra,
 int 
 infra_rtt_update(struct infra_cache* infra,
         struct sockaddr_storage* addr, socklen_t addrlen,
-        int roundtrip, uint32_t timenow)
+        int roundtrip, int orig_rtt, uint32_t timenow)
 {
        struct lruhash_entry* e = infra_lookup_host_nottl(infra, addr, 
                addrlen, 1);
@@ -454,7 +459,7 @@ infra_rtt_update(struct infra_cache* infra,
        data = (struct infra_host_data*)e->data;
        data->ttl = timenow + infra->host_ttl;
        if(roundtrip == -1)
-               rtt_lost(&data->rtt);
+               rtt_lost(&data->rtt, orig_rtt);
        else    rtt_update(&data->rtt, roundtrip);
        if(data->rtt.rto > 0)
                rto = data->rtt.rto;
@@ -483,6 +488,7 @@ infra_edns_update(struct infra_cache* infra,
        data = (struct infra_host_data*)e->data;
        data->ttl = timenow + infra->host_ttl;
        data->edns_version = edns_version;
+       data->edns_lame_known = 1;
 
        if(needtoinsert)
                slabhash_insert(infra->hosts, e->hash, e, e->data, NULL);
index 5b441e3e034973b069179d3245ec0ef8a55b2fef..6417713c195e7367b8f11c7e6dadadfea91268f1 100644 (file)
@@ -70,6 +70,10 @@ struct infra_host_data {
        struct lruhash* lameness;
        /** edns version that the host supports, -1 means no EDNS */
        int edns_version;
+       /** if the EDNS lameness is already known or not.
+        * EDNS lame is when EDNS queries or replies are dropped, 
+        * and cause a timeout */
+       uint8_t edns_lame_known;
 };
 
 /**
@@ -166,11 +170,14 @@ struct infra_host_data* infra_lookup_host(struct infra_cache* infra,
  * @param addrlen: length of addr.
  * @param timenow: what time it is now.
  * @param edns_vs: edns version it supports, is returned.
+ * @param edns_lame_known: if EDNS lame (EDNS is dropped in transit) has
+ *     already been probed, is returned.
  * @param to: timeout to use, is returned.
  * @return: 0 on error.
  */
 int infra_host(struct infra_cache* infra, struct sockaddr_storage* addr, 
-       socklen_t addrlen, uint32_t timenow, int* edns_vs, int* to);
+       socklen_t addrlen, uint32_t timenow, int* edns_vs, 
+       uint8_t* edns_lame_known, int* to);
 
 /**
  * Check for lameness of this server for a particular zone.
@@ -213,12 +220,14 @@ int infra_set_lame(struct infra_cache* infra,
  * @param addrlen: length of addr.
  * @param roundtrip: estimate of roundtrip time in milliseconds or -1 for 
  *     timeout.
+ * @param orig_rtt: original rtt for the query that timed out (roundtrip==-1).
+ *     ignored if roundtrip != -1.
  * @param timenow: what time it is now.
  * @return: 0 on error. new rto otherwise.
  */
 int infra_rtt_update(struct infra_cache* infra,
         struct sockaddr_storage* addr, socklen_t addrlen,
-       int roundtrip, uint32_t timenow);
+       int roundtrip, int orig_rtt, uint32_t timenow);
 
 /**
  * Update information for the host, store that a TCP transaction works.
index 9206ccdd36f9c47ae78f5a62f8c6a17db8d4b7bb..2e6a185ff7b82cb0fe0e7c0963dd164b84b78b7a 100644 (file)
@@ -1084,7 +1084,8 @@ serviced_delete(struct serviced_query* sq)
        if(sq->pending) {
                /* clear up the pending query */
                if(sq->status == serviced_query_UDP_EDNS ||
-                       sq->status == serviced_query_UDP) {
+                       sq->status == serviced_query_UDP ||
+                       sq->status == serviced_query_PROBE_EDNS) {
                        struct pending* p = (struct pending*)sq->pending;
                        if(p->pc)
                                portcomm_loweruse(sq->outnet, p->pc);
@@ -1184,18 +1185,30 @@ static int
 serviced_udp_send(struct serviced_query* sq, ldns_buffer* buff)
 {
        int rtt, vs;
+       uint8_t edns_lame_known;
        uint32_t now = *sq->outnet->now_secs;
 
        if(!infra_host(sq->outnet->infra, &sq->addr, sq->addrlen, now, &vs,
-               &rtt))
+               &edns_lame_known, &rtt))
                return 0;
        if(sq->status == serviced_initial) {
-               if(vs != -1)
+               if(edns_lame_known == 0 && rtt > 5000) {
+                       /* perform EDNS lame probe - check if server is
+                        * EDNS lame (EDNS queries to it are dropped) */
+                       verbose(VERB_ALGO, "serviced query: send probe to see "
+                               " if use of EDNS causes timeouts");
+                       rtt /= 10;
+                       sq->status = serviced_query_PROBE_EDNS;
+               } else if(vs != -1) {
                        sq->status = serviced_query_UDP_EDNS;
-               else    sq->status = serviced_query_UDP;
+               } else {        
+                       sq->status = serviced_query_UDP; 
+               }
        }
        serviced_encode(sq, buff, sq->status == serviced_query_UDP_EDNS);
        sq->last_sent_time = *sq->outnet->now_tv;
+       sq->last_rtt = rtt;
+       sq->edns_lame_known = (int)edns_lame_known;
        verbose(VERB_ALGO, "serviced query UDP timeout=%d msec", rtt);
        sq->pending = pending_udp_query(sq->outnet, buff, &sq->addr, 
                sq->addrlen, rtt, serviced_udp_callback, sq);
@@ -1392,9 +1405,17 @@ serviced_udp_callback(struct comm_point* c, void* arg, int error,
        sq->pending = NULL; /* removed after callback */
        if(error == NETEVENT_TIMEOUT) {
                int rto = 0;
+               if(sq->status == serviced_query_PROBE_EDNS) {
+                       /* non-EDNS probe failed; not an EDNS lame server */
+                       if(!infra_edns_update(outnet->infra, &sq->addr, 
+                               sq->addrlen, 0, (uint32_t)now.tv_sec)) {
+                               log_err("Out of memory caching edns works");
+                       }
+                       sq->status = serviced_query_UDP_EDNS;
+               }
                sq->retry++;
                if(!(rto=infra_rtt_update(outnet->infra, &sq->addr, sq->addrlen,
-                       -1, (uint32_t)now.tv_sec)))
+                       -1, sq->last_rtt, (uint32_t)now.tv_sec)))
                        log_err("out of memory in UDP exponential backoff");
                if(sq->retry < OUTBOUND_UDP_RETRY) {
                        log_name_addr(VERB_ALGO, "retry query", sq->qbuf+10,
@@ -1439,6 +1460,25 @@ serviced_udp_callback(struct comm_point* c, void* arg, int error,
                return 0;
        }
        /* yay! an answer */
+       if(sq->status == serviced_query_PROBE_EDNS) {
+               /* probe without EDNS succeeds, so we conclude that this
+                * host likely has EDNS packets dropped */
+               log_addr(VERB_OPS, "timeouts, concluded that connection to "
+                       "host drops EDNS packets", &sq->addr, sq->addrlen);
+               if(!infra_edns_update(outnet->infra, &sq->addr, sq->addrlen,
+                       -1, (uint32_t)now.tv_sec)) {
+                       log_err("Out of memory caching no edns for host");
+               }
+               sq->status = serviced_query_UDP;
+       } else if(sq->status == serviced_query_UDP_EDNS && 
+               !sq->edns_lame_known) {
+               /* now we know that edns queries received answers store that */
+               if(!infra_edns_update(outnet->infra, &sq->addr, sq->addrlen, 
+                       0, (uint32_t)now.tv_sec)) {
+                       log_err("Out of memory caching edns works");
+               }
+               sq->edns_lame_known = 1;
+       }
        if(now.tv_sec > sq->last_sent_time.tv_sec ||
                (now.tv_sec == sq->last_sent_time.tv_sec &&
                now.tv_usec > sq->last_sent_time.tv_usec)) {
@@ -1448,7 +1488,7 @@ serviced_udp_callback(struct comm_point* c, void* arg, int error,
                verbose(VERB_ALGO, "measured roundtrip at %d msec", roundtime);
                log_assert(roundtime >= 0);
                if(!infra_rtt_update(outnet->infra, &sq->addr, sq->addrlen, 
-                       roundtime, (uint32_t)now.tv_sec))
+                       roundtime, sq->last_rtt, (uint32_t)now.tv_sec))
                        log_err("out of memory noting rtt.");
        }
        serviced_callbacks(sq, error, c, rep);
@@ -1631,7 +1671,8 @@ serviced_get_mem(struct serviced_query* sq)
        for(sb = sq->cblist; sb; sb = sb->next)
                s += sizeof(*sb);
        if(sq->status == serviced_query_UDP_EDNS ||
-               sq->status == serviced_query_UDP) {
+               sq->status == serviced_query_UDP ||
+               sq->status == serviced_query_PROBE_EDNS) {
                s += sizeof(struct pending);
                s += comm_timer_get_mem(NULL);
        } else {
index 874a6f132f102e96fc565f7d600ea263013d1013..ef28ee91aafcb293872f64d20bbfb40467aac0be 100644 (file)
@@ -294,7 +294,9 @@ struct serviced_query {
                /** TCP with EDNS sent */
                serviced_query_TCP_EDNS,
                /** TCP without EDNS sent */
-               serviced_query_TCP
+               serviced_query_TCP,
+               /** probe to test EDNS lameness (EDNS is dropped) */
+               serviced_query_PROBE_EDNS
        }       
                /** variable with current status */ 
                status;
@@ -304,6 +306,10 @@ struct serviced_query {
        int retry;
        /** time last UDP was sent */
        struct timeval last_sent_time;
+       /** rtt of last (UDP) message */
+       int last_rtt;
+       /** do we know edns probe status already, for UDP_EDNS queries */
+       int edns_lame_known;
        /** outside network this is part of */
        struct outside_network* outnet;
        /** list of interested parties that need callback on results. */
index 64873defc04738b2459b65a9a0fdad60e163c7e9..9dcb79b1da38b8c938cea027163e492a451c0539 100644 (file)
@@ -112,6 +112,8 @@ static void matchline(char* line, struct entry* e)
                        e->match_ttl = true;
                } else if(str_keyword(&parse, "DO")) {
                        e->match_do = true;
+               } else if(str_keyword(&parse, "noedns")) {
+                       e->match_noedns = true;
                } else if(str_keyword(&parse, "UDP")) {
                        e->match_transport = transport_udp;
                } else if(str_keyword(&parse, "TCP")) {
@@ -233,6 +235,7 @@ static struct entry* new_entry()
        e->match_all = false;
        e->match_ttl = false;
        e->match_do = false;
+       e->match_noedns = false;
        e->match_serial = false;
        e->ixfr_soa_serial = 0;
        e->match_transport = transport_any;
@@ -697,6 +700,10 @@ find_match(struct entry* entries, ldns_pkt* query_pkt,
                        verbose(3, "no DO bit set\n");
                        continue;
                }
+               if(p->match_noedns && ldns_pkt_edns(query_pkt)) {
+                       verbose(3, "bad; EDNS OPT present\n");
+                       continue;
+               }
                if(p->match_transport != transport_any && p->match_transport != transport) {
                        verbose(3, "bad transport\n");
                        continue;
index b9735a69ab2759cff7d15fb85fa1faaca838db2c..59e428952759328882c1c438e05f6a8acb62f96b 100644 (file)
@@ -47,6 +47,7 @@
        ; 'all' has to match header byte for byte and all rrs in packet.
        ; 'ttl' used with all, rrs in packet must also have matching TTLs.
        ; 'DO' will match only queries with DO bit set.
+       ; 'noedns' matches queries without EDNS OPT records.
        MATCH [opcode] [qtype] [qname] [serial=<value>] [all] [ttl]
        MATCH [UDP|TCP] DO
        MATCH ...
@@ -168,6 +169,8 @@ struct entry {
        bool match_ttl;
        /** match DO bit */
        bool match_do;
+       /** match absence of EDNS OPT record in query */
+       bool match_noedns;
        /** match query serial with this value. */
        uint32_t ixfr_soa_serial; 
        /** match on UDP/TCP */
index 769db39abb1abd840811c3dfa77ea496063b65ba..b104fbe9850a138987af3b4c77733bf538506225 100644 (file)
@@ -289,15 +289,15 @@ rtt_test()
        rtt_init(&r);
        /* initial value sensible */
        unit_assert( rtt_timeout(&r) == init );
-       rtt_lost(&r);
+       rtt_lost(&r, init);
        unit_assert( rtt_timeout(&r) == init*2 );
-       rtt_lost(&r);
+       rtt_lost(&r, init*2);
        unit_assert( rtt_timeout(&r) == init*4 );
        rtt_update(&r, 4000);
        unit_assert( rtt_timeout(&r) >= 2000 );
-       rtt_lost(&r);
+       rtt_lost(&r, rtt_timeout(&r) );
        for(i=0; i<100; i++) {
-               rtt_lost(&r); 
+               rtt_lost(&r, rtt_timeout(&r) ); 
                unit_assert( rtt_timeout(&r) > RTT_MIN_TIMEOUT-1);
                unit_assert( rtt_timeout(&r) < RTT_MAX_TIMEOUT+1);
        }
@@ -315,6 +315,7 @@ infra_test()
        struct infra_cache* slab;
        struct config_file* cfg = config_create();
        uint32_t now = 0;
+       uint8_t edns_lame;
        int vs, to;
        struct infra_host_key* k;
        struct infra_host_data* d;
@@ -323,25 +324,25 @@ infra_test()
 
        slab = infra_create(cfg);
        unit_assert( infra_host(slab, (struct sockaddr_storage*)&one, 
-               (socklen_t)sizeof(int), now, &vs, &to) );
-       unit_assert( vs == 0 && to == init );
+               (socklen_t)sizeof(int), now, &vs, &edns_lame, &to) );
+       unit_assert( vs == 0 && to == init && edns_lame == 0 );
 
        unit_assert( infra_rtt_update(slab, (struct sockaddr_storage*)&one,
-               (socklen_t)sizeof(int), -1, now) );
+               (socklen_t)sizeof(int), -1, init, now) );
        unit_assert( infra_host(slab, (struct sockaddr_storage*)&one, 
-               (socklen_t)sizeof(int), now, &vs, &to) );
-       unit_assert( vs == 0 && to == init*2 );
+               (socklen_t)sizeof(int), now, &vs, &edns_lame, &to) );
+       unit_assert( vs == 0 && to == init*2 && edns_lame == 0 );
 
        unit_assert( infra_edns_update(slab, (struct sockaddr_storage*)&one,
                (socklen_t)sizeof(int), -1, now) );
        unit_assert( infra_host(slab, (struct sockaddr_storage*)&one, 
-               (socklen_t)sizeof(int), now, &vs, &to) );
-       unit_assert( vs == -1 && to == init*2 );
+               (socklen_t)sizeof(int), now, &vs, &edns_lame, &to) );
+       unit_assert( vs == -1 && to == init*2  && edns_lame == 1);
 
        now += cfg->host_ttl + 10;
        unit_assert( infra_host(slab, (struct sockaddr_storage*)&one, 
-               (socklen_t)sizeof(int), now, &vs, &to) );
-       unit_assert( vs == 0 && to == init );
+               (socklen_t)sizeof(int), now, &vs, &edns_lame, &to) );
+       unit_assert( vs == 0 && to == init && edns_lame == 0 );
        
        unit_assert( infra_set_lame(slab, (struct sockaddr_storage*)&one, 
                (socklen_t)sizeof(int), zone, zonelen,  now, 0, 
diff --git a/testdata/edns_lame.tpkg b/testdata/edns_lame.tpkg
new file mode 100644 (file)
index 0000000..b52e0af
Binary files /dev/null and b/testdata/edns_lame.tpkg differ
index eefdf7062235e0f4f8f23613f247474ad06e99da..efd7925a5775a39f3f4a5326a6b431b4d01fd7e2 100644 (file)
@@ -95,10 +95,17 @@ rtt_update(struct rtt_info* rtt, int ms)
 }
 
 void 
-rtt_lost(struct rtt_info* rtt)
+rtt_lost(struct rtt_info* rtt, int orig)
 {
        /* exponential backoff */
-       rtt->rto *= 2;
-       if(rtt->rto > RTT_MAX_TIMEOUT)
-               rtt->rto = RTT_MAX_TIMEOUT;
+
+       /* the original rto is doubled, not the current one to make sure
+        * that the values in the cache are not increased by lots of
+        * queries simultaneously as they time out at the same time */
+       orig *= 2;
+       if(rtt->rto <= orig) {
+               rtt->rto = orig;
+               if(rtt->rto > RTT_MAX_TIMEOUT)
+                       rtt->rto = RTT_MAX_TIMEOUT;
+       }
 }
index 260945f4f44943a0b17dbe7a8546e218ef7c8d81..9dc6c04e4e014d07ef92c9df895b4128a2969b31 100644 (file)
@@ -91,7 +91,10 @@ void rtt_update(struct rtt_info* rtt, int ms);
 /**
  * Update the statistics with a new timout expired observation.
  * @param rtt: round trip statistics structure.
+ * @param orig: original rtt time given for the query that timed out.
+ *     Used to calculate the maximum responsible backed off time that
+ *     can reasonably be applied.
  */
-void rtt_lost(struct rtt_info* rtt);
+void rtt_lost(struct rtt_info* rtt, int orig);
 
 #endif /* UTIL_RTT_H */