TARGETS=nsd nsd-checkconf nsd-checkzone nsd-control nsd.conf.sample nsd-control-setup.sh
MANUALS=nsd.8 nsd-checkconf.8 nsd-checkzone.8 nsd-control.8 nsd.conf.5
-COMMON_OBJ=answer.o axfr.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o bitset.o popen3.o
+COMMON_OBJ=answer.o axfr.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o siphash.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o bitset.o popen3.o
XFRD_OBJ=xfrd-disk.o xfrd-notify.o xfrd-tcp.o xfrd.o remote.o $(DNSTAP_OBJ)
NSD_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) difffile.o ipc.o mini_event.o netio.o nsd.o server.o dbaccess.o dbcreate.o zlexer.o zonec.o zparser.o
ALL_OBJ=$(NSD_OBJ) nsd-checkconf.o nsd-checkzone.o nsd-control.o nsd-mem.o xfr-inspect.o
cpuset.o: $(srcdir)/compat/cpuset.c
$(COMPILE) -c $(srcdir)/compat/cpuset.c
+explicit_bzero.o: $(srcdir)/compat/explicit_bzero.c
+ $(COMPILE) -c $(srcdir)/compat/explicit_bzero.c
+
cutest_dname.o: $(srcdir)/tpkg/cutest/cutest_dname.c
$(COMPILE) -c $(srcdir)/tpkg/cutest/cutest_dname.c
server.o: $(srcdir)/server.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \
$(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \
$(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/netio.h $(srcdir)/xfrd.h $(srcdir)/options.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd-disk.h \
- $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/rrl.h \
- $(srcdir)/dnstap/dnstap_collector.h
+ $(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/rrl.h
+siphash.o: $(srcdir)/siphash.c
tsig.o: $(srcdir)/tsig.c config.h $(srcdir)/tsig.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dname.h \
$(srcdir)/tsig-openssl.h $(srcdir)/dns.h $(srcdir)/packet.h $(srcdir)/namedb.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/query.h $(srcdir)/nsd.h \
$(srcdir)/edns.h
strlcpy.o: $(srcdir)/compat/strlcpy.c config.h
strptime.o: $(srcdir)/compat/strptime.c
setproctitle.o: $(srcdir)/compat/setproctitle.c config.h
+explicit_bzero.o: $(srcdir)/compat/explicit_bzero.c config.h
cutest.o: $(srcdir)/tpkg/cutest/cutest.c config.h $(srcdir)/tpkg/cutest/cutest.h
cutest_dname.o: $(srcdir)/tpkg/cutest/cutest_dname.c config.h $(srcdir)/tpkg/cutest/cutest.h \
$(srcdir)/region-allocator.h $(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h
# Copyright 2009, Wouter Wijngaards, NLnet Labs.
# BSD licensed.
#
-# Version 38
+# Version 40
+# 2021-06-14 fix nonblocking test to use host instead of target for mingw test.
+# 2021-05-17 fix nonblocking socket test from grep on mingw32 to mingw for
+# 64bit compatibility.
# 2021-03-24 fix ACX_FUNC_DEPRECATED to use CPPFLAGS and CFLAGS.
# 2021-01-05 fix defun for aclocal
# 2021-01-05 autoconf 2.70 autoupdate and fixes, no AC_TRY_COMPILE
AC_DEFUN([ACX_CHECK_NONBLOCKING_BROKEN],
[
AC_MSG_CHECKING([if nonblocking sockets work])
-if echo $target | grep mingw32 >/dev/null; then
+if echo $host | grep mingw >/dev/null; then
AC_MSG_RESULT([no (windows)])
AC_DEFINE([NONBLOCKING_IS_BROKEN], 1, [Define if the network stack does not fully support nonblocking io (causes lower performance).])
else
/* apply the noreturn attribute to a function that exits the program */
#undef ATTR_NORETURN
+/* apply the weak attribute to a symbol */
+#undef ATTR_WEAK
+
/* Define this to enable BIND8 like NSTATS & XSTATS. */
#undef BIND8_STATS
/* Whether the C compiler accepts the "unused" attribute */
#undef HAVE_ATTR_UNUSED
+/* Whether the C compiler accepts the "weak" attribute */
+#undef HAVE_ATTR_WEAK
+
/* Define to 1 if you have the `b64_ntop' function. */
#undef HAVE_B64_NTOP
/* Define to 1 if you have the `ev_loop' function. */
#undef HAVE_EV_LOOP
+/* Define to 1 if you have the `explicit_bzero' function. */
+#undef HAVE_EXPLICIT_BZERO
+
/* Define to 1 if you have the <fcntl.h> header file. */
#undef HAVE_FCNTL_H
/* Define to 1 if you have the <time.h> header file. */
#undef HAVE_TIME_H
+/* Define if TLS 1.3 is supported by OpenSSL */
+#undef HAVE_TLS_1_3
+
/* Define to 1 if you have the `tzset' function. */
#undef HAVE_TZSET
#ifndef HAVE_MEMMOVE
void *memmove(void *dest, const void *src, size_t n);
#endif
+#ifndef HAVE_EXPLICIT_BZERO
+#define explicit_bzero nsd_explicit_bzero
+void explicit_bzero(void* buf, size_t len);
+#endif
#ifndef HAVE_STRLCAT
size_t strlcat(char *dst, const char *src, size_t siz);
#endif
allow-query{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ALLOW_QUERY;}
outgoing-interface{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_OUTGOING_INTERFACE;}
allow-axfr-fallback{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ALLOW_AXFR_FALLBACK;}
+tls-auth{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_AUTH;}
+auth-domain-name{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_AUTH_DOMAIN_NAME;}
key{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_KEY;}
algorithm{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ALGORITHM;}
secret{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_SECRET;}
tls-service-ocsp{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_OCSP;}
tls-service-pem{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_SERVICE_PEM;}
tls-port{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_PORT;}
+tls-cert-bundle{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_TLS_CERT_BUNDLE; }
+answer-cookie{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ANSWER_COOKIE;}
+cookie-secret{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET;}
+cookie-secret-file{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET_FILE;}
{NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;}
servers={UNQUOTEDLETTER}* {
extern config_parser_state_type *cfg_parser;
static void append_acl(struct acl_options **list, struct acl_options *acl);
+static void add_to_last_acl(struct acl_options **list, char *ac);
static int parse_boolean(const char *str, int *bln);
static int parse_expire_expr(const char *str, long long *num, uint8_t *expr);
static int parse_number(const char *str, long long *num);
%token VAR_TLS_SERVICE_PEM
%token VAR_TLS_SERVICE_OCSP
%token VAR_TLS_PORT
+%token VAR_TLS_CERT_BUNDLE
%token VAR_CPU_AFFINITY
%token VAR_XFRD_CPU_AFFINITY
%token <llng> VAR_SERVER_CPU_AFFINITY
%token VAR_ALGORITHM
%token VAR_SECRET
+/* xot auth */
+%token VAR_TLS_AUTH
+%token VAR_TLS_AUTH_DOMAIN_NAME
+
/* pattern */
%token VAR_PATTERN
%token VAR_NAME
%token VAR_REQUEST_XFR
%token VAR_ALLOW_AXFR_FALLBACK
%token VAR_OUTGOING_INTERFACE
+%token VAR_ANSWER_COOKIE
+%token VAR_COOKIE_SECRET
+%token VAR_COOKIE_SECRET_FILE
%token VAR_MAX_REFRESH_TIME
%token VAR_MIN_REFRESH_TIME
%token VAR_MAX_RETRY_TIME
| dnstap
| remote_control
| key
+ | tls_auth
| pattern
| zone ;
(void)snprintf(buf, sizeof(buf), "%lld", $2);
cfg_parser->opt->tls_port = region_strdup(cfg_parser->opt->region, buf);
}
+ | VAR_TLS_CERT_BUNDLE STRING
+ { cfg_parser->opt->tls_cert_bundle = region_strdup(cfg_parser->opt->region, $2); }
+ | VAR_ANSWER_COOKIE boolean
+ { cfg_parser->opt->answer_cookie = $2; }
+ | VAR_COOKIE_SECRET STRING
+ { cfg_parser->opt->cookie_secret = region_strdup(cfg_parser->opt->region, $2); }
+ | VAR_COOKIE_SECRET_FILE STRING
+ { cfg_parser->opt->cookie_secret_file = region_strdup(cfg_parser->opt->region, $2); }
| VAR_CPU_AFFINITY cpus
{
cfg_parser->opt->cpu_affinity = $2;
{ cfg_parser->opt->control_cert_file = region_strdup(cfg_parser->opt->region, $2); }
;
+tls_auth:
+ VAR_TLS_AUTH
+ {
+ tls_auth_options_type *tls_auth = tls_auth_options_create(cfg_parser->opt->region);
+ assert(cfg_parser->tls_auth == NULL);
+ cfg_parser->tls_auth = tls_auth;
+ }
+ tls_auth_block
+ {
+ struct tls_auth_options *tls_auth = cfg_parser->tls_auth;
+ if(tls_auth->name == NULL) {
+ yyerror("tls-auth has no name");
+ } else if(tls_auth->auth_domain_name == NULL) {
+ yyerror("tls-auth %s has no auth-domain-name", tls_auth->name);
+ } else if(tls_auth_options_find(cfg_parser->opt, tls_auth->name)) {
+ yyerror("duplicate tls-auth %s", tls_auth->name);
+ } else {
+ tls_auth_options_insert(cfg_parser->opt, tls_auth);
+ cfg_parser->tls_auth = NULL;
+ }
+ } ;
+
+tls_auth_block:
+ tls_auth_block tls_auth_option | ;
+
+tls_auth_option:
+ VAR_NAME STRING
+ {
+ dname_type *dname;
+ dname = (dname_type *)dname_parse(cfg_parser->opt->region, $2);
+ cfg_parser->tls_auth->name = region_strdup(cfg_parser->opt->region, $2);
+ if(dname == NULL) {
+ yyerror("bad tls-auth name %s", $2);
+ } else {
+ region_recycle(cfg_parser->opt->region, dname, dname_total_size(dname));
+ }
+ }
+ | VAR_TLS_AUTH_DOMAIN_NAME STRING
+ {
+ cfg_parser->tls_auth->auth_domain_name = region_strdup(cfg_parser->opt->region, $2);
+ };
+
key:
VAR_KEY
{
yyerror("address range used for request-xfr");
append_acl(&cfg_parser->pattern->request_xfr, acl);
}
+ tlsauth_option
+ { }
| VAR_REQUEST_XFR VAR_AXFR STRING STRING
{
acl_options_type *acl = parse_acl_info(cfg_parser->opt->region, $3, $4);
yyerror("address range used for request-xfr");
append_acl(&cfg_parser->pattern->request_xfr, acl);
}
+ tlsauth_option
+ { }
| VAR_REQUEST_XFR VAR_UDP STRING STRING
{
acl_options_type *acl = parse_acl_info(cfg_parser->opt->region, $3, $4);
}
} ;
+tlsauth_option:
+ | STRING
+ { char *tls_auth_name = region_strdup(cfg_parser->opt->region, $1);
+ add_to_last_acl(&cfg_parser->pattern->request_xfr, tls_auth_name);} ;
+
%%
static void
}
}
+static void
+add_to_last_acl(struct acl_options **list, char *tls_auth_name)
+{
+ struct acl_options *tail = *list;
+ assert(list != NULL);
+ assert(*list != NULL);
+ while(tail->next != NULL)
+ tail = tail->next;
+ tail->tls_auth_name = tls_auth_name;
+}
+
static int
parse_boolean(const char *str, int *bln)
{
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for NSD 4.3.6.
+# Generated by GNU Autoconf 2.69 for NSD 4.3.7.
#
# Report bugs to <nsd-bugs@nlnetlabs.nl>.
#
# Identity of this package.
PACKAGE_NAME='NSD'
PACKAGE_TARNAME='nsd'
-PACKAGE_VERSION='4.3.6'
-PACKAGE_STRING='NSD 4.3.6'
+PACKAGE_VERSION='4.3.7'
+PACKAGE_STRING='NSD 4.3.7'
PACKAGE_BUGREPORT='nsd-bugs@nlnetlabs.nl'
PACKAGE_URL=''
# 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 NSD 4.3.6 to adapt to many kinds of systems.
+\`configure' configures NSD 4.3.7 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of NSD 4.3.6:";;
+ short | recursive ) echo "Configuration of NSD 4.3.7:";;
esac
cat <<\_ACEOF
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-NSD configure 4.3.6
+NSD configure 4.3.7
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by NSD $as_me 4.3.6, which was
+It was created by NSD $as_me 4.3.7, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
+
# Checks for typedefs, structures, and compiler characteristics.
# allow user to override the -g -O2 flags.
if test "x$CFLAGS" = "x" ; then
fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler (${CC-cc}) accepts the \"weak\" attribute" >&5
+$as_echo_n "checking whether the C compiler (${CC-cc}) accepts the \"weak\" attribute... " >&6; }
+if ${ac_cv_c_weak_attribute+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_cv_c_weak_attribute=no
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+ #include <stdio.h>
+__attribute__((weak)) void f(int x) { printf("%d", x); }
+
+int
+main ()
+{
+
+ f(1);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_c_weak_attribute="yes"
+else
+ ac_cv_c_weak_attribute="no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_weak_attribute" >&5
+$as_echo "$ac_cv_c_weak_attribute" >&6; }
+if test $ac_cv_c_weak_attribute = yes; then
+
+$as_echo "#define HAVE_ATTR_WEAK 1" >>confdefs.h
+
+
+$as_echo "#define ATTR_WEAK __attribute__((weak))" >>confdefs.h
+
+fi
+
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler (${CC-cc}) accepts the \"noreturn\" attribute" >&5
$as_echo_n "checking whether the C compiler (${CC-cc}) accepts the \"noreturn\" attribute... " >&6; }
if ${ac_cv_c_noreturn_attribute+:} false; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if nonblocking sockets work" >&5
$as_echo_n "checking if nonblocking sockets work... " >&6; }
-if echo $target | grep mingw32 >/dev/null; then
+if echo $host | grep mingw >/dev/null; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no (windows)" >&5
$as_echo "no (windows)" >&6; }
fi
+ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero"
+if test "x$ac_cv_func_explicit_bzero" = xyes; then :
+ $as_echo "#define HAVE_EXPLICIT_BZERO 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" explicit_bzero.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS explicit_bzero.$ac_objext"
+ ;;
+esac
+
+fi
+
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for reallocarray" >&5
$as_echo_n "checking for reallocarray... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
#define HAVE_DECL_SSL_CTX_SET_TMP_ECDH $ac_have_decl
_ACEOF
+ ac_fn_c_check_decl "$LINENO" "TLS1_3_VERSION" "ac_cv_have_decl_TLS1_3_VERSION" "#include <openssl/ssl.h>
+"
+if test "x$ac_cv_have_decl_TLS1_3_VERSION" = xyes; then :
+
+$as_echo "#define HAVE_TLS_1_3 1" >>confdefs.h
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: No TLS 1.3, therefore XFR-over-TLS is disabled" >&5
+$as_echo "$as_me: WARNING: No TLS 1.3, therefore XFR-over-TLS is disabled" >&2;}
+fi
BAKLIBS="$LIBS"
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by NSD $as_me 4.3.6, which was
+This file was extended by NSD $as_me 4.3.7, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-NSD config.status 4.3.6
+NSD config.status 4.3.7
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
sinclude(acx_nlnetlabs.m4)
sinclude(dnstap/dnstap.m4)
-AC_INIT([NSD],[4.3.6],[nsd-bugs@nlnetlabs.nl])
+AC_INIT([NSD],[4.3.7],[nsd-bugs@nlnetlabs.nl])
AC_CONFIG_HEADERS([config.h])
#
fi
])dnl
+AC_DEFUN([CHECK_WEAK_ATTRIBUTE],
+[AC_REQUIRE([AC_PROG_CC])
+AC_MSG_CHECKING(whether the C compiler (${CC-cc}) accepts the "weak" attribute)
+AC_CACHE_VAL(ac_cv_c_weak_attribute,
+[ac_cv_c_weak_attribute=no
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <stdio.h>
+__attribute__((weak)) void f(int x) { printf("%d", x); }
+]], [[
+ f(1);
+]])],[ac_cv_c_weak_attribute="yes"],[ac_cv_c_weak_attribute="no"])
+])
+
+AC_MSG_RESULT($ac_cv_c_weak_attribute)
+if test $ac_cv_c_weak_attribute = yes; then
+ AC_DEFINE(HAVE_ATTR_WEAK, 1, [Whether the C compiler accepts the "weak" attribute])
+ AC_DEFINE(ATTR_WEAK, [__attribute__((weak))], [apply the weak attribute to a symbol])
+fi
+])dnl End of CHECK_WEAK_ATTRIBUTE
+
AC_DEFUN([CHECK_NORETURN_ATTRIBUTE],
[AC_REQUIRE([AC_PROG_CC])
AC_MSG_CHECKING(whether the C compiler (${CC-cc}) accepts the "noreturn" attribute)
AC_CHECK_FORMAT_ATTRIBUTE
AC_CHECK_UNUSED_ATTRIBUTE
+CHECK_WEAK_ATTRIBUTE
CHECK_NORETURN_ATTRIBUTE
ACX_CHECK_MEMCMP_SIGNED
AC_CHECK_CTIME_R
AC_REPLACE_FUNCS(pselect)
AC_REPLACE_FUNCS(memmove)
AC_REPLACE_FUNCS(setproctitle)
+AC_REPLACE_FUNCS(explicit_bzero)
AC_MSG_CHECKING([for reallocarray])
AC_LINK_IFELSE([AC_LANG_SOURCE(
[[
#include <openssl/ssl.h>
#include <openssl/evp.h>
])
-
+ AC_CHECK_DECL([TLS1_3_VERSION],
+ [AC_DEFINE([HAVE_TLS_1_3], [1], [Define if TLS 1.3 is supported by OpenSSL])],
+ [AC_MSG_WARN([No TLS 1.3, therefore XFR-over-TLS is disabled])], [[#include <openssl/ssl.h>]])
BAKLIBS="$LIBS"
LIBS="-lssl $LIBS"
#ifndef HAVE_MEMMOVE
void *memmove(void *dest, const void *src, size_t n);
#endif
+#ifndef HAVE_EXPLICIT_BZERO
+#define explicit_bzero nsd_explicit_bzero
+void explicit_bzero(void* buf, size_t len);
+#endif
#ifndef HAVE_STRLCAT
size_t strlcat(char *dst, const char *src, size_t siz);
#endif
assert(fname);
if(!file_get_mtime(fname, &mtime, &nonexist)) {
if(nonexist) {
- VERBOSITY(2, (LOG_INFO, "zonefile %s does not exist",
- fname));
+ if(zone_is_slave(zone->opts)) {
+ /* for slave zones not as bad, no zonefile
+ * may just mean we have to transfer it */
+ VERBOSITY(2, (LOG_INFO, "zonefile %s does not exist",
+ fname));
+ } else {
+ /* without a download option, we can never
+ * serve data, more severe error printout */
+ log_msg(LOG_ERR, "zonefile %s does not exist", fname);
+ }
+
} else
log_msg(LOG_ERR, "zonefile %s: %s",
fname, strerror(errno));
if(zone->nsec3_param && rr->type == TYPE_NSEC3 &&
(!rr->owner->nsec3 || !rr->owner->nsec3->nsec3_node.key)
&& nsec3_rr_uses_params(rr, zone)) {
+ if(!zone->nsec3_last) {
+ /* all nsec3s have previously been deleted, but
+ * we have nsec3 parameters, set it up again from
+ * being cleared. */
+ nsec3_precompile_newparam(db, zone);
+ }
/* added NSEC3 into the chain */
nsec3_precompile_nsec3rr(db, rr->owner, zone);
/* the domain has become an NSEC3-domain, if it was precompiled
udb_ptr_unlink(&e, udb);
}
+void task_new_add_cookie_secret(udb_base* udb, udb_ptr* last,
+ const char* secret) {
+ udb_ptr e;
+ char* p;
+ size_t const secret_size = strlen(secret) + 1;
+
+ DEBUG(DEBUG_IPC, 1, (LOG_INFO, "add task add_cookie_secret"));
+
+ if(!task_create_new_elem(udb, last, &e,
+ sizeof(struct task_list_d) + secret_size, NULL)) {
+ log_msg(LOG_ERR, "tasklist: out of space, cannot add add_cookie_secret");
+ return;
+ }
+ TASKLIST(&e)->task_type = task_add_cookie_secret;
+ p = (char*)TASKLIST(&e)->zname;
+ memmove(p, secret, secret_size);
+ udb_ptr_unlink(&e, udb);
+}
+
+void task_new_drop_cookie_secret(udb_base* udb, udb_ptr* last) {
+ udb_ptr e;
+ DEBUG(DEBUG_IPC, 1, (LOG_INFO, "add task drop_cookie_secret"));
+ if(!task_create_new_elem(udb, last, &e, sizeof(struct task_list_d), NULL)) {
+ log_msg(LOG_ERR, "tasklist: out of space, cannot add drop_cookie_secret");
+ return;
+ }
+ TASKLIST(&e)->task_type = task_drop_cookie_secret;
+ udb_ptr_unlink(&e, udb);
+}
+
+void task_new_activate_cookie_secret(udb_base* udb, udb_ptr* last) {
+ udb_ptr e;
+ DEBUG(DEBUG_IPC, 1, (LOG_INFO, "add task activate_cookie_secret"));
+ if(!task_create_new_elem(udb, last, &e, sizeof(struct task_list_d), NULL)) {
+ log_msg(LOG_ERR, "tasklist: out of space, cannot add activate_cookie_secret");
+ return;
+ }
+ TASKLIST(&e)->task_type = task_activate_cookie_secret;
+ udb_ptr_unlink(&e, udb);
+}
+
void task_new_add_pattern(udb_base* udb, udb_ptr* last,
struct pattern_options* p)
{
key_options_remove(nsd->options, name);
}
+static void
+task_process_add_cookie_secret(struct nsd* nsd, struct task_list_d* task) {
+ uint8_t secret_tmp[NSD_COOKIE_SECRET_SIZE];
+ ssize_t decoded_len;
+ char* secret = (char*)task->zname;
+
+ DEBUG(DEBUG_IPC, 1, (LOG_INFO, "add_cookie_secret task %s", secret));
+
+ if( strlen(secret) != 32 ) {
+ log_msg(LOG_ERR, "invalid cookie secret: %s", secret);
+ explicit_bzero(secret, strlen(secret));
+ return;
+ }
+
+ decoded_len = hex_pton(secret, secret_tmp, NSD_COOKIE_SECRET_SIZE);
+ if( decoded_len != 16 ) {
+ explicit_bzero(secret_tmp, NSD_COOKIE_SECRET_SIZE);
+ log_msg(LOG_ERR, "unable to parse cookie secret: %s", secret);
+ explicit_bzero(secret, strlen(secret));
+ return;
+ }
+ explicit_bzero(secret, strlen(secret));
+ add_cookie_secret(nsd, secret_tmp);
+ explicit_bzero(secret_tmp, NSD_COOKIE_SECRET_SIZE);
+}
+
+static void
+task_process_drop_cookie_secret(struct nsd* nsd, struct task_list_d* task)
+{
+ (void)task;
+ DEBUG(DEBUG_IPC, 1, (LOG_INFO, "drop_cookie_secret task"));
+ if( nsd->cookie_count <= 1 ) {
+ log_msg(LOG_ERR, "can not drop the only active cookie secret");
+ return;
+ }
+ drop_cookie_secret(nsd);
+}
+
+static void
+task_process_activate_cookie_secret(struct nsd* nsd, struct task_list_d* task)
+{
+ (void)task;
+ DEBUG(DEBUG_IPC, 1, (LOG_INFO, "activate_cookie_secret task"));
+ if( nsd->cookie_count <= 1 ) {
+ log_msg(LOG_ERR, "can not activate the only active cookie secret");
+ return;
+ }
+ activate_cookie_secret(nsd);
+}
+
static void
task_process_add_pattern(struct nsd* nsd, struct task_list_d* task)
{
case task_apply_xfr:
task_process_apply_xfr(nsd, udb, last_task, task);
break;
+ case task_add_cookie_secret:
+ task_process_add_cookie_secret(nsd, TASKLIST(task));
+ break;
+ case task_drop_cookie_secret:
+ task_process_drop_cookie_secret(nsd, TASKLIST(task));
+ break;
+ case task_activate_cookie_secret:
+ task_process_activate_cookie_secret(nsd, TASKLIST(task));
+ break;
default:
log_msg(LOG_WARNING, "unhandled task in reload type %d",
(int)TASKLIST(task)->task_type);
/** options change */
task_opt_change,
/** zonestat increment */
- task_zonestat_inc
+ task_zonestat_inc,
+ /** add a new cookie secret */
+ task_add_cookie_secret,
+ /** drop the oldest cookie secret */
+ task_drop_cookie_secret,
+ /** make staging cookie secret active */
+ task_activate_cookie_secret,
} task_type;
uint32_t size; /* size of this struct */
void task_new_del_pattern(udb_base* udb, udb_ptr* last, const char* name);
void task_new_opt_change(udb_base* udb, udb_ptr* last, struct nsd_options* opt);
void task_new_zonestat_inc(udb_base* udb, udb_ptr* last, unsigned sz);
+void task_new_add_cookie_secret(udb_base* udb, udb_ptr* last, const char* secret);
+void task_new_drop_cookie_secret(udb_base* udb, udb_ptr* last);
+void task_new_activate_cookie_secret(udb_base* udb, udb_ptr* last);
int task_new_apply_xfr(udb_base* udb, udb_ptr* last, const dname_type* zone,
uint32_t old_serial, uint32_t new_serial, uint64_t filenumber);
void task_process_in_reload(struct nsd* nsd, udb_base* udb, udb_ptr *last_task,
RDATA_WF_BYTE, /* hash Algorithm */
RDATA_WF_BINARY }, /* digest */
{ RDATA_ZF_PERIOD, RDATA_ZF_BYTE, RDATA_ZF_BYTE, RDATA_ZF_HEX } },
- /* 64 */
- { 64, NULL, T_UTYPE, 1, 1, { RDATA_WF_BINARY }, { RDATA_ZF_UNKNOWN } },
- /* 65 */
- { 65, NULL, T_UTYPE, 1, 1, { RDATA_WF_BINARY }, { RDATA_ZF_UNKNOWN } },
+ /* 64 - SVCB */
+ { TYPE_SVCB, "SVCB", T_SVCB, 2, MAXRDATALEN,
+ { RDATA_WF_SHORT /* SvcFieldPriority */
+ , RDATA_WF_UNCOMPRESSED_DNAME /* SvcDomainName */
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM /* SvcFieldValue */
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ },
+ { RDATA_ZF_SHORT , RDATA_ZF_DNAME
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ } },
+ /* 65 - HTTPS */
+ { TYPE_HTTPS, "HTTPS", T_HTTPS, 2, MAXRDATALEN,
+ { RDATA_WF_SHORT /* SvcFieldPriority */
+ , RDATA_WF_UNCOMPRESSED_DNAME /* SvcDomainName */
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM /* SvcFieldValue */
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ , RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM, RDATA_WF_SVCPARAM
+ },
+ { RDATA_ZF_SHORT , RDATA_ZF_DNAME
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ , RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM, RDATA_ZF_SVCPARAM
+ } },
/* 66 */
{ 66, NULL, T_UTYPE, 1, 1, { RDATA_WF_BINARY }, { RDATA_ZF_UNKNOWN } },
/* 67 */
#define TYPE_OPENPGPKEY 61 /* RFC 7929 */
#define TYPE_CSYNC 62 /* RFC 7477 */
#define TYPE_ZONEMD 63 /* draft-ietf-dnsop-dns-zone-digest */
+#define TYPE_SVCB 64 /* draft-ietf-dnsop-svcb-https-03 */
+#define TYPE_HTTPS 65 /* draft-ietf-dnsop-svcb-https-03 */
#define TYPE_SPF 99 /* RFC 4408 */
#define TYPE_DLV 32769 /* RFC 4431 */
#define PSEUDO_TYPE_DLV RRTYPE_DESCRIPTORS_LENGTH
+#define SVCB_KEY_MANDATORY 0
+#define SVCB_KEY_ALPN 1
+#define SVCB_KEY_NO_DEFAULT_ALPN 2
+#define SVCB_KEY_PORT 3
+#define SVCB_KEY_IPV4HINT 4
+#define SVCB_KEY_ECH 5
+#define SVCB_KEY_IPV6HINT 6
+#define SVCPARAMKEY_COUNT 7
+
#define MAXLABELLEN 63
#define MAXDOMAINLEN 255
RDATA_WF_ILNP64, /* 64-bit uncompressed IPv6 address. */
RDATA_WF_EUI48, /* 48-bit address. */
RDATA_WF_EUI64, /* 64-bit address. */
- RDATA_WF_LONG_TEXT /* Long (>255) text string. */
+ RDATA_WF_LONG_TEXT, /* Long (>255) text string. */
+ RDATA_WF_SVCPARAM /* SvcParam <key>[=<value>] */
};
typedef enum rdata_wireformat rdata_wireformat_type;
RDATA_ZF_EUI64, /* EUI64 address. */
RDATA_ZF_LONG_TEXT, /* Long (>255) text string. */
RDATA_ZF_TAG, /* Text string without quotes. */
+ RDATA_ZF_SVCPARAM, /* SvcParam <key>[=<value>] */
RDATA_ZF_UNKNOWN /* Unknown data. */
};
typedef enum rdata_zoneformat rdata_zoneformat_type;
#include "namedb.h"
#include "options.h"
+#include "udb.h"
+#include "rrl.h"
+
struct dt_collector* dt_collector_create(struct nsd* nsd)
{
int i, sv[2];
struct dt_collector* dt_col = (struct dt_collector*)xalloc_zero(
sizeof(*dt_col));
- dt_col->count = nsd->child_count;
+ dt_col->count = nsd->child_count * 2;
dt_col->dt_env = NULL;
dt_col->region = region_create(xalloc, free);
dt_col->send_buffer = buffer_create(dt_col->region,
#endif
);
- /* open pipes in struct nsd */
+ /* open communication channels in struct nsd */
nsd->dt_collector_fd_send = (int*)xalloc_array_zero(dt_col->count,
sizeof(int));
nsd->dt_collector_fd_recv = (int*)xalloc_array_zero(dt_col->count,
sizeof(int));
for(i=0; i<dt_col->count; i++) {
- int fd[2];
- fd[0] = -1;
- fd[1] = -1;
- if(pipe(fd) < 0) {
- error("dnstap_collector: cannot create pipe: %s",
+ int sv[2];
+ int bufsz = buffer_capacity(dt_col->send_buffer);
+ sv[0] = -1; /* For receiving by parent (dnstap-collector) */
+ sv[1] = -1; /* For sending by child (server childs) */
+ if(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, sv) < 0) {
+ error("dnstap_collector: cannot create communication channel: %s",
strerror(errno));
}
- if(fcntl(fd[0], F_SETFL, O_NONBLOCK) == -1) {
- log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
+ if(setsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &bufsz, sizeof(bufsz))) {
+ log_msg(LOG_ERR, "setting dnstap_collector "
+ "receive buffer size failed: %s", strerror(errno));
}
- if(fcntl(fd[1], F_SETFL, O_NONBLOCK) == -1) {
- log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
+ if(setsockopt(sv[1], SOL_SOCKET, SO_SNDBUF, &bufsz, sizeof(bufsz))) {
+ log_msg(LOG_ERR, "setting dnstap_collector "
+ "send buffer size failed: %s", strerror(errno));
}
- nsd->dt_collector_fd_recv[i] = fd[0];
- nsd->dt_collector_fd_send[i] = fd[1];
+ nsd->dt_collector_fd_recv[i] = sv[0];
+ nsd->dt_collector_fd_send[i] = sv[1];
}
+ nsd->dt_collector_fd_swap = nsd->dt_collector_fd_send + nsd->child_count;
/* open socketpair */
if(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
if(!dt_col) return;
free(nsd->dt_collector_fd_recv);
nsd->dt_collector_fd_recv = NULL;
- free(nsd->dt_collector_fd_send);
+ if (nsd->dt_collector_fd_send < nsd->dt_collector_fd_swap)
+ free(nsd->dt_collector_fd_send);
+ else
+ free(nsd->dt_collector_fd_swap);
nsd->dt_collector_fd_send = NULL;
+ nsd->dt_collector_fd_swap = NULL;
region_destroy(dt_col->region);
free(dt_col);
}
void dt_collector_close(struct dt_collector* dt_col, struct nsd* nsd)
{
- int i;
+ int i, *fd_send;
if(!dt_col) return;
if(dt_col->cmd_socket_dt != -1) {
close(dt_col->cmd_socket_dt);
close(dt_col->cmd_socket_nsd);
dt_col->cmd_socket_nsd = -1;
}
+ fd_send = nsd->dt_collector_fd_send < nsd->dt_collector_fd_swap
+ ? nsd->dt_collector_fd_send : nsd->dt_collector_fd_swap;
for(i=0; i<dt_col->count; i++) {
if(nsd->dt_collector_fd_recv[i] != -1) {
close(nsd->dt_collector_fd_recv[i]);
nsd->dt_collector_fd_recv[i] = -1;
}
- if(nsd->dt_collector_fd_send[i] != -1) {
- close(nsd->dt_collector_fd_send[i]);
- nsd->dt_collector_fd_send[i] = -1;
+ if(fd_send[i] != -1) {
+ close(fd_send[i]);
+ fd_send[i] = -1;
}
}
}
}
}
-/* read data from fd into buffer, true when message is complete */
-static int read_into_buffer(int fd, struct buffer* buf)
+/* receive data from fd into buffer, 1 when message received, -1 on error */
+static int recv_into_buffer(int fd, struct buffer* buf)
{
size_t msglen;
ssize_t r;
- if(buffer_position(buf) < 4) {
- /* read the length of the message */
- r = read(fd, buffer_current(buf), 4 - buffer_position(buf));
- if(r == -1) {
- if(errno == EAGAIN || errno == EINTR) {
- /* continue to read later */
- return 0;
- }
- log_msg(LOG_ERR, "dnstap collector: read failed: %s",
- strerror(errno));
- return 0;
- }
- buffer_skip(buf, r);
- if(buffer_position(buf) < 4)
- return 0; /* continue to read more msglen later */
- }
- /* msglen complete */
- msglen = buffer_read_u32_at(buf, 0);
- /* assert we have enough space, if we don't and we wanted to continue,
- * we would have to skip the message somehow, but that should never
- * happen because send_buffer and receive_buffer have the same size */
- assert(buffer_capacity(buf) >= msglen + 4);
- r = read(fd, buffer_current(buf), msglen - (buffer_position(buf) - 4));
+ assert(buffer_position(buf) == 0);
+ r = recv(fd, buffer_current(buf), buffer_capacity(buf), MSG_DONTWAIT);
if(r == -1) {
- if(errno == EAGAIN || errno == EINTR) {
- /* continue to read later */
+ if(errno == EAGAIN || errno == EINTR || errno == EMSGSIZE) {
+ /* continue to receive a message later */
return 0;
}
- log_msg(LOG_ERR, "dnstap collector: read failed: %s",
+ log_msg(LOG_ERR, "dnstap collector: receive failed: %s",
strerror(errno));
+ return -1;
+ }
+ if(r == 0) {
+ /* Remote end closed the connection? */
+ log_msg(LOG_ERR, "dnstap collector: remote closed connection");
+ return -1;
+ }
+ assert(r > 4);
+ msglen = buffer_read_u32_at(buf, 0);
+ if(msglen != (size_t)(r - 4)) {
+ /* Is this still possible now the communication channel is of
+ * type SOCK_DGRAM? I think not, but better safe than sorry. */
+ log_msg(LOG_ERR, "dnstap collector: out of sync (msglen: %u)",
+ (unsigned int) msglen);
return 0;
}
buffer_skip(buf, r);
- if(buffer_position(buf) < 4 + msglen)
- return 0; /* read more msg later */
-
- /* msg complete */
buffer_flip(buf);
return 1;
}
{
struct dt_collector_input* dt_input = (struct dt_collector_input*)arg;
if((event&EV_READ) != 0) {
- /* read */
- if(!read_into_buffer(fd, dt_input->buffer))
+ /* receive */
+ int r = recv_into_buffer(fd, dt_input->buffer);
+ if(r == 0)
return;
-
- /* once data is complete, write it to dnstap */
+ else if(r < 0) {
+ event_base_loopexit(dt_input->dt_collector->event_base, NULL);
+ return;
+ }
+ /* once data is complete, send it to dnstap */
VERBOSITY(4, (LOG_INFO, "dnstap collector: received msg len %d",
(int)buffer_remaining(dt_input->buffer)));
if(dt_input->dt_collector->dt_env) {
void dt_collector_start(struct dt_collector* dt_col, struct nsd* nsd)
{
+ int i, *fd_send;
/* fork */
dt_col->dt_pid = fork();
if(dt_col->dt_pid == -1) {
/* close the nsd side of the command channel */
close(dt_col->cmd_socket_nsd);
dt_col->cmd_socket_nsd = -1;
+
+ /* close the send side of the communication channels */
+ assert(nsd->dt_collector_fd_send < nsd->dt_collector_fd_swap);
+ fd_send = nsd->dt_collector_fd_send < nsd->dt_collector_fd_swap
+ ? nsd->dt_collector_fd_send : nsd->dt_collector_fd_swap;
+ for(i=0; i<dt_col->count; i++) {
+ if(fd_send[i] != -1) {
+ close(fd_send[i]);
+ fd_send[i] = -1;
+ }
+ }
+#ifdef HAVE_SETPROCTITLE
+ setproctitle("dnstap_collector");
+#endif
+ /* Free serve process specific memory pages */
+#ifdef RATELIMIT
+ rrl_mmap_deinit_keep_mmap();
+#endif
+ udb_base_free_keep_mmap(nsd->task[0]);
+ udb_base_free_keep_mmap(nsd->task[1]);
+ namedb_close_udb(nsd->db); /* keeps mmap */
+ namedb_close(nsd->db);
+
dt_collector_run(dt_col, nsd);
/* NOTREACH */
exit(0);
/* close the dt side of the command channel */
close(dt_col->cmd_socket_dt);
dt_col->cmd_socket_dt = -1;
+
+ /* close the receive side of the communication channels */
+ for(i=0; i<dt_col->count; i++) {
+ if(nsd->dt_collector_fd_recv[i] != -1) {
+ close(nsd->dt_collector_fd_recv[i]);
+ nsd->dt_collector_fd_recv[i] = -1;
+ }
+ }
}
}
return 1;
}
-/* attempt to write buffer to socket, if it blocks do not write it. */
-static void attempt_to_write(int s, uint8_t* data, size_t len)
+/* attempt to send buffer to socket, if it blocks do not send it.
+ * return 0 on success, -1 on error */
+static int attempt_to_send(int s, uint8_t* data, size_t len)
{
- size_t total = 0;
ssize_t r;
- while(total < len) {
- r = write(s, data+total, len-total);
- if(r == -1) {
- if(errno == EAGAIN && total == 0) {
- /* on first write part, check if pipe is full,
- * if the nonblocking fd blocks, then drop
- * the message */
- return;
- }
- if(errno != EAGAIN && errno != EINTR) {
- /* some sort of error, print it and drop it */
- log_msg(LOG_ERR,
- "dnstap collector: write failed: %s",
- strerror(errno));
- return;
- }
- /* continue and write this again */
- /* for EINTR, we have to do this,
- * for EAGAIN, if the first part succeeded, we have
- * to continue to write the remainder of the message,
- * because otherwise partial messages confuse the
- * receiver. */
- continue;
+ if(len == 0)
+ return 0;
+ r = send(s, data, len, MSG_DONTWAIT | MSG_NOSIGNAL);
+ if(r == -1) {
+ if(errno == EAGAIN || errno == EINTR ||
+ errno == ENOBUFS || errno == EMSGSIZE) {
+ /* check if pipe is full, if the nonblocking fd blocks,
+ * then drop the message */
+ return 0;
}
- total += r;
+ /* some sort of error, print it */
+ log_msg(LOG_ERR, "dnstap collector: send failed: %s",
+ strerror(errno));
+ return -1;
}
+ assert(r > 0);
+ if(r > 0) {
+ assert((size_t)r == len);
+ return 0;
+ }
+ /* Other end closed the channel? */
+ log_msg(LOG_ERR, "dnstap collector: server child closed the channel");
+ return -1;
}
void dt_collector_submit_auth_query(struct nsd* nsd,
{
if(!nsd->dt_collector) return;
if(!nsd->options->dnstap_log_auth_query_messages) return;
+ if(nsd->dt_collector_fd_send[nsd->this_child->child_num] == -1) return;
VERBOSITY(4, (LOG_INFO, "dnstap submit auth query"));
/* marshal data into send buffer */
return; /* probably did not fit in buffer */
/* attempt to send data; do not block */
- attempt_to_write(nsd->dt_collector_fd_send[nsd->this_child->child_num],
- buffer_begin(nsd->dt_collector->send_buffer),
- buffer_remaining(nsd->dt_collector->send_buffer));
+ if(attempt_to_send(nsd->dt_collector_fd_send[nsd->this_child->child_num],
+ buffer_begin(nsd->dt_collector->send_buffer),
+ buffer_remaining(nsd->dt_collector->send_buffer))) {
+ /* Something went wrong sending to the socket. Don't send to
+ * this socket again. */
+ close(nsd->dt_collector_fd_send[nsd->this_child->child_num]);
+ nsd->dt_collector_fd_send[nsd->this_child->child_num] = -1;
+ }
}
void dt_collector_submit_auth_response(struct nsd* nsd,
{
if(!nsd->dt_collector) return;
if(!nsd->options->dnstap_log_auth_response_messages) return;
+ if(nsd->dt_collector_fd_send[nsd->this_child->child_num] == -1) return;
VERBOSITY(4, (LOG_INFO, "dnstap submit auth response"));
/* marshal data into send buffer */
return; /* probably did not fit in buffer */
/* attempt to send data; do not block */
- attempt_to_write(nsd->dt_collector_fd_send[nsd->this_child->child_num],
- buffer_begin(nsd->dt_collector->send_buffer),
- buffer_remaining(nsd->dt_collector->send_buffer));
+ if(attempt_to_send(nsd->dt_collector_fd_send[nsd->this_child->child_num],
+ buffer_begin(nsd->dt_collector->send_buffer),
+ buffer_remaining(nsd->dt_collector->send_buffer))) {
+ /* Something went wrong sending to the socket. Don't send to
+ * this socket again. */
+ close(nsd->dt_collector_fd_send[nsd->this_child->child_num]);
+ nsd->dt_collector_fd_send[nsd->this_child->child_num] = -1;
+ }
}
+22 July 2021: Wouter
+ - tag 4.3.7 release, with the fixes between rc1 and this release.
+
+20 July 2021: Wouter
+ - Fix typo in xfrd-tcp.c.
+
+15 July 2021: Wouter
+ - tag for 4.3.7rc1.
+ - Fix compile of cookies on FreeBSD without IPv6.
+ - Fix for loop initial declaration for nonc99 compiler.
+
+14 July 2021: Wouter
+ - Fix truncate test for EDNS COOKIE making one less RR is added.
+ - Attempt to fix gcc11 warning.
+
+13 July 2021: Willem
+ - Fixes for child server processes getting out of sync with the
+ dnstap-collector process
+
+13 July 2021: Willem
+ - Interoperable DNS Cookies support as per RFC7873 and RFC9018
+
+9 July 2021: Willem
+ - Client side DNS Zone Transfer-over-TLS (XoT) support as per
+ draft-ietf-dprive-xfr-over-tls
+
+29 June 2021: Willem
+ - Fix #168: Buffer overflow in the dname_to_string() function
+
+14 June 2021: Wouter
+ - Update configure nonblocking test to use host.
+
+25 May 2021: Wouter
+ - Fix #179: log notice and server-count.
+
+21 May 2021: Wouter
+ - Test code has -q option for quiet output.
+
+17 May 2021: Wouter
+ - Update the ACX_CHECK_NONBLOCKING_BROKEN test for the configure
+ script.
+
+7 May 2021: Wouter
+ - Fix #176: please review Loglevel on missing zonefile.
+
+6 May 2021: Wouter
+ - Fix #174: NS Records below delegation are not ignored (nsd-checkzone
+ also does not raise any issue).
+
+4 May 2021: Wouter
+ - Fix SVCB sort call sizeof to be the size of the elements sorted.
+
+29 April 2021: Tom
+ - Implement Syntax of SVCB and HTTPS RR type as per draft-ietf-dnsop-svcb-https
+
+13 April 2021: Wouter
+ - Fix for #128: Skip over sendmmsg invalid argument when port is zero.
+ - Fix #171: Invalid negative response (NSEC3) after IXFR.
+ - Fix to make nsec3_chain_find_prev return NULL if one nsec3 left.
+ - remove debug settings from unit test.
+
+9 April 2021: Wouter
+ - Fix for #170: Fix build warnings when IPv6 is disabled.
+ - Fix #170: Disabled IPv6 and DNSTAP enabled triggers a build error.
+
30 March 2021: Wouter
- Fix configure failure for enable systemd because of autoconf.
+ - This became release 4.3.6, the repository continues for 4.3.7
+ in development.
29 March 2021: Wouter
- Note unlisted changes in RELNOTES and prepare for 4.3.6rc1 tag.
NSD RELEASE NOTES
+4.3.7
+================
+FEATURES:
+ - Syntax of SVCB and HTTPS RR type as per draft-ietf-dnsop-svcb-https
+ - Client side DNS Zone Transfer-over-TLS (XoT) support as per
+ draft-ietf-dprive-xfr-over-tls
+ - Interoperable DNS Cookies support as per RFC7873 and RFC9018
+BUG FIXES:
+ - Fix for #170: Fix build warnings when IPv6 is disabled.
+ - Fix #170: Disabled IPv6 and DNSTAP enabled triggers a build error.
+ - Fix for #128: Skip over sendmmsg invalid argument when port is zero.
+ - Fix #171: Invalid negative response (NSEC3) after IXFR.
+ - Fix to make nsec3_chain_find_prev return NULL if one nsec3 left.
+ - Fix #174: NS Records below delegation are not ignored (nsd-checkzone
+ also does not raise any issue).
+ - Fix #176: please review Loglevel on missing zonefile.
+ - Update the ACX_CHECK_NONBLOCKING_BROKEN test for the configure
+ script.
+ - Fix #179: log notice and server-count.
+ - Update configure nonblocking test to use host.
+ - Fix #168: Buffer overflow in the dname_to_string() function
+ - Fixes for child server processes getting out of sync with the
+ dnstap-collector process
+ - Fix gcc-11 warning on array bounds.
+ - Fix compile of cookies on FreeBSD without IPv6.
+ - Fix for loop initial declaration for nonc99 compiler
+ - Fix typo in xfrd-tcp.c.
+
4.3.6
================
FEATURES:
#include "config.h"
#include <string.h>
+#ifdef HAVE_SSL
+#include <openssl/opensslv.h>
+#include <openssl/evp.h>
+#endif
#include "dns.h"
#include "edns.h"
data->error[3] = (max_length & 0xff00) >> 8; /* size_hi */
data->error[4] = max_length & 0x00ff; /* size_lo */
data->error[5] = 1; /* XXX Extended RCODE=BAD VERS */
+
+ /* COOKIE OPT HDR */
+ data->cookie[0] = (COOKIE_CODE & 0xff00) >> 8;
+ data->cookie[1] = (COOKIE_CODE & 0x00ff);
+ data->cookie[2] = (24 & 0xff00) >> 8;
+ data->cookie[3] = (24 & 0x00ff);
}
void
edns_init_nsid(edns_data_type *data, uint16_t nsid_len)
{
- /* add nsid length bytes */
- data->rdata_nsid[0] = ((OPT_HDR + nsid_len) & 0xff00) >> 8; /* length_hi */
- data->rdata_nsid[1] = ((OPT_HDR + nsid_len) & 0x00ff); /* length_lo */
-
/* NSID OPT HDR */
data->nsid[0] = (NSID_CODE & 0xff00) >> 8;
data->nsid[1] = (NSID_CODE & 0x00ff);
edns->opt_reserved_space = 0;
edns->dnssec_ok = 0;
edns->nsid = 0;
+ edns->cookie_status = COOKIE_NOT_PRESENT;
+ edns->cookie_len = 0;
edns->ede = -1; /* -1 means no Extended DNS Error */
edns->ede_text = NULL;
edns->ede_text_len = 0;
buffer_skip(packet, optlen);
}
break;
+ case COOKIE_CODE:
+ /* Cookies enabled? */
+ if(nsd->do_answer_cookie) {
+ if (optlen == 8)
+ edns->cookie_status = COOKIE_INVALID;
+ else if (optlen < 16 || optlen > 40)
+ return 0; /* FORMERR */
+ else
+ edns->cookie_status = COOKIE_UNVERIFIED;
+
+ edns->cookie_len = optlen;
+ memcpy(edns->cookie, buffer_current(packet), optlen);
+ buffer_skip(packet, optlen);
+ edns->opt_reserved_space += OPT_HDR + 24;
+ } else {
+ buffer_skip(packet, optlen);
+ }
+ break;
default:
buffer_skip(packet, optlen);
break;
return edns->status == EDNS_NOT_PRESENT ? 0
: (OPT_LEN + OPT_RDATA + edns->opt_reserved_space);
}
+
+int siphash(const uint8_t *in, const size_t inlen,
+ const uint8_t *k, uint8_t *out, const size_t outlen);
+
+/** 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;
+}
+
+void cookie_verify(query_type *q, struct nsd* nsd, uint32_t *now_p) {
+ uint8_t hash[8], hash2verify[8];
+ uint32_t cookie_time, now_uint32;
+ size_t verify_size;
+ int i;
+
+ /* We support only draft-sury-toorop-dnsop-server-cookies sizes */
+ if(q->edns.cookie_len != 24)
+ return;
+
+ if(q->edns.cookie[8] != 1)
+ return;
+
+ q->edns.cookie_status = COOKIE_INVALID;
+
+ cookie_time = (q->edns.cookie[12] << 24)
+ | (q->edns.cookie[13] << 16)
+ | (q->edns.cookie[14] << 8)
+ | q->edns.cookie[15];
+
+ now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL));
+
+ if(compare_1982(now_uint32, cookie_time) > 0) {
+ /* ignore cookies > 1 hour in past */
+ if (subtract_1982(cookie_time, now_uint32) > 3600)
+ return;
+ } else if (subtract_1982(now_uint32, cookie_time) > 300) {
+ /* ignore cookies > 5 minutes in future */
+ return;
+ }
+
+ memcpy(hash2verify, q->edns.cookie + 16, 8);
+
+#ifdef INET6
+ if(q->addr.ss_family == AF_INET6) {
+ memcpy(q->edns.cookie + 16, &((struct sockaddr_in6 *)&q->addr)->sin6_addr, 16);
+ verify_size = 32;
+ } else {
+ memcpy(q->edns.cookie + 16, &((struct sockaddr_in *)&q->addr)->sin_addr, 4);
+ verify_size = 20;
+ }
+#else
+ memcpy( q->edns.cookie + 16, &q->addr.sin_addr, 4);
+ verify_size = 20;
+#endif
+
+ q->edns.cookie_status = COOKIE_INVALID;
+ siphash(q->edns.cookie, verify_size,
+ nsd->cookie_secrets[0].cookie_secret, hash, 8);
+ if(CRYPTO_memcmp(hash2verify, hash, 8) == 0 ) {
+ if (subtract_1982(cookie_time, now_uint32) < 1800) {
+ q->edns.cookie_status = COOKIE_VALID_REUSE;
+ memcpy(q->edns.cookie + 16, hash, 8);
+ } else
+ q->edns.cookie_status = COOKIE_VALID;
+ return;
+ }
+ for(i = 1;
+ i < (int)nsd->cookie_count && i < NSD_COOKIE_HISTORY_SIZE;
+ i++) {
+ siphash(q->edns.cookie, verify_size,
+ nsd->cookie_secrets[i].cookie_secret, hash, 8);
+ if(CRYPTO_memcmp(hash2verify, hash, 8) == 0 ) {
+ q->edns.cookie_status = COOKIE_VALID;
+ return;
+ }
+ }
+}
+
+void cookie_create(query_type *q, struct nsd* nsd, uint32_t *now_p)
+{
+ uint8_t hash[8];
+ uint32_t now_uint32;
+
+ if (q->edns.cookie_status == COOKIE_VALID_REUSE)
+ return;
+
+ now_uint32 = *now_p ? *now_p : (*now_p = (uint32_t)time(NULL));
+ q->edns.cookie[ 8] = 1;
+ q->edns.cookie[ 9] = 0;
+ q->edns.cookie[10] = 0;
+ q->edns.cookie[11] = 0;
+ q->edns.cookie[12] = (now_uint32 & 0xFF000000) >> 24;
+ q->edns.cookie[13] = (now_uint32 & 0x00FF0000) >> 16;
+ q->edns.cookie[14] = (now_uint32 & 0x0000FF00) >> 8;
+ q->edns.cookie[15] = now_uint32 & 0x000000FF;
+#ifdef INET6
+ if (q->addr.ss_family == AF_INET6) {
+ memcpy( q->edns.cookie + 16
+ , &((struct sockaddr_in6 *)&q->addr)->sin6_addr, 16);
+ siphash(q->edns.cookie, 32, nsd->cookie_secrets[0].cookie_secret, hash, 8);
+ } else {
+ memcpy( q->edns.cookie + 16
+ , &((struct sockaddr_in *)&q->addr)->sin_addr, 4);
+ siphash(q->edns.cookie, 20, nsd->cookie_secrets[0].cookie_secret, hash, 8);
+ }
+#else
+ memcpy( q->edns.cookie + 16, &q->addr.sin_addr, 4);
+ siphash(q->edns.cookie, 20, nsd->cookie_secrets[0].cookie_secret, hash, 8);
+#endif
+ memcpy(q->edns.cookie + 16, hash, 8);
+}
+
#define OPT_RDATA 2 /* holds the rdata length comes after OPT_LEN */
#define OPT_HDR 4U /* NSID opt header length */
#define NSID_CODE 3 /* nsid option code */
+#define COOKIE_CODE 10 /* COOKIE option code */
#define EDE_CODE 15 /* Extended DNS Errors option code */
#define DNSSEC_OK_MASK 0x8000U /* do bit mask */
char ok[OPT_LEN];
char error[OPT_LEN];
char rdata_none[OPT_RDATA];
- char rdata_nsid[OPT_RDATA];
char nsid[OPT_HDR];
+ char cookie[OPT_HDR];
};
typedef struct edns_data edns_data_type;
};
typedef enum edns_status edns_status_type;
+enum cookie_status
+{
+ COOKIE_NOT_PRESENT,
+ COOKIE_UNVERIFIED,
+ COOKIE_VALID,
+ COOKIE_VALID_REUSE,
+ COOKIE_INVALID
+};
+typedef enum cookie_status cookie_status_type;
+
struct edns_record
{
- edns_status_type status;
- size_t position;
- size_t maxlen;
- size_t opt_reserved_space;
- int dnssec_ok;
- int nsid;
- int ede; /* RFC 8914 - Extended DNS Errors */
- char* ede_text; /* RFC 8914 - Extended DNS Errors text*/
- uint16_t ede_text_len;
+ edns_status_type status;
+ size_t position;
+ size_t maxlen;
+ size_t opt_reserved_space;
+ int dnssec_ok;
+ int nsid;
+ cookie_status_type cookie_status;
+ size_t cookie_len;
+ uint8_t cookie[40];
+ int ede; /* RFC 8914 - Extended DNS Errors */
+ char* ede_text; /* RFC 8914 - Extended DNS Errors text*/
+ uint16_t ede_text_len;
};
typedef struct edns_record edns_record_type;
void edns_init_nsid(edns_data_type *data, uint16_t nsid_len);
+void cookie_verify(struct query *q, struct nsd* nsd, uint32_t *now_p);
+void cookie_create(struct query *q, struct nsd* nsd, uint32_t *now_p);
+
#endif /* _EDNS_H_ */
domain_type *
domain_find_ns_rrsets(domain_type* domain, zone_type* zone, rrset_type **ns)
{
+ /* return highest NS RRset in the zone that is a delegation above */
+ domain_type* result = NULL;
while (domain && domain != zone->apex) {
*ns = domain_find_rrset(domain, zone, TYPE_NS);
if (*ns)
- return domain;
+ result = domain;
domain = domain->parent;
}
+ if(result)
+ return result;
+
*ns = NULL;
return NULL;
}
-.TH "nsd\-checkconf" "8" "Apr 6, 2021" "NLnet Labs" "nsd 4.3.6"
+.TH "nsd\-checkconf" "8" "Jul 22, 2021" "NLnet Labs" "nsd 4.3.7"
.\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
.\" See LICENSE for the license.
.SH "NAME"
.IR pattern ]
.RB [ \-s
.IR keyname ]
+.RB [ \-t
+.IR tlsauthname ]
.I configfile
.SH "DESCRIPTION"
.B nsd\-checkconf
Prints the key secret (base64 blob) configured for this key in the
config file. Used to help shell scripts parse the config file.
.TP
+.B \-t\fI tls-auth
+Prints the authentication domain name configured for this tls-auth clause in the
+config file. Used to help shell scripts parse the config file.
+.TP
.B \-p\fI pattern
Return the option specified with
.B \-o
usage(void)
{
fprintf(stderr, "usage: nsd-checkconf [-v|-h] [-o option] [-z zonename]\n");
- fprintf(stderr, " [-s keyname] <configfilename>\n");
+ fprintf(stderr, " [-s keyname] [-t tlsauthname] <configfilename>\n");
fprintf(stderr, " Checks NSD configuration file for errors.\n");
fprintf(stderr, " Version %s. Report bugs to <%s>.\n\n",
PACKAGE_VERSION, PACKAGE_BUGREPORT);
fprintf(stderr, "Use with a configfile as argument to check syntax.\n");
- fprintf(stderr, "Use with -o, -z or -s options to query the configuration.\n\n");
- fprintf(stderr, "-v Verbose, echo settings that take effect to std output.\n");
- fprintf(stderr, "-h Print this help information.\n");
- fprintf(stderr, "-f Use with -o to print final pathnames, ie. with chroot.\n");
- fprintf(stderr, "-o option Print value of the option specified to stdout.\n");
- fprintf(stderr, "-p pattern Print option value for the pattern given.\n");
- fprintf(stderr, "-z zonename Print option value for the zone given.\n");
- fprintf(stderr, "-a keyname Print algorithm name for the TSIG key.\n");
- fprintf(stderr, "-s keyname Print base64 secret blob for the TSIG key.\n");
+ fprintf(stderr, "Use with -o, -z, -t or -s options to query the configuration.\n\n");
+ fprintf(stderr, "-v Verbose, echo settings that take effect to std output.\n");
+ fprintf(stderr, "-h Print this help information.\n");
+ fprintf(stderr, "-f Use with -o to print final pathnames, ie. with chroot.\n");
+ fprintf(stderr, "-o option Print value of the option specified to stdout.\n");
+ fprintf(stderr, "-p pattern Print option value for the pattern given.\n");
+ fprintf(stderr, "-z zonename Print option value for the zone given.\n");
+ fprintf(stderr, "-a keyname Print algorithm name for the TSIG key.\n");
+ fprintf(stderr, "-s keyname Print base64 secret blob for the TSIG key.\n");
+ fprintf(stderr, "-t tls-auth-name Print auth domain name for the tls-auth clause.\n");
exit(1);
}
{
while(acl)
{
- printf("%s %s\n", acl->ip_address_spec,
- acl->nokey?"NOKEY":(acl->blocked?"BLOCKED":
- (acl->key_name?acl->key_name:"(null)")));
+ if (acl->tls_auth_name)
+ printf("%s %s %s\n", acl->ip_address_spec,
+ acl->nokey?"NOKEY":(acl->blocked?"BLOCKED":
+ (acl->key_name?acl->key_name:"(null)")),
+ acl->tls_auth_name?acl->tls_auth_name:"");
+ else
+ printf("%s %s\n", acl->ip_address_spec,
+ acl->nokey?"NOKEY":(acl->blocked?"BLOCKED":
+ (acl->key_name?acl->key_name:"(null)")));
acl=acl->next;
}
}
printf("AXFR ");
if(acl->allow_udp)
printf("UDP ");
- printf("%s %s\n", acl->ip_address_spec,
- acl->nokey?"NOKEY":(acl->blocked?"BLOCKED":
- (acl->key_name?acl->key_name:"(null)")));
+ if (acl->tls_auth_name)
+ printf("%s %s %s\n", acl->ip_address_spec,
+ acl->nokey?"NOKEY":(acl->blocked?"BLOCKED":
+ (acl->key_name?acl->key_name:"(null)")),
+ acl->tls_auth_name?acl->tls_auth_name:"");
+ else
+ printf("%s %s\n", acl->ip_address_spec,
+ acl->nokey?"NOKEY":(acl->blocked?"BLOCKED":
+ (acl->key_name?acl->key_name:"(null)")));
if(verbosity>1) {
printf("\t# %s", acl->is_ipv6?"ip6":"ip4");
if(acl->port == 0) printf(" noport");
void
config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o,
- const char *z, const char* pat, int final)
+ const char *z, const char* pat, const char* tls, int final)
{
ip_address_option_type* ip;
return;
}
+ if (tls) {
+ /* find tlsauth */
+ tls_auth_options_type* tlsauth = tls_auth_options_find(opt, tls);
+ if(tlsauth) {
+ quote(tlsauth->auth_domain_name);
+ return;
+ }
+ printf("Could not find tls-auth %s\n", tls);
+ return;
+ }
+
if (!o) {
return;
}
SERV_GET_STR(tls_service_ocsp, o);
SERV_GET_STR(tls_service_pem, o);
SERV_GET_STR(tls_port, o);
+ SERV_GET_STR(tls_cert_bundle, o);
+ SERV_GET_STR(cookie_secret, o);
+ SERV_GET_STR(cookie_secret_file, o);
+ SERV_GET_BIN(answer_cookie, o);
/* int */
SERV_GET_INT(server_count, o);
SERV_GET_INT(tcp_count, o);
{
ip_address_option_type* ip;
key_options_type* key;
+ tls_auth_options_type* tlsauth;
zone_options_type* zone;
pattern_options_type* pat;
print_string_var("tls-service-pem:", opt->tls_service_pem);
print_string_var("tls-service-ocsp:", opt->tls_service_ocsp);
print_string_var("tls-port:", opt->tls_port);
+ print_string_var("tls-cert-bundle:", opt->tls_cert_bundle);
+ printf("\tanswer-cookie: %s\n", opt->answer_cookie?"yes":"no");
+ if (opt->cookie_secret)
+ print_string_var("cookie-secret:", opt->cookie_secret);
+ if (opt->cookie_secret_file)
+ print_string_var("cookie-secret-file:", opt->cookie_secret_file);
#ifdef USE_DNSTAP
printf("\ndnstap:\n");
print_string_var("algorithm:", key->algorithm);
print_string_var("secret:", key->secret);
}
+ RBTREE_FOR(tlsauth, tls_auth_options_type*, opt->tls_auths)
+ {
+ printf("\ntls-auth:\n");
+ print_string_var("name:", tlsauth->name);
+ print_string_var("auth-domain-name:", tlsauth->auth_domain_name);
+ }
RBTREE_FOR(pat, pattern_options_type*, opt->patterns)
{
if(pat->implicit) continue;
errors ++;
}
if(errors != 0) {
- fprintf(stderr, "%s: %d semantic errors in %d zones, %d keys.\n",
+ fprintf(stderr, "%s: %d semantic errors in %d zones, %d keys, %d tls-auth.\n",
filename, errors, (int)nsd_options_num_zones(opt),
- (int)opt->keys->count);
+ (int)opt->keys->count,
+ (int)opt->tls_auths->count);
}
return (errors == 0);
const char * conf_opt = NULL; /* what option do you want? Can be NULL -> print all */
const char * conf_zone = NULL; /* what zone are we talking about */
const char * conf_key = NULL; /* what key is needed */
+ const char * conf_tlsauth = NULL; /* what tls-auth is needed */
const char * conf_pat = NULL; /* what pattern is talked about */
const char* configfile;
nsd_options_type *options;
log_init("nsd-checkconf");
/* Parse the command line... */
- while ((c = getopt(argc, argv, "vfho:a:p:s:z:")) != -1) {
+ while ((c = getopt(argc, argv, "vfho:a:p:s:z:t:")) != -1) {
switch (c) {
case 'v':
verbose = 1;
conf_key = optarg;
key_sec = 1;
break;
+ case 't':
+ conf_tlsauth = optarg;
+ break;
case 'z':
conf_zone = optarg;
break;
!additional_checks(options, configfile)) {
exit(2);
}
- if (conf_opt || conf_key) {
+ if (conf_opt || conf_key || conf_tlsauth) {
config_print_zone(options, conf_key, key_sec,
- underscore(conf_opt), conf_zone, conf_pat, final);
+ underscore(conf_opt), conf_zone, conf_pat, conf_tlsauth, final);
} else {
if (verbose) {
printf("# Read file %s: %d patterns, %d fixed-zones, "
- "%d keys.\n",
+ "%d keys, %d tls-auth.\n",
configfile,
(int)options->patterns->count,
(int)nsd_options_num_zones(options),
- (int)options->keys->count);
+ (int)options->keys->count,
+ (int)options->tls_auths->count);
config_test_print_server(options);
}
}
-.TH "nsd\-checkzone" "8" "Apr 6, 2021" "NLnet Labs" "nsd 4.3.6"
+.TH "nsd\-checkzone" "8" "Jul 22, 2021" "NLnet Labs" "nsd 4.3.7"
.\" Copyright (c) 2014, NLnet Labs. All rights reserved.
.\" See LICENSE for the license.
.SH "NAME"
-.TH "nsd\-control" "8" "Apr 6, 2021" "NLnet Labs" "nsd 4.3.6"
+.TH "nsd\-control" "8" "Jul 22, 2021" "NLnet Labs" "nsd 4.3.7"
.\" Copyright (c) 2011, NLnet Labs. All rights reserved.
.\" See LICENSE for the license.
.SH "NAME"
Delete the TSIG key with the given name. Prints error if the key is still
in use by some zone. The changes are only in-memory and are gone next
restart, for lasting changes edit the nsd.conf file or a file included from it.
+.TP
+.B add_cookie_secret <secret>
+Add or replace a cookie secret persistently. <secret> needs to be an 128 bit
+hex string.
+
+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.
+
+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.
+
+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.
+
+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.
+The default value for that is: @configdir@/nsd_cookiesecrets.txt .
+.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 nsd\-control program exits with status code 1 on error, 0 on success.
.SH "SET UP"
printf(" add_tsig <name> <secret> [algo] add new key with the given parameters\n");
printf(" assoc_tsig <zone> <key_name> associate <zone> with given tsig <key_name> name\n");
printf(" del_tsig <key_name> delete tsig <key_name> from configuration\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");
exit(1);
}
-.TH "NSD" "8" "Apr 6, 2021" "NLnet Labs" "NSD 4.3.6"
+.TH "NSD" "8" "Jul 22, 2021" "NLnet Labs" "NSD 4.3.7"
.\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
.\" See LICENSE for the license.
.SH "NAME"
.B nsd
-\- Name Server Daemon (NSD) version 4.3.6.
+\- Name Server Daemon (NSD) version 4.3.7.
.SH "SYNOPSIS"
.B nsd
.RB [ \-4 ]
#include <login_cap.h>
#endif /* HAVE_LOGIN_CAP_H */
#endif /* HAVE_SETUSERCONTEXT */
+#ifdef HAVE_OPENSSL_RAND_H
+#include <openssl/rand.h>
+#endif
#include <assert.h>
#include <ctype.h>
const char *udp_port, const char *tcp_port,
const struct addrinfo *hints)
{
- int r;
size_t i = 0, n = 1;
struct addrinfo ai[2] = { *hints, *hints };
* automatically mapped to our IPv6 socket.
*/
#ifdef IPV6_V6ONLY
+ int r;
struct addrinfo *addrs[2] = { NULL, NULL };
if((r = getaddrinfo(NULL, udp_port, &ai[0], &addrs[0])) == 0 &&
for(i = 0; i < ifs; i++) {
assert(udp[i].servers->size == servercnt);
- addrport2str(&udp[i].addr.ai_addr, sockbuf, sizeof(sockbuf));
+ addrport2str((void*)&udp[i].addr.ai_addr, sockbuf, sizeof(sockbuf));
print_socket_servers(&udp[i], serverbuf, serverbufsz);
nsd_bitset_or(servers, servers, udp[i].servers);
VERBOSITY(3, (LOG_NOTICE, fmt, sockbuf, "udp", serverbuf));
assert(tcp[i].servers->size == servercnt);
- addrport2str(&tcp[i].addr.ai_addr, sockbuf, sizeof(sockbuf));
+ addrport2str((void*)&tcp[i].addr.ai_addr, sockbuf, sizeof(sockbuf));
print_socket_servers(&tcp[i], serverbuf, serverbufsz);
nsd_bitset_or(servers, servers, tcp[i].servers);
VERBOSITY(3, (LOG_NOTICE, fmt, sockbuf, "tcp", serverbuf));
}
#endif /* BIND8_STATS */
+static
+int cookie_secret_file_read(nsd_type* nsd) {
+ char secret[NSD_COOKIE_SECRET_SIZE * 2 + 2/*'\n' and '\0'*/];
+ char const* file = nsd->options->cookie_secret_file;
+ FILE* f;
+ int corrupt = 0;
+ size_t count;
+
+ assert( nsd->options->cookie_secret_file != NULL );
+ f = fopen(file, "r");
+ /* a non-existing cookie file is not an error */
+ if( f == NULL ) { return errno != EPERM; }
+ /* cookie secret file exists and is readable */
+ nsd->cookie_count = 0;
+ for( count = 0; count < NSD_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; }
+ assert( secret_len <= sizeof(secret) );
+ secret_len = secret[secret_len - 1] == '\n' ? secret_len - 1 : secret_len;
+ if( secret_len != NSD_COOKIE_SECRET_SIZE * 2 ) { corrupt++; break; }
+ /* needed for `hex_pton`; stripping potential `\n` */
+ secret[secret_len] = '\0';
+ decoded_len = hex_pton(secret, nsd->cookie_secrets[count].cookie_secret,
+ NSD_COOKIE_SECRET_SIZE);
+ if( decoded_len != NSD_COOKIE_SECRET_SIZE ) { corrupt++; break; }
+ nsd->cookie_count++;
+ }
+ fclose(f);
+ return corrupt == 0;
+}
+
extern char *optarg;
extern int optind;
nsd.chrootdir = 0;
nsd.nsid = NULL;
nsd.nsid_len = 0;
+ nsd.cookie_count = 0;
nsd.child_count = 0;
nsd.maximum_tcp_count = 0;
nsd.current_tcp_count = 0;
nsd.file_rotation_ok = 0;
+ nsd.do_answer_cookie = 1;
+
/* Set up our default identity to gethostname(2) */
if (gethostname(hostname, MAXHOSTNAMELEN) == 0) {
nsd.identity = hostname;
#endif /* IPV6 MTU) */
#endif /* defined(INET6) */
+ nsd.do_answer_cookie = nsd.options->answer_cookie;
+ if (nsd.cookie_count > 0)
+ ; /* pass */
+
+ else if (nsd.options->cookie_secret) {
+ ssize_t len = hex_pton(nsd.options->cookie_secret,
+ nsd.cookie_secrets[0].cookie_secret, NSD_COOKIE_SECRET_SIZE);
+ if (len != NSD_COOKIE_SECRET_SIZE ) {
+ error("A cookie secret must be a "
+ "128 bit hex string");
+ }
+ nsd.cookie_count = 1;
+ } else {
+ size_t j;
+ size_t const cookie_secret_len = NSD_COOKIE_SECRET_SIZE;
+ /* Calculate a new random secret */
+ srandom(getpid() ^ time(NULL));
+
+ for( j = 0; j < NSD_COOKIE_HISTORY_SIZE; j++) {
+#if defined(HAVE_SSL)
+ if (!RAND_status()
+ || !RAND_bytes(nsd.cookie_secrets[j].cookie_secret, cookie_secret_len))
+#endif
+ for (i = 0; i < cookie_secret_len; i++)
+ nsd.cookie_secrets[j].cookie_secret[i] = random_generate(256);
+ }
+ // XXX: all we have is a random cookie, still pretend we have one
+ nsd.cookie_count = 1;
+ }
+
if (nsd.nsid_len == 0 && nsd.options->nsid) {
if (strlen(nsd.options->nsid) % 2 != 0) {
error("the NSID must be a hex string of an even length.");
}
#endif /* HAVE_SSL */
+ if(nsd.options->cookie_secret_file && nsd.options->cookie_secret_file[0]
+ && !cookie_secret_file_read(&nsd) ) {
+ log_msg(LOG_ERR, "cookie secret file corrupt or not readable");
+ }
+
/* Unless we're debugging, fork... */
if (!nsd.debug) {
int fd;
options_zonestatnames_create(nsd.options);
server_zonestat_alloc(&nsd);
#endif /* USE_ZONE_STATS */
-#ifdef USE_DNSTAP
- if(nsd.options->dnstap_enable) {
- nsd.dt_collector = dt_collector_create(&nsd);
- dt_collector_start(nsd.dt_collector, &nsd);
- }
-#endif /* USE_DNSTAP */
-
if(nsd.server_kind == NSD_SERVER_MAIN) {
server_prepare_xfrd(&nsd);
/* xfrd forks this before reading database, so it does not get
server_start_xfrd(&nsd, 0, 0);
/* close zonelistfile in non-xfrd processes */
zone_list_close(nsd.options);
+#ifdef USE_DNSTAP
+ if(nsd.options->dnstap_enable) {
+ nsd.dt_collector = dt_collector_create(&nsd);
+ dt_collector_start(nsd.dt_collector, &nsd);
+ }
+#endif /* USE_DNSTAP */
}
if (server_prepare(&nsd) != 0) {
unlinkpid(nsd.pidfile);
-.TH "nsd.conf" "5" "Apr 6, 2021" "NLnet Labs" "nsd 4.3.6"
+.TH "nsd.conf" "5" "Jul 22, 2021" "NLnet Labs" "nsd 4.3.7"
.\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
.\" See LICENSE for the license.
.SH "NAME"
.BR key: ,
.BR pattern: ,
.BR zone: ,
+.BR tls-auth: ,
and
.B remote-control:
are allowed. These are followed by their attributes or a new top-level keyword. The
attribute is used to define keys for authentication. The
.B pattern:
attribute is followed by the zone options for zones that use the pattern.
+A
+.B tls-auth:
+attribute is used to define credentials for authenticating an outgoing TLS connection used for XFR-over-TLS.
.P
Files can be included using the
.B include:
With the value 0 the rate is unlimited.
.\" rrlend
.TP
+.B answer\-cookie:\fR <yes or no>
+Enable to answer to requests containig DNS Cookies as specified in RFC7873.
+Default is yes.
+.TP
+.B cookie\-secret:\fR <128 bit hex string>
+Servers in an anycast deployment need to be able to verify each other's DNS
+Server Cookies. For this they need to share the secret used to construct and
+verify the DNS Cookies. 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
+.B cookie\-secret\-file:\fR <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.
+Default is @configdir@/nsd_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
+\fInsd\-control\fR(8) tool. Please see that manpage how to perform a safe
+cookie secret rollover.
+.TP
.B tls\-service\-key:\fR <filename>
If enabled, the server provides TLS service on TCP sockets with the TLS
service port number. The port number (853) is configured with tls\-port.
.B tls\-port:\fR <number>
The port number on which to provide TCP TLS service, default is 853, only
interfaces configured with that port number as @number get DNS over TLS service.
+.TP
+.B tls\-cert\-bundle:\fR <filename>
+If null or "", the default verify locations are used. Set it to the certificate
+bundle file, for example "/etc/pki/tls/certs/ca-bundle.crt". These certificates
+are used for authenticating Transfer over TLS (XoT) connections.
.SS "Remote Control"
The
.B remote\-control:
symbols.
.RE
.TP
-.B request\-xfr:\fR [AXFR|UDP] <ip\-address> <key\-name | NOKEY>
+.B request\-xfr:\fR [AXFR|UDP] <ip\-address> <key\-name | NOKEY> [tls\-auth\-name]
Access control list. The listed address (the master) is queried for
AXFR/IXFR on update. A port number can be added using a suffix of @number,
-for example 1.2.3.4@5300. The specified key is used during AXFR/IXFR.
+for example 1.2.3.4@5300. The specified key is used during AXFR/IXFR. If
+tls-auth-name is included, the specified tls-auth clause will be used to
+perform authenticated XFR-over-TLS.
.P
.RS
If the AXFR option is given, the server will not be contacted with
notifies and zone transfers. Otherwise, NSD is more vulnerable for
Kaminsky\-style attacks. If the UDP option is left out then IXFR will be
transmitted using TCP.
+.P
+If a tls-auth-name is given then TLS (by default on port 853) will be used
+for all zone transfers for the zone. If authentication of the master based on
+the specified tls-auth authentication information fails, the XFR request will
+not be sent. Support for TLS 1.3 is required for XFR-over-TLS.
.RE
.TP
.B allow\-axfr\-fallback:\fR <yes or no>
The content of the secret is the agreed base64 secret content. To make it
up, enter a password (its length must be a multiple of 4 characters, A\-Za\-z0\-9), or use
dev-random output through a base64 encode filter.
+.SS "TLS Auth Declarations"
+The
+.B tls-auth:
+clause establishes authentication attributes to use when authenticating
+the far end of an outgoing TLS connection used in access control lists for XFR-over-TLS.
+It has the following attributes.
+.TP
+.B name:\fR <string>
+The tls-auth name. Used to refer to this TLS authentication information in the
+access control list.
+.TP
+.B auth-domain-name:\fR <string>
+The authentication domain name as defined in RFC8310.
.SS DNSTAP Logging Options
DNSTAP support, when compiled in, is enabled in the \fBdnstap:\fR section.
This starts a collector process that writes the log information to the
# tls-service-pem: "path/to/publiccertfile.pem"
# tls-service-ocsp: "path/to/ocsp.pem"
# tls-port: 853
+
+ # Certificates used to authenticate connections made upstream for
+ # Transfers over TLS (XoT). Default is "" (default verify locations).
+ # tls-cert-bundle: "path/to/ca-bundle.pem"
# DNSTAP config section, if compiled with that
# dnstap:
# e.g. from dd if=/dev/random of=/dev/stdout count=1 bs=32 | base64
#secret: "K2tf3TRjvQkVCmJF3/Z9vA=="
+# The tls-auth clause establishes authentication attributes to use when
+# authenticating the far end of an outgoing TLS connection in access control
+# lists used for XFR-over-TLS. If authentication fails, the XFR request will not
+# be made. Support for TLS 1.3 is required for XFR-over-TLS. It has the
+# following attributes:
+#
+# tls-auth:
+ # The tls-auth name. Used to refer to this TLS auth information in the access control list.
+ #name: "tls-authname"
+ # The authentication domain name as defined in RFC8310.
+ #auth-domain-name: "example.com"
# Patterns have zone configuration and they are shared by one or more zones.
#
# By default, a slave will request a zone transfer with IXFR/TCP.
# If you want to make use of IXFR/UDP use: UDP addr tsigkey
# for a master that only speaks AXFR (like NSD) use AXFR addr tsigkey
+ # If you want to require use of XFR-over-TLS use: addr tsigkey tlsauthname
#request-xfr: 192.0.2.2 the_tsig_key_name
+ #request-xfr: 192.0.2.2 the_tsig_key_name the_tls_auth_name
# Attention: You cannot use UDP and AXFR together. AXFR is always over
# TCP. If you use UDP, we higly recommend you to deploy TSIG.
# Allow AXFR fallback if the master does not support IXFR. Default
#endif
};
+#define NSD_COOKIE_HISTORY_SIZE 2
+#define NSD_COOKIE_SECRET_SIZE 16
+
+typedef struct cookie_secret cookie_secret_type;
+struct cookie_secret {
+ /** cookie secret */
+ uint8_t cookie_secret[NSD_COOKIE_SECRET_SIZE];
+};
+
/* NSD configuration and run-time variables */
typedef struct nsd nsd_type;
struct nsd
/* the dnstap collector process info */
struct dt_collector* dt_collector;
/* the pipes from server processes to the dt_collector,
- * arrays of size child_count. Kept open for (re-)forks. */
+ * arrays of size child_count * 2. Kept open for (re-)forks. */
int *dt_collector_fd_send, *dt_collector_fd_recv;
+ /* the pipes from server processes to the dt_collector. Initially
+ * these point halfway into dt_collector_fd_send, but during reload
+ * the pointer is swapped with dt_collector_fd_send in order to
+ * to prevent writing to the dnstap collector by old serve childs
+ * simultaneous with new serve childs. */
+ int *dt_collector_fd_swap;
#endif /* USE_DNSTAP */
/* ratelimit for errors, time value */
time_t err_limit_time;
/* ratelimit for errors, packet count */
unsigned int err_limit_count;
+ /** do answer with server cookie when request contained cookie option */
+ int do_answer_cookie;
+
+ /** how many cookies are there in the cookies array */
+ size_t cookie_count;
+
+ /* keep track of the last `NSD_COOKIE_HISTORY_SIZE`
+ * cookies as per rfc requirement .*/
+ cookie_secret_type cookie_secrets[NSD_COOKIE_HISTORY_SIZE];
+
struct nsd_options* options;
#ifdef HAVE_SSL
return (domain_type*)r->key;
}
}
- if(zone->nsec3_last)
+ if(zone->nsec3_last && zone->nsec3_last != domain)
return zone->nsec3_last;
return NULL;
}
opt->zonestatnames = rbtree_create(opt->region, rbtree_strcmp);
opt->patterns = rbtree_create(region, rbtree_strcmp);
opt->keys = rbtree_create(region, rbtree_strcmp);
+ opt->tls_auths = rbtree_create(region, rbtree_strcmp);
opt->ip_addresses = NULL;
opt->ip_transparent = 0;
opt->ip_freebind = 0;
opt->tls_service_ocsp = NULL;
opt->tls_service_pem = NULL;
opt->tls_port = TLS_PORT;
+ opt->tls_cert_bundle = NULL;
+ opt->answer_cookie = 1;
+ opt->cookie_secret = NULL;
+ opt->cookie_secret_file = CONFIGDIR"/nsd_cookiesecrets.txt";
opt->control_enable = 0;
opt->control_interface = NULL;
opt->control_port = NSD_CONTROL_PORT;
cfg_parser->pattern = NULL;
cfg_parser->zone = NULL;
cfg_parser->key = NULL;
+ cfg_parser->tls_auth = NULL;
in = fopen(cfg_parser->filename, "r");
if(!in) {
}
for(acl=pat->request_xfr; acl; acl=acl->next)
{
+ /* Find tls_auth */
+ if (!acl->tls_auth_name)
+ ; /* pass */
+ else if (!(acl->tls_auth_options =
+ tls_auth_options_find(opt, acl->tls_auth_name)))
+ c_error("tls_auth %s in pattern %s could not be found",
+ acl->tls_auth_name, pat->pname);
+ /* Find key */
if(acl->nokey || acl->blocked)
continue;
acl->key_options = key_options_find(opt, acl->key_name);
} else if(p->key_name && !q->key_name) return 0;
else if(!p->key_name && q->key_name) return 0;
/* key_options is derived from key_name */
+ if(p->tls_auth_name && q->tls_auth_name) {
+ if(strcmp(p->tls_auth_name, q->tls_auth_name)!=0) return 0;
+ } else if(p->tls_auth_name && !q->tls_auth_name) return 0;
+ else if(!p->tls_auth_name && q->tls_auth_name) return 0;
+ /* tls_auth_options is derived from tls_auth_name */
return 1;
}
if(acl->key_name)
region_recycle(region, (void*)acl->key_name,
strlen(acl->key_name)+1);
+ if(acl->tls_auth_name)
+ region_recycle(region, (void*)acl->tls_auth_name,
+ strlen(acl->tls_auth_name)+1);
/* key_options is a convenience pointer, not owned by the acl */
region_recycle(region, acl, sizeof(*acl));
}
b->ip_address_spec = region_strdup(region, a->ip_address_spec);
if(a->key_name)
b->key_name = region_strdup(region, a->key_name);
+ if(a->tls_auth_name)
+ b->tls_auth_name = region_strdup(region, a->tls_auth_name);
b->next = NULL;
b->key_options = NULL;
+ b->tls_auth_options = NULL;
return b;
}
if(b->key_name)
b->key_options = key_options_find(opt, b->key_name);
else b->key_options = NULL;
+ /* fixup tls_auth_options */
+ if(b->tls_auth_name)
+ b->tls_auth_options = tls_auth_options_find(opt, b->tls_auth_name);
+ else b->tls_auth_options = NULL;
/* link as last into list */
b->next = NULL;
buffer_write(b, acl, sizeof(*acl));
marshal_str(b, acl->ip_address_spec);
marshal_str(b, acl->key_name);
+ marshal_str(b, acl->tls_auth_name);
}
static struct acl_options*
buffer_read(b, acl, sizeof(*acl));
acl->next = NULL;
acl->key_options = NULL;
+ acl->tls_auth_options = NULL;
acl->ip_address_spec = unmarshal_str(r, b);
acl->key_name = unmarshal_str(r, b);
+ acl->tls_auth_name = unmarshal_str(r, b);
return acl;
}
return key;
}
+struct tls_auth_options*
+tls_auth_options_create(region_type* region)
+{
+ struct tls_auth_options* tls_auth_options;
+ tls_auth_options = (struct tls_auth_options*)region_alloc_zero(region, sizeof(struct tls_auth_options));
+ return tls_auth_options;
+}
+
void
key_options_insert(struct nsd_options* opt, struct key_options* key)
{
return (struct key_options*)rbtree_search(opt->keys, name);
}
+void
+tls_auth_options_insert(struct nsd_options* opt, struct tls_auth_options* auth)
+{
+ if(!auth->name) return;
+ auth->node.key = auth->name;
+ (void)rbtree_insert(opt->tls_auths, &auth->node);
+}
+
+struct tls_auth_options*
+tls_auth_options_find(struct nsd_options* opt, const char* name)
+{
+ return (struct tls_auth_options*)rbtree_search(opt->tls_auths, name);
+}
+
/** remove tsig_key contents */
void
key_options_desetup(region_type* region, struct key_options* key)
static char f[1024];
/* if not a template, return as-is */
if(!strchr(zone->pattern->zonefile, '%')) {
- if (nsd->chrootdir && nsd->chrootdir[0] &&
+ if (nsd->chrootdir && nsd->chrootdir[0] &&
zone->pattern->zonefile &&
zone->pattern->zonefile[0] == '/' &&
strncmp(zone->pattern->zonefile, nsd->chrootdir,
acl->ixfr_disabled = 0;
acl->bad_xfr_count = 0;
acl->key_options = 0;
+ acl->tls_auth_options = 0;
+ acl->tls_auth_name = 0;
acl->is_ipv6 = 0;
acl->port = 0;
memset(&acl->addr, 0, sizeof(union acl_addr_storage));
typedef struct cpu_map_option cpu_map_option_type;
typedef struct acl_options acl_options_type;
typedef struct key_options key_options_type;
+typedef struct tls_auth_options tls_auth_options_type;
typedef struct config_parser_state config_parser_state_type;
/*
/* rbtree of keys defined, by name */
rbtree_type* keys;
+ /* rbtree of tls_auth defined, by name */
+ rbtree_type* tls_auths;
+
/* list of ip addresses to bind to (or NULL for all) */
struct ip_address_option* ip_addresses;
char* tls_service_pem;
/* TLS dedicated port */
const char* tls_port;
+ /* TLS certificate bundle */
+ const char* tls_cert_bundle;
/** remote control section. enable toggle. */
int control_enable;
/** true to log dnstap AUTH_RESPONSE message events */
int dnstap_log_auth_response_messages;
+ /** do answer with server cookie when request contained cookie option */
+ int answer_cookie;
+ /** cookie secret */
+ char *cookie_secret;
+ /** path to cookie secret store */
+ char const* cookie_secret_file;
+
region_type* region;
};
uint8_t blocked;
const char* key_name;
struct key_options* key_options;
+
+ /* tls_auth for XoT */
+ const char* tls_auth_name;
+ struct tls_auth_options* tls_auth_options;
} ATTR_PACKED;
/*
struct tsig_key* tsig_key;
} ATTR_PACKED;
+/*
+ * TLS Auth definition for XoT
+ */
+struct tls_auth_options {
+ rbnode_type node; /* key of tree is name */
+ char* name;
+ char* auth_domain_name;
+};
+
/** zone list free space */
struct zonelist_free {
struct zonelist_free* next;
struct pattern_options *pattern;
struct zone_options *zone;
struct key_options *key;
+ struct tls_auth_options *tls_auth;
struct ip_address_option *ip;
void (*err)(void*,const char*);
void* err_arg;
void key_options_add_modify(struct nsd_options* opt, struct key_options* key);
void key_options_setup(region_type* region, struct key_options* key);
void key_options_desetup(region_type* region, struct key_options* key);
+/* TLS auth */
+struct tls_auth_options* tls_auth_options_create(region_type* region);
+void tls_auth_options_insert(struct nsd_options* opt, struct tls_auth_options* auth);
+struct tls_auth_options* tls_auth_options_find(struct nsd_options* opt, const char* name);
/* read in zone list file. Returns false on failure */
int parse_zone_list_file(struct nsd_options* opt);
/* create zone entry and add to the zonelist file */
*
*/
query_state_type
-query_process(query_type *q, nsd_type *nsd)
+query_process(query_type *q, nsd_type *nsd, uint32_t *now_p)
{
/* The query... */
nsd_rc_type rc;
if(process_edns(nsd, q) == NSD_RC_OK) {
int opcode = OPCODE(q->packet);
(void)query_error(q, NSD_RC_FORMAT);
- query_add_optional(q, nsd);
+ query_add_optional(q, nsd, now_p);
FLAGS_SET(q->packet, FLAGS(q->packet) & 0x0100U);
/* Preserve the RD flag. Clear the rest. */
OPCODE_SET(q->packet, opcode);
return QUERY_PROCESSED;
}
+ if (q->edns.cookie_status == COOKIE_UNVERIFIED)
+ cookie_verify(q, nsd, now_p);
+
query_prepare_response(q);
if (q->qclass != CLASS_IN && q->qclass != CLASS_ANY) {
}
void
-query_add_optional(query_type *q, nsd_type *nsd)
+query_add_optional(query_type *q, nsd_type *nsd, uint32_t *now_p)
{
struct edns_data *edns = &nsd->edns_ipv4;
#if defined(INET6)
/* nsid payload */
buffer_write(q->packet, nsd->nsid, nsd->nsid_len);
}
+ if(q->edns.cookie_status != COOKIE_NOT_PRESENT) {
+ /* cookie opt header */
+ buffer_write(q->packet, edns->cookie, OPT_HDR);
+ /* cookie payload */
+ cookie_create(q, nsd, now_p);
+ buffer_write(q->packet, q->edns.cookie, 24);
+ }
/* Append Extended DNS Error (RFC8914) option if needed */
if (q->edns.ede >= 0) { /* < 0 means no EDE */
/* OPTION-CODE */
/*
* Process a query and write the response in the query I/O buffer.
*/
-query_state_type query_process(query_type *q, nsd_type *nsd);
+query_state_type query_process(query_type *q, nsd_type *nsd, uint32_t *now_p);
/*
* Prepare the query structure for writing the response. The packet
/*
* Add EDNS0 information to the response if required.
*/
-void query_add_optional(query_type *q, nsd_type *nsd);
+void query_add_optional(query_type *q, nsd_type *nsd, uint32_t *now_p);
/*
* Write an error response into the query structure with the indicated
{ 0, NULL }
};
+const char *svcparamkey_strs[] = {
+ "mandatory", "alpn", "no-default-alpn", "port",
+ "ipv4hint", "ech", "ipv6hint"
+ };
+
typedef int (*rdata_to_string_type)(buffer_type *output,
rdata_atom_type rdata,
rr_type *rr);
return 0;
}
+static void
+buffer_print_svcparamkey(buffer_type *output, uint16_t svcparamkey)
+{
+ if (svcparamkey < SVCPARAMKEY_COUNT)
+ buffer_printf(output, "%s", svcparamkey_strs[svcparamkey]);
+ else
+ buffer_printf(output, "key%d", (int)svcparamkey);
+}
+
+static int
+rdata_svcparam_port_to_string(buffer_type *output, uint16_t val_len,
+ uint16_t *data)
+{
+ if (val_len != 2)
+ return 0; /* wireformat error, a short is 2 bytes */
+ buffer_printf(output, "=%d", (int)ntohs(data[0]));
+ return 1;
+}
+
+static int
+rdata_svcparam_ipv4hint_to_string(buffer_type *output, uint16_t val_len,
+ uint16_t *data)
+{
+ char ip_str[INET_ADDRSTRLEN + 1];
+
+ assert(val_len > 0); /* Guaranteed by rdata_svcparam_to_string */
+
+ if ((val_len % IP4ADDRLEN) == 0) {
+ if (inet_ntop(AF_INET, data, ip_str, sizeof(ip_str)) == NULL)
+ return 0; /* wireformat error, incorrect size or inet family */
+
+ buffer_printf(output, "=%s", ip_str);
+ data += IP4ADDRLEN / sizeof(uint16_t);
+
+ while ((val_len -= IP4ADDRLEN) > 0) {
+ if (inet_ntop(AF_INET, data, ip_str, sizeof(ip_str)) == NULL)
+ return 0; /* wireformat error, incorrect size or inet family */
+
+ buffer_printf(output, ",%s", ip_str);
+ data += IP4ADDRLEN / sizeof(uint16_t);
+ }
+ return 1;
+ } else
+ return 0;
+}
+
+static int
+rdata_svcparam_ipv6hint_to_string(buffer_type *output, uint16_t val_len,
+ uint16_t *data)
+{
+ char ip_str[INET6_ADDRSTRLEN + 1];
+
+ assert(val_len > 0); /* Guaranteed by rdata_svcparam_to_string */
+
+ if ((val_len % IP6ADDRLEN) == 0) {
+ if (inet_ntop(AF_INET6, data, ip_str, sizeof(ip_str)) == NULL)
+ return 0; /* wireformat error, incorrect size or inet family */
+
+ buffer_printf(output, "=%s", ip_str);
+ data += IP6ADDRLEN / sizeof(uint16_t);
+
+ while ((val_len -= IP6ADDRLEN) > 0) {
+ if (inet_ntop(AF_INET6, data, ip_str, sizeof(ip_str)) == NULL)
+ return 0; /* wireformat error, incorrect size or inet family */
+
+ buffer_printf(output, ",%s", ip_str);
+ data += IP6ADDRLEN / sizeof(uint16_t);
+ }
+ return 1;
+ } else
+ return 0;
+}
+
+static int
+rdata_svcparam_mandatory_to_string(buffer_type *output, uint16_t val_len,
+ uint16_t *data)
+{
+ assert(val_len > 0); /* Guaranteed by rdata_svcparam_to_string */
+
+ if (val_len % sizeof(uint16_t))
+ return 0; /* wireformat error, val_len must be multiple of shorts */
+ buffer_write_u8(output, '=');
+ buffer_print_svcparamkey(output, ntohs(*data));
+ data += 1;
+
+ while ((val_len -= sizeof(uint16_t))) {
+ buffer_write_u8(output, ',');
+ buffer_print_svcparamkey(output, ntohs(*data));
+ data += 1;
+ }
+
+ return 1;
+}
+
+static int
+rdata_svcparam_ech_to_string(buffer_type *output, uint16_t val_len,
+ uint16_t *data)
+{
+ int length;
+
+ assert(val_len > 0); /* Guaranteed by rdata_svcparam_to_string */
+
+ buffer_write_u8(output, '=');
+
+ buffer_reserve(output, val_len * 2 + 1);
+ length = __b64_ntop((uint8_t*) data, val_len,
+ (char *) buffer_current(output), val_len * 2);
+ if (length > 0) {
+ buffer_skip(output, length);
+ }
+
+ return length != -1;
+}
+
+static int
+rdata_svcparam_alpn_to_string(buffer_type *output, uint16_t val_len,
+ uint16_t *data)
+{
+ uint8_t *dp = (void *)data;
+
+ assert(val_len > 0); /* Guaranteed by rdata_svcparam_to_string */
+
+ buffer_write_u8(output, '=');
+ buffer_write_u8(output, '"');
+ while (val_len) {
+ uint8_t i, str_len = *dp++;
+
+ if (str_len > --val_len)
+ return 0;
+
+ for (i = 0; i < str_len; i++) {
+ if (dp[i] == '"' || dp[i] == '\\')
+ buffer_printf(output, "\\\\\\%c", dp[i]);
+
+ else if (dp[i] == ',')
+ buffer_printf(output, "\\\\%c", dp[i]);
+
+ else if (!isprint(dp[i]))
+ buffer_printf(output, "\\%03u", (unsigned) dp[i]);
+
+ else
+ buffer_write_u8(output, dp[i]);
+ }
+ dp += str_len;
+ if ((val_len -= str_len))
+ buffer_write_u8(output, ',');
+ }
+ buffer_write_u8(output, '"');
+ return 1;
+}
+
+static int
+rdata_svcparam_to_string(buffer_type *output, rdata_atom_type rdata,
+ rr_type* ATTR_UNUSED(rr))
+{
+ uint16_t size = rdata_atom_size(rdata);
+ uint16_t* data = (uint16_t *)rdata_atom_data(rdata);
+ uint16_t svcparamkey, val_len;
+ uint8_t* dp;
+ size_t i;
+
+ if (size < 4)
+ return 0;
+ svcparamkey = ntohs(data[0]);
+
+ buffer_print_svcparamkey(output, svcparamkey);
+ val_len = ntohs(data[1]);
+ if (size != val_len + 4)
+ return 0; /* wireformat error */
+ if (!val_len) {
+ /* Some SvcParams MUST have values */
+ switch (svcparamkey) {
+ case SVCB_KEY_ALPN:
+ case SVCB_KEY_PORT:
+ case SVCB_KEY_IPV4HINT:
+ case SVCB_KEY_IPV6HINT:
+ case SVCB_KEY_MANDATORY:
+ return 0;
+ default:
+ return 1;
+ }
+ }
+ switch (svcparamkey) {
+ case SVCB_KEY_PORT:
+ return rdata_svcparam_port_to_string(output, val_len, data+2);
+ case SVCB_KEY_IPV4HINT:
+ return rdata_svcparam_ipv4hint_to_string(output, val_len, data+2);
+ case SVCB_KEY_IPV6HINT:
+ return rdata_svcparam_ipv6hint_to_string(output, val_len, data+2);
+ case SVCB_KEY_MANDATORY:
+ return rdata_svcparam_mandatory_to_string(output, val_len, data+2);
+ case SVCB_KEY_NO_DEFAULT_ALPN:
+ return 0; /* wireformat error, should not have a value */
+ case SVCB_KEY_ALPN:
+ return rdata_svcparam_alpn_to_string(output, val_len, data+2);
+ case SVCB_KEY_ECH:
+ return rdata_svcparam_ech_to_string(output, val_len, data+2);
+ default:
+ buffer_write(output, "=\"", 2);
+ dp = (void*) (data + 2);
+
+ for (i = 0; i < val_len; i++) {
+ if (dp[i] == '"' || dp[i] == '\\')
+ buffer_printf(output, "\\%c", dp[i]);
+
+ else if (!isprint(dp[i]))
+ buffer_printf(output, "\\%03u", (unsigned) dp[i]);
+
+ else
+ buffer_write_u8(output, dp[i]);
+ }
+ buffer_write_u8(output, '"');
+ break;
+ }
+ return 1;
+}
+
static int
rdata_unknown_to_string(buffer_type *output, rdata_atom_type rdata,
rr_type* ATTR_UNUSED(rr))
rdata_eui64_to_string,
rdata_long_text_to_string,
rdata_tag_to_string,
+ rdata_svcparam_to_string,
rdata_unknown_to_string
};
break;
}
break;
+ case RDATA_WF_SVCPARAM:
+ length = 4;
+ if (buffer_position(packet) + 4 <= end) {
+ length +=
+ read_uint16(buffer_current(packet) + 2);
+ }
+ break;
}
if (is_domain) {
return 1;
}
-
extern lookup_table_type dns_certificate_types[];
extern lookup_table_type dns_algorithms[];
+extern const char *svcparamkey_strs[];
int rdata_atom_to_string(buffer_type *output, rdata_zoneformat_type type,
rdata_atom_type rdata, rr_type *rr);
send_ok(ssl);
}
+/* returns `0` on failure */
+static int
+cookie_secret_file_dump(RES* ssl, nsd_type const* nsd) {
+ char const* secret_file = nsd->options->cookie_secret_file;
+ char secret_hex[NSD_COOKIE_SECRET_SIZE * 2 + 1];
+ FILE* f;
+ size_t i;
+ 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;
+ }
+ for(i = 0; i < nsd->cookie_count; i++) {
+ struct cookie_secret const* cs = &nsd->cookie_secrets[i];
+ ssize_t const len = hex_ntop(cs->cookie_secret, NSD_COOKIE_SECRET_SIZE,
+ secret_hex, sizeof(secret_hex));
+ (void)len; /* silence unused variable warning with -DNDEBUG */
+ assert( len == NSD_COOKIE_SECRET_SIZE * 2 );
+ secret_hex[NSD_COOKIE_SECRET_SIZE * 2] = '\0';
+ fprintf(f, "%s\n", secret_hex);
+ }
+ explicit_bzero(secret_hex, sizeof(secret_hex));
+ fclose(f);
+ return 1;
+}
+
+static void
+do_activate_cookie_secret(RES* ssl, xfrd_state_type* xrfd, char* arg) {
+ nsd_type* nsd = xrfd->nsd;
+ (void)arg;
+
+ if(nsd->cookie_count <= 1 ) {
+ (void)ssl_printf(ssl, "error: no staging cookie secret to activate\n");
+ return;
+ }
+ if(!nsd->options->cookie_secret_file || !nsd->options->cookie_secret_file[0]) {
+ (void)ssl_printf(ssl, "error: no cookie secret file configured\n");
+ return;
+ }
+ if(!cookie_secret_file_dump(ssl, nsd)) {
+ (void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
+ nsd->options->cookie_secret_file);
+ return;
+ }
+ activate_cookie_secret(nsd);
+ (void)cookie_secret_file_dump(ssl, nsd);
+ task_new_activate_cookie_secret(xfrd->nsd->task[xfrd->nsd->mytask],
+ xfrd->last_task);
+ xfrd_set_reload_now(xfrd);
+ send_ok(ssl);
+}
+
+static void
+do_drop_cookie_secret(RES* ssl, xfrd_state_type* xrfd, char* arg) {
+ nsd_type* nsd = xrfd->nsd;
+ (void)arg;
+
+ if(nsd->cookie_count <= 1 ) {
+ (void)ssl_printf(ssl, "error: can not drop the currently active cookie secret\n");
+ return;
+ }
+ if(!nsd->options->cookie_secret_file || !nsd->options->cookie_secret_file[0]) {
+ (void)ssl_printf(ssl, "error: no cookie secret file configured\n");
+ return;
+ }
+ if(!cookie_secret_file_dump(ssl, nsd)) {
+ (void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
+ nsd->options->cookie_secret_file);
+ return;
+ }
+ drop_cookie_secret(nsd);
+ (void)cookie_secret_file_dump(ssl, nsd);
+ task_new_drop_cookie_secret(xfrd->nsd->task[xfrd->nsd->mytask],
+ xfrd->last_task);
+ xfrd_set_reload_now(xfrd);
+ send_ok(ssl);
+}
+
+static void
+do_add_cookie_secret(RES* ssl, xfrd_state_type* xrfd, char* arg) {
+ nsd_type* nsd = xrfd->nsd;
+ uint8_t secret[NSD_COOKIE_SECRET_SIZE];
+
+ if(*arg == '\0') {
+ (void)ssl_printf(ssl, "error: missing argument (cookie_secret)\n");
+ return;
+ }
+ if(strlen(arg) != 32) {
+ 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, NSD_COOKIE_SECRET_SIZE) != NSD_COOKIE_SECRET_SIZE ) {
+ explicit_bzero(secret, NSD_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;
+ }
+ if(!nsd->options->cookie_secret_file || !nsd->options->cookie_secret_file[0]) {
+ explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE);
+ explicit_bzero(arg, strlen(arg));
+ (void)ssl_printf(ssl, "error: no cookie secret file configured\n");
+ return;
+ }
+ if(!cookie_secret_file_dump(ssl, nsd)) {
+ explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE);
+ explicit_bzero(arg, strlen(arg));
+ (void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
+ nsd->options->cookie_secret_file);
+ return;
+ }
+ add_cookie_secret(nsd, secret);
+ explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE);
+ (void)cookie_secret_file_dump(ssl, nsd);
+ task_new_add_cookie_secret(xfrd->nsd->task[xfrd->nsd->mytask],
+ xfrd->last_task, arg);
+ explicit_bzero(arg, strlen(arg));
+ xfrd_set_reload_now(xfrd);
+ send_ok(ssl);
+}
+
+static void
+do_print_cookie_secrets(RES* ssl, xfrd_state_type* xrfd, char* arg) {
+ nsd_type* nsd = xrfd->nsd;
+ char secret_hex[NSD_COOKIE_SECRET_SIZE * 2 + 1];
+ int i;
+ (void)arg;
+
+ /* (void)ssl_printf(ssl, "cookie_secret_count=%zu\n", nsd->cookie_count); */
+ for(i = 0; (size_t)i < nsd->cookie_count; i++) {
+ struct cookie_secret const* cs = &nsd->cookie_secrets[i];
+ ssize_t const len = hex_ntop(cs->cookie_secret, NSD_COOKIE_SECRET_SIZE,
+ secret_hex, sizeof(secret_hex));
+ (void)len; /* silence unused variable warning with -DNDEBUG */
+ assert( len == NSD_COOKIE_SECRET_SIZE * 2 );
+ secret_hex[NSD_COOKIE_SECRET_SIZE * 2] = '\0';
+ if (i == 0)
+ (void)ssl_printf(ssl, "active : %s\n", secret_hex);
+ else if (nsd->cookie_count == 2)
+ (void)ssl_printf(ssl, "staging: %s\n", secret_hex);
+ else
+ (void)ssl_printf(ssl, "staging[%d]: %s\n", i, secret_hex);
+ }
+ 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)
do_assoc_tsig(ssl, rc->xfrd, skipwhite(p+10));
} else if(cmdcmp(p, "del_tsig", 8)) {
do_del_tsig(ssl, rc->xfrd, skipwhite(p+8));
+ } else if(cmdcmp(p, "add_cookie_secret", 17)) {
+ do_add_cookie_secret(ssl, rc->xfrd, skipwhite(p+17));
+ } else if(cmdcmp(p, "drop_cookie_secret", 18)) {
+ do_drop_cookie_secret(ssl, rc->xfrd, skipwhite(p+18));
+ } else if(cmdcmp(p, "print_cookie_secrets", 20)) {
+ do_print_cookie_secrets(ssl, rc->xfrd, skipwhite(p+20));
+ } else if(cmdcmp(p, "activate_cookie_secret", 22)) {
+ do_activate_cookie_secret(ssl, rc->xfrd, skipwhite(p+22));
} else {
(void)ssl_printf(ssl, "error unknown command '%s'\n", p);
}
static void
log_addr(const char* descr,
#ifdef INET6
- struct sockaddr_storage* addr,
+ struct sockaddr_storage* addr
#else
- struct sockaddr_in* addr,
+ struct sockaddr_in* addr
#endif
- short family)
+ )
{
char str_buf[64];
if(verbosity < 6)
return;
- if(family == AF_INET) {
+ if(
+#ifdef INET6
+ addr->ss_family == AF_INET
+#else
+ addr->sin_family == AF_INET
+#endif
+ ) {
struct sockaddr_in* s = (struct sockaddr_in*)addr;
inet_ntop(AF_INET, &s->sin_addr.s_addr, str_buf, sizeof(str_buf));
VERBOSITY(6, (LOG_INFO, "%s: address is: %s, port is: %d", descr, str_buf, ntohs(s->sin_port)));
return 1;
}
+#ifdef INET6
static int
set_ipv6_v6only(struct nsd_socket *sock)
{
-#ifdef INET6
#ifdef IPV6_V6ONLY
int on = 1;
const char *socktype =
log_msg(LOG_ERR, "setsockopt(..., IPV6_V6ONLY, ...) failed for %s: %s",
socktype, strerror(errno));
return -1;
+#else
+ (void)sock;
#endif /* IPV6_V6ONLY */
-#endif /* INET6 */
return 0;
}
+#endif /* INET6 */
+#ifdef INET6
static int
set_ipv6_use_min_mtu(struct nsd_socket *sock)
{
-#if defined(INET6) && (defined(IPV6_USE_MIN_MTU) || defined(IPV6_MTU))
+#if defined(IPV6_USE_MIN_MTU) || defined(IPV6_MTU)
#if defined(IPV6_USE_MIN_MTU)
/* There is no fragmentation of IPv6 datagrams during forwarding in the
* network. Therefore we do not send UDP datagrams larger than the
return 0;
}
+#endif /* INET6 */
static int
set_ipv4_no_pmtu_disc(struct nsd_socket *sock)
/* listen for the signals of failed children again */
sigaction(SIGCHLD, &old_sigchld, NULL);
+#ifdef USE_DNSTAP
+ if (nsd->dt_collector) {
+ int *swap_fd_send;
+ DEBUG(DEBUG_IPC,1, (LOG_INFO, "reload: swap dnstap collector pipes"));
+ /* Swap fd_send with fd_swap so old serve child and new serve
+ * childs will not write to the same pipe ends simultaneously */
+ swap_fd_send = nsd->dt_collector_fd_send;
+ nsd->dt_collector_fd_send = nsd->dt_collector_fd_swap;
+ nsd->dt_collector_fd_swap = swap_fd_send;
+
+ }
+#endif
/* Start new child processes */
if (server_start_children(nsd, server_region, netio, &nsd->
xfrd_listener->fd) != 0) {
log_msg(LOG_ERR, "problems sending reloadpid to xfrd: %s",
strerror(errno));
}
+#ifdef USE_DNSTAP
+ } else if(nsd->dt_collector && child_pid == nsd->dt_collector->dt_pid) {
+ log_msg(LOG_WARNING,
+ "dnstap-collector %d terminated with status %d",
+ (int) child_pid, status);
+ if(nsd->dt_collector) {
+ dt_collector_close(nsd->dt_collector, nsd);
+ dt_collector_destroy(nsd->dt_collector, nsd);
+ nsd->dt_collector = NULL;
+ }
+ /* Only respawn a crashed (or exited)
+ * dnstap-collector when not reloading,
+ * to not induce a reload during a
+ * reload (which would seriously
+ * disrupt nsd procedures and lead to
+ * unpredictable results)!
+ *
+ * This will *leave* a dnstap-collector
+ * process terminated, but because
+ * signalling of the reload process to
+ * the main process to respawn in this
+ * situation will be cumbersome, and
+ * because this situation is so
+ * specific (and therefore hopefully
+ * extremely rare or non-existing at
+ * all), plus the fact that we are left
+ * with a perfectly function NSD
+ * (besides not logging dnstap
+ * messages), I consider it acceptable
+ * to leave this unresolved.
+ */
+ if(reload_pid == -1 && nsd->options->dnstap_enable) {
+ nsd->dt_collector = dt_collector_create(nsd);
+ dt_collector_start(nsd->dt_collector, nsd);
+ nsd->mode = NSD_RELOAD_REQ;
+ }
+#endif
} else if(status != 0) {
/* check for status, because we get
* the old-servermain because reload
}
static query_state_type
-server_process_query(struct nsd *nsd, struct query *query)
+server_process_query(struct nsd *nsd, struct query *query, uint32_t *now_p)
{
- return query_process(query, nsd);
+ return query_process(query, nsd, now_p);
}
static query_state_type
-server_process_query_udp(struct nsd *nsd, struct query *query)
+server_process_query_udp(struct nsd *nsd, struct query *query, uint32_t *now_p)
{
#ifdef RATELIMIT
- if(query_process(query, nsd) != QUERY_DISCARDED) {
- if(rrl_process_query(query))
+ if(query_process(query, nsd, now_p) != QUERY_DISCARDED) {
+ if(query->edns.cookie_status != COOKIE_VALID
+ && query->edns.cookie_status != COOKIE_VALID_REUSE
+ && rrl_process_query(query))
return rrl_slip(query);
else return QUERY_PROCESSED;
}
return QUERY_DISCARDED;
#else
- return query_process(query, nsd);
+ return query_process(query, nsd, now_p);
#endif
}
data->tls_accept = 1;
if(verbosity >= 2) {
char buf[48];
- addrport2str((struct sockaddr_storage*)&sock->addr.ai_addr, buf, sizeof(buf));
- VERBOSITY(2, (LOG_NOTICE, "setup TCP for TLS service on interface %s", buf));
+ addrport2str((void*)(struct sockaddr_storage*)&sock->addr.ai_addr, buf, sizeof(buf));
+ VERBOSITY(4, (LOG_NOTICE, "setup TCP for TLS service on interface %s", buf));
}
} else {
data->tls_accept = 0;
if(nsd->current_tcp_count == 0 || tcp_active_list == NULL)
return;
VERBOSITY(4, (LOG_INFO, "service remaining TCP connections"));
-
+#ifdef USE_DNSTAP
+ /* remove dnstap collector, we cannot write there because the new
+ * child process is using the file descriptor, or the child
+ * process after that. */
+ dt_collector_destroy(nsd->dt_collector, nsd);
+ nsd->dt_collector = NULL;
+#endif
/* setup event base */
event_base = nsd_child_event_base();
if(!event_base) {
}
#endif /* HAVE_SENDMMSG */
+static int
+port_is_zero(
+#ifdef INET6
+ struct sockaddr_storage *addr
+#else
+ struct sockaddr_in *addr
+#endif
+ )
+{
+#ifdef INET6
+ if(addr->ss_family == AF_INET6) {
+ return (((struct sockaddr_in6 *)addr)->sin6_port) == 0;
+ } else if(addr->ss_family == AF_INET) {
+ return (((struct sockaddr_in *)addr)->sin_port) == 0;
+ }
+ return 0;
+#else
+ if(addr->sin_family == AF_INET) {
+ return addr->sin_port == 0;
+ }
+ return 0;
+#endif
+}
+
static void
handle_udp(int fd, short event, void* arg)
{
struct udp_handler_data *data = (struct udp_handler_data *) arg;
int received, sent, recvcount, i;
struct query *q;
+ uint32_t now = 0;
if (!(event & EV_READ)) {
return;
/*
* sending UDP-query with server address (local) and client address to dnstap process
*/
- log_addr("query from client", &q->addr, data->socket->addr.ai_family);
- log_addr("to server (local)", &data->socket->addr.ai_addr, data->socket->addr.ai_family);
- dt_collector_submit_auth_query(data->nsd, &data->socket->addr.ai_addr, &q->addr, q->addrlen,
+ log_addr("query from client", &q->addr);
+ log_addr("to server (local)", (void*)&data->socket->addr.ai_addr);
+ dt_collector_submit_auth_query(data->nsd, (void*)&data->socket->addr.ai_addr, &q->addr, q->addrlen,
q->tcp, q->packet);
#endif /* USE_DNSTAP */
/* Process and answer the query... */
- if (server_process_query_udp(data->nsd, q) != QUERY_DISCARDED) {
+ if (server_process_query_udp(data->nsd, q, &now) != QUERY_DISCARDED) {
if (RCODE(q->packet) == RCODE_OK && !AA(q->packet)) {
STATUP(data->nsd, nona);
ZTATUP(data->nsd, q->zone, nona);
#endif
/* Add EDNS0 and TSIG info if necessary. */
- query_add_optional(q, data->nsd);
+ query_add_optional(q, data->nsd, &now);
buffer_flip(q->packet);
iovecs[i].iov_len = buffer_remaining(q->packet);
/*
* sending UDP-response with server address (local) and client address to dnstap process
*/
- log_addr("from server (local)", &data->socket->addr.ai_addr, data->socket->addr.ai_family);
- log_addr("response to client", &q->addr, data->socket->addr.ai_family);
- dt_collector_submit_auth_response(data->nsd, &data->socket->addr.ai_addr,
+ log_addr("from server (local)", (void*)&data->socket->addr.ai_addr);
+ log_addr("response to client", &q->addr);
+ dt_collector_submit_auth_response(data->nsd, (void*)&data->socket->addr.ai_addr,
&q->addr, q->addrlen, q->tcp, q->packet,
q->zone);
#endif /* USE_DNSTAP */
}
errno = errstore;
}
+ if(errno == EINVAL) {
+ /* skip the invalid argument entry,
+ * send the remaining packets in the list */
+ if(!(port_is_zero((void*)&queries[i]->addr) &&
+ verbosity < 3)) {
+ const char* es = strerror(errno);
+ char a[64];
+ addrport2str((void*)&queries[i]->addr, a, sizeof(a));
+ log_msg(LOG_ERR, "sendmmsg skip invalid argument [0]=%s count=%d failed: %s", a, (int)(recvcount-i), es);
+ }
+ i += 1;
+ continue;
+ }
/* don't log transient network full errors, unless
* on higher verbosity */
if(!(errno == ENOBUFS && verbosity < 1) &&
errno != EAGAIN) {
const char* es = strerror(errno);
char a[64];
- addrport2str(&queries[i]->addr, a, sizeof(a));
+ addrport2str((void*)&queries[i]->addr, a, sizeof(a));
log_msg(LOG_ERR, "sendmmsg [0]=%s count=%d failed: %s", a, (int)(recvcount-i), es);
}
#ifdef BIND8_STATS
ssize_t received;
struct event_base* ev_base;
struct timeval timeout;
+ uint32_t now = 0;
if ((event & EV_TIMEOUT)) {
/* Connection timed out. */
/*
* and send TCP-query with found address (local) and client address to dnstap process
*/
- log_addr("query from client", &data->query->addr, data->query->addr.ss_family);
- log_addr("to server (local)", &data->socket->addr.ai_addr, data->query->addr.ss_family);
- dt_collector_submit_auth_query(data->nsd, &data->socket->addr.ai_addr, &data->query->addr,
+ log_addr("query from client", &data->query->addr);
+ log_addr("to server (local)", (void*)&data->socket->addr.ai_addr);
+ dt_collector_submit_auth_query(data->nsd, (void*)&data->socket->addr.ai_addr, &data->query->addr,
data->query->addrlen, data->query->tcp, data->query->packet);
#endif /* USE_DNSTAP */
- data->query_state = server_process_query(data->nsd, data->query);
+ data->query_state = server_process_query(data->nsd, data->query, &now);
if (data->query_state == QUERY_DISCARDED) {
/* Drop the packet and the entire connection... */
STATUP(data->nsd, dropped);
#endif
#endif /* USE_ZONE_STATS */
- query_add_optional(data->query, data->nsd);
+ query_add_optional(data->query, data->nsd, &now);
/* Switch to the tcp write handler. */
buffer_flip(data->query->packet);
/*
* sending TCP-response with found (earlier) address (local) and client address to dnstap process
*/
- log_addr("from server (local)", &data->socket->addr.ai_addr, data->query->addr.ss_family);
- log_addr("response to client", &data->query->addr, data->query->addr.ss_family);
- dt_collector_submit_auth_response(data->nsd, &data->socket->addr.ai_addr, &data->query->addr,
+ log_addr("from server (local)", (void*)&data->socket->addr.ai_addr);
+ log_addr("response to client", &data->query->addr);
+ dt_collector_submit_auth_response(data->nsd, (void*)&data->socket->addr.ai_addr, &data->query->addr,
data->query->addrlen, data->query->tcp, data->query->packet,
data->query->zone);
#endif /* USE_DNSTAP */
struct query *q = data->query;
struct timeval timeout;
struct event_base* ev_base;
+ uint32_t now = 0;
if ((event & EV_TIMEOUT)) {
/* Connection timed out. */
buffer_clear(q->packet);
data->query_state = query_axfr(data->nsd, q);
if (data->query_state != QUERY_PROCESSED) {
- query_add_optional(data->query, data->nsd);
+ query_add_optional(data->query, data->nsd, &now);
/* Reset data. */
buffer_flip(q->packet);
{
struct tcp_handler_data *data = (struct tcp_handler_data *) arg;
ssize_t received;
+ uint32_t now = 0;
if ((event & EV_TIMEOUT)) {
/* Connection timed out. */
/*
* and send TCP-query with found address (local) and client address to dnstap process
*/
- log_addr("query from client", &data->query->addr, data->query->addr.ss_family);
- log_addr("to server (local)", &data->socket->addr.ai_addr, data->query->addr.ss_family);
- dt_collector_submit_auth_query(data->nsd, &data->socket->addr.ai_addr, &data->query->addr,
+ log_addr("query from client", &data->query->addr);
+ log_addr("to server (local)", (void*)&data->socket->addr.ai_addr);
+ dt_collector_submit_auth_query(data->nsd, (void*)&data->socket->addr.ai_addr, &data->query->addr,
data->query->addrlen, data->query->tcp, data->query->packet);
#endif /* USE_DNSTAP */
- data->query_state = server_process_query(data->nsd, data->query);
+ data->query_state = server_process_query(data->nsd, data->query, &now);
if (data->query_state == QUERY_DISCARDED) {
/* Drop the packet and the entire connection... */
STATUP(data->nsd, dropped);
#endif
#endif /* USE_ZONE_STATS */
- query_add_optional(data->query, data->nsd);
+ query_add_optional(data->query, data->nsd, &now);
/* Switch to the tcp write handler. */
buffer_flip(data->query->packet);
/*
* sending TCP-response with found (earlier) address (local) and client address to dnstap process
*/
- log_addr("from server (local)", &data->socket->addr.ai_addr, data->query->addr.ss_family);
- log_addr("response to client", &data->query->addr, data->query->addr.ss_family);
- dt_collector_submit_auth_response(data->nsd, &data->socket->addr.ai_addr, &data->query->addr,
+ log_addr("from server (local)", (void*)&data->socket->addr.ai_addr);
+ log_addr("response to client", &data->query->addr);
+ dt_collector_submit_auth_response(data->nsd, (void*)&data->socket->addr.ai_addr, &data->query->addr,
data->query->addrlen, data->query->tcp, data->query->packet,
data->query->zone);
#endif /* USE_DNSTAP */
* TCP length in front of the packet, like writev. */
static buffer_type* global_tls_temp_buffer = NULL;
buffer_type* write_buffer;
+ uint32_t now = 0;
if ((event & EV_TIMEOUT)) {
/* Connection timed out. */
buffer_clear(q->packet);
data->query_state = query_axfr(data->nsd, q);
if (data->query_state != QUERY_PROCESSED) {
- query_add_optional(data->query, data->nsd);
+ query_add_optional(data->query, data->nsd, &now);
/* Reset data. */
buffer_flip(q->packet);
#include "namedb.h"
#include "rdata.h"
#include "zonec.h"
+#include "nsd.h"
#ifdef USE_MMAP_ALLOC
#include <sys/mman.h>
}
#endif
#endif /* HAVE_CPUSET_T */
+
+void add_cookie_secret(struct nsd* nsd, uint8_t* secret)
+{
+ /* New cookie secret becomes the staging secret (position 1)
+ * unless there is no active cookie yet, then it becomes the active
+ * secret. If the NSD_COOKIE_HISTORY_SIZE > 2 then all staging cookies
+ * are moved one position down.
+ */
+ if(nsd->cookie_count == 0) {
+ memcpy( nsd->cookie_secrets->cookie_secret
+ , secret, NSD_COOKIE_SECRET_SIZE);
+ nsd->cookie_count = 1;
+ explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE);
+ return;
+ }
+#if NSD_COOKIE_HISTORY_SIZE > 2
+ memmove( &nsd->cookie_secrets[2], &nsd->cookie_secrets[1]
+ , sizeof(struct cookie_secret) * (NSD_COOKIE_HISTORY_SIZE - 2));
+#endif
+ memcpy( nsd->cookie_secrets[1].cookie_secret
+ , secret, NSD_COOKIE_SECRET_SIZE);
+ nsd->cookie_count = nsd->cookie_count < NSD_COOKIE_HISTORY_SIZE
+ ? nsd->cookie_count + 1 : NSD_COOKIE_HISTORY_SIZE;
+ explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE);
+}
+
+void activate_cookie_secret(struct nsd* nsd)
+{
+ uint8_t active_secret[NSD_COOKIE_SECRET_SIZE];
+ /* The staging secret becomes the active secret.
+ * The active secret becomes a staging secret.
+ * If the NSD_COOKIE_HISTORY_SIZE > 2 then all staging secrets are moved
+ * one position up and the previously active secret becomes the last
+ * staging secret.
+ */
+ if(nsd->cookie_count < 2)
+ return;
+ memcpy( active_secret, nsd->cookie_secrets[0].cookie_secret
+ , NSD_COOKIE_SECRET_SIZE);
+ memmove( &nsd->cookie_secrets[0], &nsd->cookie_secrets[1]
+ , sizeof(struct cookie_secret) * (NSD_COOKIE_HISTORY_SIZE - 1));
+ memcpy( nsd->cookie_secrets[nsd->cookie_count - 1].cookie_secret
+ , active_secret, NSD_COOKIE_SECRET_SIZE);
+ explicit_bzero(active_secret, NSD_COOKIE_SECRET_SIZE);
+}
+
+void drop_cookie_secret(struct nsd* nsd)
+{
+ /* Drops a staging cookie secret. If there are more than one, it will
+ * drop the last staging secret. */
+ if(nsd->cookie_count < 2)
+ return;
+ explicit_bzero( nsd->cookie_secrets[nsd->cookie_count - 1].cookie_secret
+ , NSD_COOKIE_SECRET_SIZE);
+ nsd->cookie_count -= 1;
+}
struct rr;
struct buffer;
struct region;
+struct nsd;
#ifdef HAVE_SYSLOG_H
# include <syslog.h>
int set_cpu_affinity(cpuset_t *set);
#endif
+/* 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. */
+void add_cookie_secret(struct nsd* nsd, uint8_t* secret);
+/* Makes the staging cookie secret active and the active secret staging. */
+void activate_cookie_secret(struct nsd* nsd);
+/* Drop a cookie secret. Drops the staging secret. An active secret will not
+ * be dropped. */
+void drop_cookie_secret(struct nsd* nsd);
#endif /* _UTIL_H_ */
#include "xfrd.h"
#include "xfrd-disk.h"
#include "util.h"
+#ifdef HAVE_TLS_1_3
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#endif
+
+#ifdef HAVE_TLS_1_3
+void log_crypto_err(const char* str); /* in server.c */
+
+static SSL_CTX*
+create_ssl_context()
+{
+ SSL_CTX *ctx;
+ ctx = SSL_CTX_new(TLS_client_method());
+ if (!ctx) {
+ log_msg(LOG_ERR, "xfrd tls: Unable to create SSL ctxt");
+ }
+ else if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
+ SSL_CTX_free(ctx);
+ log_msg(LOG_ERR, "xfrd tls: Unable to set default SSL verify paths");
+ return NULL;
+ }
+ /* Only trust 1.3 as per the specification */
+ else if (!SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION)) {
+ SSL_CTX_free(ctx);
+ log_msg(LOG_ERR, "xfrd tls: Unable to set minimum TLS version 1.3");
+ return NULL;
+ }
+ return ctx;
+}
+
+static int
+tls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
+{
+ int err = X509_STORE_CTX_get_error(ctx);
+ int depth = X509_STORE_CTX_get_error_depth(ctx);
+
+ // report the specific cert error here - will need custom verify code if
+ // SPKI pins are supported
+ if (!preverify_ok)
+ log_msg(LOG_ERR, "xfrd tls: TLS verify failed - (%d) depth: %d error: %s",
+ err,
+ depth,
+ X509_verify_cert_error_string(err));
+ return preverify_ok;
+}
+
+static int
+setup_ssl(struct xfrd_tcp_pipeline* tp, struct xfrd_tcp_set* tcp_set,
+ const char* auth_domain_name)
+{
+ if (!tcp_set->ssl_ctx) {
+ log_msg(LOG_ERR, "xfrd tls: No TLS CTX, cannot set up XFR-over-TLS");
+ return 0;
+ }
+ DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: setting up TLS for tls_auth domain name %s",
+ auth_domain_name));
+ tp->ssl = SSL_new((SSL_CTX*)tcp_set->ssl_ctx);
+ if(!tp->ssl) {
+ log_msg(LOG_ERR, "xfrd tls: Unable to create TLS object");
+ return 0;
+ }
+ SSL_set_connect_state(tp->ssl);
+ (void)SSL_set_mode(tp->ssl, SSL_MODE_AUTO_RETRY);
+ if(!SSL_set_fd(tp->ssl, tp->tcp_w->fd)) {
+ log_msg(LOG_ERR, "xfrd tls: Unable to set TLS fd");
+ SSL_free(tp->ssl);
+ tp->ssl = NULL;
+ return 0;
+ }
+
+ SSL_set_verify(tp->ssl, SSL_VERIFY_PEER, tls_verify_callback);
+ if(!SSL_set1_host(tp->ssl, auth_domain_name)) {
+ log_msg(LOG_ERR, "xfrd tls: TLS setting of hostname %s failed",
+ auth_domain_name);
+ SSL_free(tp->ssl);
+ tp->ssl = NULL;
+ return 0;
+ }
+ return 1;
+}
+
+static int
+ssl_handshake(struct xfrd_tcp_pipeline* tp)
+{
+ int ret;
+
+ ERR_clear_error();
+ ret = SSL_do_handshake(tp->ssl);
+ if(ret == 1) {
+ DEBUG(DEBUG_XFRD, 1, (LOG_INFO, "xfrd: TLS handshake successful"));
+ tp->handshake_done = 1;
+ return 1;
+ }
+ tp->handshake_want = SSL_get_error(tp->ssl, ret);
+ if(tp->handshake_want == SSL_ERROR_WANT_READ
+ || tp->handshake_want == SSL_ERROR_WANT_WRITE)
+ return 1;
+
+ return 0;
+}
+#endif
/* sort tcppipe, first on IP address, for an IPaddresss, sort on num_unused */
static int
int r;
if(x == y)
return 0;
- if(y->ip_len != x->ip_len)
+ if(y->key.ip_len != x->key.ip_len)
/* subtraction works because nonnegative and small numbers */
- return (int)y->ip_len - (int)x->ip_len;
- r = memcmp(&x->ip, &y->ip, x->ip_len);
+ return (int)y->key.ip_len - (int)x->key.ip_len;
+ r = memcmp(&x->key.ip, &y->key.ip, x->key.ip_len);
if(r != 0)
return r;
/* sort that num_unused is sorted ascending, */
- if(x->num_unused != y->num_unused) {
- return (x->num_unused < y->num_unused) ? -1 : 1;
+ if(x->key.num_unused != y->key.num_unused) {
+ return (x->key.num_unused < y->key.num_unused) ? -1 : 1;
}
/* different pipelines are different still, even with same numunused*/
return (uintptr_t)x < (uintptr_t)y ? -1 : 1;
}
-struct xfrd_tcp_set* xfrd_tcp_set_create(struct region* region)
+struct xfrd_tcp_set* xfrd_tcp_set_create(struct region* region, const char *tls_cert_bundle)
{
int i;
struct xfrd_tcp_set* tcp_set = region_alloc(region,
tcp_set->tcp_count = 0;
tcp_set->tcp_waiting_first = 0;
tcp_set->tcp_waiting_last = 0;
+#ifdef HAVE_TLS_1_3
+ /* Set up SSL context */
+ tcp_set->ssl_ctx = create_ssl_context();
+ if (tcp_set->ssl_ctx == NULL)
+ log_msg(LOG_ERR, "xfrd: XFR-over-TLS not available");
+
+ else if (tls_cert_bundle && tls_cert_bundle[0] && SSL_CTX_load_verify_locations(
+ tcp_set->ssl_ctx, tls_cert_bundle, NULL) != 1) {
+ log_msg(LOG_ERR, "xfrd tls: Unable to set the certificate bundle file %s",
+ tls_cert_bundle);
+ }
+#else
+ log_msg(LOG_INFO, "xfrd: No TLS 1.3 support - XFR-over-TLS not available");
+#endif
for(i=0; i<XFRD_MAX_TCP; i++)
tcp_set->tcp_state[i] = xfrd_tcp_pipeline_create(region);
tcp_set->pipetree = rbtree_create(region, &xfrd_pipe_cmp);
int i;
struct xfrd_tcp_pipeline* tp = (struct xfrd_tcp_pipeline*)
region_alloc_zero(region, sizeof(*tp));
- tp->num_unused = ID_PIPE_NUM;
+ tp->key.num_unused = ID_PIPE_NUM;
assert(sizeof(tp->unused)/sizeof(tp->unused[0]) == ID_PIPE_NUM);
for(i=0; i<ID_PIPE_NUM; i++)
tp->unused[i] = (uint16_t)i;
xfrd_acl_sockaddr_to(acl_options_type* acl, struct sockaddr_in *to)
#endif /* INET6 */
{
+#ifdef HAVE_TLS_1_3
+ unsigned int port = acl->port?acl->port:(acl->tls_auth_options?
+ (unsigned)atoi(TLS_PORT):(unsigned)atoi(TCP_PORT));
+#else
unsigned int port = acl->port?acl->port:(unsigned)atoi(TCP_PORT);
+#endif
#ifdef INET6
return xfrd_acl_sockaddr(acl, port, to);
#else
/* smaller buf than a full pipeline with 64kb ID array, only need
* the front part with the key info, this front part contains the
* members that the compare function uses. */
- enum { keysize = sizeof(struct xfrd_tcp_pipeline) -
- ID_PIPE_NUM*(sizeof(struct xfrd_zone*) + sizeof(uint16_t)) };
- /* void* type for alignment of the struct,
- * divide the keysize by ptr-size and then add one to round up */
- void* buf[ (keysize / sizeof(void*)) + 1 ];
- struct xfrd_tcp_pipeline* key = (struct xfrd_tcp_pipeline*)buf;
+ struct xfrd_tcp_pipeline_key k, *key=&k;
key->node.key = key;
key->ip_len = xfrd_acl_sockaddr_to(zone->master, &key->ip);
key->num_unused = ID_PIPE_NUM;
return NULL;
r = (struct xfrd_tcp_pipeline*)sme->key;
/* <= key pointed at, is the master correct ? */
- if(r->ip_len != key->ip_len)
+ if(r->key.ip_len != key->ip_len)
return NULL;
- if(memcmp(&r->ip, &key->ip, key->ip_len) != 0)
+ if(memcmp(&r->key.ip, &key->ip, key->ip_len) != 0)
return NULL;
/* correct master, is there a slot free for this transfer? */
- if(r->num_unused == 0)
+ if(r->key.num_unused == 0)
return NULL;
return r;
}
static void
tcp_pipe_id_remove(struct xfrd_tcp_pipeline* tp, xfrd_zone_type* zone)
{
- assert(tp->num_unused < ID_PIPE_NUM && tp->num_unused >= 0);
+ assert(tp->key.num_unused < ID_PIPE_NUM && tp->key.num_unused >= 0);
assert(tp->id[zone->query_id] == zone);
tp->id[zone->query_id] = NULL;
- tp->unused[tp->num_unused] = zone->query_id;
+ tp->unused[tp->key.num_unused] = zone->query_id;
/* must remove and re-add for sort order in tree */
- (void)rbtree_delete(xfrd->tcp_set->pipetree, &tp->node);
- tp->num_unused++;
- (void)rbtree_insert(xfrd->tcp_set->pipetree, &tp->node);
+ (void)rbtree_delete(xfrd->tcp_set->pipetree, &tp->key.node);
+ tp->key.num_unused++;
+ (void)rbtree_insert(xfrd->tcp_set->pipetree, &tp->key.node);
}
/* stop the tcp pipe (and all its zones need to retry) */
xfrd_tcp_pipe_stop(struct xfrd_tcp_pipeline* tp)
{
int i, conn = -1;
- assert(tp->num_unused < ID_PIPE_NUM); /* at least one 'in-use' */
- assert(ID_PIPE_NUM - tp->num_unused > tp->num_skip); /* at least one 'nonskip' */
+ assert(tp->key.num_unused < ID_PIPE_NUM); /* at least one 'in-use' */
+ assert(ID_PIPE_NUM - tp->key.num_unused > tp->key.num_skip); /* at least one 'nonskip' */
/* need to retry for all the zones connected to it */
/* these could use different lists and go to a different nextmaster*/
for(i=0; i<ID_PIPE_NUM; i++) {
event_del(&tp->handler);
memset(&tp->handler, 0, sizeof(tp->handler));
event_set(&tp->handler, fd, EV_PERSIST|EV_TIMEOUT|EV_READ|
- (tp->tcp_send_first?EV_WRITE:0), xfrd_handle_tcp_pipe, tp);
+#ifdef HAVE_TLS_1_3
+ ( tp->ssl
+ ? ( tp->handshake_done ? ( tp->tcp_send_first ? EV_WRITE : 0 )
+ : tp->handshake_want == SSL_ERROR_WANT_WRITE ? EV_WRITE : 0 )
+ : tp->tcp_send_first ? EV_WRITE : 0 ),
+#else
+ ( tp->tcp_send_first ? EV_WRITE : 0 ),
+#endif
+ xfrd_handle_tcp_pipe, tp);
if(event_base_set(xfrd->event_base, &tp->handler) != 0)
log_msg(LOG_ERR, "xfrd tcp: event_base_set failed");
if(event_add(&tp->handler, &tv) != 0)
{
/* assign the ID */
int idx;
- assert(tp->num_unused > 0);
+ assert(tp->key.num_unused > 0);
/* we pick a random ID, even though it is TCP anyway */
- idx = random_generate(tp->num_unused);
+ idx = random_generate(tp->key.num_unused);
zone->query_id = tp->unused[idx];
- tp->unused[idx] = tp->unused[tp->num_unused-1];
+ tp->unused[idx] = tp->unused[tp->key.num_unused-1];
tp->id[zone->query_id] = zone;
/* decrement unused counter, and fixup tree */
- (void)rbtree_delete(set->pipetree, &tp->node);
- tp->num_unused--;
- (void)rbtree_insert(set->pipetree, &tp->node);
+ (void)rbtree_delete(set->pipetree, &tp->key.node);
+ tp->key.num_unused--;
+ (void)rbtree_insert(set->pipetree, &tp->key.node);
/* add to sendlist, at end */
zone->tcp_send_next = NULL;
return;
}
/* ip and ip_len set by tcp_open */
- tp->node.key = tp;
- tp->num_unused = ID_PIPE_NUM;
- tp->num_skip = 0;
+ tp->key.node.key = tp;
+ tp->key.num_unused = ID_PIPE_NUM;
+ tp->key.num_skip = 0;
tp->tcp_send_first = NULL;
tp->tcp_send_last = NULL;
memset(tp->id, 0, sizeof(tp->id));
}
/* insert into tree */
- (void)rbtree_insert(set->pipetree, &tp->node);
+ (void)rbtree_insert(set->pipetree, &tp->key.node);
xfrd_deactivate_zone(zone);
xfrd_unset_timer(zone);
pipeline_setup_new_zone(set, tp, zone);
#endif
}
- tp->ip_len = xfrd_acl_sockaddr_to(zone->master, &tp->ip);
+ tp->key.ip_len = xfrd_acl_sockaddr_to(zone->master, &tp->key.ip);
/* bind it */
if (!xfrd_bind_local_interface(fd, zone->zone_options->pattern->
return 0;
}
- conn = connect(fd, (struct sockaddr*)&tp->ip, tp->ip_len);
+ conn = connect(fd, (struct sockaddr*)&tp->key.ip, tp->key.ip_len);
if (conn == -1 && errno != EINPROGRESS) {
log_msg(LOG_ERR, "xfrd: connect %s failed: %s",
zone->master->ip_address_spec, strerror(errno));
tp->tcp_r->fd = fd;
tp->tcp_w->fd = fd;
+ /* Check if an tls_auth name is configured which means we should try to
+ establish an SSL connection */
+ if (zone->master->tls_auth_options &&
+ zone->master->tls_auth_options->auth_domain_name) {
+#ifdef HAVE_TLS_1_3
+ if (!setup_ssl(tp, set, zone->master->tls_auth_options->auth_domain_name)) {
+ log_msg(LOG_ERR, "xfrd: Cannot setup TLS on pipeline for %s to %s",
+ zone->apex_str, zone->master->ip_address_spec);
+ close(fd);
+ xfrd_set_refresh_now(zone);
+ return 0;
+ }
+ tp->handshake_done = 0;
+ if(!ssl_handshake(tp)) {
+ if(tp->handshake_want == SSL_ERROR_SYSCALL) {
+ log_msg(LOG_ERR, "xfrd: TLS handshake failed "
+ "for %s to %s: %s", zone->apex_str,
+ zone->master->ip_address_spec,
+ strerror(errno));
+
+ } else if(tp->handshake_want == SSL_ERROR_SSL) {
+ char errmsg[1024];
+ snprintf(errmsg, sizeof(errmsg), "xfrd: "
+ "TLS handshake failed for %s to %s",
+ zone->apex_str,
+ zone->master->ip_address_spec);
+ log_crypto_err(errmsg);
+ } else {
+ log_msg(LOG_ERR, "xfrd: TLS handshake failed "
+ "for %s to %s with %d", zone->apex_str,
+ zone->master->ip_address_spec,
+ tp->handshake_want);
+ }
+ close(fd);
+ xfrd_set_refresh_now(zone);
+ return 0;
+ }
+#else
+ log_msg(LOG_ERR, "xfrd: TLS 1.3 is not available, XFR-over-TLS is "
+ "not supported for %s to %s",
+ zone->apex_str, zone->master->ip_address_spec);
+ close(fd);
+ xfrd_set_refresh_now(zone);
+ return 0;
+#endif
+ }
+
/* set the tcp pipe event */
if(tp->handler_added)
event_del(&tp->handler);
memset(&tp->handler, 0, sizeof(tp->handler));
- event_set(&tp->handler, fd, EV_PERSIST|EV_TIMEOUT|EV_READ|EV_WRITE,
- xfrd_handle_tcp_pipe, tp);
+ event_set(&tp->handler, fd, EV_PERSIST|EV_TIMEOUT|EV_READ|
+#ifdef HAVE_TLS_1_3
+ ( !tp->ssl
+ || tp->handshake_done
+ || tp->handshake_want == SSL_ERROR_WANT_WRITE ? EV_WRITE : 0),
+#else
+ EV_WRITE,
+#endif
+ xfrd_handle_tcp_pipe, tp);
if(event_base_set(xfrd->event_base, &tp->handler) != 0)
log_msg(LOG_ERR, "xfrd tcp: event_base_set failed");
tv.tv_sec = set->tcp_timeout;
xfrd_setup_packet(tcp->packet, TYPE_IXFR, CLASS_IN, zone->apex,
zone->query_id);
zone->query_type = TYPE_IXFR;
- NSCOUNT_SET(tcp->packet, 1);
+ NSCOUNT_SET(tcp->packet, 1);
xfrd_write_soa_buffer(tcp->packet, zone->apex, &zone->soa_disk);
}
/* old transfer needs to be removed still? */
buffer_clear(tcp->packet);
}
+#ifdef HAVE_TLS_1_3
+static int
+conn_write_ssl(struct xfrd_tcp* tcp, SSL* ssl)
+{
+ int request_length;
+ ssize_t sent;
+
+ if(tcp->total_bytes < sizeof(tcp->msglen)) {
+ uint16_t sendlen = htons(tcp->msglen);
+ // send
+ request_length = sizeof(tcp->msglen) - tcp->total_bytes;
+ ERR_clear_error();
+ sent = SSL_write(ssl, (const char*)&sendlen + tcp->total_bytes,
+ request_length);
+ switch(SSL_get_error(ssl,sent)) {
+ case SSL_ERROR_NONE:
+ break;
+ default:
+ log_msg(LOG_ERR, "xfrd: generic write problem with tls");
+ }
+
+ if(sent == -1) {
+ if(errno == EAGAIN || errno == EINTR) {
+ /* write would block, try later */
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+
+ tcp->total_bytes += sent;
+ if(sent > (ssize_t)sizeof(tcp->msglen))
+ buffer_skip(tcp->packet, sent-sizeof(tcp->msglen));
+ if(tcp->total_bytes < sizeof(tcp->msglen)) {
+ /* incomplete write, resume later */
+ return 0;
+ }
+ assert(tcp->total_bytes >= sizeof(tcp->msglen));
+ }
+
+ assert(tcp->total_bytes < tcp->msglen + sizeof(tcp->msglen));
+
+ request_length = buffer_remaining(tcp->packet);
+ ERR_clear_error();
+ sent = SSL_write(ssl, buffer_current(tcp->packet), request_length);
+ switch(SSL_get_error(ssl,sent)) {
+ case SSL_ERROR_NONE:
+ break;
+ default:
+ log_msg(LOG_ERR, "xfrd: generic write problem with tls");
+ }
+ if(sent == -1) {
+ if(errno == EAGAIN || errno == EINTR) {
+ /* write would block, try later */
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+
+ buffer_skip(tcp->packet, sent);
+ tcp->total_bytes += sent;
+
+ if(tcp->total_bytes < tcp->msglen + sizeof(tcp->msglen)) {
+ /* more to write when socket becomes writable again */
+ return 0;
+ }
+
+ assert(tcp->total_bytes == tcp->msglen + sizeof(tcp->msglen));
+ return 1;
+}
+#endif
+
int conn_write(struct xfrd_tcp* tcp)
{
ssize_t sent;
return;
}
}
- ret = conn_write(tcp);
+#ifdef HAVE_TLS_1_3
+ if (tp->ssl) {
+ if(tp->handshake_done) {
+ ret = conn_write_ssl(tcp, tp->ssl);
+
+ } else if(ssl_handshake(tp)) {
+ tcp_pipe_reset_timeout(tp); /* reschedule */
+ return;
+
+ } else {
+ if(tp->handshake_want == SSL_ERROR_SYSCALL) {
+ log_msg(LOG_ERR, "xfrd: TLS handshake failed: %s",
+ strerror(errno));
+
+ } else if(tp->handshake_want == SSL_ERROR_SSL) {
+ log_crypto_err("xfrd: TLS handshake failed");
+ } else {
+ log_msg(LOG_ERR, "xfrd: TLS handshake failed "
+ "with value: %d", tp->handshake_want);
+ }
+ xfrd_tcp_pipe_stop(tp);
+ return;
+ }
+ } else
+#endif
+ ret = conn_write(tcp);
if(ret == -1) {
log_msg(LOG_ERR, "xfrd: failed writing tcp %s", strerror(errno));
xfrd_tcp_pipe_stop(tp);
/* setup to write for this zone */
xfrd_tcp_setup_write_packet(tp, tp->tcp_send_first);
/* attempt to write for this zone (if success, continue loop)*/
- ret = conn_write(tcp);
+#ifdef HAVE_TLS_1_3
+ if (tp->ssl)
+ ret = conn_write_ssl(tcp, tp->ssl);
+ else
+#endif
+ ret = conn_write(tcp);
if(ret == -1) {
log_msg(LOG_ERR, "xfrd: failed writing tcp %s", strerror(errno));
xfrd_tcp_pipe_stop(tp);
tcp_pipe_reset_timeout(tp);
}
+#ifdef HAVE_TLS_1_3
+static int
+conn_read_ssl(struct xfrd_tcp* tcp, SSL* ssl)
+{
+ ssize_t received;
+ /* receive leading packet length bytes */
+ if(tcp->total_bytes < sizeof(tcp->msglen)) {
+ ERR_clear_error();
+ received = SSL_read(ssl,
+ (char*) &tcp->msglen + tcp->total_bytes,
+ sizeof(tcp->msglen) - tcp->total_bytes);
+ if (received <= 0) {
+ int err = SSL_get_error(ssl, received);
+ if(err == SSL_ERROR_WANT_READ && errno == EAGAIN) {
+ return 0;
+ }
+ if(err == SSL_ERROR_ZERO_RETURN) {
+ /* EOF */
+ return 0;
+ }
+ log_msg(LOG_ERR, "ssl_read returned error %d with received %zd", err, received);
+ }
+ if(received == -1) {
+ if(errno == EAGAIN || errno == EINTR) {
+ /* read would block, try later */
+ return 0;
+ } else {
+#ifdef ECONNRESET
+ if (verbosity >= 2 || errno != ECONNRESET)
+#endif /* ECONNRESET */
+ log_msg(LOG_ERR, "tls read sz: %s", strerror(errno));
+ return -1;
+ }
+ } else if(received == 0) {
+ /* EOF */
+ return -1;
+ }
+ tcp->total_bytes += received;
+ if(tcp->total_bytes < sizeof(tcp->msglen)) {
+ /* not complete yet, try later */
+ return 0;
+ }
+
+ assert(tcp->total_bytes == sizeof(tcp->msglen));
+ tcp->msglen = ntohs(tcp->msglen);
+
+ if(tcp->msglen == 0) {
+ buffer_set_limit(tcp->packet, tcp->msglen);
+ return 1;
+ }
+ if(tcp->msglen > buffer_capacity(tcp->packet)) {
+ log_msg(LOG_ERR, "buffer too small, dropping connection");
+ return 0;
+ }
+ buffer_set_limit(tcp->packet, tcp->msglen);
+ }
+
+ assert(buffer_remaining(tcp->packet) > 0);
+ ERR_clear_error();
+
+ received = SSL_read(ssl, buffer_current(tcp->packet),
+ buffer_remaining(tcp->packet));
+
+ if (received <= 0) {
+ int err = SSL_get_error(ssl, received);
+ if(err == SSL_ERROR_ZERO_RETURN) {
+ /* EOF */
+ return 0;
+ }
+ log_msg(LOG_ERR, "ssl_read returned error %d with received %zd", err, received);
+ }
+ if(received == -1) {
+ if(errno == EAGAIN || errno == EINTR) {
+ /* read would block, try later */
+ return 0;
+ } else {
+#ifdef ECONNRESET
+ if (verbosity >= 2 || errno != ECONNRESET)
+#endif /* ECONNRESET */
+ log_msg(LOG_ERR, "tcp read %s", strerror(errno));
+ return -1;
+ }
+ } else if(received == 0) {
+ /* EOF */
+ return -1;
+ }
+
+ tcp->total_bytes += received;
+ buffer_skip(tcp->packet, received);
+
+ if(buffer_remaining(tcp->packet) > 0) {
+ /* not complete yet, wait for more */
+ return 0;
+ }
+
+ /* completed */
+ assert(buffer_position(tcp->packet) == tcp->msglen);
+ return 1;
+}
+#endif
+
int
conn_read(struct xfrd_tcp* tcp)
{
struct xfrd_tcp* tcp = tp->tcp_r;
int ret;
enum xfrd_packet_result pkt_result;
+#ifdef HAVE_TLS_1_3
+ if(tp->ssl) {
+ if(tp->handshake_done) {
+ ret = conn_read_ssl(tcp, tp->ssl);
- ret = conn_read(tcp);
+ } else if(ssl_handshake(tp)) {
+ tcp_pipe_reset_timeout(tp); /* reschedule */
+ return;
+
+ } else {
+ if(tp->handshake_want == SSL_ERROR_SYSCALL) {
+ log_msg(LOG_ERR, "xfrd: TLS handshake failed: %s",
+ strerror(errno));
+
+ } else if(tp->handshake_want == SSL_ERROR_SSL) {
+ log_crypto_err("xfrd: TLS handshake failed");
+ } else {
+ log_msg(LOG_ERR, "xfrd: TLS handshake failed "
+ "with value: %d", tp->handshake_want);
+ }
+ xfrd_tcp_pipe_stop(tp);
+ return;
+ }
+ } else
+#endif
+ ret = conn_read(tcp);
if(ret == -1) {
+ log_msg(LOG_ERR, "xfrd: failed writing tcp %s", strerror(errno));
xfrd_tcp_pipe_stop(tp);
return;
}
case xfrd_packet_newlease:
/* set to skip if more packets with this ID */
tp->id[zone->query_id] = TCP_NULL_SKIP;
- tp->num_skip++;
+ tp->key.num_skip++;
/* fall through to remove zone from tp */
/* fallthrough */
case xfrd_packet_transfer:
default:
/* set to skip if more packets with this ID */
tp->id[zone->query_id] = TCP_NULL_SKIP;
- tp->num_skip++;
+ tp->key.num_skip++;
xfrd_tcp_release(xfrd->tcp_set, zone);
/* query next server */
xfrd_make_request(zone);
if(tp->id[zone->query_id] != TCP_NULL_SKIP)
tcp_pipe_id_remove(tp, zone);
DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: released tcp pipe now %d unused",
- tp->num_unused));
+ tp->key.num_unused));
/* if pipe was full, but no more, then see if waiting element is
* for the same master, and can fill the unused ID */
- if(tp->num_unused == 1 && set->tcp_waiting_first) {
+ if(tp->key.num_unused == 1 && set->tcp_waiting_first) {
#ifdef INET6
struct sockaddr_storage to;
#else
#endif
socklen_t to_len = xfrd_acl_sockaddr_to(
set->tcp_waiting_first->master, &to);
- if(to_len == tp->ip_len && memcmp(&to, &tp->ip, to_len) == 0) {
+ if(to_len == tp->key.ip_len && memcmp(&to, &tp->key.ip, to_len) == 0) {
/* use this connection for the waiting zone */
zone = set->tcp_waiting_first;
assert(zone->tcp_conn == -1);
}
/* if all unused, or only skipped leftover, close the pipeline */
- if(tp->num_unused >= ID_PIPE_NUM || tp->num_skip >= ID_PIPE_NUM - tp->num_unused)
+ if(tp->key.num_unused >= ID_PIPE_NUM || tp->key.num_skip >= ID_PIPE_NUM - tp->key.num_unused)
xfrd_tcp_pipe_release(set, tp, conn);
}
event_del(&tp->handler);
tp->handler_added = 0;
+#ifdef HAVE_TLS_1_3
+ /* close SSL */
+ if (tp->ssl) {
+ DEBUG(DEBUG_XFRD, 1, (LOG_INFO, "xfrd: Shutting down TLS"));
+ SSL_shutdown(tp->ssl);
+ SSL_free(tp->ssl);
+ tp->ssl = NULL;
+ }
+#endif
+
/* fd in tcp_r and tcp_w is the same, close once */
if(tp->tcp_r->fd != -1)
close(tp->tcp_r->fd);
tp->tcp_w->fd = -1;
/* remove from pipetree */
- (void)rbtree_delete(xfrd->tcp_set->pipetree, &tp->node);
+ (void)rbtree_delete(xfrd->tcp_set->pipetree, &tp->key.node);
/* a waiting zone can use the free tcp slot (to another server) */
/* if that zone fails to set-up or connect, we try to start the next
}
/* re-init this tcppipe */
/* ip and ip_len set by tcp_open */
- tp->node.key = tp;
- tp->num_unused = ID_PIPE_NUM;
- tp->num_skip = 0;
+ tp->key.node.key = tp;
+ tp->key.num_unused = ID_PIPE_NUM;
+ tp->key.num_skip = 0;
tp->tcp_send_first = NULL;
tp->tcp_send_last = NULL;
memset(tp->id, 0, sizeof(tp->id));
}
/* insert into tree */
- (void)rbtree_insert(set->pipetree, &tp->node);
+ (void)rbtree_insert(set->pipetree, &tp->key.node);
/* setup write */
xfrd_unset_timer(zone);
pipeline_setup_new_zone(set, tp, zone);
#define XFRD_TCP_H
#include "xfrd.h"
+#ifdef HAVE_TLS_1_3
+#include <openssl/ssl.h>
+#endif
+
struct buffer;
struct xfrd_zone;
int tcp_timeout;
/* rbtree with pipelines sorted by master */
rbtree_type* pipetree;
+#ifdef HAVE_TLS_1_3
+ /* XoT: SSL context */
+ SSL_CTX* ssl_ctx;
+#endif
/* double linked list of zones waiting for a TCP connection */
struct xfrd_zone *tcp_waiting_first, *tcp_waiting_last;
};
#define ID_PIPE_NUM 65536
/**
- * Structure to keep track of a pipelined set of queries on
- * an open tcp connection. The queries may be answered with
- * interleaved answer packets, the ID number disambiguates.
- * Sorted by the master IP address so you can use lookup with
- * smaller-or-equal to find the tcp connection most suitable.
+ * The tcp pipeline key structure. By ip_len, ip, num_unused and unique by
+ * pointer value.
*/
-struct xfrd_tcp_pipeline {
+struct xfrd_tcp_pipeline_key {
/* the rbtree node, sorted by IP and nr of unused queries */
rbnode_type node;
/* destination IP address */
int num_unused;
/* number of skip-set IDs (these are 'in-use') */
int num_skip;
+};
+
+/**
+ * Structure to keep track of a pipelined set of queries on
+ * an open tcp connection. The queries may be answered with
+ * interleaved answer packets, the ID number disambiguates.
+ * Sorted by the master IP address so you can use lookup with
+ * smaller-or-equal to find the tcp connection most suitable.
+ */
+struct xfrd_tcp_pipeline {
+ /* the key information for the tcp pipeline, in its own
+ * struct so it can be referenced on its own for comparison funcs */
+ struct xfrd_tcp_pipeline_key key;
int handler_added;
/* the event handler for this pipe (it'll disambiguate by ID) */
struct xfrd_tcp* tcp_w;
/* once a byte has been written, handshake complete */
int connection_established;
+#ifdef HAVE_TLS_1_3
+ /* XoT: SSL object */
+ SSL *ssl;
+ /* XoT: if SSL handshake is not done, handshake_want indicates the
+ * last error. This may be SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE
+ * when the handshake is still in progress.
+ */
+ int handshake_want;
+ /* XoT: 1 if the SSL handshake has succeeded, 0 otherwise */
+ int handshake_done;
+#endif
/* list of queries that want to send, first to get write event,
* if NULL, no write event interest */
};
/* create set of tcp connections */
-struct xfrd_tcp_set* xfrd_tcp_set_create(struct region* region);
+struct xfrd_tcp_set* xfrd_tcp_set_create(struct region* region, const char *tls_cert_bundle);
/* init tcp state */
struct xfrd_tcp* xfrd_tcp_create(struct region* region, size_t bufsize);
daemon_remote_attach(xfrd->nsd->rc, xfrd);
#endif
- xfrd->tcp_set = xfrd_tcp_set_create(xfrd->region);
+ xfrd->tcp_set = xfrd_tcp_set_create(xfrd->region, nsd->options->tls_cert_bundle);
xfrd->tcp_set->tcp_timeout = nsd->tcp_timeout;
#if !defined(HAVE_ARC4RANDOM) && !defined(HAVE_GETRANDOM)
srandom((unsigned long) getpid() * (unsigned long) time(NULL));
void
xfrd_init_slave_zone(xfrd_state_type* xfrd, struct zone_options* zone_opt)
{
+ int num, num_xot;
xfrd_zone_type *xzone;
xzone = (xfrd_zone_type*)region_alloc(xfrd->region,
sizeof(xfrd_zone_type));
/* set refreshing anyway, if we have data it may be old */
xfrd_set_refresh_now(xzone);
+ /*Check all or none of acls use XoT*/
+ num = 0;
+ num_xot = 0;
+ for (; xzone->master != NULL; xzone->master = xzone->master->next, num++) {
+ if (xzone->master->tls_auth_options != NULL) num_xot++;
+ }
+ if (num_xot != 0 && num != num_xot)
+ log_msg(LOG_WARNING, "Some but not all request-xfrs for %s have XFR-over-TLS configured",
+ xzone->apex_str);
+
xzone->node.key = xzone->apex;
rbtree_insert(xfrd->zones, (rbnode_type*)xzone);
}
#define ILNP_MAXDIGITS 4
#define ILNP_NUMGROUPS 4
+#define SVCB_MAX_COMMA_SEPARATED_VALUES 1000
+
const dname_type *error_dname;
domain_type *error_domain;
return r;
}
+static uint16_t
+svcbparam_lookup_key(const char *key, size_t key_len)
+{
+ char buf[64];
+ char *endptr;
+ unsigned long int key_value;
+
+ if (key_len >= 4 && key_len <= 8 && !strncmp(key, "key", 3)) {
+ memcpy(buf, key + 3, key_len - 3);
+ buf[key_len - 3] = 0;
+ key_value = strtoul(buf, &endptr, 10);
+ if (endptr > buf /* digits seen */
+ && *endptr == 0 /* no non-digit chars after digits */
+ && key_value <= 65535) /* no overflow */
+ return key_value;
+
+ } else switch (key_len) {
+ case sizeof("mandatory")-1:
+ if (!strncmp(key, "mandatory", sizeof("mandatory")-1))
+ return SVCB_KEY_MANDATORY;
+ if (!strncmp(key, "echconfig", sizeof("echconfig")-1))
+ return SVCB_KEY_ECH; /* allow "echconfig as well as "ech" */
+ break;
+
+ case sizeof("alpn")-1:
+ if (!strncmp(key, "alpn", sizeof("alpn")-1))
+ return SVCB_KEY_ALPN;
+ if (!strncmp(key, "port", sizeof("port")-1))
+ return SVCB_KEY_PORT;
+ break;
+
+ case sizeof("no-default-alpn")-1:
+ if (!strncmp( key , "no-default-alpn"
+ , sizeof("no-default-alpn")-1))
+ return SVCB_KEY_NO_DEFAULT_ALPN;
+ break;
+
+ case sizeof("ipv4hint")-1:
+ if (!strncmp(key, "ipv4hint", sizeof("ipv4hint")-1))
+ return SVCB_KEY_IPV4HINT;
+ if (!strncmp(key, "ipv6hint", sizeof("ipv6hint")-1))
+ return SVCB_KEY_IPV6HINT;
+ break;
+ case sizeof("ech")-1:
+ if (!strncmp(key, "ech", sizeof("ech")-1))
+ return SVCB_KEY_ECH;
+ break;
+ default:
+ break;
+ }
+ if (key_len > sizeof(buf) - 1)
+ zc_error_prev_line("Unknown SvcParamKey");
+ else {
+ memcpy(buf, key, key_len);
+ buf[key_len] = 0;
+ zc_error_prev_line("Unknown SvcParamKey: %s", buf);
+ }
+ /* Although the returned value might be used by the caller,
+ * the parser has erred, so the zone will not be loaded.
+ */
+ return -1;
+}
+
+static uint16_t *
+zparser_conv_svcbparam_port_value(region_type *region, const char *val)
+{
+ unsigned long int port;
+ char *endptr;
+ uint16_t *r;
+
+ port = strtoul(val, &endptr, 10);
+ if (endptr > val /* digits seen */
+ && *endptr == 0 /* no non-digit chars after digits */
+ && port <= 65535) { /* no overflow */
+
+ r = alloc_rdata(region, 3 * sizeof(uint16_t));
+ r[1] = htons(SVCB_KEY_PORT);
+ r[2] = htons(sizeof(uint16_t));
+ r[3] = htons(port);
+ return r;
+ }
+ zc_error_prev_line("Could not parse port SvcParamValue: \"%s\"", val);
+ return NULL;
+}
+
+static uint16_t *
+zparser_conv_svcbparam_ipv4hint_value(region_type *region, const char *val)
+{
+ uint16_t *r;
+ int count;
+ char ip_str[INET_ADDRSTRLEN+1];
+ char *next_ip_str;
+ uint32_t *ip_wire_dst;
+ size_t i;
+
+ for (i = 0, count = 1; val[i]; i++) {
+ if (val[i] == ',')
+ count += 1;
+ if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) {
+ zc_error_prev_line("Too many IPV4 addresses in ipv4hint");
+ return NULL;
+ }
+ }
+
+ /* count == number of comma's in val + 1, so the actual number of IPv4
+ * addresses in val
+ */
+ r = alloc_rdata(region, 2 * sizeof(uint16_t) + IP4ADDRLEN * count);
+ r[1] = htons(SVCB_KEY_IPV4HINT);
+ r[2] = htons(IP4ADDRLEN * count);
+ ip_wire_dst = (void *)&r[3];
+
+ while (count) {
+ if (!(next_ip_str = strchr(val, ','))) {
+ if (inet_pton(AF_INET, val, ip_wire_dst) != 1)
+ break;
+
+ assert(count == 1);
+
+ } else if (next_ip_str - val >= (int)sizeof(ip_str))
+ break;
+
+ else {
+ memcpy(ip_str, val, next_ip_str - val);
+ ip_str[next_ip_str - val] = 0;
+ if (inet_pton(AF_INET, ip_str, ip_wire_dst) != 1) {
+ val = ip_str; /* to use in error reporting below */
+ break;
+ }
+
+ val = next_ip_str + 1;
+ }
+ ip_wire_dst++;
+ count--;
+ }
+ if (count)
+ zc_error_prev_line("Could not parse ipv4hint SvcParamValue: %s", val);
+
+ return r;
+}
+
+static uint16_t *
+zparser_conv_svcbparam_ipv6hint_value(region_type *region, const char *val)
+{
+ uint16_t *r;
+ int i, count;
+ char ip6_str[INET6_ADDRSTRLEN+1];
+ char *next_ip6_str;
+ uint8_t *ipv6_wire_dst;
+
+ for (i = 0, count = 1; val[i]; i++) {
+ if (val[i] == ',')
+ count += 1;
+ if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) {
+ zc_error_prev_line("Too many IPV6 addresses in ipv6hint");
+ return NULL;
+ }
+ }
+
+ /* count == number of comma's in val + 1
+ * so actually the number of IPv6 addresses in val
+ */
+ r = alloc_rdata(region, 2 * sizeof(uint16_t) + IP6ADDRLEN * count);
+ r[1] = htons(SVCB_KEY_IPV6HINT);
+ r[2] = htons(IP6ADDRLEN * count);
+ ipv6_wire_dst = (void *)&r[3];
+
+ while (count) {
+ if (!(next_ip6_str = strchr(val, ','))) {
+ if ((inet_pton(AF_INET6, val, ipv6_wire_dst) != 1))
+ break;
+
+ assert(count == 1);
+
+ } else if (next_ip6_str - val >= (int)sizeof(ip6_str))
+ break;
+
+ else {
+ memcpy(ip6_str, val, next_ip6_str - val);
+ ip6_str[next_ip6_str - val] = 0;
+ if (inet_pton(AF_INET6, ip6_str, ipv6_wire_dst) != 1) {
+ val = ip6_str; /* for error reporting below */
+ break;
+ }
+
+ val = next_ip6_str + 1; /* skip the comma */
+ }
+ ipv6_wire_dst += IP6ADDRLEN;
+ count--;
+ }
+ if (count)
+ zc_error_prev_line("Could not parse ipv6hint SvcParamValue: %s", val);
+
+ return r;
+}
+
+static int
+network_uint16_cmp(const void *a, const void *b)
+{
+ return ((int)read_uint16(a)) - ((int)read_uint16(b));
+}
+
+static uint16_t *
+zparser_conv_svcbparam_mandatory_value(region_type *region,
+ const char *val, size_t val_len)
+{
+ uint16_t *r;
+ size_t i, count;
+ char* next_key;
+ uint16_t* key_dst;
+
+ for (i = 0, count = 1; val[i]; i++) {
+ if (val[i] == ',')
+ count += 1;
+ if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) {
+ zc_error_prev_line("Too many keys in mandatory");
+ return NULL;
+ }
+ }
+
+ r = alloc_rdata(region, (2 + count) * sizeof(uint16_t));
+ r[1] = htons(SVCB_KEY_MANDATORY);
+ r[2] = htons(sizeof(uint16_t) * count);
+ key_dst = (void *)&r[3];
+
+ for(;;) {
+ if (!(next_key = strchr(val, ','))) {
+ *key_dst = htons(svcbparam_lookup_key(val, val_len));
+ break;
+ } else {
+ *key_dst = htons(svcbparam_lookup_key(val, next_key - val));
+ }
+
+ val_len -= next_key - val + 1;
+ val = next_key + 1; /* skip the comma */
+ key_dst += 1;
+ }
+
+ /* In draft-ietf-dnsop-svcb-https-04 Section 7:
+ *
+ * In wire format, the keys are represented by their numeric
+ * values in network byte order, concatenated in ascending order.
+ */
+ qsort((void *)&r[3], count, sizeof(uint16_t), network_uint16_cmp);
+
+ return r;
+}
+
+static uint16_t *
+zparser_conv_svcbparam_ech_value(region_type *region, const char *b64)
+{
+ uint8_t buffer[B64BUFSIZE];
+ uint16_t *r = NULL;
+ int wire_len;
+
+ if(strcmp(b64, "0") == 0) {
+ /* single 0 represents empty buffer */
+ return alloc_rdata(region, 0);
+ }
+ wire_len = __b64_pton(b64, buffer, B64BUFSIZE);
+ if (wire_len == -1) {
+ zc_error_prev_line("invalid base64 data in ech");
+ } else {
+ r = alloc_rdata(region, 2 * sizeof(uint16_t) + wire_len);
+ r[1] = htons(SVCB_KEY_ECH);
+ r[2] = htons(wire_len);
+ memcpy(&r[3], buffer, wire_len);
+ }
+
+ return r;
+}
+
+static const char* parse_alpn_next_unescaped_comma(const char *val)
+{
+ while (*val) {
+ /* Only return when the comma is not escaped*/
+ if (*val == '\\'){
+ ++val;
+ if (!*val)
+ break;
+ } else if (*val == ',')
+ return val;
+
+ val++;
+ }
+ return NULL;
+}
+
+static size_t
+parse_alpn_copy_unescaped(uint8_t *dst, const char *src, size_t len)
+{
+ uint8_t *orig_dst = dst;
+
+ while (len) {
+ if (*src == '\\') {
+ src++;
+ len--;
+ if (!len)
+ break;
+ }
+ *dst++ = *src++;
+ len--;
+ }
+ return (size_t)(dst - orig_dst);
+}
+
+static uint16_t *
+zparser_conv_svcbparam_alpn_value(region_type *region,
+ const char *val, size_t val_len)
+{
+ uint8_t unescaped_dst[65536];
+ uint8_t *dst = unescaped_dst;
+ const char *next_str;
+ size_t str_len;
+ size_t dst_len;
+ uint16_t *r = NULL;
+
+ if (val_len > sizeof(unescaped_dst)) {
+ zc_error_prev_line("invalid alpn");
+ return r;
+ }
+ while (val_len) {
+ size_t dst_len;
+
+ str_len = (next_str = parse_alpn_next_unescaped_comma(val))
+ ? (size_t)(next_str - val) : val_len;
+
+ if (str_len > 255) {
+ zc_error_prev_line("alpn strings need to be"
+ " smaller than 255 chars");
+ return r;
+ }
+ dst_len = parse_alpn_copy_unescaped(dst + 1, val, str_len);
+ *dst++ = dst_len;
+ dst += dst_len;
+
+ if (!next_str)
+ break;
+
+ /* skip the comma for the next iteration */
+ val_len -= next_str - val + 1;
+ val = next_str + 1;
+ }
+ dst_len = dst - unescaped_dst;
+ r = alloc_rdata(region, 2 * sizeof(uint16_t) + dst_len);
+ r[1] = htons(SVCB_KEY_ALPN);
+ r[2] = htons(dst_len);
+ memcpy(&r[3], unescaped_dst, dst_len);
+ return r;
+}
+
+static uint16_t *
+zparser_conv_svcbparam_key_value(region_type *region,
+ const char *key, size_t key_len, const char *val, size_t val_len)
+{
+ uint16_t svcparamkey = svcbparam_lookup_key(key, key_len);
+ uint16_t *r;
+
+ switch (svcparamkey) {
+ case SVCB_KEY_PORT:
+ return zparser_conv_svcbparam_port_value(region, val);
+ case SVCB_KEY_IPV4HINT:
+ return zparser_conv_svcbparam_ipv4hint_value(region, val);
+ case SVCB_KEY_IPV6HINT:
+ return zparser_conv_svcbparam_ipv6hint_value(region, val);
+ case SVCB_KEY_MANDATORY:
+ return zparser_conv_svcbparam_mandatory_value(region, val, val_len);
+ case SVCB_KEY_NO_DEFAULT_ALPN:
+ if(zone_is_slave(parser->current_zone->opts))
+ zc_warning_prev_line("no-default-alpn should not have a value");
+ else
+ zc_error_prev_line("no-default-alpn should not have a value");
+ break;
+ case SVCB_KEY_ECH:
+ return zparser_conv_svcbparam_ech_value(region, val);
+ case SVCB_KEY_ALPN:
+ return zparser_conv_svcbparam_alpn_value(region, val, val_len);
+ default:
+ break;
+ }
+ r = alloc_rdata(region, 2 * sizeof(uint16_t) + val_len);
+ r[1] = htons(svcparamkey);
+ r[2] = htons(val_len);
+ memcpy(r + 3, val, val_len);
+ return r;
+}
+
+uint16_t *
+zparser_conv_svcbparam(region_type *region, const char *key, size_t key_len
+ , const char *val, size_t val_len)
+{
+ const char *eq;
+ uint16_t *r;
+ uint16_t svcparamkey;
+
+ /* Form <key>="<value>" (or at least with quoted value) */
+ if (val && val_len) {
+ /* Does key end with '=' */
+ if (key_len && key[key_len - 1] == '=')
+ return zparser_conv_svcbparam_key_value(
+ region, key, key_len - 1, val, val_len);
+
+ zc_error_prev_line( "SvcParam syntax error in param: %s\"%s\""
+ , key, val);
+ }
+ assert(val == NULL);
+ if ((eq = memchr(key, '=', key_len))) {
+ size_t new_key_len = eq - key;
+
+ if (key_len - new_key_len - 1 > 0)
+ return zparser_conv_svcbparam_key_value(region,
+ key, new_key_len, eq+1, key_len - new_key_len - 1);
+ key_len = new_key_len;
+ }
+ /* Some SvcParamKeys require values */
+ svcparamkey = svcbparam_lookup_key(key, key_len);
+ switch (svcparamkey) {
+ case SVCB_KEY_MANDATORY:
+ case SVCB_KEY_ALPN:
+ case SVCB_KEY_PORT:
+ case SVCB_KEY_IPV4HINT:
+ case SVCB_KEY_IPV6HINT:
+ if(zone_is_slave(parser->current_zone->opts))
+ zc_warning_prev_line("value expected for SvcParam: %s", key);
+ else
+ zc_error_prev_line("value expected for SvcParam: %s", key);
+ break;
+ default:
+ break;
+ }
+ /* SvcParam is only a SvcParamKey */
+ r = alloc_rdata(region, 2 * sizeof(uint16_t));
+ r[1] = htons(svcparamkey);
+ r[2] = 0;
+ return r;
+}
+
/* Parse an int terminated in the specified range. */
static int
parse_int(const char *str,
}
}
+static int
+svcparam_key_cmp(const void *a, const void *b)
+{
+ return ((int)read_uint16(rdata_atom_data(*(rdata_atom_type *)a)))
+ - ((int)read_uint16(rdata_atom_data(*(rdata_atom_type *)b)));
+}
+
+void
+zadd_rdata_svcb_check_wireformat()
+{
+ size_t i;
+ uint8_t paramkeys[65536];
+ int prev_key = - 1;
+ int key = 0;
+ size_t size;
+ uint16_t *mandatory_values;
+
+ if (parser->current_rr.rdata_count <= 2) {
+ if (!parser->error_occurred)
+ zc_error_prev_line("invalid SVCB or HTTPS rdata");
+ return;
+ } else for (i = 2; i < parser->current_rr.rdata_count; i++) {
+ if (parser->current_rr.rdatas[i].data == NULL
+ || rdata_atom_data(parser->current_rr.rdatas[i]) == NULL
+ || rdata_atom_size(parser->current_rr.rdatas[i]) < 4) {
+ if (!parser->error_occurred)
+ zc_error_prev_line("invalid SVCB or HTTPS rdata");
+ return;
+ }
+ }
+ /* After this point, all rdatas do have data larger than 4 bytes.
+ * So we may assume a uint16_t SVCB key followed by uint16_t length
+ * in each rdata in the remainder of this function.
+ */
+ memset(paramkeys, 0, sizeof(paramkeys));
+ /*
+ * In draft-ietf-dnsop-svcb-https-04 Section 7:
+ * In wire format, the keys are represented by their numeric values in
+ * network byte order, concatenated in ascending order.
+ *
+ * svcparam_key_cmp assumes the rdatas to have a SVCB key, which is
+ * safe because we checked.
+ *
+ */
+ qsort( (void *)&parser->current_rr.rdatas[2]
+ , parser->current_rr.rdata_count - 2
+ , sizeof(rdata_atom_type)
+ , svcparam_key_cmp
+ );
+
+ for (i = 2; i < parser->current_rr.rdata_count; i++) {
+ assert(parser->current_rr.rdatas[i].data);
+ assert(rdata_atom_data(parser->current_rr.rdatas[i]));
+ assert(rdata_atom_size(parser->current_rr.rdatas[i]) >= sizeof(uint16_t));
+
+ key = read_uint16(rdata_atom_data(parser->current_rr.rdatas[i]));
+
+ /* In draft-ietf-dnsop-svcb-https-04 Section 7:
+ *
+ * Keys (...) MUST NOT appear more than once.
+ *
+ * If they key has already been seen, we have a duplicate
+ */
+ if (!paramkeys[key])
+ /* keep track of keys that are present */
+ paramkeys[key] = 1;
+
+ else if (key < SVCPARAMKEY_COUNT) {
+ if(zone_is_slave(parser->current_zone->opts))
+ zc_warning_prev_line(
+ "Duplicate key found: %s",
+ svcparamkey_strs[key]);
+ else {
+ zc_error_prev_line(
+ "Duplicate key found: %s",
+ svcparamkey_strs[key]);
+ }
+ } else if(zone_is_slave(parser->current_zone->opts))
+ zc_warning_prev_line(
+ "Duplicate key found: key%d", key);
+ else
+ zc_error_prev_line(
+ "Duplicate key found: key%d", key);
+ }
+ /* Checks when a mandatory key is present */
+ if (!paramkeys[SVCB_KEY_MANDATORY])
+ return;
+
+ size = rdata_atom_size(parser->current_rr.rdatas[2]);
+ assert(size >= 4);
+ mandatory_values = (void*)rdata_atom_data(parser->current_rr.rdatas[2]);
+ mandatory_values += 2; /* skip the key type and length */
+
+ if (size % 2)
+ zc_error_prev_line("mandatory rdata must be a multiple of shorts");
+
+ else for (i = 0; i < (size - 4)/2; i++) {
+ key = ntohs(mandatory_values[i]);
+
+ if (paramkeys[key])
+ ; /* pass */
+
+ else if (key < SVCPARAMKEY_COUNT) {
+ if(zone_is_slave(parser->current_zone->opts))
+ zc_warning_prev_line("mandatory SvcParamKey: %s is missing "
+ "the record", svcparamkey_strs[key]);
+ else
+ zc_error_prev_line("mandatory SvcParamKey: %s is missing "
+ "the record", svcparamkey_strs[key]);
+ } else {
+ if(zone_is_slave(parser->current_zone->opts))
+ zc_warning_prev_line("mandatory SvcParamKey: key%d is missing "
+ "the record", key);
+ else
+ zc_error_prev_line("mandatory SvcParamKey: key%d is missing "
+ "the record", key);
+ }
+
+ /* In draft-ietf-dnsop-svcb-https-04 Section 8
+ * automatically mandatory MUST NOT appear in its own value-list
+ */
+ if (key == SVCB_KEY_MANDATORY) {
+ if(zone_is_slave(parser->current_zone->opts))
+ zc_warning_prev_line("mandatory MUST not be included"
+ " as mandatory parameter");
+ else
+ zc_error_prev_line("mandatory MUST not be included"
+ " as mandatory parameter");
+ }
+ if (key == prev_key) {
+ if(zone_is_slave(parser->current_zone->opts))
+ zc_warning_prev_line("Keys inSvcParam mandatory "
+ "MUST NOT appear more than once.");
+ else
+ zc_error_prev_line("Keys in SvcParam mandatory "
+ "MUST NOT appear more than once.");
+ }
+ prev_key = key;
+ }
+}
+
void
zadd_rdata_domain(domain_type *domain)
{
zc_error_prev_line("maximum rdata length exceeds %d octets", MAX_RDLENGTH);
return 0;
}
+
+ /* We cannot print invalid owner names,
+ * so error on that before it is used in printing other errors.
+ */
+ if (rr->owner == error_domain
+ || domain_dname(rr->owner) == error_dname) {
+ zc_error_prev_line("invalid owner name");
+ return 0;
+ }
+
/* we have the zone already */
assert(zone);
if (rr->type == TYPE_SOA) {
uint16_t *zparser_conv_certificate_type(region_type *region,
const char *typestr);
uint16_t *zparser_conv_apl_rdata(region_type *region, char *str);
+uint16_t *zparser_conv_svcbparam(region_type *region,
+ const char *key, size_t key_len, const char *value, size_t value_len);
void parse_unknown_rdata(uint16_t type, uint16_t *wireformat);
void zadd_rdata_wireformat(uint16_t *data);
void zadd_rdata_txt_wireformat(uint16_t *data, int first);
void zadd_rdata_txt_clean_wireformat(void);
+void zadd_rdata_svcb_check_wireformat(void);
void zadd_rdata_domain(domain_type *domain);
void set_bitnsec(uint8_t bits[NSEC_WINDOW_COUNT][NSEC_WINDOW_BITS_SIZE],
%token <type> T_AXFR T_MAILB T_MAILA T_DS T_DLV T_SSHFP T_RRSIG T_NSEC T_DNSKEY
%token <type> T_SPF T_NSEC3 T_IPSECKEY T_DHCID T_NSEC3PARAM T_TLSA T_URI
%token <type> T_NID T_L32 T_L64 T_LP T_EUI48 T_EUI64 T_CAA T_CDS T_CDNSKEY
-%token <type> T_OPENPGPKEY T_CSYNC T_ZONEMD T_AVC T_SMIMEA
+%token <type> T_OPENPGPKEY T_CSYNC T_ZONEMD T_AVC T_SMIMEA T_SVCB T_HTTPS
/* other tokens */
%token DOLLAR_TTL DOLLAR_ORIGIN NL SP
%type <dname> rel_dname label
%type <data> wire_dname wire_abs_dname wire_rel_dname wire_label
%type <data> str concatenated_str_seq str_sp_seq str_dot_seq
-%type <data> unquoted_dotted_str dotted_str
+%type <data> unquoted_dotted_str dotted_str svcparam svcparams
%type <data> nxt_seq nsec_more
%type <unknown> rdata_unknown
| T_CSYNC sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); }
| T_ZONEMD sp rdata_zonemd
| T_ZONEMD sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); }
+ | T_SVCB sp rdata_svcb
+ | T_SVCB sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); }
+ | T_HTTPS sp rdata_svcb
+ | T_HTTPS sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); }
| T_URI sp rdata_uri
| T_URI sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); }
| T_UTYPE sp rdata_unknown { $$ = $1; parse_unknown_rdata($1, $3); }
}
;
+svcparam: dotted_str QSTR
+ {
+ zadd_rdata_wireformat(zparser_conv_svcbparam(
+ parser->region, $1.str, $1.len, $2.str, $2.len));
+ }
+ | dotted_str
+ {
+ zadd_rdata_wireformat(zparser_conv_svcbparam(
+ parser->region, $1.str, $1.len, NULL, 0));
+ }
+ ;
+svcparams: svcparam
+ | svcparams sp svcparam
+ ;
+/* draft-ietf-dnsop-svcb-https */
+rdata_svcb_base: str sp dname
+ {
+ /* SvcFieldPriority */
+ zadd_rdata_wireformat(zparser_conv_short(parser->region, $1.str));
+ /* SvcDomainName */
+ zadd_rdata_domain($3);
+ };
+rdata_svcb: rdata_svcb_base sp svcparams trail
+ {
+ zadd_rdata_svcb_check_wireformat();
+ }
+ | rdata_svcb_base trail
+ ;
+
rdata_unknown: URR sp str sp str_sp_seq trail
{
/* $2 is the number of octets, currently ignored */