Merge pull request #762 from NLnetLabs/features/downstream-cookies

Downstream DNS Server Cookies a la RFC7873 and RFC9018
This commit is contained in:
Wouter Wijngaards 2023-08-17 15:14:11 +02:00 committed by GitHub
commit a1c82ac5fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 2027 additions and 160 deletions

View file

@ -122,13 +122,13 @@ iterator/iter_delegpt.c iterator/iter_donotq.c iterator/iter_fwd.c \
iterator/iter_hints.c iterator/iter_priv.c iterator/iter_resptype.c \
iterator/iter_scrub.c iterator/iter_utils.c services/listen_dnsport.c \
services/localzone.c services/mesh.c services/modstack.c services/view.c \
services/rpz.c \
services/rpz.c util/rfc_1982.c \
services/outbound_list.c services/outside_network.c util/alloc.c \
util/config_file.c util/configlexer.c util/configparser.c \
util/shm_side/shm_main.c services/authzone.c \
util/fptr_wlist.c util/locks.c util/log.c util/mini_event.c util/module.c \
util/netevent.c util/net_help.c util/random.c util/rbtree.c util/regional.c \
util/rtt.c util/edns.c util/storage/dnstree.c util/storage/lookup3.c \
util/rtt.c util/siphash.c util/edns.c util/storage/dnstree.c util/storage/lookup3.c \
util/storage/lruhash.c util/storage/slabhash.c util/tcp_conn_limit.c \
util/timehist.c util/tube.c util/proxy_protocol.c util/timeval_func.c \
util/ub_event.c util/ub_event_pluggable.c util/winsock_event.c \
@ -145,10 +145,10 @@ as112.lo msgparse.lo msgreply.lo packed_rrset.lo iterator.lo iter_delegpt.lo \
iter_donotq.lo iter_fwd.lo iter_hints.lo iter_priv.lo iter_resptype.lo \
iter_scrub.lo iter_utils.lo localzone.lo mesh.lo modstack.lo view.lo \
outbound_list.lo alloc.lo config_file.lo configlexer.lo configparser.lo \
fptr_wlist.lo edns.lo locks.lo log.lo mini_event.lo module.lo net_help.lo \
fptr_wlist.lo siphash.lo edns.lo locks.lo log.lo mini_event.lo module.lo net_help.lo \
random.lo rbtree.lo regional.lo rtt.lo dnstree.lo lookup3.lo lruhash.lo \
slabhash.lo tcp_conn_limit.lo timehist.lo tube.lo winsock_event.lo \
autotrust.lo val_anchor.lo rpz.lo proxy_protocol.lo \
autotrust.lo val_anchor.lo rpz.lo rfc_1982.lo proxy_protocol.lo \
validator.lo val_kcache.lo val_kentry.lo val_neg.lo val_nsec3.lo val_nsec.lo \
val_secalgo.lo val_sigcrypt.lo val_utils.lo dns64.lo $(CACHEDB_OBJ) authzone.lo \
$(SUBNET_OBJ) $(PYTHONMOD_OBJ) $(CHECKLOCK_OBJ) $(DNSTAP_OBJ) $(DNSCRYPT_OBJ) \
@ -917,7 +917,8 @@ config_file.lo config_file.o: $(srcdir)/util/config_file.c config.h $(srcdir)/ut
configlexer.lo configlexer.o: util/configlexer.c config.h $(srcdir)/util/configyyrename.h \
$(srcdir)/util/config_file.h util/configparser.h
configparser.lo configparser.o: util/configparser.c config.h $(srcdir)/util/configyyrename.h \
$(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h
$(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h $(srcdir)/sldns/str2wire.h \
$(srcdir)/sldns/rrdef.h
shm_main.lo shm_main.o: $(srcdir)/util/shm_side/shm_main.c config.h $(srcdir)/util/shm_side/shm_main.h \
$(srcdir)/libunbound/unbound.h $(srcdir)/daemon/daemon.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \
$(srcdir)/util/alloc.h $(srcdir)/services/modstack.h \
@ -1008,6 +1009,8 @@ rtt.lo rtt.o: $(srcdir)/util/rtt.c config.h $(srcdir)/util/rtt.h $(srcdir)/itera
$(srcdir)/services/outbound_list.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/storage/lruhash.h \
$(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/module.h \
$(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h
siphash.lo siphash.o: $(srcdir)/util/siphash.c
rfc_1982.lo rfc_1982.o: $(srcdir)/util/rfc_1982.c
edns.lo edns.o: $(srcdir)/util/edns.c config.h $(srcdir)/util/edns.h $(srcdir)/util/storage/dnstree.h \
$(srcdir)/util/rbtree.h $(srcdir)/util/config_file.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \
$(srcdir)/util/net_help.h $(srcdir)/util/log.h $(srcdir)/util/regional.h \

View file

@ -109,6 +109,8 @@ parse_acl_access(const char* str, enum acl_access* control)
*control = acl_allow_snoop;
else if(strcmp(str, "allow_setrd") == 0)
*control = acl_allow_setrd;
else if (strcmp(str, "allow_cookie") == 0)
*control = acl_allow_cookie;
else {
log_err("access control type %s unknown", str);
return 0;

View file

@ -64,8 +64,12 @@ enum acl_access {
acl_allow,
/** allow full access for all queries, recursion and cache snooping */
acl_allow_snoop,
/** allow full access for recursion queries and set RD flag regardless of request */
acl_allow_setrd
/** allow full access for recursion queries and set RD flag regardless
* of request */
acl_allow_setrd,
/** allow full access for recursion (+RD) queries if valid cookie
* present or stateful transport */
acl_allow_cookie
};
/**

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

@ -1319,6 +1319,40 @@ deny_refuse_non_local(struct comm_point* c, enum acl_access acl,
worker, repinfo, acladdr, ede, check_result);
}
/* Returns 1 if the ip rate limit check can happen before EDNS parsing,
* else 0 */
static int
pre_edns_ip_ratelimit_check(enum acl_access acl)
{
if(acl == acl_allow_cookie) return 0;
return 1;
}
/* Check if the query is blocked by source IP rate limiting.
* Returns 1 if it passes the check, 0 otherwise. */
static int
check_ip_ratelimit(struct worker* worker, struct sockaddr_storage* addr,
socklen_t addrlen, int has_cookie, sldns_buffer* pkt)
{
if(!infra_ip_ratelimit_inc(worker->env.infra_cache, addr, addrlen,
*worker->env.now, has_cookie,
worker->env.cfg->ip_ratelimit_backoff, pkt)) {
/* See if we can pass through with slip factor */
if(!has_cookie && worker->env.cfg->ip_ratelimit_factor != 0 &&
ub_random_max(worker->env.rnd,
worker->env.cfg->ip_ratelimit_factor) == 0) {
char addrbuf[128];
addr_to_str(addr, addrlen, addrbuf, sizeof(addrbuf));
verbose(VERB_QUERY, "ip_ratelimit allowed through for "
"ip address %s because of slip in "
"ip_ratelimit_factor", addrbuf);
return 1;
}
return 0;
}
return 1;
}
int
worker_handle_request(struct comm_point* c, void* arg, int error,
struct comm_reply* repinfo)
@ -1332,6 +1366,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
struct edns_option* original_edns_list = NULL;
enum acl_access acl;
struct acl_addr* acladdr;
int pre_edns_ip_ratelimit = 1;
int rc = 0;
int need_drop = 0;
int is_expired_answer = 0;
@ -1456,33 +1491,21 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
}
worker->stats.num_queries++;
pre_edns_ip_ratelimit = pre_edns_ip_ratelimit_check(acl);
/* check if this query should be dropped based on source ip rate limiting
* NOTE: we always check the repinfo->client_address. IP ratelimiting is
* implicitly disabled for proxies. */
if(!infra_ip_ratelimit_inc(worker->env.infra_cache,
&repinfo->client_addr, repinfo->client_addrlen,
*worker->env.now,
worker->env.cfg->ip_ratelimit_backoff, c->buffer)) {
/* See if we are passed through with slip factor */
if(worker->env.cfg->ip_ratelimit_factor != 0 &&
ub_random_max(worker->env.rnd,
worker->env.cfg->ip_ratelimit_factor) == 0) {
char addrbuf[128];
addr_to_str(&repinfo->client_addr,
repinfo->client_addrlen, addrbuf,
sizeof(addrbuf));
verbose(VERB_QUERY, "ip_ratelimit allowed through for "
"ip address %s because of slip in "
"ip_ratelimit_factor", addrbuf);
} else {
/* If the IP rate limiting check needs extra EDNS information (e.g.,
* DNS Cookies) postpone the check until after EDNS is parsed. */
if(pre_edns_ip_ratelimit) {
/* NOTE: we always check the repinfo->client_address.
* IP ratelimiting is implicitly disabled for proxies. */
if(!check_ip_ratelimit(worker, &repinfo->client_addr,
repinfo->client_addrlen, 0, c->buffer)) {
worker->stats.num_queries_ip_ratelimited++;
comm_point_drop_reply(repinfo);
return 0;
}
}
/* see if query is in the cache */
if(!query_info_parse(&qinfo, c->buffer)) {
verbose(VERB_ALGO, "worker parse request: formerror.");
log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
@ -1539,16 +1562,16 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
}
goto send_reply;
}
if((ret=parse_edns_from_query_pkt(c->buffer, &edns, worker->env.cfg, c,
worker->scratchpad)) != 0) {
if((ret=parse_edns_from_query_pkt(
c->buffer, &edns, worker->env.cfg, c, repinfo,
(worker->env.now ? *worker->env.now : time(NULL)),
worker->scratchpad)) != 0) {
struct edns_data reply_edns;
verbose(VERB_ALGO, "worker parse edns: formerror.");
log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
repinfo->client_addrlen);
memset(&reply_edns, 0, sizeof(reply_edns));
reply_edns.edns_present = 1;
reply_edns.udp_size = EDNS_ADVERTISED_SIZE;
LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret);
error_encode(c->buffer, ret, &qinfo,
*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
sldns_buffer_read_u16_at(c->buffer, 2), &reply_edns);
@ -1557,23 +1580,15 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
}
if(edns.edns_present) {
if(edns.edns_version != 0) {
edns.ext_rcode = (uint8_t)(EDNS_RCODE_BADVERS>>4);
edns.edns_version = EDNS_ADVERTISED_VERSION;
edns.udp_size = EDNS_ADVERTISED_SIZE;
edns.bits &= EDNS_DO;
edns.opt_list_in = NULL;
edns.opt_list_out = NULL;
edns.opt_list_inplace_cb_out = NULL;
edns.padding_block_size = 0;
verbose(VERB_ALGO, "query with bad edns version.");
log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
repinfo->client_addrlen);
error_encode(c->buffer, EDNS_RCODE_BADVERS&0xf, &qinfo,
extended_error_encode(c->buffer, EDNS_RCODE_BADVERS, &qinfo,
*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
sldns_buffer_read_u16_at(c->buffer, 2), NULL);
if(sldns_buffer_capacity(c->buffer) >=
sldns_buffer_limit(c->buffer)+calc_edns_field_size(&edns))
attach_edns_record(c->buffer, &edns);
sldns_buffer_read_u16_at(c->buffer, 2), 0, &edns);
regional_free_all(worker->scratchpad);
goto send_reply;
}
@ -1586,6 +1601,62 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
edns.udp_size = NORMAL_UDP_SIZE;
}
}
/* 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.
* IP ratelimiting is implicitly disabled for proxies. */
if(!check_ip_ratelimit(worker, &repinfo->client_addr,
repinfo->client_addrlen, edns.cookie_valid,
c->buffer)) {
worker->stats.num_queries_ip_ratelimited++;
comm_point_drop_reply(repinfo);
return 0;
}
}
/* "if, else if" sequence below deals with downstream DNS Cookies */
if(acl != acl_allow_cookie)
; /* pass; No cookie downstream processing whatsoever */
else if(edns.cookie_valid)
; /* pass; Valid cookie is good! */
else if(c->type != comm_udp)
; /* pass; Stateful transport */
else if(edns.cookie_present) {
/* Cookie present, but not valid: Cookie was bad! */
extended_error_encode(c->buffer,
LDNS_EXT_RCODE_BADCOOKIE, &qinfo,
*(uint16_t*)(void *)
sldns_buffer_begin(c->buffer),
sldns_buffer_read_u16_at(c->buffer, 2),
0, &edns);
regional_free_all(worker->scratchpad);
goto send_reply;
} else {
/* Cookie required, but no cookie present on UDP */
verbose(VERB_ALGO, "worker request: "
"need cookie or stateful transport");
log_addr(VERB_ALGO, "from",&repinfo->remote_addr
, repinfo->remote_addrlen);
EDNS_OPT_LIST_APPEND_EDE(&edns.opt_list_out,
worker->scratchpad, LDNS_EDE_OTHER,
"DNS Cookie needed for UDP replies");
error_encode(c->buffer,
(LDNS_RCODE_REFUSED|BIT_TC), &qinfo,
*(uint16_t*)(void *)
sldns_buffer_begin(c->buffer),
sldns_buffer_read_u16_at(c->buffer, 2),
&edns);
regional_free_all(worker->scratchpad);
goto send_reply;
}
if(edns.udp_size > worker->daemon->cfg->max_udp_size &&
c->type == comm_udp) {
verbose(VERB_QUERY,

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

@ -701,17 +701,17 @@ This option is experimental at this time.
.B access\-control: \fI<IP netblock> <action>
The netblock is given as an IP4 or IP6 address with /size appended for a
classless network block. The action can be \fIdeny\fR, \fIrefuse\fR,
\fIallow\fR, \fIallow_setrd\fR, \fIallow_snoop\fR, \fIdeny_non_local\fR or
\fIrefuse_non_local\fR.
\fIallow\fR, \fIallow_setrd\fR, \fIallow_snoop\fR, \fIallow_cookie\fR,
\fIdeny_non_local\fR or \fIrefuse_non_local\fR.
The most specific netblock match is used, if none match \fIrefuse\fR is used.
The order of the access\-control statements therefore does not matter.
.IP
The action \fIdeny\fR stops queries from hosts from that netblock.
The \fIdeny\fR action stops queries from hosts from that netblock.
.IP
The action \fIrefuse\fR stops queries too, but sends a DNS rcode REFUSED
The \fIrefuse\fR action stops queries too, but sends a DNS rcode REFUSED
error message back.
.IP
The action \fIallow\fR gives access to clients from that netblock.
The \fIallow\fR action gives access to clients from that netblock.
It gives only access for recursion clients (which is
what almost all clients need). Nonrecursive queries are refused.
.IP
@ -731,13 +731,27 @@ may be useful if another DNS server must forward requests for specific
zones to a resolver DNS server, but only supports stub domains and
sends queries to the resolver DNS server with the RD bit cleared.
.IP
The action \fIallow_snoop\fR gives nonrecursive access too. This give
The \fIallow_snoop\fR action gives nonrecursive access too. This give
both recursive and non recursive access. The name \fIallow_snoop\fR refers
to cache snooping, a technique to use nonrecursive queries to examine
the cache contents (for malicious acts). However, nonrecursive queries can
also be a valuable debugging tool (when you want to examine the cache
contents). In that case use \fIallow_snoop\fR for your administration host.
.IP
The \fIallow_cookie\fR action allows access to UDP queries that contain a
valid DNS Cookie as specified in RFC 7873 and RFC 9018, when the
\fBanswer\-cookie\fR option is enabled.
UDP queries containing only a DNS Client Cookie and no Server Cookie, or an
invalid DNS Cookie, will receive a BADCOOKIE response including a newly
generated DNS Cookie, allowing clients to retry with that DNS Cookie.
The \fIallow_cookie\fR action will also accept requests over stateful
transports, regardless of the presence of an DNS Cookie and regardless of the
\fBanswer\-cookie\fR setting.
If \fBip\-ratelimit\fR is used, clients with a valid DNS Cookie will bypass the
ratelimit.
If a ratelimit for such clients is still needed, \fBip\-ratelimit\-cookie\fR
can be used instead.
.IP
By default only localhost is \fIallow\fRed, the rest is \fIrefuse\fRd.
The default is \fIrefuse\fRd, because that is protocol\-friendly. The DNS
protocol is not designed to handle dropped packets due to policy, and
@ -1806,11 +1820,27 @@ A value of 0 will disable ratelimiting for domain names that end in this name.
.TP 5
.B ip\-ratelimit: \fI<number or 0>
Enable global ratelimiting of queries accepted per IP address.
If 0, the default, it is disabled. This option is experimental at this time.
This option is experimental at this time.
The ratelimit is in queries per second that are allowed. More queries are
completely dropped and will not receive a reply, SERVFAIL or otherwise.
IP ratelimiting happens before looking in the cache. This may be useful for
mitigating amplification attacks.
Default is 0 (disabled).
.TP 5
.B ip\-ratelimit\-cookie: \fI<number or 0>
Enable global ratelimiting of queries accepted per IP address with a valid DNS
Cookie.
This option is experimental at this time.
The ratelimit is in queries per second that are allowed.
More queries are completely dropped and will not receive a reply, SERVFAIL or
otherwise.
IP ratelimiting happens before looking in the cache.
This option could be useful in combination with \fIallow_cookie\fR in an
attempt to mitigate other amplification attacks than UDP reflections (e.g.,
attacks targeting Unbound itself) which are already handled with DNS Cookies.
If used, the value is suggested to be higher than \fBip\-ratelimit\fR e.g.,
tenfold.
Default is 0 (disabled).
.TP 5
.B ip\-ratelimit\-size: \fI<memory size>
Give the size of the data structure in which the current ongoing rates are
@ -1879,6 +1909,18 @@ Set the number of servers that should be used for fast server selection. Only
use the fastest specified number of servers with the fast\-server\-permil
option, that turns this on or off. The default is to use the fastest 3 servers.
.TP 5
.B answer\-cookie: \fI<yes or no>
If enabled, Unbound will answer to requests containing DNS Cookies as
specified in RFC 7873 and RFC 9018.
Default is no.
.TP 5
.B cookie\-secret: \fI<128 bit hex string>
Server's secret for DNS Cookie generation.
Useful to explicitly set for servers in an anycast deployment that need to
share the secret in order to verify each other's Server Cookies.
An example hex string would be "000102030405060708090a0b0c0d0e0f".
Default is a 128 bits random secret generated at startup time.
.TP 5
.B edns\-client\-string: \fI<IP netblock> <string>
Include an EDNS0 option containing configured ascii string in queries with
destination address matching the configured IP netblock. This configuration

View file

@ -603,6 +603,8 @@ setup_qinfo_edns(struct libworker* w, struct ctx_query* q,
edns->opt_list_out = NULL;
edns->opt_list_inplace_cb_out = NULL;
edns->padding_block_size = 0;
edns->cookie_present = 0;
edns->cookie_valid = 0;
if(sldns_buffer_capacity(w->back->udp_buff) < 65535)
edns->udp_size = (uint16_t)sldns_buffer_capacity(
w->back->udp_buff);

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

@ -5420,6 +5420,8 @@ xfr_transfer_lookup_host(struct auth_xfer* xfr, struct module_env* env)
edns.opt_list_out = NULL;
edns.opt_list_inplace_cb_out = NULL;
edns.padding_block_size = 0;
edns.cookie_present = 0;
edns.cookie_valid = 0;
if(sldns_buffer_capacity(buf) < 65535)
edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
else edns.udp_size = 65535;
@ -6613,6 +6615,8 @@ xfr_probe_lookup_host(struct auth_xfer* xfr, struct module_env* env)
edns.opt_list_out = NULL;
edns.opt_list_inplace_cb_out = NULL;
edns.padding_block_size = 0;
edns.cookie_present = 0;
edns.cookie_valid = 0;
if(sldns_buffer_capacity(buf) < 65535)
edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
else edns.udp_size = 65535;

View file

@ -67,6 +67,11 @@ int infra_dp_ratelimit = 0;
* in queries per second. */
int infra_ip_ratelimit = 0;
/** ratelimit value for client ip addresses,
* in queries per second.
* For clients with a valid DNS Cookie. */
int infra_ip_ratelimit_cookie = 0;
size_t
infra_sizefunc(void* k, void* ATTR_UNUSED(d))
{
@ -1051,9 +1056,50 @@ infra_get_mem(struct infra_cache* infra)
return s;
}
/* Returns 1 if the limit has not been exceeded, 0 otherwise. */
static int
check_ip_ratelimit(struct sockaddr_storage* addr, socklen_t addrlen,
struct sldns_buffer* buffer, int premax, int max, int has_cookie)
{
int limit;
if(has_cookie) limit = infra_ip_ratelimit_cookie;
else limit = infra_ip_ratelimit;
/* Disabled */
if(limit == 0) return 1;
if(premax <= limit && max > limit) {
char client_ip[128], qnm[LDNS_MAX_DOMAINLEN+1+12+12];
addr_to_str(addr, addrlen, client_ip, sizeof(client_ip));
qnm[0]=0;
if(sldns_buffer_limit(buffer)>LDNS_HEADER_SIZE &&
LDNS_QDCOUNT(sldns_buffer_begin(buffer))!=0) {
(void)sldns_wire2str_rrquestion_buf(
sldns_buffer_at(buffer, LDNS_HEADER_SIZE),
sldns_buffer_limit(buffer)-LDNS_HEADER_SIZE,
qnm, sizeof(qnm));
if(strlen(qnm)>0 && qnm[strlen(qnm)-1]=='\n')
qnm[strlen(qnm)-1] = 0; /*remove newline*/
if(strchr(qnm, '\t'))
*strchr(qnm, '\t') = ' ';
if(strchr(qnm, '\t'))
*strchr(qnm, '\t') = ' ';
verbose(VERB_OPS, "ip_ratelimit exceeded %s %d%s %s",
client_ip, limit,
has_cookie?"(cookie)":"", qnm);
} else {
verbose(VERB_OPS, "ip_ratelimit exceeded %s %d%s (no query name)",
client_ip, limit,
has_cookie?"(cookie)":"");
}
}
return (max <= limit);
}
int infra_ip_ratelimit_inc(struct infra_cache* infra,
struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow,
int backoff, struct sldns_buffer* buffer)
int has_cookie, int backoff, struct sldns_buffer* buffer)
{
int max;
struct lruhash_entry* entry;
@ -1070,31 +1116,8 @@ int infra_ip_ratelimit_inc(struct infra_cache* infra,
(*cur)++;
max = infra_rate_max(entry->data, timenow, backoff);
lock_rw_unlock(&entry->lock);
if(premax <= infra_ip_ratelimit && max > infra_ip_ratelimit) {
char client_ip[128], qnm[LDNS_MAX_DOMAINLEN+1+12+12];
addr_to_str(addr, addrlen, client_ip, sizeof(client_ip));
qnm[0]=0;
if(sldns_buffer_limit(buffer)>LDNS_HEADER_SIZE &&
LDNS_QDCOUNT(sldns_buffer_begin(buffer))!=0) {
(void)sldns_wire2str_rrquestion_buf(
sldns_buffer_at(buffer, LDNS_HEADER_SIZE),
sldns_buffer_limit(buffer)-LDNS_HEADER_SIZE,
qnm, sizeof(qnm));
if(strlen(qnm)>0 && qnm[strlen(qnm)-1]=='\n')
qnm[strlen(qnm)-1] = 0; /*remove newline*/
if(strchr(qnm, '\t'))
*strchr(qnm, '\t') = ' ';
if(strchr(qnm, '\t'))
*strchr(qnm, '\t') = ' ';
verbose(VERB_OPS, "ip_ratelimit exceeded %s %d %s",
client_ip, infra_ip_ratelimit, qnm);
} else {
verbose(VERB_OPS, "ip_ratelimit exceeded %s %d (no query name)",
client_ip, infra_ip_ratelimit);
}
}
return (max <= infra_ip_ratelimit);
return check_ip_ratelimit(addr, addrlen, buffer, premax, max,
has_cookie);
}
/* create */

View file

@ -153,6 +153,8 @@ struct rate_key {
/** ip ratelimit, 0 is off */
extern int infra_ip_ratelimit;
/** ip ratelimit for DNS Cookie clients, 0 is off */
extern int infra_ip_ratelimit_cookie;
/**
* key for ip_ratelimit lookups, a source IP.
@ -419,13 +421,14 @@ int infra_find_ratelimit(struct infra_cache* infra, uint8_t* name,
* @param addr: client address
* @param addrlen: client address length
* @param timenow: what time it is now.
* @param has_cookie: if the request came with a DNS Cookie.
* @param backoff: if backoff is enabled.
* @param buffer: with query for logging.
* @return 1 if it could be incremented. 0 if the increment overshot the
* ratelimit and the query should be dropped. */
int infra_ip_ratelimit_inc(struct infra_cache* infra,
struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow,
int backoff, struct sldns_buffer* buffer);
int has_cookie, int backoff, struct sldns_buffer* buffer);
/**
* Get memory used by the infra cache.

View file

@ -433,6 +433,7 @@ enum sldns_enum_edns_option
LDNS_EDNS_DHU = 6, /* RFC6975 */
LDNS_EDNS_N3U = 7, /* RFC6975 */
LDNS_EDNS_CLIENT_SUBNET = 8, /* RFC7871 */
LDNS_EDNS_COOKIE = 10, /* RFC7873 */
LDNS_EDNS_KEEPALIVE = 11, /* draft-ietf-dnsop-edns-tcp-keepalive*/
LDNS_EDNS_PADDING = 12, /* RFC7830 */
LDNS_EDNS_EDE = 15, /* RFC8914 */
@ -483,6 +484,9 @@ typedef enum sldns_enum_ede_code sldns_ede_code;
#define LDNS_TSIG_ERROR_BADNAME 20
#define LDNS_TSIG_ERROR_BADALG 21
/** DNS Cookie extended rcode */
#define LDNS_EXT_RCODE_BADCOOKIE 23
/**
* Contains all information about resource record types.
*

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

@ -1252,6 +1252,8 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet,
if(dnssec)
edns.bits = EDNS_DO;
edns.padding_block_size = 0;
edns.cookie_present = 0;
edns.cookie_valid = 0;
edns.opt_list_in = NULL;
edns.opt_list_out = per_upstream_opt_list;
edns.opt_list_inplace_cb_out = NULL;

View file

@ -21,7 +21,6 @@
*/
#include "config.h"
struct sockaddr_storage;
#include <errno.h>
#include <stdarg.h>
#include <ctype.h>
@ -140,6 +139,10 @@ 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, "client_cookie")) {
e->match_client_cookie = 1;
} else if(str_keyword(&parse, "server_cookie")) {
e->match_server_cookie = 1;
} else if(str_keyword(&parse, "UDP")) {
e->match_transport = transport_udp;
} else if(str_keyword(&parse, "TCP")) {
@ -905,37 +908,64 @@ get_do_flag(uint8_t* pkt, size_t len)
return (int)(edns_bits&LDNS_EDNS_MASK_DO_BIT);
}
/** Snips the EDE option out of the OPT record and returns the EDNS EDE
* INFO-CODE if found, else -1 */
/** Snips the specified EDNS option out of the OPT record and puts it in the
* provided buffer. The buffer should be able to hold any opt data ie 65535.
* Returns the length of the option written,
* or 0 if not found, else -1 on error. */
static int
extract_ede(uint8_t* pkt, size_t len)
pkt_snip_edns_option(uint8_t* pkt, size_t len, sldns_edns_option code,
uint8_t* buf)
{
uint8_t *rdata, *opt_position = pkt;
uint16_t rdlen, optlen;
size_t remaining = len;
int ede_code;
if(!pkt_find_edns_opt(&opt_position, &remaining)) return -1;
if(!pkt_find_edns_opt(&opt_position, &remaining)) return 0;
if(remaining < 8) return -1; /* malformed */
rdlen = sldns_read_uint16(opt_position+6);
rdata = opt_position + 8;
while(rdlen > 0) {
if(rdlen < 4) return -1; /* malformed */
optlen = sldns_read_uint16(rdata+2);
if(sldns_read_uint16(rdata) == LDNS_EDNS_EDE) {
if(rdlen < 6) return -1; /* malformed */
ede_code = sldns_read_uint16(rdata+4);
if(sldns_read_uint16(rdata) == code) {
/* save data to buf for caller inspection */
memmove(buf, rdata+4, optlen);
/* snip option from packet; assumes len is correct */
memmove(rdata, rdata+4+optlen,
(pkt+len)-(rdata+4+optlen));
/* update OPT size */
sldns_write_uint16(opt_position+6,
sldns_read_uint16(opt_position+6)-(4+optlen));
return ede_code;
return optlen;
}
rdlen -= 4 + optlen;
rdata += 4 + optlen;
}
return -1;
return 0;
}
/** Snips the EDE option out of the OPT record and returns the EDNS EDE
* INFO-CODE if found, else -1 */
static int
extract_ede(uint8_t* pkt, size_t len)
{
uint8_t buf[65535];
int buflen = pkt_snip_edns_option(pkt, len, LDNS_EDNS_EDE, buf);
if(buflen < 2 /*ede without text at minimum*/) return -1;
return sldns_read_uint16(buf);
}
/** Snips the DNS Cookie option out of the OPT record and puts it in the
* provided cookie buffer (should be at least 24 octets).
* Returns the length of the cookie if found, else -1. */
static int
extract_cookie(uint8_t* pkt, size_t len, uint8_t* cookie)
{
uint8_t buf[65535];
int buflen = pkt_snip_edns_option(pkt, len, LDNS_EDNS_COOKIE, buf);
if(buflen != 8 /*client cookie*/ &&
buflen != 8 + 16 /*server cookie*/) return -1;
memcpy(cookie, buf, buflen);
return buflen;
}
/** zero TTLs in packet */
@ -1530,6 +1560,27 @@ find_match(struct entry* entries, uint8_t* query_pkt, size_t len,
continue;
}
}
/* Cookies could also modify the query_pkt; keep them early */
if(p->match_client_cookie || p->match_server_cookie) {
uint8_t cookie[24];
int cookie_len = extract_cookie(query_pkt, len,
cookie);
if(cookie_len == -1) {
verbose(3, "bad DNS Cookie. "
"Expected but not found\n");
continue;
} else if(p->match_client_cookie &&
cookie_len != 8) {
verbose(3, "bad DNS Cookie. Expected client "
"cookie of length 8.");
continue;
} else if((p->match_server_cookie) &&
cookie_len != 24) {
verbose(3, "bad DNS Cookie. Expected server "
"cookie of length 24.");
continue;
}
}
if(p->match_opcode && get_opcode(query_pkt, len) !=
get_opcode(reply, rlen)) {
verbose(3, "bad opcode\n");

View file

@ -64,6 +64,14 @@ struct sldns_file_parse_state;
; 'ede=any' makes the query match any EDNS EDE info-code.
; It also snips the EDE record out of the packet to facilitate
; other matches.
; 'client_cookie' makes the query match any DNS Cookie option with
; with a length of 8 octets.
; It also snips the DNS Cookie record out of the packet to
; facilitate other matches.
; 'server_cookie' makes the query match any DNS Cookie option with
; with a length of 24 octets.
; It also snips the DNS Cookie record out of the packet to
; facilitate other matches.
MATCH [opcode] [qtype] [qname] [serial=<value>] [all] [ttl]
MATCH [UDP|TCP] DO
MATCH ...
@ -104,11 +112,11 @@ struct sldns_file_parse_state;
; be parsed, ADJUST rules for the answer packet
; are ignored. Only copy_id is done.
HEX_ANSWER_END
HEX_EDNS_BEGIN ; follow with hex data.
HEX_EDNSDATA_BEGIN ; follow with hex data.
; Raw EDNS data to match against. It must be an
; exact match (all options are matched) and will be
; evaluated only when 'MATCH ednsdata' given.
HEX_EDNS_END
HEX_EDNSDATA_END
ENTRY_END
@ -214,6 +222,10 @@ struct entry {
uint8_t match_noedns;
/** match edns data field given in hex */
uint8_t match_ednsdata_raw;
/** match an DNS cookie of length 8 */
uint8_t match_client_cookie;
/** match an DNS cookie of length 24 */
uint8_t match_server_cookie;
/** match query serial with this value. */
uint32_t ixfr_soa_serial;
/** match on UDP/TCP */
@ -235,7 +247,7 @@ struct entry {
/** increment the ECS scope copied from the sourcemask by one */
uint8_t increment_ecs_scope;
/** in seconds */
unsigned int sleeptime;
unsigned int sleeptime;
/** some number that names this entry, line number in file or so */
int lineno;

View file

@ -530,6 +530,207 @@ infra_test(void)
config_delete(cfg);
}
#include "util/edns.h"
/* Complete version-invalid client cookie; needs a new one.
* Based on edns_cookie_rfc9018_a2 */
static void
edns_cookie_invalid_version(void)
{
uint32_t timestamp = 1559734385;
uint8_t client_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
0x99, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0x9f, 0x11,
0x1f, 0x81, 0x30, 0xc3, 0xee, 0xe2, 0x94, 0x80 };
uint8_t server_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
0x01, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0xa8, 0x71,
0xd4, 0xa5, 0x64, 0xa1, 0x44, 0x2a, 0xca, 0x77 };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, client_cookie, 8 + 4 + 4);
/* copy ip 198.51.100.100 */
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) == COOKIE_STATUS_INVALID);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/* Complete hash-invalid client cookie; needs a new one. */
static void
edns_cookie_invalid_hash(void)
{
uint32_t timestamp = 0;
uint8_t client_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x32, 0xF2, 0x43, 0xB9, 0xBC, 0xFE, 0xC4, 0x06 };
uint8_t server_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0xBA, 0x0D, 0x82, 0x90, 0x8F, 0xAA, 0xEB, 0xBD };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, client_cookie, 8 + 4 + 4);
/* copy ip 203.0.113.203 */
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) == COOKIE_STATUS_INVALID);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/* Complete hash-valid client cookie; more than 30 minutes old; needs a
* refreshed server cookie.
* A slightly better variation of edns_cookie_rfc9018_a3 for Unbound to check
* that RESERVED bits do not influence cookie validation. */
static void
edns_cookie_rfc9018_a3_better(void)
{
uint32_t timestamp = 1800 + 1;
uint8_t client_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0xab, 0xcd, 0xef,
0x00, 0x00, 0x00, 0x00,
0x32, 0xF2, 0x43, 0xB9, 0xBC, 0xFE, 0xC4, 0x06 };
uint8_t server_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0x09,
0x62, 0xD5, 0x93, 0x09, 0x14, 0x5C, 0x23, 0x9D };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, client_cookie, 8 + 4 + 4);
/* copy ip 203.0.113.203 */
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) == 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 (expired);
* needs a refreshed server cookie. */
static void
edns_cookie_rfc9018_a3(void)
{
uint32_t timestamp = 1559734700;
uint8_t client_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0xab, 0xcd, 0xef,
0x5c, 0xf7, 0x8f, 0x71,
0xa3, 0x14, 0x22, 0x7b, 0x66, 0x79, 0xeb, 0xf5 };
uint8_t server_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0xa9, 0xac,
0xf7, 0x3a, 0x78, 0x10, 0xac, 0xa2, 0x38, 0x1e };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, client_cookie, 8 + 4 + 4);
/* copy ip 203.0.113.203 */
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) == COOKIE_STATUS_EXPIRED);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/* Complete hash-valid client cookie; more than 30 minutes old; needs a
* refreshed server cookie. */
static void
edns_cookie_rfc9018_a2(void)
{
uint32_t timestamp = 1559734385;
uint8_t client_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
0x01, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0x9f, 0x11,
0x1f, 0x81, 0x30, 0xc3, 0xee, 0xe2, 0x94, 0x80 };
uint8_t server_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
0x01, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0xa8, 0x71,
0xd4, 0xa5, 0x64, 0xa1, 0x44, 0x2a, 0xca, 0x77 };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, client_cookie, 8 + 4 + 4);
/* copy ip 198.51.100.100 */
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) == COOKIE_STATUS_VALID_RENEW);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/* Only client cookie; needs a complete server cookie. */
static void
edns_cookie_rfc9018_a1(void)
{
uint32_t timestamp = 1559731985;
uint8_t client_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57 };
uint8_t server_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
0x01, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0x9f, 0x11,
0x1f, 0x81, 0x30, 0xc3, 0xee, 0xe2, 0x94, 0x80 };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, server_cookie, 8 + 4 + 4);
/* copy ip 198.51.100.100 */
memcpy(buf + 16, "\306\063\144\144", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie),
/* these will not be used; it will return invalid
* because of the size. */
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);
}
/** test interoperable DNS cookies (RFC9018) */
static void
edns_cookie_test(void)
{
unit_show_feature("interoperable dns cookies");
/* Check RFC9018 appendix test vectors */
edns_cookie_rfc9018_a1();
edns_cookie_rfc9018_a2();
edns_cookie_rfc9018_a3();
/* More tests */
edns_cookie_rfc9018_a3_better();
edns_cookie_invalid_hash();
edns_cookie_invalid_version();
}
#include "util/random.h"
/** test randomness */
static void
@ -915,8 +1116,8 @@ static void edns_ede_encode_encodedecode(struct query_info* qinfo,
(void)query_dname_len(pkt);
sldns_buffer_skip(pkt, 2 + 2);
/* decode */
unit_assert(
parse_edns_from_query_pkt(pkt, edns, NULL, NULL, region)==0);
unit_assert(parse_edns_from_query_pkt(pkt, edns, NULL, NULL, NULL, 0,
region) == 0);
}
static void edns_ede_encode_check(struct edns_data* edns, int* found_ede,
@ -1119,6 +1320,7 @@ main(int argc, char* argv[])
slabhash_test();
infra_test();
ldns_test();
edns_cookie_test();
zonemd_test();
tcpreuse_test();
msgparse_test();

View file

@ -34,6 +34,7 @@ fgrep -v -e "ldns-src/" hlist > ilist; mv ilist hlist
fgrep -v -e "libunbound/python/libunbound_wrap.c" hlist > ilist; mv ilist hlist
fgrep -v -e "pythonmod/interface.h" hlist > ilist; mv ilist hlist
fgrep -v -e "dnstap" hlist > ilist; mv ilist hlist
fgrep -v -e "util/siphash.c" hlist > ilist; mv ilist hlist
# filter out compat
fgrep -v -e "compat/" hlist > ilist; mv ilist hlist
for h in `cat hlist`; do

235
testdata/edns_downstream_cookies.rpl vendored Normal file
View file

@ -0,0 +1,235 @@
; config options
server:
answer-cookie: yes
cookie-secret: "000102030405060708090a0b0c0d0e0f"
access-control: 127.0.0.1 allow_cookie
access-control: 1.2.3.4 allow
local-data: "test. TXT test"
CONFIG_END
SCENARIO_BEGIN Test downstream DNS Cookies
; Note: When a valid hash was required, it was generated by running this test
; with an invalid one and checking the output for the valid one.
; Actual hash generation is tested with unit tests.
; Query without a client cookie ...
STEP 0 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
test. IN TXT
ENTRY_END
; ... get TC and refused
STEP 1 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
REPLY QR RD RA TC REFUSED
SECTION QUESTION
test. IN TXT
ENTRY_END
; Query without a client cookie on TCP ...
STEP 10 QUERY
ENTRY_BEGIN
REPLY RD
MATCH TCP
SECTION QUESTION
test. IN TXT
ENTRY_END
; ... get an answer
STEP 11 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
REPLY QR RD RA AA NOERROR
SECTION QUESTION
test. IN TXT
SECTION ANSWER
test. IN TXT "test"
ENTRY_END
; Query with only a client cookie ...
STEP 20 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
test. IN TXT
SECTION ADDITIONAL
HEX_EDNSDATA_BEGIN
00 0a ; Opcode 10
00 08 ; Length 8
31 32 33 34 35 36 37 38 ; Random bits
HEX_EDNSDATA_END
ENTRY_END
; ... get BADCOOKIE and a new cookie
STEP 21 CHECK_ANSWER
ENTRY_BEGIN
MATCH all server_cookie
REPLY QR RD RA DO YXRRSET ; BADCOOKIE is an extended rcode
SECTION QUESTION
test. IN TXT
ENTRY_END
; Query with an invalid cookie ...
STEP 30 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
test. IN TXT
SECTION ADDITIONAL
HEX_EDNSDATA_BEGIN
00 0a ; Opcode 10
00 18 ; Length 24
31 32 33 34 35 36 37 38 ; Random bits
02 00 00 00 ; wrong version
00 00 00 00 ; Timestamp
31 32 33 34 35 36 37 38 ; wrong hash
HEX_EDNSDATA_END
ENTRY_END
; ... get BADCOOKIE and a new cookie
STEP 31 CHECK_ANSWER
ENTRY_BEGIN
MATCH all server_cookie
REPLY QR RD RA DO YXRRSET ; BADCOOKIE is an extended rcode
SECTION QUESTION
test. IN TXT
ENTRY_END
; Query with an invalid cookie from a non-cookie protected address ...
STEP 40 QUERY ADDRESS 1.2.3.4
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
test. IN TXT
SECTION ADDITIONAL
HEX_EDNSDATA_BEGIN
00 0a ; Opcode 10
00 18 ; Length 24
31 32 33 34 35 36 37 38 ; Random bits
02 00 00 00 ; wrong version
00 00 00 00 ; Timestamp
31 32 33 34 35 36 37 38 ; wrong hash
HEX_EDNSDATA_END
ENTRY_END
; ... get answer and a cookie
STEP 41 CHECK_ANSWER
ENTRY_BEGIN
MATCH all server_cookie
REPLY QR RD RA AA DO NOERROR
SECTION QUESTION
test. IN TXT
SECTION ANSWER
test. IN TXT "test"
ENTRY_END
; Query with a valid cookie ...
STEP 50 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
test. IN TXT
SECTION ADDITIONAL
HEX_EDNSDATA_BEGIN
00 0a ; Opcode 10
00 18 ; Length 24
31 32 33 34 35 36 37 38 ; Random bits
01 00 00 00 ; Version/Reserved
00 00 00 00 ; Timestamp
38 52 7b a8 c6 a4 ea 96 ; Hash
HEX_EDNSDATA_END
ENTRY_END
; ... get answer and the cookie
STEP 51 CHECK_ANSWER
ENTRY_BEGIN
MATCH all server_cookie
REPLY QR RD RA AA DO NOERROR
SECTION QUESTION
test. IN TXT
SECTION ANSWER
test. IN TXT "test"
ENTRY_END
; Query with a valid >30 minutes old cookie ...
STEP 59 TIME_PASSES ELAPSE 1801
STEP 60 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
test. IN TXT
SECTION ADDITIONAL
HEX_EDNSDATA_BEGIN
00 0a ; Opcode 10
00 18 ; Length 24
31 32 33 34 35 36 37 38 ; Random bits
01 00 00 00 ; Version/Reserved
00 00 00 00 ; Timestamp
38 52 7b a8 c6 a4 ea 96 ; Hash
HEX_EDNSDATA_END
ENTRY_END
; ... Get answer and a refreshed cookie
; (we don't check the re-freshness here; it has its own unit test)
STEP 61 CHECK_ANSWER
ENTRY_BEGIN
MATCH all server_cookie
REPLY QR RD RA AA DO NOERROR
SECTION QUESTION
test. IN TXT
SECTION ANSWER
test. IN TXT "test"
ENTRY_END
; Query with a hash-valid >60 minutes old cookie ...
STEP 69 TIME_PASSES ELAPSE 3601
STEP 70 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
test. IN TXT
SECTION ADDITIONAL
HEX_EDNSDATA_BEGIN
00 0a ; Opcode 10
00 18 ; Length 24
31 32 33 34 35 36 37 38 ; Random bits
01 00 00 00 ; Version/Reserved
00 00 07 09 ; Timestamp (1801)
77 81 38 e3 8f aa 72 86 ; Hash
HEX_EDNSDATA_END
ENTRY_END
; ... get BADCOOKIE and a new cookie
STEP 71 CHECK_ANSWER
ENTRY_BEGIN
MATCH all server_cookie
REPLY QR RD RA DO YXRRSET ; BADCOOKIE is an extended rcode
SECTION QUESTION
test. IN TXT
ENTRY_END
; Query with a valid future (<5 minutes) cookie ...
STEP 80 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
test. IN TXT
SECTION ADDITIONAL
HEX_EDNSDATA_BEGIN
00 0a ; Opcode 10
00 18 ; Length 24
31 32 33 34 35 36 37 38 ; Random bits
01 00 00 00 ; Version/Reserved
00 00 16 45 ; Timestamp (1801 + 3601 + 299)
4a f5 0f df f0 e8 c7 09 ; Hash
HEX_EDNSDATA_END
ENTRY_END
; ... get an answer
STEP 81 CHECK_ANSWER
ENTRY_BEGIN
MATCH all server_cookie
REPLY QR RD RA AA DO NOERROR
SECTION QUESTION
test. IN TXT
SECTION ANSWER
test. IN TXT "test"
ENTRY_END
SCENARIO_END

View file

@ -0,0 +1,28 @@
server:
verbosity: 5
# num-threads: 1
interface: 127.0.0.1
port: @PORT@
use-syslog: no
directory: .
pidfile: "unbound.pid"
chroot: ""
username: ""
local-data: "test. IN TXT localdata"
ip-ratelimit: 1
ip-ratelimit-cookie: 0
ip-ratelimit-factor: 0
ip-ratelimit-backoff: yes
answer-cookie: yes
access-control: 127.0.0.0/8 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

@ -0,0 +1,16 @@
BaseName: ip_ratelimit
Version: 1.0
Description: Test IP source ratelimit.
CreationDate: Tue Aug 8 00:00:00 CET 2023
Maintainer: Yorgos Thessalonikefs
Category:
Component:
CmdDepends:
Depends:
Help:
Pre: ip_ratelimit.pre
Post: ip_ratelimit.post
Test: ip_ratelimit.test
AuxFiles:
Passed:
Failure:

View file

@ -0,0 +1,13 @@
# #-- ip_ratelimit.post --#
# source the master var file when it's there
[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
# source the test var file when it's there
[ -f .tpkg.var.test ] && source .tpkg.var.test
#
# do your teardown here
. ../common.sh
kill_pid $UNBOUND_PID
if test -f unbound.log; then
echo ">>> unbound log"
cat unbound.log
fi

View file

@ -0,0 +1,24 @@
# #-- ip_ratelimit.pre--#
# source the master var file when it's there
[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
# use .tpkg.var.test for in test variable passing
[ -f .tpkg.var.test ] && source .tpkg.var.test
PRE="../.."
. ../common.sh
get_random_port 2
UNBOUND_PORT=$RND_PORT
CONTROL_PORT=$(($RND_PORT + 1))
echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
echo "CONTROL_PORT=$CONTROL_PORT" >> .tpkg.var.test
# make config file
sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@CONTROL_PORT\@/'$CONTROL_PORT'/' < ip_ratelimit.conf > ub.conf
# start unbound in the background
$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
UNBOUND_PID=$!
echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
wait_unbound_up unbound.log
cat .tpkg.var.test

View file

@ -0,0 +1,159 @@
# #-- ip_ratelimit.test --#
# source the master var file when it's there
[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
# use .tpkg.var.test for in test variable passing
[ -f .tpkg.var.test ] && source .tpkg.var.test
PRE="../.."
. ../common.sh
get_make
(cd $PRE; $MAKE streamtcp)
# These tests rely on second time precision. To combat false negatives the
# tests run multiple times and we allow 1/3 of the runs to fail.
total_runs=6
success_threshold=4 # 2/3*total_runs
echo "> First get a valid cookie"
dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:0102030405060708 +tcp +retry=0 +time=1 test. TXT >outfile 2>&1
if test "$?" -ne 0; then
echo "exit status not OK"
echo "> cat logfiles"
cat outfile
cat unbound.log
echo "Not OK"
exit 1
fi
if test `grep "COOKIE: " outfile | wc -l` -ne 1; then
echo "Could not get cookie"
echo "> cat logfiles"
cat outfile
cat unbound.log
echo "Not OK"
exit 1
fi
cookie=`grep "COOKIE: " outfile | cut -d ' ' -f 3`
successes=0
echo "> Three parallel queries with backoff and cookie"
# For this test we send three parallel queries. The ratelimit should be reached
# for that second. We send a query to verify that there is no reply.
# Then for the next second we again send three parallel queries and we expect
# none of them to be allowed through because of the backoff logic that keeps
# rolling the RATE_WINDOW based on demand.
# Again we send another query but with a valid cookie and we expect to receive
# an answer.
for i in $(seq 1 $total_runs); do
# Try to hit limit
$PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
if test "$?" -ne 0; then
echo "exit status not OK"
echo "> cat logfiles"
cat outfile
cat unbound.log
echo "Not OK"
exit 1
fi
# Expect no answer because of limit
dig @127.0.0.1 -p $UNBOUND_PORT +retry=0 +time=1 test. TXT >outfile 2>&1
if test "$?" -eq 0; then
continue
fi
# Try to keep limit
$PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
if test "$?" -ne 0; then
echo "exit status not OK"
echo "> cat logfiles"
cat outfile
cat unbound.log
echo "Not OK"
exit 1
fi
# Expect answer because of DNS cookie
dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:$cookie +retry=0 +time=1 test. TXT >outfile 2>&1
if test "$?" -ne 0; then
continue
fi
((successes++))
# We don't have to wait for all the runs to complete if we know
# we passed the threshold.
if test $successes -ge $success_threshold; then
break
fi
done
if test $successes -ge $success_threshold; then
echo "Three parallel queries with backoff and cookie OK"
else
echo "Three parallel queries with backoff and cookie NOT OK"
echo "> cat logfiles"
cat outfile
cat unbound.log
echo "Three parallel queries with backoff and cookie NOT OK"
exit 1
fi
echo "> Activating ip-ratelimit-cookie"
echo "$PRE/unbound-control -c ub.conf set_option ip-ratelimit-cookie: 1"
$PRE/unbound-control -c ub.conf set_option ip-ratelimit-cookie: 1
if test $? -ne 0; then
echo "wrong exit value after success"
exit 1
fi
successes=0
echo "> Three parallel queries with backoff and cookie with ip-ratelimit-cookie"
# This is the exact same test as above with the exception that we don't expect
# an answer on the last query because ip-ratelimit-cookie is now enabled.
for i in $(seq 1 $total_runs); do
# Try to hit limit
$PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
if test "$?" -ne 0; then
echo "exit status not OK"
echo "> cat logfiles"
cat outfile
cat unbound.log
echo "Not OK"
exit 1
fi
# Expect no answer because of limit
dig @127.0.0.1 -p $UNBOUND_PORT +retry=0 +time=1 test. TXT >outfile 2>&1
if test "$?" -eq 0; then
continue
fi
# Try to keep limit
$PRE/streamtcp -nu -f 127.0.0.1@$UNBOUND_PORT test. TXT IN test. TXT IN test. TXT IN >outfile 2>&1
if test "$?" -ne 0; then
echo "exit status not OK"
echo "> cat logfiles"
cat outfile
cat unbound.log
echo "Not OK"
exit 1
fi
# Expect no answer because of ip-ratelimit-cookie
dig @127.0.0.1 -p $UNBOUND_PORT +ednsopt=10:$cookie +retry=0 +time=1 test. TXT >outfile 2>&1
if test "$?" -eq 0; then
continue
fi
((successes++))
# We don't have to wait for all the runs to complete if we know
# we passed the threshold.
if test $successes -ge $success_threshold; then
break
fi
done
if test $successes -ge $success_threshold; then
echo "Three parallel queries with backoff and cookie with ip-ratelimit-cookie OK"
else
echo "Three parallel queries with backoff and cookie with ip-ratelimit-cookie NOT OK"
echo "> cat logfiles"
cat outfile
cat unbound.log
echo "Three parallel queries with backoff and cookie with ip-ratelimit-cookie NOT OK"
exit 1
fi
exit 0

View file

@ -0,0 +1,39 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDszCCAhsCFGD5193whHQ2bVdzbaQfdf1gc4SkMA0GCSqGSIb3DQEBCwUAMBIx
EDAOBgNVBAMMB3VuYm91bmQwHhcNMjAwNzA4MTMzMjMwWhcNNDAwMzI1MTMzMjMw
WjAaMRgwFgYDVQQDDA91bmJvdW5kLWNvbnRyb2wwggGiMA0GCSqGSIb3DQEBAQUA
A4IBjwAwggGKAoIBgQCy0Sn4/KHxcau1nvsDgWFiO9t4Hd0xrtDasZbGhvOUD2mv
OEDVoKEC9S0I4C4z6sHo1M0HxhZ9kltAdrDIw2mYLvtyDq9ctgIZRAdnICqJ03Tj
1EkXlPuPg3xWeK/XsuJF2vRLqzDLg9G8Scg89XjcRAdmoLmNW8ewqSEh+YEX8OPl
NdHKl5rBZX+bmN5Tr/gdFgx/K0Fl07WY9e5YuHSWBtDhIJuCs5RUPeueIyTXJkht
A6a+UMdip6SMNv+85bYrFRAsbThpkwnWZZ91vAbQIpCRKMhIQNnWXvq+EzqZMzR/
Bdsi/IVn3I88KV7WYXlpKXFLZyYa4ZVJFBrDxcX197dr0AIklOvfcd1KnJpmQpEB
TMtwqosKXfbd1oq/c3DABPTdOPiJAzjXoBIyiLJ8Vr/YZZMpBWN127wKtqvu0Zp/
nGuWKDgMGasG91gDeePLvb9EPvWEvCaVGgIZTt1G4Ov07ooBJSbVGL3U41n+p2My
+i/XmXa+PgZX4pdHTR0CAwEAATANBgkqhkiG9w0BAQsFAAOCAYEAd++Wen6l8Ifj
4h3p/y16PhSsWJWuJ4wdNYy3/GM84S26wGjzlEEwiW76HpH6VJzPOiBAeWnFKE83
hFyetEIxgJeIPbcs9ZP/Uoh8GZH9tRISBSN9Hgk2Slr9llo4t1H0g/XTgA5HqMQU
9YydlBh43G7Vw3FVwh09OM6poNOGQKNc/tq2/QdKeUMtyBbLWpRmjH5XcCT35fbn
ZiVOUldqSHD4kKrFO4nJYXZyipRbcXybsLiX9GP0GLemc3IgIvOXyJ2RPp06o/SJ
pzlMlkcAfLJaSuEW57xRakhuNK7m051TKKzJzIEX+NFYOVdafFHS8VwGrYsdrFvD
72tMfu+Fu55y3awdWWGc6YlaGogZiuMnJkvQphwgn+5qE/7CGEckoKEsH601rqIZ
muaIc85+nEcHJeijd/ZlBN9zeltjFoMuqTUENgmv8+tUAdVm/UMY9Vjme6b43ydP
uv6DS02+k9z8toxXworLiPr94BGaiGV1NxgwZKLZigYJt/Fi2Qte
-----END CERTIFICATE-----

View file

@ -0,0 +1,39 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDqzCCAhMCFBHWXeQ6ZIa9QcQbXLFfC6tj+KA+MA0GCSqGSIb3DQEBCwUAMBIx
EDAOBgNVBAMMB3VuYm91bmQwHhcNMjAwNzA4MTMzMjI5WhcNNDAwMzI1MTMzMjI5
WjASMRAwDgYDVQQDDAd1bmJvdW5kMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
igKCAYEAvjSVSN2QMXudpzukdLCqgg/IOhCX8KYkD0FFFfWcQjgKq5wI0x41iG32
a6wbGanre4IX7VxaSPu9kkHfnGgynCk5nwDRedE/FLFhAU78PoT0+NqqGRS7XVQ2
4vLmIz9Hqc2Ozx1um1BXBTmIT0UfN2e22I0LWQ6a3seZlEDRj45gnk7Zuh9MDgot
aBdm+v1JAbupSf6Zis4VEH3JNdvVGE3O1DHEIeuuz/3BDhpf6WBDH+8KWaBe1ca4
TZHr9ThL2gEMEfAQl0wXDwRWRoi3NjNMH+mw0L1rjwThI5GXqNIee7o5FzUReSXZ
uTdFMyGe3Owcx+XoYnwi6cplSNoGsDBu4B9bKKglR9YleJVw4L4Xi8xPq6O9UPj4
+nypHk/DOoC7DIM3ufN0yxPBsFo5TVowxfhdjZXJbbftd2TZv7AH8+XLA5UoZgRz
XgzECelXSCTBFlMTnT48LfA9pMLydyjAz2UdPHs5Iv+TK5nnI+aJoeaP7kFZSngx
dy1+A/bNAgMBAAEwDQYJKoZIhvcNAQELBQADggGBABunf93MKaCUHiZgnoOTinsW
84/EgInrgtKzAyH+BhnKkJOhhR0kkIAx5d9BpDlaSiRTACFon9moWCgDIIsK/Ar7
JE0Kln9cV//wiiNoFU0O4mnzyGUIMvlaEX6QHMJJQYvL05+w/3AAcf5XmMJtR5ca
fJ8FqvGC34b2WxX9lTQoyT52sRt+1KnQikiMEnEyAdKktMG+MwKsFDdOwDXyZhZg
XZhRrfX3/NVJolqB6EahjWIGXDeKuSSKZVtCyib6LskyeMzN5lcRfvubKDdlqFVF
qlD7rHBsKhQUWK/IO64mGf7y/de+CgHtED5vDvr/p2uj/9sABATfbrOQR3W/Of25
sLBj4OEfrJ7lX8hQgFaxkMI3x6VFT3W8dTCp7xnQgb6bgROWB5fNEZ9jk/gjSRmD
yIU+r0UbKe5kBk/CmZVFXL2TyJ92V5NYEQh8V4DGy19qZ6u/XKYyNJL4ocs35GGe
CA8SBuyrmdhx38h1RHErR2Skzadi1S7MwGf1y431fQ==
-----END CERTIFICATE-----

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

@ -55,6 +55,7 @@
#include "util/regional.h"
#include "util/fptr_wlist.h"
#include "util/data/dname.h"
#include "util/random.h"
#include "util/rtt.h"
#include "services/cache/infra.h"
#include "sldns/wire2str.h"
@ -87,6 +88,9 @@ struct config_parser_state* cfg_parser = 0;
/** init ports possible for use */
static void init_outgoing_availports(int* array, int num);
/** init cookie with random data */
static void init_cookie_secret(uint8_t* cookie_secret, size_t cookie_secret_len);
struct config_file*
config_create(void)
{
@ -326,6 +330,7 @@ config_create(void)
cfg->dnstap_bidirectional = 1;
cfg->dnstap_tls = 1;
cfg->disable_dnssec_lame_check = 0;
cfg->ip_ratelimit_cookie = 0;
cfg->ip_ratelimit = 0;
cfg->ratelimit = 0;
cfg->ip_ratelimit_slabs = 4;
@ -369,6 +374,10 @@ config_create(void)
cfg->ipsecmod_whitelist = NULL;
cfg->ipsecmod_strict = 0;
#endif
cfg->do_answer_cookie = 0;
memset(cfg->cookie_secret, 0, sizeof(cfg->cookie_secret));
cfg->cookie_secret_len = 16;
init_cookie_secret(cfg->cookie_secret, cfg->cookie_secret_len);
#ifdef USE_CACHEDB
if(!(cfg->cachedb_backend = strdup("testframe"))) goto error_exit;
if(!(cfg->cachedb_secret = strdup("default"))) goto error_exit;
@ -771,6 +780,10 @@ int config_set_option(struct config_file* cfg, const char* opt,
else S_POW2("dnscrypt-nonce-cache-slabs:",
dnscrypt_nonce_cache_slabs)
#endif
else if(strcmp(opt, "ip-ratelimit-cookie:") == 0) {
IS_NUMBER_OR_ZERO; cfg->ip_ratelimit_cookie = atoi(val);
infra_ip_ratelimit_cookie=cfg->ip_ratelimit_cookie;
}
else if(strcmp(opt, "ip-ratelimit:") == 0) {
IS_NUMBER_OR_ZERO; cfg->ip_ratelimit = atoi(val);
infra_ip_ratelimit=cfg->ip_ratelimit;
@ -1240,6 +1253,7 @@ config_get_option(struct config_file* cfg, const char* opt,
else O_LST(opt, "python-script", python_script)
else O_LST(opt, "dynlib-file", dynlib_file)
else O_YNO(opt, "disable-dnssec-lame-check", disable_dnssec_lame_check)
else O_DEC(opt, "ip-ratelimit-cookie", ip_ratelimit_cookie)
else O_DEC(opt, "ip-ratelimit", ip_ratelimit)
else O_DEC(opt, "ratelimit", ratelimit)
else O_MEM(opt, "ip-ratelimit-size", ip_ratelimit_size)
@ -1685,6 +1699,20 @@ config_delete(struct config_file* cfg)
free(cfg);
}
static void
init_cookie_secret(uint8_t* cookie_secret, size_t cookie_secret_len)
{
struct ub_randstate *rand = ub_initstate(NULL);
if (!rand)
fatal_exit("could not init random generator");
while (cookie_secret_len) {
*cookie_secret++ = (uint8_t)ub_random(rand);
cookie_secret_len--;
}
ub_randfree(rand);
}
static void
init_outgoing_availports(int* a, int num)
{

View file

@ -590,6 +590,9 @@ struct config_file {
/** ratelimit for ip addresses. 0 is off, otherwise qps (unless overridden) */
int ip_ratelimit;
/** ratelimit for ip addresses with a valid DNS Cookie. 0 is off,
* otherwise qps (unless overridden) */
int ip_ratelimit_cookie;
/** number of slabs for ip_ratelimit cache */
size_t ip_ratelimit_slabs;
/** memory size in bytes for ip_ratelimit cache */
@ -711,6 +714,13 @@ struct config_file {
int redis_expire_records;
#endif
#endif
/** Downstream DNS Cookies */
/** do answer with server cookie when request contained cookie option */
int do_answer_cookie;
/** cookie secret */
uint8_t cookie_secret[40];
/** cookie secret length */
size_t cookie_secret_len;
/* ipset module */
#ifdef USE_IPSET

View file

@ -507,6 +507,7 @@ dnstap-log-forwarder-response-messages{COLON} {
YDVAR(1, VAR_DNSTAP_LOG_FORWARDER_RESPONSE_MESSAGES) }
disable-dnssec-lame-check{COLON} { YDVAR(1, VAR_DISABLE_DNSSEC_LAME_CHECK) }
ip-ratelimit{COLON} { YDVAR(1, VAR_IP_RATELIMIT) }
ip-ratelimit-cookie{COLON} { YDVAR(1, VAR_IP_RATELIMIT_COOKIE) }
ratelimit{COLON} { YDVAR(1, VAR_RATELIMIT) }
ip-ratelimit-slabs{COLON} { YDVAR(1, VAR_IP_RATELIMIT_SLABS) }
ratelimit-slabs{COLON} { YDVAR(1, VAR_RATELIMIT_SLABS) }
@ -567,6 +568,8 @@ name-v4{COLON} { YDVAR(1, VAR_IPSET_NAME_V4) }
name-v6{COLON} { YDVAR(1, VAR_IPSET_NAME_V6) }
udp-upstream-without-downstream{COLON} { YDVAR(1, VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM) }
tcp-connection-limit{COLON} { YDVAR(2, VAR_TCP_CONNECTION_LIMIT) }
answer-cookie{COLON} { YDVAR(1, VAR_ANSWER_COOKIE ) }
cookie-secret{COLON} { YDVAR(1, VAR_COOKIE_SECRET) }
edns-client-string{COLON} { YDVAR(2, VAR_EDNS_CLIENT_STRING) }
edns-client-string-opcode{COLON} { YDVAR(1, VAR_EDNS_CLIENT_STRING_OPCODE) }
nsid{COLON} { YDVAR(1, VAR_NSID ) }

View file

@ -47,6 +47,7 @@
#include "util/configyyrename.h"
#include "util/config_file.h"
#include "util/net_help.h"
#include "sldns/str2wire.h"
int ub_c_lex(void);
void ub_c_error(const char *message);
@ -183,6 +184,7 @@ extern struct config_parser_state* cfg_parser;
%token VAR_FALLBACK_ENABLED VAR_TLS_ADDITIONAL_PORT VAR_LOW_RTT VAR_LOW_RTT_PERMIL
%token VAR_FAST_SERVER_PERMIL VAR_FAST_SERVER_NUM
%token VAR_ALLOW_NOTIFY VAR_TLS_WIN_CERT VAR_TCP_CONNECTION_LIMIT
%token VAR_ANSWER_COOKIE VAR_COOKIE_SECRET VAR_IP_RATELIMIT_COOKIE
%token VAR_FORWARD_NO_CACHE VAR_STUB_NO_CACHE VAR_LOG_SERVFAIL VAR_DENY_ANY
%token VAR_UNKNOWN_SERVER_TIME_LIMIT VAR_LOG_TAG_QUERYREPLY
%token VAR_STREAM_WAIT_SIZE VAR_TLS_CIPHERS VAR_TLS_CIPHERSUITES VAR_TLS_USE_SNI
@ -323,6 +325,7 @@ content_server: server_num_threads | server_verbosity | server_port |
server_unknown_server_time_limit | server_log_tag_queryreply |
server_stream_wait_size | server_tls_ciphers |
server_tls_ciphersuites | server_tls_session_ticket_keys |
server_answer_cookie | server_cookie_secret | server_ip_ratelimit_cookie |
server_tls_use_sni | server_edns_client_string |
server_edns_client_string_opcode | server_nsid |
server_zonemd_permissive_mode | server_max_reuse_tcp_queries |
@ -1160,7 +1163,7 @@ server_http_nodelay: VAR_HTTP_NODELAY STRING_ARG
yyerror("expected yes or no.");
else cfg_parser->cfg->http_nodelay = (strcmp($2, "yes")==0);
free($2);
}
};
server_http_notls_downstream: VAR_HTTP_NOTLS_DOWNSTREAM STRING_ARG
{
OUTYY(("P(server_http_notls_downstream:%s)\n", $2));
@ -2207,6 +2210,7 @@ server_permit_small_holddown: VAR_PERMIT_SMALL_HOLDDOWN STRING_ARG
(strcmp($2, "yes")==0);
free($2);
}
;
server_key_cache_size: VAR_KEY_CACHE_SIZE STRING_ARG
{
OUTYY(("P(server_key_cache_size:%s)\n", $2));
@ -2564,6 +2568,15 @@ server_ip_ratelimit: VAR_IP_RATELIMIT STRING_ARG
free($2);
}
;
server_ip_ratelimit_cookie: VAR_IP_RATELIMIT_COOKIE STRING_ARG
{
OUTYY(("P(server_ip_ratelimit_cookie:%s)\n", $2));
if(atoi($2) == 0 && strcmp($2, "0") != 0)
yyerror("number expected");
else cfg_parser->cfg->ip_ratelimit_cookie = atoi($2);
free($2);
}
;
server_ratelimit: VAR_RATELIMIT STRING_ARG
{
OUTYY(("P(server_ratelimit:%s)\n", $2));
@ -3517,6 +3530,7 @@ py_script: VAR_PYTHON_SCRIPT STRING_ARG
if(!cfg_strlist_append_ex(&cfg_parser->cfg->python_script, $2))
yyerror("out of memory");
}
;
dynlibstart: VAR_DYNLIB
{
OUTYY(("\nP(dynlib:)\n"));
@ -3533,6 +3547,7 @@ dl_file: VAR_DYNLIB_FILE STRING_ARG
if(!cfg_strlist_append_ex(&cfg_parser->cfg->dynlib_file, $2))
yyerror("out of memory");
}
;
server_disable_dnssec_lame_check: VAR_DISABLE_DNSSEC_LAME_CHECK STRING_ARG
{
OUTYY(("P(disable_dnssec_lame_check:%s)\n", $2));
@ -3593,7 +3608,6 @@ dnsc_dnscrypt_enable: VAR_DNSCRYPT_ENABLE STRING_ARG
free($2);
}
;
dnsc_dnscrypt_port: VAR_DNSCRYPT_PORT STRING_ARG
{
OUTYY(("P(dnsc_dnscrypt_port:%s)\n", $2));
@ -3801,6 +3815,31 @@ server_tcp_connection_limit: VAR_TCP_CONNECTION_LIMIT STRING_ARG STRING_ARG
}
}
;
server_answer_cookie: VAR_ANSWER_COOKIE STRING_ARG
{
OUTYY(("P(server_answer_cookie:%s)\n", $2));
if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
yyerror("expected yes or no.");
else cfg_parser->cfg->do_answer_cookie = (strcmp($2, "yes")==0);
free($2);
}
;
server_cookie_secret: VAR_COOKIE_SECRET STRING_ARG
{
uint8_t secret[32];
size_t secret_len = sizeof(secret);
OUTYY(("P(server_cookie_secret:%s)\n", $2));
if(sldns_str2wire_hex_buf($2, secret, &secret_len)
|| (secret_len != 16))
yyerror("expected 128 bit hex string");
else {
cfg_parser->cfg->cookie_secret_len = secret_len;
memcpy(cfg_parser->cfg->cookie_secret, secret, sizeof(secret));
}
free($2);
}
;
ipsetstart: VAR_IPSET
{
OUTYY(("\nP(ipset:)\n"));
@ -3870,10 +3909,11 @@ validate_acl_action(const char* action)
strcmp(action, "refuse_non_local")!=0 &&
strcmp(action, "allow_setrd")!=0 &&
strcmp(action, "allow")!=0 &&
strcmp(action, "allow_snoop")!=0)
strcmp(action, "allow_snoop")!=0 &&
strcmp(action, "allow_cookie")!=0)
{
yyerror("expected deny, refuse, deny_non_local, "
"refuse_non_local, allow, allow_setrd or "
"allow_snoop as access control action");
"refuse_non_local, allow, allow_setrd, "
"allow_snoop or allow_cookie as access control action");
}
}

View file

@ -1062,15 +1062,17 @@ qinfo_query_encode(sldns_buffer* pkt, struct query_info* qinfo)
sldns_buffer_flip(pkt);
}
void
error_encode(sldns_buffer* buf, int r, struct query_info* qinfo,
uint16_t qid, uint16_t qflags, struct edns_data* edns)
void
extended_error_encode(sldns_buffer* buf, uint16_t rcode,
struct query_info* qinfo, uint16_t qid, uint16_t qflags,
uint16_t xflags, struct edns_data* edns)
{
uint16_t flags;
sldns_buffer_clear(buf);
sldns_buffer_write(buf, &qid, sizeof(uint16_t));
flags = (uint16_t)(BIT_QR | BIT_RA | r); /* QR and retcode*/
flags = (uint16_t)(BIT_QR | BIT_RA | (rcode & 0xF)); /* QR and retcode*/
flags |= xflags;
flags |= (qflags & (BIT_RD|BIT_CD)); /* copy RD and CD bit */
sldns_buffer_write_u16(buf, flags);
if(qinfo) flags = 1;
@ -1097,7 +1099,7 @@ error_encode(sldns_buffer* buf, int r, struct query_info* qinfo,
struct edns_data es = *edns;
es.edns_version = EDNS_ADVERTISED_VERSION;
es.udp_size = EDNS_ADVERTISED_SIZE;
es.ext_rcode = 0;
es.ext_rcode = (uint8_t)(rcode >> 4);
es.bits &= EDNS_DO;
if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) >
edns->udp_size) {
@ -1111,3 +1113,11 @@ error_encode(sldns_buffer* buf, int r, struct query_info* qinfo,
attach_edns_record(buf, &es);
}
}
void
error_encode(sldns_buffer* buf, int r, struct query_info* qinfo,
uint16_t qid, uint16_t qflags, struct edns_data* edns)
{
extended_error_encode(buf, (r & 0x000F), qinfo, qid, qflags,
(r & 0xFFF0), edns);
}

View file

@ -137,11 +137,11 @@ uint16_t calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size);
*/
void attach_edns_record(struct sldns_buffer* pkt, struct edns_data* edns);
/**
/**
* Encode an error. With QR and RA set.
*
* @param pkt: where to store the packet.
* @param r: RCODE value to encode.
* @param r: RCODE value to encode (may contain extra flags).
* @param qinfo: if not NULL, the query is included.
* @param qid: query ID to set in packet. network order.
* @param qflags: original query flags (to copy RD and CD bits). host order.
@ -151,4 +151,21 @@ void attach_edns_record(struct sldns_buffer* pkt, struct edns_data* edns);
void error_encode(struct sldns_buffer* pkt, int r, struct query_info* qinfo,
uint16_t qid, uint16_t qflags, struct edns_data* edns);
/**
* Encode an extended error. With QR and RA set.
*
* @param pkt: where to store the packet.
* @param rcode: Extended RCODE value to encode.
* @param qinfo: if not NULL, the query is included.
* @param qid: query ID to set in packet. network order.
* @param qflags: original query flags (to copy RD and CD bits). host order.
* @param xflags: extra flags to set (such as for example BIT_AA and/or BIT_TC)
* @param edns: if not NULL, this is the query edns info,
* and an edns reply is attached. Only attached if EDNS record fits reply.
* Without edns extended errors (i.e. > 15) will not be conveyed.
*/
void extended_error_encode(struct sldns_buffer* pkt, uint16_t rcode,
struct query_info* qinfo, uint16_t qid, uint16_t qflags,
uint16_t xflags, struct edns_data* edns);
#endif /* UTIL_DATA_MSGENCODE_H */

View file

@ -45,6 +45,8 @@
#include "util/netevent.h"
#include "util/storage/lookup3.h"
#include "util/regional.h"
#include "util/rfc_1982.h"
#include "util/edns.h"
#include "sldns/rrdef.h"
#include "sldns/sbuffer.h"
#include "sldns/parseutil.h"
@ -940,22 +942,11 @@ parse_packet(sldns_buffer* pkt, struct msg_parse* msg, struct regional* region)
return 0;
}
static int
edns_opt_list_append_keepalive(struct edns_option** list, int msec,
struct regional* region)
{
uint8_t data[2]; /* For keepalive value */
data[0] = (uint8_t)((msec >> 8) & 0xff);
data[1] = (uint8_t)(msec & 0xff);
return edns_opt_list_append(list, LDNS_EDNS_KEEPALIVE, sizeof(data),
data, region);
}
/** parse EDNS options from EDNS wireformat rdata */
static int
parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
struct edns_data* edns, struct config_file* cfg, struct comm_point* c,
struct regional* region)
struct comm_reply* repinfo, uint32_t now, struct regional* region)
{
/* To respond with a Keepalive option, the client connection must have
* received one message with a TCP Keepalive EDNS option, and that
@ -979,6 +970,10 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
while(rdata_len >= 4) {
uint16_t opt_code = sldns_read_uint16(rdata_ptr);
uint16_t opt_len = sldns_read_uint16(rdata_ptr+2);
uint8_t server_cookie[40];
enum edns_cookie_val_status cookie_val_status;
int cookie_is_v4 = 1;
rdata_ptr += 4;
rdata_len -= 4;
if(opt_len > rdata_len)
@ -1041,6 +1036,76 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
edns->padding_block_size = cfg->pad_responses_block_size;
break;
case LDNS_EDNS_COOKIE:
if(!cfg || !cfg->do_answer_cookie || !repinfo)
break;
if(opt_len != 8 && (opt_len < 16 || opt_len > 40)) {
verbose(VERB_ALGO, "worker request: "
"badly formatted cookie");
return LDNS_RCODE_FORMERR;
}
edns->cookie_present = 1;
/* Copy client cookie, version and timestamp for
* validation and creation purposes.
*/
if(opt_len >= 16) {
memmove(server_cookie, rdata_ptr, 16);
} else {
memset(server_cookie, 0, 16);
memmove(server_cookie, rdata_ptr, opt_len);
}
/* Copy client ip for validation and creation
* purposes. It will be overwritten if (re)creation
* is needed.
*/
if(repinfo->remote_addr.ss_family == AF_INET) {
memcpy(server_cookie + 16,
&((struct sockaddr_in*)&repinfo->remote_addr)->sin_addr, 4);
} else {
cookie_is_v4 = 0;
memcpy(server_cookie + 16,
&((struct sockaddr_in6*)&repinfo->remote_addr)->sin6_addr, 16);
}
cookie_val_status = edns_cookie_server_validate(
rdata_ptr, opt_len, cfg->cookie_secret,
cfg->cookie_secret_len, cookie_is_v4,
server_cookie, now);
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,
opt_len, rdata_ptr, region)) {
log_err("out of memory");
return LDNS_RCODE_SERVFAIL;
}
/* Cookie to be reused added to outgoing
* options. Done!
*/
break;
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:
break;
}
@ -1115,6 +1180,8 @@ parse_extract_edns_from_response_msg(struct msg_parse* msg,
edns->opt_list_out = NULL;
edns->opt_list_inplace_cb_out = NULL;
edns->padding_block_size = 0;
edns->cookie_present = 0;
edns->cookie_valid = 0;
/* take the options */
rdata_len = found->rr_first->size-2;
@ -1170,7 +1237,8 @@ skip_pkt_rrs(sldns_buffer* pkt, int num)
int
parse_edns_from_query_pkt(sldns_buffer* pkt, struct edns_data* edns,
struct config_file* cfg, struct comm_point* c, struct regional* region)
struct config_file* cfg, struct comm_point* c,
struct comm_reply* repinfo, time_t now, struct regional* region)
{
size_t rdata_len;
uint8_t* rdata_ptr;
@ -1206,6 +1274,8 @@ parse_edns_from_query_pkt(sldns_buffer* pkt, struct edns_data* edns,
edns->opt_list_out = NULL;
edns->opt_list_inplace_cb_out = NULL;
edns->padding_block_size = 0;
edns->cookie_present = 0;
edns->cookie_valid = 0;
/* take the options */
rdata_len = sldns_buffer_read_u16(pkt);
@ -1214,7 +1284,7 @@ parse_edns_from_query_pkt(sldns_buffer* pkt, struct edns_data* edns,
rdata_ptr = sldns_buffer_current(pkt);
/* ignore rrsigs */
return parse_edns_options_from_query(rdata_ptr, rdata_len, edns, cfg,
c, region);
c, repinfo, now, region);
}
void

View file

@ -72,6 +72,7 @@ struct regional;
struct edns_option;
struct config_file;
struct comm_point;
struct comm_reply;
/** number of buckets in parse rrset hash table. Must be power of 2. */
#define PARSE_TABLE_SIZE 32
@ -217,8 +218,6 @@ struct rr_parse {
* region.
*/
struct edns_data {
/** if EDNS OPT record was present */
int edns_present;
/** Extended RCODE */
uint8_t ext_rcode;
/** The EDNS version number */
@ -238,7 +237,15 @@ struct edns_data {
struct edns_option* opt_list_inplace_cb_out;
/** block size to pad */
uint16_t padding_block_size;
};
/** if EDNS OPT record was present */
unsigned int edns_present : 1;
/** if a cookie was present */
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;
};
/**
* EDNS option
@ -310,12 +317,15 @@ int skip_pkt_rrs(struct sldns_buffer* pkt, int num);
* initialised.
* @param cfg: the configuration (with nsid value etc.)
* @param c: commpoint to determine transport (if needed)
* @param repinfo: commreply to determine the client address
* @param now: current time
* @param region: region to alloc results in (edns option contents)
* @return: 0 on success, or an RCODE on error.
* RCODE formerr if OPT is badly formatted and so on.
*/
int parse_edns_from_query_pkt(struct sldns_buffer* pkt, struct edns_data* edns,
struct config_file* cfg, struct comm_point* c, struct regional* region);
struct config_file* cfg, struct comm_point* c,
struct comm_reply* repinfo, time_t now, struct regional* region);
/**
* Calculate hash value for rrset in packet.

View file

@ -1049,6 +1049,16 @@ int edns_opt_list_append_ede(struct edns_option** list, struct regional* region,
return 1;
}
int edns_opt_list_append_keepalive(struct edns_option** list, int msec,
struct regional* region)
{
uint8_t data[2]; /* For keepalive value */
data[0] = (uint8_t)((msec >> 8) & 0xff);
data[1] = (uint8_t)(msec & 0xff);
return edns_opt_list_append(list, LDNS_EDNS_KEEPALIVE, sizeof(data),
data, region);
}
int edns_opt_list_append(struct edns_option** list, uint16_t code, size_t len,
uint8_t* data, struct regional* region)
{

View file

@ -577,6 +577,16 @@ int edns_opt_list_append(struct edns_option** list, uint16_t code, size_t len,
int edns_opt_list_append_ede(struct edns_option** list, struct regional* region,
sldns_ede_code code, const char *txt);
/**
* Append edns keep alive option to edns options list
* @param list: the edns option list to append the edns option to.
* @param msec: the duration in msecs for the keep alive.
* @param region: region to allocate the new edns option.
* @return false on failure.
*/
int edns_opt_list_append_keepalive(struct edns_option** list, int msec,
struct regional* region);
/**
* Remove any option found on the edns option list that matches the code.
* @param list: the list of edns options.

View file

@ -45,8 +45,11 @@
#include "util/netevent.h"
#include "util/net_help.h"
#include "util/regional.h"
#include "util/rfc_1982.h"
#include "util/siphash.h"
#include "util/data/msgparse.h"
#include "util/data/msgreply.h"
#include "sldns/sbuffer.h"
struct edns_strings* edns_strings_create(void)
{
@ -128,3 +131,59 @@ edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr,
return (struct edns_string_addr*)addr_tree_lookup(tree, addr, addrlen);
}
uint8_t*
edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret, int v4,
uint8_t* hash)
{
v4?siphash(in, 20, secret, hash, 8):siphash(in, 32, secret, hash, 8);
return hash;
}
void
edns_cookie_server_write(uint8_t* buf, const uint8_t* secret, int v4,
uint32_t timestamp)
{
uint8_t hash[8];
buf[ 8] = 1; /* Version */
buf[ 9] = 0; /* Reserved */
buf[10] = 0; /* Reserved */
buf[11] = 0; /* Reserved */
sldns_write_uint32(buf + 12, timestamp);
(void)edns_cookie_server_hash(buf, secret, v4, hash);
memcpy(buf + 16, hash, 8);
}
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)
{
uint8_t hash[8];
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 */
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 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 COOKIE_STATUS_FUTURE;
if(memcmp(edns_cookie_server_hash(hash_input, secret, v4, hash),
cookie + 16, 8) != 0)
/* Hashes do not match */
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 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.
@ -106,4 +115,54 @@ struct edns_string_addr*
edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr,
socklen_t addrlen);
/**
* Compute the interoperable DNS cookie (RFC9018) hash.
* @param in: buffer input for the hash generation. It needs to be:
* Client Cookie | Version | Reserved | Timestamp | Client-IP
* @param secret: the server secret; implicit length of 16 octets.
* @param v4: if the client IP is v4 or v6.
* @param hash: buffer to write the hash to.
* return a pointer to the hash.
*/
uint8_t* edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret,
int v4, uint8_t* hash);
/**
* Write an interoperable DNS server cookie (RFC9018).
* @param buf: buffer to write to. It should have a size of at least 32 octets
* as it doubles as the output buffer and the hash input buffer.
* The first 8 octets are expected to be the Client Cookie and will be
* left untouched.
* The next 8 octets will be written with Version | Reserved | Timestamp.
* The next 4 or 16 octets are expected to be the IPv4 or the IPv6 address
* based on the v4 flag.
* Thus the first 20 or 32 octets, based on the v4 flag, will be used as
* the hash input.
* The server hash (8 octets) will be written after the first 16 octets;
* overwriting the address information.
* The caller expects a complete, 24 octet long cookie in the buffer.
* @param secret: the server secret; implicit length of 16 octets.
* @param v4: if the client IP is v4 or v6.
* @param timestamp: the timestamp to use.
*/
void edns_cookie_server_write(uint8_t* buf, const uint8_t* secret, int v4,
uint32_t timestamp);
/**
* Validate an interoperable DNS cookie (RFC9018).
* @param cookie: pointer to the cookie data.
* @param cookie_len: the length of the cookie data.
* @param secret: pointer to the server secret.
* @param secret_len: the length of the secret.
* @param v4: if the client IP is v4 or v6.
* @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 edns_cookie_val_status with the cookie validation status i.e.,
* <=0 for invalid, else valid.
*/
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

