- For #762: Introduce stat counters for downstream DNS Cookies per

thread and total: num.queries_cookie_valid, num.queries_cookie_client,
  num.queries.cookie_invalid.
This commit is contained in:
George Thessalonikefs 2023-08-08 15:19:56 +02:00
parent 49e4258102
commit bab5ad623c
15 changed files with 247 additions and 33 deletions

View File

@ -672,6 +672,12 @@ print_stats(RES* ssl, const char* nm, struct ub_stats_info* s)
(unsigned long)s->svr.num_queries)) return 0;
if(!ssl_printf(ssl, "%s.num.queries_ip_ratelimited"SQ"%lu\n", nm,
(unsigned long)s->svr.num_queries_ip_ratelimited)) return 0;
if(!ssl_printf(ssl, "%s.num.queries_cookie_valid"SQ"%lu\n", nm,
(unsigned long)s->svr.num_queries_cookie_valid)) return 0;
if(!ssl_printf(ssl, "%s.num.queries_cookie_client"SQ"%lu\n", nm,
(unsigned long)s->svr.num_queries_cookie_client)) return 0;
if(!ssl_printf(ssl, "%s.num.queries_cookie_invalid"SQ"%lu\n", nm,
(unsigned long)s->svr.num_queries_cookie_invalid)) return 0;
if(!ssl_printf(ssl, "%s.num.cachehits"SQ"%lu\n", nm,
(unsigned long)(s->svr.num_queries
- s->svr.num_queries_missed_cache))) return 0;

View File

@ -435,6 +435,9 @@ void server_stats_add(struct ub_stats_info* total, struct ub_stats_info* a)
{
total->svr.num_queries += a->svr.num_queries;
total->svr.num_queries_ip_ratelimited += a->svr.num_queries_ip_ratelimited;
total->svr.num_queries_cookie_valid += a->svr.num_queries_cookie_valid;
total->svr.num_queries_cookie_client += a->svr.num_queries_cookie_client;
total->svr.num_queries_cookie_invalid += a->svr.num_queries_cookie_invalid;
total->svr.num_queries_missed_cache += a->svr.num_queries_missed_cache;
total->svr.num_queries_prefetch += a->svr.num_queries_prefetch;
total->svr.num_queries_timed_out += a->svr.num_queries_timed_out;
@ -568,3 +571,16 @@ void server_stats_insrcode(struct ub_server_stats* stats, sldns_buffer* buf)
stats->ans_rcode_nodata ++;
}
}
void server_stats_downstream_cookie(struct ub_server_stats* stats,
struct edns_data* edns)
{
if(!(edns->edns_present && edns->cookie_present)) return;
if(edns->cookie_valid) {
stats->num_queries_cookie_valid++;
} else if(edns->cookie_client) {
stats->num_queries_cookie_client++;
} else {
stats->num_queries_cookie_invalid++;
}
}

View File

