diff --git a/Makefile.in b/Makefile.in
index 0a2e7f9b6..22fb75c12 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -738,7 +738,7 @@ msgencode.lo msgencode.o: $(srcdir)/util/data/msgencode.c config.h $(srcdir)/uti
 msgparse.lo msgparse.o: $(srcdir)/util/data/msgparse.c config.h $(srcdir)/util/data/msgparse.h \
  $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/sldns/pkthdr.h \
  $(srcdir)/sldns/rrdef.h $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/packed_rrset.h \
- $(srcdir)/util/data/dname.h $(srcdir)/util/storage/lookup3.h $(srcdir)/util/regional.h $(srcdir)/sldns/sbuffer.h \
+ $(srcdir)/util/data/dname.h $(srcdir)/util/storage/lookup3.h $(srcdir)/util/regional.h $(srcdir)/util/net_help.h $(srcdir)/sldns/sbuffer.h \
  $(srcdir)/sldns/parseutil.h $(srcdir)/sldns/wire2str.h
 msgreply.lo msgreply.o: $(srcdir)/util/data/msgreply.c config.h $(srcdir)/util/data/msgreply.h \
  $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/data/packed_rrset.h \
@@ -793,7 +793,7 @@ iter_priv.lo iter_priv.o: $(srcdir)/iterator/iter_priv.c config.h $(srcdir)/iter
  $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/net_help.h \
  $(srcdir)/util/storage/dnstree.h $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/sbuffer.h
 iter_resptype.lo iter_resptype.o: $(srcdir)/iterator/iter_resptype.c config.h \
- $(srcdir)/iterator/iter_resptype.h $(srcdir)/iterator/iter_delegpt.h $(srcdir)/util/log.h \
+ $(srcdir)/iterator/iter_resptype.h $(srcdir)/iterator/iter_delegpt.h $(srcdir)/iterator/iterator.h $(srcdir)/util/log.h \
  $(srcdir)/services/cache/dns.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h \
  $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/net_help.h \
  $(srcdir)/util/data/dname.h $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/pkthdr.h
diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c
index 30645268c..b912be8ed 100644
--- a/cachedb/cachedb.c
+++ b/cachedb/cachedb.c
@@ -265,11 +265,11 @@ cachedb_init(struct module_env* env, int id)
 		return 0;
 	}
 	cachedb_env->enabled = 1;
-	if(env->cfg->serve_expired_reply_ttl)
+	if(env->cfg->serve_expired && env->cfg->serve_expired_reply_ttl)
 		log_warn(
 			"cachedb: serve-expired-reply-ttl is set but not working for data "
-			"originating from the external cache; 0 TLL is used for those.");
-	if(env->cfg->serve_expired_client_timeout)
+			"originating from the external cache; 0 TTL is used for those.");
+	if(env->cfg->serve_expired && env->cfg->serve_expired_client_timeout)
 		log_warn(
 			"cachedb: serve-expired-client-timeout is set but not working for "
 			"data originating from the external cache; expired data are used "
@@ -815,6 +815,11 @@ cachedb_handle_response(struct module_qstate* qstate,
 		qstate->ext_state[id] = module_finished;
 		return;
 	}
+	if(qstate->env->cfg->cachedb_no_store) {
+		/* do not store the item in the external cache */
+		qstate->ext_state[id] = module_finished;
+		return;
+	}
 
 	/* store the item into the backend cache */
 	cachedb_extcache_store(qstate, ie);
diff --git a/cachedb/redis.c b/cachedb/redis.c
index 93a575a4c..6cc975901 100644
--- a/cachedb/redis.c
+++ b/cachedb/redis.c
@@ -59,11 +59,28 @@ struct redis_moddata {
 	const char* server_path; /* server's unix path, or "", NULL if unused */
 	const char* server_password; /* server's AUTH password, or "", NULL if unused */
 	struct timeval timeout;	 /* timeout for connection setup and commands */
+	int logical_db;		/* the redis logical database to use */
 };
 
 static redisReply* redis_command(struct module_env*, struct cachedb_env*,
 	const char*, const uint8_t*, size_t);
 
+static void
+moddata_clean(struct redis_moddata** moddata) {
+	if(!moddata || !*moddata)
+		return;
+	if((*moddata)->ctxs) {
+		int i;
+		for(i = 0; i < (*moddata)->numctxs; i++) {
+			if((*moddata)->ctxs[i])
+				redisFree((*moddata)->ctxs[i]);
+		}
+		free((*moddata)->ctxs);
+	}
+	free(*moddata);
+	*moddata = NULL;
+}
+
 static redisContext*
 redis_connect(const struct redis_moddata* moddata)
 {
@@ -97,10 +114,21 @@ redis_connect(const struct redis_moddata* moddata)
 		}
 		freeReplyObject(rep);
 	}
+	if(moddata->logical_db > 0) {
+		redisReply* rep;
+		rep = redisCommand(ctx, "SELECT %d", moddata->logical_db);
+		if(!rep || rep->type == REDIS_REPLY_ERROR) {
+			log_err("failed to set logical database (%d)",
+				moddata->logical_db);
+			freeReplyObject(rep);
+			goto fail;
+		}
+		freeReplyObject(rep);
+	}
 	verbose(VERB_OPS, "Connection to Redis established");
 	return ctx;
 
-  fail:
+fail:
 	if(ctx)
 		redisFree(ctx);
 	return NULL;
@@ -117,14 +145,13 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
 	moddata = calloc(1, sizeof(struct redis_moddata));
 	if(!moddata) {
 		log_err("out of memory");
-		return 0;
+		goto fail;
 	}
 	moddata->numctxs = env->cfg->num_threads;
 	moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
 	if(!moddata->ctxs) {
 		log_err("out of memory");
-		free(moddata);
-		return 0;
+		goto fail;
 	}
 	/* note: server_host is a shallow reference to configured string.
 	 * we don't have to free it in this module. */
@@ -134,8 +161,15 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
 	moddata->server_password = env->cfg->redis_server_password;
 	moddata->timeout.tv_sec = env->cfg->redis_timeout / 1000;
 	moddata->timeout.tv_usec = (env->cfg->redis_timeout % 1000) * 1000;
-	for(i = 0; i < moddata->numctxs; i++)
-		moddata->ctxs[i] = redis_connect(moddata);
+	moddata->logical_db = env->cfg->redis_logical_db;
+	for(i = 0; i < moddata->numctxs; i++) {
+		redisContext* ctx = redis_connect(moddata);
+		if(!ctx) {
+			log_err("redis_init: failed to init redis");
+			goto fail;
+		}
+		moddata->ctxs[i] = ctx;
+	}
 	cachedb_env->backend_data = moddata;
 	if(env->cfg->redis_expire_records) {
 		redisReply* rep = NULL;
@@ -148,7 +182,7 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
 			log_err("redis_init: failed to init redis, the "
 				"redis-expire-records option requires the SETEX command "
 				"(redis >= 2.0.0)");
-			return 0;
+			goto fail;
 		}
 		redis_reply_type = rep->type;
 		freeReplyObject(rep);
@@ -160,11 +194,14 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
 			log_err("redis_init: failed to init redis, the "
 				"redis-expire-records option requires the SETEX command "
 				"(redis >= 2.0.0)");
-			return 0;
+			goto fail;
 		}
 	}
-
 	return 1;
+
+fail:
+	moddata_clean(&moddata);
+	return 0;
 }
 
 static void
@@ -175,18 +212,7 @@ redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env)
 	(void)env;
 
 	verbose(VERB_OPS, "Redis deinitialization");
