]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
add the 3 teststeps for upstream cookies
authorTCY16 <tom@nlnetlabs.nl>
Wed, 15 Jun 2022 12:00:46 +0000 (14:00 +0200)
committerTCY16 <tom@nlnetlabs.nl>
Wed, 15 Jun 2022 12:01:58 +0000 (14:01 +0200)
iterator/iterator.c
services/cache/infra.c
services/cache/infra.h
testcode/testpkts.c
testcode/testpkts.h
testdata/edns_upstream_cookies.rpl [new file with mode: 0644]

index 8e82caccd14a1b190549f100bf83bce0c1c3c2ff..e597aefd30fabcce6deac1ee98141855afc95016 100644 (file)
@@ -3826,19 +3826,20 @@ process_response(struct module_qstate* qstate, struct iter_qstate* iq,
 
        /* handle the upstream response cookie */
        if((cookie = edns_list_get_option(edns.opt_list_in, LDNS_EDNS_COOKIE))) {
-               /* verify this is a 'complete cookie' (RFC9018) with the length and
-                * store the complete cookie in the infra_cache */
+               /* verify this is a 'complete cookie' (client+server) (RFC9018) with
+                * the length and store the complete cookie in the infra_cache. Do
+                * nothing when the cookie is already known and update when the server
+                * cookie changed*/
                if (cookie->opt_len == 24 &&
                        infra_set_server_cookie(qstate->env->infra_cache,
                                &qstate->reply->addr, qstate->reply->addrlen,
-                               iq->dp->name, iq->dp->namelen, cookie)) {
+                               iq->dp->name, iq->dp->namelen, cookie) >= 0) {
                        /* log_hex() uses the verbosity levels of verbose() */
                        log_hex("complete cookie: ", cookie->opt_data,
                                cookie->opt_len);
                } else {
-                       // @TODO just log error? set state to COOKIE_NOT_SUPPORTED?
                        log_info("upstream response server cookie is not added to cache;"
-                               "dropping response");
+                               " dropping response");
                        goto handle_it;
                }
        } //@TODO think about what we do if we did send a cookie but did not get one back?