@ -126,4 +126,11 @@ void server_stats_insquery(struct ub_server_stats* stats, struct comm_point* c,
*/
void server_stats_insrcode(struct ub_server_stats* stats, struct sldns_buffer* buf);
/**
* Add DNS Cookie stats for this query
* @param stats: the stats
* @param edns: edns record
*/
void server_stats_downstream_cookie(struct ub_server_stats* stats,
struct edns_data* edns);
#endif /* DAEMON_STATS_H */

View File

@ -1602,6 +1602,9 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
}
}
/* Get stats for cookies */
server_stats_downstream_cookie(&worker->stats, &edns);
/* If the IP rate limiting check was postponed, check now. */
if(!pre_edns_ip_ratelimit) {
/* NOTE: we always check the repinfo->client_address.

View File

@ -369,6 +369,15 @@ number of queries received by thread
.I threadX.num.queries_ip_ratelimited
number of queries rate limited by thread
.TP
.I threadX.num.queries_cookie_valid
number of queries with a valid DNS Cookie by thread
.TP
.I threadX.num.queries_cookie_client
number of queries with a client part only DNS Cookie by thread
.TP
.I threadX.num.queries_cookie_invalid
number of queries with an invalid DNS Cookie by thread
.TP
.I threadX.num.cachehits
number of queries that were successfully answered using a cache lookup
.TP
@ -446,6 +455,18 @@ buffers are full.
.I total.num.queries
summed over threads.
.TP
.I total.num.queries_ip_ratelimited
summed over threads.
.TP
.I total.num.queries_cookie_valid
summed over threads.
.TP
.I total.num.queries_cookie_client
summed over threads.
.TP
.I total.num.queries_cookie_invalid
summed over threads.
.TP
.I total.num.cachehits
summed over threads.
.TP
@ -611,7 +632,7 @@ ratelimiting.
.TP
.I num.query.dnscrypt.shared_secret.cachemiss
The number of dnscrypt queries that did not find a shared secret in the cache.
The can be use to compute the shared secret hitrate.
This can be used to compute the shared secret hitrate.
.TP
.I num.query.dnscrypt.replay
The number of dnscrypt queries that found a nonce hit in the nonce cache and

View File

@ -695,6 +695,12 @@ struct ub_server_stats {
long long num_queries;
/** number of queries that have been dropped/ratelimited by ip. */
long long num_queries_ip_ratelimited;
/** number of queries with a valid DNS Cookie. */
long long num_queries_cookie_valid;
/** number of queries with only the client part of the DNS Cookie. */
long long num_queries_cookie_client;
/** number of queries with invalid DNS Cookie. */
long long num_queries_cookie_invalid;
/** number of queries that had a cache-miss. */
long long num_queries_missed_cache;
/** number of prefetch queries - cachehits with prefetch */

View File

@ -204,6 +204,12 @@ static void pr_stats(const char* nm, struct ub_stats_info* s)
PR_UL_NM("num.queries", s->svr.num_queries);
PR_UL_NM("num.queries_ip_ratelimited",
s->svr.num_queries_ip_ratelimited);
PR_UL_NM("num.queries_cookie_valid",
s->svr.num_queries_cookie_valid);
PR_UL_NM("num.queries_cookie_client",
s->svr.num_queries_cookie_client);
PR_UL_NM("num.queries_cookie_invalid",
s->svr.num_queries_cookie_invalid);
PR_UL_NM("num.cachehits",
s->svr.num_queries - s->svr.num_queries_missed_cache);
PR_UL_NM("num.cachemiss", s->svr.num_queries_missed_cache);

View File

@ -557,7 +557,7 @@ edns_cookie_invalid_version(void)
memcpy(buf + 16, "\306\063\144\144", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
buf, timestamp) == 0);
buf, timestamp) == COOKIE_STATUS_INVALID);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
@ -587,7 +587,7 @@ edns_cookie_invalid_hash(void)
memcpy(buf + 16, "\313\000\161\313", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
buf, timestamp) == 0);
buf, timestamp) == COOKIE_STATUS_INVALID);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
@ -620,13 +620,13 @@ edns_cookie_rfc9018_a3_better(void)
memcpy(buf + 16, "\313\000\161\313", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
buf, timestamp) == -1);
buf, timestamp) == COOKIE_STATUS_VALID_RENEW);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/* Complete hash-valid client cookie; more than 60 minutes old; needs a
* refreshed server cookie. */
/* Complete hash-valid client cookie; more than 60 minutes old (expired);
* needs a refreshed server cookie. */
static void
edns_cookie_rfc9018_a3(void)
{
@ -651,7 +651,7 @@ edns_cookie_rfc9018_a3(void)
memcpy(buf + 16, "\313\000\161\313", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
buf, timestamp) == 0);
buf, timestamp) == COOKIE_STATUS_EXPIRED);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
@ -682,7 +682,7 @@ edns_cookie_rfc9018_a2(void)
memcpy(buf + 16, "\306\063\144\144", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
buf, timestamp) == -1);
buf, timestamp) == COOKIE_STATUS_VALID_RENEW);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
@ -711,7 +711,7 @@ edns_cookie_rfc9018_a1(void)
sizeof(client_cookie),
/* these will not be used; it will return invalid
* because of the size. */
NULL, 0, 1, NULL, 0) == 0);
NULL, 0, 1, NULL, 0) == COOKIE_STATUS_CLIENT_ONLY);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}

