nsd 4.1.26

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

nsd 4.1.26

Florian Obser-2
tests, OKs?

diff --git Makefile.in Makefile.in
index 16d193f766d..fbfc44be33b 100644
--- Makefile.in
+++ Makefile.in
@@ -29,6 +29,8 @@ nsdconfigfile = @nsd_conf_file@
 zonesdir = @zonesdir@
 chrootdir= @chrootdir@
 user = @user@
+DNSTAP_SRC=@DNSTAP_SRC@
+DNSTAP_OBJ=@DNSTAP_OBJ@
 
 # override $U variable which is used by autotools for deansification (for
 # K&R C compilers), but causes problems if $U is defined in the env).
@@ -47,6 +49,7 @@ INSTALL_DATA = $(INSTALL) -m 644
 
 YACC = @YACC@
 LEX = @LEX@
+PROTOC_C = @PROTOC_C@
 
 COMPILE = $(CC) $(CPPFLAGS) $(CFLAGS)
 LINK = $(CC) $(CFLAGS) $(LDFLAGS)
@@ -72,7 +75,7 @@ TARGETS=nsd nsd-checkconf nsd-checkzone nsd-control nsd.conf.sample nsd-control-
 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
-XFRD_OBJ=xfrd-disk.o xfrd-notify.o xfrd-tcp.o xfrd.o remote.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
 NSD_CHECKCONF_OBJ=$(COMMON_OBJ) nsd-checkconf.o
@@ -306,6 +309,22 @@ configlexer.c: $(srcdir)/configlexer.lex
 configparser.c configparser.h: $(srcdir)/configparser.y
  $(YACC) -d -o configparser.c $(srcdir)/configparser.y
 
+# dnstap
+dnstap.o: $(srcdir)/dnstap/dnstap.c config.h \
+ dnstap/dnstap.pb-c.c dnstap/dnstap.pb-c.h $(srcdir)/dnstap/dnstap.h \
+ $(srcdir)/util.h $(srcdir)/options.h $(srcdir)/rbtree.h \
+ $(srcdir)/region-allocator.h
+dnstap.pb-c.o: dnstap/dnstap.pb-c.c dnstap/dnstap.pb-c.h
+dnstap_collector.o: $(srcdir)/dnstap/dnstap_collector.c config.h \
+ $(srcdir)/dnstap/dnstap.h $(srcdir)/dnstap/dnstap_collector.h \
+ $(srcdir)/util.h $(srcdir)/nsd.h $(srcdir)/region-allocator.h \
+ $(srcdir)/buffer.h $(srcdir)/namedb.h $(srcdir)/dname.h \
+ $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \
+ $(srcdir)/options.h
+dnstap/dnstap.pb-c.c dnstap/dnstap.pb-c.h: $(srcdir)/dnstap/dnstap.proto
+ @-if test ! -d dnstap; then $(INSTALL) -d dnstap; fi
+ $(PROTOC_C) --c_out=. --proto_path=$(srcdir) $(srcdir)/dnstap/dnstap.proto
+
 # autoconf rules
 config.h.in: configure.ac
  autoheader
diff --git config.h.in config.h.in
index 4d47f603062..67296ca99b7 100644
--- config.h.in
+++ config.h.in
@@ -22,6 +22,9 @@
 /* Pathname to the NSD database */
 #undef DBFILE
 
+/* default dnstap socket path */
+#undef DNSTAP_SOCKET_PATH
+
 /* Define to the default maximum message length with EDNS. */
 #undef EDNS_MAX_MESSAGE_LEN
 
@@ -510,6 +513,9 @@
 /* the user name to drop privileges to */
 #undef USER
 
+/* Define to 1 to enable dnstap support */
+#undef USE_DNSTAP
+
 /* Define if you want to use internal select based events */
 #undef USE_MINI_EVENT
 
diff --git configlexer.lex configlexer.lex
index 7fd4f17363f..ead1b96fa80 100644
--- configlexer.lex
+++ configlexer.lex
@@ -117,9 +117,8 @@ static void config_start_include_glob(const char* filename)
 #ifdef GLOB_ERR
  | GLOB_ERR
 #endif
-#ifdef GLOB_NOSORT
- | GLOB_NOSORT
-#endif
+ /* do not set GLOB_NOSORT so the results are sorted
+    and in a predictable order. */
 #ifdef GLOB_BRACE
  | GLOB_BRACE
 #endif
@@ -270,6 +269,15 @@ rrl-whitelist-ratelimit{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_RRL_WHIT
 rrl-whitelist{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_RRL_WHITELIST;}
 zonefiles-check{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ZONEFILES_CHECK;}
 zonefiles-write{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ZONEFILES_WRITE;}
+dnstap{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP;}
+dnstap-enable{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_ENABLE;}
+dnstap-socket-path{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_SOCKET_PATH; }
+dnstap-send-identity{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_SEND_IDENTITY; }
+dnstap-send-version{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_SEND_VERSION; }
+dnstap-identity{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_IDENTITY; }
+dnstap-version{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_VERSION; }
+dnstap-log-auth-query-messages{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_LOG_AUTH_QUERY_MESSAGES; }
+dnstap-log-auth-response-messages{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_LOG_AUTH_RESPONSE_MESSAGES; }
 log-time-ascii{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_LOG_TIME_ASCII;}
 round-robin{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ROUND_ROBIN;}
 minimal-responses{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MINIMAL_RESPONSES;}
diff --git configparser.y configparser.y
index 567641ce706..1e4d75e9a47 100644
--- configparser.y
+++ configparser.y
@@ -72,13 +72,16 @@ extern config_parser_state_type* cfg_parser;
 %token VAR_MAX_REFRESH_TIME VAR_MIN_REFRESH_TIME
 %token VAR_MAX_RETRY_TIME VAR_MIN_RETRY_TIME
 %token VAR_MULTI_MASTER_CHECK VAR_MINIMAL_RESPONSES VAR_REFUSE_ANY
-%token VAR_USE_SYSTEMD
+%token VAR_USE_SYSTEMD VAR_DNSTAP VAR_DNSTAP_ENABLE VAR_DNSTAP_SOCKET_PATH
+%token VAR_DNSTAP_SEND_IDENTITY VAR_DNSTAP_SEND_VERSION VAR_DNSTAP_IDENTITY
+%token VAR_DNSTAP_VERSION VAR_DNSTAP_LOG_AUTH_QUERY_MESSAGES
+%token VAR_DNSTAP_LOG_AUTH_RESPONSE_MESSAGES
 
 %%
 toplevelvars: /* empty */ | toplevelvars toplevelvar ;
 toplevelvar: serverstart contents_server | zonestart contents_zone |
  keystart contents_key | patternstart contents_pattern |
- rcstart contents_rc;
+ rcstart contents_rc | dtstart contents_dt;
 
 /* server: declaration */
 serverstart: VAR_SERVER
@@ -596,6 +599,79 @@ rc_control_cert_file: VAR_CONTROL_CERT_FILE STRING
  }
  ;
 