-
-	if(!moddata)
-		return;
-	if(moddata->ctxs) {
-		int i;
-		for(i = 0; i < moddata->numctxs; i++) {
-			if(moddata->ctxs[i])
-				redisFree(moddata->ctxs[i]);
-		}
-		free(moddata->ctxs);
-	}
-	free(moddata);
+	moddata_clean(&moddata);
 }
 
 /*
diff --git a/configure b/configure
index 4521e0a6e..8324e9b34 100755
--- a/configure
+++ b/configure
@@ -4605,450 +4605,186 @@ fi
 
 default_cflags=yes
 fi
-ac_ext=c
-ac_cpp='$CPP $CPPFLAGS'
-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
-ac_compiler_gnu=$ac_cv_c_compiler_gnu
-if test -n "$ac_tool_prefix"; then
-  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
-set dummy ${ac_tool_prefix}gcc; 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_prog_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  if test -n "$CC"; then
-  ac_cv_prog_CC="$CC" # Let the user override the test.
-else
-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_prog_CC="${ac_tool_prefix}gcc"
-    $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
-
-fi
-fi
-CC=$ac_cv_prog_CC
-if test -n "$CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
-$as_echo "$CC" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
-fi
-if test -z "$ac_cv_prog_CC"; then
-  ac_ct_CC=$CC
-  # Extract the first word of "gcc", so it can be a program name with args.
-set dummy gcc; 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_prog_ac_ct_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  if test -n "$ac_ct_CC"; then
-  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
-else
-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_prog_ac_ct_CC="gcc"
-    $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
-
-fi
-fi
-ac_ct_CC=$ac_cv_prog_ac_ct_CC
-if test -n "$ac_ct_CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
-$as_echo "$ac_ct_CC" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-  if test "x$ac_ct_CC" = x; then
-    CC=""
-  else
-    case $cross_compiling:$ac_tool_warned in
-yes:)
-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
-ac_tool_warned=yes ;;
-esac
-    CC=$ac_ct_CC
-  fi
-else
-  CC="$ac_cv_prog_CC"
-fi
-
-if test -z "$CC"; then
-          if test -n "$ac_tool_prefix"; then
-    # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
-set dummy ${ac_tool_prefix}cc; 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_prog_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  if test -n "$CC"; then
-  ac_cv_prog_CC="$CC" # Let the user override the test.
-else
-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_prog_CC="${ac_tool_prefix}cc"
-    $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
-
-fi
-fi
-CC=$ac_cv_prog_CC
-if test -n "$CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
-$as_echo "$CC" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
-  fi
-fi
-if test -z "$CC"; then
-  # Extract the first word of "cc", so it can be a program name with args.
-set dummy cc; 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_prog_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  if test -n "$CC"; then
-  ac_cv_prog_CC="$CC" # Let the user override the test.
-else
-  ac_prog_rejected=no
-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
-    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
-       ac_prog_rejected=yes
-       continue
-     fi
-    ac_cv_prog_CC="cc"
-    $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
-
-if test $ac_prog_rejected = yes; then
-  # We found a bogon in the path, so make sure we never use it.
-  set dummy $ac_cv_prog_CC
-  shift
-  if test $# != 0; then
-    # We chose a different compiler from the bogus one.
-    # However, it has the same basename, so the bogon will be chosen
-    # first if we set CC to just the basename; use the full file name.
-    shift
-    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
-  fi
-fi
-fi
-fi
-CC=$ac_cv_prog_CC
-if test -n "$CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
-$as_echo "$CC" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
-fi
-if test -z "$CC"; then
-  if test -n "$ac_tool_prefix"; then
-  for ac_prog in cl.exe
-  do
-    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
-set dummy $ac_tool_prefix$ac_prog; 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_prog_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  if test -n "$CC"; then
-  ac_cv_prog_CC="$CC" # Let the user override the test.
-else
-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_prog_CC="$ac_tool_prefix$ac_prog"
-    $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
-
-fi
-fi
-CC=$ac_cv_prog_CC
-if test -n "$CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
-$as_echo "$CC" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
-    test -n "$CC" && break
-  done
-fi
-if test -z "$CC"; then
-  ac_ct_CC=$CC
-  for ac_prog in cl.exe
-do
-  # Extract the first word of "$ac_prog", so it can be a program name with args.
-set dummy $ac_prog; 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_prog_ac_ct_CC+:} false; then :
+   case $ac_cv_prog_cc_stdc in #(
+  no) :
+    ac_cv_prog_cc_c99=no; ac_cv_prog_cc_c89=no ;; #(
+  *) :
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C99" >&5
+$as_echo_n "checking for $CC option to accept ISO C99... " >&6; }
+if ${ac_cv_prog_cc_c99+:} false; then :
   $as_echo_n "(cached) " >&6
 else
-  if test -n "$ac_ct_CC"; then
-  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
-else
-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_prog_ac_ct_CC="$ac_prog"
-    $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
+  ac_cv_prog_cc_c99=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wchar.h>
+#include <stdio.h>
 
-fi
-fi
-ac_ct_CC=$ac_cv_prog_ac_ct_CC
-if test -n "$ac_ct_CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
-$as_echo "$ac_ct_CC" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
+// Check varargs macros.  These examples are taken from C99 6.10.3.5.
+#define debug(...) fprintf (stderr, __VA_ARGS__)
+#define showlist(...) puts (#__VA_ARGS__)
+#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__))
+static void
+test_varargs_macros (void)
+{
+  int x = 1234;
+  int y = 5678;
+  debug ("Flag");
+  debug ("X = %d\n", x);
+  showlist (The first, second, and third items.);
+  report (x>y, "x is %d but y is %d", x, y);
+}
 
+// Check long long types.
+#define BIG64 18446744073709551615ull
+#define BIG32 4294967295ul
+#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0)
+#if !BIG_OK
+  your preprocessor is broken;
+#endif
+#if BIG_OK
+#else
+  your preprocessor is broken;
+#endif
+static long long int bignum = -9223372036854775807LL;
+static unsigned long long int ubignum = BIG64;
 
-  test -n "$ac_ct_CC" && break
-done
+struct incomplete_array
+{
+  int datasize;
+  double data[];
+};
 
-  if test "x$ac_ct_CC" = x; then
-    CC=""
-  else
-    case $cross_compiling:$ac_tool_warned in
-yes:)
-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
-ac_tool_warned=yes ;;
-esac
-    CC=$ac_ct_CC
-  fi
-fi
+struct named_init {
+  int number;
+  const wchar_t *name;
+  double average;
+};
 
-fi
+typedef const char *ccp;
 
+static inline int
+test_restrict (ccp restrict text)
+{
+  // See if C++-style comments work.
+  // Iterate through items via the restricted pointer.
+  // Also check for declarations in for loops.
+  for (unsigned int i = 0; *(text+i) != '\0'; ++i)
+    continue;
+  return 0;
+}
 
-test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "no acceptable C compiler found in \$PATH
-See \`config.log' for more details" "$LINENO" 5; }
+// Check varargs and va_copy.
+static void
+test_varargs (const char *format, ...)
+{
+  va_list args;
+  va_start (args, format);
+  va_list args_copy;
+  va_copy (args_copy, args);
 
-# Provide some information about the compiler.
-$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
-set X $ac_compile
-ac_compiler=$2
-for ac_option in --version -v -V -qversion; do
-  { { ac_try="$ac_compiler $ac_option >&5"
-case "(($ac_try" in
-  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
-  *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
-  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
-  ac_status=$?
-  if test -s conftest.err; then
-    sed '10a\
-... rest of stderr output deleted ...
-         10q' conftest.err >conftest.er1
-    cat conftest.er1 >&5
-  fi
-  rm -f conftest.er1 conftest.err
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }
-done
+  const char *str;
+  int number;
+  float fnumber;
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
-$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
-if ${ac_cv_c_compiler_gnu+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
+  while (*format)
+    {
+      switch (*format++)
+	{
+	case 's': // string
+	  str = va_arg (args_copy, const char *);
+	  break;
+	case 'd': // int
+	  number = va_arg (args_copy, int);
+	  break;
+	case 'f': // float
+	  fnumber = va_arg (args_copy, double);
+	  break;
+	default:
+	  break;
+	}
+    }
+  va_end (args_copy);
+  va_end (args);
+}
 
 int
 main ()
 {
-#ifndef __GNUC__
-       choke me
-#endif
 
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_compiler_gnu=yes
-else
-  ac_compiler_gnu=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-ac_cv_c_compiler_gnu=$ac_compiler_gnu
+  // Check bool.
+  _Bool success = false;
 
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
-$as_echo "$ac_cv_c_compiler_gnu" >&6; }
-if test $ac_compiler_gnu = yes; then
-  GCC=yes
-else
-  GCC=
-fi
-ac_test_CFLAGS=${CFLAGS+set}
-ac_save_CFLAGS=$CFLAGS
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
-$as_echo_n "checking whether $CC accepts -g... " >&6; }
-if ${ac_cv_prog_cc_g+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_save_c_werror_flag=$ac_c_werror_flag
-   ac_c_werror_flag=yes
-   ac_cv_prog_cc_g=no
-   CFLAGS="-g"
-   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
+  // Check restrict.
+  if (test_restrict ("String literal") == 0)
+    success = true;
+  char *restrict newvar = "Another string";
 
-int
-main ()
-{
+  // Check varargs.
+  test_varargs ("s, d' f .", "string", 65, 34.234);
+  test_varargs_macros ();
 
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_cv_prog_cc_g=yes
-else
-  CFLAGS=""
-      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
+  // Check flexible array members.
+  struct incomplete_array *ia =
+    malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10));
+  ia->datasize = 10;
+  for (int i = 0; i < ia->datasize; ++i)
+    ia->data[i] = i * 1.234;
 
-int
-main ()
-{
+  // Check named initializers.
+  struct named_init ni = {
+    .number = 34,
+    .name = L"Test wide string",
+    .average = 543.34343,
+  };
 
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+  ni.number = 58;
 
-else
-  ac_c_werror_flag=$ac_save_c_werror_flag
-	 CFLAGS="-g"
-	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
+  int dynamic_array[ni.number];
+  dynamic_array[ni.number - 1] = 543;
 
-int
-main ()
-{
+  // work around unused variable warnings
+  return (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == 'x'
+	  || dynamic_array[ni.number - 1] != 543);
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_cv_prog_cc_g=yes
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+for ac_arg in '' -std=gnu99 -std=c99 -c99 -AC99 -D_STDC_C99= -qlanglvl=extc99
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_c99=$ac_arg
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-   ac_c_werror_flag=$ac_save_c_werror_flag
+rm -f core conftest.err conftest.$ac_objext
+  test "x$ac_cv_prog_cc_c99" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
-$as_echo "$ac_cv_prog_cc_g" >&6; }
-if test "$ac_test_CFLAGS" = set; then
-  CFLAGS=$ac_save_CFLAGS
-elif test $ac_cv_prog_cc_g = yes; then
-  if test "$GCC" = yes; then
-    CFLAGS="-g -O2"
-  else
-    CFLAGS="-g"
-  fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c99" in
+  x)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+  xno)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_c99"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
+$as_echo "$ac_cv_prog_cc_c99" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c99" != xno; then :
+  ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99
 else
-  if test "$GCC" = yes; then
-    CFLAGS="-O2"
-  else
-    CFLAGS=
-  fi
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
 $as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
 if ${ac_cv_prog_cc_c89+:} false; then :
   $as_echo_n "(cached) " >&6
@@ -5135,14 +4871,31 @@ $as_echo "unsupported" >&6; } ;;
 $as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
 esac
 if test "x$ac_cv_prog_cc_c89" != xno; then :
+  ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89
+else
+  ac_cv_prog_cc_stdc=no
+fi
 
+fi
+ ;;
+esac
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO Standard C" >&5
+$as_echo_n "checking for $CC option to accept ISO Standard C... " >&6; }
+  if ${ac_cv_prog_cc_stdc+:} false; then :
+  $as_echo_n "(cached) " >&6
 fi
 
-ac_ext=c
-ac_cpp='$CPP $CPPFLAGS'
-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
-ac_compiler_gnu=$ac_cv_c_compiler_gnu
+  case $ac_cv_prog_cc_stdc in #(
+  no) :
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;; #(
+  '') :
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;; #(
+  *) :
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_stdc" >&5
+$as_echo "$ac_cv_prog_cc_stdc" >&6; } ;;
+esac
 
 
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking $CC dependency flag" >&5
diff --git a/configure.ac b/configure.ac
index f6f5f21fc..9aa781a12 100644
--- a/configure.ac
+++ b/configure.ac
@@ -280,7 +280,7 @@ ACX_CHECK_COMPILER_FLAG(g, [CFLAGS="$CFLAGS -g"])
 ACX_CHECK_COMPILER_FLAG(O2, [CFLAGS="$CFLAGS -O2"])
 default_cflags=yes
 fi
-AC_PROG_CC
+m4_version_prereq([2.70], [AC_PROG_CC], [AC_PROG_CC_STDC])
 ACX_DEPFLAG
 ACX_DETERMINE_EXT_FLAGS_UNBOUND
 
diff --git a/daemon/remote.c b/daemon/remote.c
index 4990fc8e9..3eb711ce6 100644
--- a/daemon/remote.c
+++ b/daemon/remote.c
@@ -523,12 +523,13 @@ ssl_print_text(RES* res, const char* text)
 	if(res->ssl) {
 		ERR_clear_error();
 		if((r=SSL_write(res->ssl, text, (int)strlen(text))) <= 0) {
-			if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) {
+			int r2;
+			if((r2=SSL_get_error(res->ssl, r)) == SSL_ERROR_ZERO_RETURN) {
 				verbose(VERB_QUERY, "warning, in SSL_write, peer "
 					"closed connection");
 				return 0;
 			}
-			log_crypto_err("could not SSL_write");
+			log_crypto_err_io("could not SSL_write", r2);
 			return 0;
 		}
 	} else {
@@ -579,11 +580,12 @@ ssl_read_line(RES* res, char* buf, size_t max)
 		if(res->ssl) {
 			ERR_clear_error();
 			if((r=SSL_read(res->ssl, buf+len, 1)) <= 0) {
-				if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) {
+				int r2;
+				if((r2=SSL_get_error(res->ssl, r)) == SSL_ERROR_ZERO_RETURN) {
 					buf[len] = 0;
 					return 1;
 				}
-				log_crypto_err("could not SSL_read");
+				log_crypto_err_io("could not SSL_read", r2);
 				return 0;
 			}
 		} else {
@@ -596,7 +598,7 @@ ssl_read_line(RES* res, char* buf, size_t max)
 					}
 					if(errno == EINTR || errno == EAGAIN)
 						continue;
-					log_err("could not recv: %s",
+					if(rr < 0) log_err("could not recv: %s",
 						sock_strerror(errno));
 					return 0;
 				}
@@ -1223,8 +1225,8 @@ do_zones_add(RES* ssl, struct local_zones* zones)
 	char buf[2048];
 	int num = 0;
 	while(ssl_read_line(ssl, buf, sizeof(buf))) {
-		if(buf[0] == 0x04 && buf[1] == 0)
-			break; /* end of transmission */
+		if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
+			break; /* zero byte line or end of transmission */
 		if(!perform_zone_add(ssl, zones, buf)) {
 			if(!ssl_printf(ssl, "error for input line: %s\n", buf))
 				return;
@@ -1272,8 +1274,8 @@ do_zones_remove(RES* ssl, struct local_zones* zones)
 	char buf[2048];
 	int num = 0;
 	while(ssl_read_line(ssl, buf, sizeof(buf))) {
-		if(buf[0] == 0x04 && buf[1] == 0)
-			break; /* end of transmission */
+		if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
+			break; /* zero byte line or end of transmission */
 		if(!perform_zone_remove(ssl, zones, buf)) {
 			if(!ssl_printf(ssl, "error for input line: %s\n", buf))
 				return;
@@ -1336,8 +1338,8 @@ do_datas_add(RES* ssl, struct local_zones* zones)
 	char buf[2048];
 	int num = 0, line = 0;
 	while(ssl_read_line(ssl, buf, sizeof(buf))) {
-		if(buf[0] == 0x04 && buf[1] == 0)
-			break; /* end of transmission */
+		if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
+			break; /* zero byte line or end of transmission */
 		line++;
 		if(perform_data_add(ssl, zones, buf, line))
 			num++;
@@ -1376,8 +1378,8 @@ do_datas_remove(RES* ssl, struct local_zones* zones)
 	char buf[2048];
 	int num = 0;
 	while(ssl_read_line(ssl, buf, sizeof(buf))) {
-		if(buf[0] == 0x04 && buf[1] == 0)
-			break; /* end of transmission */
+		if(buf[0] == 0 || (buf[0] == 0x04 && buf[1] == 0))
+			break; /* zero byte line or end of transmission */
 		if(!perform_data_remove(ssl, zones, buf)) {
 			if(!ssl_printf(ssl, "error for input line: %s\n", buf))
 				return;
@@ -3222,9 +3224,10 @@ handle_req(struct daemon_remote* rc, struct rc_state* s, RES* res)
 	if(res->ssl) {
 		ERR_clear_error();
 		if((r=SSL_read(res->ssl, magic, (int)sizeof(magic)-1)) <= 0) {
-			if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN)
+			int r2;
+			if((r2=SSL_get_error(res->ssl, r)) == SSL_ERROR_ZERO_RETURN)
 				return;
-			log_crypto_err("could not SSL_read");
+			log_crypto_err_io("could not SSL_read", r2);
 			return;
 		}
 	} else {
@@ -3291,7 +3294,7 @@ remote_handshake_later(struct daemon_remote* rc, struct rc_state* s,
 			log_err("remote control connection closed prematurely");
 		log_addr(VERB_OPS, "failed connection from",
 			&s->c->repinfo.remote_addr, s->c->repinfo.remote_addrlen);
-		log_crypto_err("remote control failed ssl");
+		log_crypto_err_io("remote control failed ssl", r2);
 		clean_point(rc, s);
 	}
 	return 0;
diff --git a/daemon/worker.c b/daemon/worker.c
index 8c6fa3b9a..8ae05eb67 100644
--- a/daemon/worker.c
+++ b/daemon/worker.c
@@ -66,6 +66,7 @@
 #include "util/data/msgencode.h"
 #include "util/data/dname.h"
 #include "util/fptr_wlist.h"
+#include "util/proxy_protocol.h"
 #include "util/tube.h"
 #include "util/edns.h"
 #include "util/timeval_func.h"
@@ -542,6 +543,8 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo,
 	edns->udp_size = EDNS_ADVERTISED_SIZE;
 	edns->ext_rcode = 0;
 	edns->bits &= EDNS_DO;
+	if(worker->env.cfg->disable_edns_do && (edns->bits & EDNS_DO))
+		edns->edns_present = 0;
 	if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, msg->rep,
 		(int)(flags&LDNS_RCODE_MASK), edns, repinfo, worker->scratchpad,
 		worker->env.now_tv))
@@ -702,6 +705,8 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo,
 		edns->udp_size = EDNS_ADVERTISED_SIZE;
 		edns->ext_rcode = 0;
 		edns->bits &= EDNS_DO;
+		if(worker->env.cfg->disable_edns_do && (edns->bits & EDNS_DO))
+			edns->edns_present = 0;
 		if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, rep,
 			LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
 			worker->env.now_tv))
@@ -742,6 +747,8 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo,
 	edns->udp_size = EDNS_ADVERTISED_SIZE;
 	edns->ext_rcode = 0;
 	edns->bits &= EDNS_DO;
+	if(worker->env.cfg->disable_edns_do && (edns->bits & EDNS_DO))
+		edns->edns_present = 0;
 	*alias_rrset = NULL; /* avoid confusion if caller set it to non-NULL */
 	if((worker->daemon->use_response_ip || worker->daemon->use_rpz) &&
 		!partial_rep && !apply_respip_action(worker, qinfo, cinfo, rep,
@@ -2317,6 +2324,7 @@ worker_init(struct worker* worker, struct config_file *cfg,
 			worker->env.cfg->stat_interval);
 		worker_restart_timer(worker);
 	}
+	pp_init(&sldns_write_uint16, &sldns_write_uint32);
 	return 1;
 }
 
diff --git a/dns64/dns64.c b/dns64/dns64.c
index 1e31f51e8..83fb02779 100644
--- a/dns64/dns64.c
+++ b/dns64/dns64.c
@@ -573,28 +573,29 @@ static enum module_ext_state
 handle_event_pass(struct module_qstate* qstate, int id)
 {
 	struct dns64_qstate* iq = (struct dns64_qstate*)qstate->minfo[id];
-	if (iq && iq->state == DNS64_NEW_QUERY
-            && qstate->qinfo.qtype == LDNS_RR_TYPE_PTR
-            && qstate->qinfo.qname_len == 74
-            && !strcmp((char*)&qstate->qinfo.qname[64], "\03ip6\04arpa"))
-        /* Handle PTR queries for IPv6 addresses. */
-        return handle_ipv6_ptr(qstate, id);
-
-	if (qstate->env->cfg->dns64_synthall &&
-	    iq && iq->state == DNS64_NEW_QUERY
-	    && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA)
-		return generate_type_A_query(qstate, id);
+	int synth_all_cfg = qstate->env->cfg->dns64_synthall;
+	int synth_qname = 0;
+
+	if(iq && iq->state == DNS64_NEW_QUERY
+		&& qstate->qinfo.qtype == LDNS_RR_TYPE_PTR
+		&& qstate->qinfo.qname_len == 74
+		&& !strcmp((char*)&qstate->qinfo.qname[64], "\03ip6\04arpa")) {
+		/* Handle PTR queries for IPv6 addresses. */
+		return handle_ipv6_ptr(qstate, id);
+	}
 
-	if(dns64_always_synth_for_qname(qstate, id) &&
-	    iq && iq->state == DNS64_NEW_QUERY
-	    && !(qstate->query_flags & BIT_CD)
-	    && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) {
-		verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
+	if(iq && iq->state == DNS64_NEW_QUERY &&
+		qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA &&
+		(synth_all_cfg ||
+		(synth_qname=(dns64_always_synth_for_qname(qstate, id)
+			&& !(qstate->query_flags & BIT_CD))))) {
+		if(synth_qname)
+			verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
 		return generate_type_A_query(qstate, id);
 	}
 
 	/* We are finished when our sub-query is finished. */
-	if (iq && iq->state == DNS64_SUBQUERY_FINISHED)
+	if(iq && iq->state == DNS64_SUBQUERY_FINISHED)
 		return module_finished;
 
 	/* Otherwise, pass request to next module. */
@@ -627,32 +628,37 @@ handle_event_moddone(struct module_qstate* qstate, int id)
      *        synthesize in (sec 5.1.2 of RFC6147).
      *   - A successful AAAA query with an answer.
      */
-	if((!iq || iq->state != DNS64_INTERNAL_QUERY)
-            && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA
-	    && !(qstate->query_flags & BIT_CD)
-	    && !(qstate->return_msg &&
-		    qstate->return_msg->rep &&
-		    reply_find_answer_rrset(&qstate->qinfo,
-			    qstate->return_msg->rep)))
-		/* not internal, type AAAA, not CD, and no answer RRset,
-		 * So, this is a AAAA noerror/nodata answer */
-		return generate_type_A_query(qstate, id);
 
-	if((!iq || iq->state != DNS64_INTERNAL_QUERY)
-	    && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA
-	    && !(qstate->query_flags & BIT_CD)
-	    && dns64_always_synth_for_qname(qstate, id)) {
-		/* if it is not internal, AAAA, not CD and listed domain,
-		 * generate from A record and ignore AAAA */
-		verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
+	/* When an AAAA query completes check if we want to perform DNS64
+	 * synthesis. We skip queries with DNSSEC enabled (!CD) and
+	 * ones generated by us to retrive the A/PTR record to use for
+	 * synth. */
+	int could_synth =
+		qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA &&
+		(!iq || iq->state != DNS64_INTERNAL_QUERY) &&
+		!(qstate->query_flags & BIT_CD);
+	int has_data = /* whether query returned non-empty rrset */
+		qstate->return_msg &&
+		qstate->return_msg->rep &&
+		reply_find_answer_rrset(&qstate->qinfo, qstate->return_msg->rep);
+	int synth_qname = 0;
+
+	if(could_synth &&
+		(!has_data ||
+		(synth_qname=dns64_always_synth_for_qname(qstate, id)))) {
+		if(synth_qname)
+			verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
 		return generate_type_A_query(qstate, id);
 	}
 
 	/* Store the response in cache. */
-	if ( (!iq || !iq->started_no_cache_store) &&
-		qstate->return_msg && qstate->return_msg->rep &&
-		!dns_cache_store(qstate->env, &qstate->qinfo, qstate->return_msg->rep,
-		0, 0, 0, NULL, qstate->query_flags, qstate->qstarttime))
+	if( (!iq || !iq->started_no_cache_store) &&
+		qstate->return_msg &&
+		qstate->return_msg->rep &&
+		!dns_cache_store(
+			qstate->env, &qstate->qinfo, qstate->return_msg->rep,
+			0, 0, 0, NULL,
+			qstate->query_flags, qstate->qstarttime))
 		log_err("out of memory");
 
 	/* do nothing */
@@ -969,10 +975,19 @@ dns64_inform_super(struct module_qstate* qstate, int id,
 	}
 	super_dq->state = DNS64_SUBQUERY_FINISHED;
 
-	/* If there is no successful answer, we're done. */
-	if (qstate->return_rcode != LDNS_RCODE_NOERROR
-	    || !qstate->return_msg
-	    || !qstate->return_msg->rep) {
+	/* If there is no successful answer, we're done.
+	 * Guarantee that we have at least a NOERROR reply further on. */
+	if(qstate->return_rcode != LDNS_RCODE_NOERROR
+		|| !qstate->return_msg
+		|| !qstate->return_msg->rep) {
+		return;
+	}
+
+	/* When no A record is found for synthesis fall back to AAAA again. */
+	if(qstate->qinfo.qtype == LDNS_RR_TYPE_A &&
+		!reply_find_answer_rrset(&qstate->qinfo,
+			qstate->return_msg->rep)) {
+		super_dq->state = DNS64_INTERNAL_QUERY;
 		return;
 	}
 
diff --git a/dnstap/dtstream.c b/dnstap/dtstream.c
index 9153f0404..69c951276 100644
--- a/dnstap/dtstream.c
+++ b/dnstap/dtstream.c
@@ -788,7 +788,7 @@ static int dtio_write_ssl(struct dt_io_thread* dtio, uint8_t* buf,
 			}
 			return -1;
 		}
-		log_crypto_err("dnstap io, could not SSL_write");
+		log_crypto_err_io("dnstap io, could not SSL_write", want);
 		return -1;
 	}
 	return r;
@@ -1029,7 +1029,7 @@ static int ssl_read_bytes(struct dt_io_thread* dtio, void* buf, size_t len)
 				"other side");
 			return 0;
 		}
-		log_crypto_err("could not SSL_read");
+		log_crypto_err_io("could not SSL_read", want);
 		verbose(VERB_DETAIL, "dnstap io: output closed by the "
 				"other side");
 		return 0;
@@ -1431,8 +1431,8 @@ static int dtio_ssl_handshake(struct dt_io_thread* dtio,
 		} else {
 			unsigned long err = ERR_get_error();
 			if(!squelch_err_ssl_handshake(err)) {
-				log_crypto_err_code("dnstap io, ssl handshake failed",
-					err);
+				log_crypto_err_io_code("dnstap io, ssl handshake failed",
+					want, err);
 				verbose(VERB_OPS, "dnstap io, ssl handshake failed "
 					"from %s", dtio->ip_str);
 			}
diff --git a/dnstap/unbound-dnstap-socket.c b/dnstap/unbound-dnstap-socket.c
index d172a6744..04fda74b8 100644
--- a/dnstap/unbound-dnstap-socket.c
+++ b/dnstap/unbound-dnstap-socket.c
@@ -708,7 +708,7 @@ static ssize_t ssl_read_bytes(struct tap_data* data, void* buf, size_t len)
 				(data->id?data->id:""));
 			return 0;
 		}
-		log_crypto_err("could not SSL_read");
+		log_crypto_err_io("could not SSL_read", want);
 		if(verbosity) log_info("dnstap client stream closed from %s",
 			(data->id?data->id:""));
 		return 0;
@@ -760,10 +760,11 @@ static int reply_with_accept(struct tap_data* data)
 	fd_set_block(data->fd);
 	if(data->ssl) {
 		if((r=SSL_write(data->ssl, acceptframe, len)) <= 0) {
-			if(SSL_get_error(data->ssl, r) == SSL_ERROR_ZERO_RETURN)
+			int r2;
+			if((r2=SSL_get_error(data->ssl, r)) == SSL_ERROR_ZERO_RETURN)
 				log_err("SSL_write, peer closed connection");
 			else
-				log_err("could not SSL_write");
+				log_crypto_err_io("could not SSL_write", r2);
 			fd_set_nonblock(data->fd);
 			free(acceptframe);
 			return 0;
@@ -805,10 +806,11 @@ static int reply_with_finish(struct tap_data* data)
 	if(data->ssl) {
 		int r;
 		if((r=SSL_write(data->ssl, finishframe, len)) <= 0) {
-			if(SSL_get_error(data->ssl, r) == SSL_ERROR_ZERO_RETURN)
+			int r2;
+			if((r2=SSL_get_error(data->ssl, r)) == SSL_ERROR_ZERO_RETURN)
 				log_err("SSL_write, peer closed connection");
 			else
-				log_err("could not SSL_write");
+				log_crypto_err_io("could not SSL_write", r2);
 			fd_set_nonblock(data->fd);
 			free(finishframe);
 			return 0;
diff --git a/doc/Changelog b/doc/Changelog
index ecc2b188a..9ec9fa513 100644
--- a/doc/Changelog
+++ b/doc/Changelog
@@ -1,3 +1,111 @@
+20 October 2023: Wouter
+	- Merge #951: Cachedb no store. The cachedb-no-store: yes option is
+	  used to stop cachedb from writing messages to the backend storage.
+	  It reads messages when data is available from the backend. The
+	  default is no.
+
+19 October 2023: Wouter
+	- Fix to print detailed errors when an SSL IO routine fails via
+	  SSL_get_error.
+
+18 October 2023: George
+	- Mailing list patches from Daniel Gröber for DNS64 fallback to plain
+	  AAAA when no A record exists for synthesis, and minor DNS64 code
+	  refactoring for better readability.
+	- Fixes for the DNS64 patches.
+	- Update the dns64_lookup.rpl test for the DNS64 fallback patch.
+	- Merge #955 from buevsan: fix ipset wrong behavior.
+	- Update testdata/ipset.tdir test for ipset fix.
+
+17 October 2023: Wouter
+	- Fix #954: Inconsistent RPZ handling for A record returned along with
+	  CNAME.
+
+16 October 2023: George
+	- Expose the script filename in the Python module environment 'mod_env'
+	  instead of the config_file structure which includes the linked list
+	  of scripts in a multi Python module setup; fixes #79.
+	- Expose the configured listening and outgoing interfaces, if any, as
+	  a list of strings in the Python 'config_file' class instead of the
+	  current Swig object proxy; fixes #79.
+	- For multi Python module setups, clean previously parsed module
+	  functions in __main__'s dictionary, if any, so that only current
+	  module functions are registered.
+
+13 October 2023: George
+	- Better fix for infinite loop when reading multiple lines of input on
+	  a broken remote control socket, by treating a zero byte line the
+	  same as transmission end. Addesses #947 and #948.
+
+12 October 2023: Wouter
+	- Merge #944: Disable EDNS DO.
+	  Disable the EDNS DO flag in upstream requests. This can be helpful
+	  for devices that cannot handle DNSSEC information. But it should not
+	  be enabled otherwise, because that would stop DNSSEC validation. The
+	  DNSSEC validation would not work for Unbound itself, and also not
+	  for downstream users. Default is no. The option
+	  is disable-edns-do: no
+
+11 October 2023: George
+	- Fix #850: [FR] Ability to use specific database in Redis, with new
+	  redis-logical-db configuration option.
+
+11 October 2023: Wouter
+	- Fix #949: "could not create control compt".
+	- Fix that cachedb does not warn when serve-expired is disabled about
+	  use of serve-expired-reply-ttl and serve-expired-client-timeout.
+	- Fix for #949: Fix pythonmod/ubmodule-tst.py for Python 3.x.
+
+10 October 2023: George
+	- Fix infinite loop when reading multiple lines of input on a broken
+	  remote control socket. Addesses #947 and #948.
+
+9 October 2023: Wouter
+	- Fix edns subnet so that queries with a source prefix of zero cause
+	  the recursor send no edns subnet option to the upstream.
+	- Fix that printout of EDNS options shows the EDNS cookie option by
+	  name.
+
+4 October 2023: Wouter
+	- Fix #946: Forwarder returns servfail on upstream response noerror no
+	  data.
+
+3 October 2023: George
+	- Merge #881: Generalise the proxy protocol code.
+
+2 October 2023: George
+	- Fix misplaced comment.
+
+22 September 2023: Wouter
+	- Fix #942: 1.18.0 libunbound DNS regression when built without
+	  OpenSSL.
+
+18 September 2023: Wouter
+	- Fix rpz tcp-only action with rpz triggers nsdname and nsip.
+
+15 September 2023: Wouter
+	- Merge #936: Check for c99 with autoconf versions prior to 2.70.
+	- Fix to remove two c99 notations.
+
+14 September 2023: Wouter
+	- Fix authority zone answers for obscured DNAMEs and delegations.
+
+8 September 2023: Wouter
+	- Fix send of udp retries when ENOBUFS is returned. It stops looping
+	  and also waits for the condition to go away. Reported by Florian
+	  Obser.
+
+7 September 2023: Wouter
+	- Fix to scrub resource records of type A and AAAA that have an
+	  inappropriate size. They are removed from responses.
+	- Fix to move msgparse_rrset_remove_rr code to util/msgparse.c.
+	- Fix to add EDE text when RRs have been removed due to length.
+	- Fix to set ede match in unit test for rr length removal.
+	- Fix to print EDE text in readable form in output logs.
+
+6 September 2023: Wouter
+	- Merge #931: Prevent warnings from -Wmissing-prototypes.
+
 31 August 2023: Wouter
 	- Fix autoconf 2.69 warnings in configure.
 	- Fix #927: unbound 1.18.0 make test error. Fix make test without SHA1.
diff --git a/doc/example.conf.in b/doc/example.conf.in
index 0980212e1..6bf1c668e 100644
--- a/doc/example.conf.in
+++ b/doc/example.conf.in
@@ -683,6 +683,11 @@ server:
 	# that set CD but cannot validate themselves.
 	# ignore-cd-flag: no
 
+	# Disable the DO flag in outgoing requests. It is helpful for upstream
+	# devices that cannot handle DNSSEC information. But do not enable it
+	# otherwise, because it would stop DNSSEC validation.
+	# disable-edns-do: no
+
 	# Serve expired responses from cache, with serve-expired-reply-ttl in
 	# the response, and then attempt to fetch the data afresh.
 	# serve-expired: no
@@ -1221,6 +1226,8 @@ remote-control:
 #     backend: "testframe"
 #     # secret seed string to calculate hashed keys
 #     secret-seed: "default"
+#     # if the backend should be read from, but not written to.
+#     cachedb-no-store: no
 #
 #     # For "redis" backend:
 #     # (to enable, use --with-libhiredis to configure before compiling)
@@ -1236,6 +1243,8 @@ remote-control:
 #     redis-timeout: 100
 #     # set timeout on redis records based on DNS response TTL
 #     redis-expire-records: no
+#     # redis logical database to use, 0 is the default database.
+#     redis-logical-db: 0
 
 # IPSet
 # Add specify domain into set via ipset.
diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in
index 84b903f49..76cfa2383 100644
--- a/doc/unbound.conf.5.in
+++ b/doc/unbound.conf.5.in
@@ -1302,6 +1302,20 @@ servers that set the CD flag but cannot validate DNSSEC themselves are
 the clients, and then Unbound provides them with DNSSEC protection.
 The default value is "no".
 .TP
+.B disable\-edns\-do: \fI<yes or no>
+Disable the EDNS DO flag in upstream requests.
+It breaks DNSSEC validation for Unbound's clients.
+This results in the upstream name servers to not include DNSSEC records in
+their replies and could be helpful for devices that cannot handle DNSSEC
+information.
+When the option is enabled, clients that set the DO flag receive no EDNS
+record in the response to indicate the lack of support to them.
+If this option is enabled but Unbound is already configured for DNSSEC
+validation (i.e., the validator module is enabled; default) this option is
+implicitly turned off with a warning as to not break DNSSEC validation in
+Unbound.
+Default is no.
+.TP
 .B serve\-expired: \fI<yes or no>
 If enabled, Unbound attempts to serve old responses from cache with a
 TTL of \fBserve\-expired\-reply\-ttl\fR in the response without waiting for the
@@ -2667,6 +2681,11 @@ operationally.
 If the backend database is shared by multiple Unbound instances,
 all instances must use the same secret seed.
 This option defaults to "default".
+.TP
+.B cachedb-no-store: \fI<yes or no>\fR
+If the backend should be read from, but not written to. This makes this
+instance not store dns messages in the backend. But if data is available it
+is retrieved. The default is no.
 .P
 The following
 .B cachedb
@@ -2707,6 +2726,17 @@ Unbound is configured with \fBserve-expired\fR and \fBserve-expired-ttl\fR is 0,
 this option is internally reverted to "no".  Redis SETEX support is required
 for this option (Redis >= 2.0.0).
 This option defaults to no.
+.TP
+.B redis-logical-db: \fI<logical database index>
+The logical database in Redis to use.
+These are databases in the same Redis instance sharing the same configuration
+and persisted in the same RDB/AOF file.
+If unsure about using this option, Redis documentation
+(https://redis.io/commands/select/) suggests not to use a single Redis instance
+for multiple unrelated applications.
+The default database in Redis is 0 while other logical databases need to be
+explicitly SELECT'ed upon connecting.
+This option defaults to 0.
 .SS DNSTAP Logging Options
 DNSTAP support, when compiled in by using \fB\-\-enable\-dnstap\fR, is enabled
 in the \fBdnstap:\fR section.
diff --git a/dynlibmod/dynlibmod.c b/dynlibmod/dynlibmod.c
index ffac7ff30..1e040a30e 100644
--- a/dynlibmod/dynlibmod.c
+++ b/dynlibmod/dynlibmod.c
@@ -75,6 +75,7 @@ int dynlibmod_init(struct module_env* env, int id) {
     struct config_strlist* cfg_item = env->cfg->dynlib_file;
     struct dynlibmod_env* de = (struct dynlibmod_env*)calloc(1, sizeof(struct dynlibmod_env));
     __DYNMOD dynamic_library;
+    int i;
     if (!de)
     {
         log_err("dynlibmod[%d]: malloc failure", dynlib_mod_idx);
@@ -84,7 +85,7 @@ int dynlibmod_init(struct module_env* env, int id) {
     env->modinfo[id] = (void*) de;
 
     de->fname = NULL;
-    for(int i = dynlib_mod_idx;
+    for(i = dynlib_mod_idx;
         i != 0 && cfg_item != NULL;
         i--, cfg_item = cfg_item->next) {}
 
diff --git a/edns-subnet/subnetmod.c b/edns-subnet/subnetmod.c
index 13fd669b5..cefde84e5 100644
--- a/edns-subnet/subnetmod.c
+++ b/edns-subnet/subnetmod.c
@@ -156,6 +156,7 @@ int ecs_whitelist_check(struct query_info* qinfo,
 		qstate->no_cache_store = 0;
 	}
 
+	sq->subnet_sent_no_subnet = 0;
 	if(sq->ecs_server_out.subnet_validdata && ((sq->subnet_downstream &&
 		qstate->env->cfg->client_subnet_always_forward) ||
 		ecs_is_whitelisted(sn_env->whitelist, 
@@ -166,6 +167,14 @@ int ecs_whitelist_check(struct query_info* qinfo,
 		 * set. */
 		if(!edns_opt_list_find(qstate->edns_opts_back_out,
 			qstate->env->cfg->client_subnet_opcode)) {
+			/* if the client is not wanting an EDNS subnet option,
+			 * omit it and store that we omitted it but actually
+			 * are doing EDNS subnet to the server. */
+			if(sq->ecs_server_out.subnet_source_mask == 0) {
+				sq->subnet_sent_no_subnet = 1;
+				sq->subnet_sent = 0;
+				return 1;
+			}
 			subnet_ecs_opt_list_append(&sq->ecs_server_out,
 				&qstate->edns_opts_back_out, qstate, region);
 		}
@@ -515,7 +524,7 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
 	}
 
 	/* We have not asked for subnet data */
-	if (!sq->subnet_sent) {
+	if (!sq->subnet_sent && !sq->subnet_sent_no_subnet) {
 		if (s_in->subnet_validdata)
 			verbose(VERB_QUERY, "subnetcache: received spurious data");
 		if (sq->subnet_downstream) /* Copy back to client */
@@ -524,7 +533,7 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
 	}
 
 	/* subnet sent but nothing came back */
-	if (!s_in->subnet_validdata) {
+	if (!s_in->subnet_validdata && !sq->subnet_sent_no_subnet) {
 		/* The authority indicated no support for edns subnet. As a
 		 * consequence the answer ended up in the regular cache. It
 		 * is still useful to put it in the edns subnet cache for
@@ -540,6 +549,18 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
 		return module_finished;
 	}
 
+	/* Purposefully there was no sent subnet, and there is consequently
+	 * no subnet in the answer. If there was, use the subnet in the answer
+	 * anyway. But if there is not, treat it as a prefix 0 answer. */
+	if(sq->subnet_sent_no_subnet && !s_in->subnet_validdata) {
+		/* Fill in 0.0.0.0/0 scope 0, or ::0/0 scope 0, for caching. */
+		s_in->subnet_addr_fam = s_out->subnet_addr_fam;
+		s_in->subnet_source_mask = 0;
+		s_in->subnet_scope_mask = 0;
+		memset(s_in->subnet_addr, 0, INET6_SIZE);
+		s_in->subnet_validdata = 1;
+	}
+
 	/* Being here means we have asked for and got a subnet specific 
 	 * answer. Also, the answer from the authority is not yet cached 
 	 * anywhere. */
@@ -556,6 +577,7 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
 		(void)edns_opt_list_remove(&qstate->edns_opts_back_out,
 			qstate->env->cfg->client_subnet_opcode);
 		sq->subnet_sent = 0;
+		sq->subnet_sent_no_subnet = 0;
 		return module_restart_next;
 	}
 
@@ -676,6 +698,7 @@ ecs_query_response(struct module_qstate* qstate, struct dns_msg* response,
 		edns_opt_list_remove(&qstate->edns_opts_back_out,
 			qstate->env->cfg->client_subnet_opcode);
 		sq->subnet_sent = 0;
+		sq->subnet_sent_no_subnet = 0;
 		memset(&sq->ecs_server_out, 0, sizeof(sq->ecs_server_out));
 	} else if (!sq->track_max_scope &&
 		FLAGS_GET_RCODE(response->rep->flags) == LDNS_RCODE_NOERROR &&
@@ -737,6 +760,9 @@ ecs_edns_back_parsed(struct module_qstate* qstate, int id,
 				sq->ecs_server_in.subnet_scope_mask >
 				sq->max_scope))
 				sq->max_scope = sq->ecs_server_in.subnet_scope_mask;
+	} else if(sq->subnet_sent_no_subnet) {
+		/* The answer can be stored as scope 0, not in global cache. */
+		qstate->no_cache_store = 1;
 	}
 
 	return 1;
diff --git a/edns-subnet/subnetmod.h b/edns-subnet/subnetmod.h
index f0bcaad33..1ff8a23ec 100644
--- a/edns-subnet/subnetmod.h
+++ b/edns-subnet/subnetmod.h
@@ -85,6 +85,13 @@ struct subnet_qstate {
 	struct ecs_data	ecs_server_out;
 	int subnet_downstream;
 	int subnet_sent;
+	/**
+	 * If there was no subnet sent because the client used source prefix
+	 * length 0 for omitting the information. Then the answer is cached
+	 * like subnet was a /0 scope. Like the subnet_sent flag, but when
+	 * the EDNS subnet option is omitted because the client asked.
+	 */
+	int subnet_sent_no_subnet;
 	/** keep track of longest received scope, set after receiving CNAME for
 	 * incoming QNAME. */
 	int track_max_scope;
diff --git a/ipset/ipset.c b/ipset/ipset.c
index c61ebc205..af55de8d6 100644
--- a/ipset/ipset.c
+++ b/ipset/ipset.c
@@ -158,10 +158,10 @@ ipset_check_zones_for_rrset(struct module_env *env, struct ipset_env *ie,
 		qs = NULL;
 		plen = strlen(p->str);
 
-		if (dlen >= plen) {
+		if (dlen == plen || (dlen > plen && dname[dlen - plen - 1] == '.' )) {
 			ds = dname + (dlen - plen);
 		}
-		if (qlen >= plen) {
+		if (qlen == plen || (qlen > plen && qname[qlen - plen - 1] == '.' )) {
 			qs = qname + (qlen - plen);
 		}
 		if ((ds && strncasecmp(p->str, ds, plen) == 0)
diff --git a/iterator/iter_priv.c b/iterator/iter_priv.c
index 90bea1746..be4219216 100644
--- a/iterator/iter_priv.c
+++ b/iterator/iter_priv.c
@@ -207,28 +207,6 @@ size_t priv_get_mem(struct iter_priv* priv)
 	return sizeof(*priv) + regional_get_mem(priv->region);
 }
 
-/** remove RR from msgparse RRset, return true if rrset is entirely bad */
-static int
-remove_rr(const char* str, sldns_buffer* pkt, struct rrset_parse* rrset,
-	struct rr_parse* prev, struct rr_parse** rr, struct sockaddr_storage* addr, socklen_t addrlen)
-{
-	if(verbosity >= VERB_QUERY && rrset->dname_len <= LDNS_MAX_DOMAINLEN && str) {
-		uint8_t buf[LDNS_MAX_DOMAINLEN+1];
-		dname_pkt_copy(pkt, buf, rrset->dname);
-		log_name_addr(VERB_QUERY, str, buf, addr, addrlen);
-	}
-	if(prev)
-		prev->next = (*rr)->next;
-	else	rrset->rr_first = (*rr)->next;
-	if(rrset->rr_last == *rr)
-		rrset->rr_last = prev;
-	rrset->rr_count --;
-	rrset->size -= (*rr)->size;
-	/* rr struct still exists, but is unlinked, so that in the for loop
-	 * the rr->next works fine to continue. */
-	return rrset->rr_count == 0;
-}
-
 int priv_rrset_bad(struct iter_priv* priv, sldns_buffer* pkt,
 	struct rrset_parse* rrset)
 {
@@ -261,7 +239,7 @@ int priv_rrset_bad(struct iter_priv* priv, sldns_buffer* pkt,
 					INET_SIZE);
 				memmove(&addr, &sa, len);
 				if(priv_lookup_addr(priv, &addr, len)) {
-					if(remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, &rr, &addr, len))
+					if(msgparse_rrset_remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, rr, &addr, len))
 						return 1;
 					continue;
 				}
@@ -284,7 +262,7 @@ int priv_rrset_bad(struct iter_priv* priv, sldns_buffer* pkt,
 					INET6_SIZE);
 				memmove(&addr, &sa, len);
 				if(priv_lookup_addr(priv, &addr, len)) {
-					if(remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, &rr, &addr, len))
+					if(msgparse_rrset_remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, rr, &addr, len))
 						return 1;
 					continue;
 				}
diff --git a/iterator/iter_resptype.c b/iterator/iter_resptype.c
index e85595b84..38e186e79 100644
--- a/iterator/iter_resptype.c
+++ b/iterator/iter_resptype.c
@@ -42,6 +42,7 @@
 #include "config.h"
 #include "iterator/iter_resptype.h"
 #include "iterator/iter_delegpt.h"
+#include "iterator/iterator.h"
 #include "services/cache/dns.h"
 #include "util/net_help.h"
 #include "util/data/dname.h"
@@ -105,7 +106,8 @@ response_type_from_cache(struct dns_msg* msg,
 
 enum response_type 
 response_type_from_server(int rdset,
-	struct dns_msg* msg, struct query_info* request, struct delegpt* dp)
+	struct dns_msg* msg, struct query_info* request, struct delegpt* dp,
+	int* empty_nodata_found)
 {
 	uint8_t* origzone = (uint8_t*)"\000"; /* the default */
 	struct ub_packed_rrset_key* s;
@@ -284,13 +286,22 @@ response_type_from_server(int rdset,
 
 	/* If we've gotten this far, this is NOERROR/NODATA (which could 
 	 * be an entirely empty message) */
-	/* but ignore entirely empty messages, noerror/nodata has a soa
-	 * negative ttl value in the authority section, this makes it try
-	 * again at another authority. And turns it from a 5 second empty
-	 * message into a 5 second servfail response. */
+	/* For entirely empty messages, try again, at first, then accept
+	 * it it happens more. A regular noerror/nodata response has a soa
+	 * negative ttl value in the authority section. This makes it try
+	 * again at another authority. And decides between storing a 5 second
+	 * empty message or a 5 second servfail response. */
 	if(msg->rep->an_numrrsets == 0 && msg->rep->ns_numrrsets == 0 &&
-		msg->rep->ar_numrrsets == 0)
-		return RESPONSE_TYPE_THROWAWAY;
+		msg->rep->ar_numrrsets == 0) {
+		if(empty_nodata_found) {
+			/* detect as throwaway at first, but accept later. */
+			(*empty_nodata_found)++;
+			if(*empty_nodata_found < EMPTY_NODATA_RETRY_COUNT)
+				return RESPONSE_TYPE_THROWAWAY;
+			return RESPONSE_TYPE_ANSWER;
+		}
+		return RESPONSE_TYPE_ANSWER;
+	}
 	/* check if recursive answer; saying it has empty cache */
 	if( (msg->rep->flags&BIT_RA) && !(msg->rep->flags&BIT_AA) && !rdset)
 		return RESPONSE_TYPE_REC_LAME;
diff --git a/iterator/iter_resptype.h b/iterator/iter_resptype.h
index fee9ef35f..bfd4b664f 100644
--- a/iterator/iter_resptype.h
+++ b/iterator/iter_resptype.h
@@ -119,9 +119,11 @@ enum response_type response_type_from_cache(struct dns_msg* msg,
  * @param request: the request that generated the response.
  * @param dp: The delegation point that was being queried
  *          when the response was returned.
+ * @param empty_nodata_found: flag to keep track of empty nodata detection.
  * @return the response type (CNAME or ANSWER).
  */
 enum response_type response_type_from_server(int rdset, 
-	struct dns_msg* msg, struct query_info* request, struct delegpt* dp);
+	struct dns_msg* msg, struct query_info* request, struct delegpt* dp,
+	int* empty_nodata_found);
 
 #endif /* ITERATOR_ITER_RESPTYPE_H */
diff --git a/iterator/iter_scrub.c b/iterator/iter_scrub.c
index d1fedcd0f..5f2e30337 100644
--- a/iterator/iter_scrub.c
+++ b/iterator/iter_scrub.c
@@ -716,6 +716,56 @@ static int sanitize_nsec_is_overreach(sldns_buffer* pkt,
 	return 0;
 }
 
+/** Remove individual RRs, if the length is wrong. Returns true if the RRset
+ * has been removed. */
+static int
+scrub_sanitize_rr_length(sldns_buffer* pkt, struct msg_parse* msg,
+	struct rrset_parse* prev, struct rrset_parse** rrset, int* added_ede,
+	struct module_qstate* qstate)
+{
+	struct rr_parse* rr, *rr_prev = NULL;
+	for(rr = (*rrset)->rr_first; rr; rr = rr->next) {
+
+		/* Sanity check for length of records
+		 * An A record should be 6 bytes only
+		 * (2 bytes for length and 4 for IPv4 addr)*/
+		if((*rrset)->type == LDNS_RR_TYPE_A && rr->size != 6 ) {
+			if(!*added_ede) {
+				*added_ede = 1;
+				errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.",
+					LDNS_EDE_OTHER);
+			}
+			if(msgparse_rrset_remove_rr("sanitize: removing type A RR of inappropriate length:",
+				pkt, *rrset, rr_prev, rr, NULL, 0)) {
+				remove_rrset("sanitize: removing type A RRset of inappropriate length:",
+					pkt, msg, prev, rrset);
+				return 1;
+			}
+			continue;
+		}
+
+		/* Sanity check for length of records
+		 * An AAAA record should be 18 bytes only
+		 * (2 bytes for length and 16 for IPv6 addr)*/
+		if((*rrset)->type == LDNS_RR_TYPE_AAAA && rr->size != 18 ) {
+			if(!*added_ede) {
+				*added_ede = 1;
+				errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.",
+					LDNS_EDE_OTHER);
+			}
+			if(msgparse_rrset_remove_rr("sanitize: removing type AAAA RR of inappropriate length:",
+				pkt, *rrset, rr_prev, rr, NULL, 0)) {
+				remove_rrset("sanitize: removing type AAAA RRset of inappropriate length:",
+					pkt, msg, prev, rrset);
+				return 1;
+			}
+			continue;
+		}
+		rr_prev = rr;
+	}
+	return 0;
+}
+
 /**
  * Given a response event, remove suspect RRsets from the response.
  * "Suspect" rrsets are potentially poison. Note that this routine expects
@@ -728,15 +778,17 @@ static int sanitize_nsec_is_overreach(sldns_buffer* pkt,
  * @param zonename: name of server zone.
  * @param env: module environment with config and cache.
  * @param ie: iterator environment with private address data.
+ * @param qstate: for setting errinf for EDE error messages.
  * @return 0 on error.
  */
 static int
 scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg, 
 	struct query_info* qinfo, uint8_t* zonename, struct module_env* env,
-	struct iter_env* ie)
+	struct iter_env* ie, struct module_qstate* qstate)
 {
 	int del_addi = 0; /* if additional-holding rrsets are deleted, we
 		do not trust the normalized additional-A-AAAA any more */
+	int added_rrlen_ede = 0;
 	struct rrset_parse* rrset, *prev;
 	prev = NULL;
 	rrset = msg->rrset_first;
@@ -781,6 +833,14 @@ scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg,
 	rrset = msg->rrset_first;
 	while(rrset) {
 
+		/* Sanity check for length of records */
+		if(rrset->type == LDNS_RR_TYPE_A ||
+			rrset->type == LDNS_RR_TYPE_AAAA) {
+			if(scrub_sanitize_rr_length(pkt, msg, prev, &rrset,
+				&added_rrlen_ede, qstate))
+				continue;
+		}
+
 		/* remove private addresses */
 		if( (rrset->type == LDNS_RR_TYPE_A || 
 			rrset->type == LDNS_RR_TYPE_AAAA)) {
@@ -854,7 +914,8 @@ scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg,
 int 
 scrub_message(sldns_buffer* pkt, struct msg_parse* msg, 
 	struct query_info* qinfo, uint8_t* zonename, struct regional* region,
-	struct module_env* env, struct iter_env* ie)
+	struct module_env* env, struct module_qstate* qstate,
+	struct iter_env* ie)
 {
 	/* basic sanity checks */
 	log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS, 
@@ -886,7 +947,7 @@ scrub_message(sldns_buffer* pkt, struct msg_parse* msg,
 	if(!scrub_normalize(pkt, msg, qinfo, region, env))
 		return 0;
 	/* delete all out-of-zone information */
-	if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie))
+	if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate))
 		return 0;
 	return 1;
 }
diff --git a/iterator/iter_scrub.h b/iterator/iter_scrub.h
index cbbaf73c9..4d6ce7166 100644
--- a/iterator/iter_scrub.h
+++ b/iterator/iter_scrub.h
@@ -48,6 +48,7 @@ struct query_info;
 struct regional;
 struct module_env;
 struct iter_env;
+struct module_qstate;
 
 /**
  * Cleanup the passed dns message.
@@ -59,11 +60,13 @@ struct iter_env;
  *	Used to determine out of bailiwick information.
  * @param regional: where to allocate (new) parts of the message.
  * @param env: module environment with config settings and cache. 
+ * @param qstate: for setting errinf for EDE error messages.
  * @param ie: iterator module environment data.
  * @return: false if the message is total waste. true if scrubbed with success.
  */
 int scrub_message(struct sldns_buffer* pkt, struct msg_parse* msg, 
 	struct query_info* qinfo, uint8_t* zonename, struct regional* regional,
-	struct module_env* env, struct iter_env* ie);
+	struct module_env* env, struct module_qstate* qstate,
+	struct iter_env* ie);
 
 #endif /* ITERATOR_ITER_SCRUB_H */
diff --git a/iterator/iterator.c b/iterator/iterator.c
index 1548dfcae..6ff811a27 100644
--- a/iterator/iterator.c
+++ b/iterator/iterator.c
@@ -1449,6 +1449,39 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
 			}
 			iq->qchase.qname = sname;
 			iq->qchase.qname_len = slen;
+			if(qstate->env->auth_zones) {
+				/* apply rpz qname triggers after cname */
+				struct dns_msg* forged_response =
+					rpz_callback_from_iterator_cname(qstate, iq);
+				while(forged_response && reply_find_rrset_section_an(
+					forged_response->rep, iq->qchase.qname,
+					iq->qchase.qname_len, LDNS_RR_TYPE_CNAME,
+					iq->qchase.qclass)) {
+					/* another cname to follow */
+					if(!handle_cname_response(qstate, iq, forged_response,
+						&sname, &slen)) {
+						errinf(qstate, "malloc failure, CNAME info");
+						return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+					}
+					iq->qchase.qname = sname;
+					iq->qchase.qname_len = slen;
+					forged_response =
+						rpz_callback_from_iterator_cname(qstate, iq);
+				}
+				if(forged_response != NULL) {
+					qstate->ext_state[id] = module_finished;
+					qstate->return_rcode = LDNS_RCODE_NOERROR;
+					qstate->return_msg = forged_response;
+					iq->response = forged_response;
+					next_state(iq, FINISHED_STATE);
+					if(!iter_prepend(iq, qstate->return_msg, qstate->region)) {
+						log_err("rpz: after cached cname, prepend rrsets: out of memory");
+						return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+					}
+					qstate->return_msg->qinfo = qstate->qinfo;
+					return 0;
+				}
+			}
 			/* This *is* a query restart, even if it is a cheap 
 			 * one. */
 			iq->dp = NULL;
@@ -2875,7 +2908,8 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
 		/* unset CD if to forwarder(RD set) and not dnssec retry
 		 * (blacklist nonempty) and no trust-anchors are configured
 		 * above the qname or on the first attempt when dnssec is on */
-		EDNS_DO| ((iq->chase_to_rd||(iq->chase_flags&BIT_RD)!=0)&&
+		(qstate->env->cfg->disable_edns_do?0:EDNS_DO)|
+		((iq->chase_to_rd||(iq->chase_flags&BIT_RD)!=0)&&
 		!qstate->blacklist&&(!iter_qname_indicates_dnssec(qstate->env,
 		&iq->qinfo_out)||target->attempts==1)?0:BIT_CD),
 		iq->dnssec_expected, iq->caps_fallback || is_caps_whitelisted(
@@ -2940,7 +2974,7 @@ static int
 processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
 	struct iter_env* ie, int id)
 {
-	int dnsseclame = 0, origtypecname = 0;
+	int dnsseclame = 0, origtypecname = 0, orig_empty_nodata_found;
 	enum response_type type;
 
 	iq->num_current_queries--;
@@ -2960,12 +2994,25 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
 		return next_state(iq, QUERYTARGETS_STATE);
 	}
 	iq->timeout_count = 0;
+	orig_empty_nodata_found = iq->empty_nodata_found;
 	type = response_type_from_server(
 		(int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd),
-		iq->response, &iq->qinfo_out, iq->dp);
+		iq->response, &iq->qinfo_out, iq->dp, &iq->empty_nodata_found);
 	iq->chase_to_rd = 0;
 	/* remove TC flag, if this is erroneously set by TCP upstream */
 	iq->response->rep->flags &= ~BIT_TC;
+	if(orig_empty_nodata_found != iq->empty_nodata_found &&
+		iq->empty_nodata_found < EMPTY_NODATA_RETRY_COUNT) {
+		/* try to search at another server */
+		if(qstate->reply) {
+			struct delegpt_addr* a = delegpt_find_addr(
+				iq->dp, &qstate->reply->remote_addr,
+				qstate->reply->remote_addrlen);
+			/* make selection disprefer it */
+			if(a) a->lame = 1;
+		}
+		return next_state(iq, QUERYTARGETS_STATE);
+	}
 	if(type == RESPONSE_TYPE_REFERRAL && (iq->chase_flags&BIT_RD) &&
 		!iq->auth_zone_response) {
 		/* When forwarding (RD bit is set), we handle referrals 
@@ -3501,7 +3548,7 @@ processPrimeResponse(struct module_qstate* qstate, int id)
 	iq->response->rep->flags &= ~(BIT_RD|BIT_RA); /* ignore rec-lame */
 	type = response_type_from_server(
 		(int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd), 
-		iq->response, &iq->qchase, iq->dp);
+		iq->response, &iq->qchase, iq->dp, NULL);
 	if(type == RESPONSE_TYPE_ANSWER) {
 		qstate->return_rcode = LDNS_RCODE_NOERROR;
 		qstate->return_msg = iq->response;
@@ -3874,6 +3921,23 @@ processFinished(struct module_qstate* qstate, struct iter_qstate* iq,
 
 	/* explicitly set the EDE string to NULL */
 	iq->response->rep->reason_bogus_str = NULL;
+	if((qstate->env->cfg->val_log_level >= 2 ||
+		qstate->env->cfg->log_servfail) && qstate->errinf &&
+		!qstate->env->cfg->val_log_squelch) {
+		char* err_str = errinf_to_str_misc(qstate);
+		if(err_str) {
+			size_t err_str_len = strlen(err_str);
+			verbose(VERB_ALGO, "iterator EDE: %s", err_str);
+			/* allocate space and store the error
+			 * string */
+			iq->response->rep->reason_bogus_str = regional_alloc(
+				qstate->region,
+				sizeof(char) * (err_str_len+1));
+			memcpy(iq->response->rep->reason_bogus_str,
+				err_str, err_str_len+1);
+		}
+		free(err_str);
+	}
 
 	/* we have finished processing this query */
 	qstate->ext_state[id] = module_finished;
@@ -4098,7 +4162,7 @@ process_response(struct module_qstate* qstate, struct iter_qstate* iq,
 
 	/* normalize and sanitize: easy to delete items from linked lists */
 	if(!scrub_message(pkt, prs, &iq->qinfo_out, iq->dp->name, 
-		qstate->env->scratch, qstate->env, ie)) {
+		qstate->env->scratch, qstate->env, qstate, ie)) {
 		/* if 0x20 enabled, start fallback, but we have no message */
 		if(event == module_event_capsfail && !iq->caps_fallback) {
 			iq->caps_fallback = 1;
diff --git a/iterator/iterator.h b/iterator/iterator.h
index fad7f03e6..e253f3f7e 100644
--- a/iterator/iterator.h
+++ b/iterator/iterator.h
@@ -101,6 +101,8 @@ extern int BLACKLIST_PENALTY;
  * Chosen so that the UNKNOWN_SERVER_NICENESS falls within the band of a 
  * fast server, this causes server exploration as a side benefit. msec. */
 #define RTT_BAND 400
+/** Number of retries for empty nodata packets before it is accepted. */
+#define EMPTY_NODATA_RETRY_COUNT 2
 
 /**
  * Global state for the iterator.
@@ -415,6 +417,11 @@ struct iter_qstate {
 	 */
 	int refetch_glue;
 
+	/**
+	 * This flag detects that a completely empty nodata was received,
+	 * already so that it is accepted later. */
+	int empty_nodata_found;
+
 	/** list of pending queries to authoritative servers. */
 	struct outbound_list outlist;
 
diff --git a/libunbound/libworker.c b/libunbound/libworker.c
index 104244937..0e1c40393 100644
--- a/libunbound/libworker.c
+++ b/libunbound/libworker.c
@@ -62,6 +62,7 @@
 #include "util/random.h"
 #include "util/config_file.h"
 #include "util/netevent.h"
+#include "util/proxy_protocol.h"
 #include "util/storage/lookup3.h"
 #include "util/storage/slabhash.h"
 #include "util/net_help.h"
@@ -168,6 +169,7 @@ libworker_setup(struct ub_ctx* ctx, int is_bg, struct ub_event_base* eb)
 		hints_delete(w->env->hints);
 		w->env->hints = NULL;
 	}
+#ifdef HAVE_SSL
 	w->sslctx = connect_sslctx_create(NULL, NULL,
 		cfg->tls_cert_bundle, cfg->tls_win_cert);
 	if(!w->sslctx) {
@@ -175,6 +177,7 @@ libworker_setup(struct ub_ctx* ctx, int is_bg, struct ub_event_base* eb)
 		hints_delete(w->env->hints);
 		w->env->hints = NULL;
 	}
+#endif
 	if(!w->is_bg || w->is_bg_thread) {
 		lock_basic_unlock(&ctx->cfglock);
 	}
@@ -263,6 +266,7 @@ libworker_setup(struct ub_ctx* ctx, int is_bg, struct ub_event_base* eb)
 	w->env->kill_sub = &mesh_state_delete;
 	w->env->detect_cycle = &mesh_detect_cycle;
 	comm_base_timept(w->base, &w->env->now, &w->env->now_tv);
+	pp_init(&sldns_write_uint16, &sldns_write_uint32);
 	return w;
 }
 
diff --git a/libunbound/python/libunbound.i b/libunbound/python/libunbound.i
index 0cdb3d7e5..dc125146c 100644
--- a/libunbound/python/libunbound.i
+++ b/libunbound/python/libunbound.i
@@ -863,6 +863,9 @@ Result: ['74.125.43.147', '74.125.43.99', '74.125.43.103', '74.125.43.104']
 %inline %{
   //SWIG will see the ub_ctx as a class
   struct ub_ctx {
+          /* Dummy member, so the struct is not empty, MSVC complains about
+           * that. */
+          int dummy;
   };
 %}
 
diff --git a/pythonmod/doc/examples/example0-1.py b/pythonmod/doc/examples/example0-1.py
index 7904f73a5..506235eb4 100644
--- a/pythonmod/doc/examples/example0-1.py
+++ b/pythonmod/doc/examples/example0-1.py
@@ -1,9 +1,9 @@
 def init(id, cfg):
-   log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script))
+   log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, mod_env['script']))
    return True
 
 def init_standard(id, env):
-   log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, env.cfg.port, env.cfg.python_script))
+   log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, env.cfg.port, mod_env['script']))
    return True
 
 def deinit(id):
diff --git a/pythonmod/doc/examples/example0.rst b/pythonmod/doc/examples/example0.rst
index 693972a14..cee551de0 100644
--- a/pythonmod/doc/examples/example0.rst
+++ b/pythonmod/doc/examples/example0.rst
@@ -50,7 +50,7 @@ Script file must contain four compulsory functions:
 ::
 
    def init(id, cfg):
-      log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script))
+      log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, mod_env['script']))
       return True
 
 
