(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;
{
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;
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++;
+ }
+}
*/
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 */
}
}
+ /* 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.
.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
.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
.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
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 */
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);
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);
}
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);
}
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)
{
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);
}
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);
}
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);
}
# 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=$!
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
--- /dev/null
+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"
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;
&((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,
* 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:
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;
};
/**
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)
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;
}
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.
* @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