]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- For #762: Introduce rpl testing for DNS Cookies.
authorGeorge Thessalonikefs <george@nlnetlabs.nl>
Sat, 5 Aug 2023 17:50:57 +0000 (19:50 +0200)
committerGeorge Thessalonikefs <george@nlnetlabs.nl>
Sat, 5 Aug 2023 17:50:57 +0000 (19:50 +0200)
testcode/testpkts.c
testcode/testpkts.h
testcode/unitmain.c
testdata/edns_downstream_cookies.rpl [new file with mode: 0644]

index 3702c3f184035942f2db747e0538a18856131b20..b9b740a488cc1ab71bd652cd984bc65f3501aba7 100644 (file)
@@ -21,7 +21,6 @@
  */
 
 #include "config.h"
-struct sockaddr_storage;
 #include <errno.h>
 #include <stdarg.h>
 #include <ctype.h>
@@ -140,6 +139,10 @@ static void matchline(char* line, struct entry* e)
                        e->match_noedns = 1;
                } else if(str_keyword(&parse, "ednsdata")) {
                        e->match_ednsdata_raw = 1;
+               } else if(str_keyword(&parse, "client_cookie")) {
+                       e->match_client_cookie = 1;
+               } else if(str_keyword(&parse, "server_cookie")) {
+                       e->match_server_cookie = 1;
                } else if(str_keyword(&parse, "UDP")) {
                        e->match_transport = transport_udp;
                } else if(str_keyword(&parse, "TCP")) {
@@ -905,37 +908,64 @@ get_do_flag(uint8_t* pkt, size_t len)
        return (int)(edns_bits&LDNS_EDNS_MASK_DO_BIT);
 }
 
-/** Snips the EDE option out of the OPT record and returns the EDNS EDE
- *  INFO-CODE if found, else -1 */
+/** Snips the specified EDNS option out of the OPT record and puts it in the
+ *  provided buffer. The buffer should be able to hold any opt data ie 65535.
+ *  Returns the length of the option written,
+ *  or 0 if not found, else -1 on error. */
 static int
-extract_ede(uint8_t* pkt, size_t len)
+pkt_snip_edns_option(uint8_t* pkt, size_t len, sldns_edns_option code,
+       uint8_t* buf)
 {
        uint8_t *rdata, *opt_position = pkt;
        uint16_t rdlen, optlen;
        size_t remaining = len;
-       int ede_code;
-       if(!pkt_find_edns_opt(&opt_position, &remaining)) return -1;
+       if(!pkt_find_edns_opt(&opt_position, &remaining)) return 0;
        if(remaining < 8) return -1; /* malformed */
        rdlen = sldns_read_uint16(opt_position+6);
        rdata = opt_position + 8;
        while(rdlen > 0) {
                if(rdlen < 4) return -1; /* malformed */
                optlen = sldns_read_uint16(rdata+2);
-               if(sldns_read_uint16(rdata) == LDNS_EDNS_EDE) {
-                       if(rdlen < 6) return -1; /* malformed */
-                       ede_code = sldns_read_uint16(rdata+4);
+               if(sldns_read_uint16(rdata) == code) {
+                       /* save data to buf for caller inspection */
+                       memcpy(buf, rdata+4, optlen);
                        /* snip option from packet; assumes len is correct */
                        memmove(rdata, rdata+4+optlen,
                                (pkt+len)-(rdata+4+optlen));
                        /* update OPT size */
                        sldns_write_uint16(opt_position+6,
                                sldns_read_uint16(opt_position+6)-(4+optlen));
-                       return ede_code;
+                       return optlen;
                }
                rdlen -= 4 + optlen;
                rdata += 4 + optlen;
        }
-       return -1;
+       return 0;
+}
+
+/** Snips the EDE option out of the OPT record and returns the EDNS EDE
+ *  INFO-CODE if found, else -1 */
+static int
+extract_ede(uint8_t* pkt, size_t len)
+{
+       uint8_t buf[65535];
+       int buflen = pkt_snip_edns_option(pkt, len, LDNS_EDNS_EDE, buf);
+       if(buflen < 2 /*ede without text at minimum*/) return -1;
+       return sldns_read_uint16(buf);
+}
+
+/** Snips the EDNS Cookie option out of the OPT record and puts it in the
+ *  provided cookie buffer (should be at least 24 octets).
+ *  Returns the length of the cookie if found, else -1. */
+static int
+extract_cookie(uint8_t* pkt, size_t len, uint8_t* cookie)
+{
+       uint8_t buf[65535];
+       int buflen = pkt_snip_edns_option(pkt, len, LDNS_EDNS_COOKIE, buf);
+       if(buflen != 8 /*client cookie*/ &&
+               buflen != 8 + 16 /*server cookie*/) return -1;
+       memcpy(cookie, buf, buflen);
+       return buflen;
 }
 
 /** zero TTLs in packet */
@@ -1530,6 +1560,27 @@ find_match(struct entry* entries, uint8_t* query_pkt, size_t len,
                                continue;
                        }
                }