index 51046042d02c4bdba4801bab1c31b2d011942634..02ac7228fcaca72f4949cf2c16a90ef819a285ed 100644 (file)
@@ -718,14 +718,18 @@ infra_get_cookie(struct infra_cache* infra, struct sockaddr_storage* addr,
 
        if(!e) {
                if(!(e = new_entry(infra, addr, addrlen, name, namelen, timenow))) {
-                       return 0;
+                       return NULL;
                }
                needtoinsert = 1;
        } else if(((struct infra_data*)e->data)->ttl < timenow) {
+               /* EDNS cookies have their own timeout logic controlled by the
+                * upstream, so we just copy the cookie from the old cache entry */
+               struct edns_cookie c = ((struct infra_data*)e->data)->cookie;
                /* @TODO create logic for saving server cookie? -> not sure, find out */
 
-               /* create new cookie if the TTL of the current one expired */
+               /* create new cookie if the cache TTL expired */
                data_entry_init(infra, e, timenow);
+               ((struct infra_data*)e->data)->cookie = c;
        }
 
        data = (struct infra_data*) e->data;
@@ -756,21 +760,64 @@ infra_set_server_cookie(struct infra_cache* infra, struct sockaddr_storage* addr
 
        data = (struct infra_data*) e->data;
 
-       /* check if we already know the complete cookie and that the client cookie
-        * section matches the client cookie we sent */
-       if (data->cookie.state == SERVER_COOKIE_UNKNOWN &&
-                       memcmp(data->cookie.data.components.client,
-                               cookie->opt_data+4, 8)) {
+       if (data->cookie.state == COOKIE_NOT_SUPPORTED) {
+               /* we known this upstream doesn't support cookies; the state
+                * remains unchanged */
+               lock_rw_unlock(&e->lock);
+               return 0;
+       } else if (data->cookie.state == SERVER_COOKIE_LEARNED) {
+               /* wrong client cookie; don't store the server cookie */
+               if (!(memcmp(data->cookie.data.components.client,
+                       cookie->opt_data+4, 8))) {
+                       /* the state of the cookie remains unchanged as we will
+                        * drop this upstream response */
+
+                       verbose(VERB_ALGO, "wrong client cookie from upstream with "
+                               "previously seen cookie");
+                       lock_rw_unlock(&e->lock);
+                       return -1;
+               }
+
+               /* the server cookie has changed, but the client cookie has not
+                * so we update the server cookie */
+               if (memcmp(data->cookie.data.complete+8,
+                               cookie->opt_data+12, 16) != 0) {
+                       memcpy(data->cookie.data.complete, cookie->opt_data, 24);
+                       /* the cookie state remains unchanged*/
+
+                       verbose(VERB_ALGO, "update new server cookie from upstream");
+                       lock_rw_unlock(&e->lock);
+                       return 1;
+               }
+
+               /* both the complete cookies are identical, so the state
+                * remains unchanged */
+               verbose(VERB_ALGO, "correctly received indentical cookie from upstream; don't update");
+               lock_rw_unlock(&e->lock);
+               return 0;
+       } else { /* cookie state == SERVER_COOKIE_UNKNOWN */
+
+               /* wrong client cookie; don't store the server cookie */
+               if (!(memcmp(data->cookie.data.components.client,
+                       cookie->opt_data+4, 8))) {
+                       /* the state of the cookie remains unchanged as we will
+                        * drop this upstream response */
+
+                       verbose(VERB_ALGO, "wrong client cookie from upstream");
+                       lock_rw_unlock(&e->lock);
+                       return -1;
+               }
+
+               /* else; store the client cookie */
                memcpy(data->cookie.data.complete, cookie->opt_data, 24);
                data->cookie.state = SERVER_COOKIE_LEARNED;
-               return 1;
-       } else {
+
+               verbose(VERB_QUERY, "storing received server cookie from upstream");
                lock_rw_unlock(&e->lock);
-               return 0;
+               return 1;
        }
-}
-
 
+}
 
 int
 infra_get_lame_rtt(struct infra_cache* infra,
index af0a4036934019ebbe4126bee8f13c0e38aba720..93a6c9c9a02d3ba90181b1e6abd7b86634757de1 100644 (file)
@@ -83,7 +83,6 @@ enum edns_cookie_state
  */
 struct edns_cookie {
         enum edns_cookie_state state;
-        uint8_t timeout; // @TODO change this to correct value
         union edns_cookie_data data;
 };
 
index 5a6b2f87291c96c5c8acf11f68f3e9a53adf0400..5b233e83f1fc8b44bccae351ea231b8915a1b121 100644 (file)
@@ -25,6 +25,7 @@ struct sockaddr_storage;
 #include <errno.h>
 #include <stdarg.h>
 #include <ctype.h>
+#include <time.h>
 #include "testcode/testpkts.h"
 #include "util/net_help.h"
 #include "sldns/sbuffer.h"
@@ -144,6 +145,8 @@ static void matchline(char* line, struct entry* e)
                        e->match_ednsdata_raw = 1;
                } else if(str_keyword(&parse, "random_client_cookie")) {
                        e->match_random_client_cookie = 1;
+               } else if(str_keyword(&parse, "random_complete_cookie")) {
+                       e->match_random_complete_cookie = 1;
                } else if(str_keyword(&parse, "UDP")) {
                        e->match_transport = transport_udp;
                } else if(str_keyword(&parse, "TCP")) {
@@ -267,7 +270,10 @@ static void adjustline(char* line, struct entry* e,
                        pkt->packet_sleep = (unsigned int) strtol(parse, (char**)&parse, 10);
                        while(isspace((unsigned char)*parse)) 
                                parse++;
-               } else if (str_keyword(&parse, "add_server_cookie")) {
+               } else if (str_keyword(&parse, "server_cookie_renew")) {
+                       e->server_cookie = 1;
+                       e->server_cookie_renew = 1;
+               } else if (str_keyword(&parse, "server_cookie")) {
                        e->server_cookie = 1;
                } else {
                        error("could not parse ADJUST: '%s'", parse);
@@ -305,6 +311,8 @@ static struct entry* new_entry(void)
        e->copy_ednsdata_assume_clientsubnet = 0;
        e->increment_ecs_scope = 0;
        e->sleeptime = 0;
+       e->server_cookie = 0;
+       e->server_cookie_renew = 0;
        e->next = NULL;
        return e;
 }
@@ -1514,7 +1522,7 @@ match_random_client_cookie(uint8_t* query, size_t query_len)
 
        if(!pkt_find_edns_opt(&walk_query, &walk_query_len)) {
                walk_query_len = 0;
-               log_err("!!!!!! no edns");
+               log_err("no edns found");
        }
 
        /* class + ttl + rdlen = 8 */
@@ -1535,6 +1543,37 @@ match_random_client_cookie(uint8_t* query, size_t query_len)
        return 1;
 }
 
+/** verify that a complete EDNS cookie (client+server) (RFC9018) of length 24
+  * is in the EDNS data of the query */
+static int
+match_random_complete_cookie(uint8_t* query, size_t query_len)
+{
+       uint8_t* walk_query = query;
+       size_t walk_query_len = query_len;
+
+       if(!pkt_find_edns_opt(&walk_query, &walk_query_len)) {
+               walk_query_len = 0;
+               log_err("no edns found");
+       }
+
+       /* class + ttl + rdlen = 8 */
+       if(walk_query_len <= 8) {
+               verbose(3, "No edns opt, no cookie");
+               return 0;
+       }
+
+       if (sldns_read_uint16(walk_query+8) != 10 /* LDNS_EDNS_COOKIE */) {
+               verbose(3, "EDNS option is not a cookie");
+               return 0;
+       }
+       if (sldns_read_uint16(walk_query+10) != 24) {
+               verbose(3, "EDNS cookie is not 24 bytes, so not a correct complete cookie");
+               return 0;
+       }
+
+       return 1;
+}
+
 /* finds entry in list, or returns NULL */
 struct entry* 
 find_match(struct entry* entries, uint8_t* query_pkt, size_t len,
@@ -1635,6 +1674,11 @@ find_match(struct entry* entries, uint8_t* query_pkt, size_t len,
                        verbose(3, "bad client cookie match.\n");
                        continue;
                }
+               if (p->match_random_complete_cookie &&
+                               !match_random_complete_cookie(query_pkt, len)) {
+                       verbose(3, "bad complete cookie match.\n");
+                       continue;
+               }
                if(p->match_transport != transport_any && p->match_transport != transport) {
                        verbose(3, "bad transport\n");
                        continue;
@@ -1733,33 +1777,39 @@ adjust_packet(struct entry* match, uint8_t** answer_pkt, size_t *answer_len,
 
        if(match->server_cookie) {
                /** Find the cookie option and add the server cookie if 
-                * the client cookie is present
-                * */
+                * the client cookie is present and not already there */
                uint8_t* walk_query = query_pkt;
                size_t walk_query_len = query_len;
                uint8_t* walk_response = res;
                size_t walk_response_len = reslen;
 
-               uint8_t* rd_len_ptr;
+               uint8_t* rdlen_ptr_query;
+               uint8_t* rdlen_ptr_response;
 
                /* verify that we have a EDNS record */
                if(!pkt_find_edns_opt(&walk_query, &walk_query_len)) {
                        walk_query_len = 0;
+                       log_err("testbound: no EDNS in the query packet when trying to attach a EDNS cookie");
                }
                if(!pkt_find_edns_opt(&walk_response, &walk_response_len)) {
                        walk_response_len = 0;
+                       log_err("testbound: no EDNS in the response packet when trying to attach a EDNS cookie");
                }
 
                /* verify that we have a EDNS option */
                if (walk_query_len < 12) /* class + ttl + rdlen + opt_code + opt_len */ {
                        /* invalid or no (OPT) record in the query */
                        walk_query_len = 0;
-               }                       
+                       log_err("testbound: invalid or no OPT record in the query packet");
+               }
                if (walk_response_len < 8) /* class + ttl + rdlen */ {
                        walk_response_len = 0;
+                       log_err("testbound: invalid OPT record in the response packet");
                }
 
-               rd_len_ptr = walk_response + 6; /* store the location of the rdlen */
+               /* store the location of the rdlen */
+               rdlen_ptr_query = walk_query + 6;
+               rdlen_ptr_response = walk_response + 6;
 
                /* skip past the OPT record to get to the option */
                walk_query += 8;
@@ -1767,32 +1817,60 @@ adjust_packet(struct entry* match, uint8_t** answer_pkt, size_t *answer_len,
                walk_response += 8;
                walk_response_len -= 8;
 
-               /* assume one record in the query */
-               if (sldns_read_uint16(walk_query) != 10 /* LDNS_EDNS_COOKIE */ ||
-                       sldns_read_uint16(walk_query+2) != 8) {/* client cookie length */
-                       /* incorrect client cookie */
+               /* verify that the client cookie exists */
+               if (walk_query_len < 12 /* opt_code + opt_len + client cookie */) {
                        walk_query_len = 0;
+                       log_err("testbound: no EDNS cookie in the query packet");
                }
 
-               /* verify that the client cookie exists */
-               if (walk_query_len < 12 /* opt_code + opt_len + client cookie */) {
+               /* assume one record in the query */
+               if (sldns_read_uint16(walk_query) != 10 /* LDNS_EDNS_COOKIE */ ||
+                       !(sldns_read_uint16(walk_query+2) == 8 || /* client cookie length */
+                       sldns_read_uint16(walk_query+2) == 24)) { /* client+server cookie */
+                       /* incorrect cookie */
                        walk_query_len = 0;
+                       log_err("testbound: invalid EDNS cookie in the query packet");
                }
 
                if (walk_query_len > 0 && walk_response_len == 0) {
-                       /* copy the EDNS client cookie from the query packet to the response */
-                       memcpy(walk_response, walk_query, 12);
-
-                       /* add the server cookie to the client cookie to make it
-                        * 'complete'. we fake the siphash specified in RFC9018
-                        * by hardcoding the server cookie */
-                       memcpy(walk_response+12, hardcoded_server_cookie, 16);
-
-                       /* update the RDLEN and OPTLEN */
-                       sldns_write_uint16(rd_len_ptr, 28);
-                       sldns_write_uint16(walk_response+2, 24);
+                       /* depending on the incoming cookie, add the server cookie
+                        * or copy the complete cookie to the response */
+                       if (sldns_read_uint16(walk_query+2) == 8) {
+                               /* copy the EDNS client cookie from the query packet to the response */
+                               memcpy(walk_response, walk_query, 12);
+
+                               /* add the server cookie to the client cookie to make it
+                                * 'complete'. we fake the siphash specified in RFC9018
+                                * by hardcoding the server cookie */
+                               memcpy(walk_response+12, hardcoded_server_cookie, 16);
+
+                               /* update the RDLEN and OPTLEN */
+                               sldns_write_uint16(rdlen_ptr_response, 28);
+                               sldns_write_uint16(walk_response+2, 24);
+
+                               reslen = origlen + 28;
+                       } else if (sldns_read_uint16(walk_query+2) == 24) {
+                               /* we fake verification of the cookie and send
+                                * it back like it's still valid. We renew the cookie
+                                * if this desired*/
+                               if (match->server_cookie_renew) {
+                                       /* copy the cookie from the response but add a
+                                        * different cookie (by reshuffeling server cookie) */
+                                       memcpy(rdlen_ptr_response, rdlen_ptr_query, 12);
+                                       memcpy(walk_response+12, rdlen_ptr_query+12+8, 8);
+                                       memcpy(walk_response+12+8, rdlen_ptr_query+12, 8);
+
+                                       reslen = origlen + 28;
+                               } else {
+                                       memcpy(rdlen_ptr_response, rdlen_ptr_query, 28);
 
-                       reslen = origlen + 28;
+                                       reslen = origlen + 28;
+                               }
+                       } else {
+                               log_err("testbound: the incoming EDNS cookie has the wrong length");
+                       }
+               } else {
+                       log_err("testbound: an error has occured while parsing the EDNS cookie");
                }
        }
 
index f009980f7f2cb17467bf9cbe2c11637acb82b616..df87fc645a50ec0a123b796ccb697ca8af37788e 100644 (file)
@@ -214,8 +214,11 @@ struct entry {
        uint8_t match_noedns;
        /** match edns data field given in hex */
        uint8_t match_ednsdata_raw;
-       /** match an EDNS cookie (RFC7873) of length 8 */
+       /** match an EDNS cookie (RFC7873) of length 8*/
        uint8_t match_random_client_cookie;
+       /** match an EDNS cookie (RFC7873) of length 24, we call
+        * this "complete" (RFC9018) */
+       uint8_t match_random_complete_cookie;
        /** match query serial with this value. */
        uint32_t ixfr_soa_serial;
        /** match on UDP/TCP */
@@ -242,6 +245,10 @@ struct entry {
        /** add a server cookie (RFC9018) to the response (provided the query
         * contains a client cookie) */
        uint8_t server_cookie;
+       /** renew the server cookie (RFC9018) to the response (provided the query
+        * contains a client cookie) by shuffling the bytes in the server cookie
+        * Note that this also sets "server_cookie" to 1*/
+       uint8_t server_cookie_renew;
 
        /** some number that names this entry, line number in file or so */
        int lineno;
diff --git a/testdata/edns_upstream_cookies.rpl b/testdata/edns_upstream_cookies.rpl
new file mode 100644 (file)
index 0000000..a270eb8
--- /dev/null
@@ -0,0 +1,180 @@
+; config options
+server:
+       ede: yes
+
+stub-zone:
+       name: example.com
+       stub-addr: 1.1.1.1
+
+CONFIG_END
+
+SCENARIO_BEGIN Test edns-upstream-cookies
+; Scenario overview:
+; - Send a client cookie to the upstream and receive one back and store it
+; - Send the client+server (complete) cookie and receive it back.
+; - pass time and send the old complete cookie and receive a new one back
+
+
+; Client query for upstream to Unbound
+STEP 1 QUERY
+ENTRY_BEGIN
+       REPLY RD
+       SECTION QUESTION
+               a.example.com. IN A
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+; Check that we send a server cookie to the upstream
+STEP 2 CHECK_OUT_QUERY
+ENTRY_BEGIN
+       MATCH qname qtype random_client_cookie
+       SECTION QUESTION
+               a.example.com. IN A
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+; Reply with a server cookie
+STEP 3 REPLY
+ENTRY_BEGIN
+       REPLY QR NOERROR
+       ADJUST copy_id server_cookie
+       SECTION QUESTION
+               a.example.com. IN A
+       SECTION ANSWER
+               a.example.com. IN A 1.2.3.4
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+
+; Check the answer from Unbound for the client
+STEP 4 CHECK_ANSWER
+ENTRY_BEGIN
+       MATCH all
+       REPLY QR RA RD NOERROR
+       SECTION QUESTION
+               a.example.com. IN A
+       SECTION ANSWER
+               a.example.com. IN A 1.2.3.4
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+
+; Query a second time to verify that we have the server cookie stored
+
+; Client query for upstream to Unbound
+STEP 11 QUERY
+ENTRY_BEGIN
+       REPLY RD
+       SECTION QUESTION
+               b.example.com. IN A
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+; Check that we send a server cookie to the upstream
+STEP 12 CHECK_OUT_QUERY
+ENTRY_BEGIN
+       MATCH qname qtype random_complete_cookie
+       SECTION QUESTION
+               b.example.com. IN A
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+; Reply with a server cookie
+STEP 13 REPLY
+ENTRY_BEGIN
+       REPLY QR NOERROR
+       ADJUST copy_id server_cookie
+       SECTION QUESTION
+               b.example.com. IN A
+       SECTION ANSWER
+               b.example.com. IN A 1.2.3.4
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+
+; Check the answer from Unbound for the client
+STEP 14 CHECK_ANSWER
+ENTRY_BEGIN
+       MATCH all
+       REPLY QR RA RD NOERROR
+       SECTION QUESTION
+               b.example.com. IN A
+       SECTION ANSWER
+               b.example.com. IN A 1.2.3.4
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+
+; Query a third time while waiting more than hour + 5 minutes, so we should
+; have a new cookie
+
+STEP 20 TIME_PASSES ELAPSE 4000
+
+; Client query for upstream to Unbound
+STEP 21 QUERY
+ENTRY_BEGIN
+       REPLY RD
+       SECTION QUESTION
+               c.example.com. IN A
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+; Check that we send a server cookie to the upstream
+STEP 22 CHECK_OUT_QUERY
+ENTRY_BEGIN
+       MATCH qname qtype random_complete_cookie
+       SECTION QUESTION
+               c.example.com. IN A
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+; Reply with an updated server cookie that
+STEP 23 REPLY
+ENTRY_BEGIN
+       REPLY QR NOERROR
+       ADJUST copy_id server_cookie_renew
+       SECTION QUESTION
+               c.example.com. IN A
+       SECTION ANSWER
+               c.example.com. IN A 1.2.3.4
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+
+; Check the answer from Unbound for the client
+STEP 24 CHECK_ANSWER
+ENTRY_BEGIN
+       MATCH all
+       REPLY QR RA RD NOERROR
+       SECTION QUESTION
+               c.example.com. IN A
+       SECTION ANSWER
+               c.example.com. IN A 1.2.3.4
+       SECTION ADDITIONAL
+               HEX_EDNSDATA_BEGIN
+           HEX_EDNSDATA_END
+ENTRY_END
+
+SCENARIO_END