*/
#include "config.h"
-struct sockaddr_storage;
#include <errno.h>
#include <stdarg.h>
#include <ctype.h>
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")) {
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 */
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");
; '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 ...
; 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
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 */
/** 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;
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);
}
--- /dev/null
+; 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