+               /* Cookies could also modify the query_pkt; keep them early */
+               if(p->match_client_cookie || p->match_server_cookie) {
+                       uint8_t cookie[24];
+                       int cookie_len = extract_cookie(query_pkt, len,
+                               cookie);
+                       if(cookie_len == -1) {
+                               verbose(3, "bad EDNS Cookie. "
+                                       "Expected but not found\n");
+                               continue;
+                       } else if(p->match_client_cookie &&
+                               cookie_len != 8) {
+                               verbose(3, "bad EDNS Cookie. Expected client "
+                                       "cookie of length 8.");
+                               continue;
+                       } else if((p->match_server_cookie) &&
+                               cookie_len != 24) {
+                               verbose(3, "bad EDNS Cookie. Expected server "
+                                       "cookie of length 24.");
+                               continue;
+                       }
+               }
                if(p->match_opcode && get_opcode(query_pkt, len) !=
                        get_opcode(reply, rlen)) {
                        verbose(3, "bad opcode\n");
index 2768040c68cb94b8ab47399273d122d810df9d19..3c0a44054be313046b2c5aa57b6fe9b3c5c98965 100644 (file)
@@ -64,6 +64,14 @@ struct sldns_file_parse_state;
        ; 'ede=any' makes the query match any EDNS EDE info-code.
        ;       It also snips the EDE record out of the packet to facilitate
        ;       other matches.
+       ; 'client_cookie' makes the query match any EDNS Cookie option with
+       ;       with a length of 8 octets.
+       ;       It also snips the EDNS Cookie record out of the packet to
+       ;       facilitate other matches.
+       ; 'server_cookie' makes the query match any EDNS Cookie option with
+       ;       with a length of 24 octets.
+       ;       It also snips the EDNS Cookie record out of the packet to
+       ;       facilitate other matches.
        MATCH [opcode] [qtype] [qname] [serial=<value>] [all] [ttl]
        MATCH [UDP|TCP] DO
        MATCH ...
@@ -104,11 +112,11 @@ struct sldns_file_parse_state;
                                ; be parsed, ADJUST rules for the answer packet
                                ; are ignored. Only copy_id is done.
        HEX_ANSWER_END
-       HEX_EDNS_BEGIN          ; follow with hex data.
+       HEX_EDNSDATA_BEGIN      ; follow with hex data.
                                ; Raw EDNS data to match against. It must be an 
                                ; exact match (all options are matched) and will be 
                                ; evaluated only when 'MATCH ednsdata' given.
-       HEX_EDNS_END
+       HEX_EDNSDATA_END
        ENTRY_END
 
 
@@ -214,6 +222,10 @@ struct entry {
        uint8_t match_noedns;
        /** match edns data field given in hex */
        uint8_t match_ednsdata_raw;
+       /** match an EDNS cookie of length 8 */
+       uint8_t match_client_cookie;
+       /** match an EDNS cookie of length 24 */
+       uint8_t match_server_cookie;
        /** match query serial with this value. */
        uint32_t ixfr_soa_serial;
        /** match on UDP/TCP */
@@ -235,7 +247,7 @@ struct entry {
        /** increment the ECS scope copied from the sourcemask by one */
        uint8_t increment_ecs_scope;
        /** in seconds */
-       unsigned int sleeptime; 
+       unsigned int sleeptime;
 
        /** some number that names this entry, line number in file or so */
        int lineno;
index 725393217c85a35e846a87204cc9b698b36f951a..4111b3e98d7593969df0c75bf6a0bb3437e60233 100644 (file)
@@ -559,7 +559,6 @@ edns_cookie_invalid_version(void)
                sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
                buf, timestamp) == 0);
        edns_cookie_server_write(buf, server_secret, 1, timestamp);
-       log_hex("server:", buf, 32);
        unit_assert(memcmp(server_cookie, buf, 24) == 0);
 }
 
