diff --git a/configure.ac b/configure.ac
index 3534c0f1..56124540 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,490 +1,506 @@
 #
 # Copyright (C) 2010-2020 Red Hat, Inc.  All rights reserved.
 #
 # Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
 #          Federico Simoncelli <fsimon@kronosnet.org>
 #
 # This software licensed under GPL-2.0+
 #
 
 #                                               -*- Autoconf -*-
 # Process this file with autoconf to produce a configure script.
 #
 
 AC_PREREQ([2.63])
 AC_INIT([kronosnet],
 	m4_esyscmd([build-aux/git-version-gen .tarball-version .gitarchivever]),
 	[devel@lists.kronosnet.org])
 # Don't let AC_PROC_CC (invoked by AC_USE_SYSTEM_EXTENSIONS) replace
 # undefined CFLAGS with -g -O2, overriding our special OPT_CFLAGS.
 : ${CFLAGS=""}
 AC_USE_SYSTEM_EXTENSIONS
 AM_INIT_AUTOMAKE([1.13 dist-bzip2 dist-xz color-tests -Wno-portability subdir-objects])
 
 LT_PREREQ([2.2.6])
 # --enable-new-dtags: Use RUNPATH instead of RPATH.
 # It is necessary to have this done before libtool does linker detection.
 # See also: https://github.com/kronosnet/kronosnet/issues/107
 # --as-needed: Modern systems have builtin ceil() making -lm superfluous but
 # AC_SEARCH_LIBS can't detect this because it tests with a false prototype
 AX_CHECK_LINK_FLAG([-Wl,--enable-new-dtags],
 		   [AM_LDFLAGS=-Wl,--enable-new-dtags],
 		   [AC_MSG_ERROR(["Linker support for --enable-new-dtags is required"])])
 AX_CHECK_LINK_FLAG([-Wl,--as-needed], [AM_LDFLAGS="$AM_LDFLAGS -Wl,--as-needed"])
 
 AC_SUBST([AM_LDFLAGS])
 saved_LDFLAGS="$LDFLAGS"
 LDFLAGS="$AM_LDFLAGS $LDFLAGS"
 LT_INIT
 LDFLAGS="$saved_LDFLAGS"
 
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_SRCDIR([kronosnetd/main.c])
 AC_CONFIG_HEADERS([config.h])
 
 AC_CANONICAL_HOST
 
 AC_LANG([C])
 
 systemddir=${prefix}/lib/systemd/system
 
 if test "$prefix" = "NONE"; then
 	prefix="/usr"
 	if test "$localstatedir" = "\${prefix}/var"; then
 		localstatedir="/var"
 	fi
 	if test "$sysconfdir" = "\${prefix}/etc"; then
 		sysconfdir="/etc"
 	fi
 	if test "$systemddir" = "NONE/lib/systemd/system"; then
 		systemddir=/lib/systemd/system
 	fi
 	if test "$libdir" = "\${exec_prefix}/lib"; then
 		if test -e /usr/lib64; then
 			libdir="/usr/lib64"
 		else
 			libdir="/usr/lib"
 		fi
 	fi
 fi
 
 AC_PROG_AWK
 AC_PROG_GREP
 AC_PROG_SED
 AC_PROG_CPP
 AC_PROG_CC
 AC_PROG_CC_C99
 if test "x$ac_cv_prog_cc_c99" = "xno"; then
 	AC_MSG_ERROR(["C99 support is required"])
 fi
 AC_PROG_LN_S
 AC_PROG_INSTALL
 AC_PROG_MAKE_SET
 PKG_PROG_PKG_CONFIG
 
 AC_CHECK_PROGS([VALGRIND_EXEC], [valgrind])
 AM_CONDITIONAL([HAS_VALGRIND], [test x$VALGRIND_EXEC != "x"])
 
 AC_CHECK_PROGS([COVBUILD_EXEC], [cov-build])
 AM_CONDITIONAL([HAS_COVBUILD], [test x$COVBUILD_EXEC != "x"])
 
 AC_CHECK_PROGS([COVANALYZE_EXEC], [cov-analyze])
 AM_CONDITIONAL([HAS_COVANALYZE], [test x$COVANALYZE_EXEC != "x"])
 
 AC_CHECK_PROGS([COVFORMATERRORS_EXEC], [cov-format-errors])
 AM_CONDITIONAL([HAS_COVFORMATERRORS], [test x$COVFORMATERRORS_EXEC != "x"])
 
 # KNET_OPTION_DEFINES(stem,type,detection code)
 # stem: enters name of option, Automake conditional and preprocessor define
 # type: compress or crypto, determines where the default comes from
 AC_DEFUN([KNET_OPTION_DEFINES],[
 AC_ARG_ENABLE([$2-$1],[AS_HELP_STRING([--disable-$2-$1],[disable libknet $1 support])],,
 	[enable_$2_$1="$enable_$2_all"])
 AM_CONDITIONAL([BUILD_]m4_toupper([$2_$1]),[test "x$enable_$2_$1" = xyes])
 if test "x$enable_$2_$1" = xyes; then
 	$3
 fi
 AC_DEFINE_UNQUOTED([WITH_]m4_toupper([$2_$1]), [`test "x$enable_$2_$1" != xyes; echo $?`], $1 $2 [built in])
 ])
 
 AC_ARG_ENABLE([man],
 	[AS_HELP_STRING([--disable-man],[disable man page creation])],,
 	[ enable_man="yes" ])
 AM_CONDITIONAL([BUILD_MAN], [test x$enable_man = xyes])
 
 AC_ARG_ENABLE([libknet-sctp],
 	[AS_HELP_STRING([--disable-libknet-sctp],[disable libknet SCTP support])],,
 	[ enable_libknet_sctp="yes" ])
 AM_CONDITIONAL([BUILD_SCTP], [test x$enable_libknet_sctp = xyes])
 
 AC_ARG_ENABLE([crypto-all],
 	[AS_HELP_STRING([--disable-crypto-all],[disable libknet all crypto modules support])],,
 	[ enable_crypto_all="yes" ])
 
 KNET_OPTION_DEFINES([nss],[crypto],[PKG_CHECK_MODULES([nss], [nss])])
 KNET_OPTION_DEFINES([openssl],[crypto],[PKG_CHECK_MODULES([openssl],[libcrypto])])
 
 AC_ARG_ENABLE([compress-all],
 	[AS_HELP_STRING([--disable-compress-all],[disable libknet all compress modules support])],,
 	[ enable_compress_all="yes" ])
 
 KNET_OPTION_DEFINES([zstd],[compress],[PKG_CHECK_MODULES([libzstd], [libzstd])])
 KNET_OPTION_DEFINES([zlib],[compress],[PKG_CHECK_MODULES([zlib], [zlib])])
 KNET_OPTION_DEFINES([lz4],[compress],[PKG_CHECK_MODULES([liblz4], [liblz4])])
 KNET_OPTION_DEFINES([lzo2],[compress],[
 	PKG_CHECK_MODULES([lzo2], [lzo2],
 		[# work around broken pkg-config file in v2.10
 		 AC_SUBST([lzo2_CFLAGS],[`echo $lzo2_CFLAGS | sed 's,/lzo *, ,'`])],
 		[AC_CHECK_HEADERS([lzo/lzo1x.h],
 			[AC_CHECK_LIB([lzo2], [lzo1x_decompress_safe],
 				[AC_SUBST([lzo2_LIBS], [-llzo2])])],
 				[AC_MSG_ERROR(["missing required lzo/lzo1x.h header"])])])
 ])
 KNET_OPTION_DEFINES([lzma],[compress],[PKG_CHECK_MODULES([liblzma], [liblzma])])
 KNET_OPTION_DEFINES([bzip2],[compress],[
 	PKG_CHECK_MODULES([bzip2], [bzip2],,
 		[AC_CHECK_HEADERS([bzlib.h],
 			[AC_CHECK_LIB([bz2], [BZ2_bzBuffToBuffCompress],
 				[AC_SUBST([bzip2_LIBS], [-lbz2])])],
 				[AC_MSG_ERROR(["missing required bzlib.h"])])])
 ])
 
 AC_ARG_ENABLE([install-tests],
 	[AS_HELP_STRING([--enable-install-tests],[install tests])],,
 	[ enable_install_tests="no" ])
 AM_CONDITIONAL([INSTALL_TESTS], [test x$enable_install_tests = xyes])
 
 AC_ARG_ENABLE([poc],
 	[AS_HELP_STRING([--enable-poc],[enable building poc code])],,
 	[ enable_poc="no" ])
 AM_CONDITIONAL([BUILD_POC], [test x$enable_poc = xyes])
 
 AC_ARG_ENABLE([kronosnetd],
 	[AS_HELP_STRING([--enable-kronosnetd],[Kronosnetd support])],,
 	[ enable_kronosnetd="no" ])
 AM_CONDITIONAL([BUILD_KRONOSNETD], [test x$enable_kronosnetd = xyes])
 
 AC_ARG_ENABLE([runautogen],
 	[AS_HELP_STRING([--enable-runautogen],[run autogen.sh])],,
 	[ enable_runautogen="no" ])
 AM_CONDITIONAL([BUILD_RUNAUTOGEN], [test x$enable_runautogen = xyes])
 
 override_rpm_debuginfo_option="yes"
 AC_ARG_ENABLE([rpm-debuginfo],
 	[AS_HELP_STRING([--enable-rpm-debuginfo],[build debuginfo packages])],,
 	[ enable_rpm_debuginfo="no", override_rpm_debuginfo_option="no" ])
 AM_CONDITIONAL([BUILD_RPM_DEBUGINFO], [test x$enable_rpm_debuginfo = xyes])
 AM_CONDITIONAL([OVERRIDE_RPM_DEBUGINFO], [test x$override_rpm_debuginfo_option = xyes])
 
 AC_ARG_ENABLE([libnozzle],
 	[AS_HELP_STRING([--enable-libnozzle],[libnozzle support])],,
 	[ enable_libnozzle="yes" ])
 
 if test "x$enable_kronosnetd" = xyes; then
 	enable_libnozzle=yes
 fi
 AM_CONDITIONAL([BUILD_LIBNOZZLE], [test x$enable_libnozzle = xyes])
 
 # Checks for libraries.
 AX_PTHREAD(,[AC_MSG_ERROR([POSIX threads support is required])])
 saved_LIBS="$LIBS"
 LIBS=
 AC_SEARCH_LIBS([ceil], [m], , [AC_MSG_ERROR([ceil not found])])
 AC_SUBST([m_LIBS], [$LIBS])
 LIBS=
 AC_SEARCH_LIBS([clock_gettime], [rt], , [AC_MSG_ERROR([clock_gettime not found])])
 AC_SUBST([rt_LIBS], [$LIBS])
 LIBS=
 AC_SEARCH_LIBS([dlopen], [dl dld], , [AC_MSG_ERROR([dlopen not found])])
 AC_SUBST([dl_LIBS], [$LIBS])
 LIBS="$saved_LIBS"
 
 # Check RTLD_DI_ORIGIN (not decalred by musl. glibc has it as an enum so cannot use ifdef)
 AC_CHECK_DECL([RTLD_DI_ORIGIN], [AC_DEFINE([HAVE_RTLD_DI_ORIGIN], 1,
     [define when RTLD_DI_ORIGIN is declared])], ,[[#include <dlfcn.h>]])
 
 # OS detection
 
 AC_MSG_CHECKING([for os in ${host_os}])
 case "$host_os" in
 	*linux*)
 		AC_DEFINE_UNQUOTED([KNET_LINUX], [1], [Compiling for Linux platform])
 		AC_MSG_RESULT([Linux])
 		;;
 	*bsd*)
 		AC_DEFINE_UNQUOTED([KNET_BSD], [1], [Compiling for BSD platform])
 		AC_MSG_RESULT([BSD])
 		;;
 	*)
 		AC_MSG_ERROR([Unsupported OS? hmmmm])
 		;;
 esac
 
 # Checks for header files.
 AC_CHECK_HEADERS([sys/epoll.h])
 AC_CHECK_FUNCS([kevent])
 # if neither sys/epoll.h nor kevent are present, we should fail.
 
 if test "x$ac_cv_header_sys_epoll_h" = xno && test "x$ac_cv_func_kevent" = xno; then
 	AC_MSG_ERROR([Both epoll and kevent unavailable on this OS])
 fi
 
 if test "x$ac_cv_header_sys_epoll_h" = xyes && test "x$ac_cv_func_kevent" = xyes; then
 	AC_MSG_ERROR([Both epoll and kevent available on this OS, please contact the maintainers to fix the code])
 fi
 
 if test "x$enable_libknet_sctp" = xyes; then
 	AC_CHECK_HEADERS([netinet/sctp.h],, [AC_MSG_ERROR(["missing required SCTP headers"])])
 fi
 
 # Checks for typedefs, structures, and compiler characteristics.
 AC_C_INLINE
 AC_TYPE_PID_T
 AC_TYPE_SIZE_T
 AC_TYPE_SSIZE_T
 AC_TYPE_UINT8_T
 AC_TYPE_UINT16_T
 AC_TYPE_UINT32_T
 AC_TYPE_UINT64_T
 AC_TYPE_INT8_T
 AC_TYPE_INT16_T
 AC_TYPE_INT32_T
 AC_TYPE_INT64_T
 
+PKG_CHECK_MODULES([libqb], [libqb])
+
 if test "x$enable_man" = "xyes"; then
 	AC_ARG_VAR([DOXYGEN], [override doxygen executable])
 	AC_CHECK_PROGS([DOXYGEN], [doxygen], [no])
 	if test "x$DOXYGEN" = xno; then
 		AC_MSG_ERROR(["Doxygen command not found"])
 	fi
-	# required by doxyxml to build man pages dynamically
-	# Don't let AC_PROC_CC (invoked by AX_PROG_CC_FOR_BUILD) replace
-	# undefined CFLAGS_FOR_BUILD with -g -O2, overriding our special OPT_CFLAGS.
-	: ${CFLAGS_FOR_BUILD=""}
-	AX_PROG_CC_FOR_BUILD
-	saved_PKG_CONFIG="$PKG_CONFIG"
-	saved_ac_cv_path_PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
-	unset PKG_CONFIG ac_cv_path_PKG_CONFIG
-	AC_PATH_PROG([PKG_CONFIG], [pkg-config])
-	PKG_CHECK_MODULES([libqb_BUILD], [libqb])
-	PKG_CHECK_MODULES([libxml_BUILD], [libxml-2.0])
-	PKG_CONFIG="$saved_PKG_CONFIG"
-	ac_cv_path_PKG_CONFIG="$saved_ac_cv_path_PKG_CONFIG"
+
+	AC_ARG_VAR([DOXYGEN2MAN], [override doxygen2man executable])
+	if test "x$cross_compiling" = "xno"; then
+		PKG_CHECK_VAR([libqb_PREFIX], [libqb], [prefix])
+		AC_PATH_PROG([DOXYGEN2MAN], [doxygen2man], [no], [$libqb_PREFIX/bin$PATH_SEPARATOR$PATH])
+	fi
+	if test "x$DOXYGEN2MAN" = "xno"; then
+		# required by doxyxml to build man pages dynamically
+		# Don't let AC_PROC_CC (invoked by AX_PROG_CC_FOR_BUILD) replace
+		# undefined CFLAGS_FOR_BUILD with -g -O2, overriding our special OPT_CFLAGS.
+		: ${CFLAGS_FOR_BUILD=""}
+		AX_PROG_CC_FOR_BUILD
+		saved_PKG_CONFIG="$PKG_CONFIG"
+		saved_ac_cv_path_PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
+		unset PKG_CONFIG ac_cv_path_PKG_CONFIG
+		AC_PATH_PROG([PKG_CONFIG], [pkg-config])
+		PKG_CHECK_MODULES([libqb_BUILD], [libqb])
+		PKG_CHECK_VAR([libqb_BUILD_PREFIX], [libqb], [prefix])
+		AC_PATH_PROG([DOXYGEN2MAN], [doxygen2man], [no], [$libqb_BUILD_PREFIX/bin$PATH_SEPARATOR$PATH])
+		if test "x$DOXYGEN2MAN" = "xno"; then
+			PKG_CHECK_MODULES([libxml_BUILD], [libxml-2.0])
+			DOXYGEN2MAN="\${abs_top_builddir}/man/doxyxml"
+			build_doxy=yes
+		fi
+		PKG_CONFIG="$saved_PKG_CONFIG"
+		ac_cv_path_PKG_CONFIG="$saved_ac_cv_path_PKG_CONFIG"
+	fi
+	AC_SUBST([DOXYGEN2MAN])
+	AM_CONDITIONAL([BUILD_DOXYXML], [test "x$build_doxy" = "xyes"])
 fi
 
 # checks for libnozzle
 if test "x$enable_libnozzle" = xyes; then
 	if `echo $host_os | grep -q linux`; then
 		PKG_CHECK_MODULES([libnl], [libnl-3.0])
 		PKG_CHECK_MODULES([libnlroute], [libnl-route-3.0 >= 3.3], [],
 			[PKG_CHECK_MODULES([libnlroute], [libnl-route-3.0 < 3.3],
 					   [AC_DEFINE_UNQUOTED([LIBNL3_WORKAROUND], [1], [Enable libnl < 3.3 build workaround])], [])])
 	fi
 fi
 
 # checks for kronosnetd
 if test "x$enable_kronosnetd" = xyes; then
 	AC_CHECK_HEADERS([security/pam_appl.h],
 			 [AC_CHECK_LIB([pam], [pam_start],
 				       [AC_SUBST([pam_LIBS], [-lpam])],
 				       [AC_MSG_ERROR([Unable to find LinuxPAM devel files])])])
 
 	AC_CHECK_HEADERS([security/pam_misc.h],
 			 [AC_CHECK_LIB([pam_misc], [misc_conv],
 				       [AC_SUBST([pam_misc_LIBS], [-lpam_misc])],
 				       [AC_MSG_ERROR([Unable to find LinuxPAM MISC devel files])])])
 