View File

@ -37,6 +37,7 @@ echo "FWD_EXPIRED_PID=$FWD_EXPIRED_PID" >> .tpkg.var.test
# make config file
sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' -e 's/@EXPIREDPORT\@/'$FWD_EXPIRED_PORT'/' -e 's/@CONTROL_PORT\@/'$CONTROL_PORT'/' < stat_values.conf > ub.conf
sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' -e 's/@EXPIREDPORT\@/'$FWD_EXPIRED_PORT'/' -e 's/@CONTROL_PORT\@/'$CONTROL_PORT'/' < stat_values_cachedb.conf > ub_cachedb.conf
sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@CONTROL_PORT\@/'$CONTROL_PORT'/' < stat_values_downstream_cookies.conf > ub_downstream_cookies.conf
# start unbound in the background
$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
UNBOUND_PID=$!

View File

@ -414,6 +414,97 @@ rrset.cache.count=3
infra.cache.count=2"
# Bring the downstream DNS Cookies configured Unbound up
kill_pid $UNBOUND_PID # kill current Unbound
$PRE/unbound -d -c ub_downstream_cookies.conf >unbound.log 2>&1 &
UNBOUND_PID=$!
echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
wait_unbound_up unbound.log
echo
echo "[ Get a DNS Cookie. ]"
echo "> dig www.local.zone +tcp +ednsopt=10:0102030405060708"
dig @127.0.0.1 -p $UNBOUND_PORT +tcp +ednsopt=10:0102030405060708 +retry=0 +time=1 www.local.zone. | tee outfile
echo "> check answer"
if grep "192.0.2.1" outfile; then
echo "OK"
else
end 1
fi
# Save valid cookie
valid_cookie=`grep "COOKIE: " outfile | cut -d ' ' -f 3`
invalid_cookie=`echo $valid_cookie | tr '0' '4'`
check_stats "\
total.num.queries=1
total.num.queries_cookie_client=1
total.num.cachehits=1
num.query.type.A=1
num.query.class.IN=1
num.query.opcode.QUERY=1
num.query.flags.RD=1
num.query.flags.AD=1
num.query.edns.present=1
num.query.tcp=1
num.answer.rcode.NOERROR=1"
echo
echo "[ Present the valid DNS Cookie. ]"
echo "> dig www.local.zone +ednsopt=10:valid_cookie"
dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:$valid_cookie +retry=0 +time=1 www.local.zone. | tee outfile
echo "> check answer"
if grep "192.0.2.1" outfile; then
echo "OK"
else
end 1
fi
check_stats "\
total.num.queries=1
total.num.queries_cookie_valid=1
total.num.cachehits=1
num.query.type.A=1
num.query.class.IN=1
num.query.opcode.QUERY=1
num.query.flags.RD=1
num.query.flags.AD=1
num.query.edns.present=1
num.answer.rcode.NOERROR=1"
echo
echo "[ Present an invalid DNS Cookie. ]"
echo "> dig www.local.zone +ednsopt=10:invalid_cookie"
dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:$invalid_cookie +retry=0 +time=1 www.local.zone. | tee outfile
echo "> check answer"
if grep "192.0.2.1" outfile; then
end 1
else
echo "OK"
fi
# A lot of stats are missing since BADCOOKIE error response is before
# those stat calculations.
# BADCOOKIE is an extended error code; we record YXRRSET below.
check_stats "\
total.num.queries=1
total.num.queries_cookie_invalid=1
total.num.cachehits=1
num.answer.rcode.YXRRSET=1"
echo
echo "[ Present no DNS Cookie. ]"
echo "> dig www.local.zone +ignore"
dig @127.0.0.1 -p $UNBOUND_PORT +ignore +retry=0 +time=1 www.local.zone. | tee outfile
echo "> check answer"
if grep "192.0.2.1" outfile; then
end 1
else
echo "OK"
fi
# A lot of stats are missing since REFUSED error response because of no DNS
# Cookie is before those stat calculations.
check_stats "\
total.num.queries=1
total.num.cachehits=1
num.answer.rcode.REFUSED=1"
if test x$USE_CACHEDB = "x1"; then
# Bring the cachedb configured Unbound up

