From dd11688b8c9c855073e480ba0546c14b5936b1ae Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 1 Jan 2024 17:17:25 +0000 Subject: [PATCH] Measure cryptographic work done by DNSSEC. Signed-off-by: DL6ER --- src/dnsmasq/dnsmasq.h | 10 ++++++---- src/dnsmasq/dnssec.c | 44 +++++++++++++++++++++++++++---------------- src/dnsmasq/forward.c | 42 ++++++++++++++++++++++++++++------------- 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/src/dnsmasq/dnsmasq.h b/src/dnsmasq/dnsmasq.h index 63a289f9..0a95d40f 100644 --- a/src/dnsmasq/dnsmasq.h +++ b/src/dnsmasq/dnsmasq.h @@ -802,7 +802,7 @@ struct frec { struct blockdata *stash; /* Saved reply, whilst we validate */ size_t stash_len; #ifdef HAVE_DNSSEC - int class, work_counter; + int class, work_counter, validate_counter; struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ struct frec *next_dependent; /* list of above. */ struct frec *blocking_query; /* Query which is blocking us. */ @@ -1440,10 +1440,12 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); /* dnssec.c */ #ifdef HAVE_DNSSEC size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz); -int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); -int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); +int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, + char *keyname, int class, int *validate_count); +int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, + char *keyname, int class, int *validate_count); int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, - int check_unsigned, int *neganswer, int *nons, int *nsec_ttl); + int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_count); int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); size_t filter_rrsigs(struct dns_header *header, size_t plen); int setup_timestamp(void); diff --git a/src/dnsmasq/dnssec.c b/src/dnsmasq/dnssec.c index ceb6a37d..cb35175d 100644 --- a/src/dnsmasq/dnssec.c +++ b/src/dnsmasq/dnssec.c @@ -445,7 +445,7 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int */ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, - int algo_in, int keytag_in, unsigned long *ttl_out) + int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter) { unsigned char *p; int rdlen, j, name_labels, algo, labels, key_tag, sig_fail_cnt; @@ -655,8 +655,10 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in if (key) { - if (algo_in == algo && keytag_in == key_tag && - verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo)) + if (algo_in == algo && keytag_in == key_tag) + (*validate_counter)++; + + if (verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo)) return STAT_SECURE; } else @@ -667,7 +669,9 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in crecp->addr.key.keytag == key_tag && crecp->uid == (unsigned int)class) { - if (verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo)) + (*validate_counter)++; + + if (verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo)) return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE; /* An attacker can waste a lot of our CPU by setting up a giant DNSKEY RRSET full of failing @@ -699,7 +703,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in STAT_NEED_DS DS records to validate a key not found, name in keyname STAT_NEED_KEY DNSKEY records to validate a key not found, name in keyname */ -int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, + char *keyname, int class, int *validate_counter) { unsigned char *psave, *p = (unsigned char *)(header+1); struct crec *crecp, *recp1; @@ -806,6 +811,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name); hash->update(ctx, (unsigned int)rdlen, psave); hash->digest(ctx, hash->digest_size, digest); + (*validate_counter)++; /* computing a hash is a unit of crypto work. */ from_wire(name); @@ -833,7 +839,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch failflags &= ~DNSSEC_FAIL_NOSIG; rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, - NULL, key, rdlen - 4, algo, keytag, &sig_ttl); + NULL, key, rdlen - 4, algo, keytag, &sig_ttl, validate_counter); if (STAT_ISEQUAL(rc, STAT_ABANDONED)) return STAT_ABANDONED; @@ -958,7 +964,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch STAT_ABANDONED resource exhaustion. */ -int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) +int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, + char *keyname, int class, int *validate_counter) { unsigned char *p = (unsigned char *)(header+1); int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0; @@ -983,7 +990,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char servfail = neganswer = nons = 1; else { - rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl); + rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter); if (STAT_ISEQUAL(rc, STAT_INSECURE)) { @@ -1466,8 +1473,8 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige } /* returns 0 on success, or DNSSEC_FAIL_* value on failure. */ -static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, - char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons) +static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, char *workspace1, + char *workspace2, char *name, int type, char *wildname, int *nons, int *validate_counter) { unsigned char *salt, *p, *digest; int digest_len, i, iterations, salt_len, base32_len, algo = 0; @@ -1551,6 +1558,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns nsecs[i] = nsec3p; } + (*validate_counter)++; if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0) return DNSSEC_FAIL_NONSEC; @@ -1570,6 +1578,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns if (wildname && hostname_isequal(closest_encloser, wildname)) break; + (*validate_counter)++; if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0) return DNSSEC_FAIL_NONSEC; @@ -1598,6 +1607,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns return DNSSEC_FAIL_NONSEC; /* Look for NSEC3 that proves the non-existence of the next-closest encloser */ + (*validate_counter)++; if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0) return DNSSEC_FAIL_NONSEC; @@ -1613,6 +1623,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns wildcard--; *wildcard = '*'; + (*validate_counter)++; if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) return DNSSEC_FAIL_NONSEC; @@ -1624,7 +1635,8 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns } /* returns 0 on success, or DNSSEC_FAIL_* value on failure. */ -static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl) +static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, + char *wildname, int *nons, int *nsec_ttl, int *validate_counter) { static unsigned char **nsecset = NULL, **rrsig_labels = NULL; static int nsecset_sz = 0, rrsig_labels_sz = 0; @@ -1743,7 +1755,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key if (type_found == T_NSEC) return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons); else if (type_found == T_NSEC3) - return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons); + return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons, validate_counter); else return DNSSEC_FAIL_NONSEC; } @@ -1850,7 +1862,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now) if the nons argument is non-NULL. */ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, - int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl) + int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_counter) { static unsigned char **targets = NULL; static int target_sz = 0; @@ -2025,7 +2037,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch { unsigned long sig_ttl; rc = validate_rrset(now, header, plen, class1, type1, sigcnt, - rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl); + rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl, validate_counter); if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS) || STAT_ISEQUAL(rc, STAT_ABANDONED)) { @@ -2061,7 +2073,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch That's not a problem since if the RRsets later fail we'll return BOGUS then. */ if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) && - ((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL))) != 0) + ((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0) return STAT_BOGUS | rc_nsec; rc = STAT_SECURE; @@ -2087,7 +2099,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch /* For anything other than a DS record, this situation is OK if either the answer is in an unsigned zone, or there's a NSEC records. */ - if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl)) != 0) + if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0) { /* Empty DS without NSECS */ if (qtype == T_DS) diff --git a/src/dnsmasq/forward.c b/src/dnsmasq/forward.c index a2e818b7..02be799e 100644 --- a/src/dnsmasq/forward.c +++ b/src/dnsmasq/forward.c @@ -17,6 +17,8 @@ #include "dnsmasq.h" #include "../dnsmasq_interface.h" +static int vchwm = 0; /* TODO */ + static struct frec *get_new_frec(time_t now, struct server *serv, int force); static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp); static struct frec *lookup_frec_by_query(void *hash, unsigned int flags, unsigned int flagmask); @@ -345,6 +347,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, forward->flags |= FREC_AD_QUESTION; #ifdef HAVE_DNSSEC forward->work_counter = DNSSEC_WORK; + forward->validate_counter = 0; if (do_bit) forward->flags |= FREC_DO_QUESTION; #endif @@ -936,6 +939,8 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server static void dnssec_validate(struct frec *forward, struct dns_header *header, ssize_t plen, int status, time_t now) { + struct frec *orig; + daemon->log_display_id = forward->frec_src.log_id; /* We've had a reply already, which we're validating. Ignore this duplicate */ @@ -960,6 +965,9 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, log_query(F_UPSTREAM | F_NOEXTRA, daemon->namebuff, NULL, "truncated", (forward->flags & FREC_DNSKEY_QUERY) ? T_DNSKEY : T_DS); } } + + /* Find the original query that started it all.... */ + for (orig = forward; orig->dependent; orig = orig->dependent); /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise would invite infinite loops, since the answers to DNSKEY and DS queries @@ -967,13 +975,13 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED)) { if (forward->flags & FREC_DNSKEY_QUERY) - status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); + status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter); else if (forward->flags & FREC_DS_QUERY) - status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); + status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter); else status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class, !option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC), - NULL, NULL, NULL); + NULL, NULL, NULL, &orig->validate_counter); if (STAT_ISEQUAL(status, STAT_ABANDONED)) { @@ -1030,15 +1038,11 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, else { struct server *server; - struct frec *orig; void *hash; size_t nn; int serverind, fd; struct randfd_list *rfds = NULL; - /* Find the original query that started it all.... */ - for (orig = forward; orig->dependent; orig = orig->dependent); - /* Make sure we don't expire and free the orig frec during the allocation of a new one: third arg of get_new_frec() does that. */ if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 && @@ -1393,6 +1397,11 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he log_query(F_SECSTAT, domain, &a, result, 0); } } + + if (forward->validate_counter > vchwm) + vchwm = forward->validate_counter; + if (extract_request(header, n, daemon->namebuff, NULL)) + my_syslog(LOG_INFO, "Validate_counter %s is %d, HWM is %d", daemon->namebuff, forward->validate_counter, vchwm); /* TODO */ #endif if (option_bool(OPT_NO_REBIND)) @@ -2122,7 +2131,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, /* Recurse down the key hierarchy */ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, int class, char *name, char *keyname, struct server *server, - int have_mark, unsigned int mark, int *keycount) + int have_mark, unsigned int mark, int *keycount, int *validatecount) { int first, last, start, new_status; unsigned char *packet = NULL; @@ -2139,13 +2148,13 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si if (--(*keycount) == 0) new_status = STAT_ABANDONED; else if (STAT_ISEQUAL(status, STAT_NEED_KEY)) - new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); + new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class, validatecount); else if (STAT_ISEQUAL(status, STAT_NEED_DS)) - new_status = dnssec_validate_ds(now, header, n, name, keyname, class); + new_status = dnssec_validate_ds(now, header, n, name, keyname, class, validatecount); else new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, !option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC), - NULL, NULL, NULL); + NULL, NULL, NULL, validatecount); if (STAT_ISEQUAL(new_status, STAT_ABANDONED)) { @@ -2189,7 +2198,8 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, keyname, &server->addr, STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0); - new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount); + new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, + have_mark, mark, keycount, validatecount); daemon->log_display_id = log_save; @@ -2533,8 +2543,9 @@ unsigned char *tcp_request(int confd, time_t now, if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC)) { int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ + int validatecount = 0; /* How many validations we did */ int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, - serv, have_mark, mark, &keycount); + serv, have_mark, mark, &keycount, &validatecount); char *result, *domain = "result"; union all_addr a; @@ -2560,6 +2571,11 @@ unsigned char *tcp_request(int confd, time_t now, } log_query(F_SECSTAT, domain, &a, result, 0); + + if (validatecount > vchwm) + vchwm = validatecount; + if (extract_request(header, m, daemon->namebuff, NULL)) + my_syslog(LOG_INFO, "Validate_counter %s is %d, HWM is %d", daemon->namebuff, validatecount, vchwm); /* TODO */ } #endif