-	PKG_CHECK_MODULES([libqb], [libqb])
-
 	AC_CHECK_LIB([qb], [qb_log_thread_priority_set],
 		     [have_qb_log_thread_priority_set="yes"],
 		     [have_qb_log_thread_priority_set="no"])
 	if test "x${have_qb_log_thread_priority_set}" = xyes; then
 		AC_DEFINE_UNQUOTED([HAVE_QB_LOG_THREAD_PRIORITY_SET], [1], [have qb_log_thread_priority_set])
 	fi
 fi
 
 # local options
 AC_ARG_ENABLE([debug],
 	[AS_HELP_STRING([--enable-debug],[enable debug build])])
 
 AC_ARG_WITH([testdir],
 	[AS_HELP_STRING([--with-testdir=DIR],[path to /usr/lib../kronosnet/tests/ dir where to install the test suite])],
 	[ TESTDIR="$withval" ],
 	[ TESTDIR="$libdir/kronosnet/tests" ])
 
 AC_ARG_WITH([initdefaultdir],
 	[AS_HELP_STRING([--with-initdefaultdir=DIR],[path to /etc/sysconfig or /etc/default dir])],
 	[ INITDEFAULTDIR="$withval" ],
 	[ INITDEFAULTDIR="$sysconfdir/default" ])
 
 AC_ARG_WITH([initddir],
 	[AS_HELP_STRING([--with-initddir=DIR],[path to init script directory])],
 	[ INITDDIR="$withval" ],
 	[ INITDDIR="$sysconfdir/init.d" ])
 
 AC_ARG_WITH([systemddir],
 	[AS_HELP_STRING([--with-systemddir=DIR],[path to systemd unit files directory])],
 	[ SYSTEMDDIR="$withval" ],
 	[ SYSTEMDDIR="$systemddir" ])
 
 AC_ARG_WITH([syslogfacility],
 	[AS_HELP_STRING([--with-syslogfacility=FACILITY],[default syslog facility])],
 	[ SYSLOGFACILITY="$withval" ],
 	[ SYSLOGFACILITY="LOG_DAEMON" ])
 
 AC_ARG_WITH([sysloglevel],
 	[AS_HELP_STRING([--with-sysloglevel=LEVEL],[default syslog level])],
 	[ SYSLOGLEVEL="$withval" ],
 	[ SYSLOGLEVEL="LOG_INFO" ])
 
 AC_ARG_WITH([defaultadmgroup],
 	[AS_HELP_STRING([--with-defaultadmgroup=GROUP],
 		[define PAM group. Users part of this group will be allowed to configure
 		 kronosnet. Others will only receive read-only rights.])],
 	[ DEFAULTADMGROUP="$withval" ],
 	[ DEFAULTADMGROUP="kronosnetadm" ])
 
 ## random vars
 LOGDIR=${localstatedir}/log/
 RUNDIR=${localstatedir}/run/
 DEFAULT_CONFIG_DIR=${sysconfdir}/kronosnet
 
 ## do subst
 
 AC_SUBST([TESTDIR])
 AC_SUBST([DEFAULT_CONFIG_DIR])
 AC_SUBST([INITDEFAULTDIR])
 AC_SUBST([INITDDIR])
 AC_SUBST([SYSTEMDDIR])
 AC_SUBST([LOGDIR])
 AC_SUBST([DEFAULTADMGROUP])
 
 AC_DEFINE_UNQUOTED([DEFAULT_CONFIG_DIR],
 		   ["$(eval echo ${DEFAULT_CONFIG_DIR})"],
 		   [Default config directory])
 
 AC_DEFINE_UNQUOTED([DEFAULT_CONFIG_FILE],
 		   ["$(eval echo ${DEFAULT_CONFIG_DIR}/kronosnetd.conf)"],
 		   [Default config file])
 
 AC_DEFINE_UNQUOTED([LOGDIR],
 		   ["$(eval echo ${LOGDIR})"],
 		   [Default logging directory])
 
 AC_DEFINE_UNQUOTED([DEFAULT_LOG_FILE],
 		   ["$(eval echo ${LOGDIR}/kronosnetd.log)"],
 		   [Default log file])
 
 AC_DEFINE_UNQUOTED([RUNDIR],
 		   ["$(eval echo ${RUNDIR})"],
 		   [Default run directory])
 
 AC_DEFINE_UNQUOTED([SYSLOGFACILITY],
 		   [$(eval echo ${SYSLOGFACILITY})],
 		   [Default syslog facility])
 
 AC_DEFINE_UNQUOTED([SYSLOGLEVEL],
 		   [$(eval echo ${SYSLOGLEVEL})],
 		   [Default syslog level])
 
 AC_DEFINE_UNQUOTED([DEFAULTADMGROUP],
 		   ["$(eval echo ${DEFAULTADMGROUP})"],
 		   [Default admin group])
 
 # debug build stuff
 if test "x${enable_debug}" = xyes; then
 	AC_DEFINE_UNQUOTED([DEBUG], [1], [Compiling Debugging code])
 	OPT_CFLAGS="-O0"
 else
 	OPT_CFLAGS="-O3"
 fi
 
 # gdb flags
 if test "x${GCC}" = xyes; then
 	GDB_FLAGS="-ggdb3"
 else
 	GDB_FLAGS="-g"
 fi
 
 DEFAULT_CFLAGS="-Werror -Wall -Wextra"
 
 # manual overrides
 # generates too much noise for stub APIs
 UNWANTED_CFLAGS="-Wno-unused-parameter"
 
 AC_SUBST([AM_CFLAGS],["$OPT_CFLAGS $GDB_FLAGS $DEFAULT_CFLAGS $UNWANTED_CFLAGS"])
 
 AX_PROG_DATE
 AS_IF([test "$ax_cv_prog_date_gnu_date:$ax_cv_prog_date_gnu_utc" = yes:yes],
 	[UTC_DATE_AT="date -u -d@"],
 	[AS_IF([test "x$ax_cv_prog_date_bsd_date" = xyes],
 		[UTC_DATE_AT="date -u -r"],
 		[AC_MSG_ERROR([date utility unable to convert epoch to UTC])])])
 AC_SUBST([UTC_DATE_AT])
 
 AC_ARG_VAR([SOURCE_EPOCH],[last modification date of the source])
 AC_MSG_NOTICE([trying to determine source epoch])
 AC_MSG_CHECKING([for source epoch in \$SOURCE_EPOCH])
 AS_IF([test -n "$SOURCE_EPOCH"],
 	[AC_MSG_RESULT([yes])],
 	[AC_MSG_RESULT([no])
 	 AC_MSG_CHECKING([for source epoch in source_epoch file])
 	 AS_IF([test -e "$srcdir/source_epoch"],
 		[read SOURCE_EPOCH <"$srcdir/source_epoch"
 		 AC_MSG_RESULT([yes])],
 		[AC_MSG_RESULT([no])
 		 AC_MSG_CHECKING([for source epoch baked in by gitattributes export-subst])
 		 SOURCE_EPOCH='$Format:%at$' # template for rewriting by git-archive
 		 AS_CASE([$SOURCE_EPOCH],
 			[?Format:*], # was not rewritten
 				[AC_MSG_RESULT([no])
 				 AC_MSG_CHECKING([for source epoch in \$SOURCE_DATE_EPOCH])
 				 AS_IF([test "x$SOURCE_DATE_EPOCH" != x],
 					[SOURCE_EPOCH="$SOURCE_DATE_EPOCH"
 					 AC_MSG_RESULT([yes])],
 					[AC_MSG_RESULT([no])
 					 AC_MSG_CHECKING([whether git log can provide a source epoch])
 					 SOURCE_EPOCH=f${SOURCE_EPOCH#\$F} # convert into git log --pretty format
 					 SOURCE_EPOCH=$(cd "$srcdir" && git log -1 --pretty=${SOURCE_EPOCH%$} 2>/dev/null)
 					 AS_IF([test -n "$SOURCE_EPOCH"],
 						[AC_MSG_RESULT([yes])],
 						[AC_MSG_RESULT([no, using current time and breaking reproducibility])
 						 SOURCE_EPOCH=$(date +%s)])])],
 			[AC_MSG_RESULT([yes])]
 		 )])
 	])
 AC_MSG_NOTICE([using source epoch $($UTC_DATE_AT$SOURCE_EPOCH +'%F %T %Z')])
 
 AC_CONFIG_FILES([
 		Makefile
 		init/Makefile
 		libnozzle/Makefile
 		libnozzle/libnozzle.pc
 		libnozzle/tests/Makefile
 		kronosnetd/Makefile
 		kronosnetd/kronosnetd.logrotate
 		libknet/Makefile
 		libknet/libknet.pc
 		libknet/tests/Makefile
 		man/Makefile
 		man/Doxyfile-knet
 		man/Doxyfile-nozzle
 		poc-code/Makefile
 		poc-code/iov-hash/Makefile
 		])
 
 if test "x$VERSION" = "xUNKNOWN"; then
 	AC_MSG_ERROR([m4_text_wrap([
   configure was unable to determine the source tree's current version. This
   generally happens when using git archive (or the github download button)
   generated tarball/zip file. In order to workaround this issue, either use git
   clone https://github.com/kronosnet/kronosnet.git or use an official release
   tarball, available at https://kronosnet.org/releases/.  Alternatively you
   can add a compatible version in a .tarball-version file at the top of the
   source tree, wipe your autom4te.cache dir and generated configure, and rerun
   autogen.sh.
   ], [  ], [   ], [76])])
 fi
 
 AC_OUTPUT
diff --git a/kronosnet.spec.in b/kronosnet.spec.in
index 28b0ea20..6c0b6c5d 100644
--- a/kronosnet.spec.in
+++ b/kronosnet.spec.in
@@ -1,512 +1,512 @@
 ###############################################################################
 ###############################################################################
 ##
 ##  Copyright (C) 2012-2020 Red Hat, Inc.  All rights reserved.
 ##
 ##  This copyrighted material is made available to anyone wishing to use,
 ##  modify, copy, or redistribute it subject to the terms and conditions
 ##  of the GNU General Public License v.2 or higher
 ##
 ###############################################################################
 ###############################################################################
 
 # keep around ready for later user
 %global alphatag @alphatag@
 %global numcomm @numcomm@
 %global dirty @dirty@
 
 # set defaults from ./configure invokation
 %@sctp@ sctp
 %@nss@ nss
 %@openssl@ openssl
 %@zlib@ zlib
 %@lz4@ lz4
 %@lzo2@ lzo2
 %@lzma@ lzma
 %@bzip2@ bzip2
 %@zstd@ zstd
 %@kronosnetd@ kronosnetd
 %@libnozzle@ libnozzle
 %@runautogen@ runautogen
 %@rpmdebuginfo@ rpmdebuginfo
 %@overriderpmdebuginfo@ overriderpmdebuginfo
 %@buildman@ buildman
 %@installtests@ installtests
 
 %if %{with overriderpmdebuginfo}
 %undefine _enable_debug_packages
 %endif
 
 # main (empty) package
 # http://www.rpm.org/max-rpm/s1-rpm-subpack-spec-file-changes.html
 
 Name: kronosnet
 Summary: Multipoint-to-Multipoint VPN daemon
 Version: @version@
 Release: 1%{?numcomm:.%{numcomm}}%{?alphatag:.%{alphatag}}%{?dirty:.%{dirty}}%{?dist}
 License: GPLv2+ and LGPLv2+
 URL: https://kronosnet.org
 Source0: https://kronosnet.org/releases/%{name}-%{version}%{?numcomm:.%{numcomm}}%{?alphatag:-%{alphatag}}%{?dirty:-%{dirty}}.tar.gz
 
 # Build dependencies
-BuildRequires: gcc
+BuildRequires: gcc libqb-devel
 # required to build man pages
 %if %{with buildman}
-BuildRequires: libqb-devel libxml2-devel doxygen
+BuildRequires: libxml2-devel doxygen
 %endif
 %if %{with sctp}
 BuildRequires: lksctp-tools-devel
 %endif
 %if %{with nss}
 %if 0%{?suse_version}
 BuildRequires: mozilla-nss-devel
 %else
 BuildRequires: nss-devel
 %endif
 %endif
 %if %{with openssl}
 %if 0%{?suse_version}
 BuildRequires: libopenssl-devel
 %else
 BuildRequires: openssl-devel
 %endif
 %endif
 %if %{with zlib}
 BuildRequires: zlib-devel
 %endif
 %if %{with lz4}
 %if 0%{?suse_version}
 BuildRequires: liblz4-devel
 %else
 BuildRequires: lz4-devel
 %endif
 %endif
 %if %{with lzo2}
 BuildRequires: lzo-devel
 %endif
 %if %{with lzma}
 BuildRequires: xz-devel
 %endif
 %if %{with bzip2}
 %if 0%{?suse_version}
 BuildRequires: libbz2-devel
 %else
 BuildRequires: bzip2-devel
 %endif
 %endif
 %if %{with zstd}
 BuildRequires: libzstd-devel
 %endif
 %if %{with kronosnetd}
 BuildRequires: pam-devel
 %endif
 %if %{with libnozzle}
 BuildRequires: libnl3-devel
 %endif
 %if %{with runautogen}
 BuildRequires: autoconf automake libtool
 %endif
 
 %prep
 %setup -q -n %{name}-%{version}%{?numcomm:.%{numcomm}}%{?alphatag:-%{alphatag}}%{?dirty:-%{dirty}}
 
 %build
 %if %{with runautogen}
 ./autogen.sh
 %endif
 
 %{configure} \
 %if %{with installtests}
 	--enable-install-tests \
 %else
 	--disable-install-tests \
 %endif
 %if %{with buildman}
 	--enable-man \
 %else
 	--disable-man \
 %endif
 %if %{with sctp}
 	--enable-libknet-sctp \
 %else
 	--disable-libknet-sctp \
 %endif
 %if %{with nss}
 	--enable-crypto-nss \
 %else
 	--disable-crypto-nss \
 %endif
 %if %{with openssl}
 	--enable-crypto-openssl \
 %else
 	--disable-crypto-openssl \
 %endif
 %if %{with zlib}
 	--enable-compress-zlib \
 %else
 	--disable-compress-zlib \
 %endif
 %if %{with lz4}
 	--enable-compress-lz4 \
 %else
 	--disable-compress-lz4 \
 %endif
 %if %{with lzo2}
 	--enable-compress-lzo2 \
 %else
 	--disable-compress-lzo2 \
 %endif
 %if %{with lzma}
 	--enable-compress-lzma \
 %else
 	--disable-compress-lzma \
 %endif
 %if %{with bzip2}
 	--enable-compress-bzip2 \
 %else
 	--disable-compress-bzip2 \
 %endif
 %if %{with zstd}
 	--enable-compress-zstd \
 %else
 	--disable-compress-zstd \
 %endif
 %if %{with kronosnetd}
 	--enable-kronosnetd \
 %else
 	--disable-kronosnetd \
 %endif
 %if %{with libnozzle}
 	--enable-libnozzle \
 %else
 	--disable-libnozzle \
 %endif
 	--with-initdefaultdir=%{_sysconfdir}/sysconfig/ \
 	--with-systemddir=%{_unitdir}
 
 make %{_smp_mflags}
 
 %install
 rm -rf %{buildroot}
 make install DESTDIR=%{buildroot}
 
 # tree cleanup
 # remove static libraries
 find %{buildroot} -name "*.a" -exec rm {} \;
 # remove libtools leftovers
 find %{buildroot} -name "*.la" -exec rm {} \;
 
 # remove init scripts
 rm -rf %{buildroot}/etc/init.d
 
 # remove docs
 rm -rf %{buildroot}/usr/share/doc/kronosnet
 
 # main empty package
 %description
  The kronosnet source
 
 %if %{with kronosnetd}
 ## Runtime and subpackages section
 %package -n kronosnetd
 Summary: Multipoint-to-Multipoint VPN daemon
 License: GPLv2+
 Requires(post):   systemd-sysv
 Requires(post):   systemd-units
 Requires(preun):  systemd-units
 Requires(postun): systemd-units
 Requires(post):   shadow-utils
 Requires(preun):  shadow-utils
 Requires: pam, /etc/pam.d/passwd
 
 %description -n kronosnetd
  The kronosnet daemon is a bridge between kronosnet switching engine
  and kernel network tap devices, to create and administer a
  distributed LAN over multipoint-to-multipoint VPNs.
  The daemon does a poor attempt to provide a configure UI similar
  to other known network devices/tools (Cisco, quagga).
  Beside looking horrific, it allows runtime changes and
  reconfiguration of the kronosnet(s) without daemon reload
  or service disruption.
 
 %post -n kronosnetd
 %systemd_post kronosnetd.service
 getent group @defaultadmgroup@ >/dev/null || groupadd --force --system @defaultadmgroup@
 
 %preun -n kronosnetd
 %systemd_preun kronosnetd.service
 
 %files -n kronosnetd
 %license COPYING.* COPYRIGHT
 %dir %{_sysconfdir}/kronosnet
 %dir %{_sysconfdir}/kronosnet/*
 %config(noreplace) %{_sysconfdir}/sysconfig/kronosnetd
 %config(noreplace) %{_sysconfdir}/pam.d/kronosnetd
 %config(noreplace) %{_sysconfdir}/logrotate.d/kronosnetd
 %{_unitdir}/kronosnetd.service
 %{_sbindir}/*
 %{_mandir}/man8/*
 %endif
 
 %if %{with libnozzle}
 %package -n libnozzle1
 Summary: Simple userland wrapper around kernel tap devices
 License: LGPLv2+
 
 %description -n libnozzle1
  This is an over-engineered commodity library to manage a pool
  of tap devices and provides the basic
  pre-up.d/up.d/down.d/post-down.d infrastructure.
 
 %files -n libnozzle1
 %license COPYING.* COPYRIGHT
 %{_libdir}/libnozzle.so.*
 
 %if 0%{?ldconfig_scriptlets}
 %ldconfig_scriptlets -n libnozzle1
 %else
 %post -n libnozzle1 -p /sbin/ldconfig
 %postun -n libnozzle1 -p /sbin/ldconfig
 %endif
 
 %package -n libnozzle1-devel
 Summary: Simple userland wrapper around kernel tap devices (developer files)
 License: LGPLv2+
 Requires: libnozzle1%{_isa} = %{version}-%{release}
 Requires: pkgconfig
 
 %description -n libnozzle1-devel
  This is an over-engineered commodity library to manage a pool
  of tap devices and provides the basic
  pre-up.d/up.d/down.d/post-down.d infrastructure.
 
 %files -n libnozzle1-devel
 %license COPYING.* COPYRIGHT
 %{_libdir}/libnozzle.so
 %{_includedir}/libnozzle.h
 %{_libdir}/pkgconfig/libnozzle.pc
 %if %{with buildman}
 %{_mandir}/man3/nozzle*.3.gz
 %endif
 %endif
 
 %package -n libknet1
 Summary: Kronosnet core switching implementation
 License: LGPLv2+
 
 %description -n libknet1
  The whole kronosnet core is implemented in this library.
  Please refer to the not-yet-existing documentation for further
  information.
 
 %files -n libknet1
 %license COPYING.* COPYRIGHT
 %{_libdir}/libknet.so.*
 %dir %{_libdir}/kronosnet
 
 %if 0%{?ldconfig_scriptlets}
 %ldconfig_scriptlets -n libknet1
 %else
 %post -n libknet1 -p /sbin/ldconfig
 %postun -n libknet1 -p /sbin/ldconfig
 %endif
 
 %package -n libknet1-devel
 Summary: Kronosnet core switching implementation (developer files)
 License: LGPLv2+
 Requires: libknet1%{_isa} = %{version}-%{release}
 Requires: pkgconfig
 
 %description -n libknet1-devel
  The whole kronosnet core is implemented in this library.
  Please refer to the not-yet-existing documentation for further
  information. 
 
 %files -n libknet1-devel
 %license COPYING.* COPYRIGHT
 %{_libdir}/libknet.so
 %{_includedir}/libknet.h
 %{_libdir}/pkgconfig/libknet.pc
 %if %{with buildman}
 %{_mandir}/man3/knet*.3.gz
 %endif
 
 %if %{with nss}
 %package -n libknet1-crypto-nss-plugin
 Summary: Provides libknet1 nss support
 License: LGPLv2+
 Requires: libknet1%{_isa} = %{version}-%{release}
 
 %description -n libknet1-crypto-nss-plugin
  Provides NSS crypto support for libknet1.
 
 %files -n libknet1-crypto-nss-plugin
 %{_libdir}/kronosnet/crypto_nss.so
 %endif
 
 %if %{with openssl}
 %package -n libknet1-crypto-openssl-plugin
 Summary: Provides libknet1 openssl support
 License: LGPLv2+
 Requires: libknet1%{_isa} = %{version}-%{release}
 
 %description -n libknet1-crypto-openssl-plugin
  Provides OpenSSL crypto support for libknet1.
 
 %files -n libknet1-crypto-openssl-plugin
 %{_libdir}/kronosnet/crypto_openssl.so
 %endif
 
 %if %{with zlib}
 %package -n libknet1-compress-zlib-plugin
 Summary: Provides libknet1 zlib support
 License: LGPLv2+
 Requires: libknet1%{_isa} = %{version}-%{release}
 
 %description -n libknet1-compress-zlib-plugin
  Provides zlib compression support for libknet1.
 
 %files -n libknet1-compress-zlib-plugin
 %{_libdir}/kronosnet/compress_zlib.so
 %endif
 
 %if %{with lz4}
 %package -n libknet1-compress-lz4-plugin
 Summary: Provides libknet1 lz4 and lz4hc support
 License: LGPLv2+
 Requires: libknet1%{_isa} = %{version}-%{release}
 
 %description -n libknet1-compress-lz4-plugin
  Provides lz4 and lz4hc compression support for libknet1.
 
 %files -n libknet1-compress-lz4-plugin
 %{_libdir}/kronosnet/compress_lz4.so
 %{_libdir}/kronosnet/compress_lz4hc.so
 %endif
 
 %if %{with lzo2}
 %package -n libknet1-compress-lzo2-plugin
 Summary: Provides libknet1 lzo2 support
 License: LGPLv2+
 Requires: libknet1%{_isa} = %{version}-%{release}
 
 %description -n libknet1-compress-lzo2-plugin
  Provides lzo2 compression support for libknet1.
 
 %files -n libknet1-compress-lzo2-plugin
 %{_libdir}/kronosnet/compress_lzo2.so
 %endif
 
 %if %{with lzma}
 %package -n libknet1-compress-lzma-plugin
 Summary: Provides libknet1 lzma support
 License: LGPLv2+
 Requires: libknet1%{_isa} = %{version}-%{release}
 
 %description -n libknet1-compress-lzma-plugin
  Provides lzma compression support for libknet1.
 
 %files -n libknet1-compress-lzma-plugin
 %{_libdir}/kronosnet/compress_lzma.so
 %endif
 
 %if %{with bzip2}
 %package -n libknet1-compress-bzip2-plugin
 Summary: Provides libknet1 bzip2 support
 License: LGPLv2+
 Requires: libknet1%{_isa} = %{version}-%{release}
 
 %description -n libknet1-compress-bzip2-plugin
  Provides bzip2 compression support for libknet1.
 
 %files -n libknet1-compress-bzip2-plugin
 %{_libdir}/kronosnet/compress_bzip2.so
 %endif
 
 %if %{with zstd}
 %package -n libknet1-compress-zstd-plugin
 Summary: Provides libknet1 zstd support
 License: LGPLv2+
 Requires: libknet1%{_isa} = %{version}-%{release}
 
 %description -n libknet1-compress-zstd-plugin
  Provides zstd compression support for libknet1.
 
 %files -n libknet1-compress-zstd-plugin
 %{_libdir}/kronosnet/compress_zstd.so
 %endif
 
 %package -n libknet1-crypto-plugins-all
 Summary: Provides libknet1 crypto plugins meta package
 License: LGPLv2+
 %if %{with nss}
 Requires: libknet1-crypto-nss-plugin%{_isa} = %{version}-%{release}
 %endif
 %if %{with openssl}
 Requires: libknet1-crypto-openssl-plugin%{_isa} = %{version}-%{release}
 %endif
 
 %description -n libknet1-crypto-plugins-all
  Provides meta package to install all of libknet1 crypto plugins
 
 %files -n libknet1-crypto-plugins-all
 
 %package -n libknet1-compress-plugins-all
 Summary: Provides libknet1 compress plugins meta package
 License: LGPLv2+
 %if %{with zlib}
 Requires: libknet1-compress-zlib-plugin%{_isa} = %{version}-%{release}
 %endif
 %if %{with lz4}
 Requires: libknet1-compress-lz4-plugin%{_isa} = %{version}-%{release}
 %endif
 %if %{with lzo2}
 Requires: libknet1-compress-lzo2-plugin%{_isa} = %{version}-%{release}
 %endif
 %if %{with lzma}
 Requires: libknet1-compress-lzma-plugin%{_isa} = %{version}-%{release}
 %endif
 %if %{with bzip2}
 Requires: libknet1-compress-bzip2-plugin%{_isa} = %{version}-%{release}
 %endif
 %if %{with zstd}
 Requires: libknet1-compress-zstd-plugin%{_isa} = %{version}-%{release}
 %endif
 
 %description -n libknet1-compress-plugins-all
  Meta package to install all of libknet1 compress plugins
 
 %files -n libknet1-compress-plugins-all
 
 %package -n libknet1-plugins-all
 Summary: Provides libknet1 plugins meta package
 License: LGPLv2+
 Requires: libknet1-compress-plugins-all%{_isa} = %{version}-%{release}
 Requires: libknet1-crypto-plugins-all%{_isa} = %{version}-%{release}
 
 %description -n libknet1-plugins-all
  Meta package to install all of libknet1 plugins
 
 %files -n libknet1-plugins-all
 
 %if %{with installtests}
 %package -n kronosnet-tests
 Summary: Provides kronosnet test suite
 License: GPLv2+
 Requires: libknet1%{_isa} = %{version}-%{release}
 
 %description -n kronosnet-tests
  This package contains all the libknet and libnozzle test suite.
 
 %files -n kronosnet-tests
 %{_libdir}/kronosnet/tests/*
 %endif
 
 %if %{with rpmdebuginfo}
 %debug_package
 %endif
 
 %changelog
 * @date@ Autotools generated version <nobody@nowhere.org> - @version@-1-@numcomm@.@alphatag@.@dirty@
 - These aren't the droids you're looking for.
 
diff --git a/libknet/Makefile.am b/libknet/Makefile.am
index f9ba25db..3550a86a 100644
--- a/libknet/Makefile.am
+++ b/libknet/Makefile.am
@@ -1,164 +1,166 @@
 #
 # Copyright (C) 2010-2020 Red Hat, Inc.  All rights reserved.
 #
 # Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
 #          Federico Simoncelli <fsimon@kronosnet.org>
 #
 # This software licensed under GPL-2.0+
 #
 
 MAINTAINERCLEANFILES	= Makefile.in
 
 include $(top_srcdir)/build-aux/check.mk
 
 SYMFILE			= libknet_exported_syms
 
 EXTRA_DIST		= $(SYMFILE)
 
 SUBDIRS			= . tests
 
 # https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
 libversion		= 4:0:3
 
 # override global LIBS that pulls in lots of craft we don't need here
 LIBS			=
 
 sources			= \
 			  common.c \
 			  compat.c \
 			  compress.c \
 			  crypto.c \
 			  handle.c \
 			  host.c \
 			  links.c \
 			  links_acl.c \
 			  links_acl_ip.c \
 			  links_acl_loopback.c \
 			  logging.c \
 			  netutils.c \
 			  onwire.c \
 			  threads_common.c \
 			  threads_dsthandler.c \
 			  threads_heartbeat.c \
 			  threads_pmtud.c \
 			  threads_rx.c \
 			  threads_tx.c \
 			  transports.c \
 			  transport_common.c \
 			  transport_loopback.c \
 			  transport_udp.c \
 			  transport_sctp.c
 
 include_HEADERS		= libknet.h
 
 pkgconfigdir		= $(libdir)/pkgconfig
 
 pkgconfig_DATA		= libknet.pc
 
 noinst_HEADERS		= \
 			  common.h \
 			  compat.h \
 			  compress.h \
 			  compress_model.h \
 			  crypto.h \
 			  crypto_model.h \
 			  host.h \
 			  internals.h \
 			  links.h \
 			  links_acl.h \
 			  links_acl_ip.h \
 			  links_acl_loopback.h \
 			  logging.h \
 			  netutils.h \
 			  onwire.h \
 			  threads_common.h \
 			  threads_dsthandler.h \
 			  threads_heartbeat.h \
 			  threads_pmtud.h \
 			  threads_rx.h \
 			  threads_tx.h \
 			  transports.h \
 			  transport_common.h \
 			  transport_loopback.h \
 			  transport_udp.h \
 			  transport_sctp.h
 
 lib_LTLIBRARIES		= libknet.la
 
 libknet_la_SOURCES	= $(sources)
 
+AM_CFLAGS		+= $(libqb_CFLAGS)
+
 libknet_la_CFLAGS	= $(AM_CFLAGS) $(PTHREAD_CFLAGS)
 
 EXTRA_libknet_la_DEPENDENCIES	= $(SYMFILE)
 
 libknet_la_LDFLAGS	= $(AM_LDFLAGS) \
 			  -Wl,--version-script=$(srcdir)/$(SYMFILE) \
 			  -Wl,-rpath=$(pkglibdir) \
 			  -version-info $(libversion)
 
 libknet_la_LIBADD	= $(PTHREAD_LIBS) $(dl_LIBS) $(rt_LIBS) $(m_LIBS)
 
 # Prepare empty value for appending
 pkglib_LTLIBRARIES	=
 
 # MODULE_LDFLAGS would mean a target-specific variable for Automake
 MODULELDFLAGS		= $(AM_LDFLAGS) -module -avoid-version -export-dynamic
 
 if BUILD_COMPRESS_ZSTD
 pkglib_LTLIBRARIES	+= compress_zstd.la
 compress_zstd_la_LDFLAGS = $(MODULELDFLAGS)
 compress_zstd_la_CFLAGS	= $(AM_CFLAGS) $(libzstd_CFLAGS)
 compress_zstd_la_LIBADD	= $(libzstd_LIBS)
 endif
 
 if BUILD_COMPRESS_ZLIB
 pkglib_LTLIBRARIES	+= compress_zlib.la
 compress_zlib_la_LDFLAGS = $(MODULELDFLAGS)
 compress_zlib_la_CFLAGS	= $(AM_CFLAGS) $(zlib_CFLAGS)
 compress_zlib_la_LIBADD	= $(zlib_LIBS)
 endif
 
 if BUILD_COMPRESS_LZ4
 pkglib_LTLIBRARIES	+= compress_lz4.la compress_lz4hc.la
 compress_lz4_la_LDFLAGS	= $(MODULELDFLAGS)
 compress_lz4_la_CFLAGS	= $(AM_CFLAGS) $(liblz4_CFLAGS)
 compress_lz4_la_LIBADD	= $(liblz4_LIBS)
 compress_lz4hc_la_LDFLAGS = $(MODULELDFLAGS)
 compress_lz4hc_la_CFLAGS = $(AM_CFLAGS) $(liblz4_CFLAGS)
 compress_lz4hc_la_LIBADD = $(liblz4_LIBS)
 endif
 
 if BUILD_COMPRESS_LZO2
 pkglib_LTLIBRARIES	+= compress_lzo2.la
 compress_lzo2_la_LDFLAGS = $(MODULELDFLAGS)
 compress_lzo2_la_CFLAGS	= $(AM_CFLAGS) $(lzo2_CFLAGS)
 compress_lzo2_la_LIBADD	= $(lzo2_LIBS)
 endif
 
 if BUILD_COMPRESS_LZMA
 pkglib_LTLIBRARIES	+= compress_lzma.la
 compress_lzma_la_LDFLAGS = $(MODULELDFLAGS)
 compress_lzma_la_CFLAGS	= $(AM_CFLAGS) $(liblzma_CFLAGS)
 compress_lzma_la_LIBADD	= $(liblzma_LIBS)
 endif
 
 if BUILD_COMPRESS_BZIP2
 pkglib_LTLIBRARIES	+= compress_bzip2.la
 compress_bzip2_la_LDFLAGS = $(MODULELDFLAGS)
 compress_bzip2_la_CFLAGS = $(AM_CFLAGS) $(bzip2_CFLAGS)
 compress_bzip2_la_LIBADD = $(bzip2_LIBS)
 endif
 
 if BUILD_CRYPTO_NSS
 pkglib_LTLIBRARIES	+= crypto_nss.la
 crypto_nss_la_LDFLAGS	= $(MODULELDFLAGS)
 crypto_nss_la_CFLAGS	= $(AM_CFLAGS) $(nss_CFLAGS)
 crypto_nss_la_LIBADD	= $(nss_LIBS)
 endif
 
 if BUILD_CRYPTO_OPENSSL
 pkglib_LTLIBRARIES	+= crypto_openssl.la
 crypto_openssl_la_LDFLAGS = $(MODULELDFLAGS)
 crypto_openssl_la_CFLAGS = $(AM_CFLAGS) $(openssl_CFLAGS)
 crypto_openssl_la_LIBADD = $(openssl_LIBS)
 endif
diff --git a/libknet/internals.h b/libknet/internals.h
index 94e208b6..b763d4a9 100644
--- a/libknet/internals.h
+++ b/libknet/internals.h
@@ -1,580 +1,418 @@
 /*
  * Copyright (C) 2010-2020 Red Hat, Inc.  All rights reserved.
  *
  * Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
  *          Federico Simoncelli <fsimon@kronosnet.org>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #ifndef __KNET_INTERNALS_H__
 #define __KNET_INTERNALS_H__
 
 /*
  * NOTE: you shouldn't need to include this header normally
  */
 
 #include <pthread.h>
+#include <stddef.h>
+#include <qb/qblist.h>
 #include "libknet.h"
 #include "onwire.h"
 #include "compat.h"
 #include "threads_common.h"
 
 #define KNET_DATABUFSIZE KNET_MAX_PACKET_SIZE + KNET_HEADER_ALL_SIZE
 
 #define KNET_DATABUFSIZE_CRYPT_PAD 1024
 #define KNET_DATABUFSIZE_CRYPT KNET_DATABUFSIZE + KNET_DATABUFSIZE_CRYPT_PAD
 
 #define KNET_DATABUFSIZE_COMPRESS_PAD 1024
 #define KNET_DATABUFSIZE_COMPRESS KNET_DATABUFSIZE + KNET_DATABUFSIZE_COMPRESS_PAD
 
 #define KNET_RING_RCVBUFF 8388608
 
 #define PCKT_FRAG_MAX UINT8_MAX
 #define PCKT_RX_BUFS  512
 
 #define KNET_EPOLL_MAX_EVENTS KNET_DATAFD_MAX + 1
 
 #define KNET_INTERNAL_DATA_CHANNEL KNET_DATAFD_MAX
 
 /*
  * Size of threads stack. Value is choosen by experimenting, how much is needed
  * to sucesfully finish test suite, and at the time of writing patch it was
  * ~300KiB. To have some room for future enhancement it is increased
  * by factor of 3 and rounded.
  */
 #define KNET_THREAD_STACK_SIZE (1024 * 1024)
 
 typedef void *knet_transport_link_t; /* per link transport handle */
 typedef void *knet_transport_t;      /* per knet_h transport handle */
 struct  knet_transport_ops;          /* Forward because of circular dependancy */
 
 struct knet_mmsghdr {
 	struct msghdr msg_hdr;	/* Message header */
 	unsigned int  msg_len;	/* Number of bytes transmitted */
 };
 
 struct knet_link {
 	/* required */
 	struct sockaddr_storage src_addr;
 	struct sockaddr_storage dst_addr;
 	/* configurable */
 	unsigned int dynamic;			/* see KNET_LINK_DYN_ define above */
 	uint8_t  priority;			/* higher priority == preferred for A/P */
 	unsigned long long ping_interval;	/* interval */
 	unsigned long long pong_timeout;	/* timeout */
 	unsigned long long pong_timeout_adj;	/* timeout adjusted for latency */
 	uint8_t pong_timeout_backoff;		/* see link.h for definition */
 	unsigned int latency_max_samples;	/* precision */
 	unsigned int latency_cur_samples;
 	uint8_t pong_count;			/* how many ping/pong to send/receive before link is up */
 	uint64_t flags;
 	/* status */
 	struct knet_link_status status;
 	/* internals */
 	pthread_mutex_t link_stats_mutex;	/* used to update link stats */
 	uint8_t link_id;
 	uint8_t transport;                      /* #defined constant from API */
 	knet_transport_link_t transport_link;   /* link_info_t from transport */
 	int outsock;
 	unsigned int configured:1;		/* set to 1 if src/dst have been configured transport initialized on this link*/
 	unsigned int transport_connected:1;	/* set to 1 if lower level transport is connected */
 	uint8_t received_pong;
 	struct timespec ping_last;
 	/* used by PMTUD thread as temp per-link variables and should always contain the onwire_len value! */
 	uint32_t proto_overhead;		/* IP + UDP/SCTP overhead. NOT to be confused
 						   with stats.proto_overhead that includes also knet headers
 						   and crypto headers */
 	struct timespec pmtud_last;
 	uint32_t last_ping_size;
 	uint32_t last_good_mtu;
 	uint32_t last_bad_mtu;
 	uint32_t last_sent_mtu;
 	uint32_t last_recv_mtu;
 	uint32_t pmtud_crypto_timeout_multiplier;/* used by PMTUd to adjust timeouts on high loads */
 	uint8_t has_valid_mtu;
 };
 
 #define KNET_CBUFFER_SIZE 4096
 
 struct knet_host_defrag_buf {
 	char buf[KNET_DATABUFSIZE];
 	uint8_t in_use;			/* 0 buffer is free, 1 is in use */
 	seq_num_t pckt_seq;		/* identify the pckt we are receiving */
 	uint8_t frag_recv;		/* how many frags did we receive */
 	uint8_t frag_map[PCKT_FRAG_MAX];/* bitmap of what we received? */
 	uint8_t	last_first;		/* special case if we receive the last fragment first */
 	ssize_t frag_size;		/* normal frag size (not the last one) */
 	ssize_t last_frag_size;		/* the last fragment might not be aligned with MTU size */
 	struct timespec last_update;	/* keep time of the last pckt */
 };
 
 struct knet_host {
 	/* required */
 	knet_node_id_t host_id;
 	/* configurable */
 	uint8_t link_handler_policy;
 	char name[KNET_MAX_HOST_LEN];
 	/* status */
 	struct knet_host_status status;
 	/* internals */
 	char circular_buffer[KNET_CBUFFER_SIZE];
 	seq_num_t rx_seq_num;
 	seq_num_t untimed_rx_seq_num;
 	seq_num_t timed_rx_seq_num;
 	uint8_t got_data;
 	/* defrag/reassembly buffers */
 	struct knet_host_defrag_buf defrag_buf[KNET_MAX_LINK];
 	char circular_buffer_defrag[KNET_CBUFFER_SIZE];
 	/* link stuff */
 	struct knet_link link[KNET_MAX_LINK];
 	uint8_t active_link_entries;
 	uint8_t active_links[KNET_MAX_LINK];
 	struct knet_host *next;
 };
 
 struct knet_sock {
 	int sockfd[2];   /* sockfd[0] will always be application facing
 			  * and sockfd[1] internal if sockpair has been created by knet */
 	int is_socket;   /* check if it's a socket for recvmmsg usage */
 	int is_created;  /* knet created this socket and has to clean up on exit/del */
 	int in_use;      /* set to 1 if it's use, 0 if free */
 	int has_error;   /* set to 1 if there were errors reading from the sock
 			  * and socket has been removed from epoll */
 };
 
 struct knet_fd_trackers {
 	uint8_t transport;		    /* transport type (UDP/SCTP...) */
 	uint8_t data_type;		    /* internal use for transport to define what data are associated
 					     * with this fd */
 	void *data;			    /* pointer to the data */
 	void *access_list_match_entry_head; /* pointer to access list match_entry list head */
 };
 
 #define KNET_MAX_FDS KNET_MAX_HOST * KNET_MAX_LINK * 4
 
 #define KNET_MAX_COMPRESS_METHODS UINT8_MAX
 
 struct knet_handle_stats_extra {
 	uint64_t tx_crypt_pmtu_packets;
 	uint64_t tx_crypt_pmtu_reply_packets;
 	uint64_t tx_crypt_ping_packets;
 	uint64_t tx_crypt_pong_packets;
 };
 
 struct knet_handle {
 	knet_node_id_t host_id;
 	unsigned int enabled:1;
 	struct knet_sock sockfd[KNET_DATAFD_MAX + 1];
 	int logfd;
 	uint8_t log_levels[KNET_MAX_SUBSYSTEMS];
 	int hostsockfd[2];
 	int dstsockfd[2];
 	int send_to_links_epollfd;
 	int recv_from_links_epollfd;
 	int dst_link_handler_epollfd;
 	uint8_t use_access_lists; /* set to 0 for disable, 1 for enable */
 	unsigned int pmtud_interval;
 	unsigned int manual_mtu;
 	unsigned int data_mtu;	/* contains the max data size that we can send onwire
 				 * without frags */
 	struct knet_host *host_head;
 	struct knet_host *host_index[KNET_MAX_HOST];
 	knet_transport_t transports[KNET_MAX_TRANSPORTS+1];
 	struct knet_fd_trackers knet_transport_fd_tracker[KNET_MAX_FDS]; /* track status for each fd handled by transports */
 	struct knet_handle_stats stats;
 	struct knet_handle_stats_extra stats_extra;
 	pthread_mutex_t handle_stats_mutex;	/* used to protect handle stats */
 	uint32_t reconnect_int;
 	knet_node_id_t host_ids[KNET_MAX_HOST];
 	size_t host_ids_entries;
 	struct knet_header *recv_from_sock_buf;
 	struct knet_header *send_to_links_buf[PCKT_FRAG_MAX];
 	struct knet_header *recv_from_links_buf[PCKT_RX_BUFS];
 	struct knet_header *pingbuf;
 	struct knet_header *pmtudbuf;
 	uint8_t threads_status[KNET_THREAD_MAX];
 	uint8_t threads_flush_queue[KNET_THREAD_MAX];
 	pthread_mutex_t threads_status_mutex;
 	pthread_t send_to_links_thread;
 	pthread_t recv_from_links_thread;
 	pthread_t heartbt_thread;
 	pthread_t dst_link_handler_thread;
 	pthread_t pmtud_link_handler_thread;
 	pthread_rwlock_t global_rwlock;		/* global config lock */
 	pthread_mutex_t pmtud_mutex;		/* pmtud mutex to handle conditional send/recv + timeout */
 	pthread_cond_t pmtud_cond;		/* conditional for above */
 	pthread_mutex_t tx_mutex;		/* used to protect knet_send_sync and TX thread */
 	pthread_mutex_t hb_mutex;		/* used to protect heartbeat thread and seq_num broadcasting */
 	pthread_mutex_t backoff_mutex;		/* used to protect dst_link->pong_timeout_adj */
 	pthread_mutex_t kmtu_mutex;		/* used to protect kernel_mtu */
 	uint32_t kernel_mtu;			/* contains the MTU detected by the kernel on a given link */
 	int pmtud_waiting;
 	int pmtud_running;
 	int pmtud_forcerun;
 	int pmtud_abort;
 	struct crypto_instance *crypto_instance;
 	size_t sec_block_size;
 	size_t sec_hash_size;
 	size_t sec_salt_size;
 	unsigned char *send_to_links_buf_crypt[PCKT_FRAG_MAX];
 	unsigned char *recv_from_links_buf_crypt;
 	unsigned char *recv_from_links_buf_decrypt;
 	unsigned char *pingbuf_crypt;
 	unsigned char *pmtudbuf_crypt;
 	int compress_model;
 	int compress_level;
 	size_t compress_threshold;
 	void *compress_int_data[KNET_MAX_COMPRESS_METHODS]; /* for compress method private data */
 	unsigned char *recv_from_links_buf_decompress;
 	unsigned char *send_to_links_buf_compress;
 	seq_num_t tx_seq_num;
 	pthread_mutex_t tx_seq_num_mutex;
 	uint8_t has_loop_link;
 	uint8_t loop_link;
 	void *dst_host_filter_fn_private_data;
 	int (*dst_host_filter_fn) (
 		void *private_data,
 		const unsigned char *outdata,
 		ssize_t outdata_len,
 		uint8_t tx_rx,
 		knet_node_id_t this_host_id,
 		knet_node_id_t src_node_id,
 		int8_t *channel,
 		knet_node_id_t *dst_host_ids,
 		size_t *dst_host_ids_entries);
 	void *pmtud_notify_fn_private_data;
 	void (*pmtud_notify_fn) (
 		void *private_data,
 		unsigned int data_mtu);
 	void *host_status_change_notify_fn_private_data;
 	void (*host_status_change_notify_fn) (
 		void *private_data,
 		knet_node_id_t host_id,
 		uint8_t reachable,
 		uint8_t remote,
 		uint8_t external);
 	void *sock_notify_fn_private_data;
 	void (*sock_notify_fn) (
 		void *private_data,
 		int datafd,
 		int8_t channel,
 		uint8_t tx_rx,
 		int error,
 		int errorno);
 	int fini_in_progress;
 	uint64_t flags;
 };
 
 extern pthread_rwlock_t shlib_rwlock;       /* global shared lib load lock */
 
 /*
  * NOTE: every single operation must be implementend
  *       for every protocol.
  */
 
 /*
  * for now knet supports only IP protocols (udp/sctp)
  * in future there might be others like ARP
  * or TIPC.
  * keep this around as transport information
  * to use for access lists and other operations
  */
 
 #define TRANSPORT_PROTO_LOOPBACK 0
 #define TRANSPORT_PROTO_IP_PROTO 1
 
 /*
  * some transports like SCTP can filter incoming
  * connections before knet has to process
  * any packets.
  * GENERIC_ACL -> packet has to be read and filterted
  * PROTO_ACL -> transport provides filtering at lower levels
  *              and packet does not need to be processed
  */
 
 typedef enum {
 	USE_NO_ACL,
 	USE_GENERIC_ACL,
 	USE_PROTO_ACL
 } transport_acl;
 
 /*
  * make it easier to map values in transports.c
  */
 #define TRANSPORT_PROTO_NOT_CONNECTION_ORIENTED 0
 #define TRANSPORT_PROTO_IS_CONNECTION_ORIENTED  1
 
 typedef struct knet_transport_ops {
 /*
  * transport generic information
  */
 	const char *transport_name;
 	const uint8_t transport_id;
 	const uint8_t built_in;
 
 	uint8_t transport_protocol;
 	transport_acl transport_acl_type;
 
 /*
  * connection oriented protocols like SCTP
  * don“t need dst_addr in sendto calls and
  * on some OSes are considered EINVAL.
  */
 	uint8_t transport_is_connection_oriented;
 
 	uint32_t transport_mtu_overhead;
 /*
  * transport init must allocate the new transport
  * and perform all internal initializations
  * (threads, lists, etc).
  */
 	int (*transport_init)(knet_handle_t knet_h);
 /*
  * transport free must releases _all_ resources
  * allocated by tranport_init
  */
 	int (*transport_free)(knet_handle_t knet_h);
 
 /*
  * link operations should take care of all the
  * sockets and epoll management for a given link/transport set
  * transport_link_disable should return err = -1 and errno = EBUSY
  * if listener is still in use, and any other errno in case
  * the link cannot be disabled.
  *
  * set_config/clear_config are invoked in global write lock context
  */
 	int (*transport_link_set_config)(knet_handle_t knet_h, struct knet_link *link);
 	int (*transport_link_clear_config)(knet_handle_t knet_h, struct knet_link *link);
 
 /*
  * transport callback for incoming dynamic connections
  * this is called in global read lock context
  */
 	int (*transport_link_dyn_connect)(knet_handle_t knet_h, int sockfd, struct knet_link *link);
 
 
 /*
  * return the fd to use for access lists
  */
 	int (*transport_link_get_acl_fd)(knet_handle_t knet_h, struct knet_link *link);
 
 /*
  * per transport error handling of recvmmsg
  * (see _handle_recv_from_links comments for details)
  */
 
 /*
  * transport_rx_sock_error is invoked when recvmmsg returns <= 0
  *
  * transport_rx_sock_error is invoked with both global_rdlock
  */
 
 	int (*transport_rx_sock_error)(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno);
 
 /*
  * transport_tx_sock_error is invoked with global_rwlock and
  * it's invoked when sendto or sendmmsg returns =< 0
  *
  * it should return:
  * -1 on internal error
  *  0 ignore error and continue
  *  1 retry
  *    any sleep or wait action should happen inside the transport code
  */
 	int (*transport_tx_sock_error)(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno);
 
 /*
  * this function is called on _every_ received packet
  * to verify if the packet is data or internal protocol error handling
  *
  * it should return:
  * -1 on error
  *  0 packet is not data and we should continue the packet process loop
  *  1 packet is not data and we should STOP the packet process loop
  *  2 packet is data and should be parsed as such
  *
  * transport_rx_is_data is invoked with both global_rwlock
  * and fd_tracker read lock (from RX thread)
  */
 	int (*transport_rx_is_data)(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg);
-} knet_transport_ops_t;
 
-socklen_t sockaddr_len(const struct sockaddr_storage *ss);
+/*
+ * this function is called by links.c when a link down event is recorded
+ * to notify the transport that packets are not going through, and give
+ * transport the opportunity to take actions.
+ */
+	int (*transport_link_is_down)(knet_handle_t knet_h, struct knet_link *link);
+} knet_transport_ops_t;
 
 struct pretty_names {
 	const char *name;
 	uint8_t val;
 };
 
