Skip to content

Commit

Permalink
Measure cryptographic work done by DNSSEC.
Browse files Browse the repository at this point in the history
Signed-off-by: DL6ER <dl6er@dl6er.de>
  • Loading branch information
simonkelley authored and DL6ER committed Feb 13, 2024
1 parent 70b0431 commit dd11688
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 33 deletions.
10 changes: 6 additions & 4 deletions src/dnsmasq/dnsmasq.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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);
Expand Down
44 changes: 28 additions & 16 deletions src/dnsmasq/dnssec.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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))
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand Down
42 changes: 29 additions & 13 deletions src/dnsmasq/forward.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 */
Expand All @@ -960,20 +965,23 @@ 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
will not be cached, so they'll be repeated. */
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))
{
Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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;
Expand All @@ -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))
{
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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

Expand Down

0 comments on commit dd11688

Please sign in to comment.