From 069660af4879fefd0b730168220596ac4285f07a Mon Sep 17 00:00:00 2001 From: TCY16 Date: Tue, 14 Jun 2022 15:36:25 +0200 Subject: [PATCH] add unit test for server cookie init --- iterator/iterator.c | 2 +- testcode/fake_event.c | 17 +++++++ testcode/testpkts.c | 106 ++++++++++++++++++++++++++++++++++++++++++ testcode/testpkts.h | 6 +++ 4 files changed, 130 insertions(+), 1 deletion(-) diff --git a/iterator/iterator.c b/iterator/iterator.c index 5e2110fa5..8e82caccd 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -3841,7 +3841,7 @@ process_response(struct module_qstate* qstate, struct iter_qstate* iq, "dropping response"); goto handle_it; } - } + } //@TODO think about what we do if we did send a cookie but did not get one back? /* Copy the edns options we may got from the back end */ if(edns.opt_list_in) { diff --git a/testcode/fake_event.c b/testcode/fake_event.c index be06a4721..cf715c799 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -1226,6 +1226,7 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, struct edns_option* backed_up_opt_list = qstate->edns_opts_back_out; struct edns_option* per_upstream_opt_list = NULL; + struct edns_cookie* cookie; /* If we have an already populated EDNS option list make a copy * since we may now add upstream specific EDNS options. */ if(qstate->edns_opts_back_out) { @@ -1254,6 +1255,22 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, client_string_addr->string_len, client_string_addr->string, qstate->region); } + + // @TODO Make configurable + + cookie = infra_get_cookie(env->infra_cache, addr, addrlen, zone, + zonelen, *env->now); + + if (cookie->state == SERVER_COOKIE_LEARNED) { + /* We known the complete cookie, so we attach it */ + edns_opt_list_append(&per_upstream_opt_list, LDNS_EDNS_COOKIE, + 24, cookie->data.complete, qstate->region); + } else if (cookie->state == SERVER_COOKIE_UNKNOWN) { + /* We know just client cookie, so we attach it */ + edns_opt_list_append(&per_upstream_opt_list, LDNS_EDNS_COOKIE, + 8, cookie->data.components.client, qstate->region); + } /* We ignore COOKIE_NOT_SUPPORTED */ + /* add edns */ edns.edns_present = 1; edns.ext_rcode = 0; diff --git a/testcode/testpkts.c b/testcode/testpkts.c index 3702c3f18..5a6b2f872 100644 --- a/testcode/testpkts.c +++ b/testcode/testpkts.c @@ -39,6 +39,8 @@ struct sockaddr_storage; #define MAX_LINE 10240 /** string to show in warnings and errors */ static const char* prog_name = "testpkts"; +/** hardcoded EDNS server cookie (RFC7876) */ +static const char* hardcoded_server_cookie = "7e8b5fcc6a4bc7bc"; #ifndef UTIL_LOG_H /** verbosity definition for compat */ @@ -140,6 +142,8 @@ 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, "random_client_cookie")) { + e->match_random_client_cookie = 1; } else if(str_keyword(&parse, "UDP")) { e->match_transport = transport_udp; } else if(str_keyword(&parse, "TCP")) { @@ -263,6 +267,8 @@ 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")) { + e->server_cookie = 1; } else { error("could not parse ADJUST: '%s'", parse); } @@ -1499,6 +1505,36 @@ match_ednsdata(uint8_t* q, size_t qlen, uint8_t* p, size_t plen) return (memcmp(walk_p+8, walk_q+8, walk_qlen-8) == 0); } +/** verify that a client cookie of length 8 is in the EDNS data of the query */ +static int +match_random_client_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"); + } + + /* 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) != 8) { + verbose(3, "EDNS cookie is not 8 bytes, so not a correct client 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, @@ -1594,6 +1630,11 @@ find_match(struct entry* entries, uint8_t* query_pkt, size_t len, verbose(3, "bad EDNS data match.\n"); continue; } + if (p->match_random_client_cookie && + !match_random_client_cookie(query_pkt, len)) { + verbose(3, "bad client cookie match.\n"); + continue; + } if(p->match_transport != transport_any && p->match_transport != transport) { verbose(3, "bad transport\n"); continue; @@ -1690,6 +1731,71 @@ adjust_packet(struct entry* match, uint8_t** answer_pkt, size_t *answer_len, if(match->copy_id && reslen >= 1 && query_len >= 1) res[0] = query_pkt[0]; + if(match->server_cookie) { + /** Find the cookie option and add the server cookie if + * the client cookie is present + * */ + 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; + + /* verify that we have a EDNS record */ + if(!pkt_find_edns_opt(&walk_query, &walk_query_len)) { + walk_query_len = 0; + } + if(!pkt_find_edns_opt(&walk_response, &walk_response_len)) { + walk_response_len = 0; + } + + /* 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; + } + if (walk_response_len < 8) /* class + ttl + rdlen */ { + walk_response_len = 0; + } + + rd_len_ptr = walk_response + 6; /* store the location of the rdlen */ + + /* skip past the OPT record to get to the option */ + walk_query += 8; + walk_query_len -= 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 */ + walk_query_len = 0; + } + + /* verify that the client cookie exists */ + if (walk_query_len < 12 /* opt_code + opt_len + client cookie */) { + walk_query_len = 0; + } + + 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); + + reslen = origlen + 28; + } + } + if(match->copy_ednsdata_assume_clientsubnet) { /** Assume there is only one EDNS option, which is ECS. * Copy source mask from query to scope mask in reply. Assume diff --git a/testcode/testpkts.h b/testcode/testpkts.h index 2768040c6..f009980f7 100644 --- a/testcode/testpkts.h +++ b/testcode/testpkts.h @@ -214,6 +214,8 @@ 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 */ + uint8_t match_random_client_cookie; /** match query serial with this value. */ uint32_t ixfr_soa_serial; /** match on UDP/TCP */ @@ -237,6 +239,10 @@ struct entry { /** in seconds */ unsigned int sleeptime; + /** add a server cookie (RFC9018) to the response (provided the query + * contains a client cookie) */ + uint8_t server_cookie; + /** some number that names this entry, line number in file or so */ int lineno;