-/**
- * This is a kernel style list implementation.
- *
- * @author Steven Dake <sdake@redhat.com>
- */
-
-struct knet_list_head {
-	struct knet_list_head *next;
-	struct knet_list_head *prev;
-};
-
-/**
- * @def KNET_LIST_DECLARE()
- * Declare and initialize a list head.
- */
-#define KNET_LIST_DECLARE(name) \
-    struct knet_list_head name = { &(name), &(name) }
-
-#define KNET_INIT_LIST_HEAD(ptr) do { \
-	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
-} while (0)
-
-/**
- * Initialize the list entry.
- *
- * Points next and prev pointers to head.
- * @param head pointer to the list head
- */
-static inline void knet_list_init(struct knet_list_head *head)
-{
-	head->next = head;
-	head->prev = head;
-}
-
-/**
- * Add this element to the list.
- *
- * @param element the new element to insert.
- * @param head pointer to the list head
- */
-static inline void knet_list_add(struct knet_list_head *element,
-			       struct knet_list_head *head)
-{
-	head->next->prev = element;
-	element->next = head->next;
-	element->prev = head;
-	head->next = element;
-}
-
-/**
- * Add to the list (but at the end of the list).
- *
- * @param element pointer to the element to add
- * @param head pointer to the list head
- * @see knet_list_add()
- */
-static inline void knet_list_add_tail(struct knet_list_head *element,
-				    struct knet_list_head *head)
-{
-	head->prev->next = element;
-	element->next = head;
-	element->prev = head->prev;
-	head->prev = element;
-}
-
-/**
- * Delete an entry from the list.
- *
- * @param _remove the list item to remove
- */
-static inline void knet_list_del(struct knet_list_head *_remove)
-{
-	_remove->next->prev = _remove->prev;
-	_remove->prev->next = _remove->next;
-}
-
-/**
- * Replace old entry by new one
- * @param old: the element to be replaced
- * @param new: the new element to insert
- */
-static inline void knet_list_replace(struct knet_list_head *old,
-		struct knet_list_head *new)
-{
-	new->next = old->next;
-	new->next->prev = new;
-	new->prev = old->prev;
-	new->prev->next = new;
-}
-
-/**
- * Tests whether list is the last entry in list head
- * @param list: the entry to test
- * @param head: the head of the list
- * @return boolean true/false
- */
-static inline int knet_list_is_last(const struct knet_list_head *list,
-		const struct knet_list_head *head)
-{
-	return list->next == head;
-}
-
-/**
- * A quick test to see if the list is empty (pointing to it's self).
- * @param head pointer to the list head
- * @return boolean true/false
- */
-static inline int32_t knet_list_empty(const struct knet_list_head *head)
-{
-	return head->next == head;
-}
-
-
-/**
- * Get the struct for this entry
- * @param ptr:	the &struct list_head pointer.
- * @param type:	the type of the struct this is embedded in.
- * @param member:	the name of the list_struct within the struct.
- */
-#define knet_list_entry(ptr,type,member)\
-	((type *)((char *)(ptr)-(char*)(&((type *)0)->member)))
-
-/**
- * Get the first element from a list
- * @param ptr:	the &struct list_head pointer.
- * @param type:	the type of the struct this is embedded in.
- * @param member:	the name of the list_struct within the struct.
- */
-#define knet_list_first_entry(ptr, type, member) \
-	knet_list_entry((ptr)->next, type, member)
-
-/**
- * Iterate over a list
- * @param pos:	the &struct list_head to use as a loop counter.
- * @param head:	the head for your list.
- */
-#define knet_list_for_each(pos, head) \
-	for (pos = (head)->next; pos != (head); pos = pos->next)
-
-/**
- * Iterate over a list backwards
- * @param pos:	the &struct list_head to use as a loop counter.
- * @param head:	the head for your list.
- */
-#define knet_list_for_each_reverse(pos, head) \
-	for (pos = (head)->prev; pos != (head); pos = pos->prev)
-
-/**
- * Iterate over a list safe against removal of list entry
- * @param pos:	the &struct list_head to use as a loop counter.
- * @param n:		another &struct list_head to use as temporary storage
- * @param head:	the head for your list.
- */
-#define knet_list_for_each_safe(pos, n, head) \
-	for (pos = (head)->next, n = pos->next; pos != (head); \
-		pos = n, n = pos->next)
-
-/**
- * Iterate over list of given type
- * @param pos:	the type * to use as a loop counter.
- * @param head:	the head for your list.
- * @param member:	the name of the list_struct within the struct.
- */
-#define knet_list_for_each_entry(pos, head, member)			\
-	for (pos = knet_list_entry((head)->next, typeof(*pos), member);	\
-	     &pos->member != (head);					\
-	     pos = knet_list_entry(pos->member.next, typeof(*pos), member))
-
-
 #endif