+/* dnstap: declaration */
+dtstart: VAR_DNSTAP
+ {
+ OUTYY(("\nP(dnstap:)\n"));
+ }
+ ;
+contents_dt: contents_dt content_dt
+ | ;
+content_dt: dt_dnstap_enable | dt_dnstap_socket_path |
+ dt_dnstap_send_identity | dt_dnstap_send_version |
+ dt_dnstap_identity | dt_dnstap_version |
+ dt_dnstap_log_auth_query_messages |
+ dt_dnstap_log_auth_response_messages
+ ;
+dt_dnstap_enable: VAR_DNSTAP_ENABLE STRING
+ {
+ OUTYY(("P(dt_dnstap_enable:%s)\n", $2));
+ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+ yyerror("expected yes or no.");
+ else cfg_parser->opt->dnstap_enable = (strcmp($2, "yes")==0);
+ }
+ ;
+dt_dnstap_socket_path: VAR_DNSTAP_SOCKET_PATH STRING
+ {
+ OUTYY(("P(dt_dnstap_socket_path:%s)\n", $2));
+ cfg_parser->opt->dnstap_socket_path = region_strdup(cfg_parser->opt->region, $2);
+ }
+ ;
+dt_dnstap_send_identity: VAR_DNSTAP_SEND_IDENTITY STRING
+ {
+ OUTYY(("P(dt_dnstap_send_identity:%s)\n", $2));
+ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+ yyerror("expected yes or no.");
+ else cfg_parser->opt->dnstap_send_identity = (strcmp($2, "yes")==0);
+ }
+ ;
+dt_dnstap_send_version: VAR_DNSTAP_SEND_VERSION STRING
+ {
+ OUTYY(("P(dt_dnstap_send_version:%s)\n", $2));
+ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+ yyerror("expected yes or no.");
+ else cfg_parser->opt->dnstap_send_version = (strcmp($2, "yes")==0);
+ }
+ ;
+dt_dnstap_identity: VAR_DNSTAP_IDENTITY STRING
+ {
+ OUTYY(("P(dt_dnstap_identity:%s)\n", $2));
+ cfg_parser->opt->dnstap_identity = region_strdup(cfg_parser->opt->region, $2);
+ }
+ ;
+dt_dnstap_version: VAR_DNSTAP_VERSION STRING
+ {
+ OUTYY(("P(dt_dnstap_version:%s)\n", $2));
+ cfg_parser->opt->dnstap_version = region_strdup(cfg_parser->opt->region, $2);
+ }
+ ;
+dt_dnstap_log_auth_query_messages: VAR_DNSTAP_LOG_AUTH_QUERY_MESSAGES STRING
+ {
+ OUTYY(("P(dt_dnstap_log_auth_query_messages:%s)\n", $2));
+ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+ yyerror("expected yes or no.");
+ else cfg_parser->opt->dnstap_log_auth_query_messages = (strcmp($2, "yes")==0);
+ }
+ ;
+dt_dnstap_log_auth_response_messages: VAR_DNSTAP_LOG_AUTH_RESPONSE_MESSAGES STRING
+ {
+ OUTYY(("P(dt_dnstap_log_auth_response_messages:%s)\n", $2));
+ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+ yyerror("expected yes or no.");
+ else cfg_parser->opt->dnstap_log_auth_response_messages = (strcmp($2, "yes")==0);
+ }
+ ;
+
 /* pattern: declaration */
 patternstart: VAR_PATTERN
  {
diff --git configure configure
index a4b87938db6..47736231022 100644
--- configure
+++ configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for NSD 4.1.25.
+# Generated by GNU Autoconf 2.69 for NSD 4.1.26.
 #
 # Report bugs to <[hidden email]>.
 #
@@ -580,8 +580,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='NSD'
 PACKAGE_TARNAME='nsd'
-PACKAGE_VERSION='4.1.25'
-PACKAGE_STRING='NSD 4.1.25'
+PACKAGE_VERSION='4.1.26'
+PACKAGE_STRING='NSD 4.1.26'
 PACKAGE_BUGREPORT='[hidden email]'
 PACKAGE_URL=''
 
@@ -622,6 +622,11 @@ ac_includes_default="\
 #endif"
 
 ac_subst_vars='LTLIBOBJS
+DNSTAP_OBJ
+DNSTAP_SRC
+opt_dnstap_socket_path
+ENABLE_DNSTAP
+PROTOC_C
 SSL_LIBS
 HAVE_SSL
 ratelimit_default
@@ -734,6 +739,10 @@ enable_minimal_responses
 enable_mmap
 enable_radix_tree
 enable_packed
+enable_dnstap
+with_dnstap_socket_path
+with_protobuf_c
+with_libfstrm
 enable_systemd
 '
       ac_precious_vars='build_alias
@@ -1287,7 +1296,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures NSD 4.1.25 to adapt to many kinds of systems.
+\`configure' configures NSD 4.1.26 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1348,7 +1357,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of NSD 4.1.25:";;
+     short | recursive ) echo "Configuration of NSD 4.1.26:";;
    esac
   cat <<\_ACEOF
 
@@ -1387,6 +1396,7 @@ Optional Features:
                           less memory, but uses some more CPU.
   --enable-packed         Enable packed structure alignment, uses less memory,
                           but unaligned reads.
+  --enable-dnstap         Enable dnstap support (requires fstrm, protobuf-c)
   --enable-systemd        compile with systemd support
 
 Optional Packages:
@@ -1415,6 +1425,10 @@ Optional Packages:
                           Limit the default tcp timeout
   --with-ssl=pathname     enable SSL (will check /usr/local/ssl /usr/lib/ssl
                           /usr/ssl /usr/pkg /usr/sfw /usr/local /usr)
+  --with-dnstap-socket-path=pathname
+                          set default dnstap socket path
+  --with-protobuf-c=path  Path where protobuf-c is installed, for dnstap
+  --with-libfstrm=path    Path where libfstrm is installed, for dnstap
 
 Some influential environment variables:
   CC          C compiler command
@@ -1498,7 +1512,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-NSD configure 4.1.25
+NSD configure 4.1.26
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2207,7 +2221,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by NSD $as_me 4.1.25, which was
+It was created by NSD $as_me 4.1.26, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -9300,6 +9314,251 @@ fi
  ;;
 esac
 
+# check for dnstap if requested
+
+  # Check whether --enable-dnstap was given.
+if test "${enable_dnstap+set}" = set; then :
+  enableval=$enable_dnstap; opt_dnstap=$enableval
+else
+  opt_dnstap=no
+fi
+
+
+
+# Check whether --with-dnstap-socket-path was given.
+if test "${with_dnstap_socket_path+set}" = set; then :
+  withval=$with_dnstap_socket_path; opt_dnstap_socket_path=$withval
+else
+  opt_dnstap_socket_path="${localstatedir}/run/nsd-dnstap.sock"
+fi
+
+
+  if test "x$opt_dnstap" != "xno"; then
+    # Extract the first word of "protoc-c", so it can be a program name with args.
+set dummy protoc-c; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PROTOC_C+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PROTOC_C in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PROTOC_C="$PROTOC_C" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_PROTOC_C="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+PROTOC_C=$ac_cv_path_PROTOC_C
+if test -n "$PROTOC_C"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROTOC_C" >&5
+$as_echo "$PROTOC_C" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    if test -z "$PROTOC_C"; then
+      as_fn_error $? "The protoc-c program was not found. Please install protobuf-c!" "$LINENO" 5
+    fi
+
+# Check whether --with-protobuf-c was given.
+if test "${with_protobuf_c+set}" = set; then :
+  withval=$with_protobuf_c;
+  # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
+  if test -f $withval/include/google/protobuf-c/protobuf-c.h; then
+    CFLAGS="$CFLAGS -I$withval/include/google"
+  else
+    CFLAGS="$CFLAGS -I$withval/include"
+  fi
+  LDFLAGS="$LDFLAGS -L$withval/lib"
+
+else
+
+  # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
+  if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
+    CFLAGS="$CFLAGS -I/usr/include/google"
+  else
+    if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
+      CFLAGS="$CFLAGS -I/usr/local/include/google"
+      LDFLAGS="$LDFLAGS -L/usr/local/lib"
+    fi
+  fi
+
+fi
+
+
+# Check whether --with-libfstrm was given.
+if test "${with_libfstrm+set}" = set; then :
+  withval=$with_libfstrm;
+ CFLAGS="$CFLAGS -I$withval/include"
+ LDFLAGS="$LDFLAGS -L$withval/lib"
+
+fi
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing fstrm_iothr_init" >&5
+$as_echo_n "checking for library containing fstrm_iothr_init... " >&6; }
+if ${ac_cv_search_fstrm_iothr_init+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char fstrm_iothr_init ();
+int
+main ()
+{
+return fstrm_iothr_init ();
+  ;
+  return 0;
+}
+_ACEOF
+for ac_lib in '' fstrm; do
+  if test -z "$ac_lib"; then
+    ac_res="none required"
+  else
+    ac_res=-l$ac_lib
+    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
+  fi
+  if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_search_fstrm_iothr_init=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext
+  if ${ac_cv_search_fstrm_iothr_init+:} false; then :
+  break
+fi
+done
+if ${ac_cv_search_fstrm_iothr_init+:} false; then :
+
+else
+  ac_cv_search_fstrm_iothr_init=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_fstrm_iothr_init" >&5
+$as_echo "$ac_cv_search_fstrm_iothr_init" >&6; }
+ac_res=$ac_cv_search_fstrm_iothr_init
+if test "$ac_res" != no; then :
+  test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+else
+  as_fn_error $? "The fstrm library was not found. Please install fstrm!" "$LINENO" 5
+fi
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing protobuf_c_message_pack" >&5
+$as_echo_n "checking for library containing protobuf_c_message_pack... " >&6; }
+if ${ac_cv_search_protobuf_c_message_pack+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char protobuf_c_message_pack ();
+int
+main ()
+{
+return protobuf_c_message_pack ();
+  ;
+  return 0;
+}
+_ACEOF
+for ac_lib in '' protobuf-c; do
+  if test -z "$ac_lib"; then
+    ac_res="none required"
+  else
+    ac_res=-l$ac_lib
+    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
+  fi
+  if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_search_protobuf_c_message_pack=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext
+  if ${ac_cv_search_protobuf_c_message_pack+:} false; then :
+  break
+fi
+done
+if ${ac_cv_search_protobuf_c_message_pack+:} false; then :
+
+else
+  ac_cv_search_protobuf_c_message_pack=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_protobuf_c_message_pack" >&5
+$as_echo "$ac_cv_search_protobuf_c_message_pack" >&6; }
+ac_res=$ac_cv_search_protobuf_c_message_pack
+if test "$ac_res" != no; then :
+  test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+else
+  as_fn_error $? "The protobuf-c library was not found. Please install protobuf-c!" "$LINENO" 5
+fi
+
+
+
+$as_echo "#define USE_DNSTAP 1" >>confdefs.h
+
+        ENABLE_DNSTAP=1
+
+
+
+        hdr_dnstap_socket_path="`echo $opt_dnstap_socket_path | sed -e 's/\\\\/\\\\\\\\/g'`"
+
+
+cat >>confdefs.h <<_ACEOF
+#define DNSTAP_SOCKET_PATH "$hdr_dnstap_socket_path"
+_ACEOF
+
+
+        DNSTAP_SRC="dnstap/dnstap.c dnstap/dnstap.pb-c.c dnstap/dnstap_collector.c"
+
+        DNSTAP_OBJ="dnstap.o dnstap_collector.o dnstap.pb-c.o"
+
+ dnstap_config="dnstap/dnstap_config.h"
+
+  else
+
+        ENABLE_DNSTAP=0
+
+
+
+  fi
+
+
 # Include systemd.m4 - begin
 #   macros for configuring systemd
 #   Copyright 2015, Sami Kerola, CloudFlare.
@@ -9360,7 +9619,7 @@ if test "$enable_checking" = "yes"; then
         echo "************************************************"
 fi
 
-ac_config_files="$ac_config_files Makefile"
+ac_config_files="$ac_config_files Makefile $dnstap_config"
 
 cat >confcache <<\_ACEOF
 # This file is a shell script that caches the results of configure
@@ -9868,7 +10127,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by NSD $as_me 4.1.25, which was
+This file was extended by NSD $as_me 4.1.26, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -9930,7 +10189,7 @@ _ACEOF
 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.1.25
+NSD config.status 4.1.26
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
@@ -10055,6 +10314,7 @@ do
   case $ac_config_target in
     "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
     "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+    "$dnstap_config") CONFIG_FILES="$CONFIG_FILES $dnstap_config" ;;
 
   *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
   esac
diff --git configure.ac configure.ac
index ad5399a9d72..22e3e21e2ae 100644
--- configure.ac
+++ configure.ac
@@ -3,8 +3,9 @@ dnl Some global settings
 dnl
 
 sinclude(acx_nlnetlabs.m4)
+sinclude(dnstap/dnstap.m4)
 
-AC_INIT(NSD,4.1.25,[hidden email])
+AC_INIT(NSD,4.1.26,[hidden email])
 AC_CONFIG_HEADER([config.h])
 
 CFLAGS="$CFLAGS"
@@ -961,6 +962,26 @@ case "$enable_packed" in
  ;;
 esac
 
+# check for dnstap if requested
+dt_DNSTAP([${localstatedir}/run/nsd-dnstap.sock],
+    [
+        AC_DEFINE([USE_DNSTAP], [1], [Define to 1 to enable dnstap support])
+        AC_SUBST([ENABLE_DNSTAP], [1])
+
+        AC_SUBST([opt_dnstap_socket_path])
+        ACX_ESCAPE_BACKSLASH($opt_dnstap_socket_path, hdr_dnstap_socket_path)
+        AC_DEFINE_UNQUOTED(DNSTAP_SOCKET_PATH,
+            ["$hdr_dnstap_socket_path"], [default dnstap socket path])
+
+        AC_SUBST([DNSTAP_SRC], ["dnstap/dnstap.c dnstap/dnstap.pb-c.c dnstap/dnstap_collector.c"])
+        AC_SUBST([DNSTAP_OBJ], ["dnstap.o dnstap_collector.o dnstap.pb-c.o"])
+ dnstap_config="dnstap/dnstap_config.h"
+    ],
+    [
+        AC_SUBST([ENABLE_DNSTAP], [0])
+    ]
+)
+
 # Include systemd.m4 - begin
 sinclude(systemd.m4)
 # Include systemd.m4 - end
@@ -1163,5 +1184,5 @@ if test "$enable_checking" = "yes"; then
         echo "************************************************"
 fi
 
-AC_CONFIG_FILES([Makefile])
+AC_CONFIG_FILES([Makefile $dnstap_config])
 AC_OUTPUT
diff --git dnstap/dnstap.c dnstap/dnstap.c
new file mode 100644
index 00000000000..fb724a8fc7c
--- /dev/null
+++ dnstap/dnstap.c
@@ -0,0 +1,433 @@
+/* dnstap support for NSD */
+
+/*
+ * Copyright (c) 2013-2014, Farsight Security, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dnstap/dnstap_config.h"
+
+#ifdef USE_DNSTAP
+
+#include "config.h"
+#include <string.h>
+#include <sys/time.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include <errno.h>
+#include <unistd.h>
+#include "util.h"
+#include "options.h"
+
+#include <fstrm.h>
+#include <protobuf-c/protobuf-c.h>
+
+#include "dnstap/dnstap.h"
+#include "dnstap/dnstap.pb-c.h"
+
+#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap"
+#define DNSTAP_INITIAL_BUF_SIZE 256
+
+struct dt_msg {
+ void *buf;
+ size_t len_buf;
+ Dnstap__Dnstap d;
+ Dnstap__Message m;
+};
+
+static int
+dt_pack(const Dnstap__Dnstap *d, void **buf, size_t *sz)
+{
+ ProtobufCBufferSimple sbuf;
+
+ memset(&sbuf, 0, sizeof(sbuf));
+ sbuf.base.append = protobuf_c_buffer_simple_append;
+ sbuf.len = 0;
+ sbuf.alloced = DNSTAP_INITIAL_BUF_SIZE;
+ sbuf.data = malloc(sbuf.alloced);
+ if (sbuf.data == NULL)
+ return 0;
+ sbuf.must_free_data = 1;
+
+ *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *) &sbuf);
+ if (sbuf.data == NULL)
+ return 0;
+ *buf = sbuf.data;
+
+ return 1;
+}
+
+static void
+dt_send(const struct dt_env *env, void *buf, size_t len_buf)
+{
+ fstrm_res res;
+ if (!buf)
+ return;
+ res = fstrm_iothr_submit(env->iothr, env->ioq, buf, len_buf,
+ fstrm_free_wrapper, NULL);
+ if (res != fstrm_res_success)
+ free(buf);
+}
+
+static void
+dt_msg_init(const struct dt_env *env,
+    struct dt_msg *dm,
+    Dnstap__Message__Type mtype)
+{
+ memset(dm, 0, sizeof(*dm));
+ dm->d.base.descriptor = &dnstap__dnstap__descriptor;
+ dm->m.base.descriptor = &dnstap__message__descriptor;
+ dm->d.type = DNSTAP__DNSTAP__TYPE__MESSAGE;
+ dm->d.message = &dm->m;
+ dm->m.type = mtype;
+ if (env->identity != NULL) {
+ dm->d.identity.data = (uint8_t *) env->identity;
+ dm->d.identity.len = (size_t) env->len_identity;
+ dm->d.has_identity = 1;
+ }
+ if (env->version != NULL) {
+ dm->d.version.data = (uint8_t *) env->version;
+ dm->d.version.len = (size_t) env->len_version;
+ dm->d.has_version = 1;
+ }
+}
+
+/* check that the socket file can be opened and exists, print error if not */
+static void
+check_socket_file(const char* socket_path)
+{
+ struct stat statbuf;
+ memset(&statbuf, 0, sizeof(statbuf));
+ if(stat(socket_path, &statbuf) < 0) {
+ log_msg(LOG_WARNING, "could not open dnstap-socket-path: %s, %s",
+ socket_path, strerror(errno));
+ }
+}
+
+struct dt_env *
+dt_create(const char *socket_path, unsigned num_workers)
+{
+#ifndef NDEBUG
+ fstrm_res res;
+#endif
+ struct dt_env *env;
+ struct fstrm_iothr_options *fopt;
+ struct fstrm_unix_writer_options *fuwopt;
+ struct fstrm_writer *fw;
+ struct fstrm_writer_options *fwopt;
+
+ VERBOSITY(1, (LOG_INFO, "attempting to connect to dnstap socket %s",
+ socket_path));
+ assert(socket_path != NULL);
+ assert(num_workers > 0);
+ check_socket_file(socket_path);
+
+ env = (struct dt_env *) calloc(1, sizeof(struct dt_env));
+ if (!env)
+ return NULL;
+
+ fwopt = fstrm_writer_options_init();
+#ifndef NDEBUG
+ res =
+#else
+ (void)
+#endif
+    fstrm_writer_options_add_content_type(fwopt,
+ DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1);
+ assert(res == fstrm_res_success);
+
+ fuwopt = fstrm_unix_writer_options_init();
+ fstrm_unix_writer_options_set_socket_path(fuwopt, socket_path);
+
+ fw = fstrm_unix_writer_init(fuwopt, fwopt);
+ assert(fw != NULL);
+
+ fopt = fstrm_iothr_options_init();
+ fstrm_iothr_options_set_num_input_queues(fopt, num_workers);
+ env->iothr = fstrm_iothr_init(fopt, &fw);
+ if (env->iothr == NULL) {
+ log_msg(LOG_ERR, "dt_create: fstrm_iothr_init() failed");
+ fstrm_writer_destroy(&fw);
+ free(env);
+ env = NULL;
+ }
+ fstrm_iothr_options_destroy(&fopt);
+ fstrm_unix_writer_options_destroy(&fuwopt);
+ fstrm_writer_options_destroy(&fwopt);
+
+ return env;
+}
+
+static void
+dt_apply_identity(struct dt_env *env, struct nsd_options *cfg)
+{
+ char buf[MAXHOSTNAMELEN+1];
+ if (!cfg->dnstap_send_identity)
+ return;
+ free(env->identity);
+ if (cfg->dnstap_identity == NULL || cfg->dnstap_identity[0] == 0) {
+ if (gethostname(buf, MAXHOSTNAMELEN) == 0) {
+ buf[MAXHOSTNAMELEN] = 0;
+ env->identity = strdup(buf);
+ } else {
+ error("dt_apply_identity: gethostname() failed");
+ }
+ } else {
+ env->identity = strdup(cfg->dnstap_identity);
+ }
+ if (env->identity == NULL)
+ error("dt_apply_identity: strdup() failed");
+ env->len_identity = (unsigned int)strlen(env->identity);
+ VERBOSITY(1, (LOG_INFO, "dnstap identity field set to \"%s\"",
+ env->identity));
+}
+
+static void
+dt_apply_version(struct dt_env *env, struct nsd_options *cfg)
+{
+ if (!cfg->dnstap_send_version)
+ return;
+ free(env->version);
+ if (cfg->dnstap_version == NULL || cfg->dnstap_version[0] == 0)
+ env->version = strdup(PACKAGE_STRING);
+ else
+ env->version = strdup(cfg->dnstap_version);
+ if (env->version == NULL)
+ error("dt_apply_version: strdup() failed");
+ env->len_version = (unsigned int)strlen(env->version);
+ VERBOSITY(1, (LOG_INFO, "dnstap version field set to \"%s\"",
+ env->version));
+}
+
+void
+dt_apply_cfg(struct dt_env *env, struct nsd_options *cfg)
+{
+ if (!cfg->dnstap_enable)
+ return;
+
+ dt_apply_identity(env, cfg);
+ dt_apply_version(env, cfg);
+ if ((env->log_auth_query_messages = (unsigned int)
+     cfg->dnstap_log_auth_query_messages))
+ {
+ VERBOSITY(1, (LOG_INFO, "dnstap Message/AUTH_QUERY enabled"));
+ }
+ if ((env->log_auth_response_messages = (unsigned int)
+     cfg->dnstap_log_auth_response_messages))
+ {
+ VERBOSITY(1, (LOG_INFO, "dnstap Message/AUTH_RESPONSE enabled"));
+ }
+}
+
+int
+dt_init(struct dt_env *env)
+{
+ env->ioq = fstrm_iothr_get_input_queue(env->iothr);
+ if (env->ioq == NULL)
+ return 0;
+ return 1;
+}
+
+void
+dt_delete(struct dt_env *env)
+{
+ if (!env)
+ return;
+ VERBOSITY(1, (LOG_INFO, "closing dnstap socket"));
+ fstrm_iothr_destroy(&env->iothr);
+ free(env->identity);
+ free(env->version);
+ free(env);
+}
+
+static void
+dt_fill_timeval(const struct timeval *tv,
+ uint64_t *time_sec, protobuf_c_boolean *has_time_sec,
+ uint32_t *time_nsec, protobuf_c_boolean *has_time_nsec)
+{
+#ifndef S_SPLINT_S
+ *time_sec = tv->tv_sec;
+ *time_nsec = tv->tv_usec * 1000;
+#endif
+ *has_time_sec = 1;
+ *has_time_nsec = 1;
+}
+
+static void
+dt_fill_buffer(uint8_t* pkt, size_t pktlen, ProtobufCBinaryData *p, protobuf_c_boolean *has)
+{
+ p->len = pktlen;
+ p->data = pkt;
+ *has = 1;
+}
+
+static void
+dt_msg_fill_net(struct dt_msg *dm,
+#ifdef INET6
+ struct sockaddr_storage *ss,
+#else
+ struct sockaddr_in *ss,
+#endif
+ int is_tcp,
+ ProtobufCBinaryData *addr, protobuf_c_boolean *has_addr,
+ uint32_t *port, protobuf_c_boolean *has_port)
+{
+#ifdef INET6
+ assert(ss->ss_family == AF_INET6 || ss->ss_family == AF_INET);
+ if (ss->ss_family == AF_INET6) {
+ struct sockaddr_in6 *s = (struct sockaddr_in6 *) ss;
+
+ /* socket_family */
+ dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET6;
+ dm->m.has_socket_family = 1;
+
+ /* addr: query_address or response_address */
+ addr->data = s->sin6_addr.s6_addr;
+ addr->len = 16; /* IPv6 */
+ *has_addr = 1;
+
+ /* port: query_port or response_port */
+ *port = ntohs(s->sin6_port);
+ *has_port = 1;
+ } else if (ss->ss_family == AF_INET) {
+#else
+ if (ss->ss_family == AF_INET) {
+#endif /* INET6 */
+ struct sockaddr_in *s = (struct sockaddr_in *) ss;
+
+ /* socket_family */
+ dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET;
+ dm->m.has_socket_family = 1;
+
+ /* addr: query_address or response_address */
+ addr->data = (uint8_t *) &s->sin_addr.s_addr;
+ addr->len = 4; /* IPv4 */
+ *has_addr = 1;
+
+ /* port: query_port or response_port */
+ *port = ntohs(s->sin_port);
+ *has_port = 1;
+ }
+
+ if (!is_tcp) {
+ /* socket_protocol */
+ dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__UDP;
+ dm->m.has_socket_protocol = 1;
+ } else {
+ /* socket_protocol */
+ dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__TCP;
+ dm->m.has_socket_protocol = 1;
+ }
+}
+
+void
+dt_msg_send_auth_query(struct dt_env *env,
+#ifdef INET6
+ struct sockaddr_storage* addr,
+#else
+ struct sockaddr_in* addr,
+#endif
+ int is_tcp, uint8_t* zone, size_t zonelen, uint8_t* pkt, size_t pktlen)
+{
+ struct dt_msg dm;
+ struct timeval qtime;
+
+ gettimeofday(&qtime, NULL);
+
+ /* type */
+ dt_msg_init(env, &dm, DNSTAP__MESSAGE__TYPE__AUTH_QUERY);
+
+ if(zone) {
+ /* query_zone */
+ dm.m.query_zone.data = zone;
+ dm.m.query_zone.len = zonelen;
+ dm.m.has_query_zone = 1;
+ }
+
+ /* query_time */
+ dt_fill_timeval(&qtime,
+ &dm.m.query_time_sec, &dm.m.has_query_time_sec,
+ &dm.m.query_time_nsec, &dm.m.has_query_time_nsec);
+
+ /* query_message */
+ dt_fill_buffer(pkt, pktlen, &dm.m.query_message, &dm.m.has_query_message);
+
+ /* socket_family, socket_protocol, query_address, query_port */
+ dt_msg_fill_net(&dm, addr, is_tcp,
+ &dm.m.query_address, &dm.m.has_query_address,
+ &dm.m.query_port, &dm.m.has_query_port);
+
+ if (dt_pack(&dm.d, &dm.buf, &dm.len_buf))
+ dt_send(env, dm.buf, dm.len_buf);
+}
+
+void
+dt_msg_send_auth_response(struct dt_env *env,
+#ifdef INET6
+ struct sockaddr_storage* addr,
+#else
+ struct sockaddr_in* addr,
+#endif
+ int is_tcp, uint8_t* zone, size_t zonelen, uint8_t* pkt, size_t pktlen)
+{
+ struct dt_msg dm;
+ struct timeval rtime;
+
+ gettimeofday(&rtime, NULL);
+
+ /* type */
+ dt_msg_init(env, &dm, DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE);
+
+ if(zone) {
+ /* query_zone */
+ dm.m.query_zone.data = zone;
+ dm.m.query_zone.len = zonelen;
+ dm.m.has_query_zone = 1;
+ }
+
+ /* response_time */
+ dt_fill_timeval(&rtime,
+ &dm.m.response_time_sec, &dm.m.has_response_time_sec,
+ &dm.m.response_time_nsec, &dm.m.has_response_time_nsec);
+
+ /* response_message */
+ dt_fill_buffer(pkt, pktlen, &dm.m.response_message, &dm.m.has_response_message);
+
+ /* socket_family, socket_protocol, query_address, query_port */
+ dt_msg_fill_net(&dm, addr, is_tcp,
+ &dm.m.query_address, &dm.m.has_query_address,
+ &dm.m.query_port, &dm.m.has_query_port);
+
+ if (dt_pack(&dm.d, &dm.buf, &dm.len_buf))
+ dt_send(env, dm.buf, dm.len_buf);
+}
+
+#endif /* USE_DNSTAP */
diff --git dnstap/dnstap.h dnstap/dnstap.h
new file mode 100644
index 00000000000..05b1bd049f3
--- /dev/null
+++ dnstap/dnstap.h
@@ -0,0 +1,148 @@
+/* dnstap support for NSD */
+
+/*
+ * Copyright (c) 2013-2014, Farsight Security, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef NSD_DNSTAP_H
+#define NSD_DNSTAP_H
+
+#include "dnstap/dnstap_config.h"
+
+#ifdef USE_DNSTAP
+
+struct nsd_options;
+struct fstrm_io;
+struct fstrm_queue;
+
+struct dt_env {
+ /** dnstap I/O thread */
+ struct fstrm_iothr *iothr;
+
+ /** dnstap I/O thread input queue */
+ struct fstrm_iothr_queue *ioq;
+
+ /** dnstap "identity" field, NULL if disabled */
+ char *identity;
+
+ /** dnstap "version" field, NULL if disabled */
+ char *version;
+
+ /** length of "identity" field */
+ unsigned len_identity;
+
+ /** length of "version" field */
+ unsigned len_version;
+
+ /** whether to log Message/AUTH_QUERY */
+ unsigned log_auth_query_messages : 1;
+ /** whether to log Message/AUTH_RESPONSE */
+ unsigned log_auth_response_messages : 1;
+};
+
+/**
+ * Create dnstap environment object. Afterwards, call dt_apply_cfg() to fill in
+ * the config variables and dt_init() to fill in the per-worker state. Each
+ * worker needs a copy of this object but with its own I/O queue (the fq field
+ * of the structure) to ensure lock-free access to its own per-worker circular
+ * queue.  Duplicate the environment object if more than one worker needs to
+ * share access to the dnstap I/O socket.
+ * @param socket_path: path to dnstap logging socket, must be non-NULL.
+ * @param num_workers: number of worker threads, must be > 0.
+ * @return dt_env object, NULL on failure.
+ */
+struct dt_env *
+dt_create(const char *socket_path, unsigned num_workers);
+
+/**
+ * Apply config settings.
+ * @param env: dnstap environment object.
+ * @param cfg: new config settings.
+ */
+void
+dt_apply_cfg(struct dt_env *env, struct nsd_options *cfg);
+
+/**
+ * Initialize per-worker state in dnstap environment object.
+ * @param env: dnstap environment object to initialize, created with dt_create().
+ * @return: true on success, false on failure.
+ */
+int
+dt_init(struct dt_env *env);
+
+/**
+ * Delete dnstap environment object. Closes dnstap I/O socket and deletes all
+ * per-worker I/O queues.
+ */
+void
+dt_delete(struct dt_env *env);
+
+/**
+ * Create and send a new dnstap "Message" event of type AUTH_QUERY.
+ * @param env: dnstap environment object.
+ * @param addr: address/port of client.
+ * @param is_tcp: true for tcp, false for udp.
+ * @param zone: zone name, or NULL. in wireformat.
+ * @param zonelen: length of zone in bytes.
+ * @param pkt: query message.
+ * @param pktlen: length of pkt.
+ */
+void
+dt_msg_send_auth_query(struct dt_env *env,
+#ifdef INET6
+ struct sockaddr_storage* addr,
+#else
+ struct sockaddr_in* addr,
+#endif
+ int is_tcp, uint8_t* zone, size_t zonelen, uint8_t* pkt, size_t pktlen);
+
+/**
+ * Create and send a new dnstap "Message" event of type AUTH_RESPONSE.
+ * @param env: dnstap environment object.
+ * @param addr: address/port of client.
+ * @param is_tcp: true for tcp, false for udp.
+ * @param zone: zone name, or NULL. in wireformat.
+ * @param zonelen: length of zone in bytes.
+ * @param pkt: response message.
+ * @param pktlen: length of pkt.
+ */
+void
+dt_msg_send_auth_response(struct dt_env *env,
+#ifdef INET6
+ struct sockaddr_storage* addr,
+#else
+ struct sockaddr_in* addr,
+#endif
+ int is_tcp, uint8_t* zone, size_t zonelen, uint8_t* pkt, size_t pktlen);
+
+#endif /* USE_DNSTAP */
+
+#endif /* NSD_DNSTAP_H */
diff --git dnstap/dnstap.m4 dnstap/dnstap.m4
new file mode 100644
index 00000000000..5b78b3e267c
--- /dev/null
+++ dnstap/dnstap.m4
@@ -0,0 +1,56 @@
+# dnstap.m4
+
+# dt_DNSTAP(default_dnstap_socket_path, [action-if-true], [action-if-false])
+# --------------------------------------------------------------------------
+# Check for required dnstap libraries and add dnstap configure args.
+AC_DEFUN([dt_DNSTAP],
+[
+  AC_ARG_ENABLE([dnstap],
+    AS_HELP_STRING([--enable-dnstap],
+                   [Enable dnstap support (requires fstrm, protobuf-c)]),
+    [opt_dnstap=$enableval], [opt_dnstap=no])
+
+  AC_ARG_WITH([dnstap-socket-path],
+    AS_HELP_STRING([--with-dnstap-socket-path=pathname],
+                   [set default dnstap socket path]),
+    [opt_dnstap_socket_path=$withval], [opt_dnstap_socket_path="$1"])
+
+  if test "x$opt_dnstap" != "xno"; then
+    AC_PATH_PROG([PROTOC_C], [protoc-c])
+    if test -z "$PROTOC_C"; then
+      AC_MSG_ERROR([The protoc-c program was not found. Please install protobuf-c!])
+    fi
+    AC_ARG_WITH([protobuf-c], AC_HELP_STRING([--with-protobuf-c=path],
+     [Path where protobuf-c is installed, for dnstap]), [
+  # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
+  if test -f $withval/include/google/protobuf-c/protobuf-c.h; then
+    CFLAGS="$CFLAGS -I$withval/include/google"
+  else
+    CFLAGS="$CFLAGS -I$withval/include"
+  fi
+  LDFLAGS="$LDFLAGS -L$withval/lib"
+ ], [
+  # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
+  if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
+    CFLAGS="$CFLAGS -I/usr/include/google"
+  else
+    if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
+      CFLAGS="$CFLAGS -I/usr/local/include/google"
+      LDFLAGS="$LDFLAGS -L/usr/local/lib"
+    fi
+  fi
+    ])
+    AC_ARG_WITH([libfstrm], AC_HELP_STRING([--with-libfstrm=path],
+     [Path where libfstrm is installed, for dnstap]), [
+ CFLAGS="$CFLAGS -I$withval/include"
+ LDFLAGS="$LDFLAGS -L$withval/lib"
+    ])
+    AC_SEARCH_LIBS([fstrm_iothr_init], [fstrm], [],
+      AC_MSG_ERROR([The fstrm library was not found. Please install fstrm!]))
+    AC_SEARCH_LIBS([protobuf_c_message_pack], [protobuf-c], [],
+      AC_MSG_ERROR([The protobuf-c library was not found. Please install protobuf-c!]))
+    $2
+  else
+    $3
+  fi
+])
diff --git dnstap/dnstap.proto dnstap/dnstap.proto
new file mode 100644
index 00000000000..88bfb4e9412
--- /dev/null
+++ dnstap/dnstap.proto
@@ -0,0 +1,263 @@
+// dnstap: flexible, structured event replication format for DNS software
+//
+// This file contains the protobuf schemas for the "dnstap" structured event
+// replication format for DNS software.
+
+// Written in 2013-2014 by Farsight Security, Inc.
+//
+// To the extent possible under law, the author(s) have dedicated all
+// copyright and related and neighboring rights to this file to the public
+// domain worldwide. This file is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication along
+// with this file. If not, see:
+//
+// <http://creativecommons.org/publicdomain/zero/1.0/>.
+syntax = "proto2";
+
+package dnstap;
+
+// "Dnstap": this is the top-level dnstap type, which is a "union" type that
+// contains other kinds of dnstap payloads, although currently only one type
+// of dnstap payload is defined.
+// See: https://developers.google.com/protocol-buffers/docs/techniques#union
+message Dnstap {
+    // DNS server identity.
+    // If enabled, this is the identity string of the DNS server which generated
+    // this message. Typically this would be the same string as returned by an
+    // "NSID" (RFC 5001) query.
+    optional bytes      identity = 1;
+
+    // DNS server version.
+    // If enabled, this is the version string of the DNS server which generated
+    // this message. Typically this would be the same string as returned by a
+    // "version.bind" query.
+    optional bytes      version = 2;
+
+    // Extra data for this payload.
+    // This field can be used for adding an arbitrary byte-string annotation to
+    // the payload. No encoding or interpretation is applied or enforced.
+    optional bytes      extra = 3;
+
+    // Identifies which field below is filled in.
+    enum Type {
+        MESSAGE = 1;
+    }
+    required Type       type = 15;
+
+    // One of the following will be filled in.
+    optional Message    message = 14;
+}
+
+// SocketFamily: the network protocol family of a socket. This specifies how
+// to interpret "network address" fields.
+enum SocketFamily {
+    INET = 1;   // IPv4 (RFC 791)
+    INET6 = 2;  // IPv6 (RFC 2460)
+}
+
+// SocketProtocol: the transport protocol of a socket. This specifies how to
+// interpret "transport port" fields.
+enum SocketProtocol {
+    UDP = 1;    // User Datagram Protocol (RFC 768)
+    TCP = 2;    // Transmission Control Protocol (RFC 793)
+}
+
+// Message: a wire-format (RFC 1035 section 4) DNS message and associated
+// metadata. Applications generating "Message" payloads should follow
+// certain requirements based on the MessageType, see below.
+message Message {
+
+    // There are eight types of "Message" defined that correspond to the
+    // four arrows in the following diagram, slightly modified from RFC 1035
+    // section 2:
+
+    //    +---------+               +----------+           +--------+
+    //    |         |     query     |          |   query   |        |
+    //    | Stub    |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth.  |
+    //    | Resolver|               | Server   |           | Name   |
+    //    |         |<-SR--------CR-|          |<-RR----AR-| Server |
+    //    +---------+    response   |          |  response |        |
+    //                              +----------+           +--------+
+
+    // Each arrow has two Type values each, one for each "end" of each arrow,
+    // because these are considered to be distinct events. Each end of each
+    // arrow on the diagram above has been marked with a two-letter Type
+    // mnemonic. Clockwise from upper left, these mnemonic values are:
+    //
+    //   SQ:        STUB_QUERY
+    //   CQ:      CLIENT_QUERY
+    //   RQ:    RESOLVER_QUERY
+    //   AQ:        AUTH_QUERY
+    //   AR:        AUTH_RESPONSE
+    //   RR:    RESOLVER_RESPONSE
+    //   CR:      CLIENT_RESPONSE
+    //   SR:        STUB_RESPONSE
+
+    // Two additional types of "Message" have been defined for the
+    // "forwarding" case where an upstream DNS server is responsible for
+    // further recursion. These are not shown on the diagram above, but have
+    // the following mnemonic values:
+
+    //   FQ:   FORWARDER_QUERY
+    //   FR:   FORWARDER_RESPONSE
+
+    // The "Message" Type values are defined below.
+
+    enum Type {
+        // AUTH_QUERY is a DNS query message received from a resolver by an
+        // authoritative name server, from the perspective of the authoritative
+        // name server.
+        AUTH_QUERY = 1;
+
+        // AUTH_RESPONSE is a DNS response message sent from an authoritative
+        // name server to a resolver, from the perspective of the authoritative
+        // name server.
+        AUTH_RESPONSE = 2;
+
+        // RESOLVER_QUERY is a DNS query message sent from a resolver to an
+        // authoritative name server, from the perspective of the resolver.
+        // Resolvers typically clear the RD (recursion desired) bit when
+        // sending queries.
+        RESOLVER_QUERY = 3;
+
+        // RESOLVER_RESPONSE is a DNS response message received from an
+        // authoritative name server by a resolver, from the perspective of
+        // the resolver.
+        RESOLVER_RESPONSE = 4;
+
+        // CLIENT_QUERY is a DNS query message sent from a client to a DNS
+        // server which is expected to perform further recursion, from the
+        // perspective of the DNS server. The client may be a stub resolver or
+        // forwarder or some other type of software which typically sets the RD
+        // (recursion desired) bit when querying the DNS server. The DNS server
+        // may be a simple forwarding proxy or it may be a full recursive
+        // resolver.
+        CLIENT_QUERY = 5;
+
+        // CLIENT_RESPONSE is a DNS response message sent from a DNS server to
+        // a client, from the perspective of the DNS server. The DNS server
+        // typically sets the RA (recursion available) bit when responding.
+        CLIENT_RESPONSE = 6;
+
+        // FORWARDER_QUERY is a DNS query message sent from a downstream DNS
+        // server to an upstream DNS server which is expected to perform
+        // further recursion, from the perspective of the downstream DNS
+        // server.
+        FORWARDER_QUERY = 7;
+
+        // FORWARDER_RESPONSE is a DNS response message sent from an upstream
+        // DNS server performing recursion to a downstream DNS server, from the
+        // perspective of the downstream DNS server.
+        FORWARDER_RESPONSE = 8;
+
+        // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS
+        // server, from the perspective of the stub resolver.
+        STUB_QUERY = 9;
+
+        // STUB_RESPONSE is a DNS response message sent from a DNS server to a
+        // stub resolver, from the perspective of the stub resolver.
+        STUB_RESPONSE = 10;
+    }
+
+    // One of the Type values described above.
+    required Type               type = 1;
+
+    // One of the SocketFamily values described above.
+    optional SocketFamily       socket_family = 2;
+
+    // One of the SocketProtocol values described above.
+    optional SocketProtocol     socket_protocol = 3;
+
+    // The network address of the message initiator.
+    // For SocketFamily INET, this field is 4 octets (IPv4 address).
+    // For SocketFamily INET6, this field is 16 octets (IPv6 address).
+    optional bytes              query_address = 4;
+
+    // The network address of the message responder.
+    // For SocketFamily INET, this field is 4 octets (IPv4 address).
+    // For SocketFamily INET6, this field is 16 octets (IPv6 address).
+    optional bytes              response_address = 5;
+
+    // The transport port of the message initiator.
+    // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
+    optional uint32             query_port = 6;
+
+    // The transport port of the message responder.
+    // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
+    optional uint32             response_port = 7;
+
+    // The time at which the DNS query message was sent or received, depending
+    // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY.
+    // This is the number of seconds since the UNIX epoch.
+    optional uint64             query_time_sec = 8;
+
+    // The time at which the DNS query message was sent or received.
+    // This is the seconds fraction, expressed as a count of nanoseconds.
+    optional fixed32            query_time_nsec = 9;
+
+    // The initiator's original wire-format DNS query message, verbatim.
+    optional bytes              query_message = 10;
+
+    // The "zone" or "bailiwick" pertaining to the DNS query message.
+    // This is a wire-format DNS domain name.
+    optional bytes              query_zone = 11;
+
+    // The time at which the DNS response message was sent or received,
+    // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or
+    // CLIENT_RESPONSE.
+    // This is the number of seconds since the UNIX epoch.
+    optional uint64             response_time_sec = 12;
+
+    // The time at which the DNS response message was sent or received.
+    // This is the seconds fraction, expressed as a count of nanoseconds.
+    optional fixed32            response_time_nsec = 13;
+
+    // The responder's original wire-format DNS response message, verbatim.
+    optional bytes              response_message = 14;
+}
+
+// All fields except for 'type' in the Message schema are optional.
+// It is recommended that at least the following fields be filled in for
+// particular types of Messages.
+
+// AUTH_QUERY:
+//      socket_family, socket_protocol
+//      query_address, query_port
+//      query_message
+//      query_time_sec, query_time_nsec
+
+// AUTH_RESPONSE:
+//      socket_family, socket_protocol
+//      query_address, query_port
+//      query_time_sec, query_time_nsec
+//      response_message
+//      response_time_sec, response_time_nsec
+
+// RESOLVER_QUERY:
+//      socket_family, socket_protocol
+//      query_name, query_type, query_class
+//      query_message
+//      query_time_sec, query_time_nsec
+//      query_zone
+//      response_address, response_port
+
+// RESOLVER_RESPONSE:
+//      socket_family, socket_protocol
+//      query_name, query_type, query_class
+//      query_time_sec, query_time_nsec
+//      query_zone
+//      response_address, response_port
+//      response_message
+//      response_time_sec, response_time_nsec
+
+// CLIENT_QUERY:
+//      socket_family, socket_protocol
+//      query_message
+//      query_time_sec, query_time_nsec
+
+// CLIENT_RESPONSE:
+//      socket_family, socket_protocol
+//      query_time_sec, query_time_nsec
+//      response_message
+//      response_time_sec, response_time_nsec
diff --git dnstap/dnstap_collector.c dnstap/dnstap_collector.c
new file mode 100644
index 00000000000..091113fc45f
--- /dev/null
+++ dnstap/dnstap_collector.c
@@ -0,0 +1,516 @@
+/*
+ * dnstap/dnstap_collector.c -- nsd collector process for dnstap information
+ *
+ * Copyright (c) 2018, NLnet Labs. All rights reserved.
+ *
+ * See LICENSE for the license.
+ *
+ */
+
+#include "config.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifndef USE_MINI_EVENT
+#  ifdef HAVE_EVENT_H
+#    include <event.h>
+#  else
+#    include <event2/event.h>
+#    include "event2/event_struct.h"
+#    include "event2/event_compat.h"
+#  endif
+#else
+#  include "mini_event.h"
+#endif
+#include "dnstap/dnstap_collector.h"
+#include "dnstap/dnstap.h"
+#include "util.h"
+#include "nsd.h"
+#include "region-allocator.h"
+#include "buffer.h"
+#include "namedb.h"
+#include "options.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->dt_env = NULL;
+ dt_col->region = region_create(xalloc, free);
+ dt_col->send_buffer = buffer_create(dt_col->region,
+ /* msglen + is_response + addrlen + is_tcp + packetlen + packet + zonelen + zone + spare + addr */
+ 4+1+4+1+4+TCP_MAX_MESSAGE_LEN+4+MAXHOSTNAMELEN + 32 +
+#ifdef INET6
+ sizeof(struct sockaddr_storage)
+#else
+ sizeof(struct sockaddr_in)
+#endif
+ );
+
+ /* open pipes 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",
+ strerror(errno));
+ }
+ if(fcntl(fd[0], F_SETFL, O_NONBLOCK) == -1) {
+ log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
+ }
+ if(fcntl(fd[1], F_SETFL, O_NONBLOCK) == -1) {
+ log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
+ }
+ nsd->dt_collector_fd_recv[i] = fd[0];
+ nsd->dt_collector_fd_send[i] = fd[1];
+ }
+
+ /* open socketpair */
+ if(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
+ error("dnstap_collector: cannot create socketpair: %s",
+ strerror(errno));
+ }
+ if(fcntl(sv[0], F_SETFL, O_NONBLOCK) == -1) {
+ log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
+ }
+ if(fcntl(sv[1], F_SETFL, O_NONBLOCK) == -1) {
+ log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
+ }
+ dt_col->cmd_socket_dt = sv[0];
+ dt_col->cmd_socket_nsd = sv[1];
+
+ return dt_col;
+}
+
+void dt_collector_destroy(struct dt_collector* dt_col, struct nsd* nsd)
+{
+ if(!dt_col) return;
+ free(nsd->dt_collector_fd_recv);
+ nsd->dt_collector_fd_recv = NULL;
+ free(nsd->dt_collector_fd_send);
+ nsd->dt_collector_fd_send = NULL;
+ region_destroy(dt_col->region);
+ free(dt_col);
+}
+
+void dt_collector_close(struct dt_collector* dt_col, struct nsd* nsd)
+{
+ int i;
+ if(!dt_col) return;
+ if(dt_col->cmd_socket_dt != -1) {
+ close(dt_col->cmd_socket_dt);
+ dt_col->cmd_socket_dt = -1;
+ }
+ if(dt_col->cmd_socket_nsd != -1) {
+ close(dt_col->cmd_socket_nsd);
+ dt_col->cmd_socket_nsd = -1;
+ }
+ 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;
+ }
+ }
+}
+
+/* handle command from nsd to dt collector.
+ * mostly, check for fd closed, this means we have to exit */
+void
+dt_handle_cmd_from_nsd(int ATTR_UNUSED(fd), short event, void* arg)
+{
+ struct dt_collector* dt_col = (struct dt_collector*)arg;
+ if((event&EV_READ) != 0) {
+ event_base_loopexit(dt_col->event_base, NULL);
+ }
+}
+
+/* read data from fd into buffer, true when message is complete */
+static int read_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));
+ 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 + msglen)
+ return 0; /* read more msg later */
+
+ /* msg complete */
+ buffer_flip(buf);
+ return 1;
+}
+
+/* submit the content of the buffer received to dnstap */
+static void
+dt_submit_content(struct dt_env* dt_env, struct buffer* buf)
+{
+ uint8_t is_response, is_tcp;
+#ifdef INET6
+ struct sockaddr_storage addr;
+#else
+ struct sockaddr_in addr;
+#endif
+ socklen_t addrlen;
+ size_t pktlen;
+ uint8_t* data;
+ size_t zonelen;
+ uint8_t* zone;
+
+ /* parse content from buffer */
+ if(!buffer_available(buf, 4+1+4)) return;
+ buffer_skip(buf, 4); /* skip msglen */
+ is_response = buffer_read_u8(buf);
+ addrlen = buffer_read_u32(buf);
+ if(addrlen > sizeof(addr)) return;
+ if(!buffer_available(buf, addrlen)) return;
+ buffer_read(buf, &addr, addrlen);
+ if(!buffer_available(buf, 1+4)) return;
+ is_tcp = buffer_read_u8(buf);
+ pktlen = buffer_read_u32(buf);
+ if(!buffer_available(buf, pktlen)) return;
+ data = buffer_current(buf);
+ buffer_skip(buf, pktlen);
+ if(!buffer_available(buf, 4)) return;
+ zonelen = buffer_read_u32(buf);
+ if(zonelen == 0) {
+ zone = NULL;
+ } else {
+ if(zonelen > MAXDOMAINLEN) return;
+ if(!buffer_available(buf, zonelen)) return;
+ zone = buffer_current(buf);
+ buffer_skip(buf, zonelen);
+ }
+
+ /* submit it */
+ if(is_response) {
+ dt_msg_send_auth_response(dt_env, &addr, is_tcp, zone,
+ zonelen, data, pktlen);
+ } else {
+ dt_msg_send_auth_query(dt_env, &addr, is_tcp, zone,
+ zonelen, data, pktlen);
+ }
+}
+
+/* handle input from worker for dnstap */
+void
+dt_handle_input(int fd, short event, void* arg)
+{
+ 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))
+ return;
+
+ /* once data is complete, write 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) {
+ dt_submit_content(dt_input->dt_collector->dt_env,
+ dt_input->buffer);
+ }
+
+ /* clear buffer for next message */
+ buffer_clear(dt_input->buffer);
+ }
+}
+
+/* init dnstap */
+static void dt_init_dnstap(struct dt_collector* dt_col, struct nsd* nsd)
+{
+ int num_workers = 1;
+#ifdef HAVE_CHROOT
+ if(nsd->chrootdir && nsd->chrootdir[0]) {
+ int l = strlen(nsd->chrootdir)-1; /* ends in trailing slash */
+ if (nsd->options->dnstap_socket_path &&
+ nsd->options->dnstap_socket_path[0] == '/' &&
+ strncmp(nsd->options->dnstap_socket_path,
+ nsd->chrootdir, l) == 0)
+ nsd->options->dnstap_socket_path += l;
+ }
+#endif
+ dt_col->dt_env = dt_create(nsd->options->dnstap_socket_path, num_workers);
+ if(!dt_col->dt_env) {
+ log_msg(LOG_ERR, "could not create dnstap env");
+ return;
+ }
+ dt_apply_cfg(dt_col->dt_env, nsd->options);
+ dt_init(dt_col->dt_env);
+}
+
+/* cleanup dt collector process for exit */
+static void dt_collector_cleanup(struct dt_collector* dt_col, struct nsd* nsd)
+{
+ int i;
+ dt_delete(dt_col->dt_env);
+ event_del(dt_col->cmd_event);
+ for(i=0; i<dt_col->count; i++) {
+ event_del(dt_col->inputs[i].event);
+ }
+ dt_collector_close(dt_col, nsd);
+ event_base_free(dt_col->event_base);
+#ifdef MEMCLEAN
+ free(dt_col->cmd_event);
+ if(dt_col->inputs) {
+ for(i=0; i<dt_col->count; i++) {
+ free(dt_col->inputs[i].event);
+ }
+ free(dt_col->inputs);
+ }
+ dt_collector_destroy(dt_col, nsd);
+#endif
+}
+
+/* attach events to the event base to listen to the workers and cmd channel */
+static void dt_attach_events(struct dt_collector* dt_col, struct nsd* nsd)
+{
+ int i;
+ /* create event base */
+ dt_col->event_base = nsd_child_event_base();
+ if(!dt_col->event_base) {
+ error("dnstap collector: event_base create failed");
+ }
+
+ /* add command handler */
+ dt_col->cmd_event = (struct event*)xalloc_zero(
+ sizeof(*dt_col->cmd_event));
+ event_set(dt_col->cmd_event, dt_col->cmd_socket_dt,
+ EV_PERSIST|EV_READ, dt_handle_cmd_from_nsd, dt_col);
+ if(event_base_set(dt_col->event_base, dt_col->cmd_event) != 0)
+ log_msg(LOG_ERR, "dnstap collector: event_base_set failed");
+ if(event_add(dt_col->cmd_event, NULL) != 0)
+ log_msg(LOG_ERR, "dnstap collector: event_add failed");
+
+ /* add worker input handlers */
+ dt_col->inputs = xalloc_array_zero(dt_col->count,
+ sizeof(*dt_col->inputs));
+ for(i=0; i<dt_col->count; i++) {
+ dt_col->inputs[i].dt_collector = dt_col;
+ dt_col->inputs[i].event = (struct event*)xalloc_zero(
+ sizeof(struct event));
+ event_set(dt_col->inputs[i].event,
+ nsd->dt_collector_fd_recv[i], EV_PERSIST|EV_READ,
+ dt_handle_input, &dt_col->inputs[i]);
+ if(event_base_set(dt_col->event_base,
+ dt_col->inputs[i].event) != 0)
+ log_msg(LOG_ERR, "dnstap collector: event_base_set failed");
+ if(event_add(dt_col->inputs[i].event, NULL) != 0)
+ log_msg(LOG_ERR, "dnstap collector: event_add failed");
+
+ dt_col->inputs[i].buffer = buffer_create(dt_col->region,
+ /* msglen + is_response + addrlen + is_tcp + packetlen + packet + zonelen + zone + spare + addr */
+ 4+1+4+1+4+TCP_MAX_MESSAGE_LEN+4+MAXHOSTNAMELEN + 32 +
+#ifdef INET6
+ sizeof(struct sockaddr_storage)
+#else
+ sizeof(struct sockaddr_in)
+#endif
+ );
+ assert(buffer_capacity(dt_col->inputs[i].buffer) ==
+ buffer_capacity(dt_col->send_buffer));
+ }
+}
+
+/* the dnstap collector process main routine */
+static void dt_collector_run(struct dt_collector* dt_col, struct nsd* nsd)
+{
+ /* init dnstap */
+ VERBOSITY(1, (LOG_INFO, "dnstap collector started"));
+ dt_init_dnstap(dt_col, nsd);
+ dt_attach_events(dt_col, nsd);
+
+ /* run */
+ if(event_base_loop(dt_col->event_base, 0) == -1) {
+ error("dnstap collector: event_base_loop failed");
+ }
+
+ /* cleanup and done */
+ VERBOSITY(1, (LOG_INFO, "dnstap collector stopped"));
+ dt_collector_cleanup(dt_col, nsd);
+ exit(0);
+}
+
+void dt_collector_start(struct dt_collector* dt_col, struct nsd* nsd)
+{
+ /* fork */
+ dt_col->dt_pid = fork();
+ if(dt_col->dt_pid == -1) {
+ error("dnstap_collector: fork failed: %s", strerror(errno));
+ }
+ if(dt_col->dt_pid == 0) {
+ /* the dt collector process is this */
+ /* close the nsd side of the command channel */
+ close(dt_col->cmd_socket_nsd);
+ dt_col->cmd_socket_nsd = -1;
+ dt_collector_run(dt_col, nsd);
+ /* NOTREACH */
+ exit(0);
+ } else {
+ /* the parent continues on, with starting NSD */
+ /* close the dt side of the command channel */
+ close(dt_col->cmd_socket_dt);
+ dt_col->cmd_socket_dt = -1;
+ }
+}
+
+/* put data for sending to the collector process into the buffer */
+static int
+prep_send_data(struct buffer* buf, uint8_t is_response,
+#ifdef INET6
+ struct sockaddr_storage* addr,
+#else
+ struct sockaddr_in* addr,
+#endif
+ socklen_t addrlen, int is_tcp, struct buffer* packet,
+ struct zone* zone)
+{
+ buffer_clear(buf);
+ if(!buffer_available(buf, 4+1+4+addrlen+1+4+buffer_remaining(packet)))
+ return 0; /* does not fit in send_buffer, log is dropped */
+ buffer_skip(buf, 4); /* the length of the message goes here */
+ buffer_write_u8(buf, is_response);
+ buffer_write_u32(buf, addrlen);
+ buffer_write(buf, addr, (size_t)addrlen);
+ buffer_write_u8(buf, (is_tcp?1:0));
+ buffer_write_u32(buf, buffer_remaining(packet));
+ buffer_write(buf, buffer_begin(packet), buffer_remaining(packet));
+ if(zone && zone->apex && domain_dname(zone->apex)) {
+ if(!buffer_available(buf, 4 + domain_dname(zone->apex)->name_size))
+ return 0;
+ buffer_write_u32(buf, domain_dname(zone->apex)->name_size);
+ buffer_write(buf, dname_name(domain_dname(zone->apex)),
+ domain_dname(zone->apex)->name_size);
+ } else {
+ if(!buffer_available(buf, 4))
+ return 0;
+ buffer_write_u32(buf, 0);
+ }
+
+ buffer_flip(buf);
+ /* write length of message */
+ buffer_write_u32_at(buf, 0, buffer_remaining(buf)-4);
+ 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)
+{
+ 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;
+ }
+ total += r;
+ }
+}
+
+void dt_collector_submit_auth_query(struct nsd* nsd,
+#ifdef INET6
+ struct sockaddr_storage* addr,
+#else
+ struct sockaddr_in* addr,
+#endif
+ socklen_t addrlen, int is_tcp, struct buffer* packet)
+{
+ if(!nsd->dt_collector) return;
+ if(!nsd->options->dnstap_log_auth_query_messages) return;
+ VERBOSITY(4, (LOG_INFO, "dnstap submit auth query"));
+
+ /* marshal data into send buffer */
+ if(!prep_send_data(nsd->dt_collector->send_buffer, 0, addr, addrlen,
+ is_tcp, packet, NULL))
+ 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));
+}
+
+void dt_collector_submit_auth_response(struct nsd* nsd,
+#ifdef INET6
+ struct sockaddr_storage* addr,
+#else
+ struct sockaddr_in* addr,
+#endif
+ socklen_t addrlen, int is_tcp, struct buffer* packet,
+ struct zone* zone)
+{
+ if(!nsd->dt_collector) return;
+ if(!nsd->options->dnstap_log_auth_response_messages) return;
+ VERBOSITY(4, (LOG_INFO, "dnstap submit auth response"));
+
+ /* marshal data into send buffer */
+ if(!prep_send_data(nsd->dt_collector->send_buffer, 1, addr, addrlen,
+ is_tcp, packet, zone))
+ 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));
+}
diff --git dnstap/dnstap_collector.h dnstap/dnstap_collector.h
new file mode 100644
index 00000000000..4e0825bbaea
--- /dev/null
+++ dnstap/dnstap_collector.h
@@ -0,0 +1,92 @@
+/*
+ * dnstap/dnstap_collector.h -- nsd collector process for dnstap information
+ *
+ * Copyright (c) 2018, NLnet Labs. All rights reserved.
+ *
+ * See LICENSE for the license.
+ *
+ */
+
+#ifndef DNSTAP_COLLECTOR_H
+#define DNSTAP_COLLECTOR_H
+struct dt_env;
+struct nsd;
+struct event_base;
+struct event;
+struct dt_collector_input;
+struct zone;
+struct buffer;
+struct region;
+
+/* information for the dnstap collector process. It collects information
+ * for dnstap from the worker processes.  And writes them to the dnstap
+ * socket. */
+struct dt_collector {
+ /* dnstap env for the write to the dnstap socket */
+ struct dt_env* dt_env;
+ /* number of workers to collect from */
+ int count;
+ /* socketpair for communication between (xfrd) and the
+ * dnstap collector process.  If closed, the collector process
+ * exits.  The collector closes the other side of the socketpair, so
+ * that if xfrd exits, so does the dnstap collector */
+ int cmd_socket_dt, cmd_socket_nsd;
+ /* the pid of the dt collector process (0 on that process) */
+ pid_t dt_pid;
+ /* in the collector process, the event base */
+ struct event_base* event_base;
+ /* in the collector process, the cmd handle event */
+ struct event* cmd_event;
+ /* in the collector process, array size count of input per worker */
+ struct dt_collector_input* inputs;
+ /* region for buffers */
+ struct region* region;
+ /* buffer for sending data to the collector */
+ struct buffer* send_buffer;
+};
+
+/* information per worker to get input from that worker. */
+struct dt_collector_input {
+ /* the collector this is part of (for use in callbacks) */
+ struct dt_collector* dt_collector;
+ /* the event to listen to the datagrams to process for that worker*/
+ struct event* event;
+ /* buffer to store the datagrams while they are read in */
+ struct buffer* buffer;
+};
+
+/* create dt_collector process structure and dt_env */
+struct dt_collector* dt_collector_create(struct nsd* nsd);
+/* destroy the dt_collector structure */
+void dt_collector_destroy(struct dt_collector* dt_col, struct nsd* nsd);
+/* close file descriptors */
+void dt_collector_close(struct dt_collector* dt_col, struct nsd* nsd);
+/* start the collector process */
+void dt_collector_start(struct dt_collector* dt_col, struct nsd* nsd);
+
+/* submit auth query from worker.  It attempts to send it to the collector,
+ * if the nonblocking fails, then it silently skips it.  So it does not block
+ * on the log.
+ */
+void dt_collector_submit_auth_query(struct nsd* nsd,
+#ifdef INET6
+ struct sockaddr_storage* addr,
+#else
+ struct sockaddr_in* addr,
+#endif
+ socklen_t addrlen, int is_tcp, struct buffer* packet);
+
+/* submit auth response from worker.  It attempts to send it to the collector,
+ * if the nonblocking fails, then it silently skips it.  So it does not block
+ * on the log.
+ */
+void dt_collector_submit_auth_response(struct nsd* nsd,
+#ifdef INET6
+ struct sockaddr_storage* addr,
+#else
+ struct sockaddr_in* addr,
+#endif
+ socklen_t addrlen, int is_tcp, struct buffer* packet,
+ struct zone* zone);
+
+#endif /* DNSTAP_COLLECTOR_H */
diff --git dnstap/dnstap_config.h.in dnstap/dnstap_config.h.in
new file mode 100644
index 00000000000..c9f74893a1d
--- /dev/null
+++ dnstap/dnstap_config.h.in
@@ -0,0 +1,17 @@
+#ifndef UNBOUND_DNSTAP_CONFIG_H
+#define UNBOUND_DNSTAP_CONFIG_H
+
+/*
+ * Process this file (dnstap_config.h.in) with AC_CONFIG_FILES to generate
+ * dnstap_config.h.
+ *
+ * This file exists so that USE_DNSTAP can be used without including config.h.
+ */
+
+#if @ENABLE_DNSTAP@ /* ENABLE_DNSTAP */
+# ifndef USE_DNSTAP
+#  define USE_DNSTAP 1
+# endif
+#endif
+
+#endif /* UNBOUND_DNSTAP_CONFIG_H */
diff --git ipc.c ipc.c
index 4da914d7ce8..46feb0a0129 100644
--- ipc.c
+++ ipc.c
@@ -43,7 +43,7 @@ ipc_child_quit(struct nsd* nsd)
 
 #ifdef MEMCLEAN /* OS collects memory pages */
 #ifdef RATELIMIT
-        rrl_deinit(nsd->this_child->child_num);
+ rrl_deinit(nsd->this_child->child_num);
 #endif
  event_base_free(nsd->event_base);
  region_destroy(nsd->server_region);
@@ -646,13 +646,13 @@ void
 xfrd_handle_ipc(int ATTR_UNUSED(fd), short event, void* arg)
 {
  xfrd_state_type* xfrd = (xfrd_state_type*)arg;
-        if ((event & EV_READ))
+ if ((event & EV_READ))
  {
  /* first attempt to read as a signal from main
  * could block further send operations */
  xfrd_handle_ipc_read(&xfrd->ipc_handler, xfrd);
  }
-        if ((event & EV_WRITE))
+ if ((event & EV_WRITE))
  {
  if(xfrd->ipc_send_blocked) { /* wait for RELOAD_DONE */
  ipc_xfrd_set_listening(xfrd, EV_PERSIST|EV_READ);
@@ -681,8 +681,8 @@ xfrd_handle_ipc(int ATTR_UNUSED(fd), short event, void* arg)
 static void
 xfrd_handle_ipc_read(struct event* handler, xfrd_state_type* xfrd)
 {
-        sig_atomic_t cmd;
-        int len;
+ sig_atomic_t cmd;
+ int len;
 
  if(xfrd->ipc_conn->is_reading==2) {
  buffer_type* tmp = xfrd->ipc_pass;
@@ -730,26 +730,26 @@ xfrd_handle_ipc_read(struct event* handler, xfrd_state_type* xfrd)
  return;
  }
 
-        if((len = read(handler->ev_fd, &cmd, sizeof(cmd))) == -1) {
+ if((len = read(handler->ev_fd, &cmd, sizeof(cmd))) == -1) {
  if(errno != EINTR && errno != EAGAIN)
-                 log_msg(LOG_ERR, "xfrd_handle_ipc: read: %s",
-                         strerror(errno));
-                return;
-        }
-        if(len == 0)
-        {
+ log_msg(LOG_ERR, "xfrd_handle_ipc: read: %s",
+ strerror(errno));
+ return;
+ }
+ if(len == 0)
+ {
  /* parent closed the connection. Quit */
  DEBUG(DEBUG_IPC,1, (LOG_INFO, "xfrd: main closed connection."));
  xfrd->shutdown = 1;
  return;
-        }
+ }
 
-        switch(cmd) {
-        case NSD_QUIT:
-        case NSD_SHUTDOWN:
+ switch(cmd) {
+ case NSD_QUIT:
+ case NSD_SHUTDOWN:
  DEBUG(DEBUG_IPC,1, (LOG_INFO, "xfrd: main sent shutdown cmd."));
-                xfrd->shutdown = 1;
-                break;
+ xfrd->shutdown = 1;
+ break;
  case NSD_RELOAD_DONE:
  /* reload has finished */
  DEBUG(DEBUG_IPC,1, (LOG_INFO, "xfrd: ipc recv RELOAD_DONE"));
@@ -785,11 +785,11 @@ xfrd_handle_ipc_read(struct event* handler, xfrd_state_type* xfrd)
  ipc_xfrd_set_listening(xfrd, EV_PERSIST|EV_READ|EV_WRITE);
  xfrd->need_to_send_quit = 1;
  break;
-        default:
-                log_msg(LOG_ERR, "xfrd_handle_ipc: bad mode %d (%d)", (int)cmd,
+ default:
+ log_msg(LOG_ERR, "xfrd_handle_ipc: bad mode %d (%d)", (int)cmd,
  (int)ntohl(cmd));
-                break;
-        }
+ break;
+ }
 
  if(xfrd->ipc_conn->is_reading) {
  /* setup read of info */
diff --git nsd-checkconf.8.in nsd-checkconf.8.in
index d9d7bd5342d..dccf95d690d 100644
--- nsd-checkconf.8.in
+++ nsd-checkconf.8.in
@@ -1,4 +1,4 @@
-.TH "nsd\-checkconf" "8" "Sep 25, 2018" "NLnet Labs" "nsd 4.1.25"
+.TH "nsd\-checkconf" "8" "Dec  4, 2018" "NLnet Labs" "nsd 4.1.26"
 .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
diff --git nsd-checkconf.c nsd-checkconf.c
index f4044c42295..20eb42a8cf1 100644
--- nsd-checkconf.c
+++ nsd-checkconf.c
@@ -404,6 +404,16 @@ config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o,
  SERV_GET_INT(rrl_ipv4_prefix_length, o);
  SERV_GET_INT(rrl_ipv6_prefix_length, o);
  SERV_GET_INT(rrl_whitelist_ratelimit, o);
+#endif
+#ifdef USE_DNSTAP
+ SERV_GET_BIN(dnstap_enable, o);
+ SERV_GET_STR(dnstap_socket_path, o);
+ SERV_GET_BIN(dnstap_send_identity, o);
+ SERV_GET_BIN(dnstap_send_version, o);
+ SERV_GET_STR(dnstap_identity, o);
+ SERV_GET_STR(dnstap_version, o);
+ SERV_GET_BIN(dnstap_log_auth_query_messages, o);
+ SERV_GET_BIN(dnstap_log_auth_response_messages, o);
 #endif
  SERV_GET_INT(zonefiles_write, o);
  /* remote control */
@@ -527,6 +537,18 @@ config_test_print_server(nsd_options_type* opt)
  printf("\tzonefiles-check: %s\n", opt->zonefiles_check?"yes":"no");
  printf("\tzonefiles-write: %d\n", opt->zonefiles_write);
 
+#ifdef USE_DNSTAP
+ printf("\ndnstap:\n");
+ printf("\tdnstap-enable: %s\n", opt->dnstap_enable?"yes":"no");
+ print_string_var("dnstap-socket-path:", opt->dnstap_socket_path);
+ printf("\tdnstap-send-identity: %s\n", opt->dnstap_send_identity?"yes":"no");
+ printf("\tdnstap-send-version: %s\n", opt->dnstap_send_version?"yes":"no");
+ print_string_var("dnstap-identity:", opt->dnstap_identity);
+ print_string_var("dnstap-version:", opt->dnstap_version);
+ printf("\tdnstap-log-auth-query-messages: %s\n", opt->dnstap_log_auth_query_messages?"yes":"no");
+ printf("\tdnstap-log-auth-response-messages: %s\n", opt->dnstap_log_auth_response_messages?"yes":"no");
+#endif
+
  printf("\nremote-control:\n");
  printf("\tcontrol-enable: %s\n", opt->control_enable?"yes":"no");
  for(ip = opt->control_interface; ip; ip=ip->next)
diff --git nsd-checkzone.8.in nsd-checkzone.8.in
index da43863aeb5..afadb4c9215 100644
--- nsd-checkzone.8.in
+++ nsd-checkzone.8.in
@@ -1,4 +1,4 @@
-.TH "nsd\-checkzone" "8" "Sep 25, 2018" "NLnet Labs" "nsd 4.1.25"
+.TH "nsd\-checkzone" "8" "Dec  4, 2018" "NLnet Labs" "nsd 4.1.26"
 .\" Copyright (c) 2014, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
diff --git nsd-control.8.in nsd-control.8.in
index cabaf3d72e9..40ca171c2c4 100644
--- nsd-control.8.in
+++ nsd-control.8.in
@@ -1,4 +1,4 @@
-.TH "nsd\-control" "8" "Sep 25, 2018" "NLnet Labs" "nsd 4.1.25"
+.TH "nsd\-control" "8" "Dec  4, 2018" "NLnet Labs" "nsd 4.1.26"
 .\" Copyright (c) 2011, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
@@ -49,7 +49,8 @@ loads it.
 .B reconfig
 Reload nsd.conf and apply changes to TSIG keys and configuration patterns,
 and apply the changes to add and remove zones that are mentioned in the config.
-Other changes are not applied, such as listening ip address and port and chroot.
+Other changes are not applied, such as listening ip address and port and chroot,
+also per-zone statistics are not applied.
 The pattern updates means that the configuration options for
 zones (request\-xfr, zonefile, notify, ...) are updated.  Also new
 patterns are available for use with the addzone command.
@@ -88,6 +89,12 @@ inside nsd.conf itself cannot be removed this way because the daemon
 does not write to the nsd.conf file, you need to add such zones to the
 zonelist file to be able to delete them with the delzone command.
 .TP
+.B changezone <zone name> <pattern name>
+Change a zone to use the pattern for options.  The zone is deleted and added
+in one operation, changing it to use the new pattern for the zone options.
+Zones configured in nsd.conf cannot be changed like this, instead edit
+the nsd.conf (or the included file in nsd.conf) and reconfig.
+.TP
 .B addzones
 Add zones read from stdin of nsd\-control.  Input is read per line,
 with name space patternname on a line.  For bulk additions.
diff --git nsd-control.c nsd-control.c
index f86a5f779fc..b83fc0c2fe3 100644
--- nsd-control.c
+++ nsd-control.c
@@ -91,6 +91,7 @@ usage()
  printf("  stats_noreset peek at statistics\n");
  printf("  addzone <name> <pattern> add a new zone\n");
  printf("  delzone <name> remove a zone\n");
+ printf("  changezone <name> <pattern> change zone to use pattern\n");
  printf("  addzones add zone list on stdin {name space pattern newline}\n");
  printf("  delzones remove zone list on stdin {name newline}\n");
  printf("  write [<zone>] write changed zonefiles to disk\n");
diff --git nsd.8.in nsd.8.in
index 46425aea47a..afb19cbfe66 100644
--- nsd.8.in
+++ nsd.8.in
@@ -1,9 +1,9 @@
-.TH "NSD" "8" "Sep 25, 2018" "NLnet Labs" "NSD 4.1.25"
+.TH "NSD" "8" "Dec  4, 2018" "NLnet Labs" "NSD 4.1.26"
 .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
 .B nsd
-\- Name Server Daemon (NSD) version 4.1.25.
+\- Name Server Daemon (NSD) version 4.1.26.
 .SH "SYNOPSIS"
 .B nsd
 .RB [ \-4 ]
diff --git nsd.c nsd.c
index b3d499b8c9a..57bae3232d7 100644
--- nsd.c
+++ nsd.c
@@ -45,6 +45,9 @@
 #include "tsig.h"
 #include "remote.h"
 #include "xfrd-disk.h"
+#ifdef USE_DNSTAP
+#include "dnstap/dnstap_collector.h"
+#endif
 
 /* The server handler... */
 struct nsd nsd;
@@ -1102,6 +1105,12 @@ main(int argc, char *argv[])
  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);
diff --git nsd.conf.5.in nsd.conf.5.in
index 9aaddeead28..348fcda8fe3 100644
--- nsd.conf.5.in
+++ nsd.conf.5.in
@@ -1,4 +1,4 @@
-.TH "nsd.conf" "5" "Sep 25, 2018" "NLnet Labs" "nsd 4.1.25"
+.TH "nsd.conf" "5" "Dec  4, 2018" "NLnet Labs" "nsd 4.1.26"
 .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
 .\" See LICENSE for the license.
 .SH "NAME"
diff --git nsd.conf.sample.in nsd.conf.sample.in
index f5cd7b1da9a..bd3a6e89cab 100644
--- nsd.conf.sample.in
+++ nsd.conf.sample.in
@@ -177,6 +177,18 @@ server:
  # rrl-whitelist-ratelimit: 2000
  # RRLend
 
+# DNSTAP config section, if compiled with that
+# dnstap:
+ # set this to yes and set one or more of dnstap-log-..-messages to yes.
+ # dnstap-enable: no
+ # dnstap-socket-path: "/var/run/dnstap.sock"
+ # dnstap-send-identity: no
+ # dnstap-send-version: no
+ # dnstap-identity: ""
+ # dnstap-version: ""
+ # dnstap-log-auth-query-messages: no
+ # dnstap-log-auth-response-messages: no
+
 # Remote control config section.
 remote-control:
  # Enable remote control with nsd-control(8) here.
diff --git nsd.h nsd.h
index 903dc814282..c900ca6cbaa 100644
--- nsd.h
+++ nsd.h
@@ -18,6 +18,9 @@ struct netio_handler;
 struct nsd_options;
 struct udb_base;
 struct daemon_remote;
+#ifdef USE_DNSTAP
+struct dt_collector;
+#endif
 
 /* The NSD runtime states and NSD ipc command values */
 #define NSD_RUN 0
@@ -260,6 +263,13 @@ struct nsd
  /* current zonestat array to use */
  struct nsdst* zonestatnow;
 #endif /* BIND8_STATS */
+#ifdef USE_DNSTAP
+ /* 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. */
+ int *dt_collector_fd_send, *dt_collector_fd_recv;
+#endif /* USE_DNSTAP */
  /* ratelimit for errors, time value */
  time_t err_limit_time;
  /* ratelimit for errors, packet count */
diff --git options.c options.c
index d9028c7305d..d28a2c45dbb 100644
--- options.c
+++ options.c
@@ -97,6 +97,16 @@ nsd_options_create(region_type* region)
  opt->rrl_ratelimit = RRL_LIMIT/2;
  opt->rrl_whitelist_ratelimit = RRL_WLIST_LIMIT/2;
 #  endif
+#endif
+#ifdef USE_DNSTAP
+ opt->dnstap_enable = 0;
+ opt->dnstap_socket_path = DNSTAP_SOCKET_PATH;
+ opt->dnstap_send_identity = 0;
+ opt->dnstap_send_version = 0;
+ opt->dnstap_identity = NULL;
+ opt->dnstap_version = NULL;
+ opt->dnstap_log_auth_query_messages = 0;
+ opt->dnstap_log_auth_response_messages = 0;
 #endif
  opt->zonefiles_check = 1;
  if(opt->database == NULL || opt->database[0] == 0)
diff --git options.h options.h
index 3b1ad62b9db..a83eb383e47 100644
--- options.h
+++ options.h
@@ -125,6 +125,22 @@ struct nsd_options {
  /** max qps for whitelisted queries, 0 is nolimit */
  size_t rrl_whitelist_ratelimit;
 #endif
+ /** if dnstap is enabled */
+ int dnstap_enable;
+ /** dnstap socket path */
+ char* dnstap_socket_path;
+ /** true to send "identity" via dnstap */
+ int dnstap_send_identity;
+ /** true to send "version" via dnstap */
+ int dnstap_send_version;
+ /** dnstap "identity", hostname is used if "". */
+ char* dnstap_identity;
+ /** dnstap "version", package version is used if "". */
+ char* dnstap_version;
+ /** true to log dnstap AUTH_QUERY message events */
+ int dnstap_log_auth_query_messages;
+ /** true to log dnstap AUTH_RESPONSE message events */
+ int dnstap_log_auth_response_messages;
 
  region_type* region;
 };
diff --git remote.c remote.c
index e218ba4cc64..5cc36489d37 100644
--- remote.c
+++ remote.c
@@ -1250,6 +1250,91 @@ zonestat_inc_ifneeded(xfrd_state_type* xfrd)
 #endif /* USE_ZONE_STATS */
 }
 
+/** perform the changezone command for one zone */
+static int
+perform_changezone(RES* ssl, xfrd_state_type* xfrd, char* arg)
+{
+ const dname_type* dname;
+ struct zone_options* zopt;
+ char* arg2 = NULL;
+ if(!find_arg2(ssl, arg, &arg2))
+ return 0;
+
+ /* if we add it to the xfrd now, then xfrd could download AXFR and
+ * store it and the NSD-reload would see it in the difffile before
+ * it sees the add-config task.
+ */
+ /* thus: AXFRs and IXFRs must store the pattern name in the
+ * difffile, so that it can be added when the AXFR or IXFR is seen.
+ */
+
+ /* check that the pattern exists */
+ if(!rbtree_search(xfrd->nsd->options->patterns, arg2)) {
+ (void)ssl_printf(ssl, "error pattern %s does not exist\n",
+ arg2);
+ return 0;
+ }
+
+ dname = dname_parse(xfrd->region, arg);
+ if(!dname) {
+ (void)ssl_printf(ssl, "error cannot parse zone name\n");
+ return 0;
+ }
+
+ /* see if zone is a duplicate */
+ if( (zopt=zone_options_find(xfrd->nsd->options, dname)) ) {
+ if(zopt->part_of_config) {
+ (void)ssl_printf(ssl, "error zone defined in nsd.conf, "
+  "cannot delete it in this manner: remove it from "
+  "nsd.conf yourself and repattern\n");
+ region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
+ dname = NULL;
+ return 0;
+ }
+ /* found the zone, now delete it */
+ /* create deletion task */
+ /* this deletion task is processed before the addition task,
+ * that is created below, in the same reload process, causing
+ * a seamless change from one to the other, with no downtime
+ * for the zone. */
+ task_new_del_zone(xfrd->nsd->task[xfrd->nsd->mytask],
+ xfrd->last_task, dname);
+ xfrd_set_reload_now(xfrd);
+ /* delete it in xfrd */
+ if(zone_is_slave(zopt)) {
+ xfrd_del_slave_zone(xfrd, dname);
+ }
+ xfrd_del_notify(xfrd, dname);
+ /* delete from config */
+ zone_list_del(xfrd->nsd->options, zopt);
+ } else {
+ (void)ssl_printf(ssl, "zone %s did not exist, creating", arg);
+ }
+ region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
+ dname = NULL;
+
+ /* add to zonelist and adds to config in memory */
+ zopt = zone_list_add(xfrd->nsd->options, arg, arg2);
+ if(!zopt) {
+ /* also dname parse error here */
+ (void)ssl_printf(ssl, "error could not add zonelist entry\n");
+ return 0;
+ }
+ /* make addzone task and schedule reload */
+ task_new_add_zone(xfrd->nsd->task[xfrd->nsd->mytask],
+ xfrd->last_task, arg, arg2,
+ getzonestatid(xfrd->nsd->options, zopt));
+ zonestat_inc_ifneeded(xfrd);
+ xfrd_set_reload_now(xfrd);
+ /* add to xfrd - notify (for master and slaves) */
+ init_notify_send(xfrd->notify_zones, xfrd->region, zopt);
+ /* add to xfrd - slave */
+ if(zone_is_slave(zopt)) {
+ xfrd_init_slave_zone(xfrd, zopt);
+ }
+ return 1;
+}
+
 /** perform the addzone command for one zone */
 static int
 perform_addzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
@@ -1334,7 +1419,7 @@ perform_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
  /* nothing to do */
  if(!ssl_printf(ssl, "warning zone %s not present\n", arg))
  return 0;
- return 1;
+ return 0;
  }
 
  /* see if it can be deleted */
@@ -1381,6 +1466,15 @@ do_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
  send_ok(ssl);
 }
 
+/** do the changezone command */
+static void
+do_changezone(RES* ssl, xfrd_state_type* xfrd, char* arg)
+{
+ if(!perform_changezone(ssl, xfrd, arg))
+ return;
+ send_ok(ssl);
+}
+
 /** do the addzones command */
 static void
 do_addzones(RES* ssl, xfrd_state_type* xfrd)
@@ -1867,6 +1961,8 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd, struct rc_state* rs)
  do_addzone(ssl, rc->xfrd, skipwhite(p+7));
  } else if(cmdcmp(p, "delzone", 7)) {
  do_delzone(ssl, rc->xfrd, skipwhite(p+7));
+ } else if(cmdcmp(p, "changezone", 10)) {
+ do_changezone(ssl, rc->xfrd, skipwhite(p+10));
  } else if(cmdcmp(p, "addzones", 8)) {
  do_addzones(ssl, rc->xfrd);
  } else if(cmdcmp(p, "delzones", 8)) {
diff --git server.c server.c
index af2f60f243a..edde352117b 100644
--- server.c
+++ server.c
@@ -65,6 +65,9 @@
 #include "remote.h"
 #include "lookup3.h"
 #include "rrl.h"
+#ifdef USE_DNSTAP
+#include "dnstap/dnstap_collector.h"
+#endif
 
 #define RELOAD_SYNC_TIMEOUT 25 /* seconds */
 
@@ -599,6 +602,26 @@ server_init_ifs(struct nsd *nsd, size_t from, size_t to, int* reuseport_works)
  }
 
 #ifdef SO_REUSEPORT
+#  ifdef SO_REUSEPORT_LB
+ /* on FreeBSD 12 we have SO_REUSEPORT_LB that does loadbalance
+ * like SO_REUSEPORT on Linux.  This is what the users want
+ * with the config option in nsd.conf; if we actually
+ * need local address and port reuse they'll also need to
+ * have SO_REUSEPORT set for them, assume it was _LB they want.
+ */
+ if(nsd->reuseport && *reuseport_works &&
+ setsockopt(nsd->udp[i].s, SOL_SOCKET, SO_REUSEPORT_LB,
+ (void*)&on, (socklen_t)sizeof(on)) < 0) {
+ if(verbosity >= 3
+#ifdef ENOPROTOOPT
+ || errno != ENOPROTOOPT
+#endif
+ )
+    log_msg(LOG_ERR, "setsockopt(..., SO_REUSEPORT_LB, "
+ "...) failed: %s", strerror(errno));
+ *reuseport_works = 0;
+ }
+#  else /* SO_REUSEPORT_LB */
  if(nsd->reuseport && *reuseport_works &&
  setsockopt(nsd->udp[i].s, SOL_SOCKET, SO_REUSEPORT,
  (void*)&on, (socklen_t)sizeof(on)) < 0) {
@@ -611,6 +634,7 @@ server_init_ifs(struct nsd *nsd, size_t from, size_t to, int* reuseport_works)
  "...) failed: %s", strerror(errno));
  *reuseport_works = 0;
  }
+#  endif /* SO_REUSEPORT_LB */
 #else
  (void)reuseport_works;
 #endif /* SO_REUSEPORT */
@@ -1088,6 +1112,9 @@ server_shutdown(struct nsd *nsd)
 #ifdef MEMCLEAN /* OS collects memory pages */
 #ifdef RATELIMIT
  rrl_mmap_deinit_keep_mmap();
+#endif
+#ifdef USE_DNSTAP
+ dt_collector_destroy(nsd->dt_collector, nsd);
 #endif
  udb_base_free_keep_mmap(nsd->task[0]);
  udb_base_free_keep_mmap(nsd->task[1]);
@@ -1903,6 +1930,9 @@ server_main(struct nsd *nsd)
  unlink(nsd->zonestatfname[0]);
  unlink(nsd->zonestatfname[1]);
 #endif
+#ifdef USE_DNSTAP
+ dt_collector_close(nsd->dt_collector, nsd);
+#endif
 
  if(reload_listener.fd != -1) {
  sig_atomic_t cmd = NSD_QUIT;
@@ -2215,6 +2245,7 @@ handle_udp(int fd, short event, void* arg)
  for (i = 0; i < recvcount; i++) {
  loopstart:
  received = msgs[i].msg_len;
+ queries[i]->addrlen = msgs[i].msg_hdr.msg_namelen;
  q = queries[i];
  if (received == -1) {
  log_msg(LOG_ERR, "recvmmsg %d failed %s", i, strerror(
@@ -2223,6 +2254,7 @@ handle_udp(int fd, short event, void* arg)
  /* No zone statup */
  query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
  iovecs[i].iov_len = buffer_remaining(q->packet);
+ msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
  goto swap_drop;
  }
 
@@ -2237,6 +2269,10 @@ handle_udp(int fd, short event, void* arg)
 
  buffer_skip(q->packet, received);
  buffer_flip(q->packet);
+#ifdef USE_DNSTAP
+ dt_collector_submit_auth_query(data->nsd, &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) {
@@ -2267,9 +2303,15 @@ handle_udp(int fd, short event, void* arg)
  ZTATUP(data->nsd, q->zone, truncated);
  }
 #endif /* BIND8_STATS */
+#ifdef USE_DNSTAP
+ dt_collector_submit_auth_response(data->nsd,
+ &q->addr, q->addrlen, q->tcp, q->packet,
+ q->zone);
+#endif /* USE_DNSTAP */
  } else {
  query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
  iovecs[i].iov_len = buffer_remaining(q->packet);
+ msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
  swap_drop:
  STATUP(data->nsd, dropped);
  ZTATUP(data->nsd, q->zone, dropped);
@@ -2310,6 +2352,7 @@ handle_udp(int fd, short event, void* arg)
  for(i=0; i<recvcount; i++) {
  query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
  iovecs[i].iov_len = buffer_remaining(queries[i]->packet);
+ msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
  }
 }
 
@@ -2350,13 +2393,15 @@ handle_udp(int fd, short event, void* arg)
  }
  for (i = 0; i < recvcount; i++) {
  received = msgs[i].msg_len;
- msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
+ queries[i]->addrlen = msgs[i].msg_hdr.msg_namelen;
  if (received == -1) {
  log_msg(LOG_ERR, "recvmmsg failed");
  STATUP(data->nsd, rxerr);
  /* No zone statup */
  /* the error can be found in msgs[i].msg_hdr.msg_flags */
  query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
+ iovecs[i].iov_len = buffer_remaining(queries[i]->packet);
+ msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
  continue;
  }
  q = queries[i];
@@ -2394,6 +2439,10 @@ handle_udp(int fd, short event, void* arg)
 
  buffer_skip(q->packet, received);
  buffer_flip(q->packet);
+#ifdef USE_DNSTAP
+ dt_collector_submit_auth_query(data->nsd, &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) {
@@ -2440,6 +2489,11 @@ handle_udp(int fd, short event, void* arg)
  ZTATUP(data->nsd, q->zone, truncated);
  }
 #endif /* BIND8_STATS */
+#ifdef USE_DNSTAP
+ dt_collector_submit_auth_response(data->nsd,
+ &q->addr, q->addrlen, q->tcp,
+ q->packet, q->zone);
+#endif /* USE_DNSTAP */
  }
  } else {
  STATUP(data->nsd, dropped);
@@ -2448,6 +2502,8 @@ handle_udp(int fd, short event, void* arg)
 #ifndef NONBLOCKING_IS_BROKEN
 #ifdef HAVE_RECVMMSG
  query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
+ iovecs[i].iov_len = buffer_remaining(queries[i]->packet);
+ msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
 #endif
  }
 #endif
@@ -2633,6 +2689,10 @@ handle_tcp_reading(int fd, short event, void* arg)
  data->query_count++;
 
  buffer_flip(data->query->packet);
+#ifdef USE_DNSTAP
+ dt_collector_submit_auth_query(data->nsd, &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);
  if (data->query_state == QUERY_DISCARDED) {
  /* Drop the packet and the entire connection... */
@@ -2668,6 +2728,11 @@ handle_tcp_reading(int fd, short event, void* arg)
  /* Switch to the tcp write handler.  */
  buffer_flip(data->query->packet);
  data->query->tcplen = buffer_remaining(data->query->packet);
+#ifdef USE_DNSTAP
+ dt_collector_submit_auth_response(data->nsd, &data->query->addr,
+ data->query->addrlen, data->query->tcp, data->query->packet,
+ data->query->zone);
+#endif /* USE_DNSTAP */
  data->bytes_transmitted = 0;
 
  timeout.tv_sec = data->tcp_timeout / 1000;
diff --git xfrd.c xfrd.c
index a23001fbc5f..c2eeff8a1e8 100644
--- xfrd.c
+++ xfrd.c
@@ -30,6 +30,9 @@
 #include "ipc.h"
 #include "remote.h"
 #include "rrl.h"
+#ifdef USE_DNSTAP
+#include "dnstap/dnstap_collector.h"
+#endif
 
 #ifdef HAVE_SYSTEMD
 #include <systemd/sd-daemon.h>
@@ -398,6 +401,9 @@ xfrd_shutdown()
 #ifdef HAVE_SSL
  daemon_remote_delete(xfrd->nsd->rc); /* ssl-delete secret keys */
 #endif
+#ifdef USE_DNSTAP
+ dt_collector_close(nsd.dt_collector, &nsd);
+#endif
 
  /* process-exit cleans up memory used by xfrd process */
  DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd shutdown complete"));
@@ -429,6 +435,9 @@ xfrd_shutdown()
  }
 #ifdef RATELIMIT
  rrl_mmap_deinit();
+#endif
+#ifdef USE_DNSTAP
+ dt_collector_destroy(nsd.dt_collector, &nsd);
 #endif
  udb_base_free(nsd.task[0]);
  udb_base_free(nsd.task[1]);
diff --git zparser.y zparser.y
index c301c9eda78..d19387d91a5 100644
--- zparser.y
+++ zparser.y
@@ -298,25 +298,57 @@ rel_dname: label
 
 wire_dname: wire_abs_dname
     | wire_rel_dname
+    {
+    /* terminate in root label and copy the origin in there */
+    if(parser->origin && domain_dname(parser->origin)) {
+    $$.len = $1.len + domain_dname(parser->origin)->name_size;
+    if ($$.len > MAXDOMAINLEN)
+    zc_error("domain name exceeds %d character limit",
+     MAXDOMAINLEN);
+    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
+    memmove($$.str, $1.str, $1.len);
+    memmove($$.str + $1.len, dname_name(domain_dname(parser->origin)),
+ domain_dname(parser->origin)->name_size);
+    } else {
+    $$.len = $1.len + 1;
+    if ($$.len > MAXDOMAINLEN)
+    zc_error("domain name exceeds %d character limit",
+     MAXDOMAINLEN);
+    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
+    memmove($$.str, $1.str, $1.len);
+    $$.str[ $1.len ] = 0;
+    }
+    }
     ;
 
 wire_abs_dname: '.'
     {
-    char *result = (char *) region_alloc(parser->rr_region, 2);
+    char *result = (char *) region_alloc(parser->rr_region, 1);
     result[0] = 0;
-    result[1] = '\0';
     $$.str = result;
     $$.len = 1;
     }
+    | '@'
+    {
+    if(parser->origin && domain_dname(parser->origin)) {
+    $$.len = domain_dname(parser->origin)->name_size;
+    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
+    memmove($$.str, dname_name(domain_dname(parser->origin)), $$.len);
+    } else {
+    $$.len = 1;
+    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
+    $$.str[0] = 0;
+    }
+    }
     | wire_rel_dname '.'
     {
-    char *result = (char *) region_alloc(parser->rr_region,
- $1.len + 2);
-    memcpy(result, $1.str, $1.len);
-    result[$1.len] = 0;
-    result[$1.len+1] = '\0';
-    $$.str = result;
     $$.len = $1.len + 1;
+    if ($$.len > MAXDOMAINLEN)
+    zc_error("domain name exceeds %d character limit",
+     MAXDOMAINLEN);
+    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
+    memcpy($$.str, $1.str, $1.len);
+    $$.str[$1.len] = 0;
     }
     ;
 
@@ -330,7 +362,7 @@ wire_label: STR
 
     /* make label anyway */
     result[0] = $1.len;
-    memcpy(result+1, $1.str, $1.len);
+    memmove(result+1, $1.str, $1.len);
 
     $$.str = result;
     $$.len = $1.len + 1;
@@ -340,16 +372,13 @@ wire_label: STR
 wire_rel_dname: wire_label
     | wire_rel_dname '.' wire_label
     {
-    if ($1.len + $3.len - 3 > MAXDOMAINLEN)
+    $$.len = $1.len + $3.len;
+    if ($$.len > MAXDOMAINLEN)
     zc_error("domain name exceeds %d character limit",
      MAXDOMAINLEN);
-
-    /* make dname anyway */
-    $$.len = $1.len + $3.len;
-    $$.str = (char *) region_alloc(parser->rr_region, $$.len + 1);
-    memcpy($$.str, $1.str, $1.len);
-    memcpy($$.str + $1.len, $3.str, $3.len);
-    $$.str[$$.len] = '\0';
+    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
+    memmove($$.str, $1.str, $1.len);
+    memmove($$.str + $1.len, $3.str, $3.len);
     }
     ;
 


--
I'm not entirely sure you are real.

Reply | Threaded
Open this post in threaded view
|

Re: nsd 4.1.26

Florian Obser-2
*prod*

On Thu, Dec 06, 2018 at 11:02:01AM +0100, Florian Obser wrote:

> tests, OKs?
>
> diff --git Makefile.in Makefile.in
> index 16d193f766d..fbfc44be33b 100644
> --- Makefile.in
> +++ Makefile.in
> @@ -29,6 +29,8 @@ nsdconfigfile = @nsd_conf_file@
>  zonesdir = @zonesdir@
>  chrootdir= @chrootdir@
>  user = @user@
> +DNSTAP_SRC=@DNSTAP_SRC@
> +DNSTAP_OBJ=@DNSTAP_OBJ@
>  
>  # override $U variable which is used by autotools for deansification (for
>  # K&R C compilers), but causes problems if $U is defined in the env).
> @@ -47,6 +49,7 @@ INSTALL_DATA = $(INSTALL) -m 644
>  
>  YACC = @YACC@
>  LEX = @LEX@
> +PROTOC_C = @PROTOC_C@
>  
>  COMPILE = $(CC) $(CPPFLAGS) $(CFLAGS)
>  LINK = $(CC) $(CFLAGS) $(LDFLAGS)
> @@ -72,7 +75,7 @@ TARGETS=nsd nsd-checkconf nsd-checkzone nsd-control nsd.conf.sample nsd-control-
>  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
> -XFRD_OBJ=xfrd-disk.o xfrd-notify.o xfrd-tcp.o xfrd.o remote.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
>  NSD_CHECKCONF_OBJ=$(COMMON_OBJ) nsd-checkconf.o
> @@ -306,6 +309,22 @@ configlexer.c: $(srcdir)/configlexer.lex
>  configparser.c configparser.h: $(srcdir)/configparser.y
>   $(YACC) -d -o configparser.c $(srcdir)/configparser.y
>  
> +# dnstap
> +dnstap.o: $(srcdir)/dnstap/dnstap.c config.h \
> + dnstap/dnstap.pb-c.c dnstap/dnstap.pb-c.h $(srcdir)/dnstap/dnstap.h \
> + $(srcdir)/util.h $(srcdir)/options.h $(srcdir)/rbtree.h \
> + $(srcdir)/region-allocator.h
> +dnstap.pb-c.o: dnstap/dnstap.pb-c.c dnstap/dnstap.pb-c.h
> +dnstap_collector.o: $(srcdir)/dnstap/dnstap_collector.c config.h \
> + $(srcdir)/dnstap/dnstap.h $(srcdir)/dnstap/dnstap_collector.h \
> + $(srcdir)/util.h $(srcdir)/nsd.h $(srcdir)/region-allocator.h \
> + $(srcdir)/buffer.h $(srcdir)/namedb.h $(srcdir)/dname.h \
> + $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \
> + $(srcdir)/options.h
> +dnstap/dnstap.pb-c.c dnstap/dnstap.pb-c.h: $(srcdir)/dnstap/dnstap.proto
> + @-if test ! -d dnstap; then $(INSTALL) -d dnstap; fi
> + $(PROTOC_C) --c_out=. --proto_path=$(srcdir) $(srcdir)/dnstap/dnstap.proto
> +
>  # autoconf rules
>  config.h.in: configure.ac
>   autoheader
> diff --git config.h.in config.h.in
> index 4d47f603062..67296ca99b7 100644
> --- config.h.in
> +++ config.h.in
> @@ -22,6 +22,9 @@
>  /* Pathname to the NSD database */
>  #undef DBFILE
>  
> +/* default dnstap socket path */
> +#undef DNSTAP_SOCKET_PATH
> +
>  /* Define to the default maximum message length with EDNS. */
>  #undef EDNS_MAX_MESSAGE_LEN
>  
> @@ -510,6 +513,9 @@
>  /* the user name to drop privileges to */
>  #undef USER
>  
> +/* Define to 1 to enable dnstap support */
> +#undef USE_DNSTAP
> +
>  /* Define if you want to use internal select based events */
>  #undef USE_MINI_EVENT
>  
> diff --git configlexer.lex configlexer.lex
> index 7fd4f17363f..ead1b96fa80 100644
> --- configlexer.lex
> +++ configlexer.lex
> @@ -117,9 +117,8 @@ static void config_start_include_glob(const char* filename)
>  #ifdef GLOB_ERR
>   | GLOB_ERR
>  #endif
> -#ifdef GLOB_NOSORT
> - | GLOB_NOSORT
> -#endif
> + /* do not set GLOB_NOSORT so the results are sorted
> +    and in a predictable order. */
>  #ifdef GLOB_BRACE
>   | GLOB_BRACE
>  #endif
> @@ -270,6 +269,15 @@ rrl-whitelist-ratelimit{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_RRL_WHIT
>  rrl-whitelist{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_RRL_WHITELIST;}
>  zonefiles-check{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ZONEFILES_CHECK;}
>  zonefiles-write{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ZONEFILES_WRITE;}
> +dnstap{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP;}
> +dnstap-enable{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_ENABLE;}
> +dnstap-socket-path{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_SOCKET_PATH; }
> +dnstap-send-identity{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_SEND_IDENTITY; }
> +dnstap-send-version{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_SEND_VERSION; }
> +dnstap-identity{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_IDENTITY; }
> +dnstap-version{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_VERSION; }
> +dnstap-log-auth-query-messages{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_LOG_AUTH_QUERY_MESSAGES; }
> +dnstap-log-auth-response-messages{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_LOG_AUTH_RESPONSE_MESSAGES; }
>  log-time-ascii{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_LOG_TIME_ASCII;}
>  round-robin{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ROUND_ROBIN;}
>  minimal-responses{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MINIMAL_RESPONSES;}
> diff --git configparser.y configparser.y
> index 567641ce706..1e4d75e9a47 100644
> --- configparser.y
> +++ configparser.y
> @@ -72,13 +72,16 @@ extern config_parser_state_type* cfg_parser;
>  %token VAR_MAX_REFRESH_TIME VAR_MIN_REFRESH_TIME
>  %token VAR_MAX_RETRY_TIME VAR_MIN_RETRY_TIME
>  %token VAR_MULTI_MASTER_CHECK VAR_MINIMAL_RESPONSES VAR_REFUSE_ANY
> -%token VAR_USE_SYSTEMD
> +%token VAR_USE_SYSTEMD VAR_DNSTAP VAR_DNSTAP_ENABLE VAR_DNSTAP_SOCKET_PATH
> +%token VAR_DNSTAP_SEND_IDENTITY VAR_DNSTAP_SEND_VERSION VAR_DNSTAP_IDENTITY
> +%token VAR_DNSTAP_VERSION VAR_DNSTAP_LOG_AUTH_QUERY_MESSAGES
> +%token VAR_DNSTAP_LOG_AUTH_RESPONSE_MESSAGES
>  
>  %%
>  toplevelvars: /* empty */ | toplevelvars toplevelvar ;
>  toplevelvar: serverstart contents_server | zonestart contents_zone |
>   keystart contents_key | patternstart contents_pattern |
> - rcstart contents_rc;
> + rcstart contents_rc | dtstart contents_dt;
>  
>  /* server: declaration */
>  serverstart: VAR_SERVER
> @@ -596,6 +599,79 @@ rc_control_cert_file: VAR_CONTROL_CERT_FILE STRING
>   }
>   ;
>  
> +/* dnstap: declaration */
> +dtstart: VAR_DNSTAP
> + {
> + OUTYY(("\nP(dnstap:)\n"));
> + }
> + ;
> +contents_dt: contents_dt content_dt
> + | ;
> +content_dt: dt_dnstap_enable | dt_dnstap_socket_path |
> + dt_dnstap_send_identity | dt_dnstap_send_version |
> + dt_dnstap_identity | dt_dnstap_version |
> + dt_dnstap_log_auth_query_messages |
> + dt_dnstap_log_auth_response_messages
> + ;
> +dt_dnstap_enable: VAR_DNSTAP_ENABLE STRING
> + {
> + OUTYY(("P(dt_dnstap_enable:%s)\n", $2));
> + if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
> + yyerror("expected yes or no.");
> + else cfg_parser->opt->dnstap_enable = (strcmp($2, "yes")==0);
> + }
> + ;
> +dt_dnstap_socket_path: VAR_DNSTAP_SOCKET_PATH STRING
> + {
> + OUTYY(("P(dt_dnstap_socket_path:%s)\n", $2));
> + cfg_parser->opt->dnstap_socket_path = region_strdup(cfg_parser->opt->region, $2);
> + }
> + ;
> +dt_dnstap_send_identity: VAR_DNSTAP_SEND_IDENTITY STRING
> + {
> + OUTYY(("P(dt_dnstap_send_identity:%s)\n", $2));
> + if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
> + yyerror("expected yes or no.");
> + else cfg_parser->opt->dnstap_send_identity = (strcmp($2, "yes")==0);
> + }
> + ;
> +dt_dnstap_send_version: VAR_DNSTAP_SEND_VERSION STRING
> + {
> + OUTYY(("P(dt_dnstap_send_version:%s)\n", $2));
> + if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
> + yyerror("expected yes or no.");
> + else cfg_parser->opt->dnstap_send_version = (strcmp($2, "yes")==0);
> + }
> + ;
> +dt_dnstap_identity: VAR_DNSTAP_IDENTITY STRING
> + {
> + OUTYY(("P(dt_dnstap_identity:%s)\n", $2));
> + cfg_parser->opt->dnstap_identity = region_strdup(cfg_parser->opt->region, $2);
> + }
> + ;
> +dt_dnstap_version: VAR_DNSTAP_VERSION STRING
> + {
> + OUTYY(("P(dt_dnstap_version:%s)\n", $2));
> + cfg_parser->opt->dnstap_version = region_strdup(cfg_parser->opt->region, $2);
> + }
> + ;
> +dt_dnstap_log_auth_query_messages: VAR_DNSTAP_LOG_AUTH_QUERY_MESSAGES STRING
> + {
> + OUTYY(("P(dt_dnstap_log_auth_query_messages:%s)\n", $2));
> + if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
> + yyerror("expected yes or no.");
> + else cfg_parser->opt->dnstap_log_auth_query_messages = (strcmp($2, "yes")==0);
> + }
> + ;
> +dt_dnstap_log_auth_response_messages: VAR_DNSTAP_LOG_AUTH_RESPONSE_MESSAGES STRING
> + {
> + OUTYY(("P(dt_dnstap_log_auth_response_messages:%s)\n", $2));
> + if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
> + yyerror("expected yes or no.");
> + else cfg_parser->opt->dnstap_log_auth_response_messages = (strcmp($2, "yes")==0);
> + }
> + ;
> +
>  /* pattern: declaration */
>  patternstart: VAR_PATTERN
>   {
> diff --git configure configure
> index a4b87938db6..47736231022 100644
> --- configure
> +++ configure
> @@ -1,6 +1,6 @@
>  #! /bin/sh
>  # Guess values for system-dependent variables and create Makefiles.
> -# Generated by GNU Autoconf 2.69 for NSD 4.1.25.
> +# Generated by GNU Autoconf 2.69 for NSD 4.1.26.
>  #
>  # Report bugs to <[hidden email]>.
>  #
> @@ -580,8 +580,8 @@ MAKEFLAGS=
>  # Identity of this package.
>  PACKAGE_NAME='NSD'
>  PACKAGE_TARNAME='nsd'
> -PACKAGE_VERSION='4.1.25'
> -PACKAGE_STRING='NSD 4.1.25'
> +PACKAGE_VERSION='4.1.26'
> +PACKAGE_STRING='NSD 4.1.26'
>  PACKAGE_BUGREPORT='[hidden email]'
>  PACKAGE_URL=''
>  
> @@ -622,6 +622,11 @@ ac_includes_default="\
>  #endif"
>  
>  ac_subst_vars='LTLIBOBJS
> +DNSTAP_OBJ
> +DNSTAP_SRC
> +opt_dnstap_socket_path
> +ENABLE_DNSTAP
> +PROTOC_C
>  SSL_LIBS
>  HAVE_SSL
>  ratelimit_default
> @@ -734,6 +739,10 @@ enable_minimal_responses
>  enable_mmap
>  enable_radix_tree
>  enable_packed
> +enable_dnstap
> +with_dnstap_socket_path
> +with_protobuf_c
> +with_libfstrm
>  enable_systemd
>  '
>        ac_precious_vars='build_alias
> @@ -1287,7 +1296,7 @@ if test "$ac_init_help" = "long"; then
>    # Omit some internal or obsolete options to make the list less imposing.
>    # This message is too long to be a string in the A/UX 3.1 sh.
>    cat <<_ACEOF
> -\`configure' configures NSD 4.1.25 to adapt to many kinds of systems.
> +\`configure' configures NSD 4.1.26 to adapt to many kinds of systems.
>  
>  Usage: $0 [OPTION]... [VAR=VALUE]...
>  
> @@ -1348,7 +1357,7 @@ fi
>  
>  if test -n "$ac_init_help"; then
>    case $ac_init_help in
> -     short | recursive ) echo "Configuration of NSD 4.1.25:";;
> +     short | recursive ) echo "Configuration of NSD 4.1.26:";;
>     esac
>    cat <<\_ACEOF
>  
> @@ -1387,6 +1396,7 @@ Optional Features:
>                            less memory, but uses some more CPU.
>    --enable-packed         Enable packed structure alignment, uses less memory,
>                            but unaligned reads.
> +  --enable-dnstap         Enable dnstap support (requires fstrm, protobuf-c)
>    --enable-systemd        compile with systemd support
>  
>  Optional Packages:
> @@ -1415,6 +1425,10 @@ Optional Packages:
>                            Limit the default tcp timeout
>    --with-ssl=pathname     enable SSL (will check /usr/local/ssl /usr/lib/ssl
>                            /usr/ssl /usr/pkg /usr/sfw /usr/local /usr)
> +  --with-dnstap-socket-path=pathname
> +                          set default dnstap socket path
> +  --with-protobuf-c=path  Path where protobuf-c is installed, for dnstap
> +  --with-libfstrm=path    Path where libfstrm is installed, for dnstap
>  
>  Some influential environment variables:
>    CC          C compiler command
> @@ -1498,7 +1512,7 @@ fi
>  test -n "$ac_init_help" && exit $ac_status
>  if $ac_init_version; then
>    cat <<\_ACEOF
> -NSD configure 4.1.25
> +NSD configure 4.1.26
>  generated by GNU Autoconf 2.69
>  
>  Copyright (C) 2012 Free Software Foundation, Inc.
> @@ -2207,7 +2221,7 @@ cat >config.log <<_ACEOF
>  This file contains any messages produced by compilers while
>  running configure, to aid debugging if configure makes a mistake.
>  
> -It was created by NSD $as_me 4.1.25, which was
> +It was created by NSD $as_me 4.1.26, which was
>  generated by GNU Autoconf 2.69.  Invocation command line was
>  
>    $ $0 $@
> @@ -9300,6 +9314,251 @@ fi
>   ;;
>  esac
>  
> +# check for dnstap if requested
> +
> +  # Check whether --enable-dnstap was given.
> +if test "${enable_dnstap+set}" = set; then :
> +  enableval=$enable_dnstap; opt_dnstap=$enableval
> +else
> +  opt_dnstap=no
> +fi
> +
> +
> +
> +# Check whether --with-dnstap-socket-path was given.
> +if test "${with_dnstap_socket_path+set}" = set; then :
> +  withval=$with_dnstap_socket_path; opt_dnstap_socket_path=$withval
> +else
> +  opt_dnstap_socket_path="${localstatedir}/run/nsd-dnstap.sock"
> +fi
> +
> +
> +  if test "x$opt_dnstap" != "xno"; then
> +    # Extract the first word of "protoc-c", so it can be a program name with args.
> +set dummy protoc-c; ac_word=$2
> +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
> +$as_echo_n "checking for $ac_word... " >&6; }
> +if ${ac_cv_path_PROTOC_C+:} false; then :
> +  $as_echo_n "(cached) " >&6
> +else
> +  case $PROTOC_C in
> +  [\\/]* | ?:[\\/]*)
> +  ac_cv_path_PROTOC_C="$PROTOC_C" # Let the user override the test with a path.
> +  ;;
> +  *)
> +  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
> +for as_dir in $PATH
> +do
> +  IFS=$as_save_IFS
> +  test -z "$as_dir" && as_dir=.
> +    for ac_exec_ext in '' $ac_executable_extensions; do
> +  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
> +    ac_cv_path_PROTOC_C="$as_dir/$ac_word$ac_exec_ext"
> +    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
> +    break 2
> +  fi
> +done
> +  done
> +IFS=$as_save_IFS
> +
> +  ;;
> +esac
> +fi
> +PROTOC_C=$ac_cv_path_PROTOC_C
> +if test -n "$PROTOC_C"; then
> +  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROTOC_C" >&5
> +$as_echo "$PROTOC_C" >&6; }
> +else
> +  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
> +$as_echo "no" >&6; }
> +fi
> +
> +
> +    if test -z "$PROTOC_C"; then
> +      as_fn_error $? "The protoc-c program was not found. Please install protobuf-c!" "$LINENO" 5
> +    fi
> +
> +# Check whether --with-protobuf-c was given.
> +if test "${with_protobuf_c+set}" = set; then :
> +  withval=$with_protobuf_c;
> +  # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
> +  if test -f $withval/include/google/protobuf-c/protobuf-c.h; then
> +    CFLAGS="$CFLAGS -I$withval/include/google"
> +  else
> +    CFLAGS="$CFLAGS -I$withval/include"
> +  fi
> +  LDFLAGS="$LDFLAGS -L$withval/lib"
> +
> +else
> +
> +  # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
> +  if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
> +    CFLAGS="$CFLAGS -I/usr/include/google"
> +  else
> +    if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
> +      CFLAGS="$CFLAGS -I/usr/local/include/google"
> +      LDFLAGS="$LDFLAGS -L/usr/local/lib"
> +    fi
> +  fi
> +
> +fi
> +
> +
> +# Check whether --with-libfstrm was given.
> +if test "${with_libfstrm+set}" = set; then :
> +  withval=$with_libfstrm;
> + CFLAGS="$CFLAGS -I$withval/include"
> + LDFLAGS="$LDFLAGS -L$withval/lib"
> +
> +fi
> +
> +    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing fstrm_iothr_init" >&5
> +$as_echo_n "checking for library containing fstrm_iothr_init... " >&6; }
> +if ${ac_cv_search_fstrm_iothr_init+:} false; then :
> +  $as_echo_n "(cached) " >&6
> +else
> +  ac_func_search_save_LIBS=$LIBS
> +cat confdefs.h - <<_ACEOF >conftest.$ac_ext
> +/* end confdefs.h.  */
> +
> +/* Override any GCC internal prototype to avoid an error.
> +   Use char because int might match the return type of a GCC
> +   builtin and then its argument prototype would still apply.  */
> +#ifdef __cplusplus
> +extern "C"
> +#endif
> +char fstrm_iothr_init ();
> +int
> +main ()
> +{
> +return fstrm_iothr_init ();
> +  ;
> +  return 0;
> +}
> +_ACEOF
> +for ac_lib in '' fstrm; do
> +  if test -z "$ac_lib"; then
> +    ac_res="none required"
> +  else
> +    ac_res=-l$ac_lib
> +    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
> +  fi
> +  if ac_fn_c_try_link "$LINENO"; then :
> +  ac_cv_search_fstrm_iothr_init=$ac_res
> +fi
> +rm -f core conftest.err conftest.$ac_objext \
> +    conftest$ac_exeext
> +  if ${ac_cv_search_fstrm_iothr_init+:} false; then :
> +  break
> +fi
> +done
> +if ${ac_cv_search_fstrm_iothr_init+:} false; then :
> +
> +else
> +  ac_cv_search_fstrm_iothr_init=no
> +fi
> +rm conftest.$ac_ext
> +LIBS=$ac_func_search_save_LIBS
> +fi
> +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_fstrm_iothr_init" >&5
> +$as_echo "$ac_cv_search_fstrm_iothr_init" >&6; }
> +ac_res=$ac_cv_search_fstrm_iothr_init
> +if test "$ac_res" != no; then :
> +  test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
> +
> +else
> +  as_fn_error $? "The fstrm library was not found. Please install fstrm!" "$LINENO" 5
> +fi
> +
> +    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing protobuf_c_message_pack" >&5
> +$as_echo_n "checking for library containing protobuf_c_message_pack... " >&6; }
> +if ${ac_cv_search_protobuf_c_message_pack+:} false; then :
> +  $as_echo_n "(cached) " >&6
> +else
> +  ac_func_search_save_LIBS=$LIBS
> +cat confdefs.h - <<_ACEOF >conftest.$ac_ext
> +/* end confdefs.h.  */
> +
> +/* Override any GCC internal prototype to avoid an error.
> +   Use char because int might match the return type of a GCC
> +   builtin and then its argument prototype would still apply.  */
> +#ifdef __cplusplus
> +extern "C"
> +#endif
> +char protobuf_c_message_pack ();
> +int
> +main ()
> +{
> +return protobuf_c_message_pack ();
> +  ;
> +  return 0;
> +}
> +_ACEOF
> +for ac_lib in '' protobuf-c; do
> +  if test -z "$ac_lib"; then
> +    ac_res="none required"
> +  else
> +    ac_res=-l$ac_lib
> +    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
> +  fi
> +  if ac_fn_c_try_link "$LINENO"; then :
> +  ac_cv_search_protobuf_c_message_pack=$ac_res
> +fi
> +rm -f core conftest.err conftest.$ac_objext \
> +    conftest$ac_exeext
> +  if ${ac_cv_search_protobuf_c_message_pack+:} false; then :
> +  break
> +fi
> +done
> +if ${ac_cv_search_protobuf_c_message_pack+:} false; then :
> +
> +else
> +  ac_cv_search_protobuf_c_message_pack=no
> +fi
> +rm conftest.$ac_ext
> +LIBS=$ac_func_search_save_LIBS
> +fi
> +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_protobuf_c_message_pack" >&5
> +$as_echo "$ac_cv_search_protobuf_c_message_pack" >&6; }
> +ac_res=$ac_cv_search_protobuf_c_message_pack
> +if test "$ac_res" != no; then :
> +  test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
> +
> +else
> +  as_fn_error $? "The protobuf-c library was not found. Please install protobuf-c!" "$LINENO" 5
> +fi
> +
> +
> +
> +$as_echo "#define USE_DNSTAP 1" >>confdefs.h
> +
> +        ENABLE_DNSTAP=1
> +
> +
> +
> +        hdr_dnstap_socket_path="`echo $opt_dnstap_socket_path | sed -e 's/\\\\/\\\\\\\\/g'`"
> +
> +
> +cat >>confdefs.h <<_ACEOF
> +#define DNSTAP_SOCKET_PATH "$hdr_dnstap_socket_path"
> +_ACEOF
> +
> +
> +        DNSTAP_SRC="dnstap/dnstap.c dnstap/dnstap.pb-c.c dnstap/dnstap_collector.c"
> +
> +        DNSTAP_OBJ="dnstap.o dnstap_collector.o dnstap.pb-c.o"
> +
> + dnstap_config="dnstap/dnstap_config.h"
> +
> +  else
> +
> +        ENABLE_DNSTAP=0
> +
> +
> +
> +  fi
> +
> +
>  # Include systemd.m4 - begin
>  #   macros for configuring systemd
>  #   Copyright 2015, Sami Kerola, CloudFlare.
> @@ -9360,7 +9619,7 @@ if test "$enable_checking" = "yes"; then
>          echo "************************************************"
>  fi
>  
> -ac_config_files="$ac_config_files Makefile"
> +ac_config_files="$ac_config_files Makefile $dnstap_config"
>  
>  cat >confcache <<\_ACEOF
>  # This file is a shell script that caches the results of configure
> @@ -9868,7 +10127,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
>  # report actual input values of CONFIG_FILES etc. instead of their
>  # values after options handling.
>  ac_log="
> -This file was extended by NSD $as_me 4.1.25, which was
> +This file was extended by NSD $as_me 4.1.26, which was
>  generated by GNU Autoconf 2.69.  Invocation command line was
>  
>    CONFIG_FILES    = $CONFIG_FILES
> @@ -9930,7 +10189,7 @@ _ACEOF
>  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.1.25
> +NSD config.status 4.1.26
>  configured by $0, generated by GNU Autoconf 2.69,
>    with options \\"\$ac_cs_config\\"
>  
> @@ -10055,6 +10314,7 @@ do
>    case $ac_config_target in
>      "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
>      "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
> +    "$dnstap_config") CONFIG_FILES="$CONFIG_FILES $dnstap_config" ;;
>  
>    *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
>    esac
> diff --git configure.ac configure.ac
> index ad5399a9d72..22e3e21e2ae 100644
> --- configure.ac
> +++ configure.ac
> @@ -3,8 +3,9 @@ dnl Some global settings
>  dnl
>  
>  sinclude(acx_nlnetlabs.m4)
> +sinclude(dnstap/dnstap.m4)
>  
> -AC_INIT(NSD,4.1.25,[hidden email])
> +AC_INIT(NSD,4.1.26,[hidden email])
>  AC_CONFIG_HEADER([config.h])
>  
>  CFLAGS="$CFLAGS"
> @@ -961,6 +962,26 @@ case "$enable_packed" in
>   ;;
>  esac
>  
> +# check for dnstap if requested
> +dt_DNSTAP([${localstatedir}/run/nsd-dnstap.sock],
> +    [
> +        AC_DEFINE([USE_DNSTAP], [1], [Define to 1 to enable dnstap support])
> +        AC_SUBST([ENABLE_DNSTAP], [1])
> +
> +        AC_SUBST([opt_dnstap_socket_path])
> +        ACX_ESCAPE_BACKSLASH($opt_dnstap_socket_path, hdr_dnstap_socket_path)
> +        AC_DEFINE_UNQUOTED(DNSTAP_SOCKET_PATH,
> +            ["$hdr_dnstap_socket_path"], [default dnstap socket path])
> +
> +        AC_SUBST([DNSTAP_SRC], ["dnstap/dnstap.c dnstap/dnstap.pb-c.c dnstap/dnstap_collector.c"])
> +        AC_SUBST([DNSTAP_OBJ], ["dnstap.o dnstap_collector.o dnstap.pb-c.o"])
> + dnstap_config="dnstap/dnstap_config.h"
> +    ],
> +    [
> +        AC_SUBST([ENABLE_DNSTAP], [0])
> +    ]
> +)
> +
>  # Include systemd.m4 - begin
>  sinclude(systemd.m4)
>  # Include systemd.m4 - end
> @@ -1163,5 +1184,5 @@ if test "$enable_checking" = "yes"; then
>          echo "************************************************"
>  fi
>  
> -AC_CONFIG_FILES([Makefile])
> +AC_CONFIG_FILES([Makefile $dnstap_config])
>  AC_OUTPUT
> diff --git dnstap/dnstap.c dnstap/dnstap.c
> new file mode 100644
> index 00000000000..fb724a8fc7c
> --- /dev/null
> +++ dnstap/dnstap.c
> @@ -0,0 +1,433 @@
> +/* dnstap support for NSD */
> +
> +/*
> + * Copyright (c) 2013-2014, Farsight Security, Inc.
> + * All rights reserved.
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + *
> + * 1. Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + *
> + * 2. Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in the
> + * documentation and/or other materials provided with the distribution.
> + *
> + * 3. Neither the name of the copyright holder nor the names of its
> + * contributors may be used to endorse or promote products derived from
> + * this software without specific prior written permission.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
> + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
> + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
> + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
> + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
> + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
> + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
> + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
> + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
> + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
> + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include "dnstap/dnstap_config.h"
> +
> +#ifdef USE_DNSTAP
> +
> +#include "config.h"
> +#include <string.h>
> +#include <sys/time.h>
> +#ifdef HAVE_SYS_STAT_H
> +#include <sys/stat.h>
> +#endif
> +#include <errno.h>
> +#include <unistd.h>
> +#include "util.h"
> +#include "options.h"
> +
> +#include <fstrm.h>
> +#include <protobuf-c/protobuf-c.h>
> +
> +#include "dnstap/dnstap.h"
> +#include "dnstap/dnstap.pb-c.h"
> +
> +#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap"
> +#define DNSTAP_INITIAL_BUF_SIZE 256
> +
> +struct dt_msg {
> + void *buf;
> + size_t len_buf;
> + Dnstap__Dnstap d;
> + Dnstap__Message m;
> +};
> +
> +static int
> +dt_pack(const Dnstap__Dnstap *d, void **buf, size_t *sz)
> +{
> + ProtobufCBufferSimple sbuf;
> +
> + memset(&sbuf, 0, sizeof(sbuf));
> + sbuf.base.append = protobuf_c_buffer_simple_append;
> + sbuf.len = 0;
> + sbuf.alloced = DNSTAP_INITIAL_BUF_SIZE;
> + sbuf.data = malloc(sbuf.alloced);
> + if (sbuf.data == NULL)
> + return 0;
> + sbuf.must_free_data = 1;
> +
> + *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *) &sbuf);
> + if (sbuf.data == NULL)
> + return 0;
> + *buf = sbuf.data;
> +
> + return 1;
> +}
> +
> +static void
> +dt_send(const struct dt_env *env, void *buf, size_t len_buf)
> +{
> + fstrm_res res;
> + if (!buf)
> + return;
> + res = fstrm_iothr_submit(env->iothr, env->ioq, buf, len_buf,
> + fstrm_free_wrapper, NULL);
> + if (res != fstrm_res_success)
> + free(buf);
> +}
> +
> +static void
> +dt_msg_init(const struct dt_env *env,
> +    struct dt_msg *dm,
> +    Dnstap__Message__Type mtype)
> +{
> + memset(dm, 0, sizeof(*dm));
> + dm->d.base.descriptor = &dnstap__dnstap__descriptor;
> + dm->m.base.descriptor = &dnstap__message__descriptor;
> + dm->d.type = DNSTAP__DNSTAP__TYPE__MESSAGE;
> + dm->d.message = &dm->m;
> + dm->m.type = mtype;
> + if (env->identity != NULL) {
> + dm->d.identity.data = (uint8_t *) env->identity;
> + dm->d.identity.len = (size_t) env->len_identity;
> + dm->d.has_identity = 1;
> + }
> + if (env->version != NULL) {
> + dm->d.version.data = (uint8_t *) env->version;
> + dm->d.version.len = (size_t) env->len_version;
> + dm->d.has_version = 1;
> + }
> +}
> +
> +/* check that the socket file can be opened and exists, print error if not */
> +static void
> +check_socket_file(const char* socket_path)
> +{
> + struct stat statbuf;
> + memset(&statbuf, 0, sizeof(statbuf));
> + if(stat(socket_path, &statbuf) < 0) {
> + log_msg(LOG_WARNING, "could not open dnstap-socket-path: %s, %s",
> + socket_path, strerror(errno));
> + }
> +}
> +
> +struct dt_env *
> +dt_create(const char *socket_path, unsigned num_workers)
> +{
> +#ifndef NDEBUG
> + fstrm_res res;
> +#endif
> + struct dt_env *env;
> + struct fstrm_iothr_options *fopt;
> + struct fstrm_unix_writer_options *fuwopt;
> + struct fstrm_writer *fw;
> + struct fstrm_writer_options *fwopt;
> +
> + VERBOSITY(1, (LOG_INFO, "attempting to connect to dnstap socket %s",
> + socket_path));
> + assert(socket_path != NULL);
> + assert(num_workers > 0);
> + check_socket_file(socket_path);
> +
> + env = (struct dt_env *) calloc(1, sizeof(struct dt_env));
> + if (!env)
> + return NULL;
> +
> + fwopt = fstrm_writer_options_init();
> +#ifndef NDEBUG
> + res =
> +#else
> + (void)
> +#endif
> +    fstrm_writer_options_add_content_type(fwopt,
> + DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1);
> + assert(res == fstrm_res_success);
> +
> + fuwopt = fstrm_unix_writer_options_init();
> + fstrm_unix_writer_options_set_socket_path(fuwopt, socket_path);
> +
> + fw = fstrm_unix_writer_init(fuwopt, fwopt);
> + assert(fw != NULL);
> +
> + fopt = fstrm_iothr_options_init();
> + fstrm_iothr_options_set_num_input_queues(fopt, num_workers);
> + env->iothr = fstrm_iothr_init(fopt, &fw);
> + if (env->iothr == NULL) {
> + log_msg(LOG_ERR, "dt_create: fstrm_iothr_init() failed");
> + fstrm_writer_destroy(&fw);
> + free(env);
> + env = NULL;
> + }
> + fstrm_iothr_options_destroy(&fopt);
> + fstrm_unix_writer_options_destroy(&fuwopt);
> + fstrm_writer_options_destroy(&fwopt);
> +
> + return env;
> +}
> +
> +static void
> +dt_apply_identity(struct dt_env *env, struct nsd_options *cfg)
> +{
> + char buf[MAXHOSTNAMELEN+1];
> + if (!cfg->dnstap_send_identity)
> + return;
> + free(env->identity);
> + if (cfg->dnstap_identity == NULL || cfg->dnstap_identity[0] == 0) {
> + if (gethostname(buf, MAXHOSTNAMELEN) == 0) {
> + buf[MAXHOSTNAMELEN] = 0;
> + env->identity = strdup(buf);
> + } else {
> + error("dt_apply_identity: gethostname() failed");
> + }
> + } else {
> + env->identity = strdup(cfg->dnstap_identity);
> + }
> + if (env->identity == NULL)
> + error("dt_apply_identity: strdup() failed");
> + env->len_identity = (unsigned int)strlen(env->identity);
> + VERBOSITY(1, (LOG_INFO, "dnstap identity field set to \"%s\"",
> + env->identity));
> +}
> +
> +static void
> +dt_apply_version(struct dt_env *env, struct nsd_options *cfg)
> +{
> + if (!cfg->dnstap_send_version)
> + return;
> + free(env->version);
> + if (cfg->dnstap_version == NULL || cfg->dnstap_version[0] == 0)
> + env->version = strdup(PACKAGE_STRING);
> + else
> + env->version = strdup(cfg->dnstap_version);
> + if (env->version == NULL)
> + error("dt_apply_version: strdup() failed");
> + env->len_version = (unsigned int)strlen(env->version);
> + VERBOSITY(1, (LOG_INFO, "dnstap version field set to \"%s\"",
> + env->version));
> +}
> +
> +void
> +dt_apply_cfg(struct dt_env *env, struct nsd_options *cfg)
> +{
> + if (!cfg->dnstap_enable)
> + return;
> +
> + dt_apply_identity(env, cfg);
> + dt_apply_version(env, cfg);
> + if ((env->log_auth_query_messages = (unsigned int)
> +     cfg->dnstap_log_auth_query_messages))
> + {
> + VERBOSITY(1, (LOG_INFO, "dnstap Message/AUTH_QUERY enabled"));
> + }
> + if ((env->log_auth_response_messages = (unsigned int)
> +     cfg->dnstap_log_auth_response_messages))
> + {
> + VERBOSITY(1, (LOG_INFO, "dnstap Message/AUTH_RESPONSE enabled"));
> + }
> +}
> +
> +int
> +dt_init(struct dt_env *env)
> +{
> + env->ioq = fstrm_iothr_get_input_queue(env->iothr);
> + if (env->ioq == NULL)
> + return 0;
> + return 1;
> +}
> +
> +void
> +dt_delete(struct dt_env *env)
> +{
> + if (!env)
> + return;
> + VERBOSITY(1, (LOG_INFO, "closing dnstap socket"));
> + fstrm_iothr_destroy(&env->iothr);
> + free(env->identity);
> + free(env->version);
> + free(env);
> +}
> +
> +static void
> +dt_fill_timeval(const struct timeval *tv,
> + uint64_t *time_sec, protobuf_c_boolean *has_time_sec,
> + uint32_t *time_nsec, protobuf_c_boolean *has_time_nsec)
> +{
> +#ifndef S_SPLINT_S
> + *time_sec = tv->tv_sec;
> + *time_nsec = tv->tv_usec * 1000;
> +#endif
> + *has_time_sec = 1;
> + *has_time_nsec = 1;
> +}
> +
> +static void
> +dt_fill_buffer(uint8_t* pkt, size_t pktlen, ProtobufCBinaryData *p, protobuf_c_boolean *has)
> +{
> + p->len = pktlen;
> + p->data = pkt;
> + *has = 1;
> +}
> +
> +static void
> +dt_msg_fill_net(struct dt_msg *dm,
> +#ifdef INET6
> + struct sockaddr_storage *ss,
> +#else
> + struct sockaddr_in *ss,
> +#endif
> + int is_tcp,
> + ProtobufCBinaryData *addr, protobuf_c_boolean *has_addr,
> + uint32_t *port, protobuf_c_boolean *has_port)
> +{
> +#ifdef INET6
> + assert(ss->ss_family == AF_INET6 || ss->ss_family == AF_INET);
> + if (ss->ss_family == AF_INET6) {
> + struct sockaddr_in6 *s = (struct sockaddr_in6 *) ss;
> +
> + /* socket_family */
> + dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET6;
> + dm->m.has_socket_family = 1;
> +
> + /* addr: query_address or response_address */
> + addr->data = s->sin6_addr.s6_addr;
> + addr->len = 16; /* IPv6 */
> + *has_addr = 1;
> +
> + /* port: query_port or response_port */
> + *port = ntohs(s->sin6_port);
> + *has_port = 1;
> + } else if (ss->ss_family == AF_INET) {
> +#else
> + if (ss->ss_family == AF_INET) {
> +#endif /* INET6 */
> + struct sockaddr_in *s = (struct sockaddr_in *) ss;
> +
> + /* socket_family */
> + dm->m.socket_family = DNSTAP__SOCKET_FAMILY__INET;
> + dm->m.has_socket_family = 1;
> +
> + /* addr: query_address or response_address */
> + addr->data = (uint8_t *) &s->sin_addr.s_addr;
> + addr->len = 4; /* IPv4 */
> + *has_addr = 1;
> +
> + /* port: query_port or response_port */
> + *port = ntohs(s->sin_port);
> + *has_port = 1;
> + }
> +
> + if (!is_tcp) {
> + /* socket_protocol */
> + dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__UDP;
> + dm->m.has_socket_protocol = 1;
> + } else {
> + /* socket_protocol */
> + dm->m.socket_protocol = DNSTAP__SOCKET_PROTOCOL__TCP;
> + dm->m.has_socket_protocol = 1;
> + }
> +}
> +
> +void
> +dt_msg_send_auth_query(struct dt_env *env,
> +#ifdef INET6
> + struct sockaddr_storage* addr,
> +#else
> + struct sockaddr_in* addr,
> +#endif
> + int is_tcp, uint8_t* zone, size_t zonelen, uint8_t* pkt, size_t pktlen)
> +{
> + struct dt_msg dm;
> + struct timeval qtime;
> +
> + gettimeofday(&qtime, NULL);
> +
> + /* type */
> + dt_msg_init(env, &dm, DNSTAP__MESSAGE__TYPE__AUTH_QUERY);
> +
> + if(zone) {
> + /* query_zone */
> + dm.m.query_zone.data = zone;
> + dm.m.query_zone.len = zonelen;
> + dm.m.has_query_zone = 1;
> + }
> +
> + /* query_time */
> + dt_fill_timeval(&qtime,
> + &dm.m.query_time_sec, &dm.m.has_query_time_sec,
> + &dm.m.query_time_nsec, &dm.m.has_query_time_nsec);
> +
> + /* query_message */
> + dt_fill_buffer(pkt, pktlen, &dm.m.query_message, &dm.m.has_query_message);
> +
> + /* socket_family, socket_protocol, query_address, query_port */
> + dt_msg_fill_net(&dm, addr, is_tcp,
> + &dm.m.query_address, &dm.m.has_query_address,
> + &dm.m.query_port, &dm.m.has_query_port);
> +
> + if (dt_pack(&dm.d, &dm.buf, &dm.len_buf))
> + dt_send(env, dm.buf, dm.len_buf);
> +}
> +
> +void
> +dt_msg_send_auth_response(struct dt_env *env,
> +#ifdef INET6
> + struct sockaddr_storage* addr,
> +#else
> + struct sockaddr_in* addr,
> +#endif
> + int is_tcp, uint8_t* zone, size_t zonelen, uint8_t* pkt, size_t pktlen)
> +{
> + struct dt_msg dm;
> + struct timeval rtime;
> +
> + gettimeofday(&rtime, NULL);
> +
> + /* type */
> + dt_msg_init(env, &dm, DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE);
> +
> + if(zone) {
> + /* query_zone */
> + dm.m.query_zone.data = zone;
> + dm.m.query_zone.len = zonelen;
> + dm.m.has_query_zone = 1;
> + }
> +
> + /* response_time */
> + dt_fill_timeval(&rtime,
> + &dm.m.response_time_sec, &dm.m.has_response_time_sec,
> + &dm.m.response_time_nsec, &dm.m.has_response_time_nsec);
> +
> + /* response_message */
> + dt_fill_buffer(pkt, pktlen, &dm.m.response_message, &dm.m.has_response_message);
> +
> + /* socket_family, socket_protocol, query_address, query_port */
> + dt_msg_fill_net(&dm, addr, is_tcp,
> + &dm.m.query_address, &dm.m.has_query_address,
> + &dm.m.query_port, &dm.m.has_query_port);
> +
> + if (dt_pack(&dm.d, &dm.buf, &dm.len_buf))
> + dt_send(env, dm.buf, dm.len_buf);
> +}
> +
> +#endif /* USE_DNSTAP */
> diff --git dnstap/dnstap.h dnstap/dnstap.h
> new file mode 100644
> index 00000000000..05b1bd049f3
> --- /dev/null
> +++ dnstap/dnstap.h
> @@ -0,0 +1,148 @@
> +/* dnstap support for NSD */
> +
> +/*
> + * Copyright (c) 2013-2014, Farsight Security, Inc.
> + * All rights reserved.
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + *
> + * 1. Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + *
> + * 2. Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in the
> + * documentation and/or other materials provided with the distribution.
> + *
> + * 3. Neither the name of the copyright holder nor the names of its
> + * contributors may be used to endorse or promote products derived from
> + * this software without specific prior written permission.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
> + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
> + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
> + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
> + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
> + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
> + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
> + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
> + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
> + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
> + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#ifndef NSD_DNSTAP_H
> +#define NSD_DNSTAP_H
> +
> +#include "dnstap/dnstap_config.h"
> +
> +#ifdef USE_DNSTAP
> +
> +struct nsd_options;
> +struct fstrm_io;
> +struct fstrm_queue;
> +
> +struct dt_env {
> + /** dnstap I/O thread */
> + struct fstrm_iothr *iothr;
> +
> + /** dnstap I/O thread input queue */
> + struct fstrm_iothr_queue *ioq;
> +
> + /** dnstap "identity" field, NULL if disabled */
> + char *identity;
> +
> + /** dnstap "version" field, NULL if disabled */
> + char *version;
> +
> + /** length of "identity" field */
> + unsigned len_identity;
> +
> + /** length of "version" field */
> + unsigned len_version;
> +
> + /** whether to log Message/AUTH_QUERY */
> + unsigned log_auth_query_messages : 1;
> + /** whether to log Message/AUTH_RESPONSE */
> + unsigned log_auth_response_messages : 1;
> +};
> +
> +/**
> + * Create dnstap environment object. Afterwards, call dt_apply_cfg() to fill in
> + * the config variables and dt_init() to fill in the per-worker state. Each
> + * worker needs a copy of this object but with its own I/O queue (the fq field
> + * of the structure) to ensure lock-free access to its own per-worker circular
> + * queue.  Duplicate the environment object if more than one worker needs to
> + * share access to the dnstap I/O socket.
> + * @param socket_path: path to dnstap logging socket, must be non-NULL.
> + * @param num_workers: number of worker threads, must be > 0.
> + * @return dt_env object, NULL on failure.
> + */
> +struct dt_env *
> +dt_create(const char *socket_path, unsigned num_workers);
> +
> +/**
> + * Apply config settings.
> + * @param env: dnstap environment object.
> + * @param cfg: new config settings.
> + */
> +void
> +dt_apply_cfg(struct dt_env *env, struct nsd_options *cfg);
> +
> +/**
> + * Initialize per-worker state in dnstap environment object.
> + * @param env: dnstap environment object to initialize, created with dt_create().
> + * @return: true on success, false on failure.
> + */
> +int
> +dt_init(struct dt_env *env);
> +
> +/**
> + * Delete dnstap environment object. Closes dnstap I/O socket and deletes all
> + * per-worker I/O queues.
> + */
> +void
> +dt_delete(struct dt_env *env);
> +
> +/**
> + * Create and send a new dnstap "Message" event of type AUTH_QUERY.
> + * @param env: dnstap environment object.
> + * @param addr: address/port of client.
> + * @param is_tcp: true for tcp, false for udp.
> + * @param zone: zone name, or NULL. in wireformat.
> + * @param zonelen: length of zone in bytes.
> + * @param pkt: query message.
> + * @param pktlen: length of pkt.
> + */
> +void
> +dt_msg_send_auth_query(struct dt_env *env,
> +#ifdef INET6
> + struct sockaddr_storage* addr,
> +#else
> + struct sockaddr_in* addr,
> +#endif
> + int is_tcp, uint8_t* zone, size_t zonelen, uint8_t* pkt, size_t pktlen);
> +
> +/**
> + * Create and send a new dnstap "Message" event of type AUTH_RESPONSE.
> + * @param env: dnstap environment object.
> + * @param addr: address/port of client.
> + * @param is_tcp: true for tcp, false for udp.
> + * @param zone: zone name, or NULL. in wireformat.
> + * @param zonelen: length of zone in bytes.
> + * @param pkt: response message.
> + * @param pktlen: length of pkt.
> + */
> +void
> +dt_msg_send_auth_response(struct dt_env *env,
> +#ifdef INET6
> + struct sockaddr_storage* addr,
> +#else
> + struct sockaddr_in* addr,
> +#endif
> + int is_tcp, uint8_t* zone, size_t zonelen, uint8_t* pkt, size_t pktlen);
> +
> +#endif /* USE_DNSTAP */
> +
> +#endif /* NSD_DNSTAP_H */
> diff --git dnstap/dnstap.m4 dnstap/dnstap.m4
> new file mode 100644
> index 00000000000..5b78b3e267c
> --- /dev/null
> +++ dnstap/dnstap.m4
> @@ -0,0 +1,56 @@
> +# dnstap.m4
> +
> +# dt_DNSTAP(default_dnstap_socket_path, [action-if-true], [action-if-false])
> +# --------------------------------------------------------------------------
> +# Check for required dnstap libraries and add dnstap configure args.
> +AC_DEFUN([dt_DNSTAP],
> +[
> +  AC_ARG_ENABLE([dnstap],
> +    AS_HELP_STRING([--enable-dnstap],
> +                   [Enable dnstap support (requires fstrm, protobuf-c)]),
> +    [opt_dnstap=$enableval], [opt_dnstap=no])
> +
> +  AC_ARG_WITH([dnstap-socket-path],
> +    AS_HELP_STRING([--with-dnstap-socket-path=pathname],
> +                   [set default dnstap socket path]),
> +    [opt_dnstap_socket_path=$withval], [opt_dnstap_socket_path="$1"])
> +
> +  if test "x$opt_dnstap" != "xno"; then
> +    AC_PATH_PROG([PROTOC_C], [protoc-c])
> +    if test -z "$PROTOC_C"; then
> +      AC_MSG_ERROR([The protoc-c program was not found. Please install protobuf-c!])
> +    fi
> +    AC_ARG_WITH([protobuf-c], AC_HELP_STRING([--with-protobuf-c=path],
> +     [Path where protobuf-c is installed, for dnstap]), [
> +  # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
> +  if test -f $withval/include/google/protobuf-c/protobuf-c.h; then
> +    CFLAGS="$CFLAGS -I$withval/include/google"
> +  else
> +    CFLAGS="$CFLAGS -I$withval/include"
> +  fi
> +  LDFLAGS="$LDFLAGS -L$withval/lib"
> + ], [
> +  # workaround for protobuf-c includes at old dir before protobuf-c-1.0.0
> +  if test -f /usr/include/google/protobuf-c/protobuf-c.h; then
> +    CFLAGS="$CFLAGS -I/usr/include/google"
> +  else
> +    if test -f /usr/local/include/google/protobuf-c/protobuf-c.h; then
> +      CFLAGS="$CFLAGS -I/usr/local/include/google"
> +      LDFLAGS="$LDFLAGS -L/usr/local/lib"
> +    fi
> +  fi
> +    ])
> +    AC_ARG_WITH([libfstrm], AC_HELP_STRING([--with-libfstrm=path],
> +     [Path where libfstrm is installed, for dnstap]), [
> + CFLAGS="$CFLAGS -I$withval/include"
> + LDFLAGS="$LDFLAGS -L$withval/lib"
> +    ])
> +    AC_SEARCH_LIBS([fstrm_iothr_init], [fstrm], [],
> +      AC_MSG_ERROR([The fstrm library was not found. Please install fstrm!]))
> +    AC_SEARCH_LIBS([protobuf_c_message_pack], [protobuf-c], [],
> +      AC_MSG_ERROR([The protobuf-c library was not found. Please install protobuf-c!]))
> +    $2
> +  else
> +    $3
> +  fi
> +])
> diff --git dnstap/dnstap.proto dnstap/dnstap.proto
> new file mode 100644
> index 00000000000..88bfb4e9412
> --- /dev/null
> +++ dnstap/dnstap.proto
> @@ -0,0 +1,263 @@
> +// dnstap: flexible, structured event replication format for DNS software
> +//
> +// This file contains the protobuf schemas for the "dnstap" structured event
> +// replication format for DNS software.
> +
> +// Written in 2013-2014 by Farsight Security, Inc.
> +//
> +// To the extent possible under law, the author(s) have dedicated all
> +// copyright and related and neighboring rights to this file to the public
> +// domain worldwide. This file is distributed without any warranty.
> +//
> +// You should have received a copy of the CC0 Public Domain Dedication along
> +// with this file. If not, see:
> +//
> +// <http://creativecommons.org/publicdomain/zero/1.0/>.
> +syntax = "proto2";
> +
> +package dnstap;
> +
> +// "Dnstap": this is the top-level dnstap type, which is a "union" type that
> +// contains other kinds of dnstap payloads, although currently only one type
> +// of dnstap payload is defined.
> +// See: https://developers.google.com/protocol-buffers/docs/techniques#union
> +message Dnstap {
> +    // DNS server identity.
> +    // If enabled, this is the identity string of the DNS server which generated
> +    // this message. Typically this would be the same string as returned by an
> +    // "NSID" (RFC 5001) query.
> +    optional bytes      identity = 1;
> +
> +    // DNS server version.
> +    // If enabled, this is the version string of the DNS server which generated
> +    // this message. Typically this would be the same string as returned by a
> +    // "version.bind" query.
> +    optional bytes      version = 2;
> +
> +    // Extra data for this payload.
> +    // This field can be used for adding an arbitrary byte-string annotation to
> +    // the payload. No encoding or interpretation is applied or enforced.
> +    optional bytes      extra = 3;
> +
> +    // Identifies which field below is filled in.
> +    enum Type {
> +        MESSAGE = 1;
> +    }
> +    required Type       type = 15;
> +
> +    // One of the following will be filled in.
> +    optional Message    message = 14;
> +}
> +
> +// SocketFamily: the network protocol family of a socket. This specifies how
> +// to interpret "network address" fields.
> +enum SocketFamily {
> +    INET = 1;   // IPv4 (RFC 791)
> +    INET6 = 2;  // IPv6 (RFC 2460)
> +}
> +
> +// SocketProtocol: the transport protocol of a socket. This specifies how to
> +// interpret "transport port" fields.
> +enum SocketProtocol {
> +    UDP = 1;    // User Datagram Protocol (RFC 768)
> +    TCP = 2;    // Transmission Control Protocol (RFC 793)
> +}
> +
> +// Message: a wire-format (RFC 1035 section 4) DNS message and associated
> +// metadata. Applications generating "Message" payloads should follow
> +// certain requirements based on the MessageType, see below.
> +message Message {
> +
> +    // There are eight types of "Message" defined that correspond to the
> +    // four arrows in the following diagram, slightly modified from RFC 1035
> +    // section 2:
> +
> +    //    +---------+               +----------+           +--------+
> +    //    |         |     query     |          |   query   |        |
> +    //    | Stub    |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth.  |
> +    //    | Resolver|               | Server   |           | Name   |
> +    //    |         |<-SR--------CR-|          |<-RR----AR-| Server |
> +    //    +---------+    response   |          |  response |        |
> +    //                              +----------+           +--------+
> +
> +    // Each arrow has two Type values each, one for each "end" of each arrow,
> +    // because these are considered to be distinct events. Each end of each
> +    // arrow on the diagram above has been marked with a two-letter Type
> +    // mnemonic. Clockwise from upper left, these mnemonic values are:
> +    //
> +    //   SQ:        STUB_QUERY
> +    //   CQ:      CLIENT_QUERY
> +    //   RQ:    RESOLVER_QUERY
> +    //   AQ:        AUTH_QUERY
> +    //   AR:        AUTH_RESPONSE
> +    //   RR:    RESOLVER_RESPONSE
> +    //   CR:      CLIENT_RESPONSE
> +    //   SR:        STUB_RESPONSE
> +
> +    // Two additional types of "Message" have been defined for the
> +    // "forwarding" case where an upstream DNS server is responsible for
> +    // further recursion. These are not shown on the diagram above, but have
> +    // the following mnemonic values:
> +
> +    //   FQ:   FORWARDER_QUERY
> +    //   FR:   FORWARDER_RESPONSE
> +
> +    // The "Message" Type values are defined below.
> +
> +    enum Type {
> +        // AUTH_QUERY is a DNS query message received from a resolver by an
> +        // authoritative name server, from the perspective of the authoritative
> +        // name server.
> +        AUTH_QUERY = 1;
> +
> +        // AUTH_RESPONSE is a DNS response message sent from an authoritative
> +        // name server to a resolver, from the perspective of the authoritative
> +        // name server.
> +        AUTH_RESPONSE = 2;
> +
> +        // RESOLVER_QUERY is a DNS query message sent from a resolver to an
> +        // authoritative name server, from the perspective of the resolver.
> +        // Resolvers typically clear the RD (recursion desired) bit when
> +        // sending queries.
> +        RESOLVER_QUERY = 3;
> +
> +        // RESOLVER_RESPONSE is a DNS response message received from an
> +        // authoritative name server by a resolver, from the perspective of
> +        // the resolver.
> +        RESOLVER_RESPONSE = 4;
> +
> +        // CLIENT_QUERY is a DNS query message sent from a client to a DNS
> +        // server which is expected to perform further recursion, from the
> +        // perspective of the DNS server. The client may be a stub resolver or
> +        // forwarder or some other type of software which typically sets the RD
> +        // (recursion desired) bit when querying the DNS server. The DNS server
> +        // may be a simple forwarding proxy or it may be a full recursive
> +        // resolver.
> +        CLIENT_QUERY = 5;
> +
> +        // CLIENT_RESPONSE is a DNS response message sent from a DNS server to
> +        // a client, from the perspective of the DNS server. The DNS server
> +        // typically sets the RA (recursion available) bit when responding.
> +        CLIENT_RESPONSE = 6;
> +
> +        // FORWARDER_QUERY is a DNS query message sent from a downstream DNS
> +        // server to an upstream DNS server which is expected to perform
> +        // further recursion, from the perspective of the downstream DNS
> +        // server.
> +        FORWARDER_QUERY = 7;
> +
> +        // FORWARDER_RESPONSE is a DNS response message sent from an upstream
> +        // DNS server performing recursion to a downstream DNS server, from the
> +        // perspective of the downstream DNS server.
> +        FORWARDER_RESPONSE = 8;
> +
> +        // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS
> +        // server, from the perspective of the stub resolver.
> +        STUB_QUERY = 9;
> +
> +        // STUB_RESPONSE is a DNS response message sent from a DNS server to a
> +        // stub resolver, from the perspective of the stub resolver.
> +        STUB_RESPONSE = 10;
> +    }
> +
> +    // One of the Type values described above.
> +    required Type               type = 1;
> +
> +    // One of the SocketFamily values described above.
> +    optional SocketFamily       socket_family = 2;
> +
> +    // One of the SocketProtocol values described above.
> +    optional SocketProtocol     socket_protocol = 3;
> +
> +    // The network address of the message initiator.
> +    // For SocketFamily INET, this field is 4 octets (IPv4 address).
> +    // For SocketFamily INET6, this field is 16 octets (IPv6 address).
> +    optional bytes              query_address = 4;
> +
> +    // The network address of the message responder.
> +    // For SocketFamily INET, this field is 4 octets (IPv4 address).
> +    // For SocketFamily INET6, this field is 16 octets (IPv6 address).
> +    optional bytes              response_address = 5;
> +
> +    // The transport port of the message initiator.
> +    // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
> +    optional uint32             query_port = 6;
> +
> +    // The transport port of the message responder.
> +    // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
> +    optional uint32             response_port = 7;
> +
> +    // The time at which the DNS query message was sent or received, depending
> +    // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY.
> +    // This is the number of seconds since the UNIX epoch.
> +    optional uint64             query_time_sec = 8;
> +
> +    // The time at which the DNS query message was sent or received.
> +    // This is the seconds fraction, expressed as a count of nanoseconds.
> +    optional fixed32            query_time_nsec = 9;
> +
> +    // The initiator's original wire-format DNS query message, verbatim.
> +    optional bytes              query_message = 10;
> +
> +    // The "zone" or "bailiwick" pertaining to the DNS query message.
> +    // This is a wire-format DNS domain name.
> +    optional bytes              query_zone = 11;
> +
> +    // The time at which the DNS response message was sent or received,
> +    // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or
> +    // CLIENT_RESPONSE.
> +    // This is the number of seconds since the UNIX epoch.
> +    optional uint64             response_time_sec = 12;
> +
> +    // The time at which the DNS response message was sent or received.
> +    // This is the seconds fraction, expressed as a count of nanoseconds.
> +    optional fixed32            response_time_nsec = 13;
> +
> +    // The responder's original wire-format DNS response message, verbatim.
> +    optional bytes              response_message = 14;
> +}
> +
> +// All fields except for 'type' in the Message schema are optional.
> +// It is recommended that at least the following fields be filled in for
> +// particular types of Messages.
> +
> +// AUTH_QUERY:
> +//      socket_family, socket_protocol
> +//      query_address, query_port
> +//      query_message
> +//      query_time_sec, query_time_nsec
> +
> +// AUTH_RESPONSE:
> +//      socket_family, socket_protocol
> +//      query_address, query_port
> +//      query_time_sec, query_time_nsec
> +//      response_message
> +//      response_time_sec, response_time_nsec
> +
> +// RESOLVER_QUERY:
> +//      socket_family, socket_protocol
> +//      query_name, query_type, query_class
> +//      query_message
> +//      query_time_sec, query_time_nsec
> +//      query_zone
> +//      response_address, response_port
> +
> +// RESOLVER_RESPONSE:
> +//      socket_family, socket_protocol
> +//      query_name, query_type, query_class
> +//      query_time_sec, query_time_nsec
> +//      query_zone
> +//      response_address, response_port
> +//      response_message
> +//      response_time_sec, response_time_nsec
> +
> +// CLIENT_QUERY:
> +//      socket_family, socket_protocol
> +//      query_message
> +//      query_time_sec, query_time_nsec
> +
> +// CLIENT_RESPONSE:
> +//      socket_family, socket_protocol
> +//      query_time_sec, query_time_nsec
> +//      response_message
> +//      response_time_sec, response_time_nsec
> diff --git dnstap/dnstap_collector.c dnstap/dnstap_collector.c
> new file mode 100644
> index 00000000000..091113fc45f
> --- /dev/null
> +++ dnstap/dnstap_collector.c
> @@ -0,0 +1,516 @@
> +/*
> + * dnstap/dnstap_collector.c -- nsd collector process for dnstap information
> + *
> + * Copyright (c) 2018, NLnet Labs. All rights reserved.
> + *
> + * See LICENSE for the license.
> + *
> + */
> +
> +#include "config.h"
> +#include <sys/types.h>
> +#include <sys/socket.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#ifndef USE_MINI_EVENT
> +#  ifdef HAVE_EVENT_H
> +#    include <event.h>
> +#  else
> +#    include <event2/event.h>
> +#    include "event2/event_struct.h"
> +#    include "event2/event_compat.h"
> +#  endif
> +#else
> +#  include "mini_event.h"
> +#endif
> +#include "dnstap/dnstap_collector.h"
> +#include "dnstap/dnstap.h"
> +#include "util.h"
> +#include "nsd.h"
> +#include "region-allocator.h"
> +#include "buffer.h"
> +#include "namedb.h"
> +#include "options.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->dt_env = NULL;
> + dt_col->region = region_create(xalloc, free);
> + dt_col->send_buffer = buffer_create(dt_col->region,
> + /* msglen + is_response + addrlen + is_tcp + packetlen + packet + zonelen + zone + spare + addr */
> + 4+1+4+1+4+TCP_MAX_MESSAGE_LEN+4+MAXHOSTNAMELEN + 32 +
> +#ifdef INET6
> + sizeof(struct sockaddr_storage)
> +#else
> + sizeof(struct sockaddr_in)
> +#endif
> + );
> +
> + /* open pipes 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",
> + strerror(errno));
> + }
> + if(fcntl(fd[0], F_SETFL, O_NONBLOCK) == -1) {
> + log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
> + }
> + if(fcntl(fd[1], F_SETFL, O_NONBLOCK) == -1) {
> + log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
> + }
> + nsd->dt_collector_fd_recv[i] = fd[0];
> + nsd->dt_collector_fd_send[i] = fd[1];
> + }
> +
> + /* open socketpair */
> + if(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
> + error("dnstap_collector: cannot create socketpair: %s",
> + strerror(errno));
> + }
> + if(fcntl(sv[0], F_SETFL, O_NONBLOCK) == -1) {
> + log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
> + }
> + if(fcntl(sv[1], F_SETFL, O_NONBLOCK) == -1) {
> + log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
> + }
> + dt_col->cmd_socket_dt = sv[0];
> + dt_col->cmd_socket_nsd = sv[1];
> +
> + return dt_col;
> +}
> +
> +void dt_collector_destroy(struct dt_collector* dt_col, struct nsd* nsd)
> +{
> + if(!dt_col) return;
> + free(nsd->dt_collector_fd_recv);
> + nsd->dt_collector_fd_recv = NULL;
> + free(nsd->dt_collector_fd_send);
> + nsd->dt_collector_fd_send = NULL;
> + region_destroy(dt_col->region);
> + free(dt_col);
> +}
> +
> +void dt_collector_close(struct dt_collector* dt_col, struct nsd* nsd)
> +{
> + int i;
> + if(!dt_col) return;
> + if(dt_col->cmd_socket_dt != -1) {
> + close(dt_col->cmd_socket_dt);
> + dt_col->cmd_socket_dt = -1;
> + }
> + if(dt_col->cmd_socket_nsd != -1) {
> + close(dt_col->cmd_socket_nsd);
> + dt_col->cmd_socket_nsd = -1;
> + }
> + 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;
> + }
> + }
> +}
> +
> +/* handle command from nsd to dt collector.
> + * mostly, check for fd closed, this means we have to exit */
> +void
> +dt_handle_cmd_from_nsd(int ATTR_UNUSED(fd), short event, void* arg)
> +{
> + struct dt_collector* dt_col = (struct dt_collector*)arg;
> + if((event&EV_READ) != 0) {
> + event_base_loopexit(dt_col->event_base, NULL);
> + }
> +}
> +
> +/* read data from fd into buffer, true when message is complete */
> +static int read_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));
> + 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 + msglen)
> + return 0; /* read more msg later */
> +
> + /* msg complete */
> + buffer_flip(buf);
> + return 1;
> +}
> +
> +/* submit the content of the buffer received to dnstap */
> +static void
> +dt_submit_content(struct dt_env* dt_env, struct buffer* buf)
> +{
> + uint8_t is_response, is_tcp;
> +#ifdef INET6
> + struct sockaddr_storage addr;
> +#else
> + struct sockaddr_in addr;
> +#endif
> + socklen_t addrlen;
> + size_t pktlen;
> + uint8_t* data;
> + size_t zonelen;
> + uint8_t* zone;
> +
> + /* parse content from buffer */
> + if(!buffer_available(buf, 4+1+4)) return;
> + buffer_skip(buf, 4); /* skip msglen */
> + is_response = buffer_read_u8(buf);
> + addrlen = buffer_read_u32(buf);
> + if(addrlen > sizeof(addr)) return;
> + if(!buffer_available(buf, addrlen)) return;
> + buffer_read(buf, &addr, addrlen);
> + if(!buffer_available(buf, 1+4)) return;
> + is_tcp = buffer_read_u8(buf);
> + pktlen = buffer_read_u32(buf);
> + if(!buffer_available(buf, pktlen)) return;
> + data = buffer_current(buf);
> + buffer_skip(buf, pktlen);
> + if(!buffer_available(buf, 4)) return;
> + zonelen = buffer_read_u32(buf);
> + if(zonelen == 0) {
> + zone = NULL;
> + } else {
> + if(zonelen > MAXDOMAINLEN) return;
> + if(!buffer_available(buf, zonelen)) return;
> + zone = buffer_current(buf);
> + buffer_skip(buf, zonelen);
> + }
> +
> + /* submit it */
> + if(is_response) {
> + dt_msg_send_auth_response(dt_env, &addr, is_tcp, zone,
> + zonelen, data, pktlen);
> + } else {
> + dt_msg_send_auth_query(dt_env, &addr, is_tcp, zone,
> + zonelen, data, pktlen);
> + }
> +}
> +
> +/* handle input from worker for dnstap */
> +void
> +dt_handle_input(int fd, short event, void* arg)
> +{
> + 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))
> + return;
> +
> + /* once data is complete, write 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) {
> + dt_submit_content(dt_input->dt_collector->dt_env,
> + dt_input->buffer);
> + }
> +
> + /* clear buffer for next message */
> + buffer_clear(dt_input->buffer);
> + }
> +}
> +
> +/* init dnstap */
> +static void dt_init_dnstap(struct dt_collector* dt_col, struct nsd* nsd)
> +{
> + int num_workers = 1;
> +#ifdef HAVE_CHROOT
> + if(nsd->chrootdir && nsd->chrootdir[0]) {
> + int l = strlen(nsd->chrootdir)-1; /* ends in trailing slash */
> + if (nsd->options->dnstap_socket_path &&
> + nsd->options->dnstap_socket_path[0] == '/' &&
> + strncmp(nsd->options->dnstap_socket_path,
> + nsd->chrootdir, l) == 0)
> + nsd->options->dnstap_socket_path += l;
> + }
> +#endif
> + dt_col->dt_env = dt_create(nsd->options->dnstap_socket_path, num_workers);
> + if(!dt_col->dt_env) {
> + log_msg(LOG_ERR, "could not create dnstap env");
> + return;
> + }
> + dt_apply_cfg(dt_col->dt_env, nsd->options);
> + dt_init(dt_col->dt_env);
> +}
> +
> +/* cleanup dt collector process for exit */
> +static void dt_collector_cleanup(struct dt_collector* dt_col, struct nsd* nsd)
> +{
> + int i;
> + dt_delete(dt_col->dt_env);
> + event_del(dt_col->cmd_event);
> + for(i=0; i<dt_col->count; i++) {
> + event_del(dt_col->inputs[i].event);
> + }
> + dt_collector_close(dt_col, nsd);
> + event_base_free(dt_col->event_base);
> +#ifdef MEMCLEAN
> + free(dt_col->cmd_event);
> + if(dt_col->inputs) {
> + for(i=0; i<dt_col->count; i++) {
> + free(dt_col->inputs[i].event);
> + }
> + free(dt_col->inputs);
> + }
> + dt_collector_destroy(dt_col, nsd);
> +#endif
> +}
> +
> +/* attach events to the event base to listen to the workers and cmd channel */
> +static void dt_attach_events(struct dt_collector* dt_col, struct nsd* nsd)
> +{
> + int i;
> + /* create event base */
> + dt_col->event_base = nsd_child_event_base();
> + if(!dt_col->event_base) {
> + error("dnstap collector: event_base create failed");
> + }
> +
> + /* add command handler */
> + dt_col->cmd_event = (struct event*)xalloc_zero(
> + sizeof(*dt_col->cmd_event));
> + event_set(dt_col->cmd_event, dt_col->cmd_socket_dt,
> + EV_PERSIST|EV_READ, dt_handle_cmd_from_nsd, dt_col);
> + if(event_base_set(dt_col->event_base, dt_col->cmd_event) != 0)
> + log_msg(LOG_ERR, "dnstap collector: event_base_set failed");
> + if(event_add(dt_col->cmd_event, NULL) != 0)
> + log_msg(LOG_ERR, "dnstap collector: event_add failed");
> +
> + /* add worker input handlers */
> + dt_col->inputs = xalloc_array_zero(dt_col->count,
> + sizeof(*dt_col->inputs));
> + for(i=0; i<dt_col->count; i++) {
> + dt_col->inputs[i].dt_collector = dt_col;
> + dt_col->inputs[i].event = (struct event*)xalloc_zero(
> + sizeof(struct event));
> + event_set(dt_col->inputs[i].event,
> + nsd->dt_collector_fd_recv[i], EV_PERSIST|EV_READ,
> + dt_handle_input, &dt_col->inputs[i]);
> + if(event_base_set(dt_col->event_base,
> + dt_col->inputs[i].event) != 0)
> + log_msg(LOG_ERR, "dnstap collector: event_base_set failed");
> + if(event_add(dt_col->inputs[i].event, NULL) != 0)
> + log_msg(LOG_ERR, "dnstap collector: event_add failed");
> +
> + dt_col->inputs[i].buffer = buffer_create(dt_col->region,
> + /* msglen + is_response + addrlen + is_tcp + packetlen + packet + zonelen + zone + spare + addr */
> + 4+1+4+1+4+TCP_MAX_MESSAGE_LEN+4+MAXHOSTNAMELEN + 32 +
> +#ifdef INET6
> + sizeof(struct sockaddr_storage)
> +#else
> + sizeof(struct sockaddr_in)
> +#endif
> + );
> + assert(buffer_capacity(dt_col->inputs[i].buffer) ==
> + buffer_capacity(dt_col->send_buffer));
> + }
> +}
> +
> +/* the dnstap collector process main routine */
> +static void dt_collector_run(struct dt_collector* dt_col, struct nsd* nsd)
> +{
> + /* init dnstap */
> + VERBOSITY(1, (LOG_INFO, "dnstap collector started"));
> + dt_init_dnstap(dt_col, nsd);
> + dt_attach_events(dt_col, nsd);
> +
> + /* run */
> + if(event_base_loop(dt_col->event_base, 0) == -1) {
> + error("dnstap collector: event_base_loop failed");
> + }
> +
> + /* cleanup and done */
> + VERBOSITY(1, (LOG_INFO, "dnstap collector stopped"));
> + dt_collector_cleanup(dt_col, nsd);
> + exit(0);
> +}
> +
> +void dt_collector_start(struct dt_collector* dt_col, struct nsd* nsd)
> +{
> + /* fork */
> + dt_col->dt_pid = fork();
> + if(dt_col->dt_pid == -1) {
> + error("dnstap_collector: fork failed: %s", strerror(errno));
> + }
> + if(dt_col->dt_pid == 0) {
> + /* the dt collector process is this */
> + /* close the nsd side of the command channel */
> + close(dt_col->cmd_socket_nsd);
> + dt_col->cmd_socket_nsd = -1;
> + dt_collector_run(dt_col, nsd);
> + /* NOTREACH */
> + exit(0);
> + } else {
> + /* the parent continues on, with starting NSD */
> + /* close the dt side of the command channel */
> + close(dt_col->cmd_socket_dt);
> + dt_col->cmd_socket_dt = -1;
> + }
> +}
> +
> +/* put data for sending to the collector process into the buffer */
> +static int
> +prep_send_data(struct buffer* buf, uint8_t is_response,
> +#ifdef INET6
> + struct sockaddr_storage* addr,
> +#else
> + struct sockaddr_in* addr,
> +#endif
> + socklen_t addrlen, int is_tcp, struct buffer* packet,
> + struct zone* zone)
> +{
> + buffer_clear(buf);
> + if(!buffer_available(buf, 4+1+4+addrlen+1+4+buffer_remaining(packet)))
> + return 0; /* does not fit in send_buffer, log is dropped */
> + buffer_skip(buf, 4); /* the length of the message goes here */
> + buffer_write_u8(buf, is_response);
> + buffer_write_u32(buf, addrlen);
> + buffer_write(buf, addr, (size_t)addrlen);
> + buffer_write_u8(buf, (is_tcp?1:0));
> + buffer_write_u32(buf, buffer_remaining(packet));
> + buffer_write(buf, buffer_begin(packet), buffer_remaining(packet));
> + if(zone && zone->apex && domain_dname(zone->apex)) {
> + if(!buffer_available(buf, 4 + domain_dname(zone->apex)->name_size))
> + return 0;
> + buffer_write_u32(buf, domain_dname(zone->apex)->name_size);
> + buffer_write(buf, dname_name(domain_dname(zone->apex)),
> + domain_dname(zone->apex)->name_size);
> + } else {
> + if(!buffer_available(buf, 4))
> + return 0;
> + buffer_write_u32(buf, 0);
> + }
> +
> + buffer_flip(buf);
> + /* write length of message */
> + buffer_write_u32_at(buf, 0, buffer_remaining(buf)-4);
> + 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)
> +{
> + 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;
> + }
> + total += r;
> + }
> +}
> +
> +void dt_collector_submit_auth_query(struct nsd* nsd,
> +#ifdef INET6
> + struct sockaddr_storage* addr,
> +#else
> + struct sockaddr_in* addr,
> +#endif
> + socklen_t addrlen, int is_tcp, struct buffer* packet)
> +{
> + if(!nsd->dt_collector) return;
> + if(!nsd->options->dnstap_log_auth_query_messages) return;
> + VERBOSITY(4, (LOG_INFO, "dnstap submit auth query"));
> +
> + /* marshal data into send buffer */
> + if(!prep_send_data(nsd->dt_collector->send_buffer, 0, addr, addrlen,
> + is_tcp, packet, NULL))
> + 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));
> +}
> +
> +void dt_collector_submit_auth_response(struct nsd* nsd,
> +#ifdef INET6
> + struct sockaddr_storage* addr,
> +#else
> + struct sockaddr_in* addr,
> +#endif
> + socklen_t addrlen, int is_tcp, struct buffer* packet,
> + struct zone* zone)
> +{
> + if(!nsd->dt_collector) return;
> + if(!nsd->options->dnstap_log_auth_response_messages) return;
> + VERBOSITY(4, (LOG_INFO, "dnstap submit auth response"));
> +
> + /* marshal data into send buffer */
> + if(!prep_send_data(nsd->dt_collector->send_buffer, 1, addr, addrlen,
> + is_tcp, packet, zone))
> + 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));
> +}
> diff --git dnstap/dnstap_collector.h dnstap/dnstap_collector.h
> new file mode 100644
> index 00000000000..4e0825bbaea
> --- /dev/null
> +++ dnstap/dnstap_collector.h
> @@ -0,0 +1,92 @@
> +/*
> + * dnstap/dnstap_collector.h -- nsd collector process for dnstap information
> + *
> + * Copyright (c) 2018, NLnet Labs. All rights reserved.
> + *
> + * See LICENSE for the license.
> + *
> + */
> +
> +#ifndef DNSTAP_COLLECTOR_H
> +#define DNSTAP_COLLECTOR_H
> +struct dt_env;
> +struct nsd;
> +struct event_base;
> +struct event;
> +struct dt_collector_input;
> +struct zone;
> +struct buffer;
> +struct region;
> +
> +/* information for the dnstap collector process. It collects information
> + * for dnstap from the worker processes.  And writes them to the dnstap
> + * socket. */
> +struct dt_collector {
> + /* dnstap env for the write to the dnstap socket */
> + struct dt_env* dt_env;
> + /* number of workers to collect from */
> + int count;
> + /* socketpair for communication between (xfrd) and the
> + * dnstap collector process.  If closed, the collector process
> + * exits.  The collector closes the other side of the socketpair, so
> + * that if xfrd exits, so does the dnstap collector */
> + int cmd_socket_dt, cmd_socket_nsd;
> + /* the pid of the dt collector process (0 on that process) */
> + pid_t dt_pid;
> + /* in the collector process, the event base */
> + struct event_base* event_base;
> + /* in the collector process, the cmd handle event */
> + struct event* cmd_event;
> + /* in the collector process, array size count of input per worker */
> + struct dt_collector_input* inputs;
> + /* region for buffers */
> + struct region* region;
> + /* buffer for sending data to the collector */
> + struct buffer* send_buffer;
> +};
> +
> +/* information per worker to get input from that worker. */
> +struct dt_collector_input {
> + /* the collector this is part of (for use in callbacks) */
> + struct dt_collector* dt_collector;
> + /* the event to listen to the datagrams to process for that worker*/
> + struct event* event;
> + /* buffer to store the datagrams while they are read in */
> + struct buffer* buffer;
> +};
> +
> +/* create dt_collector process structure and dt_env */
> +struct dt_collector* dt_collector_create(struct nsd* nsd);
> +/* destroy the dt_collector structure */
> +void dt_collector_destroy(struct dt_collector* dt_col, struct nsd* nsd);
> +/* close file descriptors */
> +void dt_collector_close(struct dt_collector* dt_col, struct nsd* nsd);
> +/* start the collector process */
> +void dt_collector_start(struct dt_collector* dt_col, struct nsd* nsd);
> +
> +/* submit auth query from worker.  It attempts to send it to the collector,
> + * if the nonblocking fails, then it silently skips it.  So it does not block
> + * on the log.
> + */
> +void dt_collector_submit_auth_query(struct nsd* nsd,
> +#ifdef INET6
> + struct sockaddr_storage* addr,
> +#else
> + struct sockaddr_in* addr,
> +#endif
> + socklen_t addrlen, int is_tcp, struct buffer* packet);
> +
> +/* submit auth response from worker.  It attempts to send it to the collector,
> + * if the nonblocking fails, then it silently skips it.  So it does not block
> + * on the log.
> + */
> +void dt_collector_submit_auth_response(struct nsd* nsd,
> +#ifdef INET6
> + struct sockaddr_storage* addr,
> +#else
> + struct sockaddr_in* addr,
> +#endif
> + socklen_t addrlen, int is_tcp, struct buffer* packet,
> + struct zone* zone);
> +
> +#endif /* DNSTAP_COLLECTOR_H */
> diff --git dnstap/dnstap_config.h.in dnstap/dnstap_config.h.in
> new file mode 100644
> index 00000000000..c9f74893a1d
> --- /dev/null
> +++ dnstap/dnstap_config.h.in
> @@ -0,0 +1,17 @@
> +#ifndef UNBOUND_DNSTAP_CONFIG_H
> +#define UNBOUND_DNSTAP_CONFIG_H
> +
> +/*
> + * Process this file (dnstap_config.h.in) with AC_CONFIG_FILES to generate
> + * dnstap_config.h.
> + *
> + * This file exists so that USE_DNSTAP can be used without including config.h.
> + */
> +
> +#if @ENABLE_DNSTAP@ /* ENABLE_DNSTAP */
> +# ifndef USE_DNSTAP
> +#  define USE_DNSTAP 1
> +# endif
> +#endif
> +
> +#endif /* UNBOUND_DNSTAP_CONFIG_H */
> diff --git ipc.c ipc.c
> index 4da914d7ce8..46feb0a0129 100644
> --- ipc.c
> +++ ipc.c
> @@ -43,7 +43,7 @@ ipc_child_quit(struct nsd* nsd)
>  
>  #ifdef MEMCLEAN /* OS collects memory pages */
>  #ifdef RATELIMIT
> -        rrl_deinit(nsd->this_child->child_num);
> + rrl_deinit(nsd->this_child->child_num);
>  #endif
>   event_base_free(nsd->event_base);
>   region_destroy(nsd->server_region);
> @@ -646,13 +646,13 @@ void
>  xfrd_handle_ipc(int ATTR_UNUSED(fd), short event, void* arg)
>  {
>   xfrd_state_type* xfrd = (xfrd_state_type*)arg;
> -        if ((event & EV_READ))
> + if ((event & EV_READ))
>   {
>   /* first attempt to read as a signal from main
>   * could block further send operations */
>   xfrd_handle_ipc_read(&xfrd->ipc_handler, xfrd);
>   }
> -        if ((event & EV_WRITE))
> + if ((event & EV_WRITE))
>   {
>   if(xfrd->ipc_send_blocked) { /* wait for RELOAD_DONE */
>   ipc_xfrd_set_listening(xfrd, EV_PERSIST|EV_READ);
> @@ -681,8 +681,8 @@ xfrd_handle_ipc(int ATTR_UNUSED(fd), short event, void* arg)
>  static void
>  xfrd_handle_ipc_read(struct event* handler, xfrd_state_type* xfrd)
>  {
> -        sig_atomic_t cmd;
> -        int len;
> + sig_atomic_t cmd;
> + int len;
>  
>   if(xfrd->ipc_conn->is_reading==2) {
>   buffer_type* tmp = xfrd->ipc_pass;
> @@ -730,26 +730,26 @@ xfrd_handle_ipc_read(struct event* handler, xfrd_state_type* xfrd)
>   return;
>   }
>  
> -        if((len = read(handler->ev_fd, &cmd, sizeof(cmd))) == -1) {
> + if((len = read(handler->ev_fd, &cmd, sizeof(cmd))) == -1) {
>   if(errno != EINTR && errno != EAGAIN)
> -                 log_msg(LOG_ERR, "xfrd_handle_ipc: read: %s",
> -                         strerror(errno));
> -                return;
> -        }
> -        if(len == 0)
> -        {
> + log_msg(LOG_ERR, "xfrd_handle_ipc: read: %s",
> + strerror(errno));
> + return;
> + }
> + if(len == 0)
> + {
>   /* parent closed the connection. Quit */
>   DEBUG(DEBUG_IPC,1, (LOG_INFO, "xfrd: main closed connection."));
>   xfrd->shutdown = 1;
>   return;
> -        }
> + }
>  
> -        switch(cmd) {
> -        case NSD_QUIT:
> -        case NSD_SHUTDOWN:
> + switch(cmd) {
> + case NSD_QUIT:
> + case NSD_SHUTDOWN:
>   DEBUG(DEBUG_IPC,1, (LOG_INFO, "xfrd: main sent shutdown cmd."));
> -                xfrd->shutdown = 1;
> -                break;
> + xfrd->shutdown = 1;
> + break;
>   case NSD_RELOAD_DONE:
>   /* reload has finished */
>   DEBUG(DEBUG_IPC,1, (LOG_INFO, "xfrd: ipc recv RELOAD_DONE"));
> @@ -785,11 +785,11 @@ xfrd_handle_ipc_read(struct event* handler, xfrd_state_type* xfrd)
>   ipc_xfrd_set_listening(xfrd, EV_PERSIST|EV_READ|EV_WRITE);
>   xfrd->need_to_send_quit = 1;
>   break;
> -        default:
> -                log_msg(LOG_ERR, "xfrd_handle_ipc: bad mode %d (%d)", (int)cmd,
> + default:
> + log_msg(LOG_ERR, "xfrd_handle_ipc: bad mode %d (%d)", (int)cmd,
>   (int)ntohl(cmd));
> -                break;
> -        }
> + break;
> + }
>  
>   if(xfrd->ipc_conn->is_reading) {
>   /* setup read of info */
> diff --git nsd-checkconf.8.in nsd-checkconf.8.in
> index d9d7bd5342d..dccf95d690d 100644
> --- nsd-checkconf.8.in
> +++ nsd-checkconf.8.in
> @@ -1,4 +1,4 @@
> -.TH "nsd\-checkconf" "8" "Sep 25, 2018" "NLnet Labs" "nsd 4.1.25"
> +.TH "nsd\-checkconf" "8" "Dec  4, 2018" "NLnet Labs" "nsd 4.1.26"
>  .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
>  .\" See LICENSE for the license.
>  .SH "NAME"
> diff --git nsd-checkconf.c nsd-checkconf.c
> index f4044c42295..20eb42a8cf1 100644
> --- nsd-checkconf.c
> +++ nsd-checkconf.c
> @@ -404,6 +404,16 @@ config_print_zone(nsd_options_type* opt, const char* k, int s, const char *o,
>   SERV_GET_INT(rrl_ipv4_prefix_length, o);
>   SERV_GET_INT(rrl_ipv6_prefix_length, o);
>   SERV_GET_INT(rrl_whitelist_ratelimit, o);
> +#endif
> +#ifdef USE_DNSTAP
> + SERV_GET_BIN(dnstap_enable, o);
> + SERV_GET_STR(dnstap_socket_path, o);
> + SERV_GET_BIN(dnstap_send_identity, o);
> + SERV_GET_BIN(dnstap_send_version, o);
> + SERV_GET_STR(dnstap_identity, o);
> + SERV_GET_STR(dnstap_version, o);
> + SERV_GET_BIN(dnstap_log_auth_query_messages, o);
> + SERV_GET_BIN(dnstap_log_auth_response_messages, o);
>  #endif
>   SERV_GET_INT(zonefiles_write, o);
>   /* remote control */
> @@ -527,6 +537,18 @@ config_test_print_server(nsd_options_type* opt)
>   printf("\tzonefiles-check: %s\n", opt->zonefiles_check?"yes":"no");
>   printf("\tzonefiles-write: %d\n", opt->zonefiles_write);
>  
> +#ifdef USE_DNSTAP
> + printf("\ndnstap:\n");
> + printf("\tdnstap-enable: %s\n", opt->dnstap_enable?"yes":"no");
> + print_string_var("dnstap-socket-path:", opt->dnstap_socket_path);
> + printf("\tdnstap-send-identity: %s\n", opt->dnstap_send_identity?"yes":"no");
> + printf("\tdnstap-send-version: %s\n", opt->dnstap_send_version?"yes":"no");
> + print_string_var("dnstap-identity:", opt->dnstap_identity);
> + print_string_var("dnstap-version:", opt->dnstap_version);
> + printf("\tdnstap-log-auth-query-messages: %s\n", opt->dnstap_log_auth_query_messages?"yes":"no");
> + printf("\tdnstap-log-auth-response-messages: %s\n", opt->dnstap_log_auth_response_messages?"yes":"no");
> +#endif
> +
>   printf("\nremote-control:\n");
>   printf("\tcontrol-enable: %s\n", opt->control_enable?"yes":"no");
>   for(ip = opt->control_interface; ip; ip=ip->next)
> diff --git nsd-checkzone.8.in nsd-checkzone.8.in
> index da43863aeb5..afadb4c9215 100644
> --- nsd-checkzone.8.in
> +++ nsd-checkzone.8.in
> @@ -1,4 +1,4 @@
> -.TH "nsd\-checkzone" "8" "Sep 25, 2018" "NLnet Labs" "nsd 4.1.25"
> +.TH "nsd\-checkzone" "8" "Dec  4, 2018" "NLnet Labs" "nsd 4.1.26"
>  .\" Copyright (c) 2014, NLnet Labs. All rights reserved.
>  .\" See LICENSE for the license.
>  .SH "NAME"
> diff --git nsd-control.8.in nsd-control.8.in
> index cabaf3d72e9..40ca171c2c4 100644
> --- nsd-control.8.in
> +++ nsd-control.8.in
> @@ -1,4 +1,4 @@
> -.TH "nsd\-control" "8" "Sep 25, 2018" "NLnet Labs" "nsd 4.1.25"
> +.TH "nsd\-control" "8" "Dec  4, 2018" "NLnet Labs" "nsd 4.1.26"
>  .\" Copyright (c) 2011, NLnet Labs. All rights reserved.
>  .\" See LICENSE for the license.
>  .SH "NAME"
> @@ -49,7 +49,8 @@ loads it.
>  .B reconfig
>  Reload nsd.conf and apply changes to TSIG keys and configuration patterns,
>  and apply the changes to add and remove zones that are mentioned in the config.
> -Other changes are not applied, such as listening ip address and port and chroot.
> +Other changes are not applied, such as listening ip address and port and chroot,
> +also per-zone statistics are not applied.
>  The pattern updates means that the configuration options for
>  zones (request\-xfr, zonefile, notify, ...) are updated.  Also new
>  patterns are available for use with the addzone command.
> @@ -88,6 +89,12 @@ inside nsd.conf itself cannot be removed this way because the daemon
>  does not write to the nsd.conf file, you need to add such zones to the
>  zonelist file to be able to delete them with the delzone command.
>  .TP
> +.B changezone <zone name> <pattern name>
> +Change a zone to use the pattern for options.  The zone is deleted and added
> +in one operation, changing it to use the new pattern for the zone options.
> +Zones configured in nsd.conf cannot be changed like this, instead edit
> +the nsd.conf (or the included file in nsd.conf) and reconfig.
> +.TP
>  .B addzones
>  Add zones read from stdin of nsd\-control.  Input is read per line,
>  with name space patternname on a line.  For bulk additions.
> diff --git nsd-control.c nsd-control.c
> index f86a5f779fc..b83fc0c2fe3 100644
> --- nsd-control.c
> +++ nsd-control.c
> @@ -91,6 +91,7 @@ usage()
>   printf("  stats_noreset peek at statistics\n");
>   printf("  addzone <name> <pattern> add a new zone\n");
>   printf("  delzone <name> remove a zone\n");
> + printf("  changezone <name> <pattern> change zone to use pattern\n");
>   printf("  addzones add zone list on stdin {name space pattern newline}\n");
>   printf("  delzones remove zone list on stdin {name newline}\n");
>   printf("  write [<zone>] write changed zonefiles to disk\n");
> diff --git nsd.8.in nsd.8.in
> index 46425aea47a..afb19cbfe66 100644
> --- nsd.8.in
> +++ nsd.8.in
> @@ -1,9 +1,9 @@
> -.TH "NSD" "8" "Sep 25, 2018" "NLnet Labs" "NSD 4.1.25"
> +.TH "NSD" "8" "Dec  4, 2018" "NLnet Labs" "NSD 4.1.26"
>  .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
>  .\" See LICENSE for the license.
>  .SH "NAME"
>  .B nsd
> -\- Name Server Daemon (NSD) version 4.1.25.
> +\- Name Server Daemon (NSD) version 4.1.26.
>  .SH "SYNOPSIS"
>  .B nsd
>  .RB [ \-4 ]
> diff --git nsd.c nsd.c
> index b3d499b8c9a..57bae3232d7 100644
> --- nsd.c
> +++ nsd.c
> @@ -45,6 +45,9 @@
>  #include "tsig.h"
>  #include "remote.h"
>  #include "xfrd-disk.h"
> +#ifdef USE_DNSTAP
> +#include "dnstap/dnstap_collector.h"
> +#endif
>  
>  /* The server handler... */
>  struct nsd nsd;
> @@ -1102,6 +1105,12 @@ main(int argc, char *argv[])
>   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);
> diff --git nsd.conf.5.in nsd.conf.5.in
> index 9aaddeead28..348fcda8fe3 100644
> --- nsd.conf.5.in
> +++ nsd.conf.5.in
> @@ -1,4 +1,4 @@
> -.TH "nsd.conf" "5" "Sep 25, 2018" "NLnet Labs" "nsd 4.1.25"
> +.TH "nsd.conf" "5" "Dec  4, 2018" "NLnet Labs" "nsd 4.1.26"
>  .\" Copyright (c) 2001\-2008, NLnet Labs. All rights reserved.
>  .\" See LICENSE for the license.
>  .SH "NAME"
> diff --git nsd.conf.sample.in nsd.conf.sample.in
> index f5cd7b1da9a..bd3a6e89cab 100644
> --- nsd.conf.sample.in
> +++ nsd.conf.sample.in
> @@ -177,6 +177,18 @@ server:
>   # rrl-whitelist-ratelimit: 2000
>   # RRLend
>  
> +# DNSTAP config section, if compiled with that
> +# dnstap:
> + # set this to yes and set one or more of dnstap-log-..-messages to yes.
> + # dnstap-enable: no
> + # dnstap-socket-path: "/var/run/dnstap.sock"
> + # dnstap-send-identity: no
> + # dnstap-send-version: no
> + # dnstap-identity: ""
> + # dnstap-version: ""
> + # dnstap-log-auth-query-messages: no
> + # dnstap-log-auth-response-messages: no
> +
>  # Remote control config section.
>  remote-control:
>   # Enable remote control with nsd-control(8) here.
> diff --git nsd.h nsd.h
> index 903dc814282..c900ca6cbaa 100644
> --- nsd.h
> +++ nsd.h
> @@ -18,6 +18,9 @@ struct netio_handler;
>  struct nsd_options;
>  struct udb_base;
>  struct daemon_remote;
> +#ifdef USE_DNSTAP
> +struct dt_collector;
> +#endif
>  
>  /* The NSD runtime states and NSD ipc command values */
>  #define NSD_RUN 0
> @@ -260,6 +263,13 @@ struct nsd
>   /* current zonestat array to use */
>   struct nsdst* zonestatnow;
>  #endif /* BIND8_STATS */
> +#ifdef USE_DNSTAP
> + /* 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. */
> + int *dt_collector_fd_send, *dt_collector_fd_recv;
> +#endif /* USE_DNSTAP */
>   /* ratelimit for errors, time value */
>   time_t err_limit_time;
>   /* ratelimit for errors, packet count */
> diff --git options.c options.c
> index d9028c7305d..d28a2c45dbb 100644
> --- options.c
> +++ options.c
> @@ -97,6 +97,16 @@ nsd_options_create(region_type* region)
>   opt->rrl_ratelimit = RRL_LIMIT/2;
>   opt->rrl_whitelist_ratelimit = RRL_WLIST_LIMIT/2;
>  #  endif
> +#endif
> +#ifdef USE_DNSTAP
> + opt->dnstap_enable = 0;
> + opt->dnstap_socket_path = DNSTAP_SOCKET_PATH;
> + opt->dnstap_send_identity = 0;
> + opt->dnstap_send_version = 0;
> + opt->dnstap_identity = NULL;
> + opt->dnstap_version = NULL;
> + opt->dnstap_log_auth_query_messages = 0;
> + opt->dnstap_log_auth_response_messages = 0;
>  #endif
>   opt->zonefiles_check = 1;
>   if(opt->database == NULL || opt->database[0] == 0)
> diff --git options.h options.h
> index 3b1ad62b9db..a83eb383e47 100644
> --- options.h
> +++ options.h
> @@ -125,6 +125,22 @@ struct nsd_options {
>   /** max qps for whitelisted queries, 0 is nolimit */
>   size_t rrl_whitelist_ratelimit;
>  #endif
> + /** if dnstap is enabled */
> + int dnstap_enable;
> + /** dnstap socket path */
> + char* dnstap_socket_path;
> + /** true to send "identity" via dnstap */
> + int dnstap_send_identity;
> + /** true to send "version" via dnstap */
> + int dnstap_send_version;
> + /** dnstap "identity", hostname is used if "". */
> + char* dnstap_identity;
> + /** dnstap "version", package version is used if "". */
> + char* dnstap_version;
> + /** true to log dnstap AUTH_QUERY message events */
> + int dnstap_log_auth_query_messages;
> + /** true to log dnstap AUTH_RESPONSE message events */
> + int dnstap_log_auth_response_messages;
>  
>   region_type* region;
>  };
> diff --git remote.c remote.c
> index e218ba4cc64..5cc36489d37 100644
> --- remote.c
> +++ remote.c
> @@ -1250,6 +1250,91 @@ zonestat_inc_ifneeded(xfrd_state_type* xfrd)
>  #endif /* USE_ZONE_STATS */
>  }
>  
> +/** perform the changezone command for one zone */
> +static int
> +perform_changezone(RES* ssl, xfrd_state_type* xfrd, char* arg)
> +{
> + const dname_type* dname;
> + struct zone_options* zopt;
> + char* arg2 = NULL;
> + if(!find_arg2(ssl, arg, &arg2))
> + return 0;
> +
> + /* if we add it to the xfrd now, then xfrd could download AXFR and
> + * store it and the NSD-reload would see it in the difffile before
> + * it sees the add-config task.
> + */
> + /* thus: AXFRs and IXFRs must store the pattern name in the
> + * difffile, so that it can be added when the AXFR or IXFR is seen.
> + */
> +
> + /* check that the pattern exists */
> + if(!rbtree_search(xfrd->nsd->options->patterns, arg2)) {
> + (void)ssl_printf(ssl, "error pattern %s does not exist\n",
> + arg2);
> + return 0;
> + }
> +
> + dname = dname_parse(xfrd->region, arg);
> + if(!dname) {
> + (void)ssl_printf(ssl, "error cannot parse zone name\n");
> + return 0;
> + }
> +
> + /* see if zone is a duplicate */
> + if( (zopt=zone_options_find(xfrd->nsd->options, dname)) ) {
> + if(zopt->part_of_config) {
> + (void)ssl_printf(ssl, "error zone defined in nsd.conf, "
> +  "cannot delete it in this manner: remove it from "
> +  "nsd.conf yourself and repattern\n");
> + region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
> + dname = NULL;
> + return 0;
> + }
> + /* found the zone, now delete it */
> + /* create deletion task */
> + /* this deletion task is processed before the addition task,
> + * that is created below, in the same reload process, causing
> + * a seamless change from one to the other, with no downtime
> + * for the zone. */
> + task_new_del_zone(xfrd->nsd->task[xfrd->nsd->mytask],
> + xfrd->last_task, dname);
> + xfrd_set_reload_now(xfrd);
> + /* delete it in xfrd */
> + if(zone_is_slave(zopt)) {
> + xfrd_del_slave_zone(xfrd, dname);
> + }
> + xfrd_del_notify(xfrd, dname);
> + /* delete from config */
> + zone_list_del(xfrd->nsd->options, zopt);
> + } else {
> + (void)ssl_printf(ssl, "zone %s did not exist, creating", arg);
> + }
> + region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
> + dname = NULL;
> +
> + /* add to zonelist and adds to config in memory */
> + zopt = zone_list_add(xfrd->nsd->options, arg, arg2);
> + if(!zopt) {
> + /* also dname parse error here */
> + (void)ssl_printf(ssl, "error could not add zonelist entry\n");
> + return 0;
> + }
> + /* make addzone task and schedule reload */
> + task_new_add_zone(xfrd->nsd->task[xfrd->nsd->mytask],
> + xfrd->last_task, arg, arg2,
> + getzonestatid(xfrd->nsd->options, zopt));
> + zonestat_inc_ifneeded(xfrd);
> + xfrd_set_reload_now(xfrd);
> + /* add to xfrd - notify (for master and slaves) */
> + init_notify_send(xfrd->notify_zones, xfrd->region, zopt);
> + /* add to xfrd - slave */
> + if(zone_is_slave(zopt)) {
> + xfrd_init_slave_zone(xfrd, zopt);
> + }
> + return 1;
> +}
> +
>  /** perform the addzone command for one zone */
>  static int
>  perform_addzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
> @@ -1334,7 +1419,7 @@ perform_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
>   /* nothing to do */
>   if(!ssl_printf(ssl, "warning zone %s not present\n", arg))
>   return 0;
> - return 1;
> + return 0;
>   }
>  
>   /* see if it can be deleted */
> @@ -1381,6 +1466,15 @@ do_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
>   send_ok(ssl);
>  }
>  
> +/** do the changezone command */
> +static void
> +do_changezone(RES* ssl, xfrd_state_type* xfrd, char* arg)
> +{
> + if(!perform_changezone(ssl, xfrd, arg))
> + return;
> + send_ok(ssl);
> +}
> +
>  /** do the addzones command */
>  static void
>  do_addzones(RES* ssl, xfrd_state_type* xfrd)
> @@ -1867,6 +1961,8 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd, struct rc_state* rs)
>   do_addzone(ssl, rc->xfrd, skipwhite(p+7));
>   } else if(cmdcmp(p, "delzone", 7)) {
>   do_delzone(ssl, rc->xfrd, skipwhite(p+7));
> + } else if(cmdcmp(p, "changezone", 10)) {
> + do_changezone(ssl, rc->xfrd, skipwhite(p+10));
>   } else if(cmdcmp(p, "addzones", 8)) {
>   do_addzones(ssl, rc->xfrd);
>   } else if(cmdcmp(p, "delzones", 8)) {
> diff --git server.c server.c
> index af2f60f243a..edde352117b 100644
> --- server.c
> +++ server.c
> @@ -65,6 +65,9 @@
>  #include "remote.h"
>  #include "lookup3.h"
>  #include "rrl.h"
> +#ifdef USE_DNSTAP
> +#include "dnstap/dnstap_collector.h"
> +#endif
>  
>  #define RELOAD_SYNC_TIMEOUT 25 /* seconds */
>  
> @@ -599,6 +602,26 @@ server_init_ifs(struct nsd *nsd, size_t from, size_t to, int* reuseport_works)
>   }
>  
>  #ifdef SO_REUSEPORT
> +#  ifdef SO_REUSEPORT_LB
> + /* on FreeBSD 12 we have SO_REUSEPORT_LB that does loadbalance
> + * like SO_REUSEPORT on Linux.  This is what the users want
> + * with the config option in nsd.conf; if we actually
> + * need local address and port reuse they'll also need to
> + * have SO_REUSEPORT set for them, assume it was _LB they want.
> + */
> + if(nsd->reuseport && *reuseport_works &&
> + setsockopt(nsd->udp[i].s, SOL_SOCKET, SO_REUSEPORT_LB,
> + (void*)&on, (socklen_t)sizeof(on)) < 0) {
> + if(verbosity >= 3
> +#ifdef ENOPROTOOPT
> + || errno != ENOPROTOOPT
> +#endif
> + )
> +    log_msg(LOG_ERR, "setsockopt(..., SO_REUSEPORT_LB, "
> + "...) failed: %s", strerror(errno));
> + *reuseport_works = 0;
> + }
> +#  else /* SO_REUSEPORT_LB */
>   if(nsd->reuseport && *reuseport_works &&
>   setsockopt(nsd->udp[i].s, SOL_SOCKET, SO_REUSEPORT,
>   (void*)&on, (socklen_t)sizeof(on)) < 0) {
> @@ -611,6 +634,7 @@ server_init_ifs(struct nsd *nsd, size_t from, size_t to, int* reuseport_works)
>   "...) failed: %s", strerror(errno));
>   *reuseport_works = 0;
>   }
> +#  endif /* SO_REUSEPORT_LB */
>  #else
>   (void)reuseport_works;
>  #endif /* SO_REUSEPORT */
> @@ -1088,6 +1112,9 @@ server_shutdown(struct nsd *nsd)
>  #ifdef MEMCLEAN /* OS collects memory pages */
>  #ifdef RATELIMIT
>   rrl_mmap_deinit_keep_mmap();
> +#endif
> +#ifdef USE_DNSTAP
> + dt_collector_destroy(nsd->dt_collector, nsd);
>  #endif
>   udb_base_free_keep_mmap(nsd->task[0]);
>   udb_base_free_keep_mmap(nsd->task[1]);
> @@ -1903,6 +1930,9 @@ server_main(struct nsd *nsd)
>   unlink(nsd->zonestatfname[0]);
>   unlink(nsd->zonestatfname[1]);
>  #endif
> +#ifdef USE_DNSTAP
> + dt_collector_close(nsd->dt_collector, nsd);
> +#endif
>  
>   if(reload_listener.fd != -1) {
>   sig_atomic_t cmd = NSD_QUIT;
> @@ -2215,6 +2245,7 @@ handle_udp(int fd, short event, void* arg)
>   for (i = 0; i < recvcount; i++) {
>   loopstart:
>   received = msgs[i].msg_len;
> + queries[i]->addrlen = msgs[i].msg_hdr.msg_namelen;
>   q = queries[i];
>   if (received == -1) {
>   log_msg(LOG_ERR, "recvmmsg %d failed %s", i, strerror(
> @@ -2223,6 +2254,7 @@ handle_udp(int fd, short event, void* arg)
>   /* No zone statup */
>   query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
>   iovecs[i].iov_len = buffer_remaining(q->packet);
> + msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
>   goto swap_drop;
>   }
>  
> @@ -2237,6 +2269,10 @@ handle_udp(int fd, short event, void* arg)
>  
>   buffer_skip(q->packet, received);
>   buffer_flip(q->packet);
> +#ifdef USE_DNSTAP
> + dt_collector_submit_auth_query(data->nsd, &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) {
> @@ -2267,9 +2303,15 @@ handle_udp(int fd, short event, void* arg)
>   ZTATUP(data->nsd, q->zone, truncated);
>   }
>  #endif /* BIND8_STATS */
> +#ifdef USE_DNSTAP
> + dt_collector_submit_auth_response(data->nsd,
> + &q->addr, q->addrlen, q->tcp, q->packet,
> + q->zone);
> +#endif /* USE_DNSTAP */
>   } else {
>   query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
>   iovecs[i].iov_len = buffer_remaining(q->packet);
> + msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
>   swap_drop:
>   STATUP(data->nsd, dropped);
>   ZTATUP(data->nsd, q->zone, dropped);
> @@ -2310,6 +2352,7 @@ handle_udp(int fd, short event, void* arg)
>   for(i=0; i<recvcount; i++) {
>   query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
>   iovecs[i].iov_len = buffer_remaining(queries[i]->packet);
> + msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
>   }
>  }
>  
> @@ -2350,13 +2393,15 @@ handle_udp(int fd, short event, void* arg)
>   }
>   for (i = 0; i < recvcount; i++) {
>   received = msgs[i].msg_len;
> - msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
> + queries[i]->addrlen = msgs[i].msg_hdr.msg_namelen;
>   if (received == -1) {
>   log_msg(LOG_ERR, "recvmmsg failed");
>   STATUP(data->nsd, rxerr);
>   /* No zone statup */
>   /* the error can be found in msgs[i].msg_hdr.msg_flags */
>   query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
> + iovecs[i].iov_len = buffer_remaining(queries[i]->packet);
> + msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
>   continue;
>   }
>   q = queries[i];
> @@ -2394,6 +2439,10 @@ handle_udp(int fd, short event, void* arg)
>  
>   buffer_skip(q->packet, received);
>   buffer_flip(q->packet);
> +#ifdef USE_DNSTAP
> + dt_collector_submit_auth_query(data->nsd, &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) {
> @@ -2440,6 +2489,11 @@ handle_udp(int fd, short event, void* arg)
>   ZTATUP(data->nsd, q->zone, truncated);
>   }
>  #endif /* BIND8_STATS */
> +#ifdef USE_DNSTAP
> + dt_collector_submit_auth_response(data->nsd,
> + &q->addr, q->addrlen, q->tcp,
> + q->packet, q->zone);
> +#endif /* USE_DNSTAP */
>   }
>   } else {
>   STATUP(data->nsd, dropped);
> @@ -2448,6 +2502,8 @@ handle_udp(int fd, short event, void* arg)
>  #ifndef NONBLOCKING_IS_BROKEN
>  #ifdef HAVE_RECVMMSG
>   query_reset(queries[i], UDP_MAX_MESSAGE_LEN, 0);
> + iovecs[i].iov_len = buffer_remaining(queries[i]->packet);
> + msgs[i].msg_hdr.msg_namelen = queries[i]->addrlen;
>  #endif
>   }
>  #endif
> @@ -2633,6 +2689,10 @@ handle_tcp_reading(int fd, short event, void* arg)
>   data->query_count++;
>  
>   buffer_flip(data->query->packet);
> +#ifdef USE_DNSTAP
> + dt_collector_submit_auth_query(data->nsd, &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);
>   if (data->query_state == QUERY_DISCARDED) {
>   /* Drop the packet and the entire connection... */
> @@ -2668,6 +2728,11 @@ handle_tcp_reading(int fd, short event, void* arg)
>   /* Switch to the tcp write handler.  */
>   buffer_flip(data->query->packet);
>   data->query->tcplen = buffer_remaining(data->query->packet);
> +#ifdef USE_DNSTAP
> + dt_collector_submit_auth_response(data->nsd, &data->query->addr,
> + data->query->addrlen, data->query->tcp, data->query->packet,
> + data->query->zone);
> +#endif /* USE_DNSTAP */
>   data->bytes_transmitted = 0;
>  
>   timeout.tv_sec = data->tcp_timeout / 1000;
> diff --git xfrd.c xfrd.c
> index a23001fbc5f..c2eeff8a1e8 100644
> --- xfrd.c
> +++ xfrd.c
> @@ -30,6 +30,9 @@
>  #include "ipc.h"
>  #include "remote.h"
>  #include "rrl.h"
> +#ifdef USE_DNSTAP
> +#include "dnstap/dnstap_collector.h"
> +#endif
>  
>  #ifdef HAVE_SYSTEMD
>  #include <systemd/sd-daemon.h>
> @@ -398,6 +401,9 @@ xfrd_shutdown()
>  #ifdef HAVE_SSL
>   daemon_remote_delete(xfrd->nsd->rc); /* ssl-delete secret keys */
>  #endif
> +#ifdef USE_DNSTAP
> + dt_collector_close(nsd.dt_collector, &nsd);
> +#endif
>  
>   /* process-exit cleans up memory used by xfrd process */
>   DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd shutdown complete"));
> @@ -429,6 +435,9 @@ xfrd_shutdown()
>   }
>  #ifdef RATELIMIT
>   rrl_mmap_deinit();
> +#endif
> +#ifdef USE_DNSTAP
> + dt_collector_destroy(nsd.dt_collector, &nsd);
>  #endif
>   udb_base_free(nsd.task[0]);
>   udb_base_free(nsd.task[1]);
> diff --git zparser.y zparser.y
> index c301c9eda78..d19387d91a5 100644
> --- zparser.y
> +++ zparser.y
> @@ -298,25 +298,57 @@ rel_dname: label
>  
>  wire_dname: wire_abs_dname
>      | wire_rel_dname
> +    {
> +    /* terminate in root label and copy the origin in there */
> +    if(parser->origin && domain_dname(parser->origin)) {
> +    $$.len = $1.len + domain_dname(parser->origin)->name_size;
> +    if ($$.len > MAXDOMAINLEN)
> +    zc_error("domain name exceeds %d character limit",
> +     MAXDOMAINLEN);
> +    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
> +    memmove($$.str, $1.str, $1.len);
> +    memmove($$.str + $1.len, dname_name(domain_dname(parser->origin)),
> + domain_dname(parser->origin)->name_size);
> +    } else {
> +    $$.len = $1.len + 1;
> +    if ($$.len > MAXDOMAINLEN)
> +    zc_error("domain name exceeds %d character limit",
> +     MAXDOMAINLEN);
> +    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
> +    memmove($$.str, $1.str, $1.len);
> +    $$.str[ $1.len ] = 0;
> +    }
> +    }
>      ;
>  
>  wire_abs_dname: '.'
>      {
> -    char *result = (char *) region_alloc(parser->rr_region, 2);
> +    char *result = (char *) region_alloc(parser->rr_region, 1);
>      result[0] = 0;
> -    result[1] = '\0';
>      $$.str = result;
>      $$.len = 1;
>      }
> +    | '@'
> +    {
> +    if(parser->origin && domain_dname(parser->origin)) {
> +    $$.len = domain_dname(parser->origin)->name_size;
> +    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
> +    memmove($$.str, dname_name(domain_dname(parser->origin)), $$.len);
> +    } else {
> +    $$.len = 1;
> +    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
> +    $$.str[0] = 0;
> +    }
> +    }
>      | wire_rel_dname '.'
>      {
> -    char *result = (char *) region_alloc(parser->rr_region,
> - $1.len + 2);
> -    memcpy(result, $1.str, $1.len);
> -    result[$1.len] = 0;
> -    result[$1.len+1] = '\0';
> -    $$.str = result;
>      $$.len = $1.len + 1;
> +    if ($$.len > MAXDOMAINLEN)
> +    zc_error("domain name exceeds %d character limit",
> +     MAXDOMAINLEN);
> +    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
> +    memcpy($$.str, $1.str, $1.len);
> +    $$.str[$1.len] = 0;
>      }
>      ;
>  
> @@ -330,7 +362,7 @@ wire_label: STR
>  
>      /* make label anyway */
>      result[0] = $1.len;
> -    memcpy(result+1, $1.str, $1.len);
> +    memmove(result+1, $1.str, $1.len);
>  
>      $$.str = result;
>      $$.len = $1.len + 1;
> @@ -340,16 +372,13 @@ wire_label: STR
>  wire_rel_dname: wire_label
>      | wire_rel_dname '.' wire_label
>      {
> -    if ($1.len + $3.len - 3 > MAXDOMAINLEN)
> +    $$.len = $1.len + $3.len;
> +    if ($$.len > MAXDOMAINLEN)
>      zc_error("domain name exceeds %d character limit",
>       MAXDOMAINLEN);
> -
> -    /* make dname anyway */
> -    $$.len = $1.len + $3.len;
> -    $$.str = (char *) region_alloc(parser->rr_region, $$.len + 1);
> -    memcpy($$.str, $1.str, $1.len);
> -    memcpy($$.str + $1.len, $3.str, $3.len);
> -    $$.str[$$.len] = '\0';
> +    $$.str = (char *) region_alloc(parser->rr_region, $$.len);
> +    memmove($$.str, $1.str, $1.len);
> +    memmove($$.str + $1.len, $3.str, $3.len);
>      }
>      ;
>  
>
>
> --
> I'm not entirely sure you are real.
>

--
I'm not entirely sure you are real.

Reply | Threaded
Open this post in threaded view
|

Re: nsd 4.1.26

Stuart Henderson
In reply to this post by Florian Obser-2
On 2018/12/06 11:02, Florian Obser wrote:
> tests, OKs?

OK. Very little change apart from the dnstap interface that we can't use.