@@ -69,7 +69,7 @@ Script file must contain four compulsory functions:
 ::
 
     def init_standard(id, env):
-       log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, env.cfg.port, env.cfg.python_script))
+       log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, env.cfg.port, mod_env['script']))
        return True
 
 
diff --git a/pythonmod/doc/modules/config.rst b/pythonmod/doc/modules/config.rst
index 89afbef8a..ac4db4c94 100644
--- a/pythonmod/doc/modules/config.rst
+++ b/pythonmod/doc/modules/config.rst
@@ -129,7 +129,7 @@ config_file
    
    .. attribute:: ifs
    
-      Interface description strings (IP addresses).
+      List of interface description strings (IP addresses).
 
    .. attribute:: num_out_ifs
    
@@ -138,7 +138,7 @@ config_file
 
    .. attribute:: out_ifs
    
-      Outgoing interface description strings (IP addresses).
+      List of outgoing interface description strings (IP addresses).
       
    .. attribute:: root_hints
    
@@ -339,4 +339,5 @@ config_file
 
    .. attribute:: python_script
    
-      Python script file.
+      Linked list of Python script files.
+      Deprecated; `mod_env['script']` should be used instead.
diff --git a/pythonmod/doc/modules/env.rst b/pythonmod/doc/modules/env.rst
index eae4c73c7..be5c3b1db 100644
--- a/pythonmod/doc/modules/env.rst
+++ b/pythonmod/doc/modules/env.rst
@@ -6,8 +6,11 @@ Global variables
 
 .. envvar:: mod_env
 
-   Module environment, contains data pointer for module-specific data.
-   See :class:`pythonmod_env`.
+   Module environment, it is the 'data' pointer for module-specific data
+   in :class:`pythonmod_env`.
+   It is initialized as a dictionary with the 'script' key pointing to the
+   module's python script.
+   It can be further populated during runtime for module-specific data.
 
 
 Predefined constants
diff --git a/pythonmod/examples/edns.py b/pythonmod/examples/edns.py
index ddcccc51c..4e2eebd4f 100644
--- a/pythonmod/examples/edns.py
+++ b/pythonmod/examples/edns.py
@@ -80,7 +80,7 @@ def init_standard(id, env):
     ..note:: The previously accessible configuration options can now be found in
              env.cfg.
     """
-    log_info("python: inited script {}".format(env.cfg.python_script))
+    log_info("python: inited script {}".format(mod_env['script']))
 
     # Register EDNS option 65001 as a known EDNS option.
     if not register_edns_option(env, 65001, bypass_cache_stage=True,
diff --git a/pythonmod/examples/inplace_callbacks.py b/pythonmod/examples/inplace_callbacks.py
index e1caaecc7..42806daa1 100644
--- a/pythonmod/examples/inplace_callbacks.py
+++ b/pythonmod/examples/inplace_callbacks.py
@@ -287,7 +287,7 @@ def init_standard(id, env):
              env.cfg.
 
     """
-    log_info("python: inited script {}".format(env.cfg.python_script))
+    log_info("python: inited script {}".format(mod_env['script']))
 
     # Register the inplace_reply_callback function as an inplace callback
     # function when answering a resolved query.
diff --git a/pythonmod/examples/log.py b/pythonmod/examples/log.py
index c17106b0f..03f741962 100644
--- a/pythonmod/examples/log.py
+++ b/pythonmod/examples/log.py
@@ -87,7 +87,7 @@ def logDnsMsg(qstate):
     print "-"*100
 
 def init(id, cfg):
-   log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script))
+   log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, mod_env['script']))
    return True
 
 def deinit(id):
diff --git a/pythonmod/interface.i b/pythonmod/interface.i
index a436389e1..d9839fc38 100644
--- a/pythonmod/interface.i
+++ b/pythonmod/interface.i
@@ -86,6 +86,20 @@
      }
      return list;
    }
+
+   /* converts an array of strings (char**) to a List of strings */
+   PyObject* CharArrayAsStringList(char** array, int len) {
+     PyObject* list;
+     int i;
+
+     if(!array||len==0) return PyList_New(0);
+
+     list = PyList_New(len);
+     for (i=0; i < len; i++) {
+            PyList_SET_ITEM(list, i, PyString_FromString(array[i]));
+     }
+     return list;
+   }
 %}
 
 /* ************************************************************************************ *
@@ -952,6 +966,9 @@ struct config_str2list {
 /* ************************************************************************************ *
    Structure config_file
  * ************************************************************************************ */
+%ignore config_file::ifs;
+%ignore config_file::out_ifs;
+%ignore config_file::python_script;
 struct config_file {
    int verbosity;
    int stat_interval;
@@ -1035,6 +1052,25 @@ struct config_file {
    struct config_strlist* python_script;
 };
 
+%inline %{
+   PyObject* _get_ifs_tuple(struct config_file* cfg) {
+      return CharArrayAsStringList(cfg->ifs, cfg->num_ifs);
+   }
+   PyObject* _get_ifs_out_tuple(struct config_file* cfg) {
+      return CharArrayAsStringList(cfg->out_ifs, cfg->num_out_ifs);
+   }
+%}
+
+%extend config_file {
+   %pythoncode %{
+        ifs = property(_unboundmodule._get_ifs_tuple)
+        out_ifs = property(_unboundmodule._get_ifs_out_tuple)
+
+        def _deprecated_python_script(self): return "cfg.python_script is deprecated, you can use `mod_env['script']` instead."
+        python_script = property(_deprecated_python_script)
+   %}
+}
+
 /* ************************************************************************************ *
    ASN: Adding structures related to forwards_lookup and dns_cache_find_delegation
  * ************************************************************************************ */
diff --git a/pythonmod/pythonmod.c b/pythonmod/pythonmod.c
index 628308612..c6294a1d5 100644
--- a/pythonmod/pythonmod.c
+++ b/pythonmod/pythonmod.c
@@ -112,6 +112,34 @@ struct pythonmod_qstate {
 	PyObject* data;
 };
 
+/* The dict from __main__ could have remnants from a previous script
+ * invocation, in a multi python module setup. Usually this is fine since newer
+ * scripts will update their values. The obvious erroneous case is when mixing
+ * python scripts that make use of both 'init' and 'init_standard'. This
+ * results in 'init_standard' to persist on following scripts that don't use it
+ * (thus not replacing it). This is also problematic in case where a script
+ * does not define a required function but a previously loaded script did. The
+ * current solution is to make sure to clean offensive remnants that influence
+ * further parsing of the individual scripts.
+ */
+static void
+clean_python_function_objects(PyObject* dict) {
+	const char* function_names[] = {
+		"init",
+		"init_standard",
+		"deinit",
+		"operate",
+		"inform_super"
+	};
+	size_t i;
+
+	for(i=0; i<sizeof(function_names)/sizeof(function_names[0]); i++) {
+		if(PyDict_GetItemString(dict, function_names[i]) != NULL) {
+			PyDict_DelItemString(dict, function_names[i]);
+		}
+	}
+};
+
 /* Generated */
 #ifndef S_SPLINT_S
 #include "pythonmod/interface.h"
@@ -269,7 +297,7 @@ int pythonmod_init(struct module_env* env, int id)
 
    /* Initialize module */
    FILE* script_py = NULL;
-   PyObject* py_init_arg = NULL, *res = NULL;
+   PyObject* py_init_arg = NULL, *res = NULL, *fname = NULL;
    PyGILState_STATE gil;
    int init_standard = 1, i = 0;
 #if PY_MAJOR_VERSION < 3
@@ -418,7 +446,17 @@ int pythonmod_init(struct module_env* env, int id)
    Py_XINCREF(pe->module);
    pe->dict = PyModule_GetDict(pe->module);
    Py_XINCREF(pe->dict);
+   clean_python_function_objects(pe->dict);
+
    pe->data = PyDict_New();
+   /* add the script filename to the global "mod_env" for trivial access */
+   fname = PyString_FromString(pe->fname);
+   if(PyDict_SetItemString(pe->data, "script", fname) < 0) {
+	log_err("pythonmod: could not add item to dictionary");
+	Py_XDECREF(fname);
+	goto python_init_fail;
+   }
+   Py_XDECREF(fname);
    Py_XINCREF(pe->data);  /* reference will be stolen below */
    if(PyModule_AddObject(pe->module, "mod_env", pe->data) < 0) {
 	log_err("pythonmod: could not add mod_env object");
diff --git a/pythonmod/ubmodule-msg.py b/pythonmod/ubmodule-msg.py
index 648368080..6a690e281 100644
--- a/pythonmod/ubmodule-msg.py
+++ b/pythonmod/ubmodule-msg.py
@@ -35,7 +35,7 @@
 import os
 
 def init(id, cfg):
-    log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script))
+    log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, mod_env['script']))
     return True
 
 def deinit(id):