diff --git a/libknet/links.c b/libknet/links.c
index d08065bd..a127144a 100644
--- a/libknet/links.c
+++ b/libknet/links.c
@@ -1,1551 +1,1556 @@
 /*
  * Copyright (C) 2012-2020 Red Hat, Inc.  All rights reserved.
  *
  * Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
  *          Federico Simoncelli <fsimon@kronosnet.org>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #include "config.h"
 
 #include <errno.h>
 #include <netdb.h>
 #include <string.h>
 #include <pthread.h>
 
 #include "internals.h"
 #include "logging.h"
 #include "links.h"
 #include "transports.h"
 #include "host.h"
 #include "threads_common.h"
 #include "links_acl.h"
 
 int _link_updown(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 		 unsigned int enabled, unsigned int connected, unsigned int lock_stats)
 {
 	struct knet_host *host = knet_h->host_index[host_id];
 	struct knet_link *link = &host->link[link_id];
 	int savederrno = 0;
 
 	if ((link->status.enabled == enabled) &&
 	    (link->status.connected == connected))
 		return 0;
 
 	link->status.enabled = enabled;
 	link->status.connected = connected;
 
 	_host_dstcache_update_async(knet_h, knet_h->host_index[host_id]);
 
 	if ((link->status.dynconnected) &&
-	    (!link->status.connected))
+	    (!link->status.connected)) {
 		link->status.dynconnected = 0;
+	}
+
+	if (!connected) {
+		transport_link_is_down(knet_h, link);
+	}
 
 	if (lock_stats) {
 		savederrno = pthread_mutex_lock(&link->link_stats_mutex);
 		if (savederrno) {
 			log_err(knet_h, KNET_SUB_LINK, "Unable to get stats mutex lock for host %u link %u: %s",
 				host_id, link_id, strerror(savederrno));
 			errno = savederrno;
 			return -1;
 		}
 	}
 
 	if (connected) {
 		time(&link->status.stats.last_up_times[link->status.stats.last_up_time_index]);
 		link->status.stats.up_count++;
 		if (++link->status.stats.last_up_time_index >= MAX_LINK_EVENTS) {
 			link->status.stats.last_up_time_index = 0;
 		}
 	} else {
 		time(&link->status.stats.last_down_times[link->status.stats.last_down_time_index]);
 		link->status.stats.down_count++;
 		if (++link->status.stats.last_down_time_index >= MAX_LINK_EVENTS) {
 			link->status.stats.last_down_time_index = 0;
 		}
 	}
 
 	if (lock_stats) {
 		pthread_mutex_unlock(&link->link_stats_mutex);
 	}
 	return 0;
 }
 
 void _link_clear_stats(knet_handle_t knet_h)
 {
 	struct knet_host *host;
 	struct knet_link *link;
 	uint32_t host_id;
 	uint8_t link_id;
 
 	for (host_id = 0; host_id < KNET_MAX_HOST; host_id++) {
 		host = knet_h->host_index[host_id];
 		if (!host) {
 			continue;
 		}
 		for (link_id = 0; link_id < KNET_MAX_LINK; link_id++) {
 			link = &host->link[link_id];
 			memset(&link->status.stats, 0, sizeof(struct knet_link_stats));
 		}
 	}
 }
 
 int knet_link_set_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			 uint8_t transport,
 			 struct sockaddr_storage *src_addr,
 			 struct sockaddr_storage *dst_addr,
 			 uint64_t flags)
 {
 	int savederrno = 0, err = 0, i;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!src_addr) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (dst_addr && (src_addr->ss_family != dst_addr->ss_family)) {
 		log_err(knet_h, KNET_SUB_LINK, "Source address family does not match destination address family");
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (transport >= KNET_MAX_TRANSPORTS) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	if (transport == KNET_TRANSPORT_LOOPBACK && knet_h->host_id != host_id) {
 		log_err(knet_h, KNET_SUB_LINK, "Cannot create loopback link to remote node");
 		err = -1;
 		savederrno = EINVAL;
 		goto exit_unlock;
 	}
 
 	if (knet_h->host_id == host_id && knet_h->has_loop_link) {
 		log_err(knet_h, KNET_SUB_LINK, "Cannot create more than 1 link when loopback is active");
 		err = -1;
 		savederrno = EINVAL;
 		goto exit_unlock;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	if (transport == KNET_TRANSPORT_LOOPBACK && knet_h->host_id == host_id) {
 		for (i=0; i<KNET_MAX_LINK; i++) {
 			if (host->link[i].configured) {
 				log_err(knet_h, KNET_SUB_LINK, "Cannot add loopback link when other links are already configured.");
 				err = -1;
 				savederrno = EINVAL;
 				goto exit_unlock;
 			}
 		}
 	}
 
 	link = &host->link[link_id];
 
 	if (link->configured != 0) {
 		err =-1;
 		savederrno = EBUSY;
 		log_err(knet_h, KNET_SUB_LINK, "Host %u link %u is currently configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	if (link->status.enabled != 0) {
 		err =-1;
 		savederrno = EBUSY;
 		log_err(knet_h, KNET_SUB_LINK, "Host %u link %u is currently in use: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	memmove(&link->src_addr, src_addr, sizeof(struct sockaddr_storage));
 
 	err = knet_addrtostr(src_addr, sizeof(struct sockaddr_storage),
 			     link->status.src_ipaddr, KNET_MAX_HOST_LEN,
 			     link->status.src_port, KNET_MAX_PORT_LEN);
 	if (err) {
 		if (err == EAI_SYSTEM) {
 			savederrno = errno;
 			log_warn(knet_h, KNET_SUB_LINK,
 				 "Unable to resolve host: %u link: %u source addr/port: %s",
 				 host_id, link_id, strerror(savederrno));
 		} else {
 			savederrno = EINVAL;
 			log_warn(knet_h, KNET_SUB_LINK,
 				 "Unable to resolve host: %u link: %u source addr/port: %s",
 				 host_id, link_id, gai_strerror(err));
 		}
 		err = -1;
 		goto exit_unlock;
 	}
 
 	if (!dst_addr) {
 		link->dynamic = KNET_LINK_DYNIP;
 	} else {
 
 		link->dynamic = KNET_LINK_STATIC;
 
 		memmove(&link->dst_addr, dst_addr, sizeof(struct sockaddr_storage));
 		err = knet_addrtostr(dst_addr, sizeof(struct sockaddr_storage),
 				     link->status.dst_ipaddr, KNET_MAX_HOST_LEN,
 				     link->status.dst_port, KNET_MAX_PORT_LEN);
 		if (err) {
 			if (err == EAI_SYSTEM) {
 				savederrno = errno;
 				log_warn(knet_h, KNET_SUB_LINK,
 					 "Unable to resolve host: %u link: %u destination addr/port: %s",
 					 host_id, link_id, strerror(savederrno));
 			} else {
 				savederrno = EINVAL;
 				log_warn(knet_h, KNET_SUB_LINK,
 					 "Unable to resolve host: %u link: %u destination addr/port: %s",
 					 host_id, link_id, gai_strerror(err));
 			}
 			err = -1;
 			goto exit_unlock;
 		}
 	}
 
 	link->pmtud_crypto_timeout_multiplier = KNET_LINK_PMTUD_CRYPTO_TIMEOUT_MULTIPLIER_MIN;
 	link->pong_count = KNET_LINK_DEFAULT_PONG_COUNT;
 	link->has_valid_mtu = 0;
 	link->ping_interval = KNET_LINK_DEFAULT_PING_INTERVAL * 1000; /* microseconds */
 	link->pong_timeout = KNET_LINK_DEFAULT_PING_TIMEOUT * 1000; /* microseconds */
 	link->pong_timeout_backoff = KNET_LINK_PONG_TIMEOUT_BACKOFF;
 	link->pong_timeout_adj = link->pong_timeout * link->pong_timeout_backoff; /* microseconds */
 	link->latency_max_samples = KNET_LINK_DEFAULT_PING_PRECISION;
 	link->latency_cur_samples = 0;
 	link->flags = flags;
 
 	savederrno = pthread_mutex_init(&link->link_stats_mutex, NULL);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to initialize link stats mutex: %s", strerror(savederrno));
 		err = -1;
 		goto exit_unlock;
 	}
 
 	if (transport_link_set_config(knet_h, link, transport) < 0) {
 		savederrno = errno;
 		err = -1;
 		goto exit_unlock;
 	}
 
 	/*
 	 * we can only configure default access lists if we know both endpoints
 	 * and the protocol uses GENERIC_ACL, otherwise the protocol has
 	 * to setup their own access lists above in transport_link_set_config.
 	 */
 	if ((transport_get_acl_type(knet_h, transport) == USE_GENERIC_ACL) &&
 	    (link->dynamic == KNET_LINK_STATIC)) {
 		log_debug(knet_h, KNET_SUB_LINK, "Configuring default access lists for host: %u link: %u socket: %d",
 			  host_id, link_id, link->outsock);
 		if ((check_add(knet_h, link->outsock, transport, -1,
 			       &link->dst_addr, &link->dst_addr,
 			       CHECK_TYPE_ADDRESS, CHECK_ACCEPT) < 0) && (errno != EEXIST)) {
 			log_warn(knet_h, KNET_SUB_LINK, "Failed to configure default access lists for host: %u link: %u", host_id, link_id);
 			savederrno = errno;
 			err = -1;
 			goto exit_unlock;
 		}
 	}
 
 	link->configured = 1;
 	log_debug(knet_h, KNET_SUB_LINK, "host: %u link: %u is configured",
 		  host_id, link_id);
 
 	if (transport == KNET_TRANSPORT_LOOPBACK) {
 		knet_h->has_loop_link = 1;
 		knet_h->loop_link = link_id;
 		host->status.reachable = 1;
 		link->status.mtu = KNET_PMTUD_SIZE_V6;
 	} else {
 		/*
 		 * calculate the minimum MTU that is safe to use,
 		 * based on RFCs and that each network device should
 		 * be able to support without any troubles
 		 */
 		if (link->dynamic == KNET_LINK_STATIC) {
 			/*
 			 * with static link we can be more precise than using
 			 * the generic calc_min_mtu()
 			 */
 			switch (link->dst_addr.ss_family) {
 				case AF_INET6:
 					link->status.mtu =  calc_max_data_outlen(knet_h, KNET_PMTUD_MIN_MTU_V6 - (KNET_PMTUD_OVERHEAD_V6 + link->proto_overhead));
 					break;
 				case AF_INET:
 					link->status.mtu =  calc_max_data_outlen(knet_h, KNET_PMTUD_MIN_MTU_V4 - (KNET_PMTUD_OVERHEAD_V4 + link->proto_overhead));
 					break;
 			}
 		} else {
 			/*
 			 * for dynamic links we start with the minimum MTU
 			 * possible and PMTUd will kick in immediately
 			 * after connection status is 1
 			 */
 			link->status.mtu =  calc_min_mtu(knet_h);
 		}
 		link->has_valid_mtu = 1;
 	}
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_get_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			 uint8_t *transport,
 			 struct sockaddr_storage *src_addr,
 			 struct sockaddr_storage *dst_addr,
 			 uint8_t *dynamic,
 			 uint64_t *flags)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!src_addr) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!dynamic) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!transport) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!flags) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	if ((link->dynamic == KNET_LINK_STATIC) && (!dst_addr)) {
 		savederrno = EINVAL;
 		err = -1;
 		goto exit_unlock;
 	}
 
 	memmove(src_addr, &link->src_addr, sizeof(struct sockaddr_storage));
 
 	*transport = link->transport;
 	*flags = link->flags;
 
 	if (link->dynamic == KNET_LINK_STATIC) {
 		*dynamic = 0;
 		memmove(dst_addr, &link->dst_addr, sizeof(struct sockaddr_storage));
 	} else {
 		*dynamic = 1;
 	}
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_clear_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 	int sock;
 	uint8_t transport;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (link->configured != 1) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	if (link->status.enabled != 0) {
 		err = -1;
 		savederrno = EBUSY;
 		log_err(knet_h, KNET_SUB_LINK, "Host %u link %u is currently in use: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	/*
 	 * remove well known access lists here.
 	 * After the transport has done clearing the config,
 	 * then we can remove any leftover access lists if the link
 	 * is no longer in use.
 	 */
 	if ((transport_get_acl_type(knet_h, link->transport) == USE_GENERIC_ACL) &&
 	    (link->dynamic == KNET_LINK_STATIC)) {
 		if ((check_rm(knet_h, link->outsock, link->transport,
 			      &link->dst_addr, &link->dst_addr,
 			      CHECK_TYPE_ADDRESS, CHECK_ACCEPT) < 0) && (errno != ENOENT)) {
 			err = -1;
 			savederrno = errno;
 			log_err(knet_h, KNET_SUB_LINK, "Host %u link %u: unable to remove default access list",
 				host_id, link_id);
 			goto exit_unlock;
 		}
 	}
 
 	/*
 	 * cache it for later as we don't know if the transport
 	 * will clear link info during clear_config.
 	 */
 	sock = link->outsock;
 	transport = link->transport;
 
 	if ((transport_link_clear_config(knet_h, link) < 0)  &&
 	    (errno != EBUSY)) {
 		savederrno = errno;
 		err = -1;
 		goto exit_unlock;
 	}
 
 	/*
 	 * remove any other access lists when the socket is no
 	 * longer in use by the transport.
 	 */
 	if ((transport_get_acl_type(knet_h, link->transport) == USE_GENERIC_ACL) &&
 	    (knet_h->knet_transport_fd_tracker[sock].transport == KNET_MAX_TRANSPORTS)) {
 		check_rmall(knet_h, sock, transport);
 	}
 
 	pthread_mutex_destroy(&link->link_stats_mutex);
 
 	memset(link, 0, sizeof(struct knet_link));
 	link->link_id = link_id;
 
 	if (knet_h->has_loop_link && host_id == knet_h->host_id && link_id == knet_h->loop_link) {
 		knet_h->has_loop_link = 0;
 		if (host->active_link_entries == 0) {
 			host->status.reachable = 0;
 		}
 	}
 
 	log_debug(knet_h, KNET_SUB_LINK, "host: %u link: %u config has been wiped",
 		  host_id, link_id);
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_set_enable(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			 unsigned int enabled)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (enabled > 1) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	if (link->status.enabled == enabled) {
 		err = 0;
 		goto exit_unlock;
 	}
 
 	err = _link_updown(knet_h, host_id, link_id, enabled, link->status.connected, 0);
 	savederrno = errno;
 
 	if (enabled) {
 		goto exit_unlock;
 	}
 
 	log_debug(knet_h, KNET_SUB_LINK, "host: %u link: %u is disabled",
 		  host_id, link_id);
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_get_enable(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			 unsigned int *enabled)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!enabled) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	*enabled = link->status.enabled;
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_set_pong_count(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			     uint8_t pong_count)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (pong_count < 1) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link->pong_count = pong_count;
 
 	log_debug(knet_h, KNET_SUB_LINK,
 		  "host: %u link: %u pong count update: %u",
 		  host_id, link_id, link->pong_count);
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_get_pong_count(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			     uint8_t *pong_count)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!pong_count) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	*pong_count = link->pong_count;
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_set_ping_timers(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			      time_t interval, time_t timeout, unsigned int precision)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!interval) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!timeout) {
 		errno = ENOSYS;
 		return -1;
 	}
 
 	if (!precision) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link->ping_interval = interval * 1000; /* microseconds */
 	link->pong_timeout = timeout * 1000; /* microseconds */
 	link->latency_max_samples = precision;
 
 	log_debug(knet_h, KNET_SUB_LINK,
 		  "host: %u link: %u timeout update - interval: %llu timeout: %llu precision: %u",
 		  host_id, link_id, link->ping_interval, link->pong_timeout, precision);
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_get_ping_timers(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			      time_t *interval, time_t *timeout, unsigned int *precision)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!interval) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!timeout) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!precision) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	*interval = link->ping_interval / 1000; /* microseconds */
 	*timeout = link->pong_timeout / 1000;
 	*precision = link->latency_max_samples;
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_set_priority(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			   uint8_t priority)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 	uint8_t old_priority;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	old_priority = link->priority;
 
 	if (link->priority == priority) {
 		err = 0;
 		goto exit_unlock;
 	}
 
 	link->priority = priority;
 
 	if (_host_dstcache_update_sync(knet_h, host)) {
 		savederrno = errno;
 		log_debug(knet_h, KNET_SUB_LINK,
 			  "Unable to update link priority (host: %u link: %u priority: %u): %s",
 			  host_id, link_id, link->priority, strerror(savederrno));
 		link->priority = old_priority;
 		err = -1;
 		goto exit_unlock;
 	}
 
 	log_debug(knet_h, KNET_SUB_LINK,
 		  "host: %u link: %u priority set to: %u",
 		  host_id, link_id, link->priority);
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_get_priority(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			   uint8_t *priority)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!priority) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	*priority = link->priority;
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_get_link_list(knet_handle_t knet_h, knet_node_id_t host_id,
 			    uint8_t *link_ids, size_t *link_ids_entries)
 {
 	int savederrno = 0, err = 0, i, count = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!link_ids) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!link_ids_entries) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	for (i = 0; i < KNET_MAX_LINK; i++) {
 		link = &host->link[i];
 		if (!link->configured) {
 			continue;
 		}
 		link_ids[count] = i;
 		count++;
 	}
 
 	*link_ids_entries = count;
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_get_status(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			 struct knet_link_status *status, size_t struct_size)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!status) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	savederrno = pthread_mutex_lock(&link->link_stats_mutex);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get stats mutex lock for host %u link %u: %s",
 			host_id, link_id, strerror(savederrno));
 		err = -1;
 		goto exit_unlock;
 	}
 
 	memmove(status, &link->status, struct_size);
 
 	pthread_mutex_unlock(&link->link_stats_mutex);
 
 	/* Calculate totals - no point in doing this on-the-fly */
 	status->stats.rx_total_packets =
 		status->stats.rx_data_packets +
 		status->stats.rx_ping_packets +
 		status->stats.rx_pong_packets +
 		status->stats.rx_pmtu_packets;
 	status->stats.tx_total_packets =
 		status->stats.tx_data_packets +
 		status->stats.tx_ping_packets +
 		status->stats.tx_pong_packets +
 		status->stats.tx_pmtu_packets;
 	status->stats.rx_total_bytes =
 		status->stats.rx_data_bytes +
 		status->stats.rx_ping_bytes +
 		status->stats.rx_pong_bytes +
 		status->stats.rx_pmtu_bytes;
 	status->stats.tx_total_bytes =
 		status->stats.tx_data_bytes +
 		status->stats.tx_ping_bytes +
 		status->stats.tx_pong_bytes +
 		status->stats.tx_pmtu_bytes;
 	status->stats.tx_total_errors =
 		status->stats.tx_data_errors +
 		status->stats.tx_ping_errors +
 		status->stats.tx_pong_errors +
 		status->stats.tx_pmtu_errors;
 	status->stats.tx_total_retries =
 		status->stats.tx_data_retries +
 		status->stats.tx_ping_retries +
 		status->stats.tx_pong_retries +
 		status->stats.tx_pmtu_retries;
 
 	/* Tell the caller our full size in case they have an old version */
 	status->size = sizeof(struct knet_link_status);
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = err ? savederrno : 0;
 	return err;
 }
 
 int knet_link_add_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 		      struct sockaddr_storage *ss1,
 		      struct sockaddr_storage *ss2,
 		      check_type_t type, check_acceptreject_t acceptreject)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!ss1) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((type != CHECK_TYPE_ADDRESS) &&
 	    (type != CHECK_TYPE_MASK) &&
 	    (type != CHECK_TYPE_RANGE)) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((acceptreject != CHECK_ACCEPT) &&
 	    (acceptreject != CHECK_REJECT)) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((type != CHECK_TYPE_ADDRESS) && (!ss2)) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((type == CHECK_TYPE_RANGE) &&
 	    (ss1->ss_family != ss2->ss_family)) {
 			errno = EINVAL;
 			return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	if (link->dynamic != KNET_LINK_DYNIP) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is a point to point connection: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	err = check_add(knet_h, transport_link_get_acl_fd(knet_h, link), link->transport, -1,
 			ss1, ss2, type, acceptreject);
 	savederrno = errno;
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 
 	errno = savederrno;
 	return err;
 }
 
 int knet_link_insert_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			 int index,
 			 struct sockaddr_storage *ss1,
 			 struct sockaddr_storage *ss2,
 			 check_type_t type, check_acceptreject_t acceptreject)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!ss1) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((type != CHECK_TYPE_ADDRESS) &&
 	    (type != CHECK_TYPE_MASK) &&
 	    (type != CHECK_TYPE_RANGE)) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((acceptreject != CHECK_ACCEPT) &&
 	    (acceptreject != CHECK_REJECT)) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((type != CHECK_TYPE_ADDRESS) && (!ss2)) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((type == CHECK_TYPE_RANGE) &&
 	    (ss1->ss_family != ss2->ss_family)) {
 			errno = EINVAL;
 			return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	if (link->dynamic != KNET_LINK_DYNIP) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is a point to point connection: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	err = check_add(knet_h, transport_link_get_acl_fd(knet_h, link), link->transport, index,
 			ss1, ss2, type, acceptreject);
 	savederrno = errno;
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 
 	errno = savederrno;
 	return err;
 }
 
 int knet_link_rm_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 		     struct sockaddr_storage *ss1,
 		     struct sockaddr_storage *ss2,
 		     check_type_t type, check_acceptreject_t acceptreject)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!ss1) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((type != CHECK_TYPE_ADDRESS) &&
 	    (type != CHECK_TYPE_MASK) &&
 	    (type != CHECK_TYPE_RANGE)) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((acceptreject != CHECK_ACCEPT) &&
 	    (acceptreject != CHECK_REJECT)) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((type != CHECK_TYPE_ADDRESS) && (!ss2)) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((type == CHECK_TYPE_RANGE) &&
 	    (ss1->ss_family != ss2->ss_family)) {
 			errno = EINVAL;
 			return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	if (link->dynamic != KNET_LINK_DYNIP) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is a point to point connection: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	err = check_rm(knet_h, transport_link_get_acl_fd(knet_h, link), link->transport,
 		       ss1, ss2, type, acceptreject);
 	savederrno = errno;
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 
 	errno = savederrno;
 	return err;
 }
 
 int knet_link_clear_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id)
 {
 	int savederrno = 0, err = 0;
 	struct knet_host *host;
 	struct knet_link *link;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (link_id >= KNET_MAX_LINK) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	host = knet_h->host_index[host_id];
 	if (!host) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s",
 			host_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	link = &host->link[link_id];
 
 	if (!link->configured) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	if (link->dynamic != KNET_LINK_DYNIP) {
 		err = -1;
 		savederrno = EINVAL;
 		log_err(knet_h, KNET_SUB_LINK, "host %u link %u is a point to point connection: %s",
 			host_id, link_id, strerror(savederrno));
 		goto exit_unlock;
 	}
 
 	check_rmall(knet_h, transport_link_get_acl_fd(knet_h, link), link->transport);
 
 exit_unlock:
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 
 	errno = savederrno;
 	return err;
 }
diff --git a/libknet/links_acl_ip.c b/libknet/links_acl_ip.c
index b77eaf43..0f269ef1 100644
--- a/libknet/links_acl_ip.c
+++ b/libknet/links_acl_ip.c
@@ -1,310 +1,310 @@
 /*
  * Copyright (C) 2016-2020 Red Hat, Inc.  All rights reserved.
  *
  * Author: Christine Caulfield <ccaulfie@redhat.com>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #include "config.h"
 
 #include <errno.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <stdint.h>
 #include <string.h>
 #include <stdlib.h>
 
 #include "internals.h"
 #include "logging.h"
 #include "transports.h"
 #include "links_acl.h"
 #include "links_acl_ip.h"
 
 struct ip_acl_match_entry {
 	check_type_t type;
 	check_acceptreject_t acceptreject;
 	struct sockaddr_storage addr1; /* Actual IP address, mask top or low IP */
 	struct sockaddr_storage addr2; /* high IP address or address bitmask */
 	struct ip_acl_match_entry *next;
 };
 
 /*
  * s6_addr32 is not defined in BSD userland, only kernel.
  * definition is the same as linux and it works fine for
  * what we need.
  */
 #ifndef s6_addr32
 #define s6_addr32 __u6_addr.__u6_addr32
 #endif
 
 /*
  * IPv4 See if the address we have matches the current match entry
  */
 
 static int ip_matches_v4(struct sockaddr_storage *checkip, struct ip_acl_match_entry *match_entry)
 {
 	struct sockaddr_in *ip_to_check;
 	struct sockaddr_in *match1;
 	struct sockaddr_in *match2;
 
 	ip_to_check = (struct sockaddr_in *)checkip;
 	match1 = (struct sockaddr_in *)&match_entry->addr1;
 	match2 = (struct sockaddr_in *)&match_entry->addr2;
 
 	switch(match_entry->type) {
 	case CHECK_TYPE_ADDRESS:
 		if (ip_to_check->sin_addr.s_addr == match1->sin_addr.s_addr)
 			return 1;
 		break;
 	case CHECK_TYPE_MASK:
 		if ((ip_to_check->sin_addr.s_addr & match2->sin_addr.s_addr) ==
 		    match1->sin_addr.s_addr)
 			return 1;
 		break;
 	case CHECK_TYPE_RANGE:
 		if ((ntohl(ip_to_check->sin_addr.s_addr) >= ntohl(match1->sin_addr.s_addr)) &&
 		    (ntohl(ip_to_check->sin_addr.s_addr) <= ntohl(match2->sin_addr.s_addr)))
 			return 1;
 		break;
 
 	}
 	return 0;
 }
 
 /*
  * Compare two IPv6 addresses
  */
 
 static int ip6addr_cmp(struct in6_addr *a, struct in6_addr *b)
 {
 	uint64_t a_high, a_low;
 	uint64_t b_high, b_low;
 
 	a_high = ((uint64_t)htonl(a->s6_addr32[0]) << 32) | (uint64_t)htonl(a->s6_addr32[1]);
 	a_low  = ((uint64_t)htonl(a->s6_addr32[2]) << 32) | (uint64_t)htonl(a->s6_addr32[3]);
 
 	b_high = ((uint64_t)htonl(b->s6_addr32[0]) << 32) | (uint64_t)htonl(b->s6_addr32[1]);
 	b_low  = ((uint64_t)htonl(b->s6_addr32[2]) << 32) | (uint64_t)htonl(b->s6_addr32[3]);
 
 	if (a_high > b_high)
 		return 1;
 	if (a_high < b_high)
 		return -1;
 
 	if (a_low > b_low)
 		return 1;
 	if (a_low < b_low)
 		return -1;
 
 	return 0;
 }
 
 /*
  * IPv6 See if the address we have matches the current match entry
  */
 
 static int ip_matches_v6(struct sockaddr_storage *checkip, struct ip_acl_match_entry *match_entry)
 {
 	struct sockaddr_in6 *ip_to_check;
 	struct sockaddr_in6 *match1;
 	struct sockaddr_in6 *match2;
 	int i;
 
 	ip_to_check = (struct sockaddr_in6 *)checkip;
 	match1 = (struct sockaddr_in6 *)&match_entry->addr1;
 	match2 = (struct sockaddr_in6 *)&match_entry->addr2;
 
 	switch(match_entry->type) {
 	case CHECK_TYPE_ADDRESS:
 		if (!memcmp(ip_to_check->sin6_addr.s6_addr32, match1->sin6_addr.s6_addr32, sizeof(struct in6_addr)))
 			return 1;
 		break;
 
 	case CHECK_TYPE_MASK:
 		/*
 		 * Note that this little loop will quit early if there is a non-match so the
 		 * comparison might look backwards compared to the IPv4 one
 		 */
 		for (i=sizeof(struct in6_addr)/4-1; i>=0; i--) {
 			if ((ip_to_check->sin6_addr.s6_addr32[i] & match2->sin6_addr.s6_addr32[i]) !=
 			    match1->sin6_addr.s6_addr32[i])
 				return 0;
 		}
 		return 1;
 	case CHECK_TYPE_RANGE:
 		if ((ip6addr_cmp(&ip_to_check->sin6_addr, &match1->sin6_addr) >= 0) &&
 		    (ip6addr_cmp(&ip_to_check->sin6_addr, &match2->sin6_addr) <= 0))
 			return 1;
 		break;
 	}
 	return 0;
 }
 
 
 int ipcheck_validate(void *fd_tracker_match_entry_head, struct sockaddr_storage *checkip)
 {
 	struct ip_acl_match_entry **match_entry_head = (struct ip_acl_match_entry **)fd_tracker_match_entry_head;
 	struct ip_acl_match_entry *match_entry = *match_entry_head;
 	int (*match_fn)(struct sockaddr_storage *checkip, struct ip_acl_match_entry *match_entry);
 
-	if (checkip->ss_family == AF_INET){
+	if (checkip->ss_family == AF_INET) {
 		match_fn = ip_matches_v4;
 	} else {
 		match_fn = ip_matches_v6;
 	}
 
 	while (match_entry) {
 		if (match_fn(checkip, match_entry)) {
 			if (match_entry->acceptreject == CHECK_ACCEPT)
 				return 1;
 			else
 				return 0;
 		}
 		match_entry = match_entry->next;
 	}
 	return 0; /* Default reject */
 }
 
 /*
  * Routines to manuipulate access lists
  */
 
 void ipcheck_rmall(void *fd_tracker_match_entry_head)
 {
 	struct ip_acl_match_entry **match_entry_head = (struct ip_acl_match_entry **)fd_tracker_match_entry_head;
 	struct ip_acl_match_entry *next_match_entry;
 	struct ip_acl_match_entry *match_entry = *match_entry_head;
 
 	while (match_entry) {
 		next_match_entry = match_entry->next;
 		free(match_entry);
 		match_entry = next_match_entry;
 	}
 	*match_entry_head = NULL;
 }
 
 static struct ip_acl_match_entry *ipcheck_findmatch(struct ip_acl_match_entry **match_entry_head,
 						 struct sockaddr_storage *ss1, struct sockaddr_storage *ss2,
 						 check_type_t type, check_acceptreject_t acceptreject)
 {
 	struct ip_acl_match_entry *match_entry = *match_entry_head;
 
 	while (match_entry) {
 		if ((!memcmp(&match_entry->addr1, ss1, sizeof(struct sockaddr_storage))) &&
 		    (!memcmp(&match_entry->addr2, ss2, sizeof(struct sockaddr_storage))) &&
 		    (match_entry->type == type) &&
 		    (match_entry->acceptreject == acceptreject)) {
 			return match_entry;
 		}
 		match_entry = match_entry->next;
 	}
 
 	return NULL;
 }
 
 int ipcheck_rmip(void *fd_tracker_match_entry_head,
 		 struct sockaddr_storage *ss1, struct sockaddr_storage *ss2,
 		 check_type_t type, check_acceptreject_t acceptreject)
 {
 	struct ip_acl_match_entry **match_entry_head = (struct ip_acl_match_entry **)fd_tracker_match_entry_head;
 	struct ip_acl_match_entry *next_match_entry = NULL;
 	struct ip_acl_match_entry *rm_match_entry;
 	struct ip_acl_match_entry *match_entry = *match_entry_head;
 
 	rm_match_entry = ipcheck_findmatch(match_entry_head, ss1, ss2, type, acceptreject);
 	if (!rm_match_entry) {
 		errno = ENOENT;
 		return -1;
 	}
 
 	while (match_entry) {
 		next_match_entry = match_entry->next;
 		/*
 		 * we are removing the list head, be careful
 		 */
 		if (rm_match_entry == match_entry) {
 			*match_entry_head = next_match_entry;
 			free(match_entry);
 			break;
 		}
 		/*
 		 * the next one is the one we need to remove
 		 */
 		if (rm_match_entry == next_match_entry) {
 			match_entry->next = next_match_entry->next;
 			free(next_match_entry);
 			break;
 		}
 		match_entry = next_match_entry;
 	}
 
 	return 0;
 }
 
 int ipcheck_addip(void *fd_tracker_match_entry_head, int index,
 		  struct sockaddr_storage *ss1, struct sockaddr_storage *ss2,
 		  check_type_t type, check_acceptreject_t acceptreject)
 {
 	struct ip_acl_match_entry **match_entry_head = (struct ip_acl_match_entry **)fd_tracker_match_entry_head;
 	struct ip_acl_match_entry *new_match_entry;
 	struct ip_acl_match_entry *match_entry = *match_entry_head;
 	int i = 0;
 
 	if (ipcheck_findmatch(match_entry_head, ss1, ss2, type, acceptreject) != NULL) {
 		errno = EEXIST;
 		return -1;
 	}
 
 	new_match_entry = malloc(sizeof(struct ip_acl_match_entry));
 	if (!new_match_entry) {
 		return -1;
 	}
 
 	memmove(&new_match_entry->addr1, ss1, sizeof(struct sockaddr_storage));
 	memmove(&new_match_entry->addr2, ss2, sizeof(struct sockaddr_storage));
 	new_match_entry->type = type;
 	new_match_entry->acceptreject = acceptreject;
 	new_match_entry->next = NULL;
 
 	if (match_entry) {
 		/*
 		 * special case for index 0, since we need to update
 		 * the head of the list
 		 */
 		if (index == 0) {
 			*match_entry_head = new_match_entry;
 			new_match_entry->next = match_entry;
 		} else {
 			/*
 			 * find the end of the list or stop at "index"
 			 */
 
 			while (match_entry->next) {
 				match_entry = match_entry->next;
 				if (i == index) {
 					break;
 				}
 				i++;
 			}
 
 			/*
 			 * insert if there are more entries in the list
 			 */
 			if (match_entry->next) {
 				new_match_entry->next = match_entry->next;
 			}
 			/*
 			 * add if we are at the end
 			 */
 			match_entry->next = new_match_entry;
 		}
 	} else {
 		/*
 		 * first entry in the list
 		 */
 		*match_entry_head = new_match_entry;
 	}
 
 	return 0;
 }
diff --git a/libknet/netutils.h b/libknet/netutils.h
index e668ada6..ee10b2b1 100644
--- a/libknet/netutils.h
+++ b/libknet/netutils.h
@@ -1,19 +1,19 @@
 /*
  * Copyright (C) 2010-2020 Red Hat, Inc.  All rights reserved.
  *
  * Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
  *          Federico Simoncelli <fsimon@kronosnet.org>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #ifndef __KNET_NETUTILS_H__
 #define __KNET_NETUTILS_H__
 
 #include <sys/socket.h>
 
 int cmpaddr(const struct sockaddr_storage *ss1, socklen_t sslen1, const struct sockaddr_storage *ss2, socklen_t sslen2);
 int cpyaddrport(struct sockaddr_storage *dst, const struct sockaddr_storage *src);
 
-socklen_t knet_sockaddr_len(const struct sockaddr_storage *ss);
+socklen_t sockaddr_len(const struct sockaddr_storage *ss);
 #endif
diff --git a/libknet/tests/Makefile.am b/libknet/tests/Makefile.am
index 37f5935f..86312072 100644
--- a/libknet/tests/Makefile.am
+++ b/libknet/tests/Makefile.am
@@ -1,104 +1,104 @@
 #
 # Copyright (C) 2016-2020 Red Hat, Inc.  All rights reserved.
 #
 # Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
 #
 # This software licensed under GPL-2.0+
 #
 
 MAINTAINERCLEANFILES	= Makefile.in
 
 include $(top_srcdir)/build-aux/check.mk
 include $(top_srcdir)/libknet/tests/api-check.mk
 
 EXTRA_DIST		= \
 			  api-test-coverage \
 			  api-check.mk
 
 AM_CPPFLAGS		= -I$(top_srcdir)/libknet
-AM_CFLAGS		+= $(PTHREAD_CFLAGS)
+AM_CFLAGS		+= $(PTHREAD_CFLAGS) $(libqb_CFLAGS)
 LIBS			= $(top_builddir)/libknet/libknet.la \
 			  $(PTHREAD_LIBS) $(dl_LIBS)
 
 noinst_HEADERS		= \
 			  test-common.h
 
 # the order of those tests is NOT random.
 # some functions can only be tested properly after some dependents
 # API have been validated upfront.
 
 check_PROGRAMS		= \
 			  $(api_checks) \
 			  $(int_checks) \
 			  $(fun_checks)
 
 int_checks		= \
 			  int_links_acl_ip_test \
 			  int_timediff_test
 
 fun_checks		=
 
 # checks below need to be executed manually
 # or with a specifi environment
 
 long_run_checks		= \
 			  fun_pmtud_crypto_test
 
 benchmarks		= \
 			  knet_bench_test
 
 noinst_PROGRAMS		= \
 			  api_knet_handle_new_limit_test \
 			  pckt_test \
 			  $(benchmarks) \
 			  $(long_run_checks) \
 			  $(check_PROGRAMS)
 
 noinst_SCRIPTS		= \
 			  api-test-coverage
 
 TESTS			= $(check_PROGRAMS)
 
 if INSTALL_TESTS
 testsuitedir		= $(TESTDIR)
 testsuite_PROGRAMS	= $(noinst_PROGRAMS)
 endif
 
 check-local: check-api-test-coverage
 
 check-api-test-coverage:
 	chmod u+x $(top_srcdir)/libknet/tests/api-test-coverage
 	$(top_srcdir)/libknet/tests/api-test-coverage $(top_srcdir) $(top_builddir)
 
 pckt_test_SOURCES	= pckt_test.c
 
 int_links_acl_ip_test_SOURCES = int_links_acl_ip.c \
 				../common.c \
 				../compat.c \
 				../logging.c \
 				../netutils.c \
 				../threads_common.c \
 				../onwire.c \
 				../transports.c \
 				../transport_common.c \
 				../transport_loopback.c \
 				../transport_sctp.c \
 				../transport_udp.c \
 				../links_acl.c \
 				../links_acl_ip.c \
 				../links_acl_loopback.c
 
 int_timediff_test_SOURCES = int_timediff.c
 
 knet_bench_test_SOURCES	= knet_bench.c \
 			  test-common.c \
 			  ../common.c \
 			  ../logging.c \
 			  ../compat.c \
 			  ../transport_common.c \
 			  ../threads_common.c \
 			  ../onwire.c
 
 fun_pmtud_crypto_test_SOURCES = fun_pmtud_crypto.c \
 				test-common.c \
 				../onwire.c
diff --git a/libknet/tests/test-common.c b/libknet/tests/test-common.c
index 481d7fe0..b3cf9638 100644
--- a/libknet/tests/test-common.c
+++ b/libknet/tests/test-common.c
@@ -1,585 +1,586 @@
 /*
  * Copyright (C) 2016-2020 Red Hat, Inc.  All rights reserved.
  *
  * Author: Fabio M. Di Nitto <fabbione@kronosnet.org>
  *
  * This software licensed under GPL-2.0+
  */
 
 #include "config.h"
 
 #include <errno.h>
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <fcntl.h>
 #include <pthread.h>
 #include <sys/select.h>
 
 #include "libknet.h"
 #include "test-common.h"
 
 static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
 static int log_init = 0;
 static pthread_mutex_t log_thread_mutex = PTHREAD_MUTEX_INITIALIZER;
 static pthread_t log_thread;
 static int log_thread_init = 0;
 static int log_fds[2];
 struct log_thread_data {
 	int logfd;
 	FILE *std;
 };
 static struct log_thread_data data;
 static pthread_mutex_t shutdown_mutex = PTHREAD_MUTEX_INITIALIZER;
 static int stop_in_progress = 0;
 
 static int _read_pipe(int fd, char **file, size_t *length)
 {
 	char buf[4096];
 	int n;
 	int done = 0;
 
 	*file = NULL;
 	*length = 0;
 
 	memset(buf, 0, sizeof(buf));
 
 	while (!done) {
 
 		n = read(fd, buf, sizeof(buf));
 
 		if (n < 0) {
 			if (errno == EINTR)
 				continue;
 
 			if (*file)
 				free(*file);
 
 			return n;
 		}
 
 		if (n == 0 && (!*length))
 			return 0;
 
 		if (n == 0)
 			done = 1;
 
 		if (*file)
 			*file = realloc(*file, (*length) + n + done);
 		else
 			*file = malloc(n + done);
 
 		if (!*file)
 			return -1;
 
 		memmove((*file) + (*length), buf, n);
 		*length += (done + n);
 	}
 
 	/* Null terminator */
 	(*file)[(*length) - 1] = 0;
 
 	return 0;
 }
 
 int execute_shell(const char *command, char **error_string)
 {
 	pid_t pid;
 	int status, err = 0;
 	int fd[2];
 	size_t size = 0;
 
 	if ((command == NULL) || (!error_string)) {
 		errno = EINVAL;
 		return FAIL;
 	}
 
 	*error_string = NULL;
 
 	err = pipe(fd);
 	if (err)
 		goto out_clean;
 
 	pid = fork();
 	if (pid < 0) {
 		err = pid;
 		goto out_clean;
 	}
 
 	if (pid) { /* parent */
 
 		close(fd[1]);
 		err = _read_pipe(fd[0], error_string, &size);
 		if (err)
 			goto out_clean0;
 
 		waitpid(pid, &status, 0);
 		if (!WIFEXITED(status)) {
 			err = -1;
 			goto out_clean0;
 		}
 		if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
 			err = WEXITSTATUS(status);
 			goto out_clean0;
 		}
 		goto out_clean0;
 	} else { /* child */
 		close(0);
 		close(1);
 		close(2);
 
 		close(fd[0]);
 		dup2(fd[1], 1);
 		dup2(fd[1], 2);
 		close(fd[1]);
 
 		execlp("/bin/sh", "/bin/sh", "-c", command, NULL);
 		exit(FAIL);
 	}
 
 out_clean:
 	close(fd[1]);
 out_clean0:
 	close(fd[0]);
 
 	return err;
 }
 
 int is_memcheck(void)
 {
 	char *val;
 
 	val = getenv("KNETMEMCHECK");
 
 	if (val) {
 		if (!strncmp(val, "yes", 3)) {
 			return 1;
 		}
 	}
 
 	return 0;
 }
 
 int is_helgrind(void)
 {
 	char *val;
 
 	val = getenv("KNETHELGRIND");
 
 	if (val) {
 		if (!strncmp(val, "yes", 3)) {
 			return 1;
 		}
 	}
 
 	return 0;
 }
 
 void set_scheduler(int policy)
 {
 	struct sched_param sched_param;
 	int err;
 
 	err = sched_get_priority_max(policy);
 	if (err < 0) {
 		printf("Could not get maximum scheduler priority\n");
 		exit(FAIL);
 	}
 	sched_param.sched_priority = err;
 	err = sched_setscheduler(0, policy, &sched_param);
 	if (err < 0) {
 		printf("Could not set priority\n");
 		exit(FAIL);
 	}
 	return;
 }
 
 int setup_logpipes(int *logfds)
 {
 	if (pipe2(logfds, O_CLOEXEC | O_NONBLOCK) < 0) {
 		printf("Unable to setup logging pipe\n");
 		exit(FAIL);
 	}
 
 	return PASS;
 }
 
 void close_logpipes(int *logfds)
 {
 	close(logfds[0]);
 	logfds[0] = 0;
 	close(logfds[1]);
 	logfds[1] = 0;
 }
 
 void flush_logs(int logfd, FILE *std)
 {
 	struct knet_log_msg msg;
 	int len;
 
 	while (1) {
 		len = read(logfd, &msg, sizeof(msg));
 		if (len != sizeof(msg)) {
 			/*
 			 * clear errno to avoid incorrect propagation
 			 */
 			errno = 0;
 			return;
 		}
 
 		msg.msg[sizeof(msg.msg) - 1] = 0;
 
 		fprintf(std, "[knet]: [%s] %s: %.*s\n",
 			knet_log_get_loglevel_name(msg.msglevel),
 			knet_log_get_subsystem_name(msg.subsystem),
 			KNET_MAX_LOG_MSG_SIZE, msg.msg);
 	}
 }
 
 static void *_logthread(void *args)
 {
 	while (1) {
 		int num;
 		struct timeval tv = { 60, 0 };
 		fd_set rfds;
 
 		FD_ZERO(&rfds);
 		FD_SET(data.logfd, &rfds);
 
 		num = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
 		if (num < 0) {
 			fprintf(data.std, "Unable select over logfd!\nHALTING LOGTHREAD!\n");
 			return NULL;
 		}
 		if (num == 0) {
 			fprintf(data.std, "[knet]: No logs in the last 60 seconds\n");
 			continue;
 		}
 		if (FD_ISSET(data.logfd, &rfds)) {
 			flush_logs(data.logfd, data.std);
 		}
 	}
 }
 
 int start_logthread(int logfd, FILE *std)
 {
 	int savederrno = 0;
 
 	savederrno = pthread_mutex_lock(&log_thread_mutex);
 	if (savederrno) {
 		printf("Unable to get log_thread mutex lock\n");
 		return -1;
 	}
 
 	if (!log_thread_init) {
 		data.logfd = logfd;
 		data.std = std;
 
 		savederrno = pthread_create(&log_thread, 0, _logthread, NULL);
 		if (savederrno) {
 			printf("Unable to start logging thread: %s\n", strerror(savederrno));
 			pthread_mutex_unlock(&log_thread_mutex);
 			return -1;
 		}
 		log_thread_init = 1;
 	}
 
 	pthread_mutex_unlock(&log_thread_mutex);
 	return 0;
 }
 
 int stop_logthread(void)
 {
 	int savederrno = 0;
 	void *retval;
 
 	savederrno = pthread_mutex_lock(&log_thread_mutex);
 	if (savederrno) {
 		printf("Unable to get log_thread mutex lock\n");
 		return -1;
 	}
 
 	if (log_thread_init) {
 		pthread_cancel(log_thread);
 		pthread_join(log_thread, &retval);
 		log_thread_init = 0;
 	}
 
 	pthread_mutex_unlock(&log_thread_mutex);
 	return 0;
 }
 
 static void stop_logging(void)
 {
 	stop_logthread();
 	flush_logs(log_fds[0], stdout);
 	close_logpipes(log_fds);
 }
 
 int start_logging(FILE *std)
 {
 	int savederrno = 0;
 
 	savederrno = pthread_mutex_lock(&log_mutex);
 	if (savederrno) {
 		printf("Unable to get log_mutex lock\n");
 		return -1;
 	}
 
 	if (!log_init) {
 		setup_logpipes(log_fds);
 
 		if (atexit(&stop_logging) != 0) {
 			printf("Unable to register atexit handler to stop logging: %s\n",
 			       strerror(errno));
 			exit(FAIL);
 		}
 
 		if (start_logthread(log_fds[0], std) < 0) {
 			exit(FAIL);
 		}
 
 		log_init = 1;
 	}
 
 	pthread_mutex_unlock(&log_mutex);
 
 	return log_fds[1];
 }
 
 knet_handle_t knet_handle_start(int logfds[2], uint8_t log_level)
 {
 	knet_handle_t knet_h = knet_handle_new_ex(1, logfds[1], log_level, 0);
 
 	if (knet_h) {
 		return knet_h;
 	} else {
 		printf("knet_handle_new failed: %s\n", strerror(errno));
 		flush_logs(logfds[0], stdout);
 		close_logpipes(logfds);
 		exit(FAIL);
 	}
 }
 
 int knet_handle_stop(knet_handle_t knet_h)
 {
 	int savederrno;
 	size_t i, j;
 	knet_node_id_t host_ids[KNET_MAX_HOST];
 	uint8_t link_ids[KNET_MAX_LINK];
 	size_t host_ids_entries = 0, link_ids_entries = 0;
 	struct knet_link_status status;
 
 	savederrno = pthread_mutex_lock(&shutdown_mutex);
 	if (savederrno) {
 		printf("Unable to get shutdown mutex lock\n");
 		return -1;
 	}
 
 	if (stop_in_progress) {
 		pthread_mutex_unlock(&shutdown_mutex);
 		errno = EINVAL;
 		return -1;
 	}
 
 	stop_in_progress = 1;
 
 	pthread_mutex_unlock(&shutdown_mutex);
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (knet_handle_setfwd(knet_h, 0) < 0) {
 		printf("knet_handle_setfwd failed: %s\n", strerror(errno));
 		return -1;
 	}
 
 	if (knet_host_get_host_list(knet_h, host_ids, &host_ids_entries) < 0) {
 		printf("knet_host_get_host_list failed: %s\n", strerror(errno));
 		return -1;
 	}
 
 	for (i = 0; i < host_ids_entries; i++) {
 		if (knet_link_get_link_list(knet_h, host_ids[i], link_ids, &link_ids_entries)) {
 			printf("knet_link_get_link_list failed: %s\n", strerror(errno));
 			return -1;
 		}
 		for (j = 0; j < link_ids_entries; j++) {
 			if (knet_link_get_status(knet_h, host_ids[i], link_ids[j], &status, sizeof(struct knet_link_status))) {
 				printf("knet_link_get_status failed: %s\n", strerror(errno));
 				return -1;
 			}
 			if (status.enabled) {
 				if (knet_link_set_enable(knet_h, host_ids[i], j, 0)) {
 					printf("knet_link_set_enable failed: %s\n", strerror(errno));
 					return -1;
 				}
 			}
 			knet_link_clear_config(knet_h, host_ids[i], j);
 		}
 		if (knet_host_remove(knet_h, host_ids[i]) < 0) {
 			printf("knet_host_remove failed: %s\n", strerror(errno));
 			return -1;
 		}
 	}
 
 	if (knet_handle_free(knet_h)) {
 		printf("knet_handle_free failed: %s\n", strerror(errno));
 		return -1;
 	}
 	return 0;
 }
 
 static int _make_local_sockaddr(struct sockaddr_storage *lo, int offset, int family)
 {
 	in_port_t port;
 	char portstr[32];
 
 	if (offset < 0) {
 		/*
 		 * api_knet_link_set_config needs to access the API directly, but
 		 * it does not send any traffic, so it“s safe to ask the kernel
 		 * for a random port.
 		 */
 		port = 0;
 	} else {
 		/* Use the pid if we can. but makes sure its in a sensible range */
 		port = (getpid() + offset) % (65536-1024) + 1024;
 	}
 	sprintf(portstr, "%u", port);
 	memset(lo, 0, sizeof(struct sockaddr_storage));
 	printf("Using port %u\n", port);
 
 	if (family == AF_INET6) {
 		return knet_strtoaddr("::1", portstr, lo, sizeof(struct sockaddr_storage));
 	}
 	return knet_strtoaddr("127.0.0.1", portstr, lo, sizeof(struct sockaddr_storage));
 }
 
 int make_local_sockaddr(struct sockaddr_storage *lo, int offset)
 {
 	return _make_local_sockaddr(lo, offset, AF_INET);
 }
 
 int make_local_sockaddr6(struct sockaddr_storage *lo, int offset)
 {
 	return _make_local_sockaddr(lo, offset, AF_INET6);
 }
 
 int _knet_link_set_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id,
 			  uint8_t transport, uint64_t flags, int family, int dynamic,
 			  struct sockaddr_storage *lo)
 {
 	int err = 0, savederrno = 0;
 	uint32_t port;
 	char portstr[32];
 
 	for (port = 1025; port < 65536; port++) {
 		sprintf(portstr, "%u", port);
 		memset(lo, 0, sizeof(struct sockaddr_storage));
 		if (family == AF_INET6) {
 			err = knet_strtoaddr("::1", portstr, lo, sizeof(struct sockaddr_storage));
 		} else {
 			err = knet_strtoaddr("127.0.0.1", portstr, lo, sizeof(struct sockaddr_storage));
 		}
 		if (err < 0) {
 			printf("Unable to convert loopback to sockaddr: %s\n", strerror(errno));
 			goto out;
 		}
 		errno = 0;
 		if (dynamic) {
 			err = knet_link_set_config(knet_h, host_id, link_id, transport, lo, NULL, flags);
 		} else {
 			err = knet_link_set_config(knet_h, host_id, link_id, transport, lo, lo, flags);
 		}
 		savederrno = errno;
 		if ((err < 0)  && (savederrno != EADDRINUSE)) {
 			printf("Unable to configure link: %s\n", strerror(savederrno));
 			goto out;
 		}
 		if (!err) {
 			printf("Using port %u\n", port);
 			goto out;
 		}
 	}
 
 	if (err) {
 		printf("No more ports available\n");
 	}
 out:
 	errno = savederrno;
 	return err;
 }
 
 void test_sleep(knet_handle_t knet_h, int seconds)
 {
 	if (is_memcheck() || is_helgrind()) {
 		printf("Test suite is running under valgrind, adjusting sleep timers\n");
 		seconds = seconds * 16;
 	}
 	sleep(seconds);
 }
 
 int wait_for_host(knet_handle_t knet_h, uint16_t host_id, int seconds, int logfd, FILE *std)
 {
 	int i = 0;
 
 	if (is_memcheck() || is_helgrind()) {
 		printf("Test suite is running under valgrind, adjusting wait_for_host timeout\n");
 		seconds = seconds * 16;
 	}
 
 	while (i < seconds) {
 		flush_logs(logfd, std);
 		if (knet_h->host_index[host_id]->status.reachable == 1) {
 			printf("Waiting for host to settle\n");
 			test_sleep(knet_h, 1);
 			return 0;
 		}
 		printf("waiting host %u to be reachable for %d more seconds\n", host_id, seconds - i);
 		sleep(1);
 		i++;
 	}
 	return -1;
 }
 
 int wait_for_packet(knet_handle_t knet_h, int seconds, int datafd, int logfd, FILE *std)
 {
 	fd_set rfds;
 	struct timeval tv;
 	int err = 0, i = 0;
 
 	if (is_memcheck() || is_helgrind()) {
 		printf("Test suite is running under valgrind, adjusting wait_for_packet timeout\n");
 		seconds = seconds * 16;
 	}
 
 try_again:
 	FD_ZERO(&rfds);
 	FD_SET(datafd, &rfds);
 
 	tv.tv_sec = 1;
 	tv.tv_usec = 0;
 
 	err = select(datafd+1, &rfds, NULL, NULL, &tv);
 	/*
 	 * on slow arches the first call to select can return 0.
 	 * pick an arbitrary 10 times loop (multiplied by waiting seconds)
 	 * before failing.
 	 */
 	if ((!err) && (i < seconds)) {
 		flush_logs(logfd, std);
 		i++;
 		goto try_again;
 	}
 	if ((err > 0) && (FD_ISSET(datafd, &rfds))) {
 		return 0;
 	}
 
+	errno = ETIMEDOUT;
 	return -1;
 }