74
util/rfc_1982.c Normal file
View file

@ -0,0 +1,74 @@
/*
* util/rfc_1982.c - RFC 1982 Serial Number Arithmetic
*
* Copyright (c) 2023, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
*
* This file contains functions for RFC 1982 serial number arithmetic.
*/
#include "config.h"
int
compare_1982(uint32_t a, uint32_t b)
{
/* for 32 bit values */
const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
if (a == b) {
return 0;
} else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) {
return -1;
} else {
return 1;
}
}
uint32_t
subtract_1982(uint32_t a, uint32_t b)
{
/* for 32 bit values */
const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
if(a == b)
return 0;
if(a < b && b - a < cutoff) {
return b-a;
}
if(a > b && a - b > cutoff) {
return ((uint32_t)0xffffffff) - (a-b-1);
}
/* wrong case, b smaller than a */
return 0;
}

63
util/rfc_1982.h Normal file
View file

@ -0,0 +1,63 @@
/*
* util/rfc_1982.h - RFC 1982 Serial Number Arithmetic
*
* Copyright (c) 2023, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
*
* This file contains functions for RFC 1982 serial number arithmetic.
*/
#ifndef RFC_1982_H
#define RFC_1982_H
/**
* RFC 1982 comparison, uses unsigned integers, and tries to avoid
* compiler optimization (eg. by avoiding a-b<0 comparisons).
* @param a: value to compare.
* @param b: value to compare.
* @return 0 if equal, 1 if a > b, else -1.
*/
int compare_1982(uint32_t a, uint32_t b);
/**
* RFC 1982 subtraction, uses unsigned integers, and tries to avoid
* compiler optimization (eg. by avoiding a-b<0 comparisons).
* @param a: value to subtract from.
* @param b: value to subtract.
* @return the difference between them if we know that b is larger than a,
* that is the distance between them in serial number arithmetic.
*/
uint32_t subtract_1982(uint32_t a, uint32_t b);
#endif /* RFC_1982_H */