diff --git a/pythonmod/ubmodule-tst.py b/pythonmod/ubmodule-tst.py
index 0b9b5a9d2..07543e399 100644
--- a/pythonmod/ubmodule-tst.py
+++ b/pythonmod/ubmodule-tst.py
@@ -33,7 +33,7 @@
  POSSIBILITY OF SUCH DAMAGE.
 '''
 def init(id, cfg):
-    log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script))
+    log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, mod_env['script']))
     return True
 
 def deinit(id):
@@ -55,12 +55,15 @@ def setTTL(qstate, ttl):
 
 def dataHex(data, prefix=""):
     res = ""
-    for i in range(0, (len(data)+15)/16):
+    for i in range(0, int((len(data)+15)/16)):
         res += "%s0x%02X | " % (prefix, i*16)
-        d = map(lambda x:ord(x), data[i*16:i*16+17])
+        if type(data[0]) == type(1):
+            d = map(lambda x:int(x), data[i*16:i*16+17])
+        else:
+            d = map(lambda x:ord(x), data[i*16:i*16+17])
         for ch in d:
             res += "%02X " % ch
-        for i in range(0,17-len(d)):
+        for i in range(0,17-len(data[i*16:i*16+17])):
             res += "   "
         res += "| "
         for ch in d:
@@ -72,31 +75,31 @@ def dataHex(data, prefix=""):
     return res
 
 def printReturnMsg(qstate):
-    print "Return MSG rep   :: flags: %04X, QDcount: %d, Security:%d, TTL=%d" % (qstate.return_msg.rep.flags, qstate.return_msg.rep.qdcount,qstate.return_msg.rep.security, qstate.return_msg.rep.ttl)
-    print "           qinfo :: qname:",qstate.return_msg.qinfo.qname_list, qstate.return_msg.qinfo.qname_str, "type:",qstate.return_msg.qinfo.qtype_str, "class:",qstate.return_msg.qinfo.qclass_str
+    print("Return MSG rep   :: flags: %04X, QDcount: %d, Security:%d, TTL=%d" % (qstate.return_msg.rep.flags, qstate.return_msg.rep.qdcount,qstate.return_msg.rep.security, qstate.return_msg.rep.ttl))
+    print("           qinfo :: qname:",qstate.return_msg.qinfo.qname_list, qstate.return_msg.qinfo.qname_str, "type:",qstate.return_msg.qinfo.qtype_str, "class:",qstate.return_msg.qinfo.qclass_str)
     if (qstate.return_msg.rep):
-        print "RRSets:",qstate.return_msg.rep.rrset_count
+        print("RRSets:",qstate.return_msg.rep.rrset_count)
         prevkey = None
         for i in range(0,qstate.return_msg.rep.rrset_count):
             r = qstate.return_msg.rep.rrsets[i]
             rk = r.rk
-            print i,":",rk.dname_list, rk.dname_str, "flags: %04X" % rk.flags,
-            print "type:",rk.type_str,"(%d)" % ntohs(rk.type), "class:",rk.rrset_class_str,"(%d)" % ntohs(rk.rrset_class)
+            print(i,":",rk.dname_list, rk.dname_str, "flags: %04X" % rk.flags)
+            print("type:",rk.type_str,"(%d)" % ntohs(rk.type), "class:",rk.rrset_class_str,"(%d)" % ntohs(rk.rrset_class))
 
             d = r.entry.data
-            print "    RRDatas:",d.count+d.rrsig_count
+            print("    RRDatas:",d.count+d.rrsig_count)
             for j in range(0,d.count+d.rrsig_count):
-                print "    ",j,":","TTL=",d.rr_ttl[j],"RR data:"
-                print dataHex(d.rr_data[j],"         ")
+                print("    ",j,":","TTL=",d.rr_ttl[j],"RR data:")
+                print(dataHex(d.rr_data[j],"         "))
 
 def operate(id, event, qstate, qdata):
     log_info("pythonmod: operate called, id: %d, event:%s" % (id, strmodulevent(event)))
-    #print "pythonmod: per query data", qdata
+    #print("pythonmod: per query data", qdata)
 
-    print "Query:", ''.join(map(lambda x:chr(max(32,ord(x))),qstate.qinfo.qname)), qstate.qinfo.qname_list, 
-    print "Type:",qstate.qinfo.qtype_str,"(%d)" % qstate.qinfo.qtype,
-    print "Class:",qstate.qinfo.qclass_str,"(%d)" % qstate.qinfo.qclass
-    print
+    print("Query:", qstate.qinfo.qname, qstate.qinfo.qname_list, qstate.qinfo.qname_str)
+    print("Type:",qstate.qinfo.qtype_str,"(%d)" % qstate.qinfo.qtype)
+    print("Class:",qstate.qinfo.qclass_str,"(%d)" % qstate.qinfo.qclass)
+    print("")
 
     # TEST:
     #   > dig @127.0.0.1 www.seznam.cz A
@@ -118,7 +121,7 @@ def operate(id, event, qstate, qdata):
             invalidateQueryInCache(qstate, qstate.return_msg.qinfo)
 
             if (qstate.return_msg.rep.authoritative):
-                print "X"*300
+                print("X"*300)
 
             setTTL(qstate, 10) #do cache nastavime TTL na 10
             if not storeQueryInCache(qstate, qstate.return_msg.qinfo, qstate.return_msg.rep, 0):
diff --git a/services/authzone.c b/services/authzone.c
index cd3ef8dbb..a1b3d2278 100644
--- a/services/authzone.c
+++ b/services/authzone.c
@@ -2475,6 +2475,7 @@ az_find_ce(struct auth_zone* z, struct query_info* qinfo,
 	struct auth_rrset** rrset)
 {
 	struct auth_data* n = node;
+	struct auth_rrset* lookrrset;
 	*ce = NULL;
 	*rrset = NULL;
 	if(!node_exact) {
@@ -2497,21 +2498,23 @@ az_find_ce(struct auth_zone* z, struct query_info* qinfo,
 		/* see if the current candidate has issues */
 		/* not zone apex and has type NS */
 		if(n->namelen != z->namelen &&
-			(*rrset=az_domain_rrset(n, LDNS_RR_TYPE_NS)) &&
+			(lookrrset=az_domain_rrset(n, LDNS_RR_TYPE_NS)) &&
 			/* delegate here, but DS at exact the dp has notype */
 			(qinfo->qtype != LDNS_RR_TYPE_DS || 
 			n->namelen != qinfo->qname_len)) {
 			/* referral */
 			/* this is ce and the lowernode is nonexisting */
 			*ce = n;
-			return 0;
+			*rrset = lookrrset;
+			node_exact = 0;
 		}
 		/* not equal to qname and has type DNAME */
 		if(n->namelen != qinfo->qname_len &&
-			(*rrset=az_domain_rrset(n, LDNS_RR_TYPE_DNAME))) {
+			(lookrrset=az_domain_rrset(n, LDNS_RR_TYPE_DNAME))) {
 			/* this is ce and the lowernode is nonexisting */
 			*ce = n;
-			return 0;
+			*rrset = lookrrset;
+			node_exact = 0;
 		}
 
 		if(*ce == NULL && !domain_has_only_nsec3(n)) {
diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c
index 60f9b41e5..753550978 100644
--- a/services/listen_dnsport.c
+++ b/services/listen_dnsport.c
@@ -1327,7 +1327,9 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp,
 			log_warn("socket timestamping is not available");
 		}
 		if(!port_insert(list, s, is_dnscrypt
-			?listen_type_udp_dnscrypt:listen_type_udp,
+			?listen_type_udp_dnscrypt :
+			(sock_queue_timeout ?
+				listen_type_udpancil:listen_type_udp),
 			is_pp2, ub_sock)) {
 			sock_close(s);
 			if(ub_sock->addr)
@@ -1498,9 +1500,13 @@ listen_create(struct comm_base* base, struct listen_port* ports,
 			}
 		} else if(ports->ftype == listen_type_udpancil ||
 				  ports->ftype == listen_type_udpancil_dnscrypt) {
+#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
 			cp = comm_point_create_udp_ancil(base, ports->fd,
 				front->udp_buff, ports->pp2_enabled, cb,
 				cb_arg, ports->socket);
+#else
+			log_warn("This system does not support UDP ancilliary data.");
+#endif
 		}
 		if(!cp) {
 			log_err("can't create commpoint");
diff --git a/services/mesh.c b/services/mesh.c
index 52d14a2d1..509bee36a 100644
--- a/services/mesh.c
+++ b/services/mesh.c
@@ -1197,6 +1197,8 @@ mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep,
 		r->edns.udp_size = EDNS_ADVERTISED_SIZE;
 		r->edns.ext_rcode = 0;
 		r->edns.bits &= EDNS_DO;
+		if(m->s.env->cfg->disable_edns_do && (r->edns.bits&EDNS_DO))
+			r->edns.edns_present = 0;
 
 		if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep,
 			LDNS_RCODE_NOERROR, &r->edns, NULL, m->s.region, start_time) ||
@@ -1224,11 +1226,12 @@ static inline int
 mesh_is_rpz_respip_tcponly_action(struct mesh_state const* m)
 {
 	struct respip_action_info const* respip_info = m->s.respip_action_info;
-	return respip_info == NULL
+	return (respip_info == NULL
 			? 0
 			: (respip_info->rpz_used
 			&& !respip_info->rpz_disabled
-			&& respip_info->action == respip_truncate);
+			&& respip_info->action == respip_truncate))
+		|| m->s.tcp_required;
 }
 
 static inline int
@@ -1371,6 +1374,8 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
 		r->edns.udp_size = EDNS_ADVERTISED_SIZE;
 		r->edns.ext_rcode = 0;
 		r->edns.bits &= EDNS_DO;
+		if(m->s.env->cfg->disable_edns_do && (r->edns.bits&EDNS_DO))
+			r->edns.edns_present = 0;
 		m->s.qinfo.qname = r->qname;
 		m->s.qinfo.local_alias = r->local_alias;
 
diff --git a/services/outside_network.c b/services/outside_network.c
index 77de4563c..e403f5ee7 100644
--- a/services/outside_network.c
+++ b/services/outside_network.c
@@ -550,7 +550,6 @@ reuse_tcp_find(struct outside_network* outnet, struct sockaddr_storage* addr,
 		log_assert(&key_p.reuse != (struct reuse_tcp*)result);
 		log_assert(&key_p != ((struct reuse_tcp*)result)->pending);
 	}
-	/* not found, return null */
 
 	/* It is possible that we search for something before the first element
 	 * in the tree. Replace a null pointer with the first element.
@@ -560,6 +559,7 @@ reuse_tcp_find(struct outside_network* outnet, struct sockaddr_storage* addr,
 		result = rbtree_first(&outnet->tcp_reuse);
 	}
 
+	/* not found, return null */
 	if(!result || result == RBTREE_NULL)
 		return NULL;
 
diff --git a/services/rpz.c b/services/rpz.c
index 6ce83cb66..18d76c07b 100644
--- a/services/rpz.c
+++ b/services/rpz.c
@@ -2162,7 +2162,7 @@ rpz_apply_nsip_trigger(struct module_qstate* ms, struct rpz* r,
 	case RPZ_TCP_ONLY_ACTION:
 		/* basically a passthru here but the tcp-only will be
 		 * honored before the query gets sent. */
-		ms->respip_action_info->action = respip_truncate;
+		ms->tcp_required = 1;
 		ret = NULL;
 		break;
 	case RPZ_DROP_ACTION:
@@ -2217,7 +2217,7 @@ rpz_apply_nsdname_trigger(struct module_qstate* ms, struct rpz* r,
 	case RPZ_TCP_ONLY_ACTION:
 		/* basically a passthru here but the tcp-only will be
 		 * honored before the query gets sent. */
-		ms->respip_action_info->action = respip_truncate;
+		ms->tcp_required = 1;
 		ret = NULL;
 		break;
 	case RPZ_DROP_ACTION:
@@ -2428,7 +2428,7 @@ struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms,
 	case RPZ_TCP_ONLY_ACTION:
 		/* basically a passthru here but the tcp-only will be
 		 * honored before the query gets sent. */
-		ms->respip_action_info->action = respip_truncate;
+		ms->tcp_required = 1;
 		ret = NULL;
 		break;
 	case RPZ_DROP_ACTION:
@@ -2448,6 +2448,10 @@ struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms,
 			rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
 		ret = NULL;
 	}
+	if(r->log)
+		log_rpz_apply("qname", (z?z->name:NULL), NULL,
+			localzone_type_to_rpz_action(lzt),
+			&is->qchase, NULL, ms, r->log_name);
 	lock_rw_unlock(&z->lock);
 	lock_rw_unlock(&a->lock);
 	return ret;
diff --git a/sldns/wire2str.c b/sldns/wire2str.c
index e6278ff56..2b5dc0513 100644
--- a/sldns/wire2str.c
+++ b/sldns/wire2str.c
@@ -192,6 +192,7 @@ static sldns_lookup_table sldns_edns_options_data[] = {
 	{ 6, "DHU" },
 	{ 7, "N3U" },
 	{ 8, "edns-client-subnet" },
+	{ 10, "COOKIE" },
 	{ 11, "edns-tcp-keepalive"},
 	{ 12, "Padding" },
 	{ 15, "EDE"},
@@ -199,6 +200,38 @@ static sldns_lookup_table sldns_edns_options_data[] = {
 };
 sldns_lookup_table* sldns_edns_options = sldns_edns_options_data;
 
+/* From RFC8914 5.2 Table 3, the "Extended DNS Error Codes" registry. */
+static sldns_lookup_table sldns_edns_ede_codes_data[] = {
+	{ LDNS_EDE_NONE, "None" },
+	{ LDNS_EDE_OTHER, "Other Error" },
+	{ LDNS_EDE_UNSUPPORTED_DNSKEY_ALG, "Unsupported DNSKEY Algorithm" },
+	{ LDNS_EDE_UNSUPPORTED_DS_DIGEST, "Unsupported DS Digest Type" },
+	{ LDNS_EDE_STALE_ANSWER, "Stale Answer" },
+	{ LDNS_EDE_FORGED_ANSWER, "Forged Answer" },
+	{ LDNS_EDE_DNSSEC_INDETERMINATE, "DNSSEC Indeterminate" },
+	{ LDNS_EDE_DNSSEC_BOGUS, "DNSSEC Bogus" },
+	{ LDNS_EDE_SIGNATURE_EXPIRED, "Signature Expired" },
+	{ LDNS_EDE_SIGNATURE_NOT_YET_VALID, "Signature Not Yet Valid" },
+	{ LDNS_EDE_DNSKEY_MISSING, "DNSKEY Missing" },
+	{ LDNS_EDE_RRSIGS_MISSING, "RRSIGs Missing" },
+	{ LDNS_EDE_NO_ZONE_KEY_BIT_SET, "No Zone Key Bit Set" },
+	{ LDNS_EDE_NSEC_MISSING, "NSEC Missing" },
+	{ LDNS_EDE_CACHED_ERROR, "Cached Error" },
+	{ LDNS_EDE_NOT_READY, "Not Ready" },
+	{ LDNS_EDE_BLOCKED, "Blocked" },
+	{ LDNS_EDE_CENSORED, "Censored" },
+	{ LDNS_EDE_FILTERED, "Filtered" },
+	{ LDNS_EDE_PROHIBITED, "Prohibited" },
+	{ LDNS_EDE_STALE_NXDOMAIN_ANSWER, "Stale NXDOMAIN Answer" },
+	{ LDNS_EDE_NOT_AUTHORITATIVE, "Not Authoritative" },
+	{ LDNS_EDE_NOT_SUPPORTED, "Not Supported" },
+	{ LDNS_EDE_NO_REACHABLE_AUTHORITY, "No Reachable Authority" },
+	{ LDNS_EDE_NETWORK_ERROR, "Network Error" },
+	{ LDNS_EDE_INVALID_DATA, "Invalid Data" },
+	{ 0, NULL}
+};
+sldns_lookup_table* sldns_edns_ede_codes = sldns_edns_ede_codes_data;
+
 static sldns_lookup_table sldns_tsig_errors_data[] = {
 	{ LDNS_TSIG_ERROR_NOERROR, "NOERROR" },
 	{ LDNS_RCODE_FORMERR, "FORMERR" },
@@ -2234,6 +2267,52 @@ static int sldns_wire2str_edns_keepalive_print(char** s, size_t* sl,
 	return w;
 }
 
+int sldns_wire2str_edns_ede_print(char** s, size_t* sl,
+	uint8_t* data, size_t len)
+{
+	uint16_t ede_code;
+	int w = 0;
+	sldns_lookup_table *lt;
+	size_t i;
+	int printable;
+
+	if(len < 2) {
+		w += sldns_str_print(s, sl, "malformed ede ");
+		w += print_hex_buf(s, sl, data, len);
+		return w;
+	}
+
+	ede_code = sldns_read_uint16(data);
+	lt = sldns_lookup_by_id(sldns_edns_ede_codes, (int)ede_code);
+	if(lt && lt->name)
+		w += sldns_str_print(s, sl, "%s", lt->name);
+	else 	w += sldns_str_print(s, sl, "%d", (int)ede_code);
+
+	if(len == 2)
+		return w;
+
+	w += sldns_str_print(s, sl, " ");
+
+	/* If it looks like text, show it as text. */
+	printable=1;
+	for(i=2; i<len; i++) {
+		if(isprint((unsigned char)data[i]) || data[i] == '\t')
+			continue;
+		printable = 0;
+		break;
+	}
+	if(printable) {
+		w += sldns_str_print(s, sl, "\"");
+		for(i=2; i<len; i++) {
+			w += str_char_print(s, sl, data[i]);
+		}
+		w += sldns_str_print(s, sl, "\"");
+	} else {
+		w += print_hex_buf(s, sl, data+2, len-2);
+	}
+	return w;
+}
+
 int sldns_wire2str_edns_option_print(char** s, size_t* sl,
 	uint16_t option_code, uint8_t* optdata, size_t optlen)
 {
@@ -2268,6 +2347,9 @@ int sldns_wire2str_edns_option_print(char** s, size_t* sl,
 	case LDNS_EDNS_PADDING:
 		w += print_hex_buf(s, sl, optdata, optlen);
 		break;
+	case LDNS_EDNS_EDE:
+		w += sldns_wire2str_edns_ede_print(s, sl, optdata, optlen);
+		break;
 	default:
 		/* unknown option code */
 		w += print_hex_buf(s, sl, optdata, optlen);
diff --git a/sldns/wire2str.h b/sldns/wire2str.h
index 548c66300..a7c58b85a 100644
--- a/sldns/wire2str.h
+++ b/sldns/wire2str.h
@@ -36,6 +36,8 @@ extern struct sldns_struct_lookup_table* sldns_opcodes;
 extern struct sldns_struct_lookup_table* sldns_edns_flags;
 /** EDNS option codes */
 extern struct sldns_struct_lookup_table* sldns_edns_options;
+/** EDNS EDE codes */
+extern struct sldns_struct_lookup_table* sldns_edns_ede_codes;
 /** error string from wireparse */
 extern struct sldns_struct_lookup_table* sldns_wireparse_errors;
 /** tsig errors are the rcodes with extra (higher) values */
@@ -1020,6 +1022,17 @@ int sldns_wire2str_edns_n3u_print(char** str, size_t* str_len,
 int sldns_wire2str_edns_subnet_print(char** str, size_t* str_len,
 	uint8_t* option_data, size_t option_len);
 
+/**
+ * Print EDNS EDE option data to string. User buffers, moves string pointers.
+ * @param str: string buffer.
+ * @param str_len: length of string buffer.
+ * @param option_data: buffer with EDNS option code data.
+ * @param option_len: length of the data for this option.
+ * @return number of characters (except null) needed to print.
+ */
+int sldns_wire2str_edns_ede_print(char** str, size_t* str_len,
+	uint8_t* option_data, size_t option_len);
+
 /**
  * Print an EDNS option as OPT: VALUE.  User buffers, moves string pointers.
  * @param str: string buffer.
diff --git a/smallapp/unbound-checkconf.c b/smallapp/unbound-checkconf.c
index ff8043711..8b45578fa 100644
--- a/smallapp/unbound-checkconf.c
+++ b/smallapp/unbound-checkconf.c
@@ -707,6 +707,23 @@ morechecks(struct config_file* cfg)
 		cfg->auto_trust_anchor_file_list, cfg->chrootdir, cfg);
 	check_chroot_filelist_wild("trusted-keys-file",
 		cfg->trusted_keys_file_list, cfg->chrootdir, cfg);
+	if(cfg->disable_edns_do && strstr(cfg->module_conf, "validator")
+		&& (cfg->trust_anchor_file_list
+		|| cfg->trust_anchor_list
+		|| cfg->auto_trust_anchor_file_list
+		|| cfg->trusted_keys_file_list)) {
+		char* key = NULL;
+		if(cfg->auto_trust_anchor_file_list)
+			key = cfg->auto_trust_anchor_file_list->str;
+		if(!key && cfg->trust_anchor_file_list)
+			key = cfg->trust_anchor_file_list->str;
+		if(!key && cfg->trust_anchor_list)
+			key = cfg->trust_anchor_list->str;
+		if(!key && cfg->trusted_keys_file_list)
+			key = cfg->trusted_keys_file_list->str;
+		if(!key) key = "";
+		fatal_exit("disable-edns-do does not allow DNSSEC to work, but the validator module uses a trust anchor %s, turn off disable-edns-do or disable validation", key);
+	}
 #ifdef USE_IPSECMOD
 	if(cfg->ipsecmod_enabled && strstr(cfg->module_conf, "ipsecmod")) {
 		/* only check hook if enabled */
diff --git a/testcode/dohclient.c b/testcode/dohclient.c
index de9f39d7d..2c12a5043 100644
--- a/testcode/dohclient.c
+++ b/testcode/dohclient.c
@@ -286,7 +286,7 @@ static ssize_t http2_recv_cb(nghttp2_session* ATTR_UNUSED(session),
 			if(want == SSL_ERROR_ZERO_RETURN) {
 				return NGHTTP2_ERR_EOF;
 			}
-			log_crypto_err("could not SSL_read");
+			log_crypto_err_io("could not SSL_read", want);
 			return NGHTTP2_ERR_EOF;
 		}
 		return r;
@@ -317,7 +317,7 @@ static ssize_t http2_send_cb(nghttp2_session* ATTR_UNUSED(session),
 			if(want == SSL_ERROR_ZERO_RETURN) {
 				return NGHTTP2_ERR_CALLBACK_FAILURE;
 			}
-			log_crypto_err("could not SSL_write");
+			log_crypto_err_io("could not SSL_write", want);
 			return NGHTTP2_ERR_CALLBACK_FAILURE;
 		}
 		return r;
@@ -526,7 +526,7 @@ run(struct http2_session* h2_session, int port, int no_tls, int count, char** q)
 			r = SSL_get_error(ssl, r);
 			if(r != SSL_ERROR_WANT_READ &&
 				r != SSL_ERROR_WANT_WRITE) {
-				log_crypto_err("could not ssl_handshake");
+				log_crypto_err_io("could not ssl_handshake", r);
 				exit(1);
 			}
 		}
diff --git a/testcode/fake_event.c b/testcode/fake_event.c
index 2140b212a..13970c377 100644
--- a/testcode/fake_event.c
+++ b/testcode/fake_event.c
@@ -1249,7 +1249,7 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet,
 		edns.edns_version = EDNS_ADVERTISED_VERSION;
 		edns.udp_size = EDNS_ADVERTISED_SIZE;
 		edns.bits = 0;
-		if(dnssec)
+		if((dnssec & EDNS_DO))
 			edns.bits = EDNS_DO;
 		edns.padding_block_size = 0;
 		edns.cookie_present = 0;
diff --git a/testcode/streamtcp.1 b/testcode/streamtcp.1
index f02b168d2..55ed4a279 100644
--- a/testcode/streamtcp.1
+++ b/testcode/streamtcp.1
@@ -61,6 +61,17 @@ Specify the server to send the queries to. If not specified localhost (127.0.0.1
 .B \-d \fIsecs
 Delay after the connection before sending query.  This tests the timeout
 on the other side, eg. if shorter the connection is closed.
+.TP
+.B \-p \fIclient
+Use proxy protocol to send the query. Specify the ipaddr@portnr of the client
+to include in PROXYv2.
+.TP
+.B IXFR=serial
+Pass the type of the query as IXFR=N to send an IXFR query with serial N.
+.TP
+.B NOTIFY[=serial]
+Pass the type of the query as NOTIFY[=N] to send a notify packet. The serial N
+of the new zone can be included.
 .SH "EXAMPLES"
 .LP
 Some examples of use.
diff --git a/testcode/streamtcp.c b/testcode/streamtcp.c
index 84d2b65f6..037bcfd8b 100644
--- a/testcode/streamtcp.c
+++ b/testcode/streamtcp.c
@@ -79,6 +79,8 @@ static void usage(char* argv[])
 	printf("-d secs		delay after connection before sending query\n");
 	printf("-s		use ssl\n");
 	printf("-h 		this help text\n");
+	printf("IXFR=N 		for the type, sends ixfr query with serial N.\n");
+	printf("NOTIFY[=N] 	for the type, sends notify. Can set new zone serial N.\n");
 	exit(1);
 }
 
@@ -115,6 +117,29 @@ open_svr(const char* svr, int udp, struct sockaddr_storage* addr,
 	return fd;
 }
 
+/** Append a SOA record with serial number */
+static void
+write_soa_serial_to_buf(sldns_buffer* buf, struct query_info* qinfo,
+	uint32_t serial)
+{
+	sldns_buffer_set_position(buf, sldns_buffer_limit(buf));
+	sldns_buffer_set_limit(buf, sldns_buffer_capacity(buf));
+	/* Write compressed reference to the query */
+	sldns_buffer_write_u16(buf, PTR_CREATE(LDNS_HEADER_SIZE));
+	sldns_buffer_write_u16(buf, LDNS_RR_TYPE_SOA);
+	sldns_buffer_write_u16(buf, qinfo->qclass);
+	sldns_buffer_write_u32(buf, 3600); /* TTL */
+	sldns_buffer_write_u16(buf, 1+1+4*5); /* rdatalen */
+	sldns_buffer_write_u8(buf, 0); /* primary "." */
+	sldns_buffer_write_u8(buf, 0); /* email "." */
+	sldns_buffer_write_u32(buf, serial); /* serial */
+	sldns_buffer_write_u32(buf, 0); /* refresh */
+	sldns_buffer_write_u32(buf, 0); /* retry */
+	sldns_buffer_write_u32(buf, 0); /* expire */
+	sldns_buffer_write_u32(buf, 0); /* minimum */
+	sldns_buffer_flip(buf);
+}
+
 /** write a query over the TCP fd */
 static void
 write_q(int fd, int udp, SSL* ssl, sldns_buffer* buf, uint16_t id,
@@ -123,6 +148,8 @@ write_q(int fd, int udp, SSL* ssl, sldns_buffer* buf, uint16_t id,
 {
 	struct query_info qinfo;
 	size_t proxy_buf_limit = sldns_buffer_limit(proxy_buf);
+	int have_serial = 0, is_notify = 0;
+	uint32_t serial = 0;
 	/* qname */
 	qinfo.qname = sldns_str2wire_dname(strname, &qinfo.qname_len);
 	if(!qinfo.qname) {
@@ -130,12 +157,27 @@ write_q(int fd, int udp, SSL* ssl, sldns_buffer* buf, uint16_t id,
 		exit(1);
 	}
 
-	/* qtype and qclass */
-	qinfo.qtype = sldns_get_rr_type_by_name(strtype);
-	if(qinfo.qtype == 0 && strcmp(strtype, "TYPE0") != 0) {
-		printf("cannot parse query type: '%s'\n", strtype);
-		exit(1);
+	/* qtype */
+	if(strncasecmp(strtype, "IXFR=", 5) == 0) {
+		serial = (uint32_t)atoi(strtype+5);
+		have_serial = 1;
+		qinfo.qtype = LDNS_RR_TYPE_IXFR;
+	} else if(strcasecmp(strtype, "NOTIFY") == 0) {
+		is_notify = 1;
+		qinfo.qtype = LDNS_RR_TYPE_SOA;
+	} else if(strncasecmp(strtype, "NOTIFY=", 7) == 0) {
+		serial = (uint32_t)atoi(strtype+7);
+		have_serial = 1;
+		is_notify = 1;
+		qinfo.qtype = LDNS_RR_TYPE_SOA;
+	} else {
+		qinfo.qtype = sldns_get_rr_type_by_name(strtype);
+		if(qinfo.qtype == 0 && strcmp(strtype, "TYPE0") != 0) {
+			printf("cannot parse query type: '%s'\n", strtype);
+			exit(1);
+		}
 	}
+	/* qclass */
 	qinfo.qclass = sldns_get_rr_class_by_name(strclass);
 	if(qinfo.qclass == 0 && strcmp(strclass, "CLASS0") != 0) {
 		printf("cannot parse query class: '%s'\n", strclass);
@@ -150,6 +192,21 @@ write_q(int fd, int udp, SSL* ssl, sldns_buffer* buf, uint16_t id,
 	sldns_buffer_write_u16_at(buf, 0, id);
 	sldns_buffer_write_u16_at(buf, 2, BIT_RD);
 
+	if(have_serial && qinfo.qtype == LDNS_RR_TYPE_IXFR) {
+		/* Attach serial to SOA record in the authority section. */
+		write_soa_serial_to_buf(buf, &qinfo, serial);
+		LDNS_NSCOUNT_SET(sldns_buffer_begin(buf), 1);
+	}
+	if(is_notify) {
+		LDNS_OPCODE_SET(sldns_buffer_begin(buf), LDNS_PACKET_NOTIFY);
+		LDNS_RD_CLR(sldns_buffer_begin(buf));
+		LDNS_AA_SET(sldns_buffer_begin(buf));
+		if(have_serial) {
+			write_soa_serial_to_buf(buf, &qinfo, serial);
+			LDNS_ANCOUNT_SET(sldns_buffer_begin(buf), 1);
+		}
+	}
+
 	if(1) {
 		/* add EDNS DO */
 		struct edns_data edns;
@@ -361,6 +418,7 @@ static int parse_pp2_client(const char* pp2_client, int udp,
 	sldns_buffer* proxy_buf)
 {
 	struct sockaddr_storage pp2_addr;
+	size_t bytes_written;
 	socklen_t pp2_addrlen = 0;
 	memset(&pp2_addr, 0, sizeof(pp2_addr));
 	if(*pp2_client == 0) return 0;
@@ -369,7 +427,9 @@ static int parse_pp2_client(const char* pp2_client, int udp,
 		exit(1);
 	}
 	sldns_buffer_clear(proxy_buf);
-	pp2_write_to_buf(proxy_buf, &pp2_addr, !udp);
+	bytes_written = pp2_write_to_buf(sldns_buffer_begin(proxy_buf),
+		sldns_buffer_remaining(proxy_buf), &pp2_addr, !udp);
+	sldns_buffer_set_position(proxy_buf, bytes_written);
 	sldns_buffer_flip(proxy_buf);
 	return 1;
 }
@@ -406,7 +466,7 @@ send_em(const char* svr, const char* pp2_client, int udp, int usessl,
 			r = SSL_get_error(ssl, r);
 			if(r != SSL_ERROR_WANT_READ &&
 				r != SSL_ERROR_WANT_WRITE) {
-				log_crypto_err("could not ssl_handshake");
+				log_crypto_err_io("could not ssl_handshake", r);
 				exit(1);
 			}
 		}
@@ -541,6 +601,8 @@ int main(int argc, char** argv)
 				break;
 			case 'p':
 				pp2_client = optarg;
+				pp_init(&sldns_write_uint16,
+					&sldns_write_uint32);
 				break;
 			case 'a':
 				onarrival = 1;
diff --git a/testcode/unitauth.c b/testcode/unitauth.c
index d193526b8..11eeb43b2 100644
--- a/testcode/unitauth.c
+++ b/testcode/unitauth.c
@@ -76,10 +76,18 @@ static const char* zone_example_com =
 "out.example.com.	3600	IN	CNAME	www.example.com.\n"
 "plan.example.com.	3600	IN	CNAME	nonexist.example.com.\n"
 "redir.example.com.	3600	IN	DNAME	redir.example.org.\n"
+"redir2.example.com.	3600	IN	DNAME	redir2.example.org.\n"
+"obscured.redir2.example.com.	3600	IN	A	10.0.0.12\n"
+"under2.redir2.example.com.	3600	IN	DNAME	redir3.example.net.\n"
+"doubleobscured.under2.redir2.example.com.	3600	IN	A	10.0.0.13\n"
 "sub.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
 "sub.example.com.	3600	IN	NS	ns2.sub.example.com.\n"
 "ns1.sub.example.com.	3600	IN	A	10.0.0.6\n"
 "ns2.sub.example.com.	3600	IN	AAAA	2001::7\n"
+"sub2.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
+"obscured.sub2.example.com.	3600	IN	A	10.0.0.10\n"
+"under.sub2.example.com.	3600	IN	NS	ns.under.sub2.example.com.\n"
+"doubleobscured.under.sub2.example.com.	3600	IN	A	10.0.0.11\n"
 "*.wild.example.com.	3600	IN	A	10.0.0.8\n"
 "*.wild2.example.com.	3600	IN	CNAME	www.example.com.\n"
 "*.wild3.example.com.	3600	IN	A	10.0.0.8\n"
@@ -281,6 +289,54 @@ static struct q_ans example_com_queries[] = {
 "foo.abc.redir.example.com.	0	IN	CNAME	foo.abc.redir.example.org.\n"
 	},
 
+	{ "example.com", "redir2.example.com. DNAME", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"redir2.example.com.	3600	IN	DNAME	redir2.example.org.\n"
+	},
+
+	{ "example.com", "abc.redir2.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"redir2.example.com.	3600	IN	DNAME	redir2.example.org.\n"
+"abc.redir2.example.com.	0	IN	CNAME	abc.redir2.example.org.\n"
+	},
+
+	{ "example.com", "obscured.redir2.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"redir2.example.com.	3600	IN	DNAME	redir2.example.org.\n"
+"obscured.redir2.example.com.	0	IN	CNAME	obscured.redir2.example.org.\n"
+	},
+
+	{ "example.com", "under2.redir2.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"redir2.example.com.	3600	IN	DNAME	redir2.example.org.\n"
+"under2.redir2.example.com.	0	IN	CNAME	under2.redir2.example.org.\n"
+	},
+
+	{ "example.com", "doubleobscured.under2.redir2.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"redir2.example.com.	3600	IN	DNAME	redir2.example.org.\n"
+"doubleobscured.under2.redir2.example.com.	0	IN	CNAME	doubleobscured.under2.redir2.example.org.\n"
+	},
+
+	{ "example.com", "foo.doubleobscured.under2.redir2.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"redir2.example.com.	3600	IN	DNAME	redir2.example.org.\n"
+"foo.doubleobscured.under2.redir2.example.com.	0	IN	CNAME	foo.doubleobscured.under2.redir2.example.org.\n"
+	},
+
+	{ "example.com", "foo.under2.redir2.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"redir2.example.com.	3600	IN	DNAME	redir2.example.org.\n"
+"foo.under2.redir2.example.com.	0	IN	CNAME	foo.under2.redir2.example.org.\n"
+	},
+
 	{ "example.com", "sub.example.com. NS", "",
 ";flags QR rcode NOERROR\n"
 ";authority section\n"
@@ -357,6 +413,78 @@ static struct q_ans example_com_queries[] = {
 "ns2.sub.example.com.	3600	IN	AAAA	2001::7\n"
 	},
 
+	{ "example.com", "sub2.example.com. A", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub2.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.	3600	IN	A	10.0.0.6\n"
+	},
+
+	{ "example.com", "sub2.example.com. NS", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub2.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.	3600	IN	A	10.0.0.6\n"
+	},
+
+	{ "example.com", "obscured.sub2.example.com. A", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub2.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.	3600	IN	A	10.0.0.6\n"
+	},
+
+	{ "example.com", "abc.obscured.sub2.example.com. A", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub2.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.	3600	IN	A	10.0.0.6\n"
+	},
+
+	{ "example.com", "under.sub2.example.com. A", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub2.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.	3600	IN	A	10.0.0.6\n"
+	},
+
+	{ "example.com", "under.sub2.example.com. NS", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub2.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.	3600	IN	A	10.0.0.6\n"
+	},
+
+	{ "example.com", "abc.under.sub2.example.com. A", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub2.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.	3600	IN	A	10.0.0.6\n"
+	},
+
+	{ "example.com", "doubleobscured.under.sub2.example.com. A", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub2.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.	3600	IN	A	10.0.0.6\n"
+	},
+
+	{ "example.com", "abc.doubleobscured.under.sub2.example.com. A", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub2.example.com.	3600	IN	NS	ns1.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.	3600	IN	A	10.0.0.6\n"
+	},
+
 	{ "example.com", "wild.example.com. A", "",
 ";flags QR AA rcode NOERROR\n"
 ";authority section\n"
diff --git a/testdata/cachedb_no_store.tdir/cachedb_no_store.conf b/testdata/cachedb_no_store.tdir/cachedb_no_store.conf
new file mode 100644
index 000000000..ff76cc379
--- /dev/null
+++ b/testdata/cachedb_no_store.tdir/cachedb_no_store.conf
@@ -0,0 +1,29 @@
+server:
+	verbosity: 4
+	interface: 127.0.0.1
+	port: @PORT@
+	use-syslog: no
+	directory: ""
+	pidfile: "unbound.pid"
+	chroot: ""
+	username: ""
+	module-config: "cachedb iterator"
+	do-not-query-localhost: no
+	qname-minimisation: no
+
+forward-zone:
+	name: "."
+	forward-addr: 127.0.0.1@@TOPORT@
+
+stub-zone:
+	name: "example.com"
+	stub-addr: 127.0.0.1@@TOPORT@
+
+remote-control:
+	control-enable: yes
+	control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
+	control-use-cert: no
+
+cachedb:
+	backend: "testframe"
+	secret-seed: "testvalue"
diff --git a/testdata/cachedb_no_store.tdir/cachedb_no_store.dsc b/testdata/cachedb_no_store.tdir/cachedb_no_store.dsc
new file mode 100644
index 000000000..9d267436e
--- /dev/null
+++ b/testdata/cachedb_no_store.tdir/cachedb_no_store.dsc
@@ -0,0 +1,16 @@
+BaseName: cachedb_no_store
+Version: 1.0
+Description: cachedb test the cachedb-no-store option
+CreationDate: Wed 11 Oct 11:00:00 CEST 2023
+Maintainer: dr. W.C.A. Wijngaards
+Category: 
+Component:
+CmdDepends: 
+Depends: 
+Help:
+Pre: cachedb_no_store.pre
+Post: cachedb_no_store.post
+Test: cachedb_no_store.test
+AuxFiles: 
+Passed:
+Failure:
diff --git a/testdata/cachedb_no_store.tdir/cachedb_no_store.post b/testdata/cachedb_no_store.tdir/cachedb_no_store.post
new file mode 100644
index 000000000..901f01a87
--- /dev/null
+++ b/testdata/cachedb_no_store.tdir/cachedb_no_store.post
@@ -0,0 +1,20 @@
+# #-- cachedb_no_store.post --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# source the test var file when it's there
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+#
+# do your teardown here
+PRE="../.."
+. ../common.sh
+
+echo "> cat logfiles"
+cat fwd.log 
+if test -f fwd2.log; then cat fwd2.log; else echo "no fwd2.log"; fi
+if test -f fwd3.log; then cat fwd3.log; else echo "no fwd3.log"; fi
+if test -f fwd4.log; then cat fwd4.log; else echo "no fwd4.log"; fi
+cat unbound.log
+if test -f unbound2.log; then cat unbound2.log; else echo "no unbound2.log"; fi
+kill_pid $FWD_PID
+kill_pid `cat unbound.pid`
+rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
diff --git a/testdata/cachedb_no_store.tdir/cachedb_no_store.pre b/testdata/cachedb_no_store.tdir/cachedb_no_store.pre
new file mode 100644
index 000000000..e59d3b8da
--- /dev/null
+++ b/testdata/cachedb_no_store.tdir/cachedb_no_store.pre
@@ -0,0 +1,36 @@
+# #-- cachedb_no_store.pre--#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+if grep "define USE_CACHEDB 1" $PRE/config.h; then echo test enabled; else skip_test "test skipped"; fi
+
+get_random_port 2
+UNBOUND_PORT=$RND_PORT
+FWD_PORT=$(($RND_PORT + 1))
+echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
+echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test
+
+# start forwarder
+get_ldns_testns
+$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.testns >fwd.log 2>&1 &
+FWD_PID=$!
+echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
+
+# make config file
+CONTROL_PATH=/tmp
+CONTROL_PID=$$
+sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < cachedb_no_store.conf > ub.conf
+# start unbound in the background
+$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
+UNBOUND_PID=$!
+echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
+echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
+echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
+
+cat .tpkg.var.test
+wait_ldns_testns_up fwd.log
+wait_unbound_up unbound.log
diff --git a/testdata/cachedb_no_store.tdir/cachedb_no_store.servfail.testns b/testdata/cachedb_no_store.tdir/cachedb_no_store.servfail.testns
new file mode 100644
index 000000000..b41abb0ff
--- /dev/null
+++ b/testdata/cachedb_no_store.tdir/cachedb_no_store.servfail.testns
@@ -0,0 +1,8 @@
+ENTRY_BEGIN
+MATCH opcode
+ADJUST copy_id copy_query
+REPLY QR AA SERVFAIL
+SECTION QUESTION
+txt1.example.com. IN TXT
+SECTION ANSWER
+ENTRY_END
diff --git a/testdata/cachedb_no_store.tdir/cachedb_no_store.test b/testdata/cachedb_no_store.tdir/cachedb_no_store.test
new file mode 100644
index 000000000..352026844
--- /dev/null
+++ b/testdata/cachedb_no_store.tdir/cachedb_no_store.test
@@ -0,0 +1,132 @@
+# #-- cachedb_no_store.test --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+
+# do the test
+get_ldns_testns
+
+# query for a text record that is stored by unbound's cache and cachedb
+# in the testframe cache.
+echo "> dig txt1.example.com."
+dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
+if grep "example text message" outfile; then
+	echo "OK"
+else
+	echo "Not OK"
+	exit 1
+fi
+
+# stop the forwarder with servfail, to check the answer came from the cache
+echo "> stop ldns-testns"
+kill_pid $FWD_PID
+echo "> start ldns-testns with servfails"
+$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.servfail.testns >fwd2.log 2>&1 &
+FWD_PID=$!
+echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
+wait_ldns_testns_up fwd2.log
+
+echo "> dig txt1.example.com. from unbound cache"
+dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
+if grep "example text message" outfile; then
+	echo "OK"
+else
+	echo "Not OK"
+	exit 1
+fi
+
+# clear the cache of unbound, but not cachedb testframe cache
+echo "> unbound-control flush"
+$PRE/unbound-control -c ub.conf flush_type txt1.example.com. TXT
+if test $? -ne 0; then
+	echo "wrong exit value."
+	exit 1
+else
+	echo "exit value: OK"
+fi
+
+echo "> dig txt1.example.com. from cachedb"
+dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
+if grep "example text message" outfile; then
+	echo "OK"
+else
+	echo "Not OK"
+	exit 1
+fi
+
+# start the forwarder again.
+echo "> stop ldns-testns"
+kill_pid $FWD_PID
+echo "> start ldns-testns"
+$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.testns >fwd3.log 2>&1 &
+FWD_PID=$!
+echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
+wait_ldns_testns_up fwd3.log
+
+# stop unbound to flush the cachedb cache
+echo "> stop unbound"
+kill_pid `cat unbound.pid`
+
+echo ""
+echo "> config unbound with cachedb-no-store: yes"
+echo "cachedb: cachedb-no-store: yes" >> ub.conf
+
+# start unbound again.
+echo "> start unbound"
+$PRE/unbound -d -c ub.conf >unbound2.log 2>&1 &
+UNBOUND_PID=$!
+echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
+wait_unbound_up unbound2.log
+
+echo ""
+echo "> dig txt1.example.com."
+dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
+if grep "example text message" outfile; then
+	echo "OK"
+else
+	echo "Not OK"
+	exit 1
+fi
+
+# stop the forwarder with servfail, to check the answer came from the cache
+echo "> stop ldns-testns"
+kill_pid $FWD_PID
+echo "> start ldns-testns with servfails"
+$LDNS_TESTNS -p $FWD_PORT cachedb_no_store.servfail.testns >fwd4.log 2>&1 &
+FWD_PID=$!
+echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
+wait_ldns_testns_up fwd4.log
+
+echo "> dig txt1.example.com. from unbound cache"
+dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
+if grep "example text message" outfile; then
+	echo "OK"
+else
+	echo "Not OK"
+	exit 1
+fi
+
+# clear the cache of unbound, but not cachedb testframe cache
+echo "> unbound-control flush"
+$PRE/unbound-control -c ub.conf flush_type txt1.example.com. TXT
+if test $? -ne 0; then
+	echo "wrong exit value."
+	exit 1
+else
+	echo "exit value: OK"
+fi
+
+echo "> dig txt1.example.com. from cachedb, but that has no message stored"
+dig @localhost -p $UNBOUND_PORT txt1.example.com. TXT | tee outfile
+if grep "SERVFAIL" outfile; then
+	echo "OK"
+else
+	echo "Not OK"
+	exit 1
+fi
+
+exit 0
diff --git a/testdata/cachedb_no_store.tdir/cachedb_no_store.testns b/testdata/cachedb_no_store.tdir/cachedb_no_store.testns
new file mode 100644
index 000000000..282b224f8
--- /dev/null
+++ b/testdata/cachedb_no_store.tdir/cachedb_no_store.testns
@@ -0,0 +1,9 @@
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+txt1.example.com. IN TXT
+SECTION ANSWER
+txt1.example.com. IN TXT "example text message"
+ENTRY_END
diff --git a/testdata/disable_edns_do.rpl b/testdata/disable_edns_do.rpl
new file mode 100644
index 000000000..82a16da06
--- /dev/null
+++ b/testdata/disable_edns_do.rpl
@@ -0,0 +1,164 @@
+; config options
+; The island of trust is at example.com
+server:
+	target-fetch-policy: "0 0 0 0 0"
+	qname-minimisation: "no"
+	trust-anchor-signaling: no
+	minimal-responses: no
+	disable-edns-do: yes
+
+stub-zone:
+	name: "."
+	stub-addr: 193.0.14.129 	# K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test lookup with disable-edns-do
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+	ADDRESS 193.0.14.129 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS	K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET.	IN	A	193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+com.	IN NS	a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.	IN 	A	192.5.6.30
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+	ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com.    IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.     IN      A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 100
+	ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.    IN NS   ns.example.com.
+example.com.    3600    IN      RRSIG   NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+SECTION ADDITIONAL
+ns.example.com.         IN      A       1.2.3.4
+ns.example.com. 3600    IN      RRSIG   A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
+ENTRY_END
+
+; response to DNSKEY priming query
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN DNSKEY
+SECTION ANSWER
+example.com.    3600    IN      DNSKEY  256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
+example.com.    3600    IN      RRSIG   DNSKEY 3 2 3600 20070926134802 20070829134802 2854 example.com. MCwCFG1yhRNtTEa3Eno2zhVVuy2EJX3wAhQeLyUp6+UXcpC5qGNu9tkrTEgPUg== ;{id = 2854}
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+example.com.    3600    IN      RRSIG   NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ns.example.com. 3600    IN      RRSIG   A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
+ENTRY_END
+
+; response to query of interest, when sent with EDNS DO
+ENTRY_BEGIN
+MATCH opcode qtype qname DO
+ADJUST copy_id
+REPLY QR AA DO NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A	10.20.30.40
+ns.example.com. 3600    IN      RRSIG   A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+example.com.    3600    IN      RRSIG   NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+www.example.com.        3600    IN      RRSIG   A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ENTRY_END
+
+; response to query of interest, when sent without DO
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A	10.20.30.40
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A	10.20.30.40
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+SCENARIO_END
diff --git a/testdata/dns64_lookup.rpl b/testdata/dns64_lookup.rpl
index 898d0d01a..327f7dfed 100644
--- a/testdata/dns64_lookup.rpl
+++ b/testdata/dns64_lookup.rpl
@@ -4,6 +4,8 @@ server:
 	qname-minimisation: "no"
 	module-config: "dns64 validator iterator"
 	dns64-prefix: 64:ff9b::0/96
+	dns64-ignore-aaaa: ip6ignore.example.com
+	dns64-ignore-aaaa: ip6only.example.com
 	minimal-responses: no
 
 stub-zone:
@@ -15,6 +17,7 @@ SCENARIO_BEGIN Test dns64 lookup and synthesis.
 ; normal A lookup should still succeed
 ; AAAA is synthesized if not present.
 ; AAAA if present, is passed through unchanged.
+; AAAA if present (but configured to be ignored) but no A, AAAA is passed through unchanged.
 
 ; K.ROOT-SERVERS.NET.
 RANGE_BEGIN 0 200
@@ -145,11 +148,13 @@ MATCH opcode qtype qname
 ADJUST copy_id
 REPLY QR NOERROR
 SECTION QUESTION
-broken.example.com. IN AAAA
+ip6.example.com. IN AAAA
 SECTION ANSWER
-; NO AAAA present
+ip6.example.com. IN AAAA 1:2:3::4
 SECTION AUTHORITY
-example.com.	IN SOA	a. b. 1 2 3 4 5
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
 ENTRY_END
 
 ENTRY_BEGIN
@@ -157,10 +162,9 @@ MATCH opcode qtype qname
 ADJUST copy_id
 REPLY QR NOERROR
 SECTION QUESTION
-broken.example.com. IN A
+ip6ignore.example.com. IN AAAA
 SECTION ANSWER
-broken.example.com. IN A	5.6.7.8
-broken.example.com. IN A \# 3 030405
+ip6ignore.example.com. IN AAAA 1:2:3::4
 SECTION AUTHORITY
 example.com.	IN NS	ns.example.com.
 SECTION ADDITIONAL
@@ -172,15 +176,42 @@ MATCH opcode qtype qname
 ADJUST copy_id
 REPLY QR NOERROR
 SECTION QUESTION
-ip6.example.com. IN AAAA
+ip6ignore.example.com. IN A
 SECTION ANSWER
-ip6.example.com. IN AAAA 1:2:3::4
+ip6ignore.example.com. IN A	5.6.7.8
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+ip6only.example.com. IN AAAA
+SECTION ANSWER
+ip6only.example.com. IN AAAA 1:2:3::4
 SECTION AUTHORITY
 example.com.	IN NS	ns.example.com.
 SECTION ADDITIONAL
 ns.example.com.		IN 	A	1.2.3.4
 ENTRY_END
 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+ip6only.example.com. IN A
+SECTION ANSWER
+; NO A present
+SECTION AUTHORITY
+example.com.	IN SOA	a. b. 1 2 3 4 5
+ENTRY_END
+
+
 ENTRY_BEGIN
 MATCH opcode qtype qname
 ADJUST copy_id
@@ -284,12 +315,12 @@ SECTION AUTHORITY
 7.6.5.in-addr.arpa. IN NS ns.example.com.
 ENTRY_END
 
-; synthesize from broken, malformed A records 
+; ignore AAAA and synthesize from A record 5.6.7.8
 STEP 80 QUERY
 ENTRY_BEGIN
 REPLY RD
 SECTION QUESTION
-broken.example.com. IN AAAA
+ip6ignore.example.com. IN AAAA
 ENTRY_END
 
 ; recursion happens here.
@@ -298,11 +329,36 @@ ENTRY_BEGIN
 MATCH all
 REPLY QR RD RA NOERROR
 SECTION QUESTION
-broken.example.com. IN AAAA
+ip6ignore.example.com. IN AAAA
 SECTION ANSWER
+ip6ignore.example.com.        IN      AAAA    64:ff9b::506:708
 SECTION AUTHORITY
-example.com.	IN SOA	a. b. 1 2 3 4 5
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+; try to ignore AAAA but no A record to synthesize, fallback to AAAA
+STEP 100 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+ip6only.example.com. IN AAAA
+ENTRY_END
+
+; recursion happens here.
+STEP 110 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+ip6only.example.com. IN AAAA
+SECTION ANSWER
+ip6only.example.com.        IN      AAAA    1:2:3::4
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
 SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
 ENTRY_END
 
 SCENARIO_END
diff --git a/testdata/ipset.tdir/ipset.conf b/testdata/ipset.tdir/ipset.conf
index 7cc34912d..3a5eb7b64 100644
--- a/testdata/ipset.tdir/ipset.conf
+++ b/testdata/ipset.tdir/ipset.conf
@@ -18,6 +18,9 @@ stub-zone:
 stub-zone:
 	name: "example.com."
 	stub-addr: "127.0.0.1@@TOPORT@"
+stub-zone:
+	name: "lookslikeexample.net."
+	stub-addr: "127.0.0.1@@TOPORT@"
 ipset:
 	name-v4: atotallymadeupnamefor4
 	name-v6: atotallymadeupnamefor6
diff --git a/testdata/ipset.tdir/ipset.test b/testdata/ipset.tdir/ipset.test
index 4dab457ba..bfe17b6e6 100644
--- a/testdata/ipset.tdir/ipset.test
+++ b/testdata/ipset.tdir/ipset.test
@@ -146,6 +146,29 @@ else
 	exit 1
 fi
 
+echo "> dig lookslikeexample.net. AAAA"
+dig @127.0.0.1 -p $UNBOUND_PORT lookslikeexample.net. AAAA | tee outfile
+echo "> check answer"
+if grep "::4" outfile; then
+	echo "OK"
+else
+	echo "> cat logfiles"
+	cat fwd.log
+	cat unbound.log
+	echo "Not OK"
+	exit 1
+fi
+echo "> check ipset"
+if grep "ipset: add ::4 to atotallymadeupnamefor6 for lookslikeexample.net." unbound.log; then
+	echo "> cat logfiles"
+	cat fwd.log
+	cat unbound.log
+	echo "Not OK"
+	exit 1
+else
+	echo "ipset OK"
+fi
+
 echo "> cat logfiles"
 cat tap.log
 cat tap.errlog
diff --git a/testdata/ipset.tdir/ipset.testns b/testdata/ipset.tdir/ipset.testns
index 2b626e915..f67d77ed6 100644
--- a/testdata/ipset.tdir/ipset.testns
+++ b/testdata/ipset.tdir/ipset.testns
@@ -101,3 +101,13 @@ target.example.com.	IN	AAAA
 SECTION ANSWER
 target.example.com.	IN	AAAA	::3
 ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+REPLY QR AA NOERROR
+ADJUST copy_id
+SECTION QUESTION
+lookslikeexample.net.	IN	AAAA
+SECTION ANSWER
+lookslikeexample.net.	IN	AAAA	::4
+ENTRY_END
diff --git a/testdata/iter_ignore_empty.rpl b/testdata/iter_ignore_empty.rpl
index c70dd7e8d..4b2f695b8 100644
--- a/testdata/iter_ignore_empty.rpl
+++ b/testdata/iter_ignore_empty.rpl
@@ -78,6 +78,18 @@ example2.com.	IN NS	ns2.example2.com.
 SECTION ADDITIONAL
 ns2.example2.com.		IN 	A	1.2.3.5
 ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+foo.com. IN NS
+SECTION AUTHORITY
+foo.com.	IN NS	ns.foo.com.
+SECTION ADDITIONAL
+ns.foo.com.		IN 	A	1.2.3.5
+ENTRY_END
 RANGE_END
 
 ; ns.example.com.
@@ -172,6 +184,27 @@ www.example.com. IN A
 SECTION ANSWER
 www.example.com. IN A	10.20.30.40
 ENTRY_END
+
+; foo.com
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.foo.com. IN A
+SECTION ANSWER
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+ns.foo.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+;foo.com.	IN SOA ns2.foo.com root.foo.com 4 14400 3600 604800 3600
+ENTRY_END
 RANGE_END
 
 STEP 1 QUERY
@@ -195,4 +228,21 @@ ENTRY_END
 ; wait for pending nameserver lookups.
 STEP 20 TRAFFIC
 
+; Test that a nodata stays a nodata.
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.foo.com. IN A
+ENTRY_END
+
+STEP 40 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.foo.com. IN A
+SECTION ANSWER
+ENTRY_END
+
 SCENARIO_END
diff --git a/testdata/iter_scrub_rr_length.rpl b/testdata/iter_scrub_rr_length.rpl
new file mode 100644
index 000000000..2ef73c2fe
--- /dev/null
+++ b/testdata/iter_scrub_rr_length.rpl
@@ -0,0 +1,298 @@
+; config options
+server:
+	target-fetch-policy: "0 0 0 0 0"
+	qname-minimisation: "no"
+	minimal-responses: no
+	rrset-roundrobin: no
+	ede: yes
+	log-servfail: yes
+
+stub-zone:
+	name: "."
+	stub-addr: 193.0.14.129 	# K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test scrub of RRs of inappropriate length
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 200
+	ADDRESS 193.0.14.129 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS	K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET.	IN	A	193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+com.	IN NS	a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.	IN 	A	192.5.6.30
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 200
+	ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com.	IN NS	a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.	IN 	A	192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 200
+	ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A	10.20.30.40
+www.example.com. IN A \# 3 030405
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+www.example.com. IN AAAA
+SECTION ANSWER
+www.example.com. IN AAAA	2001:db8::1234
+www.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+broken1.example.com. IN A
+SECTION ANSWER
+broken1.example.com. IN A \# 3 030405
+broken1.example.com. IN A \# 3 030406
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+broken1.example.com. IN AAAA
+SECTION ANSWER
+broken1.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F
+broken1.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E30
+broken1.example.com. IN AAAA \# 48 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E31
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+broken2.example.com. IN A
+SECTION ANSWER
+broken2.example.com. IN A 1.2.3.4
+broken2.example.com. IN A \# 3 030405
+broken2.example.com. IN A 1.2.3.5
+broken2.example.com. IN A \# 3 030406
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A \# 3 030407
+ns.example.com.		IN 	A	1.2.3.6
+ns.example.com. IN A \# 3 030408
+ns.example.com. IN A \# 3 030409
+ns.example.com.		IN 	A	1.2.3.7
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A	10.20.30.40
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+STEP 20 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+www.example.com. IN AAAA
+ENTRY_END
+
+STEP 30 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+www.example.com. IN AAAA
+SECTION ANSWER
+www.example.com. IN AAAA	2001:db8::1234
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+STEP 40 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+broken1.example.com. IN A
+ENTRY_END
+
+STEP 50 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+broken1.example.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+STEP 60 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+broken1.example.com. IN AAAA
+ENTRY_END
+
+STEP 70 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+broken1.example.com. IN AAAA
+SECTION ANSWER
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+
+STEP 80 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+broken2.example.com. IN A
+ENTRY_END
+
+STEP 90 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+broken2.example.com. IN A
+SECTION ANSWER
+broken2.example.com. IN A 1.2.3.4
+broken2.example.com. IN A 1.2.3.5
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.6
+ns.example.com.		IN 	A	1.2.3.7
+ENTRY_END
+
+STEP 100 QUERY
+ENTRY_BEGIN
+REPLY RD CD DO
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+STEP 110 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ede=0
+REPLY QR RD CD RA DO NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A	10.20.30.40
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.6
+ns.example.com.		IN 	A	1.2.3.7
+ENTRY_END
+
+SCENARIO_END
diff --git a/testdata/pymod.tdir/pymod.py b/testdata/pymod.tdir/pymod.py
index 1eb7af5b1..f1e988b39 100644
--- a/testdata/pymod.tdir/pymod.py
+++ b/testdata/pymod.tdir/pymod.py
@@ -37,12 +37,7 @@
 import os
 
 def init(id, cfg):
-    scripts=[]
-    s = cfg.python_script
-    while s != None:
-        scripts.append(s.str)
-        s = s.next
-    log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, scripts))
+    log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, mod_env['script']))
     return True
 
 def deinit(id):
diff --git a/testdata/pymod_thread.tdir/pymod_thread.py b/testdata/pymod_thread.tdir/pymod_thread.py
index 30c258875..58304ab46 100644
--- a/testdata/pymod_thread.tdir/pymod_thread.py
+++ b/testdata/pymod_thread.tdir/pymod_thread.py
@@ -37,7 +37,7 @@
 import os
 
 def init(id, cfg):
-    log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script))
+    log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, mod_env['script']))
     return True
 
 def deinit(id):
diff --git a/testdata/root_zonemd.tdir/root_zonemd.conf b/testdata/root_zonemd.tdir/root_zonemd.conf
new file mode 100644
index 000000000..befb4fbe9
--- /dev/null
+++ b/testdata/root_zonemd.tdir/root_zonemd.conf
@@ -0,0 +1,34 @@
+server:
+	verbosity: 7
+	# num-threads: 1
+	interface: 127.0.0.1
+	port: @PORT@
+	use-syslog: no
+	directory: ""
+	pidfile: "unbound.pid"
+	chroot: ""
+	username: ""
+	do-not-query-localhost: no
+	# for the test, so that DNSSEC verification works.
+	#val-override-date: 20230929090000
+	trust-anchor: ". DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D"
+
+remote-control:
+	control-enable: yes
+	control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
+	control-use-cert: no
+
+# for the test, an upstream server in the test setup.
+stub-zone:
+	name: "."
+	stub-addr: 127.0.0.1@@TOPORT@
+
+# hyperlocal root zone
+auth-zone:
+	name: "."
+	fallback-enabled: yes
+	for-downstream: no
+	for-upstream: yes
+	zonefile: "root.zone"
+	zonemd-check: yes
+	zonemd-reject-absence: yes
diff --git a/testdata/root_zonemd.tdir/root_zonemd.dsc b/testdata/root_zonemd.tdir/root_zonemd.dsc
new file mode 100644
index 000000000..8015ac2d1
--- /dev/null
+++ b/testdata/root_zonemd.tdir/root_zonemd.dsc
@@ -0,0 +1,16 @@
+BaseName: root_zonemd
+Version: 1.0
+Description: ZONEMD check for root zone
+CreationDate: Fri 29 Sep 09:00:00 CEST 2023
+Maintainer: dr. W.C.A. Wijngaards
+Category: 
+Component:
+CmdDepends: 
+Depends: 
+Help:
+Pre: root_zonemd.pre
+Post: root_zonemd.post
+Test: root_zonemd.test
+AuxFiles: 
+Passed:
+Failure:
diff --git a/testdata/root_zonemd.tdir/root_zonemd.post b/testdata/root_zonemd.tdir/root_zonemd.post
new file mode 100644
index 000000000..a28599faf
--- /dev/null
+++ b/testdata/root_zonemd.tdir/root_zonemd.post
@@ -0,0 +1,14 @@
+# #-- root_zonemd.post --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# source the test var file when it's there
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+#
+# do your teardown here
+. ../common.sh
+echo "> cat logfiles"
+cat fwd.log 
+cat unbound.log
+kill_pid $FWD_PID
+kill_pid $UNBOUND_PID
+rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
diff --git a/testdata/root_zonemd.tdir/root_zonemd.pre b/testdata/root_zonemd.tdir/root_zonemd.pre
new file mode 100644
index 000000000..fe369bb20
--- /dev/null
+++ b/testdata/root_zonemd.tdir/root_zonemd.pre
@@ -0,0 +1,50 @@
+# #-- root_zonemd.pre--#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+. ../common.sh
+
+# attempt to download the root zone
+from=k.root-servers.net
+dig @$from . AXFR > root.txt
+if test $? -ne 0; then
+	echo "could not fetch root zone"
+	skip_test "could not fetch root zone"
+fi
+grep "	SOA	" root.txt | head -1 > root.soa
+cat root.soa >> root.zone
+grep -v "	SOA	" root.txt >> root.zone
+echo "fetched root.zone"
+ls -l root.zone
+cat root.soa
+
+get_random_port 2
+UNBOUND_PORT=$RND_PORT
+FWD_PORT=$(($RND_PORT + 1))
+echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
+echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test
+
+# start forwarder
+get_ldns_testns
+$LDNS_TESTNS -p $FWD_PORT root_zonemd.testns >fwd.log 2>&1 &
+FWD_PID=$!
+echo "FWD_PID=$FWD_PID" >> .tpkg.var.test
+
+# make config file
+CONTROL_PATH=/tmp
+CONTROL_PID=$$
+sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < root_zonemd.conf > ub.conf
+# start unbound in the background
+PRE="../.."
+$PRE/unbound -d -c ub.conf >unbound.log 2>&1 &
+UNBOUND_PID=$!
+echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test
+echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test
+echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test
+
+cat .tpkg.var.test
+wait_ldns_testns_up fwd.log
+wait_unbound_up unbound.log
+
diff --git a/testdata/root_zonemd.tdir/root_zonemd.test b/testdata/root_zonemd.tdir/root_zonemd.test
new file mode 100644
index 000000000..da64ab6e9
--- /dev/null
+++ b/testdata/root_zonemd.tdir/root_zonemd.test
@@ -0,0 +1,51 @@
+# #-- root_zonemd.test --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+# do the test
+echo "> dig www.example.com."
+dig @localhost -p $UNBOUND_PORT . SOA | tee outfile
+echo "> check answer"
+if grep root-servers outfile | grep "nstld.verisign-grs.com"; then
+	echo "OK"
+else
+	echo "Not OK"
+	exit 1
+fi
+
+echo "> unbound-control status"
+$PRE/unbound-control -c ub.conf status
+if test $? -ne 0; then
+	echo "wrong exit value."
+	exit 1
+else
+	echo "exit value: OK"
+fi
+
+# This is the output when an unsupported algorithm is used.
+if grep "auth zone . ZONEMD unsupported algorithm" unbound.log; then
+	echo "OK"
+else
+	echo "ZONEMD verification not OK"
+	exit 1
+fi
+
+echo "> unbound-control auth_zone_reload ."
+$PRE/unbound-control -c ub.conf auth_zone_reload . 2>&1 | tee outfile
+if test $? -ne 0; then
+	echo "wrong exit value."
+	exit 1
+fi
+# The output of the reload can be checked.
+#echo "> check unbound-control output"
+#if grep "example.com: ZONEMD verification successful" outfile; then
+	#echo "OK"
+#else
+	#echo "Not OK"
+	#exit 1
+#fi
+
+exit 0
diff --git a/testdata/root_zonemd.tdir/root_zonemd.testns b/testdata/root_zonemd.tdir/root_zonemd.testns
new file mode 100644
index 000000000..d538f2215
--- /dev/null
+++ b/testdata/root_zonemd.tdir/root_zonemd.testns
@@ -0,0 +1,9 @@
+# reply to everything
+ENTRY_BEGIN
+MATCH opcode
+ADJUST copy_id copy_query
+REPLY QR SERVFAIL
+SECTION QUESTION
+example.com. IN SOA
+SECTION ANSWER
+ENTRY_END
diff --git a/testdata/rpz_cached_cname.rpl b/testdata/rpz_cached_cname.rpl
new file mode 100644
index 000000000..198b94631
--- /dev/null
+++ b/testdata/rpz_cached_cname.rpl
@@ -0,0 +1,122 @@
+; config options
+server:
+	module-config: "respip validator iterator"
+	target-fetch-policy: "0 0 0 0 0"
+	qname-minimisation: no
+	rrset-roundrobin: no
+	access-control: 192.0.0.0/8 allow
+
+rpz:
+	name: "rpz.example.com"
+	rpz-log: yes
+	rpz-log-name: "rpz.example.com"
+	zonefile:
+TEMPFILE_NAME rpz.example.com
+TEMPFILE_CONTENTS rpz.example.com
+rpz.example.com. 3600 IN SOA ns.rpz.example.com. hostmaster.rpz.example.com. 1 3600 900 86400 3600
+rpz.example.com.	3600	IN	NS	ns.rpz.example.net.
+a.foo.rpz.example.com. 120 IN A 10.99.99.99
+TEMPFILE_END
+
+stub-zone:
+	name: "."
+	stub-addr: 10.20.30.40
+
+CONFIG_END
+
+SCENARIO_BEGIN Test RPZ with cached CNAME to A record
+
+RANGE_BEGIN 0 100
+	ADDRESS 10.20.30.40
+
+ENTRY_BEGIN
+MATCH opcode qname qtype
+ADJUST copy_id
+REPLY QR NOERROR AA
+SECTION QUESTION
+.	IN	NS
+SECTION ANSWER
+.	IN	NS	ns.
+SECTION ADDITIONAL
+ns.	IN	NS	10.20.30.40
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qname qtype
+ADJUST copy_id
+REPLY QR NOERROR AA
+SECTION QUESTION
+b.foo.	IN	A
+SECTION ANSWER
+b.foo.	30 CNAME	a.foo.
+a.foo.  30 A 1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qname qtype
+ADJUST copy_id
+REPLY QR NOERROR AA
+SECTION QUESTION
+a.foo.	IN	A
+SECTION ANSWER
+a.foo.  A 1.2.3.4
+ENTRY_END
+
+RANGE_END
+
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+a.foo.	IN	A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+a.foo.	IN	A
+SECTION ANSWER
+a.foo.  120 A 10.99.99.99
+ENTRY_END
+
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+b.foo.	IN	A
+ENTRY_END
+
+STEP 40 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+b.foo.	IN	A
+SECTION ANSWER
+b.foo.	30 CNAME	a.foo.
+a.foo.  120 A 10.99.99.99
+ENTRY_END
+
+STEP 50 TIME_PASSES ELAPSE 3
+
+STEP 60 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+b.foo.	IN	A
+ENTRY_END
+
+STEP 70 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AA NOERROR
+SECTION QUESTION
+b.foo.	IN	A
+SECTION ANSWER
+b.foo.	30 CNAME	a.foo.
+a.foo.  120 A 10.99.99.99
+ENTRY_END
+
+SCENARIO_END
diff --git a/testdata/rpz_nsdname.rpl b/testdata/rpz_nsdname.rpl
index 1c678cc13..a4e9bb31d 100644
--- a/testdata/rpz_nsdname.rpl
+++ b/testdata/rpz_nsdname.rpl
@@ -225,6 +225,36 @@ ENTRY_END
 
 RANGE_END
 
+; dd. ------------------------------------------------------------------------
+RANGE_BEGIN 0 100
+	ADDRESS 8.8.3.8
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+dd. IN NS
+SECTION ANSWER
+dd. IN NS ns1.dd.
+SECTION ADDITIONAL
+ns1.dd. IN A 8.8.3.8
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+gotham.dd. IN A
+SECTION AUTHORITY
+gotham.dd.	IN NS	ns1.gotham.dd.
+SECTION ADDITIONAL
+ns1.gotham.dd. IN A 192.0.3.1
+ENTRY_END
+
+RANGE_END
+
 ; ff. ------------------------------------------------------------------------
 RANGE_BEGIN 0 100
 	ADDRESS 8.8.6.8
@@ -303,6 +333,22 @@ ENTRY_END
 
 RANGE_END
 
+; ns1.gotham.dd. -------------------------------------------------------------
+RANGE_BEGIN 0 100
+	ADDRESS 192.0.3.1
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+gotham.dd. IN A
+SECTION ANSWER
+gotham.dd. IN A 192.0.3.2
+ENTRY_END
+
+RANGE_END
+
 ; ns1.gotham.ff. -------------------------------------------------------------
 RANGE_BEGIN 0 100
 	ADDRESS 192.0.5.1
@@ -387,4 +433,39 @@ SECTION ANSWER
 gotham.ff. IN A 127.0.0.1
 ENTRY_END
 
+STEP 40 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+gotham.dd. IN A
+ENTRY_END
+
+; should come back truncated because TCP is required.
+STEP 41 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA TC NOERROR
+SECTION QUESTION
+gotham.dd. IN A
+SECTION ANSWER
+ENTRY_END
+
+STEP 42 QUERY
+ENTRY_BEGIN
+MATCH TCP
+REPLY RD
+SECTION QUESTION
+gotham.dd. IN A
+ENTRY_END
+
+STEP 43 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all TCP
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+gotham.dd. IN A
+SECTION ANSWER
+gotham.dd. IN A 192.0.3.2
+ENTRY_END
+
 SCENARIO_END
diff --git a/testdata/subnet_prezero.crpl b/testdata/subnet_prezero.crpl
new file mode 100644
index 000000000..22cdfffb0
--- /dev/null
+++ b/testdata/subnet_prezero.crpl
@@ -0,0 +1,155 @@
+; subnet unit test
+server:
+	trust-anchor-signaling: no
+	send-client-subnet: 1.2.3.4
+	send-client-subnet: 1.2.3.5
+	target-fetch-policy: "0 0 0 0 0"
+	module-config: "subnetcache validator iterator"
+	qname-minimisation: no
+	minimal-responses: no
+
+stub-zone:
+	name: "example.com"
+	stub-addr: 1.2.3.4
+CONFIG_END
+
+SCENARIO_BEGIN Test subnetcache source prefix zero from client.
+; In RFC7871 section-7.1.2 (para. 2).
+; It says that the recursor must send no EDNS subnet or its own address
+; in the EDNS subnet to the upstream server. And use that answer for the
+; source prefix length zero query. That type of query is for privacy.
+; The authority server is then going to use the resolver's IP, if any, to
+; tailor the answer to the query source address.
+
+; ns.example.com
+RANGE_BEGIN 0 100
+	ADDRESS 1.2.3.4
+
+; reply with 0.0.0.0/0 in reply
+; For the test the answers for 0.0.0.0/0 queries are SERVFAIL, the normal
+; answers are NOERROR.
+ENTRY_BEGIN
+MATCH opcode qtype qname ednsdata
+ADJUST copy_id
+REPLY QR AA DO SERVFAIL
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN CNAME star.c10r.example.com.
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+	00 08 00 04     ; OPCODE=subnet, optlen
+	00 01 00 00     ; ip4, scope 0, source 0
+			; 0.0.0.0/0
+HEX_EDNSDATA_END
+ENTRY_END
+
+; reply without subnet
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA DO NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN CNAME star.c10r.example.com.
+ENTRY_END
+
+; delegation answer for c10r.example.com, with subnet /0
+ENTRY_BEGIN
+MATCH opcode subdomain ednsdata
+ADJUST copy_id copy_query
+REPLY QR DO SERVFAIL
+SECTION QUESTION
+c10r.example.com. IN NS
+SECTION AUTHORITY
+c10r.example.com. IN NS ns.c10r.example.com.
+SECTION ADDITIONAL
+ns.c10r.example.com. IN A 1.2.3.5
+HEX_EDNSDATA_BEGIN
+	00 08 00 04     ; OPCODE=subnet, optlen
+	00 01 00 00     ; ip4, scope 0, source 0
+			; 0.0.0.0/0
+HEX_EDNSDATA_END
+ENTRY_END
+
+; delegation answer for c10r.example.com, without subnet
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR DO NOERROR
+SECTION QUESTION
+c10r.example.com. IN NS
+SECTION AUTHORITY
+c10r.example.com. IN NS ns.c10r.example.com.
+SECTION ADDITIONAL
+ns.c10r.example.com. IN A 1.2.3.5
+ENTRY_END
+RANGE_END
+
+; ns.c10r.example.com
+RANGE_BEGIN 0 100
+	ADDRESS 1.2.3.5
+
+; reply with 0.0.0.0/0 in reply
+ENTRY_BEGIN
+MATCH opcode qtype qname ednsdata
+ADJUST copy_id
+REPLY QR AA DO SERVFAIL
+SECTION QUESTION
+star.c10r.example.com. IN A
+SECTION ANSWER
+star.c10r.example.com. IN A 1.2.3.6
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+	00 08 00 04     ; OPCODE=subnet, optlen
+	00 01 00 00     ; ip4, scope 0, source 0
+			; 0.0.0.0/0
+HEX_EDNSDATA_END
+ENTRY_END
+
+; reply without subnet
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA DO NOERROR
+SECTION QUESTION
+star.c10r.example.com. IN A
+SECTION ANSWER
+star.c10r.example.com. IN A 1.2.3.6
+ENTRY_END
+RANGE_END
+
+; ask for www.example.com
+; server answers with CNAME to a delegation, that then
+; returns a /24 answer.
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+www.example.com. IN A
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+	00 08 00 04     ; OPCODE=subnet, optlen
+	00 01 00 00     ; ip4, scope 0, source 0
+			; 0.0.0.0/0
+HEX_EDNSDATA_END
+ENTRY_END
+
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ednsdata
+REPLY QR RD RA DO NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN CNAME star.c10r.example.com.
+star.c10r.example.com. IN A 1.2.3.6
+SECTION ADDITIONAL
+HEX_EDNSDATA_BEGIN
+	00 08 00 04     ; OPCODE=subnet, optlen
+	00 01 00 00     ; ip4, scope 0, source 0
+			; 0.0.0.0/0
+HEX_EDNSDATA_END
+ENTRY_END
+SCENARIO_END
diff --git a/testdata/val_scrub_rr_length.rpl b/testdata/val_scrub_rr_length.rpl
new file mode 100644
index 000000000..0219b918e
--- /dev/null
+++ b/testdata/val_scrub_rr_length.rpl
@@ -0,0 +1,164 @@
+; config options
+; The island of trust is at example.com
+server:
+	trust-anchor: "example.com.	IN	DS	55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af"
+	val-override-date: "20070916134226"
+	target-fetch-policy: "0 0 0 0 0"
+	qname-minimisation: "no"
+	trust-anchor-signaling: no
+	minimal-responses: no
+	rrset-roundrobin: no
+	ede: yes
+	log-servfail: yes
+
+stub-zone:
+	name: "."
+	stub-addr: 193.0.14.129 	# K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test validator with scrub of RR for inappropriate length
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+	ADDRESS 193.0.14.129 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS	K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET.	IN	A	193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+com.	IN NS	a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.	IN 	A	192.5.6.30
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+	ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com.    IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.     IN      A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 100
+	ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.    IN NS   ns.example.com.
+example.com.	3600	IN	RRSIG	NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
+SECTION ADDITIONAL
+ns.example.com.         IN      A       1.2.3.4
+ns.example.com.	3600	IN	RRSIG	A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
+ENTRY_END
+
+; response to DNSKEY priming query
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN DNSKEY
+SECTION ANSWER
+example.com.	IN	DNSKEY	256 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55566 (zsk), size = 1024b}
+example.com.	3600	IN	RRSIG	DNSKEY 8 2 3600 20070926134150 20070829134150 55566 example.com. Ni7Q17l2dzKcAnHdU3Mycpdwo0I6qgGxRvBhBNI43xIUFHJpgKpbeMFxKvVTkbwHyMPMIuHmOaC82IBhOpGD10SExVh4erQhWS3Hvl+m4Cwl3WI9N+AW6CTB9yj+d4xzX3bHjjBt6MSk4bU8ABR7qIoAjgjY7zdtUDWQlaM+d18=
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+example.com.	3600	IN	RRSIG	NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ns.example.com.	3600	IN	RRSIG	A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+ns.example.com. IN AAAA
+SECTION AUTHORITY
+example.com.    IN NS   ns.example.com.
+example.com.	3600	IN	RRSIG	NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
+SECTION ADDITIONAL
+ns.example.com.         IN      A       1.2.3.4
+ns.example.com.	3600	IN	RRSIG	A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
+ENTRY_END
+
+; response to query of interest
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A	10.20.30.40
+www.example.com. IN A \# 5 0102030405
+; RRSIG includes the malformed record.
+www.example.com.	3600	IN	RRSIG	A 8 3 3600 20070926134150 20070829134150 55566 example.com. W4WFu9B81uRvp3Dj8uLIscypznKWuLuKrZqVg1on5/45/3/xyjHvj3TjTL3gruWFXPiQpldvOstXLZ5eN3OpqILdkVey0eqVATujpHwIruY6GWztVx5WptmFfK6E6zzshZ3RmAARqq/czQ+IZli2A9xixdY2H0o1dSU6gohEjjE=
+SECTION AUTHORITY
+example.com.	IN NS	ns.example.com.
+example.com.	3600	IN	RRSIG	NS 8 2 3600 20070926134150 20070829134150 55566 example.com. cHdLVCzujUQs6b67c1SmCX+/br4tgOg86Gj/R/x+PKUQmWHyeVwBSTlJuLOHbca3CQoyIQc+V2ilK6fjwjbY/dLk4uOlux8L+Zn7HsUXSOwJPIjsM3LuTa8CYDMvYhOP7KGR+vNpJVSsQ25pyDn6Rzsdl3E7DAf7uSkPV8VJwa8=
+SECTION ADDITIONAL
+ns.example.com.		IN 	A	1.2.3.4
+ns.example.com.	3600	IN	RRSIG	A 8 3 3600 20070926134150 20070829134150 55566 example.com. PBwNifMNxTXlDorHX1neq1wUhWLmqk+PZ+PBZCI5BJAmakdgOXdLQiVqlKaErJyA/4uN+99fUf6/DqxwgxL8FIPdBkxMOTJaKrCFjEhL6qozTd3+DI6qFJPgTm1lrkpvb9W72MtK2vxAyT5I/bG2SWKdpzOaQXysbDb2hnxq3as=
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+www.example.com. IN A
+ENTRY_END
+
+; recursion happens here.
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ede=0
+REPLY QR RD RA DO SERVFAIL
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+ENTRY_END
+
+SCENARIO_END
diff --git a/util/config_file.c b/util/config_file.c
index 454096342..31ae8c808 100644
--- a/util/config_file.c
+++ b/util/config_file.c
@@ -271,6 +271,7 @@ config_create(void)
 	cfg->val_permissive_mode = 0;
 	cfg->aggressive_nsec = 1;
 	cfg->ignore_cd = 0;
+	cfg->disable_edns_do = 0;
 	cfg->serve_expired = 0;
 	cfg->serve_expired_ttl = 0;
 	cfg->serve_expired_ttl_reset = 0;
@@ -381,6 +382,7 @@ config_create(void)
 #ifdef USE_CACHEDB
 	if(!(cfg->cachedb_backend = strdup("testframe"))) goto error_exit;
 	if(!(cfg->cachedb_secret = strdup("default"))) goto error_exit;
+	cfg->cachedb_no_store = 0;
 #ifdef USE_REDIS
 	if(!(cfg->redis_server_host = strdup("127.0.0.1"))) goto error_exit;
 	cfg->redis_server_path = NULL;
@@ -388,6 +390,7 @@ config_create(void)
 	cfg->redis_timeout = 100;
 	cfg->redis_server_port = 6379;
 	cfg->redis_expire_records = 0;
+	cfg->redis_logical_db = 0;
 #endif  /* USE_REDIS */
 #endif  /* USE_CACHEDB */
 #ifdef USE_IPSET
@@ -690,6 +693,7 @@ int config_set_option(struct config_file* cfg, const char* opt,
 	else S_YNO("val-permissive-mode:", val_permissive_mode)
 	else S_YNO("aggressive-nsec:", aggressive_nsec)
 	else S_YNO("ignore-cd-flag:", ignore_cd)
+	else S_YNO("disable-edns-do:", disable_edns_do)
 	else if(strcmp(opt, "serve-expired:") == 0)
 	{ IS_YES_OR_NO; cfg->serve_expired = (strcmp(val, "yes") == 0);
 	  SERVE_EXPIRED = cfg->serve_expired; }
@@ -819,6 +823,9 @@ int config_set_option(struct config_file* cfg, const char* opt,
 	{ IS_NUMBER_OR_ZERO; cfg->ipsecmod_max_ttl = atoi(val); }
 	else S_YNO("ipsecmod-strict:", ipsecmod_strict)
 #endif
+#ifdef USE_CACHEDB
+	else S_YNO("cachedb-no-store:", cachedb_no_store)
+#endif /* USE_CACHEDB */
 	else if(strcmp(opt, "define-tag:") ==0) {
 		return config_add_tag(cfg, val);
 	/* val_sig_skew_min, max and val_max_restart are copied into val_env
@@ -1149,6 +1156,7 @@ config_get_option(struct config_file* cfg, const char* opt,
 	else O_YNO(opt, "val-permissive-mode", val_permissive_mode)
 	else O_YNO(opt, "aggressive-nsec", aggressive_nsec)
 	else O_YNO(opt, "ignore-cd-flag", ignore_cd)
+	else O_YNO(opt, "disable-edns-do", disable_edns_do)
 	else O_YNO(opt, "serve-expired", serve_expired)
 	else O_DEC(opt, "serve-expired-ttl", serve_expired_ttl)
 	else O_YNO(opt, "serve-expired-ttl-reset", serve_expired_ttl_reset)
@@ -1306,6 +1314,7 @@ config_get_option(struct config_file* cfg, const char* opt,
 #ifdef USE_CACHEDB
 	else O_STR(opt, "backend", cachedb_backend)
 	else O_STR(opt, "secret-seed", cachedb_secret)
+	else O_YNO(opt, "cachedb-no-store", cachedb_no_store)
 #ifdef USE_REDIS
 	else O_STR(opt, "redis-server-host", redis_server_host)
 	else O_DEC(opt, "redis-server-port", redis_server_port)
@@ -1313,6 +1322,7 @@ config_get_option(struct config_file* cfg, const char* opt,
 	else O_STR(opt, "redis-server-password", redis_server_password)
 	else O_DEC(opt, "redis-timeout", redis_timeout)
 	else O_YNO(opt, "redis-expire-records", redis_expire_records)
+	else O_DEC(opt, "redis-logical-db", redis_logical_db)
 #endif  /* USE_REDIS */
 #endif  /* USE_CACHEDB */
 #ifdef USE_IPSET
diff --git a/util/config_file.h b/util/config_file.h
index 452f3c6a7..ad22b8330 100644
--- a/util/config_file.h
+++ b/util/config_file.h
@@ -409,6 +409,8 @@ struct config_file {
 	int aggressive_nsec;
 	/** ignore the CD flag in incoming queries and refuse them bogus data */
 	int ignore_cd;
+	/** disable EDNS DO flag in outgoing requests */
+	int disable_edns_do;
 	/** serve expired entries and prefetch them */
 	int serve_expired;
 	/** serve expired entries until TTL after expiration */
@@ -699,6 +701,8 @@ struct config_file {
 	char* cachedb_backend;
 	/** secret seed for hash key calculation */
 	char* cachedb_secret;
+	/** cachedb that does not store, but only reads from database, if on */
+	int cachedb_no_store;
 #ifdef USE_REDIS
 	/** redis server's IP address or host name */
 	char* redis_server_host;
@@ -712,6 +716,8 @@ struct config_file {
 	int redis_timeout;
 	/** set timeout on redis records based on DNS response ttl */
 	int redis_expire_records;
+	/** set the redis logical database upon connection */
+	int redis_logical_db;
 #endif
 #endif
 	/** Downstream DNS Cookies */
diff --git a/util/configlexer.lex b/util/configlexer.lex
index 3fcdfa62e..fdc267434 100644
--- a/util/configlexer.lex
+++ b/util/configlexer.lex
@@ -403,6 +403,7 @@ val-clean-additional{COLON}	{ YDVAR(1, VAR_VAL_CLEAN_ADDITIONAL) }
 val-permissive-mode{COLON}	{ YDVAR(1, VAR_VAL_PERMISSIVE_MODE) }
 aggressive-nsec{COLON}		{ YDVAR(1, VAR_AGGRESSIVE_NSEC) }
 ignore-cd-flag{COLON}		{ YDVAR(1, VAR_IGNORE_CD_FLAG) }
+disable-edns-do{COLON}		{ YDVAR(1, VAR_DISABLE_EDNS_DO) }
 serve-expired{COLON}		{ YDVAR(1, VAR_SERVE_EXPIRED) }
 serve-expired-ttl{COLON}	{ YDVAR(1, VAR_SERVE_EXPIRED_TTL) }
 serve-expired-ttl-reset{COLON}	{ YDVAR(1, VAR_SERVE_EXPIRED_TTL_RESET) }
@@ -557,12 +558,14 @@ ipsecmod-strict{COLON}		{ YDVAR(1, VAR_IPSECMOD_STRICT) }
 cachedb{COLON}			{ YDVAR(0, VAR_CACHEDB) }
 backend{COLON}			{ YDVAR(1, VAR_CACHEDB_BACKEND) }
 secret-seed{COLON}		{ YDVAR(1, VAR_CACHEDB_SECRETSEED) }
+cachedb-no-store{COLON}		{ YDVAR(1, VAR_CACHEDB_NO_STORE) }
 redis-server-host{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISHOST) }
 redis-server-port{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISPORT) }
 redis-server-path{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISPATH) }
 redis-server-password{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISPASSWORD) }
 redis-timeout{COLON}		{ YDVAR(1, VAR_CACHEDB_REDISTIMEOUT) }
 redis-expire-records{COLON}	{ YDVAR(1, VAR_CACHEDB_REDISEXPIRERECORDS) }
+redis-logical-db{COLON}		{ YDVAR(1, VAR_CACHEDB_REDISLOGICALDB) }
 ipset{COLON}			{ YDVAR(0, VAR_IPSET) }
 name-v4{COLON}			{ YDVAR(1, VAR_IPSET_NAME_V4) }
 name-v6{COLON}			{ YDVAR(1, VAR_IPSET_NAME_V6) }
diff --git a/util/configparser.y b/util/configparser.y
index d8f25a67e..da5d6608f 100644
--- a/util/configparser.y
+++ b/util/configparser.y
@@ -179,6 +179,7 @@ extern struct config_parser_state* cfg_parser;
 %token VAR_CACHEDB VAR_CACHEDB_BACKEND VAR_CACHEDB_SECRETSEED
 %token VAR_CACHEDB_REDISHOST VAR_CACHEDB_REDISPORT VAR_CACHEDB_REDISTIMEOUT
 %token VAR_CACHEDB_REDISEXPIRERECORDS VAR_CACHEDB_REDISPATH VAR_CACHEDB_REDISPASSWORD
+%token VAR_CACHEDB_REDISLOGICALDB
 %token VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM VAR_FOR_UPSTREAM
 %token VAR_AUTH_ZONE VAR_ZONEFILE VAR_MASTER VAR_URL VAR_FOR_DOWNSTREAM
 %token VAR_FALLBACK_ENABLED VAR_TLS_ADDITIONAL_PORT VAR_LOW_RTT VAR_LOW_RTT_PERMIL
@@ -198,7 +199,7 @@ extern struct config_parser_state* cfg_parser;
 %token VAR_INTERFACE_ACTION VAR_INTERFACE_VIEW VAR_INTERFACE_TAG
 %token VAR_INTERFACE_TAG_ACTION VAR_INTERFACE_TAG_DATA
 %token VAR_PROXY_PROTOCOL_PORT VAR_STATISTICS_INHIBIT_ZERO
-%token VAR_HARDEN_UNKNOWN_ADDITIONAL
+%token VAR_HARDEN_UNKNOWN_ADDITIONAL VAR_DISABLE_EDNS_DO VAR_CACHEDB_NO_STORE
 
 %%
 toplevelvars: /* empty */ | toplevelvars toplevelvar ;
@@ -332,7 +333,7 @@ content_server: server_num_threads | server_verbosity | server_port |
 	server_tcp_reuse_timeout | server_tcp_auth_query_timeout |
 	server_interface_automatic_ports | server_ede |
 	server_proxy_protocol_port | server_statistics_inhibit_zero |
-	server_harden_unknown_additional
+	server_harden_unknown_additional | server_disable_edns_do
 	;
 stubstart: VAR_STUB_ZONE
 	{
@@ -2060,6 +2061,15 @@ server_ignore_cd_flag: VAR_IGNORE_CD_FLAG STRING_ARG
 		free($2);
 	}
 	;
+server_disable_edns_do: VAR_DISABLE_EDNS_DO STRING_ARG
+	{
+		OUTYY(("P(server_disable_edns_do:%s)\n", $2));
+		if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+			yyerror("expected yes or no.");
+		else cfg_parser->cfg->disable_edns_do = (strcmp($2, "yes")==0);
+		free($2);
+	}
+	;
 server_serve_expired: VAR_SERVE_EXPIRED STRING_ARG
 	{
 		OUTYY(("P(server_serve_expired:%s)\n", $2));
@@ -3701,7 +3711,8 @@ contents_cachedb: contents_cachedb content_cachedb
 	| ;
 content_cachedb: cachedb_backend_name | cachedb_secret_seed |
 	redis_server_host | redis_server_port | redis_timeout |
-	redis_expire_records | redis_server_path | redis_server_password
+	redis_expire_records | redis_server_path | redis_server_password |
+	cachedb_no_store | redis_logical_db
 	;
 cachedb_backend_name: VAR_CACHEDB_BACKEND STRING_ARG
 	{
@@ -3727,6 +3738,19 @@ cachedb_secret_seed: VAR_CACHEDB_SECRETSEED STRING_ARG
 	#endif
 	}
 	;
+cachedb_no_store: VAR_CACHEDB_NO_STORE STRING_ARG
+	{
+	#ifdef USE_CACHEDB
+		OUTYY(("P(cachedb_no_store:%s)\n", $2));
+		if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0)
+			yyerror("expected yes or no.");
+		else cfg_parser->cfg->cachedb_no_store = (strcmp($2, "yes")==0);
+	#else
+		OUTYY(("P(Compiled without cachedb, ignoring)\n"));
+	#endif
+		free($2);
+	}
+	;
 redis_server_host: VAR_CACHEDB_REDISHOST STRING_ARG
 	{
 	#if defined(USE_CACHEDB) && defined(USE_REDIS)
@@ -3804,6 +3828,21 @@ redis_expire_records: VAR_CACHEDB_REDISEXPIRERECORDS STRING_ARG
 		free($2);
 	}
 	;
+redis_logical_db: VAR_CACHEDB_REDISLOGICALDB STRING_ARG
+	{
+	#if defined(USE_CACHEDB) && defined(USE_REDIS)
+		int db;
+		OUTYY(("P(redis_logical_db:%s)\n", $2));
+		db = atoi($2);
+		if((db == 0 && strcmp($2, "0") != 0) || db < 0)
+			yyerror("valid redis logical database index expected");
+		else cfg_parser->cfg->redis_logical_db = db;
+	#else
+		OUTYY(("P(Compiled without cachedb or redis, ignoring)\n"));
+	#endif
+		free($2);
+	}
+	;
 server_tcp_connection_limit: VAR_TCP_CONNECTION_LIMIT STRING_ARG STRING_ARG
 	{
 		OUTYY(("P(server_tcp_connection_limit:%s %s)\n", $2, $3));
diff --git a/util/data/msgencode.c b/util/data/msgencode.c
index a170eb7b8..80ae33a38 100644
--- a/util/data/msgencode.c
+++ b/util/data/msgencode.c
@@ -1012,8 +1012,10 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep,
 	ede_size = calc_ede_option_size(edns, &ede_txt_size);
 	if(sldns_buffer_capacity(pkt) < udpsize)
 		udpsize = sldns_buffer_capacity(pkt);
+	if(!edns || !edns->edns_present) {
+		attach_edns = 0;
 	/* EDEs are optional, try to fit anything else before them */
-	if(udpsize < LDNS_HEADER_SIZE + edns_field_size - ede_size) {
+	} else if(udpsize < LDNS_HEADER_SIZE + edns_field_size - ede_size) {
 		/* packet too small to contain edns, omit it. */
 		attach_edns = 0;
 	} else {
diff --git a/util/data/msgparse.c b/util/data/msgparse.c
index b5414c6d0..d06b7bb25 100644
--- a/util/data/msgparse.c
+++ b/util/data/msgparse.c
@@ -47,6 +47,7 @@
 #include "util/regional.h"
 #include "util/rfc_1982.h"
 #include "util/edns.h"
+#include "util/net_help.h"
 #include "sldns/rrdef.h"
 #include "sldns/sbuffer.h"
 #include "sldns/parseutil.h"
@@ -1306,3 +1307,27 @@ log_edns_opt_list(enum verbosity_value level, const char* info_str,
 	}
 }
 
+/** remove RR from msgparse RRset, return true if rrset is entirely bad */
+int
+msgparse_rrset_remove_rr(const char* str, sldns_buffer* pkt, struct rrset_parse* rrset,
+	struct rr_parse* prev, struct rr_parse* rr, struct sockaddr_storage* addr, socklen_t addrlen)
+{
+	if(verbosity >= VERB_QUERY && rrset->dname_len <= LDNS_MAX_DOMAINLEN && str) {
+		uint8_t buf[LDNS_MAX_DOMAINLEN+1];
+		dname_pkt_copy(pkt, buf, rrset->dname);
+		if(addr)
+			log_name_addr(VERB_QUERY, str, buf, addr, addrlen);
+		else	log_nametypeclass(VERB_QUERY, str, buf,
+				rrset->type, ntohs(rrset->rrset_class));
+	}
+	if(prev)
+		prev->next = rr->next;
+	else	rrset->rr_first = rr->next;
+	if(rrset->rr_last == rr)
+		rrset->rr_last = prev;
+	rrset->rr_count --;
+	rrset->size -= rr->size;
+	/* rr struct still exists, but is unlinked, so that in the for loop
+	 * the rr->next works fine to continue. */
+	return rrset->rr_count == 0;
+}
diff --git a/util/data/msgparse.h b/util/data/msgparse.h
index b7dc235d6..8e5c94a28 100644
--- a/util/data/msgparse.h
+++ b/util/data/msgparse.h
@@ -371,4 +371,22 @@ void msgparse_bucket_remove(struct msg_parse* msg, struct rrset_parse* rrset);
 void log_edns_opt_list(enum verbosity_value level, const char* info_str,
 	struct edns_option* list);
 
+/**
+ * Remove RR from msgparse RRset.
+ * @param str: this string is used for logging if verbose. If NULL, there is
+ *	no logging of the remove.
+ * @param pkt: packet in buffer that is removed from. Used to log the name
+ * 	of the item removed.
+ * @param rrset: RRset that the RR is removed from.
+ * @param prev: previous RR in list, or NULL.
+ * @param rr: RR that is removed.
+ * @param addr: address used for logging, if verbose, or NULL then it is not
+ *	used.
+ * @param addrlen: length of addr, if that is not NULL.
+ * @return true if rrset is entirely bad, it would then need to be removed.
+ */
+int msgparse_rrset_remove_rr(const char* str, struct sldns_buffer* pkt,
+	struct rrset_parse* rrset, struct rr_parse* prev, struct rr_parse* rr,
+	struct sockaddr_storage* addr, socklen_t addrlen);
+
 #endif /* UTIL_DATA_MSGPARSE_H */
diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c
index 3b88da235..43d38dc37 100644
--- a/util/fptr_wlist.c
+++ b/util/fptr_wlist.c
@@ -168,7 +168,9 @@ int
 fptr_whitelist_event(void (*fptr)(int, short, void *))
 {
 	if(fptr == &comm_point_udp_callback) return 1;
+#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
 	else if(fptr == &comm_point_udp_ancil_callback) return 1;
+#endif
 	else if(fptr == &comm_point_tcp_accept_callback) return 1;
 	else if(fptr == &comm_point_tcp_handle_callback) return 1;
 	else if(fptr == &comm_timer_callback) return 1;
diff --git a/util/module.c b/util/module.c
index 773dab853..62e5de4a0 100644
--- a/util/module.c
+++ b/util/module.c
@@ -194,6 +194,24 @@ char* errinf_to_str_servfail(struct module_qstate* qstate)
 	return p;
 }
 
+char* errinf_to_str_misc(struct module_qstate* qstate)
+{
+	char buf[20480];
+	char* p = buf;
+	size_t left = sizeof(buf);
+	struct errinf_strlist* s;
+	if(!qstate->errinf)
+		snprintf(p, left, "misc failure");
+	else for(s=qstate->errinf; s; s=s->next) {
+		snprintf(p, left, "%s%s", (s==qstate->errinf?"":" "), s->str);
+		left -= strlen(p); p += strlen(p);
+	}
+	p = strdup(buf);
+	if(!p)
+		log_err("malloc failure in errinf_to_str");
+	return p;
+}
+
 void errinf_rrset(struct module_qstate* qstate, struct ub_packed_rrset_key *rr)
 {
 	char buf[1024];
diff --git a/util/module.h b/util/module.h
index 5b6fcc93c..8a9da3f93 100644
--- a/util/module.h
+++ b/util/module.h
@@ -691,6 +691,8 @@ struct module_qstate {
 	struct respip_action_info* respip_action_info;
 	/** if the query is rpz passthru, no further rpz processing for it */
 	int rpz_passthru;
+	/* Flag tcp required. */
+	int tcp_required;
 
 	/** whether the reply should be dropped */
 	int is_drop;
@@ -842,6 +844,14 @@ sldns_ede_code errinf_to_reason_bogus(struct module_qstate* qstate);
  */
 char* errinf_to_str_servfail(struct module_qstate* qstate);
 
+/**
+ * Create error info in string.  For misc failures that are not servfail.
+ * @param qstate: query state.
+ * @return string or NULL on malloc failure (already logged).
+ *    This string is malloced and has to be freed by caller.
+ */
+char* errinf_to_str_misc(struct module_qstate* qstate);
+
 /**
  * Initialize the edns known options by allocating the required space.
  * @param env: the module environment.
diff --git a/util/net_help.c b/util/net_help.c
index e559c9b2f..16692b788 100644
--- a/util/net_help.c
+++ b/util/net_help.c
@@ -809,6 +809,7 @@ addr_to_nat64(const struct sockaddr_storage* addr,
 	struct sockaddr_in *sin = (struct sockaddr_in *)addr;
 	struct sockaddr_in6 *sin6;
 	uint8_t *v4_byte;
+	int i;
 
 	/* This needs to be checked by the caller */
 	log_assert(addr->ss_family == AF_INET);
@@ -826,7 +827,7 @@ addr_to_nat64(const struct sockaddr_storage* addr,
 	nat64_prefixnet = nat64_prefixnet / 8;
 
 	v4_byte = (uint8_t *)&sin->sin_addr.s_addr;
-	for(int i = 0; i < 4; i++) {
+	for(i = 0; i < 4; i++) {
 		if(nat64_prefixnet == 8) {
 			/* bits 64...71 are MBZ */
 			sin6->sin6_addr.s6_addr[nat64_prefixnet++] = 0;
@@ -951,6 +952,110 @@ void log_crypto_err_code(const char* str, unsigned long err)
 #endif /* HAVE_SSL */
 }
 
+/** Print crypt erro with SSL_get_error want code and err_get_error code */
+static void log_crypto_err_io_code_arg(const char* str, int r,
+	unsigned long err, int err_present)
+{
+#ifdef HAVE_SSL
+	int print_errno = 0, print_crypto_err = 0;
+	const char* inf = NULL;
+
+	switch(r) {
+	case SSL_ERROR_NONE:
+		inf = "no error";
+		break;
+	case SSL_ERROR_ZERO_RETURN:
+		inf = "channel closed";
+		break;
+	case SSL_ERROR_WANT_READ:
+		inf = "want read";
+		break;
+	case SSL_ERROR_WANT_WRITE:
+		inf = "want write";
+		break;
+	case SSL_ERROR_WANT_CONNECT:
+		inf = "want connect";
+		break;
+	case SSL_ERROR_WANT_ACCEPT:
+		inf = "want accept";
+		break;
+	case SSL_ERROR_WANT_X509_LOOKUP:
+		inf = "want X509 lookup";
+		break;
+	case SSL_ERROR_WANT_ASYNC:
+		inf = "want async";
+		break;
+	case SSL_ERROR_WANT_ASYNC_JOB:
+		inf = "want async job";
+		break;
+	case SSL_ERROR_WANT_CLIENT_HELLO_CB:
+		inf = "want client hello cb";
+		break;
+	case SSL_ERROR_SYSCALL:
+		print_errno = 1;
+		inf = "syscall";
+		break;
+	case SSL_ERROR_SSL:
+		print_crypto_err = 1;
+		inf = "SSL, usually protocol, error";
+		break;
+	default:
+		inf = "unknown SSL_get_error result code";
+		print_errno = 1;
+		print_crypto_err = 1;
+	}
+	if(print_crypto_err) {
+		if(print_errno) {
+			char buf[1024];
+			snprintf(buf, sizeof(buf), "%s with errno %s",
+				str, strerror(errno));
+			if(err_present)
+				log_crypto_err_code(buf, err);
+			else	log_crypto_err(buf);
+		} else {
+			if(err_present)
+				log_crypto_err_code(str, err);
+			else	log_crypto_err(str);
+		}
+	} else {
+		if(print_errno) {
+			if(errno == 0)
+				log_err("str: syscall error with errno %s",
+					strerror(errno));
+			else log_err("str: %s", strerror(errno));
+		} else {
+			log_err("str: %s", inf);
+		}
+	}
+#else
+	(void)str;
+	(void)r;
+	(void)err;
+	(void)err_present;
+#endif /* HAVE_SSL */
+}
+
+void log_crypto_err_io(const char* str, int r)
+{
+#ifdef HAVE_SSL
+	log_crypto_err_io_code_arg(str, r, 0, 0);
+#else
+	(void)str;
+	(void)r;
+#endif /* HAVE_SSL */
+}
+
+void log_crypto_err_io_code(const char* str, int r, unsigned long err)
+{
+#ifdef HAVE_SSL
+	log_crypto_err_io_code_arg(str, r, err, 1);
+#else
+	(void)str;
+	(void)r;
+	(void)err;
+#endif /* HAVE_SSL */
+}
+
 #ifdef HAVE_SSL
 /** log certificate details */
 void
diff --git a/util/net_help.h b/util/net_help.h
index a9de910d5..edaea4235 100644
--- a/util/net_help.h
+++ b/util/net_help.h
@@ -429,6 +429,24 @@ void log_crypto_err(const char* str);
  */
 void log_crypto_err_code(const char* str, unsigned long err);
 
+/**
+ * Log an error from libcrypto that came from SSL_write and so on, with
+ * a value from SSL_get_error, calls log_err. If that fails it logs with
+ * log_crypto_err.
+ * @param str: what failed
+ * @param r: output of SSL_get_error on the I/O operation result.
+ */
+void log_crypto_err_io(const char* str, int r);
+
+/**
+ * Log an error from libcrypt that came from an I/O routine with the
+ * errcode from ERR_get_error. Calls log_err() and log_crypto_err_code.
+ * @param str: what failed
+ * @param r: output of SSL_get_error on the I/O operation result.
+ * @param err: error code from ERR_get_error
+ */
+void log_crypto_err_io_code(const char* str, int r, unsigned long err);
+
 /**
  * Log certificate details verbosity, string, of X509 cert
  * @param level: verbosity level
diff --git a/util/netevent.c b/util/netevent.c
index 204e4883c..c26b13e4a 100644
--- a/util/netevent.c
+++ b/util/netevent.c
@@ -116,6 +116,8 @@
 
 /** timeout in millisec to wait for write to unblock, packets dropped after.*/
 #define SEND_BLOCKED_WAIT_TIMEOUT 200
+/** max number of times to wait for write to unblock, packets dropped after.*/
+#define SEND_BLOCKED_MAX_RETRY 5
 
 /** Let's make timestamping code cleaner and redefine SO_TIMESTAMP* */
 #ifndef SO_TIMESTAMP
@@ -402,9 +404,10 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet,
 			WSAGetLastError() == WSAENOBUFS ||
 			WSAGetLastError() == WSAEWOULDBLOCK) {
 #endif
+			int retries = 0;
 			/* if we set the fd blocking, other threads suddenly
 			 * have a blocking fd that they operate on */
-			while(sent == -1 && (
+			while(sent == -1 && retries < SEND_BLOCKED_MAX_RETRY && (
 #ifndef USE_WINSOCK
 				errno == EAGAIN || errno == EINTR ||
 #  ifdef EWOULDBLOCK
@@ -419,6 +422,13 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet,
 #endif
 			)) {
 #if defined(HAVE_POLL) || defined(USE_WINSOCK)
+				int send_nobufs = (
+#ifndef USE_WINSOCK
+					errno == ENOBUFS
+#else
+					WSAGetLastError() == WSAENOBUFS
+#endif
+				);
 				struct pollfd p;
 				int pret;
 				memset(&p, 0, sizeof(p));
@@ -457,8 +467,48 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet,
 					log_err("poll udp out failed: %s",
 						sock_strerror(errno));
 					return 0;
+				} else if((pret < 0 &&
+#ifndef USE_WINSOCK
+					errno == ENOBUFS
+#else
+					WSAGetLastError() == WSAENOBUFS
+#endif
+					) || (send_nobufs && retries > 0)) {
+					/* ENOBUFS, and poll returned without
+					 * a timeout. Or the retried send call
+					 * returned ENOBUFS. It is good to
+					 * wait a bit for the error to clear. */
+					/* The timeout is 20*(2^(retries+1)),
+					 * it increases exponentially, starting
+					 * at 40 msec. After 5 tries, 1240 msec
+					 * have passed in total, when poll
+					 * returned the error, and 1200 msec
+					 * when send returned the errors. */
+#ifndef USE_WINSOCK
+					pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+#else
+					pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+#endif
+					if(pret < 0 &&
+#ifndef USE_WINSOCK
+						errno != EAGAIN && errno != EINTR &&
+#  ifdef EWOULDBLOCK
+						errno != EWOULDBLOCK &&
+#  endif
+						errno != ENOBUFS
+#else
+						WSAGetLastError() != WSAEINPROGRESS &&
+						WSAGetLastError() != WSAEINTR &&
+						WSAGetLastError() != WSAENOBUFS &&
+						WSAGetLastError() != WSAEWOULDBLOCK
+#endif
+					) {
+						log_err("poll udp out timer failed: %s",
+							sock_strerror(errno));
+					}
 				}
 #endif /* defined(HAVE_POLL) || defined(USE_WINSOCK) */
+				retries++;
 				if (!is_connected) {
 					sent = sendto(c->fd, (void*)sldns_buffer_begin(packet),
 						sldns_buffer_remaining(packet), 0,
@@ -665,7 +715,8 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet,
 			WSAGetLastError() == WSAENOBUFS ||
 			WSAGetLastError() == WSAEWOULDBLOCK) {
 #endif
-			while(sent == -1 && (
+			int retries = 0;
+			while(sent == -1 && retries < SEND_BLOCKED_MAX_RETRY && (
 #ifndef USE_WINSOCK
 				errno == EAGAIN || errno == EINTR ||
 #  ifdef EWOULDBLOCK
@@ -680,6 +731,13 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet,
 #endif
 			)) {
 #if defined(HAVE_POLL) || defined(USE_WINSOCK)
+				int send_nobufs = (
+#ifndef USE_WINSOCK
+					errno == ENOBUFS
+#else
+					WSAGetLastError() == WSAENOBUFS
+#endif
+				);
 				struct pollfd p;
 				int pret;
 				memset(&p, 0, sizeof(p));
@@ -718,8 +776,48 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet,
 					log_err("poll udp out failed: %s",
 						sock_strerror(errno));
 					return 0;
+				} else if((pret < 0 &&
+#ifndef USE_WINSOCK
+					errno == ENOBUFS
+#else
+					WSAGetLastError() == WSAENOBUFS
+#endif
+					) || (send_nobufs && retries > 0)) {
+					/* ENOBUFS, and poll returned without
+					 * a timeout. Or the retried send call
+					 * returned ENOBUFS. It is good to
+					 * wait a bit for the error to clear. */
+					/* The timeout is 20*(2^(retries+1)),
+					 * it increases exponentially, starting
+					 * at 40 msec. After 5 tries, 1240 msec
+					 * have passed in total, when poll
+					 * returned the error, and 1200 msec
+					 * when send returned the errors. */
+#ifndef USE_WINSOCK
+					pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+#else
+					pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1));
+#endif
+					if(pret < 0 &&
+#ifndef USE_WINSOCK
+						errno != EAGAIN && errno != EINTR &&
+#  ifdef EWOULDBLOCK
+						errno != EWOULDBLOCK &&
+#  endif
+						errno != ENOBUFS
+#else
+						WSAGetLastError() != WSAEINPROGRESS &&
+						WSAGetLastError() != WSAEINTR &&
+						WSAGetLastError() != WSAENOBUFS &&
+						WSAGetLastError() != WSAEWOULDBLOCK
+#endif
+					) {
+						log_err("poll udp out timer failed: %s",
+							sock_strerror(errno));
+					}
 				}
 #endif /* defined(HAVE_POLL) || defined(USE_WINSOCK) */
+				retries++;
 				sent = sendmsg(c->fd, &msg, 0);
 			}
 		}
@@ -794,15 +892,18 @@ static int udp_recv_needs_log(int err)
 static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
 	int stream) {
 	size_t size;
-	struct pp2_header *header = pp2_read_header(buf);
-	if(header == NULL) return 0;
+	struct pp2_header *header;
+	int err = pp2_read_header(sldns_buffer_begin(buf),
+		sldns_buffer_remaining(buf));
+	if(err) return 0;
+	header = (struct pp2_header*)sldns_buffer_begin(buf);
 	size = PP2_HEADER_SIZE + ntohs(header->len);
 	if((header->ver_cmd & 0xF) == PP2_CMD_LOCAL) {
 		/* A connection from the proxy itself.
 		 * No need to do anything with addresses. */
 		goto done;
 	}
-	if(header->fam_prot == 0x00) {
+	if(header->fam_prot == PP2_UNSPEC_UNSPEC) {
 		/* Unspecified family and protocol. This could be used for
 		 * health checks by proxies.
 		 * No need to do anything with addresses. */
@@ -810,8 +911,8 @@ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
 	}
 	/* Read the proxied address */
 	switch(header->fam_prot) {
-		case 0x11: /* AF_INET|STREAM */
-		case 0x12: /* AF_INET|DGRAM */
+		case PP2_INET_STREAM:
+		case PP2_INET_DGRAM:
 			{
 			struct sockaddr_in* addr =
 				(struct sockaddr_in*)&rep->client_addr;
@@ -822,8 +923,8 @@ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
 			}
 			/* Ignore the destination address; it should be us. */
 			break;
-		case 0x21: /* AF_INET6|STREAM */
-		case 0x22: /* AF_INET6|DGRAM */
+		case PP2_INET6_STREAM:
+		case PP2_INET6_DGRAM:
 			{
 			struct sockaddr_in6* addr =
 				(struct sockaddr_in6*)&rep->client_addr;
@@ -836,6 +937,10 @@ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
 			}
 			/* Ignore the destination address; it should be us. */
 			break;
+		default:
+			log_err("proxy_protocol: unsupported family and "
+				"protocol 0x%x", (int)header->fam_prot);
+			return 0;
 	}
 	rep->is_proxied = 1;
 done:
@@ -850,10 +955,10 @@ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep,
 	return 1;
 }
 
+#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
 void
 comm_point_udp_ancil_callback(int fd, short event, void* arg)
 {
-#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
 	struct comm_reply rep;
 	struct msghdr msg;
 	struct iovec iov[1];
@@ -972,14 +1077,8 @@ comm_point_udp_ancil_callback(int fd, short event, void* arg)
 		if(!rep.c || rep.c->fd == -1) /* commpoint closed */
 			break;
 	}
-#else
-	(void)fd;
-	(void)event;
-	(void)arg;
-	fatal_exit("recvmsg: No support for IPV6_PKTINFO; IP_PKTINFO or IP_RECVDSTADDR. "
-		"Please disable interface-automatic");
-#endif /* AF_INET6 && IPV6_PKTINFO && HAVE_RECVMSG */
 }
+#endif /* AF_INET6 && IPV6_PKTINFO && HAVE_RECVMSG */
 
 void
 comm_point_udp_callback(int fd, short event, void* arg)
@@ -1567,7 +1666,8 @@ ssl_handshake(struct comm_point* c)
 		} else {
 			unsigned long err = ERR_get_error();
 			if(!squelch_err_ssl_handshake(err)) {
-				log_crypto_err_code("ssl handshake failed", err);
+				log_crypto_err_io_code("ssl handshake failed",
+					want, err);
 				log_addr(VERB_OPS, "ssl handshake failed",
 					&c->repinfo.remote_addr,
 					c->repinfo.remote_addrlen);
@@ -1717,23 +1817,30 @@ ssl_handle_read(struct comm_point* c)
 								strerror(errno));
 						return 0;
 					}
-					log_crypto_err("could not SSL_read");
+					log_crypto_err_io("could not SSL_read",
+						want);
 					return 0;
 				}
 				c->tcp_byte_count += r;
+				sldns_buffer_skip(c->buffer, r);
 				if(c->tcp_byte_count != current_read_size) return 1;
 				c->pp2_header_state = pp2_header_init;
 			}
 		}
 		if(c->pp2_header_state == pp2_header_init) {
-			header = pp2_read_header(c->buffer);
-			if(!header) {
+			int err;
+			err = pp2_read_header(
+				sldns_buffer_begin(c->buffer),
+				sldns_buffer_limit(c->buffer));
+			if(err) {
 				log_err("proxy_protocol: could not parse "
-					"PROXYv2 header");
+					"PROXYv2 header (%s)",
+					pp_lookup_error(err));
 				return 0;
 			}
+			header = (struct pp2_header*)sldns_buffer_begin(c->buffer);
 			want_read_size = ntohs(header->len);
-			if(sldns_buffer_remaining(c->buffer) <
+			if(sldns_buffer_limit(c->buffer) <
 				PP2_HEADER_SIZE + want_read_size) {
 				log_err_addr("proxy_protocol: not enough "
 					"buffer size to read PROXYv2 header", "",
@@ -1778,10 +1885,12 @@ ssl_handle_read(struct comm_point* c)
 								strerror(errno));
 						return 0;
 					}
-					log_crypto_err("could not SSL_read");
+					log_crypto_err_io("could not SSL_read",
+						want);
 					return 0;
 				}
 				c->tcp_byte_count += r;
+				sldns_buffer_skip(c->buffer, r);
 				if(c->tcp_byte_count != current_read_size) return 1;
 				c->pp2_header_state = pp2_header_done;
 			}
@@ -1792,6 +1901,7 @@ ssl_handle_read(struct comm_point* c)
 				c->repinfo.remote_addrlen);
 			return 0;
 		}
+		sldns_buffer_flip(c->buffer);
 		if(!consume_pp2_header(c->buffer, &c->repinfo, 1)) {
 			log_err_addr("proxy_protocol: could not consume "
 				"PROXYv2 header", "", &c->repinfo.remote_addr,
@@ -1836,7 +1946,7 @@ ssl_handle_read(struct comm_point* c)
 						strerror(errno));
 				return 0;
 			}
-			log_crypto_err("could not SSL_read");
+			log_crypto_err_io("could not SSL_read", want);
 			return 0;
 		}
 		c->tcp_byte_count += r;
@@ -1886,7 +1996,7 @@ ssl_handle_read(struct comm_point* c)
 						strerror(errno));
 				return 0;
 			}
-			log_crypto_err("could not SSL_read");
+			log_crypto_err_io("could not SSL_read", want);
 			return 0;
 		}
 		sldns_buffer_skip(c->buffer, (ssize_t)r);
@@ -1977,7 +2087,7 @@ ssl_handle_write(struct comm_point* c)
 						strerror(errno));
 				return 0;
 			}
-			log_crypto_err("could not SSL_write");
+			log_crypto_err_io("could not SSL_write", want);
 			return 0;
 		}
 		if(c->tcp_write_and_read) {
@@ -2029,7 +2139,7 @@ ssl_handle_write(struct comm_point* c)
 					strerror(errno));
 			return 0;
 		}
-		log_crypto_err("could not SSL_write");
+		log_crypto_err_io("could not SSL_write", want);
 		return 0;
 	}
 	if(c->tcp_write_and_read) {
@@ -2113,19 +2223,25 @@ comm_point_tcp_handle_read(int fd, struct comm_point* c, int short_ok)
 					goto recv_error_initial;
 				}
 				c->tcp_byte_count += r;
+				sldns_buffer_skip(c->buffer, r);
 				if(c->tcp_byte_count != current_read_size) return 1;
 				c->pp2_header_state = pp2_header_init;
 			}
 		}
 		if(c->pp2_header_state == pp2_header_init) {
-			header = pp2_read_header(c->buffer);
-			if(!header) {
+			int err;
+			err = pp2_read_header(
+				sldns_buffer_begin(c->buffer),
+				sldns_buffer_limit(c->buffer));
+			if(err) {
 				log_err("proxy_protocol: could not parse "
-					"PROXYv2 header");
+					"PROXYv2 header (%s)",
+					pp_lookup_error(err));
 				return 0;
 			}
+			header = (struct pp2_header*)sldns_buffer_begin(c->buffer);
 			want_read_size = ntohs(header->len);
-			if(sldns_buffer_remaining(c->buffer) <
+			if(sldns_buffer_limit(c->buffer) <
 				PP2_HEADER_SIZE + want_read_size) {
 				log_err_addr("proxy_protocol: not enough "
 					"buffer size to read PROXYv2 header", "",
@@ -2152,6 +2268,7 @@ comm_point_tcp_handle_read(int fd, struct comm_point* c, int short_ok)
 					goto recv_error;
 				}
 				c->tcp_byte_count += r;
+				sldns_buffer_skip(c->buffer, r);
 				if(c->tcp_byte_count != current_read_size) return 1;
 				c->pp2_header_state = pp2_header_done;
 			}
@@ -2162,6 +2279,7 @@ comm_point_tcp_handle_read(int fd, struct comm_point* c, int short_ok)
 				c->repinfo.remote_addrlen);
 			return 0;
 		}
+		sldns_buffer_flip(c->buffer);
 		if(!consume_pp2_header(c->buffer, &c->repinfo, 1)) {
 			log_err_addr("proxy_protocol: could not consume "
 				"PROXYv2 header", "", &c->repinfo.remote_addr,
@@ -2815,7 +2933,7 @@ ssl_http_read_more(struct comm_point* c)
 					strerror(errno));
 			return 0;
 		}
-		log_crypto_err("could not SSL_read");
+		log_crypto_err_io("could not SSL_read", want);
 		return 0;
 	}
 	verbose(VERB_ALGO, "ssl http read more skip to %d + %d",
@@ -3266,7 +3384,7 @@ ssize_t http2_recv_cb(nghttp2_session* ATTR_UNUSED(session), uint8_t* buf,
 						strerror(errno));
 				return NGHTTP2_ERR_CALLBACK_FAILURE;
 			}
-			log_crypto_err("could not SSL_read");
+			log_crypto_err_io("could not SSL_read", want);
 			return NGHTTP2_ERR_CALLBACK_FAILURE;
 		}
 		return r;
@@ -3521,7 +3639,7 @@ ssl_http_write_more(struct comm_point* c)
 					strerror(errno));
 			return 0;
 		}
-		log_crypto_err("could not SSL_write");
+		log_crypto_err_io("could not SSL_write", want);
 		return 0;
 	}
 	sldns_buffer_skip(c->buffer, (ssize_t)r);
@@ -3594,7 +3712,7 @@ ssize_t http2_send_cb(nghttp2_session* ATTR_UNUSED(session), const uint8_t* buf,
 						strerror(errno));
 				return NGHTTP2_ERR_CALLBACK_FAILURE;
 			}
-			log_crypto_err("could not SSL_write");
+			log_crypto_err_io("could not SSL_write", want);
 			return NGHTTP2_ERR_CALLBACK_FAILURE;
 		}
 		return r;
@@ -3860,11 +3978,7 @@ comm_point_create_udp(struct comm_base *base, int fd, sldns_buffer* buffer,
 	evbits = UB_EV_READ | UB_EV_PERSIST;
 	/* ub_event stuff */
 	c->ev->ev = ub_event_new(base->eb->base, c->fd, evbits,
-#ifdef USE_WINSOCK
 		comm_point_udp_callback, c);
-#else
-		comm_point_udp_ancil_callback, c);
-#endif
 	if(c->ev->ev == NULL) {
 		log_err("could not baseset udp event");
 		comm_point_delete(c);
@@ -3879,6 +3993,7 @@ comm_point_create_udp(struct comm_base *base, int fd, sldns_buffer* buffer,
 	return c;
 }
 
+#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG)
 struct comm_point*
 comm_point_create_udp_ancil(struct comm_base *base, int fd,
 	sldns_buffer* buffer, int pp2_enabled,
@@ -3941,6 +4056,7 @@ comm_point_create_udp_ancil(struct comm_base *base, int fd,
 	c->event_added = 1;
 	return c;
 }
+#endif
 
 static struct comm_point*
 comm_point_create_tcp_handler(struct comm_base *base,
diff --git a/util/proxy_protocol.c b/util/proxy_protocol.c
index 757c5141d..a18804974 100644
--- a/util/proxy_protocol.c
+++ b/util/proxy_protocol.c
@@ -38,102 +38,162 @@
  *
  * This file contains PROXY protocol functions.
  */
-#include "config.h"
-#include "util/log.h"
 #include "util/proxy_protocol.h"
 
-int
-pp2_write_to_buf(struct sldns_buffer* buf, struct sockaddr_storage* src,
+/**
+ * Internal struct initialized with function pointers for writing uint16 and
+ * uint32.
+ */
+struct proxy_protocol_data {
+	void (*write_uint16)(void* buf, uint16_t data);
+	void (*write_uint32)(void* buf, uint32_t data);
+};
+struct proxy_protocol_data pp_data;
+
+/**
+ * Internal lookup table; could be further generic like sldns_lookup_table
+ * for all the future generic stuff.
+ */
+struct proxy_protocol_lookup_table {
+	int id;
+	const char *text;
+};
+
+/**
+ * Internal parsing error text; could be exposed with pp_lookup_error.
+ */
+static struct proxy_protocol_lookup_table pp_parse_errors_data[] = {
+	{ PP_PARSE_NOERROR, "no parse error" },
+	{ PP_PARSE_SIZE, "not enough space for header" },
+	{ PP_PARSE_WRONG_HEADERv2, "could not match PROXYv2 header" },
+	{ PP_PARSE_UNKNOWN_CMD, "unknown command" },
+	{ PP_PARSE_UNKNOWN_FAM_PROT, "unknown family and protocol" },
+};
+
+void
+pp_init(void (*write_uint16)(void* buf, uint16_t data),
+	void (*write_uint32)(void* buf, uint32_t data)) {
+	pp_data.write_uint16 = write_uint16;
+	pp_data.write_uint32 = write_uint32;
+}
+
+const char*
+pp_lookup_error(enum pp_parse_errors error) {
+	return pp_parse_errors_data[error].text;
+}
+
+size_t
+pp2_write_to_buf(uint8_t* buf, size_t buflen,
+#ifdef INET6
+	struct sockaddr_storage* src,
+#else
+	struct sockaddr_in* src,
+#endif
 	int stream)
 {
 	int af;
+	size_t expected_size;
 	if(!src) return 0;
 	af = (int)((struct sockaddr_in*)src)->sin_family;
-	if(sldns_buffer_remaining(buf) <
-		PP2_HEADER_SIZE + (af==AF_INET?12:36)) {
+	expected_size = PP2_HEADER_SIZE + (af==AF_INET?12:36);
+	if(buflen < expected_size) {
 		return 0;
 	}
 	/* sig */
-	sldns_buffer_write(buf, PP2_SIG, PP2_SIG_LEN);
+	memcpy(buf, PP2_SIG, PP2_SIG_LEN);
+	buf += PP2_SIG_LEN;
 	/* version and command */
-	sldns_buffer_write_u8(buf, (PP2_VERSION << 4) | PP2_CMD_PROXY);
-	if(af==AF_INET) {
+	*buf = (PP2_VERSION << 4) | PP2_CMD_PROXY;
+	buf++;
+	switch(af) {
+	case AF_INET:
 		/* family and protocol */
-		sldns_buffer_write_u8(buf,
-			(PP2_AF_INET<<4) |
-			(stream?PP2_PROT_STREAM:PP2_PROT_DGRAM));
+		*buf = (PP2_AF_INET<<4) |
+			(stream?PP2_PROT_STREAM:PP2_PROT_DGRAM);
+		buf++;
 		/* length */
-		sldns_buffer_write_u16(buf, 12);
+		(*pp_data.write_uint16)(buf, 12);
+		buf += 2;
 		/* src addr */
-		sldns_buffer_write(buf,
+		memcpy(buf,
 			&((struct sockaddr_in*)src)->sin_addr.s_addr, 4);
+		buf += 4;
 		/* dst addr */
-		sldns_buffer_write_u32(buf, 0);
+		(*pp_data.write_uint32)(buf, 0);
+		buf += 4;
 		/* src port */
-		sldns_buffer_write(buf,
+		memcpy(buf,
 			&((struct sockaddr_in*)src)->sin_port, 2);
+		buf += 2;
+		/* dst addr */
 		/* dst port */
-		sldns_buffer_write_u16(buf, 0);
-	} else {
+		(*pp_data.write_uint16)(buf, 12);
+		break;
+#ifdef INET6
+	case AF_INET6:
 		/* family and protocol */
-		sldns_buffer_write_u8(buf,
-			(PP2_AF_INET6<<4) |
-			(stream?PP2_PROT_STREAM:PP2_PROT_DGRAM));
+		*buf = (PP2_AF_INET6<<4) |
+			(stream?PP2_PROT_STREAM:PP2_PROT_DGRAM);
+		buf++;
 		/* length */
-		sldns_buffer_write_u16(buf, 36);
+		(*pp_data.write_uint16)(buf, 36);
+		buf += 2;
 		/* src addr */
-		sldns_buffer_write(buf,
+		memcpy(buf,
 			&((struct sockaddr_in6*)src)->sin6_addr, 16);
+		buf += 16;
 		/* dst addr */
-		sldns_buffer_set_at(buf,
-			sldns_buffer_position(buf), 0, 16);
-		sldns_buffer_skip(buf, 16);
+		memset(buf, 0, 16);
+		buf += 16;
 		/* src port */
-		sldns_buffer_write(buf,
-			&((struct sockaddr_in6*)src)->sin6_port, 2);
+		memcpy(buf, &((struct sockaddr_in6*)src)->sin6_port, 2);
+		buf += 2;
 		/* dst port */
-		sldns_buffer_write_u16(buf, 0);
+		(*pp_data.write_uint16)(buf, 0);
+		break;
+#endif /* INET6 */
+	case AF_UNIX:
+		/* fallthrough */
+	default:
+		return 0;
 	}
-	return 1;
+	return expected_size;
 }
 
-struct pp2_header*
-pp2_read_header(struct sldns_buffer* buf)
+int
+pp2_read_header(uint8_t* buf, size_t buflen)
 {
 	size_t size;
-	struct pp2_header* header = (struct pp2_header*)sldns_buffer_begin(buf);
+	struct pp2_header* header = (struct pp2_header*)buf;
 	/* Try to fail all the unsupported cases first. */
-	if(sldns_buffer_remaining(buf) < PP2_HEADER_SIZE) {
-		log_err("proxy_protocol: not enough space for header");
-		return NULL;
+	if(buflen < PP2_HEADER_SIZE) {
+		return PP_PARSE_SIZE;
 	}
 	/* Check for PROXYv2 header */
 	if(memcmp(header, PP2_SIG, PP2_SIG_LEN) != 0 ||
 		((header->ver_cmd & 0xF0)>>4) != PP2_VERSION) {
-		log_err("proxy_protocol: could not match PROXYv2 header");
-		return NULL;
+		return PP_PARSE_WRONG_HEADERv2;
 	}
 	/* Check the length */
 	size = PP2_HEADER_SIZE + ntohs(header->len);
-	if(sldns_buffer_remaining(buf) < size) {
-		log_err("proxy_protocol: not enough space for header");
-		return NULL;
+	if(buflen < size) {
+		return PP_PARSE_SIZE;
 	}
 	/* Check for supported commands */
 	if((header->ver_cmd & 0xF) != PP2_CMD_LOCAL &&
 		(header->ver_cmd & 0xF) != PP2_CMD_PROXY) {
-		log_err("proxy_protocol: unsupported command");
-		return NULL;
+		return PP_PARSE_UNKNOWN_CMD;
 	}
 	/* Check for supported family and protocol */
-	if(header->fam_prot != 0x00 /* AF_UNSPEC|UNSPEC */ &&
-		header->fam_prot != 0x11 /* AF_INET|STREAM */ &&
-		header->fam_prot != 0x12 /* AF_INET|DGRAM */ &&
-		header->fam_prot != 0x21 /* AF_INET6|STREAM */ &&
-		header->fam_prot != 0x22 /* AF_INET6|DGRAM */) {
-		log_err("proxy_protocol: unsupported family and protocol");
-		return NULL;
+	if(header->fam_prot != PP2_UNSPEC_UNSPEC &&
+		header->fam_prot != PP2_INET_STREAM &&
+		header->fam_prot != PP2_INET_DGRAM &&
+		header->fam_prot != PP2_INET6_STREAM &&
+		header->fam_prot != PP2_INET6_DGRAM &&
+		header->fam_prot != PP2_UNIX_STREAM &&
+		header->fam_prot != PP2_UNIX_DGRAM) {
+		return PP_PARSE_UNKNOWN_FAM_PROT;
 	}
 	/* We have a correct header */
-	return header;
+	return PP_PARSE_NOERROR;
 }
diff --git a/util/proxy_protocol.h b/util/proxy_protocol.h
index 13cab9d74..ca81065bf 100644
--- a/util/proxy_protocol.h
+++ b/util/proxy_protocol.h
@@ -42,7 +42,7 @@
 #ifndef PROXY_PROTOCOL_H
 #define PROXY_PROTOCOL_H
 
-#include "sldns/sbuffer.h"
+#include "config.h"
 
 /** PROXYv2 minimum header size */
 #define PP2_HEADER_SIZE 16
@@ -51,11 +51,11 @@
 #define PP2_SIG "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
 #define PP2_SIG_LEN 12
 
-/** PROXYv2 version */
+/** PROXYv2 version (protocol value) */
 #define PP2_VERSION 0x2
 
 /**
- * PROXYv2 command.
+ * PROXYv2 command (protocol value).
  */
 enum pp2_command {
 	PP2_CMD_LOCAL = 0x0,
@@ -63,7 +63,7 @@ enum pp2_command {
 };
 
 /**
- * PROXYv2 address family.
+ * PROXYv2 address family (protocol value).
  */
 enum pp2_af {
 	PP2_AF_UNSPEC = 0x0,
@@ -73,7 +73,7 @@ enum pp2_af {
 };
 
 /**
- * PROXYv2 protocol.
+ * PROXYv2 protocol (protocol value).
  */
 enum pp2_protocol {
 	PP2_PROT_UNSPEC = 0x0,
@@ -81,6 +81,19 @@ enum pp2_protocol {
 	PP2_PROT_DGRAM = 0x2
 };
 
+/**
+ * Expected combinations of address family and protocol values used in checks.
+ */
+enum pp2_af_protocol_combination {
+	PP2_UNSPEC_UNSPEC = (PP2_AF_UNSPEC<<4)|PP2_PROT_UNSPEC,
+	PP2_INET_STREAM = (PP2_AF_INET<<4)|PP2_PROT_STREAM,
+	PP2_INET_DGRAM = (PP2_AF_INET<<4)|PP2_PROT_DGRAM,
+	PP2_INET6_STREAM = (PP2_AF_INET6<<4)|PP2_PROT_STREAM,
+	PP2_INET6_DGRAM = (PP2_AF_INET6<<4)|PP2_PROT_DGRAM,
+	PP2_UNIX_STREAM = (PP2_AF_UNIX<<4)|PP2_PROT_STREAM,
+	PP2_UNIX_DGRAM = (PP2_AF_UNIX<<4)|PP2_PROT_DGRAM
+};
+
 /**
  * PROXYv2 header.
  */
@@ -109,23 +122,56 @@ struct pp2_header {
 	} addr;
 };
 
+/**
+ * PROXY parse errors.
+ */
+enum pp_parse_errors {
+	PP_PARSE_NOERROR = 0,
+	PP_PARSE_SIZE,
+	PP_PARSE_WRONG_HEADERv2,
+	PP_PARSE_UNKNOWN_CMD,
+	PP_PARSE_UNKNOWN_FAM_PROT,
+};
+
+/**
+ * Initialize the internal proxy structure.
+ * @param write_uint16: pointer to a function that can write uint16.
+ * @param write_uint32: pointer to a function that can write uint32.
+ */
+void pp_init(void (*write_uint16)(void* buf, uint16_t data),
+	void (*write_uint32)(void* buf, uint32_t data));
+
+/**
+ * Lookup the parsing error description.
+ * @param error: parsing error from pp2_read_header.
+ * @return the description.
+ */
+const char* pp_lookup_error(enum pp_parse_errors error);
+
 /**
  * Write a PROXYv2 header at the current position of the buffer.
- * @param buf: the buffer to write to.
+ * @param buf: pointer to the buffer to write data to.
+ * @param buflen: available size on the buffer.
  * @param src: the source address.
  * @param stream: if the protocol is stream or datagram.
  * @return 1 on success, 0 on failure.
  */
-int pp2_write_to_buf(struct sldns_buffer* buf, struct sockaddr_storage* src,
+size_t pp2_write_to_buf(uint8_t* buf, size_t buflen,
+#ifdef INET6
+	struct sockaddr_storage* src,
+#else
+	struct sockaddr_in* src,
+#endif
 	int stream);
 
 /**
  * Read a PROXYv2 header from the current position of the buffer.
  * It does initial validation and returns a pointer to the buffer position on
  * success.
- * @param buf: the buffer to read from.
- * @return the pointer to the buffer position on success, NULL on error.
+ * @param buf: pointer to the buffer data to read from.
+ * @param buflen: available size on the buffer.
+ * @return parsing error, 0 on success.
  */
-struct pp2_header* pp2_read_header(struct sldns_buffer* buf);
+int pp2_read_header(uint8_t* buf, size_t buflen);
 
 #endif /* PROXY_PROTOCOL_H */
diff --git a/util/rfc_1982.c b/util/rfc_1982.c
index c28deded6..cf64e21d0 100644
--- a/util/rfc_1982.c
+++ b/util/rfc_1982.c
@@ -39,6 +39,7 @@
  * This file contains functions for RFC 1982 serial number arithmetic.
  */
 #include "config.h"
+#include "util/rfc_1982.h"
 
 int
 compare_1982(uint32_t a, uint32_t b)
diff --git a/util/siphash.c b/util/siphash.c
index 0e1b597d0..32797dff6 100644
--- a/util/siphash.c
+++ b/util/siphash.c
@@ -26,6 +26,11 @@
  */
 #include "config.h"
 
+/** EDIT
+  * prevent warning from -Wmissing-prototypes
+  */
+#include "util/siphash.h"
+
 /* default: SipHash-2-4 */
 #define cROUNDS 2
 #define dROUNDS 4
diff --git a/validator/val_anchor.c b/validator/val_anchor.c
index b1a54e1f0..8466a8923 100644
--- a/validator/val_anchor.c
+++ b/validator/val_anchor.c
@@ -1322,3 +1322,24 @@ anchor_has_keytag(struct val_anchors* anchors, uint8_t* name, int namelabs,
 	free(taglist);
 	return 0;
 }
+
+struct trust_anchor*
+anchors_find_any_noninsecure(struct val_anchors* anchors)
+{
+	struct trust_anchor* ta, *next;
+	lock_basic_lock(&anchors->lock);
+	ta=(struct trust_anchor*)rbtree_first(anchors->tree);
+	while((rbnode_type*)ta != RBTREE_NULL) {
+		next = (struct trust_anchor*)rbtree_next(&ta->node);
+		lock_basic_lock(&ta->lock);
+		if(ta->numDS != 0 || ta->numDNSKEY != 0) {
+			/* not an insecurepoint */
+			lock_basic_unlock(&anchors->lock);
+			return ta;
+		}
+		lock_basic_unlock(&ta->lock);
+		ta = next;
+	}
+	lock_basic_unlock(&anchors->lock);
+	return NULL;
+}
diff --git a/validator/val_anchor.h b/validator/val_anchor.h
index 1597a7d62..02e7e17b5 100644
--- a/validator/val_anchor.h
+++ b/validator/val_anchor.h
@@ -240,4 +240,12 @@ size_t anchor_list_keytags(struct trust_anchor* ta, uint16_t* list, size_t num);
 int anchor_has_keytag(struct val_anchors* anchors, uint8_t* name, int namelabs,
 	size_t namelen, uint16_t dclass, uint16_t keytag);
 
+/**
+ * Find an anchor that is not an insecure point, if any, or there are no
+ * DNSSEC verification anchors if none.
+ * @param anchors: anchor storage
+ * @return trust anchor or NULL. It is locked.
+ */
+struct trust_anchor* anchors_find_any_noninsecure(struct val_anchors* anchors);
+
 #endif /* VALIDATOR_VAL_ANCHOR_H */
diff --git a/validator/validator.c b/validator/validator.c
index 9de9d54db..6cd15cfc1 100644
--- a/validator/validator.c
+++ b/validator/validator.c
@@ -200,6 +200,17 @@ val_init(struct module_env* env, int id)
 		log_err("validator: could not apply configuration settings.");
 		return 0;
 	}
+	if(env->cfg->disable_edns_do) {
+		struct trust_anchor* anchor = anchors_find_any_noninsecure(
+			env->anchors);
+		if(anchor) {
+			char b[LDNS_MAX_DOMAINLEN+2];
+			dname_str(anchor->name, b);
+			log_warn("validator: disable-edns-do is enabled, but there is a trust anchor for '%s'. Since DNSSEC could not work, the disable-edns-do setting is turned off. Continuing without it.", b);
+			lock_basic_unlock(&anchor->lock);
+			env->cfg->disable_edns_do = 0;
+		}
+	}
 
 	return 1;
 }