diff --git a/libknet/transport_loopback.c b/libknet/transport_loopback.c
index b36fed20..dfd6384d 100644
--- a/libknet/transport_loopback.c
+++ b/libknet/transport_loopback.c
@@ -1,80 +1,85 @@
 /*
  * Copyright (C) 2017-2020 Red Hat, Inc.  All rights reserved.
  *
  * Author: Christine Caulfield <ccaulfie@redhat.com>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #include "config.h"
 
 #include <string.h>
 #include <unistd.h>
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <stdlib.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
 #include <netinet/ip_icmp.h>
 
 #include "libknet.h"
 #include "compat.h"
 #include "host.h"
 #include "link.h"
 #include "logging.h"
 #include "common.h"
 #include "transports.h"
 #include "transport_loopback.h"
 #include "threads_common.h"
 
 /* This is just a file of empty calls as the actual loopback is in threads_tx.c as a special case
    when receiving a packet from the localhost */
 
 
 int loopback_transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	kn_link->transport_connected = 1;
 	kn_link->status.connected = 1;
 	return 0;
 }
 
 int loopback_transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	return 0;
 }
 
 int loopback_transport_free(knet_handle_t knet_h)
 {
 	return 0;
 }
 
 int loopback_transport_init(knet_handle_t knet_h)
 {
 	return 0;
 }
 
 int loopback_transport_rx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno)
 {
 	return 0;
 }
 
 int loopback_transport_tx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno)
 {
 	return 0;
 }
 
 int loopback_transport_rx_is_data(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg)
 {
 	return 0;
 }
 
 int loopback_transport_link_dyn_connect(knet_handle_t knet_h, int sockfd, struct knet_link *kn_link)
 {
 	return 0;
 }
 
 int loopback_transport_link_get_acl_fd(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	return 0;
 }