187
util/siphash.c Normal file
View file

@ -0,0 +1,187 @@
/*
SipHash reference C implementation
Copyright (c) 2012-2016 Jean-Philippe Aumasson
<jeanphilippe.aumasson@gmail.com>
Copyright (c) 2012-2014 Daniel J. Bernstein <djb@cr.yp.to>
To the extent possible under law, the author(s) have dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
You should have received a copy of the CC0 Public Domain Dedication along
with
this software. If not, see
<http://creativecommons.org/publicdomain/zero/1.0/>.
*/
/**
* Edited slightly for integration in Unbound. Edits are noted with 'EDIT'.
*/
/** EDIT
* \#include <assert.h>
* \#include <stdint.h>
* \#include <stdio.h>
* \#include <string.h>
* Replaced the above includes with Unbound's config.h
*/
#include "config.h"
/* default: SipHash-2-4 */
#define cROUNDS 2
#define dROUNDS 4
#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
#define U32TO8_LE(p, v) \
(p)[0] = (uint8_t)((v)); \
(p)[1] = (uint8_t)((v) >> 8); \
(p)[2] = (uint8_t)((v) >> 16); \
(p)[3] = (uint8_t)((v) >> 24);
#define U64TO8_LE(p, v) \
U32TO8_LE((p), (uint32_t)((v))); \
U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));
#define U8TO64_LE(p) \
(((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \
((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \
((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \
((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))
#define SIPROUND \
do { \
v0 += v1; \
v1 = ROTL(v1, 13); \
v1 ^= v0; \
v0 = ROTL(v0, 32); \
v2 += v3; \
v3 = ROTL(v3, 16); \
v3 ^= v2; \
v0 += v3; \
v3 = ROTL(v3, 21); \
v3 ^= v0; \
v2 += v1; \
v1 = ROTL(v1, 17); \
v1 ^= v2; \
v2 = ROTL(v2, 32); \
} while (0)
#ifdef DEBUG
#define TRACE \
do { \
printf("(%3d) v0 %08x %08x\n", (int)inlen, (uint32_t)(v0 >> 32), \
(uint32_t)v0); \
printf("(%3d) v1 %08x %08x\n", (int)inlen, (uint32_t)(v1 >> 32), \
(uint32_t)v1); \
printf("(%3d) v2 %08x %08x\n", (int)inlen, (uint32_t)(v2 >> 32), \
(uint32_t)v2); \
printf("(%3d) v3 %08x %08x\n", (int)inlen, (uint32_t)(v3 >> 32), \
(uint32_t)v3); \
} while (0)
#else
#define TRACE
#endif
int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
uint8_t *out, const size_t outlen) {
uint64_t v0 = 0x736f6d6570736575ULL;
uint64_t v1 = 0x646f72616e646f6dULL;
uint64_t v2 = 0x6c7967656e657261ULL;
uint64_t v3 = 0x7465646279746573ULL;
uint64_t k0 = U8TO64_LE(k);
uint64_t k1 = U8TO64_LE(k + 8);
uint64_t m;
int i;
const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t));
const int left = inlen & 7;
uint64_t b = ((uint64_t)inlen) << 56;
/** EDIT
* The following assert moved here from the top for C90 compliance.
*/
assert((outlen == 8) || (outlen == 16));
v3 ^= k1;
v2 ^= k0;
v1 ^= k1;
v0 ^= k0;
if (outlen == 16)
v1 ^= 0xee;
for (; in != end; in += 8) {
m = U8TO64_LE(in);
v3 ^= m;
TRACE;
for (i = 0; i < cROUNDS; ++i)
SIPROUND;
v0 ^= m;
}
switch (left) {
case 7:
b |= ((uint64_t)in[6]) << 48;
/** EDIT annotate case statement fallthrough for gcc */
/* fallthrough */
case 6:
b |= ((uint64_t)in[5]) << 40;
/** EDIT annotate case statement fallthrough for gcc */
/* fallthrough */
case 5:
b |= ((uint64_t)in[4]) << 32;
/** EDIT annotate case statement fallthrough for gcc */
/* fallthrough */
case 4:
b |= ((uint64_t)in[3]) << 24;
/** EDIT annotate case statement fallthrough for gcc */
/* fallthrough */
case 3:
b |= ((uint64_t)in[2]) << 16;
/** EDIT annotate case statement fallthrough for gcc */
/* fallthrough */
case 2:
b |= ((uint64_t)in[1]) << 8;
/** EDIT annotate case statement fallthrough for gcc */
/* fallthrough */
case 1:
b |= ((uint64_t)in[0]);
break;
case 0:
break;
}
v3 ^= b;
TRACE;
for (i = 0; i < cROUNDS; ++i)
SIPROUND;
v0 ^= b;
if (outlen == 16)
v2 ^= 0xee;
else
v2 ^= 0xff;
TRACE;
for (i = 0; i < dROUNDS; ++i)
SIPROUND;
b = v0 ^ v1 ^ v2 ^ v3;
U64TO8_LE(out, b);
if (outlen == 8)
return 0;
v1 ^= 0xdd;
TRACE;
for (i = 0; i < dROUNDS; ++i)
SIPROUND;
b = v0 ^ v1 ^ v2 ^ v3;
U64TO8_LE(out + 8, b);
return 0;
}

43
util/siphash.h Normal file
View file

@ -0,0 +1,43 @@
/*
* util/siphash.h - header for SipHash reference C implementation.
*
* Copyright (c) 2023, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
* Contains the SipHash reference C implementation.
*/
#ifndef UTIL_SIPHASH_H
#define UTIL_SIPHASH_H
int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
uint8_t *out, const size_t outlen);
#endif /* UTIL_SIPHASH_H */

View file

@ -2376,6 +2376,8 @@ probe_anchor(struct module_env* env, struct trust_anchor* tp)
edns.opt_list_out = NULL;
edns.opt_list_inplace_cb_out = NULL;
edns.padding_block_size = 0;
edns.cookie_present = 0;
edns.cookie_valid = 0;
if(sldns_buffer_capacity(buf) < 65535)
edns.udp_size = (uint16_t)sldns_buffer_capacity(buf);
else edns.udp_size = 65535;

View file

@ -48,6 +48,7 @@
#include "util/data/msgparse.h"
#include "util/data/dname.h"
#include "util/rbtree.h"
#include "util/rfc_1982.h"
#include "util/module.h"
#include "util/net_help.h"
#include "util/regional.h"
@ -1378,44 +1379,6 @@ sigdate_error(const char* str, int32_t expi, int32_t incep, int32_t now)
(unsigned)incep, (unsigned)now);
}
/** RFC 1982 comparison, uses unsigned integers, and tries to avoid
* compiler optimization (eg. by avoiding a-b<0 comparisons),
* this routine matches compare_serial(), for SOA serial number checks */
static int
compare_1982(uint32_t a, uint32_t b)
{
/* for 32 bit values */
const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
if (a == b) {
return 0;
} else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) {
return -1;
} else {
return 1;
}
}
/** if we know that b is larger than a, return the difference between them,
* that is the distance between them. in RFC1982 arith */
static uint32_t
subtract_1982(uint32_t a, uint32_t b)
{
/* for 32 bit values */
const uint32_t cutoff = ((uint32_t) 1 << (32 - 1));
if(a == b)
return 0;
if(a < b && b - a < cutoff) {
return b-a;
}
if(a > b && a - b > cutoff) {
return ((uint32_t)0xffffffff) - (a-b-1);
}
/* wrong case, b smaller than a */
return 0;
}
/** check rrsig dates */
static int
check_dates(struct val_env* ve, uint32_t unow, uint8_t* expi_p,