Merge branch 'master' into fast-reload-option

This commit is contained in:
W.C.A. Wijngaards 2024-08-16 09:14:19 +02:00
commit a00c28790b
42 changed files with 1532 additions and 126 deletions

View File

@ -1298,7 +1298,8 @@ remote.lo remote.o: $(srcdir)/daemon/remote.c config.h $(srcdir)/daemon/remote.h
$(srcdir)/validator/val_anchor.h $(srcdir)/iterator/iterator.h $(srcdir)/services/outbound_list.h \
$(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h $(srcdir)/iterator/iter_delegpt.h \
$(srcdir)/services/outside_network.h $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/parseutil.h \
$(srcdir)/sldns/wire2str.h $(srcdir)/util/locks.h $(srcdir)/util/ub_event.h \
$(srcdir)/sldns/wire2str.h $(srcdir)/util/edns.h \
$(srcdir)/util/locks.h $(srcdir)/util/ub_event.h \
$(srcdir)/util/tcp_conn_limit.h $(srcdir)/util/edns.h $(srcdir)/validator/val_neg.h \
$(srcdir)/iterator/iter_utils.h $(srcdir)/iterator/iter_donotq.h $(srcdir)/iterator/iter_priv.h
stats.lo stats.o: $(srcdir)/daemon/stats.c config.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h \

View File

@ -1499,6 +1499,7 @@ struct sockaddr_storage;
# define calloc(n,s) unbound_stat_calloc_log(n, s, __FILE__, __LINE__, __func__)
# define free(p) unbound_stat_free_log(p, __FILE__, __LINE__, __func__)
# define realloc(p,s) unbound_stat_realloc_log(p, s, __FILE__, __LINE__, __func__)
# define strdup(s) unbound_stat_strdup_log(s, __FILE__, __LINE__, __func__)
void *unbound_stat_malloc(size_t size);
void *unbound_stat_calloc(size_t nmemb, size_t size);
void unbound_stat_free(void *ptr);
@ -1511,6 +1512,8 @@ void unbound_stat_free_log(void *ptr, const char* file, int line,
const char* func);
void *unbound_stat_realloc_log(void *ptr, size_t size, const char* file,
int line, const char* func);
char *unbound_stat_strdup_log(const char *s, const char* file, int line,
const char* func);
#elif defined(UNBOUND_ALLOC_LITE)
# include "util/alloc.h"
#endif /* UNBOUND_ALLOC_LITE and UNBOUND_ALLOC_STATS */

27
configure vendored
View File

@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for unbound 1.20.1.
# Generated by GNU Autoconf 2.71 for unbound 1.21.1.
#
# Report bugs to <unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues>.
#
@ -622,8 +622,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='unbound'
PACKAGE_TARNAME='unbound'
PACKAGE_VERSION='1.20.1'
PACKAGE_STRING='unbound 1.20.1'
PACKAGE_VERSION='1.21.1'
PACKAGE_STRING='unbound 1.21.1'
PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues'
PACKAGE_URL=''
@ -1508,7 +1508,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures unbound 1.20.1 to adapt to many kinds of systems.
\`configure' configures unbound 1.21.1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@ -1574,7 +1574,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of unbound 1.20.1:";;
short | recursive ) echo "Configuration of unbound 1.21.1:";;
esac
cat <<\_ACEOF
@ -1822,7 +1822,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
unbound configure 1.20.1
unbound configure 1.21.1
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@ -2479,7 +2479,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by unbound $as_me 1.20.1, which was
It was created by unbound $as_me 1.21.1, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@ -3241,13 +3241,13 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
UNBOUND_VERSION_MAJOR=1
UNBOUND_VERSION_MINOR=20
UNBOUND_VERSION_MINOR=21
UNBOUND_VERSION_MICRO=1
LIBUNBOUND_CURRENT=9
LIBUNBOUND_REVISION=28
LIBUNBOUND_REVISION=29
LIBUNBOUND_AGE=1
# 1.0.0 had 0:12:0
# 1.0.1 had 0:13:0
@ -3342,7 +3342,8 @@ LIBUNBOUND_AGE=1
# 1.19.2 had 9:25:1
# 1.19.3 had 9:26:1
# 1.20.0 had 9:27:1
# 1.20.1 had 9:28:1
# 1.21.0 had 9:28:1
# 1.21.1 had 9:29:1
# Current -- the number of the binary API that we're implementing
# Revision -- which iteration of the implementation of the binary
@ -24652,7 +24653,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 10240" >>confdefs.h
version=1.20.1
version=1.21.1
date=`date +'%b %e, %Y'`
@ -25164,7 +25165,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by unbound $as_me 1.20.1, which was
This file was extended by unbound $as_me 1.21.1, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@ -25232,7 +25233,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
unbound config.status 1.20.1
unbound config.status 1.21.1
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"

View File

@ -10,7 +10,7 @@ sinclude(dnscrypt/dnscrypt.m4)
# must be numbers. ac_defun because of later processing
m4_define([VERSION_MAJOR],[1])
m4_define([VERSION_MINOR],[20])
m4_define([VERSION_MINOR],[21])
m4_define([VERSION_MICRO],[1])
AC_INIT([unbound],m4_defn([VERSION_MAJOR]).m4_defn([VERSION_MINOR]).m4_defn([VERSION_MICRO]),[unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues],[unbound])
AC_SUBST(UNBOUND_VERSION_MAJOR, [VERSION_MAJOR])
@ -18,7 +18,7 @@ AC_SUBST(UNBOUND_VERSION_MINOR, [VERSION_MINOR])
AC_SUBST(UNBOUND_VERSION_MICRO, [VERSION_MICRO])
LIBUNBOUND_CURRENT=9
LIBUNBOUND_REVISION=28
LIBUNBOUND_REVISION=29
LIBUNBOUND_AGE=1
# 1.0.0 had 0:12:0
# 1.0.1 had 0:13:0
@ -113,7 +113,8 @@ LIBUNBOUND_AGE=1
# 1.19.2 had 9:25:1
# 1.19.3 had 9:26:1
# 1.20.0 had 9:27:1
# 1.20.1 had 9:28:1
# 1.21.0 had 9:28:1
# 1.21.1 had 9:29:1
# Current -- the number of the binary API that we're implementing
# Revision -- which iteration of the implementation of the binary
@ -2330,6 +2331,7 @@ struct sockaddr_storage;
# define calloc(n,s) unbound_stat_calloc_log(n, s, __FILE__, __LINE__, __func__)
# define free(p) unbound_stat_free_log(p, __FILE__, __LINE__, __func__)
# define realloc(p,s) unbound_stat_realloc_log(p, s, __FILE__, __LINE__, __func__)
# define strdup(s) unbound_stat_strdup_log(s, __FILE__, __LINE__, __func__)
void *unbound_stat_malloc(size_t size);
void *unbound_stat_calloc(size_t nmemb, size_t size);
void unbound_stat_free(void *ptr);
@ -2342,6 +2344,8 @@ void unbound_stat_free_log(void *ptr, const char* file, int line,
const char* func);
void *unbound_stat_realloc_log(void *ptr, size_t size, const char* file,
int line, const char* func);
char *unbound_stat_strdup_log(const char *s, const char* file, int line,
const char* func);
#elif defined(UNBOUND_ALLOC_LITE)
# include "util/alloc.h"
#endif /* UNBOUND_ALLOC_LITE and UNBOUND_ALLOC_STATS */

View File

@ -734,6 +734,14 @@ daemon_fork(struct daemon* daemon)
"dnscrypt support");
#endif
}
if(daemon->cfg->cookie_secret_file &&
daemon->cfg->cookie_secret_file[0]) {
if(!(daemon->cookie_secrets = cookie_secrets_create()))
fatal_exit("Could not create cookie_secrets: out of memory");
if(!cookie_secrets_apply_cfg(daemon->cookie_secrets,
daemon->cfg->cookie_secret_file))
fatal_exit("Could not setup cookie_secrets");
}
/* create global local_zones */
if(!(daemon->local_zones = local_zones_create()))
fatal_exit("Could not create local zones: out of memory");
@ -932,6 +940,7 @@ daemon_delete(struct daemon* daemon)
acl_list_delete(daemon->acl);
acl_list_delete(daemon->acl_interface);
tcl_list_delete(daemon->tcl);
cookie_secrets_delete(daemon->cookie_secrets);
listen_desetup_locks();
free(daemon->chroot);
free(daemon->pidfile);

View File

@ -58,6 +58,7 @@ struct ub_randstate;
struct daemon_remote;
struct respip_set;
struct shm_main_info;
struct cookie_secrets;
struct fast_reload_thread;
struct fast_reload_printq;
@ -146,6 +147,8 @@ struct daemon {
#endif
/** reuse existing cache on reload if other conditions allow it. */
int reuse_cache;
/** the EDNS cookie secrets from the cookie-secret-file */
struct cookie_secrets* cookie_secrets;
/** the fast reload thread, or NULL */
struct fast_reload_thread* fast_reload_thread;
/** the fast reload printq list */

View File

@ -3278,6 +3278,210 @@ do_rpz_disable(RES* ssl, struct worker* worker, char* arg)
do_rpz_enable_disable(ssl, worker, arg, 0);
}
/** Write the cookie secrets to file, returns `0` on failure.
* Caller has to hold the lock. */
static int
cookie_secret_file_dump(RES* ssl, struct worker* worker) {
char const* secret_file = worker->env.cfg->cookie_secret_file;
struct cookie_secrets* cookie_secrets = worker->daemon->cookie_secrets;
char secret_hex[UNBOUND_COOKIE_SECRET_SIZE * 2 + 1];
FILE* f;
size_t i;
if(secret_file == NULL || secret_file[0]==0) {
(void)ssl_printf(ssl, "error: no cookie secret file configured\n");
return 0;
}
log_assert( secret_file != NULL );
/* open write only and truncate */
if((f = fopen(secret_file, "w")) == NULL ) {
(void)ssl_printf(ssl, "unable to open cookie secret file %s: %s",
secret_file, strerror(errno));
return 0;
}
if(cookie_secrets == NULL) {
/* nothing to write */
fclose(f);
return 1;
}
for(i = 0; i < cookie_secrets->cookie_count; i++) {
struct cookie_secret const* cs = &cookie_secrets->
cookie_secrets[i];
ssize_t const len = hex_ntop(cs->cookie_secret,
UNBOUND_COOKIE_SECRET_SIZE, secret_hex,
sizeof(secret_hex));
(void)len; /* silence unused variable warning with -DNDEBUG */
log_assert( len == UNBOUND_COOKIE_SECRET_SIZE * 2 );
secret_hex[UNBOUND_COOKIE_SECRET_SIZE * 2] = '\0';
fprintf(f, "%s\n", secret_hex);
}
explicit_bzero(secret_hex, sizeof(secret_hex));
fclose(f);
return 1;
}
/** Activate cookie secret */
static void
do_activate_cookie_secret(RES* ssl, struct worker* worker) {
char const* secret_file = worker->env.cfg->cookie_secret_file;
struct cookie_secrets* cookie_secrets = worker->daemon->cookie_secrets;
if(secret_file == NULL || secret_file[0] == 0) {
(void)ssl_printf(ssl, "error: no cookie secret file configured\n");
return;
}
if(cookie_secrets == NULL) {
(void)ssl_printf(ssl, "error: there are no cookie_secrets.");
return;
}
lock_basic_lock(&cookie_secrets->lock);
if(cookie_secrets->cookie_count <= 1 ) {
lock_basic_unlock(&cookie_secrets->lock);
(void)ssl_printf(ssl, "error: no staging cookie secret to activate\n");
return;
}
/* Only the worker 0 writes to file, the others update state. */
if(worker->thread_num == 0 && !cookie_secret_file_dump(ssl, worker)) {
lock_basic_unlock(&cookie_secrets->lock);
(void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
secret_file);
return;
}
activate_cookie_secret(cookie_secrets);
if(worker->thread_num == 0)
(void)cookie_secret_file_dump(ssl, worker);
lock_basic_unlock(&cookie_secrets->lock);
send_ok(ssl);
}
/** Drop cookie secret */
static void
do_drop_cookie_secret(RES* ssl, struct worker* worker) {
char const* secret_file = worker->env.cfg->cookie_secret_file;
struct cookie_secrets* cookie_secrets = worker->daemon->cookie_secrets;
if(secret_file == NULL || secret_file[0] == 0) {
(void)ssl_printf(ssl, "error: no cookie secret file configured\n");
return;
}
if(cookie_secrets == NULL) {
(void)ssl_printf(ssl, "error: there are no cookie_secrets.");
return;
}
lock_basic_lock(&cookie_secrets->lock);
if(cookie_secrets->cookie_count <= 1 ) {
lock_basic_unlock(&cookie_secrets->lock);
(void)ssl_printf(ssl, "error: can not drop the currently active cookie secret\n");
return;
}
/* Only the worker 0 writes to file, the others update state. */
if(worker->thread_num == 0 && !cookie_secret_file_dump(ssl, worker)) {
lock_basic_unlock(&cookie_secrets->lock);
(void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
secret_file);
return;
}
drop_cookie_secret(cookie_secrets);
if(worker->thread_num == 0)
(void)cookie_secret_file_dump(ssl, worker);
lock_basic_unlock(&cookie_secrets->lock);
send_ok(ssl);
}
/** Add cookie secret */
static void
do_add_cookie_secret(RES* ssl, struct worker* worker, char* arg) {
uint8_t secret[UNBOUND_COOKIE_SECRET_SIZE];
char const* secret_file = worker->env.cfg->cookie_secret_file;
struct cookie_secrets* cookie_secrets = worker->daemon->cookie_secrets;
if(secret_file == NULL || secret_file[0] == 0) {
(void)ssl_printf(ssl, "error: no cookie secret file configured\n");
return;
}
if(cookie_secrets == NULL) {
worker->daemon->cookie_secrets = cookie_secrets_create();
if(!worker->daemon->cookie_secrets) {
(void)ssl_printf(ssl, "error: out of memory");
return;
}
cookie_secrets = worker->daemon->cookie_secrets;
}
lock_basic_lock(&cookie_secrets->lock);
if(*arg == '\0') {
lock_basic_unlock(&cookie_secrets->lock);
(void)ssl_printf(ssl, "error: missing argument (cookie_secret)\n");
return;
}
if(strlen(arg) != 32) {
lock_basic_unlock(&cookie_secrets->lock);
explicit_bzero(arg, strlen(arg));
(void)ssl_printf(ssl, "invalid cookie secret: invalid argument length\n");
(void)ssl_printf(ssl, "please provide a 128bit hex encoded secret\n");
return;
}
if(hex_pton(arg, secret, UNBOUND_COOKIE_SECRET_SIZE) !=
UNBOUND_COOKIE_SECRET_SIZE ) {
lock_basic_unlock(&cookie_secrets->lock);
explicit_bzero(secret, UNBOUND_COOKIE_SECRET_SIZE);
explicit_bzero(arg, strlen(arg));
(void)ssl_printf(ssl, "invalid cookie secret: parse error\n");
(void)ssl_printf(ssl, "please provide a 128bit hex encoded secret\n");
return;
}
/* Only the worker 0 writes to file, the others update state. */
if(worker->thread_num == 0 && !cookie_secret_file_dump(ssl, worker)) {
lock_basic_unlock(&cookie_secrets->lock);
explicit_bzero(secret, UNBOUND_COOKIE_SECRET_SIZE);
explicit_bzero(arg, strlen(arg));
(void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
secret_file);
return;
}
add_cookie_secret(cookie_secrets, secret, UNBOUND_COOKIE_SECRET_SIZE);
explicit_bzero(secret, UNBOUND_COOKIE_SECRET_SIZE);
if(worker->thread_num == 0)
(void)cookie_secret_file_dump(ssl, worker);
lock_basic_unlock(&cookie_secrets->lock);
explicit_bzero(arg, strlen(arg));
send_ok(ssl);
}
/** Print cookie secrets */
static void
do_print_cookie_secrets(RES* ssl, struct worker* worker) {
struct cookie_secrets* cookie_secrets = worker->daemon->cookie_secrets;
char secret_hex[UNBOUND_COOKIE_SECRET_SIZE * 2 + 1];
int i;
if(!cookie_secrets)
return; /* Output is empty. */
lock_basic_lock(&cookie_secrets->lock);
for(i = 0; (size_t)i < cookie_secrets->cookie_count; i++) {
struct cookie_secret const* cs = &cookie_secrets->
cookie_secrets[i];
ssize_t const len = hex_ntop(cs->cookie_secret,
UNBOUND_COOKIE_SECRET_SIZE, secret_hex,
sizeof(secret_hex));
(void)len; /* silence unused variable warning with -DNDEBUG */
log_assert( len == UNBOUND_COOKIE_SECRET_SIZE * 2 );
secret_hex[UNBOUND_COOKIE_SECRET_SIZE * 2] = '\0';
if (i == 0)
(void)ssl_printf(ssl, "active : %s\n", secret_hex);
else if (cookie_secrets->cookie_count == 2)
(void)ssl_printf(ssl, "staging: %s\n", secret_hex);
else
(void)ssl_printf(ssl, "staging[%d]: %s\n", i,
secret_hex);
}
lock_basic_unlock(&cookie_secrets->lock);
explicit_bzero(secret_hex, sizeof(secret_hex));
}
/** check for name with end-of-string, space or tab after it */
static int
cmdcmp(char* p, const char* cmd, size_t len)
@ -3413,6 +3617,9 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd,
} else if(cmdcmp(p, "view_local_datas", 16)) {
do_view_datas_add(rc, ssl, worker, skipwhite(p+16));
return;
} else if(cmdcmp(p, "print_cookie_secrets", 20)) {
do_print_cookie_secrets(ssl, worker);
return;
}
#ifdef THREADS_DISABLED
@ -3477,6 +3684,12 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd,
do_rpz_enable(ssl, worker, skipwhite(p+10));
} else if(cmdcmp(p, "rpz_disable", 11)) {
do_rpz_disable(ssl, worker, skipwhite(p+11));
} else if(cmdcmp(p, "add_cookie_secret", 17)) {
do_add_cookie_secret(ssl, worker, skipwhite(p+17));
} else if(cmdcmp(p, "drop_cookie_secret", 18)) {
do_drop_cookie_secret(ssl, worker);
} else if(cmdcmp(p, "activate_cookie_secret", 22)) {
do_activate_cookie_secret(ssl, worker);
} else {
(void)ssl_printf(ssl, "error unknown command '%s'\n", p);
}

View File

@ -160,9 +160,11 @@ worker_mem_report(struct worker* ATTR_UNUSED(worker),
+ sizeof(worker->rndstate)
+ regional_get_mem(worker->scratchpad)
+ sizeof(*worker->env.scratch_buffer)
+ sldns_buffer_capacity(worker->env.scratch_buffer)
+ forwards_get_mem(worker->env.fwds)
+ hints_get_mem(worker->env.hints);
+ sldns_buffer_capacity(worker->env.scratch_buffer);
if(worker->daemon->env->fwds)
log_info("forwards=%u", (unsigned)forwards_get_mem(worker->env.fwds));
if(worker->daemon->env->hints)
log_info("hints=%u", (unsigned)hints_get_mem(worker->env.hints));
if(worker->thread_num == 0)
me += acl_list_get_mem(worker->daemon->acl);
if(cur_serv) {
@ -1660,7 +1662,8 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
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) {
worker->scratchpad,
worker->daemon->cookie_secrets)) != 0) {
struct edns_data reply_edns;
verbose(VERB_ALGO, "worker parse edns: formerror.");
log_addr(VERB_CLIENT, "from", &repinfo->client_addr,

View File

@ -200,18 +200,25 @@ static void tap_data_list_try_to_free_tail(struct tap_data_list* list)
}
/** delete the tap structure */
static void tap_data_free(struct tap_data* data)
static void tap_data_free(struct tap_data* data, int free_tail)
{
ub_event_del(data->ev);
ub_event_free(data->ev);
if(!data)
return;
if(data->ev) {
ub_event_del(data->ev);
ub_event_free(data->ev);
}
#ifdef HAVE_SSL
SSL_free(data->ssl);
#endif
close(data->fd);
sock_close(data->fd);
free(data->id);
free(data->frame);
data->data_list->d = NULL;
tap_data_list_try_to_free_tail(data->data_list);
if(data->data_list) {
data->data_list->d = NULL;
if(free_tail)
tap_data_list_try_to_free_tail(data->data_list);
}
free(data);
}
@ -237,7 +244,7 @@ static void tap_data_list_delete(struct tap_data_list* list)
while(e) {
next = e->next;
if(e->d) {
tap_data_free(e->d);
tap_data_free(e->d, 0);
e->d = NULL;
}
free(e);
@ -260,7 +267,7 @@ static void tap_socket_close(struct tap_socket* s)
{
if(!s) return;
if(s->fd == -1) return;
close(s->fd);
sock_close(s->fd);
s->fd = -1;
}
@ -992,7 +999,7 @@ static int tap_handshake(struct tap_data* data)
return 0;
} else if(r == 0) {
/* closed */
tap_data_free(data);
tap_data_free(data, 1);
return 0;
} else if(want == SSL_ERROR_SYSCALL) {
/* SYSCALL and errno==0 means closed uncleanly */
@ -1010,7 +1017,7 @@ static int tap_handshake(struct tap_data* data)
if(!silent)
log_err("SSL_handshake syscall: %s",
strerror(errno));
tap_data_free(data);
tap_data_free(data, 1);
return 0;
} else {
unsigned long err = ERR_get_error();
@ -1020,7 +1027,7 @@ static int tap_handshake(struct tap_data* data)
verbose(VERB_OPS, "ssl handshake failed "
"from %s", data->id);
}
tap_data_free(data);
tap_data_free(data, 1);
return 0;
}
}
@ -1028,7 +1035,7 @@ static int tap_handshake(struct tap_data* data)
data->ssl_handshake_done = 1;
if(!tap_check_peer(data)) {
/* closed */
tap_data_free(data);
tap_data_free(data, 1);
return 0;
}
return 1;
@ -1054,7 +1061,7 @@ void dtio_tap_callback(int ATTR_UNUSED(fd), short ATTR_UNUSED(bits), void* arg)
if(verbosity>=4) log_info("s recv %d", (int)ret);
if(ret == 0) {
/* closed or error */
tap_data_free(data);
tap_data_free(data, 1);
return;
} else if(ret == -1) {
/* continue later */
@ -1076,7 +1083,7 @@ void dtio_tap_callback(int ATTR_UNUSED(fd), short ATTR_UNUSED(bits), void* arg)
data->frame = calloc(1, data->len);
if(!data->frame) {
log_err("out of memory");
tap_data_free(data);
tap_data_free(data, 1);
return;
}
}
@ -1089,7 +1096,7 @@ void dtio_tap_callback(int ATTR_UNUSED(fd), short ATTR_UNUSED(bits), void* arg)
if(verbosity>=4) log_info("f recv %d", (int)r);
if(r == 0) {
/* closed or error */
tap_data_free(data);
tap_data_free(data, 1);
return;
} else if(r == -1) {
/* continue later */
@ -1114,13 +1121,13 @@ void dtio_tap_callback(int ATTR_UNUSED(fd), short ATTR_UNUSED(bits), void* arg)
data->is_bidirectional = 1;
if(verbosity) log_info("bidirectional stream");
if(!reply_with_accept(data)) {
tap_data_free(data);
tap_data_free(data, 1);
return;
}
} else if(data->len >= 4 && sldns_read_uint32(data->frame) ==
FSTRM_CONTROL_FRAME_STOP && data->is_bidirectional) {
if(!reply_with_finish(data)) {
tap_data_free(data);
tap_data_free(data, 1);
return;
}
}
@ -1400,6 +1407,41 @@ static int internal_unittest()
/* clean up */
tap_data_list_delete(socket->data_list);
free(socket);
/* Start again. Add two elements */
socket = calloc(1, sizeof(*socket));
log_assert(socket);
for(i=0; i<2; i++) {
datas[i] = calloc(1, sizeof(struct tap_data));
log_assert(datas[i]);
log_assert(tap_data_list_insert(&socket->data_list, datas[i]));
}
/* sanity base check */
list = socket->data_list;
for(i=0; list; i++) list = list->next;
log_assert(i==2);
/* Free the last data, tail cannot be erased */
list = socket->data_list;
while(list->next) list = list->next;
free(list->d);
list->d = NULL;
tap_data_list_try_to_free_tail(list);
list = socket->data_list;
for(i=0; list; i++) list = list->next;
log_assert(i==2);
/* clean up */
tap_data_list_delete(socket->data_list);
free(socket);
if(log_get_lock()) {
lock_basic_destroy((lock_basic_type*)log_get_lock());
}
checklock_stop();
#ifdef USE_WINSOCK
WSACleanup();
#endif
return 0;
}
@ -1531,6 +1573,9 @@ int main(int argc, char** argv)
config_delstrlist(tcp_list.first);
config_delstrlist(tls_list.first);
if(log_get_lock()) {
lock_basic_destroy((lock_basic_type*)log_get_lock());
}
checklock_stop();
#ifdef USE_WINSOCK
WSACleanup();

View File

@ -1,3 +1,61 @@
9 August 2024: Wouter
- Fix spelling for the cache-min-negative-ttl entry in the
example.conf.
- Tag for release 1.21.0, the repository continues with 1.21.1
in development.
8 August 2024: Wouter
- Fix CAMP issues with global quota. Thanks to Huayi Duan, Marco
Bearzi, Jodok Vieli, and Cagin Tanir from NetSec group, ETH Zurich.
- Fix CacheFlush issues with limit on NS RRs. Thanks to Yehuda Afek,
Anat Bremler-Barr, Shoham Danino and Yuval Shavitt (Tel-Aviv
University and Reichman University).
- Set version number to 1.21.0 for release. This has tag 1.21.0rc1.
- Fix that for windows the module startup is called and sets up
the module-config.
2 August 2024: Wouter
- Fix that alloc stats has strdup checks, it stops debuggers from
complaining about mismatch at free time.
- Fix testbound for alloc stats strdup in util/alloc.c.
- Merge #1090: Cookie secret file. Adds
`cookie-secret-file: "unbound_cookiesecrets.txt"` option to store
cookie secrets for EDNS COOKIE secret rollover. The remote control
add_cookie_secret, activate_cookie_secret and drop_cookie_secret
commands can be used for rollover, the command print_cookie_secrets
shows the values in use.
- Fix that alloc stats for forwards and hints are printed, and when
alloc stats is enabled, the unit test for unbound control waits for
reloads to complete.
1 August 2024: Wouter
- Fix dnstap test program, cleans up to have clean memory on exit,
for tap_data_free, does not delete NULL items. Also it does not try
to free the tail, specifically in the free of the list since that
picked up the next item in the list for its loop causing invalid
free. Added internal unit test to unbound-dnstap-socket for that.
- Fix that the worker mem report with alloc stats does not attempt
to print memory use of forwards and hints if they have been
deleted already.
31 July 2024: Wouter
- Fix for #1114: Fix that cache fill for forward-host names is
performed, so that with nonzero target-fetch-policy it fetches
forwarder addresses and uses them from cache. Also updated that
delegation point cache fill routines use CDflag for AAAA message
lookups, so that its negative lookup stops a recursion since the
cache uses the bit for disambiguation for dns64 but the recursion
uses CDflag for the AAAA target lookups, so the check correctly
stops a useless recursion by its cache lookup.
30 July 2024: Wouter
- Fix to document parameters of auth_zone_verify_zonemd_with_key.
25 July 2024: Wouter
- Add root key 38696 from 2024 for DNSSEC validation. It is added
to the default root keys in unbound-anchor. The content can be
inspected with `unbound-anchor -l`.
23 July 2024: Yorgos
- Fix #1106: ratelimit-below-domain logs the wrong FROM address.
- Cleanup ede.tdir test.

View File

@ -228,7 +228,7 @@ server:
# the time to live (TTL) value lower bound, in seconds. Default 0.
# For negative responses in the cache. If disabled, default,
# cache-min-tll applies if configured.
# cache-min-ttl applies if configured.
# cache-min-negative-ttl: 0
# the time to live (TTL) value for cached roundtrip times, lameness and
@ -1044,6 +1044,11 @@ server:
# example value "000102030405060708090a0b0c0d0e0f".
# cookie-secret: <128 bit random hex string>
# File with cookie secrets, the 'cookie-secret:' option is ignored
# and the file can be managed to have staging and active secrets
# with remote control commands. Disabled with "". Default is "".
# cookie-secret-file: "/usr/local/etc/unbound_cookiesecrets.txt"
# Enable to attach Extended DNS Error codes (RFC8914) to responses.
# ede: no

View File

@ -433,6 +433,41 @@ Remove a list of \fIlocal_data\fR for given view from stdin. Like local_datas_re
.TP
.B view_local_datas \fIview\fR
Add a list of \fIlocal_data\fR for given view from stdin. Like local_datas.
.TP
.B add_cookie_secret <secret>
Add or replace a cookie secret persistently. <secret> needs to be an 128 bit
hex string.
.IP
Cookie secrets can be either \fIactive\fR or \fIstaging\fR. \fIActive\fR cookie
secrets are used to create DNS Cookies, but verification of a DNS Cookie
succeeds with any of the \fIactive\fR or \fIstaging\fR cookie secrets. The
state of the current cookie secrets can be printed with the
\fBprint_cookie_secrets\fR command.
.IP
When there are no cookie secrets configured yet, the <secret> is added as
\fIactive\fR. If there is already an \fIactive\fR cookie secret, the <secret>
is added as \fIstaging\fR or replacing an existing \fIstaging\fR secret.
.IP
To "roll" a cookie secret used in an anycast set. The new secret has to be
added as staging secret to \fBall\fR nodes in the anycast set. When \fBall\fR
nodes can verify DNS Cookies with the new secret, the new secret can be
activated with the \fBactivate_cookie_secret\fR command. After \fBall\fR nodes
have the new secret \fIactive\fR for at least one hour, the previous secret can
be dropped with the \fBdrop_cookie_secret\fR command.
.IP
Persistence is accomplished by writing to a file which if configured with the
\fBcookie\-secret\-file\fR option in the server section of the config file.
This is disabled by default, "".
.TP
.B drop_cookie_secret
Drop the \fIstaging\fR cookie secret.
.TP
.B activate_cookie_secret
Make the current \fIstaging\fR cookie secret \fIactive\fR, and the current
\fIactive\fR cookie secret \fIstaging\fR.
.TP
.B print_cookie_secrets
Show the current configured cookie secrets with their status.
.SH "EXIT CODE"
The unbound\-control program exits with status code 1 on error, 0 on success.
.SH "SET UP"

View File

@ -1983,6 +1983,20 @@ 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.
This option is ignored if a \fBcookie\-secret\-file\fR is
present. In that case the secrets from that file are used in DNS Cookie
calculations.
.TP 5
.B cookie\-secret\-file: \fI<filename>
File from which the secrets are read used in DNS Cookie calculations. When this
file exists, the secrets in this file are used and the secret specified by the
\fBcookie-secret\fR option is ignored.
Enable it by setting a filename, like "/usr/local/etc/unbound_cookiesecrets.txt".
The content of this file must be manipulated with the \fBadd_cookie_secret\fR,
\fBdrop_cookie_secret\fR and \fBactivate_cookie_secret\fR commands to the
\fIunbound\-control\fR(8) tool. Please see that manpage on how to perform a
safe cookie secret rollover.
Default is "" (disabled).
.TP 5
.B edns\-client\-string: \fI<IP netblock> <string>
Include an EDNS0 option containing configured ascii string in queries with

View File

@ -367,6 +367,47 @@ type_allowed_in_additional_section(uint16_t tp)
return 0;
}
/** Shorten RRset */
static void
shorten_rrset(sldns_buffer* pkt, struct rrset_parse* rrset, int count)
{
/* The too large NS RRset is shortened. This is so that too large
* content does not overwhelm the cache. It may make the rrset
* bogus if it was signed, and then the domain is not resolved any
* more, that is okay, the NS RRset was too large. During a referral
* it can be shortened and then the first part of the list could
* be used to resolve. The scrub continues to disallow glue for the
* removed nameserver RRs and removes that too. Because the glue
* is not marked as okay, since the RRs have been removed here. */
int i;
struct rr_parse* rr = rrset->rr_first, *prev = NULL;
if(!rr)
return;
for(i=0; i<count; i++) {
prev = rr;
rr = rr->next;
if(!rr)
return; /* The RRset is already short. */
}
if(verbosity >= VERB_QUERY
&& rrset->dname_len <= LDNS_MAX_DOMAINLEN) {
uint8_t buf[LDNS_MAX_DOMAINLEN+1];
dname_pkt_copy(pkt, buf, rrset->dname);
log_nametypeclass(VERB_QUERY, "normalize: shorten RRset:", buf,
rrset->type, ntohs(rrset->rrset_class));
}
/* remove further rrs */
rrset->rr_last = prev;
rrset->rr_count = count;
while(rr) {
rrset->size -= rr->size;
rr = rr->next;
}
if(rrset->rr_last)
rrset->rr_last->next = NULL;
else rrset->rr_first = NULL;
}
/**
* This routine normalizes a response. This includes removing "irrelevant"
* records from the answer and additional sections and (re)synthesizing
@ -387,6 +428,7 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
uint8_t* sname = qinfo->qname;
size_t snamelen = qinfo->qname_len;
struct rrset_parse* rrset, *prev, *nsset=NULL;
int cname_length = 0; /* number of CNAMEs, or DNAMEs */
if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR &&
FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN)
@ -401,6 +443,16 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
prev = NULL;
rrset = msg->rrset_first;
while(rrset && rrset->section == LDNS_SECTION_ANSWER) {
if(cname_length > 11 /* env->cfg.iter_scrub_cname */) {
/* Too many CNAMEs, or DNAMEs, from the authority
* server, scrub down the length to something
* shorter. This deletes everything after the limit
* is reached. The iterator is going to look up
* the content one by one anyway. */
remove_rrset("normalize: removing because too many cnames:",
pkt, msg, prev, &rrset);
continue;
}
if(rrset->type == LDNS_RR_TYPE_DNAME &&
pkt_strict_sub(pkt, sname, rrset->dname)) {
/* check if next rrset is correct CNAME. else,
@ -420,6 +472,7 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
"too long");
return 0;
}
cname_length++;
if(nx && nx->type == LDNS_RR_TYPE_CNAME &&
dname_pkt_compare(pkt, sname, nx->dname) == 0) {
/* check next cname */
@ -460,6 +513,7 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
if(rrset->type == LDNS_RR_TYPE_CNAME) {
struct rrset_parse* nx = rrset->rrset_all_next;
uint8_t* oldsname = sname;
cname_length++;
/* see if the next one is a DNAME, if so, swap them */
if(nx && nx->section == LDNS_SECTION_ANSWER &&
nx->type == LDNS_RR_TYPE_DNAME &&
@ -507,6 +561,10 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
LDNS_SECTION_ANSWER &&
dname_pkt_compare(pkt, oldsname,
rrset->dname) == 0) {
if(rrset->type == LDNS_RR_TYPE_NS &&
rrset->rr_count > 20 /* env->cfg->iter_scrub_ns */) {
shorten_rrset(pkt, rrset, 20 /* env->cfg->iter_scrub_ns */);
}
prev = rrset;
rrset = rrset->rrset_all_next;
}
@ -522,6 +580,11 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
continue;
}
if(rrset->type == LDNS_RR_TYPE_NS &&
rrset->rr_count > 20 /* env->cfg->iter_scrub_ns */) {
shorten_rrset(pkt, rrset, 20 /* env->cfg->iter_scrub_ns */);
}
/* Mark the additional names from relevant rrset as OK. */
/* only for RRsets that match the query name, other ones
* will be removed by sanitize, so no additional for them */
@ -578,6 +641,25 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
"RRset:", pkt, msg, prev, &rrset);
continue;
}
if(rrset->rr_count > 20 /* env->cfg->iter_scrub_ns */) {
/* If this is not a referral, and the NS RRset
* is signed, then remove it entirely, so
* that when it becomes bogus it does not
* make the message that is otherwise fine
* into a bogus message. */
if(!(msg->an_rrsets == 0 &&
FLAGS_GET_RCODE(msg->flags) ==
LDNS_RCODE_NOERROR &&
!soa_in_auth(msg) &&
!(msg->flags & BIT_AA)) &&
rrset->rrsig_count != 0) {
remove_rrset("normalize: removing too large NS "
"RRset:", pkt, msg, prev, &rrset);
continue;
} else {
shorten_rrset(pkt, rrset, 20 /* env->cfg->iter_scrub_ns */);
}
}
}
/* if this is type DS and we query for type DS we just got
* a referral answer for our type DS query, fix packet */

View File

@ -747,6 +747,14 @@ target_count_increase_nx(struct iter_qstate* iq, int num)
iq->target_count[TARGET_COUNT_NX] += num;
}
static void
target_count_increase_global_quota(struct iter_qstate* iq, int num)
{
target_count_create(iq);
if(iq->target_count)
iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] += num;
}
/**
* Generate a subrequest.
* Generate a local request event. Local events are tied to this module, and
@ -1547,6 +1555,11 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
errinf(qstate, "malloc failure for forward zone");
return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
}
if(!cache_fill_missing(qstate->env, iq->qchase.qclass,
qstate->region, iq->dp)) {
errinf(qstate, "malloc failure, copy extra info into delegation point");
return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
}
if((qstate->query_flags&BIT_RD)==0) {
/* If the server accepts RD=0 queries and forwards
* with RD=1, then if the server is listed as an NS
@ -2995,6 +3008,17 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
}
}
target_count_increase_global_quota(iq, 1);
if(iq->target_count && iq->target_count[TARGET_COUNT_GLOBAL_QUOTA]
> MAX_GLOBAL_QUOTA) {
char s[LDNS_MAX_DOMAINLEN+1];
dname_str(qstate->qinfo.qname, s);
verbose(VERB_QUERY, "request %s has exceeded the maximum "
"global quota on number of upstream queries %d", s,
iq->target_count[TARGET_COUNT_GLOBAL_QUOTA]);
return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL);
}
/* Do not check ratelimit for forwarding queries or if we already got a
* pass. */
sq_check_ratelimit = (!(iq->chase_flags & BIT_RD) && !iq->ratelimit_ok);

View File

@ -53,6 +53,9 @@ struct rbtree_type;
/** max number of targets spawned for a query and its subqueries */
#define MAX_TARGET_COUNT 64
/** max number of upstream queries for a query and its subqueries, it is
* never reset. */
#define MAX_GLOBAL_QUOTA 128
/** max number of target lookups per qstate, per delegation point */
#define MAX_DP_TARGET_COUNT 16
/** max number of nxdomains allowed for target lookups for a query and
@ -254,6 +257,9 @@ enum target_count_variables {
TARGET_COUNT_QUERIES,
/** Number of nxdomain responses encountered. */
TARGET_COUNT_NX,
/** Global quota on number of queries to upstream servers per
* client request, that is never reset. */
TARGET_COUNT_GLOBAL_QUOTA,
/** This should stay last here, it is used for the allocation */
TARGET_COUNT_MAX,

14
services/cache/dns.c vendored
View File

@ -346,6 +346,13 @@ find_add_addrs(struct module_env* env, uint16_t qclass,
* not use dns64 translation */
neg = msg_cache_lookup(env, ns->name, ns->namelen,
LDNS_RR_TYPE_AAAA, qclass, 0, now, 0);
/* Because recursion for lookup uses BIT_CD, check
* for that so it stops the recursion lookup, if a
* negative answer is cached. Because the cache uses
* the CD flag for type AAAA. */
if(!neg)
neg = msg_cache_lookup(env, ns->name, ns->namelen,
LDNS_RR_TYPE_AAAA, qclass, BIT_CD, now, 0);
if(neg) {
delegpt_add_neg_msg(dp, neg);
lock_rw_unlock(&neg->entry.lock);
@ -405,6 +412,13 @@ cache_fill_missing(struct module_env* env, uint16_t qclass,
* not use dns64 translation */
neg = msg_cache_lookup(env, ns->name, ns->namelen,
LDNS_RR_TYPE_AAAA, qclass, 0, now, 0);
/* Because recursion for lookup uses BIT_CD, check
* for that so it stops the recursion lookup, if a
* negative answer is cached. Because the cache uses
* the CD flag for type AAAA. */
if(!neg)
neg = msg_cache_lookup(env, ns->name, ns->namelen,
LDNS_RR_TYPE_AAAA, qclass, BIT_CD, now, 0);
if(neg) {
delegpt_add_neg_msg(dp, neg);
lock_rw_unlock(&neg->entry.lock);

View File

@ -183,7 +183,9 @@ static const char DS_TRUST_ANCHOR[] =
/* The anchors must start on a new line with ". IN DS and end with \n"[;]
* because the makedist script greps on the source here */
/* anchor 20326 is from 2017 */
". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D\n";
". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D\n"
/* anchor 38696 is from 2024 */
". IN DS 38696 8 2 683D2D0ACB8C9B712A1948B27F741219298D0A450D612C483AF444A4C0FB2B16\n";
/** verbosity for this application */
static int verb = 0;

View File

@ -190,6 +190,10 @@ usage(void)
printf(" rpz_enable zone Enable the RPZ zone if it had previously\n");
printf(" been disabled\n");
printf(" rpz_disable zone Disable the RPZ zone\n");
printf(" add_cookie_secret <secret> add (or replace) a new cookie secret <secret>\n");
printf(" drop_cookie_secret drop a staging cookie secret\n");
printf(" activate_cookie_secret make a staging cookie secret active\n");
printf(" print_cookie_secrets show all cookie secrets with their status\n");
printf("Version %s\n", PACKAGE_VERSION);
printf("BSD licensed, see LICENSE in source package for details.\n");
printf("Report bugs to %s\n", PACKAGE_BUGREPORT);

View File

@ -72,23 +72,6 @@ int daemon_main(int argc, char* argv[]);
/** config files (removed at exit) */
static struct config_strlist* cfgfiles = NULL;
#ifdef UNBOUND_ALLOC_STATS
# define strdup(s) unbound_stat_strdup_log(s, __FILE__, __LINE__, __func__)
char* unbound_stat_strdup_log(char* s, const char* file, int line,
const char* func);
char* unbound_stat_strdup_log(char* s, const char* file, int line,
const char* func) {
char* result;
size_t len;
if(!s) return NULL;
len = strlen(s);
log_info("%s:%d %s strdup(%u)", file, line, func, (unsigned)len+1);
result = unbound_stat_malloc(len+1);
memmove(result, s, len+1);
return result;
}
#endif /* UNBOUND_ALLOC_STATS */
/** give commandline usage for testbound. */
static void
testbound_usage(void)

View File

@ -1117,7 +1117,7 @@ static void edns_ede_encode_encodedecode(struct query_info* qinfo,
sldns_buffer_skip(pkt, 2 + 2);
/* decode */
unit_assert(parse_edns_from_query_pkt(pkt, edns, NULL, NULL, NULL, 0,
region) == 0);
region, NULL) == 0);
}
static void edns_ede_encode_check(struct edns_data* edns, int* found_ede,

View File

@ -73,12 +73,53 @@ control_command () {
$PRE/unbound-control $@ > outfile
}
# Reload the server and check the reload has finished processing
# when a lot of debug is enabled, a lot of log needs to be printed.
control_reload () {
prelines=`wc -l unbound.log | awk '{print $1;}'`
cmd="$1"
if test -z "$cmd"; then cmd="reload"; fi
control_command -c ub.conf $cmd
expect_exit_value 0
# see if the reload has completed.
lines1=`wc -l unbound.log | awk '{print $1;}'`
count=0
lines2=`wc -l unbound.log | awk '{print $1;}'`
# See if the log finishes up without sleeping too long.
while test "$lines1" -ne "$lines2"; do
lines1=`wc -l unbound.log | awk '{print $1;}'`
# There is no sleep here. The add and compare are a
# brief wait.
count=`expr "$count" + 1`
if test "$count" -gt 30; then
break;
fi
lines2=`wc -l unbound.log | awk '{print $1;}'`
done
if test "$lines1" -ne "$lines2"; then
count=0
while test "$lines1" -ne "$lines2"; do
tail -1 unbound.log
lines1=`wc -l unbound.log | awk '{print $1;}'`
sleep 1
count=`expr "$count" + 1`
if test "$count" -gt 30; then
echo "reload is taking too long"
exit 1
fi
lines2=`wc -l unbound.log | awk '{print $1;}'`
done
if test "$count" -ne "0"; then
echo "reload done with $count sec"
fi
fi
}
# Reload the server for a clean state
clean_reload () {
echo "> Reloading the server for a clean state"
cp main.conf ub.conf
control_command -c ub.conf reload
expect_exit_value 0
control_reload
}
# Reload the server for a clean state and populate the cache
@ -175,8 +216,7 @@ expect_exit_value 1
# local-data element in the server.
teststep "reload the server"
echo "server: local-data: 'afterreload. IN A 5.6.7.8'" >> ub.conf
control_command -c ub.conf reload
expect_exit_value 0
control_reload
query afterreload.
expect_answer "5.6.7.8"
@ -336,16 +376,14 @@ fi
clean_reload_and_fill_cache
teststep "reload and check cache - should be empty"
control_command -c ub.conf reload
expect_exit_value 0
control_reload
query www.example.com +nordflag
fail_answer "10.20.30.40"
clean_reload_and_fill_cache
teststep "reload_keep_cache and check cache - should not be empty"
control_command -c ub.conf reload_keep_cache
expect_exit_value 0
control_reload reload_keep_cache
query www.example.com +nordflag
expect_answer "10.20.30.40"
@ -353,8 +391,7 @@ clean_reload_and_fill_cache
teststep "change msg-cache-size and reload_keep_cache - should be empty"
echo "server: msg-cache-size: 2m" >> ub.conf
control_command -c ub.conf reload_keep_cache
expect_exit_value 0
control_reload reload_keep_cache
query www.example.com +nordflag
fail_answer "10.20.30.40"
@ -362,8 +399,7 @@ clean_reload_and_fill_cache
teststep "change rrset-cache-size and reload_keep_cache - should be empty"
echo "server: rrset-cache-size: 2m" >> ub.conf
control_command -c ub.conf reload_keep_cache
expect_exit_value 0
control_reload reload_keep_cache
query www.example.com +nordflag
fail_answer "10.20.30.40"
@ -375,8 +411,7 @@ clean_reload_and_fill_cache
teststep "change num-threads and reload_keep_cache - should be empty"
echo "server: num-threads: 2" >> ub.conf
control_command -c ub.conf reload_keep_cache
expect_exit_value 0
control_reload reload_keep_cache
query www.example.com +nordflag
fail_answer "10.20.30.40"
@ -384,8 +419,7 @@ clean_reload_and_fill_cache
teststep "change minimal-responses and reload_keep_cache - should not be empty"
echo "server: minimal-responses: no" >> ub.conf
control_command -c ub.conf reload_keep_cache
expect_exit_value 0
control_reload reload_keep_cache
query www.example.com +nordflag
expect_answer "10.20.30.40"

View File

@ -0,0 +1,19 @@
server:
verbosity: 7
use-syslog: no
directory: ""
pidfile: "unbound.pid"
chroot: ""
username: ""
do-not-query-localhost: no
use-caps-for-id: no
port: @SERVER_PORT@
interface: 127.0.0.1
cookie-secret-file: "cookie_secrets.txt"
answer-cookie: yes
access-control: 127.0.0.0/8 allow_cookie # BADCOOKIE for incomplete/invalid cookies
remote-control:
control-enable: yes
control-port: @CONTROL_PORT@
control-use-cert: no

View File

@ -0,0 +1,16 @@
BaseName: cookie_file
Version: 1.0
Description: Check the cookie rollover
CreationDate: Fri 14 Jun 11:00:00 CEST 2024
Maintainer:
Category:
Component:
CmdDepends:
Depends:
Help:
Pre: cookie_file.pre
Post: cookie_file.post
Test: cookie_file.test
AuxFiles:
Passed:
Failure:

View File

@ -0,0 +1,10 @@
# #-- cookie_file.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_from_pidfile "unbound.pid"
cat unbound.log

View File

@ -0,0 +1,24 @@
# #-- cookie_file.pre--#
PRE="../.."
. ../common.sh
get_random_port 2
SERVER_PORT=$RND_PORT
CONTROL_PORT=$(($RND_PORT + 1))
echo "SERVER_PORT=$SERVER_PORT" >> .tpkg.var.test
echo "CONTROL_PORT=$CONTROL_PORT" >> .tpkg.var.test
# make config file
sed \
-e 's/@SERVER_PORT\@/'$SERVER_PORT'/' \
-e 's/@CONTROL_PORT\@/'$CONTROL_PORT'/' \
< cookie_file.conf > ub.conf
# empty cookie file
touch cookie_secrets.txt
# start unbound in the background
$PRE/unbound -d -c ub.conf > unbound.log 2>&1 &
cat .tpkg.var.test
wait_unbound_up unbound.log

View File

@ -0,0 +1,248 @@
# #-- cookie_file.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
first_secret=dd3bdf9344b678b185a6f5cb60fca715
second_secret=445536bcd2513298075a5d379663c962
teststep "Add first secret"
echo ">> add_cookie_secret $first_secret"
$PRE/unbound-control -c ub.conf add_cookie_secret $first_secret
# check secret is persisted
outfile=cookie_secrets.1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
if ! grep -q "$first_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if ! grep -q "$first_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if ! grep -q "$first_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if ! grep -q "^active.*$first_secret" $outfile
then
cat $outfile
echo "First secret was not provisioned"
exit 1
fi
echo ">> print_cookie_secrets"
cat $outfile
teststep "Get a valid cookie for this secret"
outfile=dig.output.1
dig version.server ch txt @127.0.0.1 -p $SERVER_PORT +cookie=3132333435363738 > $outfile
if ! grep -q "BADCOOKIE" $outfile
then
cat $outfile
echo "Did not get a BADCOOKIE response for a client-only cookie"
exit 1
fi
if ! grep -q "COOKIE: 3132333435363738" $outfile
then
cat $outfile
echo "Did not get a cookie in the response"
exit 1
fi
first_cookie=$(grep "; COOKIE:" $outfile | cut -d ' ' -f 3)
cat $outfile
echo "first cookie: $first_cookie"
teststep "Verify the first cookie can be reused"
outfile=dig.output.2
dig version.server ch txt @127.0.0.1 -p $SERVER_PORT +cookie=$first_cookie > $outfile
if grep -q "BADCOOKIE" $outfile
then
cat $outfile
echo "Got BADCOOKIE response for a valid cookie"
exit 1
fi
if ! grep -q "COOKIE: $first_cookie" $outfile
then
cat $outfile
echo "Did not get the same first cookie in the response"
exit 1
fi
teststep "Add second secret"
outfile=cookie_secrets.2
echo ">> add_cookie_secret $second_secret"
$PRE/unbound-control -c ub.conf add_cookie_secret $second_secret
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
if ! grep -q "$second_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if ! grep -q "$second_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if ! grep -q "$second_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if ! grep -q "^staging.*$second_secret" $outfile \
|| ! grep -q "^active.*$first_secret" $outfile
then
cat $outfile
echo "Secrets were not provisioned"
exit 1
fi
echo ">> print_cookie_secrets"
cat $outfile
echo ">> cookie_secrets.txt"
cat cookie_secrets.txt
teststep "Verify the first cookie can be reused"
outfile=dig.output.3
dig version.server ch txt @127.0.0.1 -p $SERVER_PORT +cookie=$first_cookie > $outfile
if grep -q "BADCOOKIE" $outfile
then
cat $outfile
echo "Got BADCOOKIE response for a valid cookie"
exit 1
fi
if ! grep -q "COOKIE: $first_cookie" $outfile
then
cat $outfile
echo "Did not get the same first cookie in the response"
exit 1
fi
teststep "Secret rollover"
outfile=cookie_secrets.3
$PRE/unbound-control -c ub.conf activate_cookie_secret
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
if ! grep -q "^active.*$second_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if ! grep -q "^active.*$second_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if ! grep -q "^active.*$second_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if ! grep -q "^active.*$second_secret" $outfile \
|| ! grep -q "^staging.*$first_secret" $outfile
then
cat $outfile
echo "Second secret was not activated"
exit 1
fi
echo ">> activate cookie secret, printout"
cat $outfile
echo ">> cookie_secrets.txt"
cat cookie_secrets.txt
teststep "Verify the first cookie can be reused but a new cookie is returned from the second secret"
outfile=dig.output.4
dig version.server ch txt @127.0.0.1 -p $SERVER_PORT +cookie=$first_cookie > $outfile
if grep -q "BADCOOKIE" $outfile
then
cat $outfile
echo "Got BADCOOKIE response for a valid cookie"
exit 1
fi
if ! grep -q "COOKIE: 3132333435363738" $outfile
then
cat $outfile
echo "Did not get a cookie in the response"
exit 1
fi
if grep -q "COOKIE: $first_cookie" $outfile
then
cat $outfile
echo "Got the same first cookie in the response while the second secret is active"
exit 1
fi
second_cookie=$(grep "; COOKIE:" $outfile | cut -d ' ' -f 3)
cat $outfile
echo "second cookie: $second_cookie"
teststep "Drop cookie secret"
outfile=cookie_secrets.4
$PRE/unbound-control -c ub.conf drop_cookie_secret
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
if grep -q "^staging.*$first_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if grep -q "^staging.*$first_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if grep -q "^staging.*$first_secret" $outfile
then
sleep 1
$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
fi
if grep -q "^staging.*$first_secret" $outfile
then
cat $outfile
echo "First secret was not dropped"
exit 1
fi
echo ">> drop cookie secret, printout"
cat $outfile
echo ">> cookie_secrets.txt"
cat cookie_secrets.txt
teststep "Verify the first cookie can not be reused and the second cookie is returned instead"
outfile=dig.output.4
dig version.server ch txt @127.0.0.1 -p $SERVER_PORT +cookie=$first_cookie > $outfile
if ! grep -q "BADCOOKIE" $outfile
then
cat $outfile
echo "Did not get BADCOOKIE response for an invalid cookie"
exit 1
fi
if ! grep -q "COOKIE: 3132333435363738" $outfile
then
cat $outfile
echo "Did not get a cookie in the response"
exit 1
fi
if grep -q "COOKIE: $first_cookie" $outfile
then
cat $outfile
echo "Got the same first cookie in the response while the second secret is active"
exit 1
fi
if ! grep -q "COOKIE: $second_cookie" $outfile
then
cat $outfile
echo "Did not get the same second cookie in the response"
exit 1
fi
exit 0

View File

@ -12,4 +12,6 @@ kill_pid $FWD_PID
kill $UNBOUND_PID
kill $UNBOUND_PID >/dev/null 2>&1
cat unbound.log
cat tap.log
cat tap.errlog
exit 0

View File

@ -122,8 +122,6 @@ if test $num_responses -gt 2; then
fi
echo "> cat logfiles"
cat tap.log
cat tap.errlog
cat fwd.log
echo "> OK"
exit 0

152
testdata/fwd_name_lookup.rpl vendored Normal file
View File

@ -0,0 +1,152 @@
; config options
server:
# must have target-fetch-policy to fetch forward-host name.
target-fetch-policy: "3 2 1 0 0"
qname-minimisation: no
minimal-responses: no
forward-zone:
name: "."
forward-addr: 1.2.3.4
forward-host: ns.example.com
CONFIG_END
SCENARIO_BEGIN Test forward with forward-host lookup for more addresses
; Forward server
RANGE_BEGIN 0 15
ADDRESS 1.2.3.4
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns.example.com. IN A
SECTION ANSWER
ns.example.com. IN A 1.2.3.4
ns.example.com. IN A 1.2.3.5
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
ns.example.com. IN AAAA
SECTION ANSWER
SECTION AUTHORITY
example.com. IN SOA ns.example.com. host.example.com. 3 3600 300 86400 3600
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
www.example.com. IN A
SECTION ANSWER
www.example.com. IN A 1.2.3.6
ENTRY_END
RANGE_END
; The forward server gives no answers.
RANGE_BEGIN 20 55
ADDRESS 1.2.3.4
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR SERVFAIL
SECTION QUESTION
www2.example.com. IN A
SECTION ANSWER
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR SERVFAIL
SECTION QUESTION
www3.example.com. IN A
SECTION ANSWER
ENTRY_END
RANGE_END
; The other forward server.
RANGE_BEGIN 20 55
ADDRESS 1.2.3.5
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
www2.example.com. IN A
SECTION ANSWER
www2.example.com. IN A 1.2.3.7
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
www3.example.com. IN A
SECTION ANSWER
www3.example.com. IN A 1.2.3.8
ENTRY_END
RANGE_END
STEP 1 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
www.example.com. IN A
ENTRY_END
; recursion happens here.
STEP 10 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
REPLY QR RD RA NOERROR
SECTION QUESTION
www.example.com. IN A
SECTION ANSWER
www.example.com. IN A 1.2.3.6
ENTRY_END
; The address 1.2.3.4 is not responding so it has to fail over to the
; address from the name lookup.
STEP 20 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
www2.example.com. IN A
ENTRY_END
STEP 30 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
REPLY QR RD RA NOERROR
SECTION QUESTION
www2.example.com. IN A
SECTION ANSWER
www2.example.com. IN A 1.2.3.7
ENTRY_END
STEP 40 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
www3.example.com. IN A
ENTRY_END
STEP 50 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
REPLY QR RD RA NOERROR
SECTION QUESTION
www3.example.com. IN A
SECTION ANSWER
www3.example.com. IN A 1.2.3.8
ENTRY_END
SCENARIO_END

View File

@ -466,6 +466,18 @@ void *unbound_stat_realloc(void *ptr, size_t size)
memcpy(res+8, &mem_special, sizeof(mem_special));
return res+16;
}
/** strdup with stats */
char *unbound_stat_strdup(const char* s)
{
size_t len;
char* res;
if(!s) return NULL;
len = strlen(s);
res = unbound_stat_malloc(len+1);
if(!res) return NULL;
memmove(res, s, len+1);
return res;
}
/** log to file where alloc was done */
void *unbound_stat_malloc_log(size_t size, const char* file, int line,
@ -507,6 +519,15 @@ void *unbound_stat_realloc_log(void *ptr, size_t size, const char* file,
return unbound_stat_realloc(ptr, size);
}
/** log to file where strdup was done */
char *unbound_stat_strdup_log(const char *s, const char* file, int line,
const char* func)
{
log_info("%s:%d %s strdup size %u", file, line, func,
(s?(unsigned)strlen(s)+1:0));
return unbound_stat_strdup(s);
}
#endif /* UNBOUND_ALLOC_STATS */
#ifdef UNBOUND_ALLOC_LITE
#undef malloc

View File

@ -387,6 +387,7 @@ config_create(void)
memset(cfg->cookie_secret, 0, sizeof(cfg->cookie_secret));
cfg->cookie_secret_len = 16;
init_cookie_secret(cfg->cookie_secret, cfg->cookie_secret_len);
cfg->cookie_secret_file = NULL;
#ifdef USE_CACHEDB
if(!(cfg->cachedb_backend = strdup("testframe"))) goto error_exit;
if(!(cfg->cachedb_secret = strdup("default"))) goto error_exit;
@ -839,6 +840,8 @@ int config_set_option(struct config_file* cfg, const char* opt,
{ IS_NUMBER_OR_ZERO; cfg->ipsecmod_max_ttl = atoi(val); }
else S_YNO("ipsecmod-strict:", ipsecmod_strict)
#endif
else S_YNO("answer-cookie:", do_answer_cookie)
else S_STR("cookie-secret-file:", cookie_secret_file)
#ifdef USE_CACHEDB
else S_YNO("cachedb-no-store:", cachedb_no_store)
else S_YNO("cachedb-check-when-serve-expired:", cachedb_check_when_serve_expired)
@ -1336,6 +1339,8 @@ config_get_option(struct config_file* cfg, const char* opt,
else O_LST(opt, "ipsecmod-whitelist", ipsecmod_whitelist)
else O_YNO(opt, "ipsecmod-strict", ipsecmod_strict)
#endif
else O_YNO(opt, "answer-cookie", do_answer_cookie)
else O_STR(opt, "cookie-secret-file", cookie_secret_file)
#ifdef USE_CACHEDB
else O_STR(opt, "backend", cachedb_backend)
else O_STR(opt, "secret-seed", cachedb_secret)
@ -1721,6 +1726,7 @@ config_delete(struct config_file* cfg)
free(cfg->ipsecmod_hook);
config_delstrlist(cfg->ipsecmod_whitelist);
#endif
free(cfg->cookie_secret_file);
#ifdef USE_CACHEDB
free(cfg->cachedb_backend);
free(cfg->cachedb_secret);

View File

@ -750,6 +750,8 @@ struct config_file {
uint8_t cookie_secret[40];
/** cookie secret length */
size_t cookie_secret_len;
/** path to cookie secret store */
char* cookie_secret_file;
/* ipset module */
#ifdef USE_IPSET

View File

@ -582,6 +582,7 @@ udp-upstream-without-downstream{COLON} { YDVAR(1, VAR_UDP_UPSTREAM_WITHOUT_DOWNS
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) }
cookie-secret-file{COLON} { YDVAR(1, VAR_COOKIE_SECRET_FILE) }
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

@ -205,6 +205,7 @@ extern struct config_parser_state* cfg_parser;
%token VAR_PROXY_PROTOCOL_PORT VAR_STATISTICS_INHIBIT_ZERO
%token VAR_HARDEN_UNKNOWN_ADDITIONAL VAR_DISABLE_EDNS_DO VAR_CACHEDB_NO_STORE
%token VAR_LOG_DESTADDR VAR_CACHEDB_CHECK_WHEN_SERVE_EXPIRED
%token VAR_COOKIE_SECRET_FILE
%%
toplevelvars: /* empty */ | toplevelvars toplevelvar ;
@ -342,7 +343,7 @@ content_server: server_num_threads | server_verbosity | server_port |
server_interface_automatic_ports | server_ede |
server_proxy_protocol_port | server_statistics_inhibit_zero |
server_harden_unknown_additional | server_disable_edns_do |
server_log_destaddr
server_log_destaddr | server_cookie_secret_file
;
stubstart: VAR_STUB_ZONE
{
@ -3998,45 +3999,52 @@ server_cookie_secret: VAR_COOKIE_SECRET STRING_ARG
free($2);
}
;
ipsetstart: VAR_IPSET
{
OUTYY(("\nP(ipset:)\n"));
cfg_parser->started_toplevel = 1;
}
;
contents_ipset: contents_ipset content_ipset
| ;
content_ipset: ipset_name_v4 | ipset_name_v6
;
ipset_name_v4: VAR_IPSET_NAME_V4 STRING_ARG
{
#ifdef USE_IPSET
OUTYY(("P(name-v4:%s)\n", $2));
if(cfg_parser->cfg->ipset_name_v4)
yyerror("ipset name v4 override, there must be one "
"name for ip v4");
free(cfg_parser->cfg->ipset_name_v4);
cfg_parser->cfg->ipset_name_v4 = $2;
#else
OUTYY(("P(Compiled without ipset, ignoring)\n"));
free($2);
#endif
}
;
ipset_name_v6: VAR_IPSET_NAME_V6 STRING_ARG
server_cookie_secret_file: VAR_COOKIE_SECRET_FILE STRING_ARG
{
#ifdef USE_IPSET
OUTYY(("P(name-v6:%s)\n", $2));
if(cfg_parser->cfg->ipset_name_v6)
yyerror("ipset name v6 override, there must be one "
"name for ip v6");
free(cfg_parser->cfg->ipset_name_v6);
cfg_parser->cfg->ipset_name_v6 = $2;
#else
OUTYY(("P(Compiled without ipset, ignoring)\n"));
free($2);
#endif
}
OUTYY(("P(cookie_secret_file:%s)\n", $2));
free(cfg_parser->cfg->cookie_secret_file);
cfg_parser->cfg->cookie_secret_file = $2;
}
;
ipsetstart: VAR_IPSET
{
OUTYY(("\nP(ipset:)\n"));
cfg_parser->started_toplevel = 1;
}
;
contents_ipset: contents_ipset content_ipset
| ;
content_ipset: ipset_name_v4 | ipset_name_v6
;
ipset_name_v4: VAR_IPSET_NAME_V4 STRING_ARG
{
#ifdef USE_IPSET
OUTYY(("P(name-v4:%s)\n", $2));
if(cfg_parser->cfg->ipset_name_v4)
yyerror("ipset name v4 override, there must be one "
"name for ip v4");
free(cfg_parser->cfg->ipset_name_v4);
cfg_parser->cfg->ipset_name_v4 = $2;
#else
OUTYY(("P(Compiled without ipset, ignoring)\n"));
free($2);
#endif
}
;
ipset_name_v6: VAR_IPSET_NAME_V6 STRING_ARG
{
#ifdef USE_IPSET
OUTYY(("P(name-v6:%s)\n", $2));
if(cfg_parser->cfg->ipset_name_v6)
yyerror("ipset name v6 override, there must be one "
"name for ip v6");
free(cfg_parser->cfg->ipset_name_v6);
cfg_parser->cfg->ipset_name_v6 = $2;
#else
OUTYY(("P(Compiled without ipset, ignoring)\n"));
free($2);
#endif
}
;
%%

View File

@ -947,7 +947,8 @@ parse_packet(sldns_buffer* pkt, struct msg_parse* msg, struct regional* region)
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 comm_reply* repinfo, uint32_t now, struct regional* region)
struct comm_reply* repinfo, uint32_t now, struct regional* region,
struct cookie_secrets* cookie_secrets)
{
/* To respond with a Keepalive option, the client connection must have
* received one message with a TCP Keepalive EDNS option, and that
@ -1070,13 +1071,24 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
&((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);
if(cfg->cookie_secret_file &&
cfg->cookie_secret_file[0]) {
/* Loop over the active and staging cookies. */
cookie_val_status =
cookie_secrets_server_validate(
rdata_ptr, opt_len, cookie_secrets,
cookie_is_v4, server_cookie, now);
} else {
/* Use the cookie option value to validate. */
cookie_val_status = edns_cookie_server_validate(
rdata_ptr, opt_len, cfg->cookie_secret,
cfg->cookie_secret_len, cookie_is_v4,
server_cookie, now);
}
if(cookie_val_status == COOKIE_STATUS_VALID_RENEW)
edns->cookie_valid = 1;
switch(cookie_val_status) {
case COOKIE_STATUS_VALID:
case COOKIE_STATUS_VALID_RENEW:
edns->cookie_valid = 1;
/* Reuse cookie */
if(!edns_opt_list_append(
@ -1093,12 +1105,28 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
edns->cookie_client = 1;
ATTR_FALLTHROUGH
/* fallthrough */
case COOKIE_STATUS_VALID_RENEW:
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(cfg->cookie_secret_file &&
cfg->cookie_secret_file[0]) {
if(!cookie_secrets)
break;
lock_basic_lock(&cookie_secrets->lock);
if(cookie_secrets->cookie_count < 1) {
lock_basic_unlock(&cookie_secrets->lock);
break;
}
edns_cookie_server_write(server_cookie,
cookie_secrets->cookie_secrets[0].cookie_secret,
cookie_is_v4, now);
lock_basic_unlock(&cookie_secrets->lock);
} else {
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)) {
@ -1240,7 +1268,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 comm_reply* repinfo, time_t now, struct regional* region)
struct comm_reply* repinfo, time_t now, struct regional* region,
struct cookie_secrets* cookie_secrets)
{
size_t rdata_len;
uint8_t* rdata_ptr;
@ -1286,7 +1315,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, repinfo, now, region);
c, repinfo, now, region, cookie_secrets);
}
void

View File

@ -73,6 +73,7 @@ struct edns_option;
struct config_file;
struct comm_point;
struct comm_reply;
struct cookie_secrets;
/** number of buckets in parse rrset hash table. Must be power of 2. */
#define PARSE_TABLE_SIZE 32
@ -322,12 +323,14 @@ int skip_pkt_rrs(struct sldns_buffer* pkt, int num);
* @param repinfo: commreply to determine the client address
* @param now: current time
* @param region: region to alloc results in (edns option contents)
* @param cookie_secrets: the cookie secrets for EDNS COOKIE validation.
* @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 comm_reply* repinfo, time_t now, struct regional* region);
struct comm_reply* repinfo, time_t now, struct regional* region,
struct cookie_secrets* cookie_secrets);
/**
* Calculate hash value for rrset in packet.

View File

@ -210,3 +210,189 @@ edns_cookie_server_validate(const uint8_t* cookie, size_t cookie_len,
return COOKIE_STATUS_VALID_RENEW;
return COOKIE_STATUS_VALID;
}
struct cookie_secrets*
cookie_secrets_create(void)
{
struct cookie_secrets* cookie_secrets = calloc(1,
sizeof(*cookie_secrets));
if(!cookie_secrets)
return NULL;
lock_basic_init(&cookie_secrets->lock);
lock_protect(&cookie_secrets->lock, &cookie_secrets->cookie_count,
sizeof(cookie_secrets->cookie_count));
lock_protect(&cookie_secrets->lock, cookie_secrets->cookie_secrets,
sizeof(cookie_secret_type)*UNBOUND_COOKIE_HISTORY_SIZE);
return cookie_secrets;
}
void
cookie_secrets_delete(struct cookie_secrets* cookie_secrets)
{
if(!cookie_secrets)
return;
lock_basic_destroy(&cookie_secrets->lock);
explicit_bzero(cookie_secrets->cookie_secrets,
sizeof(cookie_secret_type)*UNBOUND_COOKIE_HISTORY_SIZE);
free(cookie_secrets);
}
/** Read the cookie secret file */
static int
cookie_secret_file_read(struct cookie_secrets* cookie_secrets,
char* cookie_secret_file)
{
char secret[UNBOUND_COOKIE_SECRET_SIZE * 2 + 2/*'\n' and '\0'*/];
FILE* f;
int corrupt = 0;
size_t count;
log_assert(cookie_secret_file != NULL);
cookie_secrets->cookie_count = 0;
f = fopen(cookie_secret_file, "r");
/* a non-existing cookie file is not an error */
if( f == NULL ) {
if(errno != EPERM) {
log_err("Could not read cookie-secret-file '%s': %s",
cookie_secret_file, strerror(errno));
return 0;
}
return 1;
}
/* cookie secret file exists and is readable */
for( count = 0; count < UNBOUND_COOKIE_HISTORY_SIZE; count++ ) {
size_t secret_len = 0;
ssize_t decoded_len = 0;
if( fgets(secret, sizeof(secret), f) == NULL ) { break; }
secret_len = strlen(secret);
if( secret_len == 0 ) { break; }
log_assert( secret_len <= sizeof(secret) );
secret_len = secret[secret_len - 1] == '\n' ? secret_len - 1 : secret_len;
if( secret_len != UNBOUND_COOKIE_SECRET_SIZE * 2 ) { corrupt++; break; }
/* needed for `hex_pton`; stripping potential `\n` */
secret[secret_len] = '\0';
decoded_len = hex_pton(secret, cookie_secrets->cookie_secrets[count].cookie_secret,
UNBOUND_COOKIE_SECRET_SIZE);
if( decoded_len != UNBOUND_COOKIE_SECRET_SIZE ) { corrupt++; break; }
cookie_secrets->cookie_count++;
}
fclose(f);
return corrupt == 0;
}
int
cookie_secrets_apply_cfg(struct cookie_secrets* cookie_secrets,
char* cookie_secret_file)
{
if(!cookie_secrets) {
if(!cookie_secret_file || !cookie_secret_file[0])
return 1; /* There is nothing to read anyway */
log_err("Could not read cookie secrets, no structure alloced");
return 0;
}
if(!cookie_secret_file_read(cookie_secrets, cookie_secret_file))
return 0;
return 1;
}
enum edns_cookie_val_status
cookie_secrets_server_validate(const uint8_t* cookie, size_t cookie_len,
struct cookie_secrets* cookie_secrets, int v4,
const uint8_t* hash_input, uint32_t now)
{
size_t i;
enum edns_cookie_val_status cookie_val_status,
last = COOKIE_STATUS_INVALID;
if(!cookie_secrets)
return COOKIE_STATUS_INVALID; /* There are no cookie secrets.*/
lock_basic_lock(&cookie_secrets->lock);
if(cookie_secrets->cookie_count == 0) {
lock_basic_unlock(&cookie_secrets->lock);
return COOKIE_STATUS_INVALID; /* There are no cookie secrets.*/
}
for(i=0; i<cookie_secrets->cookie_count; i++) {
cookie_val_status = edns_cookie_server_validate(cookie,
cookie_len,
cookie_secrets->cookie_secrets[i].cookie_secret,
UNBOUND_COOKIE_SECRET_SIZE, v4, hash_input, now);
if(cookie_val_status == COOKIE_STATUS_VALID ||
cookie_val_status == COOKIE_STATUS_VALID_RENEW) {
lock_basic_unlock(&cookie_secrets->lock);
/* For staging cookies, write a fresh cookie. */
if(i != 0)
return COOKIE_STATUS_VALID_RENEW;
return cookie_val_status;
}
if(last == COOKIE_STATUS_INVALID)
last = cookie_val_status; /* Store more interesting
failure to return. */
}
lock_basic_unlock(&cookie_secrets->lock);
return last;
}
void add_cookie_secret(struct cookie_secrets* cookie_secrets,
uint8_t* secret, size_t secret_len)
{
log_assert(secret_len == UNBOUND_COOKIE_SECRET_SIZE);
(void)secret_len;
if(!cookie_secrets)
return;
/* New cookie secret becomes the staging secret (position 1)
* unless there is no active cookie yet, then it becomes the active
* secret. If the UNBOUND_COOKIE_HISTORY_SIZE > 2 then all staging cookies
* are moved one position down.
*/
if(cookie_secrets->cookie_count == 0) {
memcpy( cookie_secrets->cookie_secrets->cookie_secret
, secret, UNBOUND_COOKIE_SECRET_SIZE);
cookie_secrets->cookie_count = 1;
explicit_bzero(secret, UNBOUND_COOKIE_SECRET_SIZE);
return;
}
#if UNBOUND_COOKIE_HISTORY_SIZE > 2
memmove( &cookie_secrets->cookie_secrets[2], &cookie_secrets->cookie_secrets[1]
, sizeof(struct cookie_secret) * (UNBOUND_COOKIE_HISTORY_SIZE - 2));
#endif
memcpy( cookie_secrets->cookie_secrets[1].cookie_secret
, secret, UNBOUND_COOKIE_SECRET_SIZE);
cookie_secrets->cookie_count = cookie_secrets->cookie_count < UNBOUND_COOKIE_HISTORY_SIZE
? cookie_secrets->cookie_count + 1 : UNBOUND_COOKIE_HISTORY_SIZE;
explicit_bzero(secret, UNBOUND_COOKIE_SECRET_SIZE);
}
void activate_cookie_secret(struct cookie_secrets* cookie_secrets)
{
uint8_t active_secret[UNBOUND_COOKIE_SECRET_SIZE];
if(!cookie_secrets)
return;
/* The staging secret becomes the active secret.
* The active secret becomes a staging secret.
* If the UNBOUND_COOKIE_HISTORY_SIZE > 2 then all staging secrets are moved
* one position up and the previously active secret becomes the last
* staging secret.
*/
if(cookie_secrets->cookie_count < 2)
return;
memcpy( active_secret, cookie_secrets->cookie_secrets[0].cookie_secret
, UNBOUND_COOKIE_SECRET_SIZE);
memmove( &cookie_secrets->cookie_secrets[0], &cookie_secrets->cookie_secrets[1]
, sizeof(struct cookie_secret) * (UNBOUND_COOKIE_HISTORY_SIZE - 1));
memcpy( cookie_secrets->cookie_secrets[cookie_secrets->cookie_count - 1].cookie_secret
, active_secret, UNBOUND_COOKIE_SECRET_SIZE);
explicit_bzero(active_secret, UNBOUND_COOKIE_SECRET_SIZE);
}
void drop_cookie_secret(struct cookie_secrets* cookie_secrets)
{
if(!cookie_secrets)
return;
/* Drops a staging cookie secret. If there are more than one, it will
* drop the last staging secret. */
if(cookie_secrets->cookie_count < 2)
return;
explicit_bzero( cookie_secrets->cookie_secrets[cookie_secrets->cookie_count - 1].cookie_secret
, UNBOUND_COOKIE_SECRET_SIZE);
cookie_secrets->cookie_count -= 1;
}

View File

@ -43,6 +43,7 @@
#define UTIL_EDNS_H
#include "util/storage/dnstree.h"
#include "util/locks.h"
struct edns_data;
struct config_file;
@ -75,6 +76,31 @@ struct edns_string_addr {
size_t string_len;
};
#define UNBOUND_COOKIE_HISTORY_SIZE 2
#define UNBOUND_COOKIE_SECRET_SIZE 16
typedef struct cookie_secret cookie_secret_type;
struct cookie_secret {
/** cookie secret */
uint8_t cookie_secret[UNBOUND_COOKIE_SECRET_SIZE];
};
/**
* The cookie secrets from the cookie-secret-file.
*/
struct cookie_secrets {
/** lock on the structure, in case there are modifications
* from remote control, this avoids race conditions. */
lock_basic_type lock;
/** how many cookies are there in the cookies array */
size_t cookie_count;
/* keep track of the last `UNBOUND_COOKIE_HISTORY_SIZE`
* cookies as per rfc requirement .*/
cookie_secret_type cookie_secrets[UNBOUND_COOKIE_HISTORY_SIZE];
};
enum edns_cookie_val_status {
COOKIE_STATUS_CLIENT_ONLY = -3,
COOKIE_STATUS_FUTURE = -2,
@ -181,4 +207,63 @@ 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);
/**
* Create the cookie secrets structure.
* @return the structure or NULL on failure.
*/
struct cookie_secrets* cookie_secrets_create(void);
/**
* Delete the cookie secrets.
* @param cookie_secrets: the cookie secrets.
*/
void cookie_secrets_delete(struct cookie_secrets* cookie_secrets);
/**
* Apply configuration to cookie secrets, read them from file.
* @param cookie_secrets: the cookie secrets structure.
* @param cookie_secret_file: the file name, it is read.
* @return false on failure.
*/
int cookie_secrets_apply_cfg(struct cookie_secrets* cookie_secrets,
char* cookie_secret_file);
/**
* Validate the cookie secrets, try all of them.
* @param cookie: pointer to the cookie data.
* @param cookie_len: the length of the cookie data.
* @param cookie_secrets: struct of cookie secrets.
* @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 cookie_secrets_server_validate(
const uint8_t* cookie, size_t cookie_len,
struct cookie_secrets* cookie_secrets, int v4,
const uint8_t* hash_input, uint32_t now);
/**
* Add a cookie secret. If there are no secrets yet, the secret will become
* the active secret. Otherwise it will become the staging secret.
* Active secrets are used to both verify and create new DNS Cookies.
* Staging secrets are only used to verify DNS Cookies. Caller has to lock.
*/
void add_cookie_secret(struct cookie_secrets* cookie_secrets, uint8_t* secret,
size_t secret_len);
/**
* Makes the staging cookie secret active and the active secret staging.
* Caller has to lock.
*/
void activate_cookie_secret(struct cookie_secrets* cookie_secrets);
/**
* Drop a cookie secret. Drops the staging secret. An active secret will not
* be dropped. Caller has to lock.
*/
void drop_cookie_secret(struct cookie_secrets* cookie_secrets);
#endif

View File

@ -47,6 +47,7 @@
#ifdef HAVE_NETIOAPI_H
#include <netioapi.h>
#endif
#include <ctype.h>
#include "util/net_help.h"
#include "util/log.h"
#include "util/data/dname.h"
@ -1871,3 +1872,42 @@ sock_close(int socket)
closesocket(socket);
}
# endif /* USE_WINSOCK */
ssize_t
hex_ntop(uint8_t const *src, size_t srclength, char *target, size_t targsize)
{
static char hexdigits[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
size_t i;
if (targsize < srclength * 2 + 1) {
return -1;
}
for (i = 0; i < srclength; ++i) {
*target++ = hexdigits[src[i] >> 4U];
*target++ = hexdigits[src[i] & 0xfU];
}
*target = '\0';
return 2 * srclength;
}
ssize_t
hex_pton(const char* src, uint8_t* target, size_t targsize)
{
uint8_t *t = target;
if(strlen(src) % 2 != 0 || strlen(src)/2 > targsize) {
return -1;
}
while(*src) {
if(!isxdigit((unsigned char)src[0]) ||
!isxdigit((unsigned char)src[1]))
return -1;
*t++ = sldns_hexdigit_to_int(src[0]) * 16 +
sldns_hexdigit_to_int(src[1]) ;
src += 2;
}
return t-target;
}

View File

@ -572,4 +572,13 @@ char* sock_strerror(int errn);
/** close the socket with close, or wsa closesocket */
void sock_close(int socket);
/**
* Convert binary data to a string of hexadecimal characters.
*/
ssize_t hex_ntop(uint8_t const *src, size_t srclength, char *target,
size_t targsize);
/** Convert hexadecimal data to binary. */
ssize_t hex_pton(const char* src, uint8_t* target, size_t targsize);
#endif /* NET_HELP_H */

View File

@ -352,6 +352,10 @@ service_init(int r, struct daemon** d, struct config_file** c)
daemon_apply_cfg(daemon, cfg);
if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2300);
if(!r) {
if(!daemon_privileged(daemon))
fatal_exit("could not do privileged setup");
}
if(!(daemon->rc = daemon_remote_create(cfg))) {
log_err("could not set up remote-control");
daemon_delete(daemon);