+
+int loopback_transport_link_is_down(knet_handle_t knet_h, struct knet_link *kn_link)
+{
+	return 0;
+}
diff --git a/libknet/transport_loopback.h b/libknet/transport_loopback.h
index 0cb7bb37..636034bb 100644
--- a/libknet/transport_loopback.h
+++ b/libknet/transport_loopback.h
@@ -1,28 +1,29 @@
 /*
  * Copyright (C) 2017-2020 Red Hat, Inc.  All rights reserved.
  *
  * Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #include "config.h"
 
 #include "internals.h"
 
 #ifndef __KNET_TRANSPORT_LOOPBACK_H__
 #define __KNET_TRANSPORT_LOOPBACK_H__
 
 #define KNET_PMTUD_LOOPBACK_OVERHEAD 0
 
 int loopback_transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link);
 int loopback_transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link);
 int loopback_transport_free(knet_handle_t knet_h);
 int loopback_transport_init(knet_handle_t knet_h);
 int loopback_transport_rx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno);
 int loopback_transport_tx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno);
 int loopback_transport_rx_is_data(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg);
 int loopback_transport_link_dyn_connect(knet_handle_t knet_h, int sockfd, struct knet_link *kn_link);
 int loopback_transport_link_get_acl_fd(knet_handle_t knet_h, struct knet_link *kn_link);
+int loopback_transport_link_is_down(knet_handle_t knet_h, struct knet_link *kn_link);
 
 #endif
diff --git a/libknet/transport_sctp.c b/libknet/transport_sctp.c
index 667d80cf..17e2f542 100644
--- a/libknet/transport_sctp.c
+++ b/libknet/transport_sctp.c
@@ -1,1562 +1,1602 @@
 /*
  * Copyright (C) 2016-2020 Red Hat, Inc.  All rights reserved.
  *
  * Author: Christine Caulfield <ccaulfie@redhat.com>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #include "config.h"
 
 #include <string.h>
 #include <unistd.h>
 #include <errno.h>
 #include <pthread.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <stdlib.h>
 #include <assert.h>
 
 #include "compat.h"
 #include "host.h"
 #include "links.h"
 #include "links_acl.h"
 #include "links_acl_ip.h"
 #include "logging.h"
+#include "netutils.h"
 #include "common.h"
 #include "transport_common.h"
 #include "transports.h"
 #include "threads_common.h"
 
 #ifdef HAVE_NETINET_SCTP_H
 #include <netinet/sctp.h>
 #include "transport_sctp.h"
 
 typedef struct sctp_handle_info {
-	struct knet_list_head listen_links_list;
-	struct knet_list_head connect_links_list;
+	struct qb_list_head listen_links_list;
+	struct qb_list_head connect_links_list;
 	int connect_epollfd;
 	int connectsockfd[2];
 	int listen_epollfd;
 	int listensockfd[2];
 	pthread_t connect_thread;
 	pthread_t listen_thread;
 	socklen_t event_subscribe_kernel_size;
 	char *event_subscribe_buffer;
 } sctp_handle_info_t;
 
 /*
  * use by fd_tracker data type
  */
 #define SCTP_NO_LINK_INFO       0
 #define SCTP_LISTENER_LINK_INFO 1
 #define SCTP_ACCEPTED_LINK_INFO 2
 #define SCTP_CONNECT_LINK_INFO  3
 
 /*
  * this value is per listener
  */
 #define MAX_ACCEPTED_SOCKS 256
 
 typedef struct sctp_listen_link_info {
-	struct knet_list_head list;
+	struct qb_list_head list;
 	int listen_sock;
 	int accepted_socks[MAX_ACCEPTED_SOCKS];
 	struct sockaddr_storage src_address;
 	int on_listener_epoll;
 	int on_rx_epoll;
 	int sock_shutdown;
 } sctp_listen_link_info_t;
 
 typedef struct sctp_accepted_link_info {
 	char mread_buf[KNET_DATABUFSIZE];
 	ssize_t mread_len;
 	sctp_listen_link_info_t *link_info;
 } sctp_accepted_link_info_t ;
 
 typedef struct sctp_connect_link_info {
-	struct knet_list_head list;
+	struct qb_list_head list;
 	sctp_listen_link_info_t *listener;
 	struct knet_link *link;
 	struct sockaddr_storage dst_address;
 	int connect_sock;
 	int on_rx_epoll;
 	int close_sock;
 	int sock_shutdown;
 } sctp_connect_link_info_t;
 
 /*
  * socket handling functions
  *
  * those functions do NOT perform locking. locking
  * should be handled in the right context from callers
  */
 
 /*
  * sockets are removed from rx_epoll from callers
  * see also error handling functions
  */
 static int _close_connect_socket(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	int err = 0, savederrno = 0;
 	struct epoll_event ev;
 	sctp_connect_link_info_t *info = kn_link->transport_link;
 
 	if (info->connect_sock != -1) {
 		if (info->on_rx_epoll) {
 			memset(&ev, 0, sizeof(struct epoll_event));
 			ev.events = EPOLLIN;
 			ev.data.fd = info->connect_sock;
 			if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_DEL, info->connect_sock, &ev)) {
 				savederrno = errno;
 				err = -1;
 				log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to remove connected socket from epoll pool: %s",
 				strerror(savederrno));
 				goto exit_error;
 			}
 			info->on_rx_epoll = 0;
 		}
 
 		if (_set_fd_tracker(knet_h, info->connect_sock, KNET_MAX_TRANSPORTS, SCTP_NO_LINK_INFO, NULL) < 0) {
 			savederrno = errno;
 			err = -1;
 			log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to set fd tracker: %s",
 				strerror(savederrno));
 		} else {
 			close(info->connect_sock);
 			info->connect_sock = -1;
 		}
 	}
 
 exit_error:
 	errno = savederrno;
 	return err;
 }
 
 static int _enable_sctp_notifications(knet_handle_t knet_h, int sock, const char *type)
 {
 	int err = 0, savederrno = 0;
 	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 
 	if (setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS,
 		       handle_info->event_subscribe_buffer,
 		       handle_info->event_subscribe_kernel_size) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to enable %s events: %s",
 			type, strerror(savederrno));
 	}
 
 	errno = savederrno;
 	return err;
 }
 
 static int _configure_sctp_socket(knet_handle_t knet_h, int sock, struct sockaddr_storage *address, uint64_t flags, const char *type)
 {
 	int err = 0, savederrno = 0;
 	int value;
 	int level;
 
 #ifdef SOL_SCTP
 	level = SOL_SCTP;
 #else
 	level = IPPROTO_SCTP;
 #endif
 
 	if (_configure_transport_socket(knet_h, sock, address, flags, type) < 0) {
 		savederrno = errno;
 		err = -1;
 		goto exit_error;
 	}
 
 	value = 1;
 	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set reuseaddr on socket %d: %s",
 			sock, strerror(savederrno));
 		goto exit_error;
 	}
 
 	value = 1;
 	if (setsockopt(sock, level, SCTP_NODELAY, &value, sizeof(value)) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set sctp nodelay: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	if (_enable_sctp_notifications(knet_h, sock, type) < 0) {
 		savederrno = errno;
 		err = -1;
 	}
 
 exit_error:
 	errno = savederrno;
 	return err;
 }
 
 static int _reconnect_socket(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	int err = 0, savederrno = 0;
 	sctp_connect_link_info_t *info = kn_link->transport_link;
 
 	if (connect(info->connect_sock, (struct sockaddr *)&kn_link->dst_addr, sockaddr_len(&kn_link->dst_addr)) < 0) {
 		savederrno = errno;
 		log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "SCTP socket %d received error: %s", info->connect_sock, strerror(savederrno));
 		if ((savederrno != EALREADY) && (savederrno != EINPROGRESS) && (savederrno != EISCONN)) {
 			err = -1;
 			log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to connect SCTP socket %d: %s",
 				info->connect_sock, strerror(savederrno));
 		}
 	}
 
 	errno = savederrno;
 	return err;
 }
 
 static int _create_connect_socket(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	int err = 0, savederrno = 0;
 	struct epoll_event ev;
 	sctp_connect_link_info_t *info = kn_link->transport_link;
 	int connect_sock;
+	struct sockaddr_storage connect_addr;
 
 	connect_sock = socket(kn_link->dst_addr.ss_family, SOCK_STREAM, IPPROTO_SCTP);
 	if (connect_sock < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to create send/recv socket: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	if (_configure_sctp_socket(knet_h, connect_sock, &kn_link->dst_addr, kn_link->flags, "SCTP connect") < 0) {
 		savederrno = errno;
 		err = -1;
 		goto exit_error;
 	}
 
+	memset(&connect_addr, 0, sizeof(struct sockaddr_storage));
+	if (knet_strtoaddr(kn_link->status.src_ipaddr, "0", &connect_addr, sockaddr_len(&connect_addr)) < 0) {
+		savederrno = errno;
+		err = -1;
+		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to resolve connecting socket: %s",
+			strerror(savederrno));
+		goto exit_error;
+
+	}
+
+	if (bind(connect_sock, (struct sockaddr *)&connect_addr, sockaddr_len(&connect_addr)) < 0) {
+		savederrno = errno;
+		err = -1;
+		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to bind connecting socket: %s",
+			strerror(savederrno));
+		goto exit_error;
+	}
+
 	if (_set_fd_tracker(knet_h, connect_sock, KNET_TRANSPORT_SCTP, SCTP_CONNECT_LINK_INFO, info) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to set fd tracker: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	memset(&ev, 0, sizeof(struct epoll_event));
 	ev.events = EPOLLIN;
 	ev.data.fd = connect_sock;
 	if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_ADD, connect_sock, &ev)) {
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to add connected socket to epoll pool: %s",
 			strerror(errno));
 	}
 	info->on_rx_epoll = 1;
 
 	info->connect_sock = connect_sock;
 	info->close_sock = 0;
 	kn_link->outsock = info->connect_sock;
 
 	if (_reconnect_socket(knet_h, kn_link) < 0) {
 		savederrno = errno;
 		err = -1;
 		goto exit_error;
 	}
 
 exit_error:
 	if (err) {
 		if (connect_sock >= 0) {
 			close(connect_sock);
 		}
 	}
 	errno = savederrno;
 	return err;
 }
 
 static void _lock_sleep_relock(knet_handle_t knet_h)
 {
 	int i = 0;
 
 	/* Don't hold onto the lock while sleeping */
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 
 	while (i < 5) {
 		usleep(KNET_THREADS_TIMERES / 16);
 		if (!pthread_rwlock_rdlock(&knet_h->global_rwlock)) {
 			/*
 			 * lock acquired, we can go out
 			 */
 			return;
 		} else {
 			log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to get read lock!");
 			i++;
 		}
 	}
 	/*
 	 * time to crash! if we cannot re-acquire the lock
 	 * there is no easy way out of this one
 	 */
 	assert(0);
 }
 
 int sctp_transport_tx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno)
 {
 	sctp_connect_link_info_t *connect_info = knet_h->knet_transport_fd_tracker[sockfd].data;
 	sctp_accepted_link_info_t *accepted_info = knet_h->knet_transport_fd_tracker[sockfd].data;
 	sctp_listen_link_info_t *listen_info;
 
 	if (recv_err < 0) {
 		switch (knet_h->knet_transport_fd_tracker[sockfd].data_type) {
 			case SCTP_CONNECT_LINK_INFO:
 				if (connect_info->link->transport_connected == 0) {
 					return -1;
 				}
 				break;
 			case SCTP_ACCEPTED_LINK_INFO:
 				listen_info = accepted_info->link_info;
 				if (listen_info->listen_sock != sockfd) {
 					if (listen_info->on_rx_epoll == 0) {
 						return -1;
 					}
 				}
 				break;
 		}
 		if (recv_errno == EAGAIN) {
 #ifdef DEBUG
 			log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Sock: %d is overloaded. Slowing TX down", sockfd);
 #endif
 			_lock_sleep_relock(knet_h);
 			return 1;
 		}
 		return -1;
 	}
 	return 0;
 }
 
 /*
  * socket error management functions
  *
  * both called with global read lock.
  *
  * NOTE: we need to remove the fd from the epoll as soon as possible
  *       even before we notify the respective thread to take care of it
  *       because scheduling can make it so that this thread will overload
  *       and the threads supposed to take care of the error will never
  *       be able to take action.
  *       we CANNOT handle FDs here directly (close/reconnect/etc) due
  *       to locking context. We need to delegate that to their respective
  *       management threads within the global write lock.
  *
  * this function is called from:
  * - RX thread with recv_err <= 0 directly on recvmmsg error
  * - transport_rx_is_data when msg_len == 0 (recv_err = 1)
  * - transport_rx_is_data on notification (recv_err = 2)
  *
  * basically this small abuse of recv_err is to detect notifications
  * generated by sockets created by listen().
  */
 int sctp_transport_rx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno)
 {
 	struct epoll_event ev;
 	sctp_accepted_link_info_t *accepted_info = knet_h->knet_transport_fd_tracker[sockfd].data;
 	sctp_listen_link_info_t *listen_info;
 	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 
 	switch (knet_h->knet_transport_fd_tracker[sockfd].data_type) {
 		case SCTP_CONNECT_LINK_INFO:
 			/*
 			 * all connect link have notifications enabled
 			 * and we accept only data from notification and
 			 * generic recvmmsg errors.
 			 *
 			 * Errors generated by msg_len 0 can be ignored because
 			 * they follow a notification (double notification)
 			 */
 			if (recv_err != 1) {
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Notifying connect thread that sockfd %d received an error", sockfd);
 				if (sendto(handle_info->connectsockfd[1], &sockfd, sizeof(int), MSG_DONTWAIT | MSG_NOSIGNAL, NULL, 0) != sizeof(int)) {
 					log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to notify connect thread: %s", strerror(errno));
 				}
 			}
 			break;
 		case SCTP_ACCEPTED_LINK_INFO:
 			listen_info = accepted_info->link_info;
 			if (listen_info->listen_sock != sockfd) {
 				if (recv_err != 1) {
 					if (listen_info->on_rx_epoll) {
 						memset(&ev, 0, sizeof(struct epoll_event));
 						ev.events = EPOLLIN;
 						ev.data.fd = sockfd;
 						if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_DEL, sockfd, &ev)) {
 							log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to remove EOFed socket from epoll pool: %s",
 							strerror(errno));
 							return -1;
 						}
 						listen_info->on_rx_epoll = 0;
 					}
 					log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Notifying listen thread that sockfd %d received an error", sockfd);
 					if (sendto(handle_info->listensockfd[1], &sockfd, sizeof(int), MSG_DONTWAIT | MSG_NOSIGNAL, NULL, 0) != sizeof(int)) {
 						log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to notify listen thread: %s", strerror(errno));
 					}
 				}
 			} else {
 				/*
 				 * this means the listen() socket has generated
 				 * a notification. now what? :-)
 				 */
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Received stray notification for listen() socket %d", sockfd);
 			}
 			break;
 		default:
 			log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Received unknown notification? %d", sockfd);
 			break;
 	}
 	/*
 	 * Under RX pressure we need to give time to IPC to pick up the message
 	 */
 
 	_lock_sleep_relock(knet_h);
 	return 0;
 }
 
 /*
  * NOTE: sctp_transport_rx_is_data is called with global rdlock
  *       delegate any FD error management to sctp_transport_rx_sock_error
  *       and keep this code to parsing incoming data only
  */
 int sctp_transport_rx_is_data(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg)
 {
 	size_t i;
 	struct iovec *iov = msg->msg_hdr.msg_iov;
 	size_t iovlen = msg->msg_hdr.msg_iovlen;
 	struct sctp_assoc_change *sac;
 	union sctp_notification  *snp;
 	sctp_accepted_link_info_t *listen_info = knet_h->knet_transport_fd_tracker[sockfd].data;
 	sctp_connect_link_info_t *connect_info = knet_h->knet_transport_fd_tracker[sockfd].data;
 
 	if (!(msg->msg_hdr.msg_flags & MSG_NOTIFICATION)) {
 		if (msg->msg_len == 0) {
 			/*
 			 * NOTE: with event notification enabled, we receive error twice:
 			 *       1) from the event notification
 			 *       2) followed by a 0 byte msg_len
 			 *
 			 * the event handler should take care to avoid #2 by stopping
 			 * the rx thread from processing more packets than necessary.
 			 */
 			if (knet_h->knet_transport_fd_tracker[sockfd].data_type == SCTP_CONNECT_LINK_INFO) {
 				if (connect_info->sock_shutdown) {
 					return KNET_TRANSPORT_RX_OOB_DATA_CONTINUE;
 				}
 			} else {
 				if (listen_info->link_info->sock_shutdown) {
 					return KNET_TRANSPORT_RX_OOB_DATA_CONTINUE;
 				}
 			}
 			/*
 			 * this is pretty much dead code and we should never hit it.
 			 * keep it for safety and avoid the rx thread to process
 			 * bad info / data.
 			 */
 			return KNET_TRANSPORT_RX_NOT_DATA_STOP;
 		}
 		/*
 		 * missing MSG_EOR has to be treated as a short read
 		 * from the socket and we need to fill in the mread buf
 		 * while we wait for MSG_EOR
 		 */
 		if (!(msg->msg_hdr.msg_flags & MSG_EOR)) {
 			/*
 			 * copy the incoming data into mread_buf + mread_len (incremental)
 			 * and increase mread_len
 			 */
 			memmove(listen_info->mread_buf + listen_info->mread_len, iov->iov_base, msg->msg_len);
 			listen_info->mread_len = listen_info->mread_len + msg->msg_len;
 			return KNET_TRANSPORT_RX_NOT_DATA_CONTINUE;
 		}
 		/*
 		 * got EOR.
 		 * if mread_len is > 0 we are completing a packet from short reads
 		 * complete reassembling the packet in mread_buf, copy it back in the iov
 		 * and set the iov/msg len numbers (size) correctly
 		 */
 		if (listen_info->mread_len) {
 			/*
 			 * add last fragment to mread_buf
 			 */
 			memmove(listen_info->mread_buf + listen_info->mread_len, iov->iov_base, msg->msg_len);
 			listen_info->mread_len = listen_info->mread_len + msg->msg_len;
 			/*
 			 * move all back into the iovec
 			 */
 			memmove(iov->iov_base, listen_info->mread_buf, listen_info->mread_len);
 			msg->msg_len = listen_info->mread_len;
 			listen_info->mread_len = 0;
 		}
 		return KNET_TRANSPORT_RX_IS_DATA;
 	}
 
 	if (!(msg->msg_hdr.msg_flags & MSG_EOR)) {
 		return KNET_TRANSPORT_RX_NOT_DATA_STOP;
 	}
 
 	for (i = 0; i < iovlen; i++) {
 		snp = iov[i].iov_base;
 
 		switch (snp->sn_header.sn_type) {
 			case SCTP_ASSOC_CHANGE:
 				sac = &snp->sn_assoc_change;
 				switch (sac->sac_state) {
 					case SCTP_COMM_LOST:
 						log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] sctp assoc change socket %d: comm_lost", sockfd);
+						if (knet_h->knet_transport_fd_tracker[sockfd].data_type == SCTP_CONNECT_LINK_INFO) {
+							connect_info->close_sock = 1;
+							connect_info->link->transport_connected = 0;
+						}
 						sctp_transport_rx_sock_error(knet_h, sockfd, 2, 0);
 						return KNET_TRANSPORT_RX_OOB_DATA_STOP;
 						break;
 					case SCTP_COMM_UP:
 						log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] sctp assoc change socket %d: comm_up", sockfd);
 						if (knet_h->knet_transport_fd_tracker[sockfd].data_type == SCTP_CONNECT_LINK_INFO) {
 							connect_info->link->transport_connected = 1;
 						}
 						break;
 					case SCTP_RESTART:
 						log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] sctp assoc change socket %d: restart", sockfd);
 						break;
 					case SCTP_SHUTDOWN_COMP:
 						log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] sctp assoc change socket %d: shutdown comp", sockfd);
 						if (knet_h->knet_transport_fd_tracker[sockfd].data_type == SCTP_CONNECT_LINK_INFO) {
 							connect_info->close_sock = 1;
 						}
 						sctp_transport_rx_sock_error(knet_h, sockfd, 2, 0);
 						return KNET_TRANSPORT_RX_OOB_DATA_STOP;
 						break;
 					case SCTP_CANT_STR_ASSOC:
 						log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] sctp assoc change socket %d: cant str assoc", sockfd);
 						sctp_transport_rx_sock_error(knet_h, sockfd, 2, 0);
 						break;
 					default:
 						log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] sctp assoc change socket %d: unknown %d", sockfd, sac->sac_state);
 						break;
 				}
 				break;
 			case SCTP_SHUTDOWN_EVENT:
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] sctp shutdown event socket %d", sockfd);
 				if (knet_h->knet_transport_fd_tracker[sockfd].data_type == SCTP_CONNECT_LINK_INFO) {
 					connect_info->link->transport_connected = 0;
 					connect_info->sock_shutdown = 1;
 				} else {
 					listen_info->link_info->sock_shutdown = 1;
 				}
 				break;
 			case SCTP_SEND_FAILED:
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] sctp send failed socket: %d", sockfd);
 				break;
 			case SCTP_PEER_ADDR_CHANGE:
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] sctp peer addr change socket %d", sockfd);
 				break;
 			case SCTP_REMOTE_ERROR:
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] sctp remote error socket %d", sockfd);
 				break;
 			default:
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "[event] unknown sctp event socket: %d type: %hu", sockfd, snp->sn_header.sn_type);
 				break;
 		}
 	}
 	return KNET_TRANSPORT_RX_OOB_DATA_CONTINUE;
 }
 
+int sctp_transport_link_is_down(knet_handle_t knet_h, struct knet_link *kn_link)
+{
+	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
+	sctp_connect_link_info_t *info = kn_link->transport_link;
+
+	kn_link->transport_connected = 0;
+	info->close_sock = 1;
+
+	log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Notifying connect thread that sockfd %d received a link down event", info->connect_sock);
+	if (sendto(handle_info->connectsockfd[1], &info->connect_sock, sizeof(int), MSG_DONTWAIT | MSG_NOSIGNAL, NULL, 0) != sizeof(int)) {
+		log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to notify connect thread: %s", strerror(errno));
+	}
+
+	return 0;
+}
+
 /*
  * connect / outgoing socket management thread
  */
 
 /*
  * _handle_connected_sctp* are called with a global write lock
  * from the connect_thread
  */
 static void _handle_connected_sctp_socket(knet_handle_t knet_h, int connect_sock)
 {
 	int err;
 	unsigned int status, len = sizeof(status);
 	sctp_connect_link_info_t *info = knet_h->knet_transport_fd_tracker[connect_sock].data;
 	struct knet_link *kn_link = info->link;
 
 	if (info->close_sock) {
 		if (_close_connect_socket(knet_h, kn_link) < 0) {
 			log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to close sock %d from _handle_connected_sctp_socket: %s", connect_sock, strerror(errno));
 			return;
 		}
 		info->close_sock = 0;
 		if (_create_connect_socket(knet_h, kn_link) < 0) {
 			log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to recreate connecting sock! %s", strerror(errno));
 			return;
 		}
 	}
 
 	_reconnect_socket(knet_h, info->link);
 
 	err = getsockopt(connect_sock, SOL_SOCKET, SO_ERROR, &status, &len);
 	if (err) {
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "SCTP getsockopt() on connecting socket %d failed: %s",
 			connect_sock, strerror(errno));
 		return;
 	}
 
 	if (status) {
 		log_info(knet_h, KNET_SUB_TRANSP_SCTP, "SCTP connect on %d to %s port %s failed: %s",
 			 connect_sock, kn_link->status.dst_ipaddr, kn_link->status.dst_port,
 			 strerror(status));
 
 		/*
 		 * No need to create a new socket if connect failed,
 		 * just retry connect
 		 */
 		return;
 	}
 
 	log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "SCTP handler fd %d now connected to %s port %s",
 		  connect_sock,
 		  kn_link->status.dst_ipaddr, kn_link->status.dst_port);
 }
 
 static void _handle_connected_sctp_notifications(knet_handle_t knet_h)
 {
 	int sockfd = -1;
 	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 
 	if (recv(handle_info->connectsockfd[0], &sockfd, sizeof(int), MSG_DONTWAIT | MSG_NOSIGNAL) != sizeof(int)) {
 		log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Short read on connectsockfd");
 		return;
 	}
 
 	if (_is_valid_fd(knet_h, sockfd) < 1) {
 		log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Received stray notification for connected socket fd error");
 		return;
 	}
 
 	/*
 	 * revalidate sockfd
 	 */
 	if ((sockfd < 0) || (sockfd >= KNET_MAX_FDS)) {
 		return;
 	}
 
 	log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Processing connected error on socket: %d", sockfd);
 
 	_handle_connected_sctp_socket(knet_h, sockfd);
 }
 
 static void *_sctp_connect_thread(void *data)
 {
 	int savederrno;
 	int i, nev;
 	knet_handle_t knet_h = (knet_handle_t) data;
 	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 	struct epoll_event events[KNET_EPOLL_MAX_EVENTS];
 
 	set_thread_status(knet_h, KNET_THREAD_SCTP_CONN, KNET_THREAD_STARTED);
 
 	while (!shutdown_in_progress(knet_h)) {
 		nev = epoll_wait(handle_info->connect_epollfd, events, KNET_EPOLL_MAX_EVENTS, KNET_THREADS_TIMERES / 1000);
 
 		/*
 		 * we use timeout to detect if thread is shutting down
 		 */
 		if (nev == 0) {
 			continue;
 		}
 
 		if (nev < 0) {
 			log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "SCTP connect handler EPOLL ERROR: %s",
 				  strerror(errno));
 			continue;
 		}
 
 		/*
 		 * Sort out which FD has a connection
 		 */
 		savederrno = get_global_wrlock(knet_h);
 		if (savederrno) {
 			log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to get write lock: %s",
 				strerror(savederrno));
 			continue;
 		}
 
 		/*
 		 * minor optimization: deduplicate events
 		 *
 		 * in some cases we can receive multiple notifcations
 		 * of the same FD having issues or need handling.
 		 * It's enough to process it once even tho it's safe
 		 * to handle them multiple times.
 		 */
 		for (i = 0; i < nev; i++) {
 			if (events[i].data.fd == handle_info->connectsockfd[0]) {
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Received notification from rx_error for connected socket");
 				_handle_connected_sctp_notifications(knet_h);
 			} else {
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Received stray notification on connected sockfd %d\n", events[i].data.fd);
 			}
 		}
 		pthread_rwlock_unlock(&knet_h->global_rwlock);
 		/*
 		 * this thread can generate events for itself.
 		 * we need to sleep in between loops to allow other threads
 		 * to be scheduled
 		 */
 		usleep(knet_h->reconnect_int * 1000);
 	}
 
 	set_thread_status(knet_h, KNET_THREAD_SCTP_CONN, KNET_THREAD_STOPPED);
 
 	return NULL;
 }
 
 /*
  * listen/incoming connections management thread
  */
 
 /*
  * Listener received a new connection
  * called with a write lock from main thread
  */
 static void _handle_incoming_sctp(knet_handle_t knet_h, int listen_sock)
 {
 	int err = 0, savederrno = 0;
 	int new_fd;
 	int i = -1;
 	sctp_listen_link_info_t *info = knet_h->knet_transport_fd_tracker[listen_sock].data;
 	struct epoll_event ev;
 	struct sockaddr_storage ss;
 	socklen_t sock_len = sizeof(ss);
 	char addr_str[KNET_MAX_HOST_LEN];
 	char port_str[KNET_MAX_PORT_LEN];
 	sctp_accepted_link_info_t *accept_info = NULL;
 
 	new_fd = accept(listen_sock, (struct sockaddr *)&ss, &sock_len);
 	if (new_fd < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Incoming: accept error: %s", strerror(errno));
 		goto exit_error;
 	}
 
 	if (knet_addrtostr(&ss, sizeof(ss),
 			   addr_str, KNET_MAX_HOST_LEN,
 			   port_str, KNET_MAX_PORT_LEN) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Incoming: unable to gather socket info");
 		goto exit_error;
 	}
 
 	log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Incoming: received connection from: %s port: %s",
 						addr_str, port_str);
 	if (knet_h->use_access_lists) {
 		if (!check_validate(knet_h, listen_sock, KNET_TRANSPORT_SCTP, &ss)) {
 			savederrno = EINVAL;
 			log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Connection rejected from %s/%s", addr_str, port_str);
 			close(new_fd);
 			errno = savederrno;
 			return;
 		}
 	}
 
 	/*
 	 * Keep a track of all accepted FDs
 	 */
 	for (i=0; i<MAX_ACCEPTED_SOCKS; i++) {
 		if (info->accepted_socks[i] == -1) {
 			info->accepted_socks[i] = new_fd;
 			break;
 		}
 	}
 
 	if (i == MAX_ACCEPTED_SOCKS) {
 		errno = EBUSY;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Incoming: too many connections!");
 		goto exit_error;
 	}
 
 	if (_configure_common_socket(knet_h, new_fd, 0, "SCTP incoming") < 0) { /* Inherit flags from listener? */
 		savederrno = errno;
 		err = -1;
 		goto exit_error;
 	}
 
 	if (_enable_sctp_notifications(knet_h, new_fd, "Incoming connection") < 0) {
 		savederrno = errno;
 		err = -1;
 		goto exit_error;
 	}
 
 	accept_info = malloc(sizeof(sctp_accepted_link_info_t));
 	if (!accept_info) {
 		savederrno = errno;
 		err = -1;
 		goto exit_error;
 	}
 	memset(accept_info, 0, sizeof(sctp_accepted_link_info_t));
 
 	accept_info->link_info = info;
 
 	if (_set_fd_tracker(knet_h, new_fd, KNET_TRANSPORT_SCTP, SCTP_ACCEPTED_LINK_INFO, accept_info) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to set fd tracker: %s",
 			strerror(errno));
 		goto exit_error;
 	}
 
 	memset(&ev, 0, sizeof(struct epoll_event));
 	ev.events = EPOLLIN;
 	ev.data.fd = new_fd;
 	if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_ADD, new_fd, &ev)) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Incoming: unable to add accepted socket %d to epoll pool: %s",
 			new_fd, strerror(errno));
 		goto exit_error;
 	}
 	info->on_rx_epoll = 1;
 
 	log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Incoming: accepted new fd %d for %s/%s (listen fd: %d). index: %d",
 		  new_fd, addr_str, port_str, info->listen_sock, i);
 
 exit_error:
 	if (err) {
 		if ((i >= 0) && (i < MAX_ACCEPTED_SOCKS)) {
 			info->accepted_socks[i] = -1;
 		}
 		_set_fd_tracker(knet_h, new_fd, KNET_MAX_TRANSPORTS, SCTP_NO_LINK_INFO, NULL);
 		free(accept_info);
 		if (new_fd >= 0) {
 			close(new_fd);
 		}
 	}
 	errno = savederrno;
 	return;
 }
 
 /*
  * Listen thread received a notification of a bad socket that needs closing
  * called with a write lock from main thread
  */
 static void _handle_listen_sctp_errors(knet_handle_t knet_h)
 {
 	int sockfd = -1;
 	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 	sctp_accepted_link_info_t *accept_info;
 	sctp_listen_link_info_t *info;
 	struct knet_host *host;
 	int link_idx;
 	int i;
 
 	if (recv(handle_info->listensockfd[0], &sockfd, sizeof(int), MSG_DONTWAIT | MSG_NOSIGNAL) != sizeof(int)) {
 		log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Short read on listensockfd");
 		return;
 	}
 
 	if (_is_valid_fd(knet_h, sockfd) < 1) {
 		log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Received stray notification for listen socket fd error");
 		return;
 	}
 
 	/*
 	 * revalidate sockfd
 	 */
 	if ((sockfd < 0) || (sockfd >= KNET_MAX_FDS)) {
 		return;
 	}
 
 	log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Processing listen error on socket: %d", sockfd);
 
 	accept_info = knet_h->knet_transport_fd_tracker[sockfd].data;
 	info = accept_info->link_info;
 
 	/*
 	 * clear all links using this accepted socket as
 	 * outbound dynamically connected socket
 	 */
 
 	for (host = knet_h->host_head; host != NULL; host = host->next) {
 		for (link_idx = 0; link_idx < KNET_MAX_LINK; link_idx++) {
 			if ((host->link[link_idx].dynamic == KNET_LINK_DYNIP) &&
 			    (host->link[link_idx].outsock == sockfd)) {
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Found dynamic connection on host %d link %d (%d)",
 					  host->host_id, link_idx, sockfd);
 				host->link[link_idx].status.dynconnected = 0;
 				host->link[link_idx].transport_connected = 0;
 				host->link[link_idx].outsock = 0;
 				memset(&host->link[link_idx].dst_addr, 0, sizeof(struct sockaddr_storage));
 			}
 		}
 	}
 
 	for (i=0; i<MAX_ACCEPTED_SOCKS; i++) {
 		if (sockfd == info->accepted_socks[i]) {
 			log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Closing accepted socket %d", sockfd);
 			_set_fd_tracker(knet_h, sockfd, KNET_MAX_TRANSPORTS, SCTP_NO_LINK_INFO, NULL);
 			info->accepted_socks[i] = -1;
 			free(accept_info);
 			close(sockfd);
 			break; /* Keeps covscan happy */
 		}
 	}
 }
 
 static void *_sctp_listen_thread(void *data)
 {
 	int savederrno;
 	int i, nev;
 	knet_handle_t knet_h = (knet_handle_t) data;
 	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 	struct epoll_event events[KNET_EPOLL_MAX_EVENTS];
 
 	set_thread_status(knet_h, KNET_THREAD_SCTP_LISTEN, KNET_THREAD_STARTED);
 
 	while (!shutdown_in_progress(knet_h)) {
 		nev = epoll_wait(handle_info->listen_epollfd, events, KNET_EPOLL_MAX_EVENTS, KNET_THREADS_TIMERES / 1000);
 
 		/*
 		 * we use timeout to detect if thread is shutting down
 		 */
 		if (nev == 0) {
 			continue;
 		}
 
 		if (nev < 0) {
 			log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "SCTP listen handler EPOLL ERROR: %s",
 				  strerror(errno));
 			continue;
 		}
 
 		savederrno = get_global_wrlock(knet_h);
 		if (savederrno) {
 			log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to get write lock: %s",
 				strerror(savederrno));
 			continue;
 		}
 		/*
 		 * Sort out which FD has an incoming connection
 		 */
 		for (i = 0; i < nev; i++) {
 			if (events[i].data.fd == handle_info->listensockfd[0]) {
 				log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Received notification from rx_error for listener/accepted socket");
 				_handle_listen_sctp_errors(knet_h);
 			} else {
 				if (_is_valid_fd(knet_h, events[i].data.fd) == 1) {
 					_handle_incoming_sctp(knet_h, events[i].data.fd);
 				} else {
 					log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Received listen notification from invalid socket");
 				}
 			}
 
 		}
 		pthread_rwlock_unlock(&knet_h->global_rwlock);
 	}
 
 	set_thread_status(knet_h, KNET_THREAD_SCTP_LISTEN, KNET_THREAD_STOPPED);
 
 	return NULL;
 }
 
 /*
  * sctp_link_listener_start/stop are called in global write lock
  * context from set_config and clear_config.
  */
 static sctp_listen_link_info_t *sctp_link_listener_start(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	int err = 0, savederrno = 0;
 	int listen_sock = -1;
 	struct epoll_event ev;
 	sctp_listen_link_info_t *info = NULL;
 	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 
 	/*
 	 * Only allocate a new listener if src address is different
 	 */
-	knet_list_for_each_entry(info, &handle_info->listen_links_list, list) {
+	qb_list_for_each_entry(info, &handle_info->listen_links_list, list) {
 		if (memcmp(&info->src_address, &kn_link->src_addr, sizeof(struct sockaddr_storage)) == 0) {
 			if ((check_add(knet_h, info->listen_sock, KNET_TRANSPORT_SCTP, -1,
 				       &kn_link->dst_addr, &kn_link->dst_addr, CHECK_TYPE_ADDRESS, CHECK_ACCEPT) < 0) && (errno != EEXIST)) {
 				return NULL;
 			}
 			return info;
 		}
 	}
 
 	info = malloc(sizeof(sctp_listen_link_info_t));
 	if (!info) {
 		err = -1;
 		goto exit_error;
 	}
 
 	memset(info, 0, sizeof(sctp_listen_link_info_t));
 
 	memset(info->accepted_socks, -1, sizeof(info->accepted_socks));
 	memmove(&info->src_address, &kn_link->src_addr, sizeof(struct sockaddr_storage));
 
 	listen_sock = socket(kn_link->src_addr.ss_family, SOCK_STREAM, IPPROTO_SCTP);
 	if (listen_sock < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to create listener socket: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	if (_configure_sctp_socket(knet_h, listen_sock, &kn_link->src_addr, kn_link->flags, "SCTP listener") < 0) {
 		savederrno = errno;
 		err = -1;
 		goto exit_error;
 	}
 
 	if (bind(listen_sock, (struct sockaddr *)&kn_link->src_addr, sockaddr_len(&kn_link->src_addr)) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to bind listener socket: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	if (listen(listen_sock, 5) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to listen on listener socket: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	if (_set_fd_tracker(knet_h, listen_sock, KNET_TRANSPORT_SCTP, SCTP_LISTENER_LINK_INFO, info) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to set fd tracker: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	if ((check_add(knet_h, listen_sock, KNET_TRANSPORT_SCTP, -1,
 		       &kn_link->dst_addr, &kn_link->dst_addr, CHECK_TYPE_ADDRESS, CHECK_ACCEPT) < 0) && (errno != EEXIST)) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to configure default access lists: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	memset(&ev, 0, sizeof(struct epoll_event));
 	ev.events = EPOLLIN;
 	ev.data.fd = listen_sock;
 	if (epoll_ctl(handle_info->listen_epollfd, EPOLL_CTL_ADD, listen_sock, &ev)) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to add listener to epoll pool: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 	info->on_listener_epoll = 1;
 
 	info->listen_sock = listen_sock;
-	knet_list_add(&info->list, &handle_info->listen_links_list);
+	qb_list_add(&info->list, &handle_info->listen_links_list);
 
 	log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Listening on fd %d for %s:%s", listen_sock, kn_link->status.src_ipaddr, kn_link->status.src_port);
 
 exit_error:
 	if (err) {
 		if ((info) && (info->on_listener_epoll)) {
 			epoll_ctl(handle_info->listen_epollfd, EPOLL_CTL_DEL, listen_sock, &ev);
 		}
 		if (listen_sock >= 0) {
 			check_rmall(knet_h, listen_sock, KNET_TRANSPORT_SCTP);
 			close(listen_sock);
 		}
 		if (info) {
 			free(info);
 			info = NULL;
 		}
 	}
 	errno = savederrno;
 	return info;
 }
 
 static int sctp_link_listener_stop(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	int err = 0, savederrno = 0;
 	int found = 0, i;
 	struct knet_host *host;
 	int link_idx;
 	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 	sctp_connect_link_info_t *this_link_info = kn_link->transport_link;
 	sctp_listen_link_info_t *info = this_link_info->listener;
 	sctp_connect_link_info_t *link_info;
 	struct epoll_event ev;
 
 	for (host = knet_h->host_head; host != NULL; host = host->next) {
 		for (link_idx = 0; link_idx < KNET_MAX_LINK; link_idx++) {
 			if (&host->link[link_idx] == kn_link)
 				continue;
 
 			link_info = host->link[link_idx].transport_link;
 			if ((link_info) &&
 			    (link_info->listener == info)) {
 				found = 1;
 				break;
 			}
 		}
 	}
 
 	if ((check_rm(knet_h, info->listen_sock, KNET_TRANSPORT_SCTP,
 		      &kn_link->dst_addr, &kn_link->dst_addr, CHECK_TYPE_ADDRESS, CHECK_ACCEPT) < 0) && (errno != ENOENT)) {
 		log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to remove default access lists for %d", info->listen_sock);
 	}
 
 	if (found) {
 		this_link_info->listener = NULL;
 		log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "SCTP listener socket %d still in use", info->listen_sock);
 		savederrno = EBUSY;
 		err = -1;
 		goto exit_error;
 	}
 
 	if (info->on_listener_epoll) {
 		memset(&ev, 0, sizeof(struct epoll_event));
 		ev.events = EPOLLIN;
 		ev.data.fd = info->listen_sock;
 		if (epoll_ctl(handle_info->listen_epollfd, EPOLL_CTL_DEL, info->listen_sock, &ev)) {
 			savederrno = errno;
 			err = -1;
 			log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to remove listener to epoll pool: %s",
 				strerror(savederrno));
 			goto exit_error;
 		}
 		info->on_listener_epoll = 0;
 	}
 
 	if (_set_fd_tracker(knet_h, info->listen_sock, KNET_MAX_TRANSPORTS, SCTP_NO_LINK_INFO, NULL) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to set fd tracker: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	check_rmall(knet_h, info->listen_sock, KNET_TRANSPORT_SCTP);
 
 	close(info->listen_sock);
 
 	for (i=0; i< MAX_ACCEPTED_SOCKS; i++) {
 		if (info->accepted_socks[i] > -1) {
 			memset(&ev, 0, sizeof(struct epoll_event));
 			ev.events = EPOLLIN;
 			ev.data.fd = info->accepted_socks[i];
 			if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_DEL, info->accepted_socks[i], &ev)) {
 				log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to remove EOFed socket from epoll pool: %s",
 					strerror(errno));
 			}
 			info->on_rx_epoll = 0;
 			free(knet_h->knet_transport_fd_tracker[info->accepted_socks[i]].data);
 			close(info->accepted_socks[i]);
 			if (_set_fd_tracker(knet_h, info->accepted_socks[i], KNET_MAX_TRANSPORTS, SCTP_NO_LINK_INFO, NULL) < 0) {
 				savederrno = errno;
 				err = -1;
 				log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to set fd tracker: %s",
 					strerror(savederrno));
 				goto exit_error;
 			}
 			info->accepted_socks[i] = -1;
 		}
 	}
 
-	knet_list_del(&info->list);
+	qb_list_del(&info->list);
 	free(info);
 	this_link_info->listener = NULL;
 
 exit_error:
 	errno = savederrno;
 	return err;
 }
 
 /*
  * Links config/clear. Both called with global wrlock from link_set_config/clear_config
  */
 int sctp_transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	int savederrno = 0, err = 0;
 	sctp_connect_link_info_t *info;
 	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 
 	info = malloc(sizeof(sctp_connect_link_info_t));
 	if (!info) {
 		goto exit_error;
 	}
 
 	memset(info, 0, sizeof(sctp_connect_link_info_t));
 
 	kn_link->transport_link = info;
 	info->link = kn_link;
 
 	memmove(&info->dst_address, &kn_link->dst_addr, sizeof(struct sockaddr_storage));
 	info->connect_sock = -1;
 
 	info->listener = sctp_link_listener_start(knet_h, kn_link);
 	if (!info->listener) {
 		savederrno = errno;
 		err = -1;
 		goto exit_error;
 	}
 
 	if (kn_link->dynamic == KNET_LINK_STATIC) {
 		if (_create_connect_socket(knet_h, kn_link) < 0) {
 			savederrno = errno;
 			err = -1;
 			goto exit_error;
 		}
 		kn_link->outsock = info->connect_sock;
 	}
 
