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;
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,
#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"
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")) {
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);
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;
}
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 */
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,
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;
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;
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");
}
}
--- /dev/null
+; 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