View File

@ -0,0 +1,32 @@
server:
verbosity: 5
module-config: "iterator"
num-threads: 1
interface: 127.0.0.1
port: @PORT@
use-syslog: no
directory: ""
pidfile: "unbound.pid"
chroot: ""
username: ""
extended-statistics: yes
identity: "stat_values"
outbound-msg-retry: 0
root-key-sentinel: no
trust-anchor-signaling: no
local-zone: local.zone static
local-data: "www.local.zone A 192.0.2.1"
answer-cookie: yes
access-control: 127.0.0.1 allow_cookie
remote-control:
control-enable: yes
control-interface: 127.0.0.1
# control-interface: ::1
control-port: @CONTROL_PORT@
server-key-file: "unbound_server.key"
server-cert-file: "unbound_server.pem"
control-key-file: "unbound_control.key"
control-cert-file: "unbound_control.pem"

View File

@ -971,7 +971,7 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
uint16_t opt_code = sldns_read_uint16(rdata_ptr);
uint16_t opt_len = sldns_read_uint16(rdata_ptr+2);
uint8_t server_cookie[40];
int cookie_is_valid;
enum edns_cookie_val_status cookie_val_status;
int cookie_is_v4 = 1;
rdata_ptr += 4;
@ -1064,12 +1064,14 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
&((struct sockaddr_in6*)&repinfo->remote_addr)->sin6_addr, 16);
}
cookie_is_valid = edns_cookie_server_validate(
cookie_val_status = edns_cookie_server_validate(
rdata_ptr, opt_len, cfg->cookie_secret,
cfg->cookie_secret_len, cookie_is_v4,
server_cookie, now);
if(cookie_is_valid != 0) edns->cookie_valid = 1;
if(cookie_is_valid == 1) {
switch(cookie_val_status) {
case COOKIE_STATUS_VALID:
case COOKIE_STATUS_VALID_RENEW:
edns->cookie_valid = 1;
/* Reuse cookie */
if(!edns_opt_list_append(
&edns->opt_list_out, LDNS_EDNS_COOKIE,
@ -1081,13 +1083,22 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
* options. Done!
*/
break;
}
edns_cookie_server_write(server_cookie,
cfg->cookie_secret, cookie_is_v4, now);
if(!edns_opt_list_append(&edns->opt_list_out,
LDNS_EDNS_COOKIE, 24, server_cookie, region)) {
log_err("out of memory");
return LDNS_RCODE_SERVFAIL;
case COOKIE_STATUS_CLIENT_ONLY:
edns->cookie_client = 1;
/* fallthrough */
case COOKIE_STATUS_FUTURE:
case COOKIE_STATUS_EXPIRED:
case COOKIE_STATUS_INVALID:
default:
edns_cookie_server_write(server_cookie,
cfg->cookie_secret, cookie_is_v4, now);
if(!edns_opt_list_append(&edns->opt_list_out,
LDNS_EDNS_COOKIE, 24, server_cookie,
region)) {
log_err("out of memory");
return LDNS_RCODE_SERVFAIL;
}
break;
}
break;
default:

View File

@ -243,6 +243,8 @@ struct edns_data {
unsigned int cookie_present : 1;
/** if the cookie validated */
unsigned int cookie_valid : 1;
/** if the cookie holds only the client part */
unsigned int cookie_client : 1;
};
/**

View File

@ -153,7 +153,7 @@ edns_cookie_server_write(uint8_t* buf, const uint8_t* secret, int v4,
memcpy(buf + 16, hash, 8);
}
int
enum edns_cookie_val_status
edns_cookie_server_validate(const uint8_t* cookie, size_t cookie_len,
const uint8_t* secret, size_t secret_len, int v4,
const uint8_t* hash_input, uint32_t now)
@ -162,26 +162,28 @@ edns_cookie_server_validate(const uint8_t* cookie, size_t cookie_len,
uint32_t timestamp;
uint32_t subt_1982 = 0; /* Initialize for the compiler; unused value */
int comp_1982;
if(cookie_len != 24 || /* RFC9018 cookies are 24 bytes long */
secret_len != 16 || /* RFC9018 cookies have 16 byte secrets */
cookie[8] != 1) /* RFC9018 cookies are cookie version 1 */
return 0;
if(cookie_len != 24)
/* RFC9018 cookies are 24 bytes long */
return COOKIE_STATUS_CLIENT_ONLY;
if(secret_len != 16 || /* RFC9018 cookies have 16 byte secrets */
cookie[8] != 1) /* RFC9018 cookies are cookie version 1 */
return COOKIE_STATUS_INVALID;
timestamp = sldns_read_uint32(cookie + 12);
if((comp_1982 = compare_1982(now, timestamp)) > 0
&& (subt_1982 = subtract_1982(timestamp, now)) > 3600)
/* Cookie is older than 1 hour (see RFC9018 Section 4.3.) */
return 0;
return COOKIE_STATUS_EXPIRED;
if(comp_1982 <= 0 && subtract_1982(now, timestamp) > 300)
/* Cookie time is more than 5 minutes in the future.
* (see RFC9018 Section 4.3.) */
return 0;
return COOKIE_STATUS_FUTURE;
if(memcmp(edns_cookie_server_hash(hash_input, secret, v4, hash),
cookie + 16, 8) != 0)
/* Hashes do not match */
return 0;
return COOKIE_STATUS_INVALID;
if(comp_1982 > 0 && subt_1982 > 1800)
/* Valid cookie but older than 30 minutes, so create a new one
* anyway */
return -1;
return 1;
return COOKIE_STATUS_VALID_RENEW;
return COOKIE_STATUS_VALID;
}

View File

@ -75,6 +75,15 @@ struct edns_string_addr {
size_t string_len;
};
enum edns_cookie_val_status {
COOKIE_STATUS_CLIENT_ONLY = -3,
COOKIE_STATUS_FUTURE = -2,
COOKIE_STATUS_EXPIRED = -1,
COOKIE_STATUS_INVALID = 0,
COOKIE_STATUS_VALID = 1,
COOKIE_STATUS_VALID_RENEW = 2,
};
/**
* Create structure to hold EDNS strings
* @return: newly created edns_strings, NULL on alloc failure.
@ -149,10 +158,11 @@ void edns_cookie_server_write(uint8_t* buf, const uint8_t* secret, int v4,
* @param hash_input: pointer to the hash input for validation. It needs to be:
* Client Cookie | Version | Reserved | Timestamp | Client-IP
* @param now: the current time.
* return 1 if valid, -1 if valid but a new one SHOULD be generated, else 0.
* return edns_cookie_val_status with the cookie validation status i.e.,
* <=0 for invalid, else valid.
*/
int edns_cookie_server_validate(const uint8_t* cookie, size_t cookie_len,
const uint8_t* secret, size_t secret_len, int v4,
enum edns_cookie_val_status edns_cookie_server_validate(const uint8_t* cookie,
size_t cookie_len, const uint8_t* secret, size_t secret_len, int v4,
const uint8_t* hash_input, uint32_t now);
#endif