-	knet_list_add(&info->list, &handle_info->connect_links_list);
+	qb_list_add(&info->list, &handle_info->connect_links_list);
 
 exit_error:
 	if (err) {
 		if (info) {
 			if (info->connect_sock >= 0) {
 				close(info->connect_sock);
 			}
 			if (info->listener) {
 				sctp_link_listener_stop(knet_h, kn_link);
 			}
 			kn_link->transport_link = NULL;
 			free(info);
 		}
 	}
 	errno = savederrno;
 	return err;
 }
 
 /*
  * called with global wrlock
  */
 int sctp_transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	int err = 0, savederrno = 0;
 	sctp_connect_link_info_t *info;
 
 	if (!kn_link) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	info = kn_link->transport_link;
 
 	if (!info) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((sctp_link_listener_stop(knet_h, kn_link) <0) && (errno != EBUSY)) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to remove listener transport: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	if (_close_connect_socket(knet_h, kn_link) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to close connected socket: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
-	knet_list_del(&info->list);
+	qb_list_del(&info->list);
 
 	free(info);
 	kn_link->transport_link = NULL;
 
 exit_error:
 	errno = savederrno;
 	return err;
 }
 
 /*
  * transport_free and transport_init are
  * called only from knet_handle_new and knet_handle_free.
  * all resources (hosts/links) should have been already freed at this point
  * and they are called in a write locked context, hence they
  * don't need their own locking.
  */
 
 int sctp_transport_free(knet_handle_t knet_h)
 {
 	sctp_handle_info_t *handle_info;
 	void *thread_status;
 	struct epoll_event ev;
 
 	if (!knet_h->transports[KNET_TRANSPORT_SCTP]) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 
 	/*
 	 * keep it here while we debug list usage and such
 	 */
-	if (!knet_list_empty(&handle_info->listen_links_list)) {
+	if (!qb_list_empty(&handle_info->listen_links_list)) {
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Internal error. listen links list is not empty");
 	}
-	if (!knet_list_empty(&handle_info->connect_links_list)) {
+	if (!qb_list_empty(&handle_info->connect_links_list)) {
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Internal error. connect links list is not empty");
 	}
 
 	if (handle_info->listen_thread) {
 		pthread_cancel(handle_info->listen_thread);
 		pthread_join(handle_info->listen_thread, &thread_status);
 	}
 
 	if (handle_info->connect_thread) {
 		pthread_cancel(handle_info->connect_thread);
 		pthread_join(handle_info->connect_thread, &thread_status);
 	}
 
 	if (handle_info->listensockfd[0] >= 0) {
 		memset(&ev, 0, sizeof(struct epoll_event));
 		ev.events = EPOLLIN;
 		ev.data.fd = handle_info->listensockfd[0];
 		epoll_ctl(handle_info->listen_epollfd, EPOLL_CTL_DEL, handle_info->listensockfd[0], &ev);
 	}
 
 	if (handle_info->connectsockfd[0] >= 0) {
 		memset(&ev, 0, sizeof(struct epoll_event));
 		ev.events = EPOLLIN;
 		ev.data.fd = handle_info->connectsockfd[0];
 		epoll_ctl(handle_info->connect_epollfd, EPOLL_CTL_DEL, handle_info->connectsockfd[0], &ev);
 	}
 
 	_close_socketpair(knet_h, handle_info->connectsockfd);
 	_close_socketpair(knet_h, handle_info->listensockfd);
 
 	if (handle_info->listen_epollfd >= 0) {
 		close(handle_info->listen_epollfd);
 	}
 
 	if (handle_info->connect_epollfd >= 0) {
 		close(handle_info->connect_epollfd);
 	}
 
 	free(handle_info->event_subscribe_buffer);
 	free(handle_info);
 	knet_h->transports[KNET_TRANSPORT_SCTP] = NULL;
 
 	return 0;
 }
 
 static int _sctp_subscribe_init(knet_handle_t knet_h)
 {
 	int test_socket, savederrno;
 	sctp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_SCTP];
 	char dummy_events[100];
 	struct sctp_event_subscribe *events;
 	/* Below we set the first 6 fields of this expanding struct.
 	 * SCTP_EVENTS is deprecated, but SCTP_EVENT is not available
 	 * on Linux; on the other hand, FreeBSD and old Linux does not
 	 * accept small transfers, so we can't simply use this minimum
 	 * everywhere.  Thus we query and store the native size. */
 	const unsigned int subscribe_min = 6;
 
 	test_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_SCTP);
 	if (test_socket < 0) {
 		if (errno == EPROTONOSUPPORT) {
 			log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "SCTP not supported, skipping initialization");
 			return 0;
 		}
 		savederrno = errno;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to create test socket: %s",
 			strerror(savederrno));
 		return savederrno;
 	}
 	handle_info->event_subscribe_kernel_size = sizeof dummy_events;
 	if (getsockopt(test_socket, IPPROTO_SCTP, SCTP_EVENTS, &dummy_events,
 		       &handle_info->event_subscribe_kernel_size)) {
 		close(test_socket);
 		savederrno = errno;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to query kernel size of struct sctp_event_subscribe: %s",
 			strerror(savederrno));
 		return savederrno;
 	}
 	close(test_socket);
 	if (handle_info->event_subscribe_kernel_size < subscribe_min) {
 		savederrno = ERANGE;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP,
 			"No kernel support for the necessary notifications: struct sctp_event_subscribe is %u bytes, %u needed",
 			handle_info->event_subscribe_kernel_size, subscribe_min);
 		return savederrno;
 	}
 	events = malloc(handle_info->event_subscribe_kernel_size);
 	if (!events) {
 		savederrno = errno;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP,
 			"Failed to allocate event subscribe buffer: %s", strerror(savederrno));
 		return savederrno;
 	}
 	memset(events, 0, handle_info->event_subscribe_kernel_size);
 	events->sctp_data_io_event = 1;
 	events->sctp_association_event = 1;
 	events->sctp_address_event = 1;
 	events->sctp_send_failure_event = 1;
 	events->sctp_peer_error_event = 1;
 	events->sctp_shutdown_event = 1;
 	handle_info->event_subscribe_buffer = (char *)events;
 	log_debug(knet_h, KNET_SUB_TRANSP_SCTP, "Size of struct sctp_event_subscribe is %u in kernel, %zu in user space",
 		  handle_info->event_subscribe_kernel_size, sizeof(struct sctp_event_subscribe));
 	return 0;
 }
 
 int sctp_transport_init(knet_handle_t knet_h)
 {
 	int err = 0, savederrno = 0;
 	sctp_handle_info_t *handle_info;
 	struct epoll_event ev;
 
 	if (knet_h->transports[KNET_TRANSPORT_SCTP]) {
 		errno = EEXIST;
 		return -1;
 	}
 
 	handle_info = malloc(sizeof(sctp_handle_info_t));
 	if (!handle_info) {
 		return -1;
 	}
 
 	memset(handle_info, 0,sizeof(sctp_handle_info_t));
 
 	knet_h->transports[KNET_TRANSPORT_SCTP] = handle_info;
 
 	savederrno = _sctp_subscribe_init(knet_h);
 	if (savederrno) {
 		err = -1;
 		goto exit_fail;
 	}
 
-	knet_list_init(&handle_info->listen_links_list);
-	knet_list_init(&handle_info->connect_links_list);
+	qb_list_init(&handle_info->listen_links_list);
+	qb_list_init(&handle_info->connect_links_list);
 
 	handle_info->listen_epollfd = epoll_create(KNET_EPOLL_MAX_EVENTS + 1);
 	if (handle_info->listen_epollfd < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to create epoll listen fd: %s",
 			strerror(savederrno));
 		goto exit_fail;
         }
 
 	if (_fdset_cloexec(handle_info->listen_epollfd)) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to set CLOEXEC on listen_epollfd: %s",
 			strerror(savederrno));
 		goto exit_fail;
 	}
 
 	handle_info->connect_epollfd = epoll_create(KNET_EPOLL_MAX_EVENTS + 1);
         if (handle_info->connect_epollfd < 0) {
                 savederrno = errno;
 		err = -1;
                 log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to create epoll connect fd: %s",
                         strerror(savederrno));
                 goto exit_fail;
         }
 
 	if (_fdset_cloexec(handle_info->connect_epollfd)) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to set CLOEXEC on connect_epollfd: %s",
 			strerror(savederrno));
 		goto exit_fail;
 	}
 
 	if (_init_socketpair(knet_h, handle_info->connectsockfd) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to init connect socketpair: %s",
 			strerror(savederrno));
 		goto exit_fail;
 	}
 
 	memset(&ev, 0, sizeof(struct epoll_event));
 	ev.events = EPOLLIN;
 	ev.data.fd = handle_info->connectsockfd[0];
 	if (epoll_ctl(handle_info->connect_epollfd, EPOLL_CTL_ADD, handle_info->connectsockfd[0], &ev)) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to add connectsockfd[0] to connect epoll pool: %s",
 			strerror(savederrno));
 		goto exit_fail;
 	}
 
 	if (_init_socketpair(knet_h, handle_info->listensockfd) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to init listen socketpair: %s",
 			strerror(savederrno));
 		goto exit_fail;
 	}
 
 	memset(&ev, 0, sizeof(struct epoll_event));
 	ev.events = EPOLLIN;
 	ev.data.fd = handle_info->listensockfd[0];
 	if (epoll_ctl(handle_info->listen_epollfd, EPOLL_CTL_ADD, handle_info->listensockfd[0], &ev)) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to add listensockfd[0] to listen epoll pool: %s",
 			strerror(savederrno));
 		goto exit_fail;
 	}
 
 	/*
 	 * Start connect & listener threads
 	 */
 	set_thread_status(knet_h, KNET_THREAD_SCTP_LISTEN, KNET_THREAD_REGISTERED);
 	savederrno = pthread_create(&handle_info->listen_thread, 0, _sctp_listen_thread, (void *) knet_h);
 	if (savederrno) {
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to start sctp listen thread: %s",
 			strerror(savederrno));
 		goto exit_fail;
 	}
 
 	set_thread_status(knet_h, KNET_THREAD_SCTP_CONN, KNET_THREAD_REGISTERED);
 	savederrno = pthread_create(&handle_info->connect_thread, 0, _sctp_connect_thread, (void *) knet_h);
 	if (savederrno) {
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_SCTP, "Unable to start sctp connect thread: %s",
 			strerror(savederrno));
 		goto exit_fail;
 	}
 
 exit_fail:
 	if (err < 0) {
 		sctp_transport_free(knet_h);
 	}
 	errno = savederrno;
 	return err;
 }
 
 int sctp_transport_link_dyn_connect(knet_handle_t knet_h, int sockfd, struct knet_link *kn_link)
 {
 	kn_link->outsock = sockfd;
 	kn_link->status.dynconnected = 1;
 	kn_link->transport_connected = 1;
 	return 0;
 }
 
 int sctp_transport_link_get_acl_fd(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	sctp_connect_link_info_t *this_link_info = kn_link->transport_link;
 	sctp_listen_link_info_t *info = this_link_info->listener;
 	return info->listen_sock;
 }
 #endif
diff --git a/libknet/transport_sctp.h b/libknet/transport_sctp.h
index bbf7a789..3cd9740d 100644
--- a/libknet/transport_sctp.h
+++ b/libknet/transport_sctp.h
@@ -1,38 +1,39 @@
 /*
  * Copyright (C) 2017-2020 Red Hat, Inc.  All rights reserved.
  *
  * Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #include "config.h"
 
 #include "internals.h"
 
 #ifndef __KNET_TRANSPORT_SCTP_H__
 #define __KNET_TRANSPORT_SCTP_H__
 
 /*
  * https://en.wikipedia.org/wiki/SCTP_packet_structure
  */
 
 #define KNET_PMTUD_SCTP_OVERHEAD_COMMON 12
 #define KNET_PMTUD_SCTP_OVERHEAD_DATA_CHUNK 16
 #define KNET_PMTUD_SCTP_OVERHEAD KNET_PMTUD_SCTP_OVERHEAD_COMMON + KNET_PMTUD_SCTP_OVERHEAD_DATA_CHUNK
 
 #ifdef HAVE_NETINET_SCTP_H
 
 int sctp_transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link);
 int sctp_transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link);
 int sctp_transport_free(knet_handle_t knet_h);
 int sctp_transport_init(knet_handle_t knet_h);
 int sctp_transport_rx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno);
 int sctp_transport_tx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno);
 int sctp_transport_rx_is_data(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg);
 int sctp_transport_link_dyn_connect(knet_handle_t knet_h, int sockfd, struct knet_link *kn_link);
 int sctp_transport_link_get_acl_fd(knet_handle_t knet_h, struct knet_link *kn_link);
+int sctp_transport_link_is_down(knet_handle_t knet_h, struct knet_link *kn_link);
 
 #endif
 
 #endif
diff --git a/libknet/transport_udp.c b/libknet/transport_udp.c
index 2b3e4327..fe6c0968 100644
--- a/libknet/transport_udp.c
+++ b/libknet/transport_udp.c
@@ -1,439 +1,445 @@
 /*
  * Copyright (C) 2016-2020 Red Hat, Inc.  All rights reserved.
  *
  * Author: Christine Caulfield <ccaulfie@redhat.com>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #include "config.h"
 
 #include <string.h>
 #include <unistd.h>
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <stdlib.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
 #include <netinet/ip_icmp.h>
 #if defined (IP_RECVERR) || defined (IPV6_RECVERR)
 #include <linux/errqueue.h>
 #endif
 
 #include "libknet.h"
 #include "compat.h"
 #include "host.h"
 #include "link.h"
 #include "logging.h"
 #include "common.h"
+#include "netutils.h"
 #include "transport_common.h"
 #include "transport_udp.h"
 #include "transports.h"
 #include "threads_common.h"
 
 typedef struct udp_handle_info {
-	struct knet_list_head links_list;
+	struct qb_list_head links_list;
 } udp_handle_info_t;
 
 typedef struct udp_link_info {
-	struct knet_list_head list;
+	struct qb_list_head list;
 	struct sockaddr_storage local_address;
 	int socket_fd;
 	int on_epoll;
 } udp_link_info_t;
 
 int udp_transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	int err = 0, savederrno = 0;
 	int sock = -1;
 	struct epoll_event ev;
 	udp_link_info_t *info;
 	udp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_UDP];
 #if defined (IP_RECVERR) || defined (IPV6_RECVERR)
 	int value;
 #endif
 
 	/*
 	 * Only allocate a new link if the local address is different
 	 */
-	knet_list_for_each_entry(info, &handle_info->links_list, list) {
+	qb_list_for_each_entry(info, &handle_info->links_list, list) {
 		if (memcmp(&info->local_address, &kn_link->src_addr, sizeof(struct sockaddr_storage)) == 0) {
 			log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Re-using existing UDP socket for new link");
 			kn_link->outsock = info->socket_fd;
 			kn_link->transport_link = info;
 			kn_link->transport_connected = 1;
 			return 0;
 		}
 	}
 
 	info = malloc(sizeof(udp_link_info_t));
 	if (!info) {
 		err = -1;
 		goto exit_error;
 	}
 	memset(info, 0, sizeof(udp_link_info_t));
 
 	sock = socket(kn_link->src_addr.ss_family, SOCK_DGRAM, 0);
 	if (sock < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to create listener socket: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	if (_configure_transport_socket(knet_h, sock, &kn_link->src_addr, kn_link->flags, "UDP") < 0) {
 		savederrno = errno;
 		err = -1;
 		goto exit_error;
 	}
 
 #ifdef IP_RECVERR
 	if (kn_link->src_addr.ss_family == AF_INET) {
 		value = 1;
 		if (setsockopt(sock, SOL_IP, IP_RECVERR, &value, sizeof(value)) <0) {
 			savederrno = errno;
 			err = -1;
 			log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set RECVERR on socket: %s",
 				strerror(savederrno));
 			goto exit_error;
 		}
 		log_debug(knet_h, KNET_SUB_TRANSP_UDP, "IP_RECVERR enabled on socket: %i", sock);
 	}
 #else
 	log_debug(knet_h, KNET_SUB_TRANSP_UDP, "IP_RECVERR not available in this build/platform");
 #endif
 #ifdef IPV6_RECVERR
 	if (kn_link->src_addr.ss_family == AF_INET6) {
 		value = 1;
 		if (setsockopt(sock, SOL_IPV6, IPV6_RECVERR, &value, sizeof(value)) <0) {
 			savederrno = errno;
 			err = -1;
 			log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set RECVERR on socket: %s",
 				strerror(savederrno));
 			goto exit_error;
 		}
 		log_debug(knet_h, KNET_SUB_TRANSP_UDP, "IPV6_RECVERR enabled on socket: %i", sock);
 	}
 #else
 	log_debug(knet_h, KNET_SUB_TRANSP_UDP, "IPV6_RECVERR not available in this build/platform");
 #endif
 
 	if (bind(sock, (struct sockaddr *)&kn_link->src_addr, sockaddr_len(&kn_link->src_addr))) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to bind listener socket: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	memset(&ev, 0, sizeof(struct epoll_event));
 	ev.events = EPOLLIN;
 	ev.data.fd = sock;
 
 	if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_ADD, sock, &ev)) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to add listener to epoll pool: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	info->on_epoll = 1;
 
 	if (_set_fd_tracker(knet_h, sock, KNET_TRANSPORT_UDP, 0, info) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set fd tracker: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	memmove(&info->local_address, &kn_link->src_addr, sizeof(struct sockaddr_storage));
 	info->socket_fd = sock;
-	knet_list_add(&info->list, &handle_info->links_list);
+	qb_list_add(&info->list, &handle_info->links_list);
 
 	kn_link->outsock = sock;
 	kn_link->transport_link = info;
 	kn_link->transport_connected = 1;
 
 exit_error:
 	if (err) {
 		if (info) {
 			if (info->on_epoll) {
 				epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_DEL, sock, &ev);
 			}
 			free(info);
 		}
 		if (sock >= 0) {
 			close(sock);
 		}
 	}
 	errno = savederrno;
 	return err;
 }
 
 int udp_transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	int err = 0, savederrno = 0;
 	int found = 0;
 	struct knet_host *host;
 	int link_idx;
 	udp_link_info_t *info = kn_link->transport_link;
 	struct epoll_event ev;
 
 	for (host = knet_h->host_head; host != NULL; host = host->next) {
 		for (link_idx = 0; link_idx < KNET_MAX_LINK; link_idx++) {
 			if (&host->link[link_idx] == kn_link)
 				continue;
 
 			if (host->link[link_idx].transport_link == info) {
 				found = 1;
 				break;
 			}
 		}
 	}
 
 	if (found) {
 		log_debug(knet_h, KNET_SUB_TRANSP_UDP, "UDP socket %d still in use", info->socket_fd);
 		savederrno = EBUSY;
 		err = -1;
 		goto exit_error;
 	}
 
 	if (info->on_epoll) {
 		memset(&ev, 0, sizeof(struct epoll_event));
 		ev.events = EPOLLIN;
 		ev.data.fd = info->socket_fd;
 
 		if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_DEL, info->socket_fd, &ev) < 0) {
 			savederrno = errno;
 			err = -1;
 			log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to remove UDP socket from epoll poll: %s",
 				strerror(errno));
 			goto exit_error;
 		}
 		info->on_epoll = 0;
 	}
 
 	if (_set_fd_tracker(knet_h, info->socket_fd, KNET_MAX_TRANSPORTS, 0, NULL) < 0) {
 		savederrno = errno;
 		err = -1;
 		log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set fd tracker: %s",
 			strerror(savederrno));
 		goto exit_error;
 	}
 
 	close(info->socket_fd);