diff --git a/testdata/edns_downstream_cookies.rpl b/testdata/edns_downstream_cookies.rpl
new file mode 100644 (file)
index 0000000..e745f58
--- /dev/null
@@ -0,0 +1,235 @@
+; config options
+server:
+       answer-cookie: yes
+       cookie-secret: "000102030405060708090a0b0c0d0e0f"
+       access-control: 127.0.0.1 allow_cookie
+       access-control: 1.2.3.4 allow
+       local-data: "test. TXT test"
+
+CONFIG_END
+
+SCENARIO_BEGIN Test downstream EDNS Cookies
+
+; Note: When a valid hash was required, it was generated by running this test
+; with an invalid one and checking the output for the valid one.
+; Actual hash generation is tested with unit tests.
+
+; Query without a client cookie ...
+STEP 0 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+; ... get TC and refused
+STEP 1 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA TC REFUSED
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+; Query without a client cookie on TCP ...
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+MATCH TCP
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+; ... get an answer
+STEP 11 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. IN TXT "test"
+ENTRY_END
+
+; Query with only a client cookie ...
+STEP 20 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test. IN TXT
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+       00 0a                   ; Opcode 10
+       00 08                   ; Length 8
+       31 32 33 34 35 36 37 38 ; Random bits
+HEX_EDNSDATA_END
+ENTRY_END
+; ... get BADCOOKIE and a new cookie
+STEP 21 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all server_cookie
+REPLY QR RD RA DO YXRRSET      ; BADCOOKIE is an extended rcode
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+; Query with an invalid cookie ...
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test. IN TXT
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+       00 0a                   ; Opcode 10
+       00 18                   ; Length 24
+       31 32 33 34 35 36 37 38 ; Random bits
+       02 00 00 00             ; wrong version
+       00 00 00 00             ; Timestamp
+       31 32 33 34 35 36 37 38 ; wrong hash
+HEX_EDNSDATA_END
+ENTRY_END
+; ... get BADCOOKIE and a new cookie
+STEP 31 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all server_cookie
+REPLY QR RD RA DO YXRRSET      ; BADCOOKIE is an extended rcode
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+; Query with an invalid cookie from a non-cookie protected address ...
+STEP 40 QUERY ADDRESS 1.2.3.4
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test. IN TXT
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+       00 0a                   ; Opcode 10
+       00 18                   ; Length 24
+       31 32 33 34 35 36 37 38 ; Random bits
+       02 00 00 00             ; wrong version
+       00 00 00 00             ; Timestamp
+       31 32 33 34 35 36 37 38 ; wrong hash
+HEX_EDNSDATA_END
+ENTRY_END
+; ... get answer and a cookie
+STEP 41 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all server_cookie
+REPLY QR RD RA AA DO NOERROR
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. IN TXT "test"
+ENTRY_END
+
+; Query with a valid cookie ...
+STEP 50 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test. IN TXT
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+       00 0a                   ; Opcode 10
+       00 18                   ; Length 24
+       31 32 33 34 35 36 37 38 ; Random bits
+       01 00 00 00             ; wrong version
+       00 00 00 00             ; Timestamp
+       38 52 7b a8 c6 a4 ea 96 ; Hash
+HEX_EDNSDATA_END
+ENTRY_END
+; ... get answer and the cookie
+STEP 51 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all server_cookie
+REPLY QR RD RA AA DO NOERROR
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. IN TXT "test"
+ENTRY_END
+
+; Query with a valid >30 minutes old cookie ...
+STEP 59 TIME_PASSES ELAPSE 1801
+STEP 60 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test. IN TXT
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+       00 0a                   ; Opcode 10
+       00 18                   ; Length 24
+       31 32 33 34 35 36 37 38 ; Random bits
+       01 00 00 00             ; Version/Reserved
+       00 00 00 00             ; Timestamp
+       38 52 7b a8 c6 a4 ea 96 ; Hash
+HEX_EDNSDATA_END
+ENTRY_END
+; ... Get answer and a refreshed cookie
+;     (we don't check the re-freshness here; it has its own unit test)
+STEP 61 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all server_cookie
+REPLY QR RD RA AA DO NOERROR
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. IN TXT "test"
+ENTRY_END
+
+; Query with a hash-valid >60 minutes old cookie ...
+STEP 69 TIME_PASSES ELAPSE 3601
+STEP 70 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test. IN TXT
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+       00 0a                   ; Opcode 10
+       00 18                   ; Length 24
+       31 32 33 34 35 36 37 38 ; Random bits
+       01 00 00 00             ; Version/Reserved
+       00 00 07 09             ; Timestamp (1801)
+       77 81 38 e3 8f aa 72 86 ; Hash
+HEX_EDNSDATA_END
+ENTRY_END
+; ... get BADCOOKIE and a new cookie
+STEP 71 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all server_cookie
+REPLY QR RD RA DO YXRRSET      ; BADCOOKIE is an extended rcode
+SECTION QUESTION
+test. IN TXT
+ENTRY_END
+
+; Query with a valid future (<5 minutes) cookie ...
+STEP 80 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+test. IN TXT
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+       00 0a                   ; Opcode 10
+       00 18                   ; Length 24
+       31 32 33 34 35 36 37 38 ; Random bits
+       01 00 00 00             ; Version/Reserved
+       00 00 16 45             ; Timestamp (1801 + 3601 + 299)
+       4a f5 0f df f0 e8 c7 09 ; Hash
+HEX_EDNSDATA_END
+ENTRY_END
+; ... get an answer
+STEP 81 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all server_cookie
+REPLY QR RD RA AA DO NOERROR
+SECTION QUESTION
+test. IN TXT
+SECTION ANSWER
+test. IN TXT "test"
+ENTRY_END
+
+SCENARIO_END