-	knet_list_del(&info->list);
+	qb_list_del(&info->list);
 	free(kn_link->transport_link);
 
 exit_error:
 	errno = savederrno;
 	return err;
 }
 
 int udp_transport_free(knet_handle_t knet_h)
 {
 	udp_handle_info_t *handle_info;
 
 	if (!knet_h->transports[KNET_TRANSPORT_UDP]) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	handle_info = knet_h->transports[KNET_TRANSPORT_UDP];
 
 	/*
 	 * keep it here while we debug list usage and such
 	 */
-	if (!knet_list_empty(&handle_info->links_list)) {
+	if (!qb_list_empty(&handle_info->links_list)) {
 		log_err(knet_h, KNET_SUB_TRANSP_UDP, "Internal error. handle list is not empty");
 		return -1;
 	}
 
 	free(handle_info);
 
 	knet_h->transports[KNET_TRANSPORT_UDP] = NULL;
 
 	return 0;
 }
 
 int udp_transport_init(knet_handle_t knet_h)
 {
 	udp_handle_info_t *handle_info;
 
 	if (knet_h->transports[KNET_TRANSPORT_UDP]) {
 		errno = EEXIST;
 		return -1;
 	}
 
 	handle_info = malloc(sizeof(udp_handle_info_t));
 	if (!handle_info) {
 		return -1;
 	}
 
 	memset(handle_info, 0, sizeof(udp_handle_info_t));
 
 	knet_h->transports[KNET_TRANSPORT_UDP] = handle_info;
 
-	knet_list_init(&handle_info->links_list);
+	qb_list_init(&handle_info->links_list);
 
 	return 0;
 }
 
 #if defined (IP_RECVERR) || defined (IPV6_RECVERR)
 static int read_errs_from_sock(knet_handle_t knet_h, int sockfd)
 {
 	int err = 0, savederrno = 0;
 	int got_err = 0;
 	char buffer[1024];
 	struct iovec iov;
 	struct msghdr msg;
 	struct cmsghdr *cmsg;
 	struct sock_extended_err *sock_err;
 	struct icmphdr icmph;
 	struct sockaddr_storage remote;
 	struct sockaddr_storage *origin;
 	char addr_str[KNET_MAX_HOST_LEN];
 	char port_str[KNET_MAX_PORT_LEN];
 	char addr_remote_str[KNET_MAX_HOST_LEN];
 	char port_remote_str[KNET_MAX_PORT_LEN];
 
 	iov.iov_base = &icmph;
 	iov.iov_len = sizeof(icmph);
 	msg.msg_name = (void*)&remote;
 	msg.msg_namelen = sizeof(remote);
 	msg.msg_iov = &iov;
 	msg.msg_iovlen = 1;
 	msg.msg_flags = 0;
 	msg.msg_control = buffer;
 	msg.msg_controllen = sizeof(buffer);
 
 	for (;;) {
 		err = recvmsg(sockfd, &msg, MSG_ERRQUEUE);
 		savederrno = errno;
 		if (err < 0) {
 			if (!got_err) {
 				errno = savederrno;
 				return -1;
 			} else {
 				return 0;
 			}
 		}
 		got_err = 1;
 		for (cmsg = CMSG_FIRSTHDR(&msg);cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
 			if (((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_RECVERR)) ||
 			    ((cmsg->cmsg_level == SOL_IPV6 && (cmsg->cmsg_type == IPV6_RECVERR)))) {
 				sock_err = (struct sock_extended_err*)(void *)CMSG_DATA(cmsg);
 				if (sock_err) {
 					switch (sock_err->ee_origin) {
 						case SO_EE_ORIGIN_NONE: /* no origin */
 						case SO_EE_ORIGIN_LOCAL: /* local source (EMSGSIZE) */
 							if (sock_err->ee_errno == EMSGSIZE) {
 								if (pthread_mutex_lock(&knet_h->kmtu_mutex) != 0) {
 									log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Unable to get mutex lock");
 									knet_h->kernel_mtu = 0;
 									break;
 								} else {
 									knet_h->kernel_mtu = sock_err->ee_info;
 									log_debug(knet_h, KNET_SUB_TRANSP_UDP, "detected kernel MTU: %u", knet_h->kernel_mtu);
 									pthread_mutex_unlock(&knet_h->kmtu_mutex);
 								}
 
 								force_pmtud_run(knet_h, KNET_SUB_TRANSP_UDP, 0);
 							}
 							/*
 							 * those errors are way too noisy
 							 */
 							break;
 						case SO_EE_ORIGIN_ICMP:  /* ICMP */
 						case SO_EE_ORIGIN_ICMP6: /* ICMP6 */
 							origin = (struct sockaddr_storage *)(void *)SO_EE_OFFENDER(sock_err);
 							if (knet_addrtostr(origin, sizeof(*origin),
 									   addr_str, KNET_MAX_HOST_LEN,
 									   port_str, KNET_MAX_PORT_LEN) < 0) {
 								log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Received ICMP error from unknown source: %s", strerror(sock_err->ee_errno));
 
 							} else {
 								if (knet_addrtostr(&remote, sizeof(remote),
 									       addr_remote_str, KNET_MAX_HOST_LEN,
 									       port_remote_str, KNET_MAX_PORT_LEN) < 0) {
 									log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Received ICMP error from %s: %s destination unknown", addr_str, strerror(sock_err->ee_errno));
 								} else {
 									log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Received ICMP error from %s: %s %s", addr_str, strerror(sock_err->ee_errno), addr_remote_str);
 								}
 							}
 							break;
 					}
 				} else {
 					log_debug(knet_h, KNET_SUB_TRANSP_UDP, "No data in MSG_ERRQUEUE");
 				}
 			}
 		}
 	}
 }
 #else
 static int read_errs_from_sock(knet_handle_t knet_h, int sockfd)
 {
 	return 0;
 }
 #endif
 
 int udp_transport_rx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno)
 {
 	if (recv_errno == EAGAIN) {
 		read_errs_from_sock(knet_h, sockfd);
 	}
 	return 0;
 }
 
 int udp_transport_tx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno)
 {
 	if (recv_err < 0) {
 		if (recv_errno == EMSGSIZE) {
 			read_errs_from_sock(knet_h, sockfd);
 			return 0;
 		}
 		if ((recv_errno == EINVAL) || (recv_errno == EPERM) ||
 		    (recv_errno == ENETUNREACH) || (recv_errno == ENETDOWN)) {
 #ifdef DEBUG
 			if ((recv_errno == ENETUNREACH) || (recv_errno == ENETDOWN)) {
 				log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Sock: %d is unreachable.", sockfd);
 			}
 #endif
 			return -1;
 		}
 		if ((recv_errno == ENOBUFS) || (recv_errno == EAGAIN)) {
 #ifdef DEBUG
 			log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Sock: %d is overloaded. Slowing TX down", sockfd);
 #endif
 			usleep(KNET_THREADS_TIMERES / 16);
 		} else {
 			read_errs_from_sock(knet_h, sockfd);
 		}
 		return 1;
 	}
 
 	return 0;
 }
 
 int udp_transport_rx_is_data(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg)
 {
 	if (msg->msg_len == 0)
 		return KNET_TRANSPORT_RX_NOT_DATA_CONTINUE;
 
 	return KNET_TRANSPORT_RX_IS_DATA;
 }
 
 int udp_transport_link_dyn_connect(knet_handle_t knet_h, int sockfd, struct knet_link *kn_link)
 {
 	kn_link->status.dynconnected = 1;
 	return 0;
 }
 
 int udp_transport_link_get_acl_fd(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	return kn_link->outsock;
 }
+
+int udp_transport_link_is_down(knet_handle_t knet_h, struct knet_link *kn_link)
+{
+	return 0;
+}
diff --git a/libknet/transport_udp.h b/libknet/transport_udp.h
index 8e3ea5f6..e2aa2dcd 100644
--- a/libknet/transport_udp.h
+++ b/libknet/transport_udp.h
@@ -1,28 +1,29 @@
 /*
  * Copyright (C) 2017-2020 Red Hat, Inc.  All rights reserved.
  *
  * Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #include "config.h"
 
 #include "internals.h"
 
 #ifndef __KNET_TRANSPORT_UDP_H__
 #define __KNET_TRANSPORT_UDP_H__
 
 #define KNET_PMTUD_UDP_OVERHEAD 8
 
 int udp_transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link);
 int udp_transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link);
 int udp_transport_free(knet_handle_t knet_h);
 int udp_transport_init(knet_handle_t knet_h);
 int udp_transport_rx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno);
 int udp_transport_tx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno);
 int udp_transport_rx_is_data(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg);
 int udp_transport_link_dyn_connect(knet_handle_t knet_h, int sockfd, struct knet_link *kn_link);
 int udp_transport_link_get_acl_fd(knet_handle_t knet_h, struct knet_link *kn_link);
+int udp_transport_link_is_down(knet_handle_t knet_h, struct knet_link *kn_link);
 
 #endif
diff --git a/libknet/transports.c b/libknet/transports.c
index e6457238..1ec0aafc 100644
--- a/libknet/transports.c
+++ b/libknet/transports.c
@@ -1,292 +1,297 @@
 /*
  * Copyright (C) 2017-2020 Red Hat, Inc.  All rights reserved.
  *
  * Author: Fabio M. Di Nitto <fabbione@kronosnet.org>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #include "config.h"
 
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
 #include <pthread.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 
 #include "libknet.h"
 #include "compat.h"
 #include "host.h"
 #include "link.h"
 #include "logging.h"
 #include "common.h"
 #include "transports.h"
 #include "transport_loopback.h"
 #include "transport_udp.h"
 #include "transport_sctp.h"
 #include "threads_common.h"
 
-#define empty_module 0, -1, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+#define empty_module 0, -1, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
 
 static knet_transport_ops_t transport_modules_cmd[KNET_MAX_TRANSPORTS] = {
-	{ "LOOPBACK", KNET_TRANSPORT_LOOPBACK, 1, TRANSPORT_PROTO_LOOPBACK, USE_NO_ACL, TRANSPORT_PROTO_NOT_CONNECTION_ORIENTED, KNET_PMTUD_LOOPBACK_OVERHEAD, loopback_transport_init, loopback_transport_free, loopback_transport_link_set_config, loopback_transport_link_clear_config, loopback_transport_link_dyn_connect, loopback_transport_link_get_acl_fd, loopback_transport_rx_sock_error, loopback_transport_tx_sock_error, loopback_transport_rx_is_data },
-	{ "UDP", KNET_TRANSPORT_UDP, 1, TRANSPORT_PROTO_IP_PROTO, USE_GENERIC_ACL, TRANSPORT_PROTO_NOT_CONNECTION_ORIENTED, KNET_PMTUD_UDP_OVERHEAD, udp_transport_init, udp_transport_free, udp_transport_link_set_config, udp_transport_link_clear_config, udp_transport_link_dyn_connect, udp_transport_link_get_acl_fd, udp_transport_rx_sock_error, udp_transport_tx_sock_error, udp_transport_rx_is_data },
+	{ "LOOPBACK", KNET_TRANSPORT_LOOPBACK, 1, TRANSPORT_PROTO_LOOPBACK, USE_NO_ACL, TRANSPORT_PROTO_NOT_CONNECTION_ORIENTED, KNET_PMTUD_LOOPBACK_OVERHEAD, loopback_transport_init, loopback_transport_free, loopback_transport_link_set_config, loopback_transport_link_clear_config, loopback_transport_link_dyn_connect, loopback_transport_link_get_acl_fd, loopback_transport_rx_sock_error, loopback_transport_tx_sock_error, loopback_transport_rx_is_data, loopback_transport_link_is_down },
+	{ "UDP", KNET_TRANSPORT_UDP, 1, TRANSPORT_PROTO_IP_PROTO, USE_GENERIC_ACL, TRANSPORT_PROTO_NOT_CONNECTION_ORIENTED, KNET_PMTUD_UDP_OVERHEAD, udp_transport_init, udp_transport_free, udp_transport_link_set_config, udp_transport_link_clear_config, udp_transport_link_dyn_connect, udp_transport_link_get_acl_fd, udp_transport_rx_sock_error, udp_transport_tx_sock_error, udp_transport_rx_is_data, udp_transport_link_is_down },
 	{ "SCTP", KNET_TRANSPORT_SCTP,
 #ifdef HAVE_NETINET_SCTP_H
-				       1, TRANSPORT_PROTO_IP_PROTO, USE_PROTO_ACL, TRANSPORT_PROTO_IS_CONNECTION_ORIENTED, KNET_PMTUD_SCTP_OVERHEAD, sctp_transport_init, sctp_transport_free, sctp_transport_link_set_config, sctp_transport_link_clear_config, sctp_transport_link_dyn_connect, sctp_transport_link_get_acl_fd, sctp_transport_rx_sock_error, sctp_transport_tx_sock_error, sctp_transport_rx_is_data },
+				       1, TRANSPORT_PROTO_IP_PROTO, USE_PROTO_ACL, TRANSPORT_PROTO_IS_CONNECTION_ORIENTED, KNET_PMTUD_SCTP_OVERHEAD, sctp_transport_init, sctp_transport_free, sctp_transport_link_set_config, sctp_transport_link_clear_config, sctp_transport_link_dyn_connect, sctp_transport_link_get_acl_fd, sctp_transport_rx_sock_error, sctp_transport_tx_sock_error, sctp_transport_rx_is_data, sctp_transport_link_is_down },
 #else
 empty_module
 #endif
 	{ NULL, KNET_MAX_TRANSPORTS, empty_module
 };
 
 /*
  * transport wrappers
  */
 
 int start_all_transports(knet_handle_t knet_h)
 {
 	int idx = 0, savederrno = 0, err = 0;
 
 	while (transport_modules_cmd[idx].transport_name != NULL) {
 		if (transport_modules_cmd[idx].built_in) {
 			if (transport_modules_cmd[idx].transport_init(knet_h) < 0) {
 				savederrno = errno;
 				log_err(knet_h, KNET_SUB_HANDLE,
 					"Failed to allocate transport handle for %s: %s",
 					transport_modules_cmd[idx].transport_name,
 					strerror(savederrno));
 				err = -1;
 				goto out;
 			}
 		}
 		idx++;
 	}
 
 out:
 	errno = savederrno;
 	return err;
 }
 
 void stop_all_transports(knet_handle_t knet_h)
 {
 	int idx = 0;
 
 	while (transport_modules_cmd[idx].transport_name != NULL) {
 		if (transport_modules_cmd[idx].built_in) {
 			transport_modules_cmd[idx].transport_free(knet_h);
 		}
 		idx++;
 	}
 }
 
 int transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link, uint8_t transport)
 {
 	if (!transport_modules_cmd[transport].built_in) {
 		errno = EINVAL;
 		return -1;
 	}
 	kn_link->transport_connected = 0;
 	kn_link->transport = transport;
 	kn_link->proto_overhead = transport_modules_cmd[transport].transport_mtu_overhead;
 	return transport_modules_cmd[transport].transport_link_set_config(knet_h, kn_link);
 }
 
 int transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	return transport_modules_cmd[kn_link->transport].transport_link_clear_config(knet_h, kn_link);
 }
 
 int transport_link_dyn_connect(knet_handle_t knet_h, int sockfd, struct knet_link *kn_link)
 {
 	return transport_modules_cmd[kn_link->transport].transport_link_dyn_connect(knet_h, sockfd, kn_link);
 }
 
 int transport_link_get_acl_fd(knet_handle_t knet_h, struct knet_link *kn_link)
 {
 	return transport_modules_cmd[kn_link->transport].transport_link_get_acl_fd(knet_h, kn_link);
 }
 
 int transport_rx_sock_error(knet_handle_t knet_h, uint8_t transport, int sockfd, int recv_err, int recv_errno)
 {
 	return transport_modules_cmd[transport].transport_rx_sock_error(knet_h, sockfd, recv_err, recv_errno);
 }
 
 int transport_tx_sock_error(knet_handle_t knet_h, uint8_t transport, int sockfd, int recv_err, int recv_errno)
 {
 	return transport_modules_cmd[transport].transport_tx_sock_error(knet_h, sockfd, recv_err, recv_errno);
 }
 
 int transport_rx_is_data(knet_handle_t knet_h, uint8_t transport, int sockfd, struct knet_mmsghdr *msg)
 {
 	return transport_modules_cmd[transport].transport_rx_is_data(knet_h, sockfd, msg);
 }
 
 int transport_get_proto(knet_handle_t knet_h, uint8_t transport)
 {
 	return transport_modules_cmd[transport].transport_protocol;
 }
 
 int transport_get_acl_type(knet_handle_t knet_h, uint8_t transport)
 {
 	return transport_modules_cmd[transport].transport_acl_type;
 }
 
 int transport_get_connection_oriented(knet_handle_t knet_h, uint8_t transport)
 {
 	return transport_modules_cmd[transport].transport_is_connection_oriented;
 }
 
+int transport_link_is_down(knet_handle_t knet_h, struct knet_link *kn_link)
+{
+	return transport_modules_cmd[kn_link->transport].transport_link_is_down(knet_h, kn_link);
+}
+
 /*
  * public api
  */
 
 int knet_get_transport_list(struct knet_transport_info *transport_list,
 			    size_t *transport_list_entries)
 {
 	int err = 0;
 	int idx = 0;
 	int outidx = 0;
 
 	if (!transport_list_entries) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	while (transport_modules_cmd[idx].transport_name != NULL) {
 		if (transport_modules_cmd[idx].built_in) {
 			if (transport_list) {
 				transport_list[outidx].name = transport_modules_cmd[idx].transport_name;
 				transport_list[outidx].id = transport_modules_cmd[idx].transport_id;
 			}
 			outidx++;
 		}
 		idx++;
 	}
 
 	*transport_list_entries = outidx;
 
 	if (!err)
 		errno = 0;
 	return err;
 }
 
 const char *knet_get_transport_name_by_id(uint8_t transport)
 {
 	int savederrno = 0;
 	const char *name = NULL;
 
 	if (transport == KNET_MAX_TRANSPORTS) {
 		errno = EINVAL;
 		return name;
 	}
 
 	if ((transport_modules_cmd[transport].transport_name) &&
 	    (transport_modules_cmd[transport].built_in)) {
 		name = transport_modules_cmd[transport].transport_name;
 	} else {
 		savederrno = ENOENT;
 	}
 
 	errno = name ? 0 : savederrno;
 	return name;
 }
 
 uint8_t knet_get_transport_id_by_name(const char *name)
 {
 	int savederrno = 0;
 	uint8_t err = KNET_MAX_TRANSPORTS;
 	int i, found;
 
 	if (!name) {
 		errno = EINVAL;
 		return err;
 	}
 
 	i = 0;
 	found = 0;
 	while (transport_modules_cmd[i].transport_name != NULL) {
 		if (transport_modules_cmd[i].built_in) {
 			if (!strcmp(transport_modules_cmd[i].transport_name, name)) {
 				err = transport_modules_cmd[i].transport_id;
 				found = 1;
 				break;
 			}
 		}
 		i++;
 	}
 
 	if (!found) {
 		savederrno = EINVAL;
 	}
 
 	errno = err == KNET_MAX_TRANSPORTS ? savederrno : 0;
 	return err;
 }
 
 int knet_handle_set_transport_reconnect_interval(knet_handle_t knet_h, uint32_t msecs)
 {
 	int savederrno = 0;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!msecs) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (msecs < 1000) {
 		log_warn(knet_h, KNET_SUB_HANDLE, "reconnect internval below 1 sec (%u msecs) might be too aggressive", msecs);
 	}
 
 	if (msecs > 60000) {
 		log_warn(knet_h, KNET_SUB_HANDLE, "reconnect internval above 1 minute (%u msecs) could cause long delays in network convergiance", msecs);
 	}
 
 	savederrno = get_global_wrlock(knet_h);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_HANDLE, "Unable to get read lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	knet_h->reconnect_int = msecs;
 
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = 0;
 	return 0;
 }
 
 int knet_handle_get_transport_reconnect_interval(knet_handle_t knet_h, uint32_t *msecs)
 {
 	int savederrno = 0;
 
 	if (!knet_h) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	if (!msecs) {
 		errno = EINVAL;
 		return -1;
 	}
 
 	savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock);
 	if (savederrno) {
 		log_err(knet_h, KNET_SUB_HANDLE, "Unable to get read lock: %s",
 			strerror(savederrno));
 		errno = savederrno;
 		return -1;
 	}
 
 	*msecs = knet_h->reconnect_int;
 
 	pthread_rwlock_unlock(&knet_h->global_rwlock);
 	errno = 0;
 	return 0;
 }
diff --git a/libknet/transports.h b/libknet/transports.h
index 763fce37..51063864 100644
--- a/libknet/transports.h
+++ b/libknet/transports.h
@@ -1,33 +1,34 @@
 /*
  * Copyright (C) 2016-2020 Red Hat, Inc.  All rights reserved.
  *
  * Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
  *
  * This software licensed under LGPL-2.0+
  */
 
 #ifndef __KNET_TRANSPORTS_H__
 #define __KNET_TRANSPORTS_H__
 
 #define KNET_TRANSPORT_RX_ERROR -1
 #define KNET_TRANSPORT_RX_NOT_DATA_CONTINUE 0
 #define KNET_TRANSPORT_RX_NOT_DATA_STOP 1
 #define KNET_TRANSPORT_RX_IS_DATA 2
 #define KNET_TRANSPORT_RX_OOB_DATA_CONTINUE 3
 #define KNET_TRANSPORT_RX_OOB_DATA_STOP 4
 
 int start_all_transports(knet_handle_t knet_h);
 void stop_all_transports(knet_handle_t knet_h);
 
 int transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link, uint8_t transport);
 int transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link);
 int transport_link_dyn_connect(knet_handle_t knet_h, int sockfd, struct knet_link *kn_link);
 int transport_link_get_acl_fd(knet_handle_t knet_h, struct knet_link *kn_link);
 int transport_rx_sock_error(knet_handle_t knet_h, uint8_t transport, int sockfd, int recv_err, int recv_errno);
 int transport_tx_sock_error(knet_handle_t knet_h, uint8_t transport, int sockfd, int recv_err, int recv_errno);
 int transport_rx_is_data(knet_handle_t knet_h, uint8_t transport, int sockfd, struct knet_mmsghdr *msg);
 int transport_get_proto(knet_handle_t knet_h, uint8_t transport);
 int transport_get_acl_type(knet_handle_t knet_h, uint8_t transport);
 int transport_get_connection_oriented(knet_handle_t knet_h, uint8_t transport);
+int transport_link_is_down(knet_handle_t knet_h, struct knet_link *link);
 
 #endif
diff --git a/m4/pkg_check_var.m4 b/m4/pkg_check_var.m4
new file mode 100644
index 00000000..ae1bf222
--- /dev/null
+++ b/m4/pkg_check_var.m4
@@ -0,0 +1,14 @@
+# PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE,
+# [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+# -------------------------------------------
+# Retrieves the value of the pkg-config variable for the given module.
+
+m4_ifndef([PKG_CHECK_VAR],
+	  [AC_DEFUN([PKG_CHECK_VAR],
+		    [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+		     AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl
+		     _PKG_CONFIG([$1], [variable="][$3]["], [$2])
+		     AS_VAR_COPY([$1], [pkg_cv_][$1])
+		     AS_VAR_IF([$1], [""], [$5], [$4])dnl
+		    ])# PKG_CHECK_VAR
+])
diff --git a/man/Makefile.am b/man/Makefile.am
index b4454af5..f813b97d 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -1,160 +1,162 @@
 #
 # Copyright (C) 2017-2020 Red Hat, Inc.  All rights reserved.
 #
 # Authors: Fabio M. Di Nitto <fabbione@kronosnet.org>
 #          Federico Simoncelli <fsimon@kronosnet.org>
 #
 # This software licensed under GPL-2.0+
 #
 
 MAINTAINERCLEANFILES	= Makefile.in
 
 include $(top_srcdir)/build-aux/check.mk
 
 EXTRA_DIST	= \
 		  kronosnetd.8 knet-keygen.8 \
 		  api-to-man-page-coverage
 
 # Avoid Automake warnings about overriding these user variables.
 # Programs in this directory are used during the build only.
 AUTOMAKE_OPTIONS = -Wno-gnu
 EXEEXT=$(BUILD_EXEEXT)
 CC=$(CC_FOR_BUILD)
 CFLAGS=$(CFLAGS_FOR_BUILD)
 CPPFLAGS=$(CPPFLAGS_FOR_BUILD)
 LDFLAGS=$(LDFLAGS_FOR_BUILD)
 
 if BUILD_MAN
 
 if BUILD_KRONOSNETD
 man8_MANS	= kronosnetd.8 knet-keygen.8
 endif
 
+if BUILD_DOXYXML
 noinst_PROGRAMS	= doxyxml
 
 doxyxml_SOURCES = doxyxml.c
 doxyxml_CFLAGS = $(AM_CFLAGS) $(libqb_BUILD_CFLAGS) $(libxml_BUILD_CFLAGS)
 doxyxml_LDADD = $(libqb_BUILD_LIBS) $(libxml_BUILD_LIBS)
+endif
 
 knet_man3_MANS = \
 		knet_addrtostr.3 \
 		knet_handle_add_datafd.3 \
 		knet_handle_clear_stats.3 \
 		knet_handle_compress.3 \
 		knet_handle_crypto.3 \
 		knet_handle_enable_filter.3 \
 		knet_handle_enable_pmtud_notify.3 \
 		knet_handle_enable_sock_notify.3 \
 		knet_handle_free.3 \
 		knet_handle_get_channel.3 \
 		knet_get_compress_list.3 \
 		knet_get_crypto_list.3 \
 		knet_handle_get_datafd.3 \
 		knet_handle_get_stats.3 \
 		knet_get_transport_id_by_name.3 \
 		knet_get_transport_list.3 \
 		knet_get_transport_name_by_id.3 \
 		knet_handle_get_transport_reconnect_interval.3 \
 		knet_handle_new.3 \
 		knet_handle_new_ex.3 \
 		knet_handle_pmtud_get.3 \
 		knet_handle_pmtud_set.3 \
 		knet_handle_pmtud_getfreq.3 \
 		knet_handle_pmtud_setfreq.3 \
 		knet_handle_remove_datafd.3 \
 		knet_handle_setfwd.3 \
 		knet_handle_set_transport_reconnect_interval.3 \
 		knet_host_add.3 \
 		knet_host_enable_status_change_notify.3 \
 		knet_host_get_host_list.3 \
 		knet_host_get_id_by_host_name.3 \
 		knet_host_get_name_by_host_id.3 \
 		knet_host_get_policy.3 \
 		knet_host_get_status.3 \
 		knet_host_remove.3 \
 		knet_host_set_name.3 \
 		knet_host_set_policy.3 \
 		knet_link_clear_config.3 \
 		knet_link_get_config.3 \
 		knet_link_get_enable.3 \
 		knet_link_get_link_list.3 \
 		knet_link_get_ping_timers.3 \
 		knet_link_get_pong_count.3 \
 		knet_link_get_priority.3 \
 		knet_link_get_status.3 \
 		knet_link_set_config.3 \
 		knet_link_set_enable.3 \
 		knet_link_set_ping_timers.3 \
 		knet_link_set_pong_count.3 \
 		knet_link_set_priority.3 \
 		knet_log_get_loglevel.3 \
 		knet_log_get_loglevel_id.3 \
 		knet_log_get_loglevel_name.3 \
 		knet_log_get_subsystem_id.3 \
 		knet_log_get_subsystem_name.3 \
 		knet_log_set_loglevel.3 \
 		knet_recv.3 \
 		knet_send.3 \
 		knet_send_sync.3 \
 		knet_strtoaddr.3 \
 		knet_handle_enable_access_lists.3 \
 		knet_link_add_acl.3 \
 		knet_link_insert_acl.3 \
 		knet_link_rm_acl.3 \
 		knet_link_clear_acl.3
 
 if BUILD_LIBNOZZLE
 nozzle_man3_MANS = \
 		nozzle_add_ip.3 \
 		nozzle_close.3 \
 		nozzle_del_ip.3 \
 		nozzle_get_fd.3 \
 		nozzle_get_handle_by_name.3 \
 		nozzle_get_ips.3 \
 		nozzle_get_mac.3 \
 		nozzle_get_mtu.3 \
 		nozzle_get_name_by_handle.3 \
 		nozzle_open.3 \
 		nozzle_reset_mac.3 \
 		nozzle_reset_mtu.3 \
 		nozzle_run_updown.3 \
 		nozzle_set_down.3 \
 		nozzle_set_mac.3 \
 		nozzle_set_mtu.3 \
 		nozzle_set_up.3
 endif
 
 man3_MANS = $(knet_man3_MANS) $(nozzle_man3_MANS)
 
 $(MANS): doxyfile-knet.stamp doxyfile-nozzle.stamp
 
 doxyfile-knet.stamp: $(noinst_PROGRAMS) Doxyfile-knet $(top_srcdir)/libknet/libknet.h
 	$(DOXYGEN) Doxyfile-knet
-	$(builddir)/doxyxml -m -P -o $(builddir) -s 3 -p @PACKAGE_NAME@ -H "Kronosnet Programmer's Manual" \
+	$(DOXYGEN2MAN) -m -P -o $(builddir) -s 3 -p @PACKAGE_NAME@ -H "Kronosnet Programmer's Manual" \
 		$$($(UTC_DATE_AT)$(SOURCE_EPOCH) +"-D %F -Y %Y") -d $(builddir)/xml-knet/ libknet_8h.xml
 	touch doxyfile-knet.stamp
 
 doxyfile-nozzle.stamp: $(noinst_PROGRAMS) Doxyfile-nozzle $(top_srcdir)/libnozzle/libnozzle.h
 if BUILD_LIBNOZZLE
 	$(DOXYGEN) Doxyfile-nozzle
-	$(builddir)/doxyxml -m -P -o $(builddir) -s 3 -p @PACKAGE_NAME@ -H "Kronosnet Programmer's Manual" \
+	$(DOXYGEN2MAN) -m -P -o $(builddir) -s 3 -p @PACKAGE_NAME@ -H "Kronosnet Programmer's Manual" \
 		$$($(UTC_DATE_AT)$(SOURCE_EPOCH) +"-D %F -Y %Y") -d $(builddir)/xml-nozzle/ libnozzle_8h.xml
 endif
 	touch doxyfile-nozzle.stamp
 
 noinst_SCRIPTS	= api-to-man-page-coverage
 
 check-local: check-api-to-man-page-coverage-libknet check-api-to-man-page-coverage-libnozzle
 
 check-api-to-man-page-coverage-libnozzle:
 if BUILD_LIBNOZZLE
 	$(srcdir)/api-to-man-page-coverage $(top_srcdir) nozzle
 endif
 
 check-api-to-man-page-coverage-libknet:
 	$(srcdir)/api-to-man-page-coverage $(top_srcdir) knet
 
 endif
 
 clean-local:
-	rm -rf doxyfile*.stamp xml* *.3
+	rm -rf doxyxml doxyfile*.stamp xml* *.3