Merge "libnl: Add to recovery" into main
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 708928e..6ad5fd2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,11 +4,14 @@
   push:
   pull_request:
 
+env:
+  NLTST_IN_CI: 1
+
 jobs:
   clang-format:
     runs-on: ubuntu-latest
     container:
-      image: fedora:39
+      image: fedora:40
     steps:
       - name: Install packages
         run: |
@@ -70,21 +73,24 @@
           set -x
 
           export CC="${{ matrix.cc }}"
-          export CFLAGS="-DNL_MORE_ASSERTS=1000 -O2 -Werror -Wall -Wdeclaration-after-statement -Wvla -std=gnu11 -fexceptions"
+          export CFLAGS="-DNL_MORE_ASSERTS=1000 -O2 -Werror -std=gnu11 -fexceptions"
+          CONFIGURE_ARGS=
           if [ "$CC" = "clang" ]; then
                   CFLAGS="$CFLAGS -Wno-error=unused-command-line-argument -Wno-error=unused-function"
                   export LDFLAGS="-Wl,--no-undefined-version,--fatal-warnings"
+                  CONFIGURE_ARGS="--enable-debug=no"
           else
                   export LDFLAGS="-Wl,--no-undefined-version"
           fi
 
           ./autogen.sh
-          ./configure
-          make -j 5
+          ./configure $CONFIGURE_ARGS
+          make -j 15
         shell: bash
 
       - name: Build Unit Tests
-        run: make -j 5 check-progs
+        run: |
+          make -j 15 check-build
 
       - name: Run Unit Tests
         run: |
@@ -93,7 +99,7 @@
           for i in `seq 1 5`; do
             tests/check-direct
             tests/check-all
-            make -j check
+            make -j 15 check || (cat ./test-suite.log; false)
           done
 
       - name: Run Unit Tests w/Valgrind
@@ -128,7 +134,7 @@
           set -x
           git clean -fdx
           export CC="${{ matrix.cc }}"
-          export CFLAGS="-Werror -Wall -Wdeclaration-after-statement -Wvla -std=gnu11 -fexceptions"
+          export CFLAGS="-Werror -std=gnu11 -fexceptions"
           if [ "$CC" = "clang" ]; then
                   CFLAGS="$CFLAGS -Wno-error=unused-command-line-argument -Wno-error=unused-function"
           fi
@@ -137,10 +143,10 @@
           mkdir build
           cd build
           ../configure --disable-static
-          make -j 5
-          make -j 5 check-progs
+          make -j 15
+          make -j 15 check-build
           export NLTST_SEED_RAND=
-          make -j 5 check
+          make -j 15 check || (cat ./test-suite.log; false)
 
       - name: Link with mold
         run: |
@@ -150,7 +156,7 @@
           export LDFLAGS="-fuse-ld=mold -Wl,--fatal-warnings"
           ./autogen.sh
           ./configure
-          make -j 5 V=1
+          make -j 15 V=1
 
       - run: echo "🍏 This job's status is ${{ job.status }}."
 
@@ -188,7 +194,7 @@
           set -x
 
           export CC="${{ matrix.cc }}"
-          export CFLAGS="-DNL_MORE_ASSERTS=1000 -O2 -Werror -Wall -Wdeclaration-after-statement -Wvla -std=gnu11 -fexceptions"
+          export CFLAGS="-DNL_MORE_ASSERTS=1000 -O2 -Werror -std=gnu11 -fexceptions"
           if [ "$CC" = "clang" ]; then
                   CFLAGS="$CFLAGS -Wno-error=unused-command-line-argument -Wno-error=unused-function"
                   export LDFLAGS="-Wl,--no-undefined-version,--fatal-warnings"
@@ -198,8 +204,11 @@
 
           ./autogen.sh
           ./configure
-          make -j 5
-          make -j 5 check-progs
+          make -j 15
+
+      - name: Build Unit Tests
+        run: |
+          make -j 15 check-build
 
       - name: Run Unit Tests
         run: |
@@ -210,5 +219,5 @@
             # unshare() does not work (EPERM). This test currently cannot pass
             # (odd).
             # tests/check-all
-            # make -j check
+            # make -j 15 check || (cat ./test-suite.log; false)
           done
diff --git a/Android.bp b/Android.bp
index cd75e3f..e711be0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -98,11 +98,12 @@
         "-Wno-pointer-arith",
         "-UNDEBUG",
         "-D_GNU_SOURCE",
-        "-DSYSCONFDIR=\"\\\"/etc/libnl\\\"\"",
+        "-D_NL_SYSCONFDIR_LIBNL=\"\\\"/etc/libnl\\\"\"",
     ],
 
     sanitize: {
         integer_overflow: true,
+        blocklist: "libnl_blocklist.txt",
     },
     apex_available: [
         "//apex_available:platform",
diff --git a/METADATA b/METADATA
index 5e52a66..8b03e00 100644
--- a/METADATA
+++ b/METADATA
@@ -8,13 +8,13 @@
   license_type: RESTRICTED
   last_upgrade_date {
     year: 2024
-    month: 4
+    month: 6
     day: 4
   }
   homepage: "https://github.com/thom311/libnl"
   identifier {
     type: "Git"
     value: "https://github.com/thom311/libnl.git"
-    version: "libnl3_9_0"
+    version: "5248e1a45576617b349465997822cef34cbc5053"
   }
 }
diff --git a/Makefile.am b/Makefile.am
index 1e76649..227fa56 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -9,6 +9,7 @@
 check_PROGRAMS =
 check_programs =
 check_local =
+check_build =
 
 bin_PROGRAMS =
 sbin_PROGRAMS =
@@ -34,11 +35,18 @@
 warn_cppflags = \
 	-Wall \
 	-Wextra \
+	\
+	-Wdeclaration-after-statement \
 	-Wmissing-prototypes \
-	-Wno-unused-parameter \
-	-Wno-sign-compare \
-	-Wno-missing-field-initializers \
 	-Wpointer-arith \
+	-Wvla \
+	\
+	-Wno-unused-parameter \
+	$(NULL)
+
+defines_cppflags = \
+	-D_NL_SYSCONFDIR_LIBNL=\"$(sysconfdir)/libnl\" \
+	-D_NL_PKGLIBDIR=\"$(pkglibdir)\" \
 	$(NULL)
 
 ###############################################################################
@@ -387,8 +395,7 @@
 
 lib_cppflags = \
 	$(warn_cppflags) \
-	-D_GNU_SOURCE \
-	-DSYSCONFDIR=\"$(sysconfdir)/libnl\" \
+	$(defines_cppflags) \
 	$(default_includes) \
 	$(NULL)
 
@@ -705,9 +712,7 @@
 	libnl-cli-3.sym
 src_lib_libnl_cli_3_la_CPPFLAGS = \
 	$(warn_cppflags) \
-	-D_GNU_SOURCE \
-	-DPKGLIBDIR=\"$(pkglibdir)\" \
-	-DSYSCONFDIR=\"$(sysconfdir)\" \
+	$(defines_cppflags) \
 	$(default_includes) \
 	$(NULL)
 src_lib_libnl_cli_3_la_LDFLAGS = \
@@ -725,8 +730,7 @@
 
 src_cppflags = \
 	$(warn_cppflags) \
-	-D_GNU_SOURCE \
-	-DSYSCONFDIR=\"$(sysconfdir)/libnl\" \
+	$(defines_cppflags) \
 	$(default_includes) \
 	$(NULL)
 
@@ -905,8 +909,7 @@
 
 tests_cppflags = \
 	$(warn_cppflags) \
-	-D_GNU_SOURCE \
-	-DSYSCONFDIR=\"$(sysconfdir)/libnl\" \
+	$(defines_cppflags) \
 	$(default_includes) \
 	$(NULL)
 
@@ -1136,24 +1139,43 @@
 
 %.build-headers-test.c: %
 	mkdir -p "$(dir $@)"
-	printf "#include <$$(echo "$<" | sed 's|.*\<include/netlink/|netlink/|')>\nint main(int argc, char **argv) { return 0; }" > $@
+	printf "#include <$$(echo "$<" | sed 's|.*\<include/netlink/|netlink/|')>\nint main(void) { return 0; }" > $@
 
 %.build-headers-test.o: %.build-headers-test.c
-	$(COMPILE) -Wall -Werror -Wno-error=cpp -I$(srcdir)/include -I$(builddir)/include -c -o $@ $<
+	$(CC) -Wall -Werror -D_NL_NO_WARN_DEPRECATED_HEADER -I$(srcdir)/include -I$(builddir)/include -c -o $@ $<
 
 BUILD_HEADERS_OBJS = $(patsubst %,%.build-headers-test.o,$(public_headers))
 
 # Test whether the public headers are all self-contained and can be build.
 # This test is not hooked up as `make check`.
-check-local-build-headers: $(BUILD_HEADERS_OBJS)
+check-build-headers: $(BUILD_HEADERS_OBJS)
 
 CLEANFILES += $(BUILD_HEADERS_OBJS)
 
-check_local += check-local-build-headers
+check_build += check-build-headers
 
 ###############################################################################
 
-check-local: $(check_local)
+if HAS_CXX
+%.build-headers-test-cxx.cpp: %
+	mkdir -p "$(dir $@)"
+	printf "#include <cstdio>\n#include <$$(echo "$<" | sed 's|.*\<include/netlink/|netlink/|')>\nint main(void) { return 0; }" > $@
+
+%.build-headers-test-cxx.o: %.build-headers-test-cxx.cpp %.build-headers-test.o
+	$(CXX) -Wall -Werror -D_NL_NO_WARN_DEPRECATED_HEADER -I$(srcdir)/include -I$(builddir)/include -c -o $@ $<
+
+BUILD_HEADERS_OBJS_CXX = $(patsubst %,%.build-headers-test-cxx.o,$(public_headers))
+
+CLEANFILES += $(BUILD_HEADERS_OBJS_CXX)
+
+check-build-headers-cxx: $(BUILD_HEADERS_OBJS_CXX)
+
+check_build += check-build-headers-cxx
+endif
+
+###############################################################################
+
+check-local: $(check_build) $(check_local)
 
 .PHONY: $(check_local)
 
@@ -1182,8 +1204,10 @@
 
 ###############################################################################
 
-check-progs: all $(check_PROGRAMS) $(check_LTLIBRARIES)
+check-build: all $(check_PROGRAMS) $(check_LTLIBRARIES) $(check_build)
 
-.PHONY: check-progs
+check-progs: check-build
+
+.PHONY: check-progs check-build
 
 ###############################################################################
diff --git a/configure.ac b/configure.ac
index bc8b390..4db4385 100644
--- a/configure.ac
+++ b/configure.ac
@@ -61,8 +61,11 @@
 LT_AGE=libnl_lt_age
 AC_SUBST(LT_AGE)
 
+AC_USE_SYSTEM_EXTENSIONS
+
 AC_PROG_CC
 AM_PROG_CC_C_O
+AC_PROG_CXX
 AC_PROG_INSTALL
 LT_INIT
 AC_PROG_MKDIR_P
@@ -72,6 +75,15 @@
 AC_C_CONST
 AC_C_INLINE
 
+AC_LANG_PUSH([C++])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+  [[#ifndef __cplusplus
+    #error "broken C++"
+    #endif]])],,
+  [CXX=;])
+AC_LANG_POP([C++])
+AM_CONDITIONAL([HAS_CXX], [test "x$CXX" != x])
+
 PKG_CHECK_MODULES([CHECK], [check >= 0.9.0],
 	[has_check="yes"],
 	[AC_MSG_WARN([*** Disabling building of unit tests])
@@ -110,10 +122,13 @@
 AM_CONDITIONAL([ENABLE_STATIC], [test "$enable_static" != "no"])
 
 AC_ARG_ENABLE([debug],
-	AS_HELP_STRING([--disable-debug], [Do not include debugging statements]),
-	[enable_debug="$enableval"], [enable_debug="yes"])
-if test "x$enable_debug" = "xyes"; then
+        AS_HELP_STRING([--disable-debug], [Do not include debugging statements]),
+        [enable_debug="$enableval"], [enable_debug="yes"])
+if test "$enable_debug" = "yes"; then
     AC_DEFINE([NL_DEBUG], [1], [Define to 1 to enable debugging])
+else
+    enable_debug=no
+    AC_DEFINE([NL_DEBUG], [0], [Define to 1 to enable debugging])
 fi
 
 AC_CONFIG_SUBDIRS([doc])
@@ -152,6 +167,11 @@
 
 AC_OUTPUT
 
+CXX_MSG="none"
+if test "x$CXX" != x ; then
+	CXX_MSG="[$CXX]"
+fi
+
 echo
 echo "libnl $LIBNL_VERSION configuration${LIBNL_GIT_SHA:+ (git:$LIBNL_GIT_SHA)}:"
 echo "    --enable-pthreads=$enable_pthreads"
@@ -161,4 +181,5 @@
 echo "    --enable-cli=$enable_cli"
 echo
 echo "    check: $has_check"
+echo "    CXX: $CXX_MSG (only used for tests)"
 echo
diff --git a/include/base/nl-base-utils.h b/include/base/nl-base-utils.h
index 03d568e..a2bedee 100644
--- a/include/base/nl-base-utils.h
+++ b/include/base/nl-base-utils.h
@@ -754,10 +754,7 @@
 	const char *a;
 };
 
-#define __ADD(id, name)             \
-	{                           \
-		.i = id, .a = #name \
-	}
+#define __ADD(id, name) { .i = id, .a = #name }
 
 #define BUG()                                                                \
 	do {                                                                 \
@@ -816,8 +813,8 @@
 }
 
 #else
-#define NL_LOCK(NAME) int __unused_lock_##NAME __attribute__((unused))
-#define NL_RW_LOCK(NAME) int __unused_lock_##NAME __attribute__((unused))
+#define NL_LOCK(NAME) int __unused_lock_##NAME _nl_unused
+#define NL_RW_LOCK(NAME) int __unused_lock_##NAME _nl_unused
 
 #define nl_lock(LOCK) \
 	do {          \
diff --git a/include/config.h b/include/config.h
index a64cb61..3ce6d50 100644
--- a/include/config.h
+++ b/include/config.h
@@ -56,7 +56,7 @@
 #undef LT_OBJDIR
 
 /* Define to 1 to enable debugging */
-#undef NL_DEBUG
+#define NL_DEBUG 0
 
 /* Define to 1 if your C compiler doesn't accept -c and -o together. */
 #undef NO_MINUS_C_MINUS_O
diff --git a/include/netlink/cache-api.h b/include/netlink/cache-api.h
index 851eca0..e8a4170 100644
--- a/include/netlink/cache-api.h
+++ b/include/netlink/cache-api.h
@@ -9,6 +9,8 @@
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 
+#ifndef _NL_NO_WARN_DEPRECATED_HEADER
 #warning "You are including a deprecated header file, include <netlink/cache.h>."
+#endif
 
 #endif
diff --git a/include/netlink/cache.h b/include/netlink/cache.h
index abeeccb..955f979 100644
--- a/include/netlink/cache.h
+++ b/include/netlink/cache.h
@@ -140,12 +140,16 @@
 
 struct nl_cache_mngr;
 
-#define NL_AUTO_PROVIDE		1
-#define NL_ALLOCATED_SOCK	2  /* For internal use only, do not use */
+#define NL_AUTO_PROVIDE		    1
+#define NL_ALLOCATED_SOCK	    2  /* For internal use only, do not use */
 
 extern int			nl_cache_mngr_alloc(struct nl_sock *,
 						    int, int,
 						    struct nl_cache_mngr **);
+extern int			nl_cache_mngr_alloc_ex(struct nl_sock *,
+						       struct nl_sock *,
+						       int, int,
+						       struct nl_cache_mngr **);
 extern int			nl_cache_mngr_add(struct nl_cache_mngr *,
 						  const char *,
 						  change_func_t,
diff --git a/include/netlink/route/link/api.h b/include/netlink/route/link/api.h
index abdd8b2..ef11d2f 100644
--- a/include/netlink/route/link/api.h
+++ b/include/netlink/route/link/api.h
@@ -9,6 +9,8 @@
 #include <netlink/netlink.h>
 #include <netlink/route/link.h>
 
+#ifndef _NL_NO_WARN_DEPRECATED_HEADER
 #warning "You are including a deprecated header file, include <netlink/route/link.h>."
+#endif
 
 #endif
diff --git a/include/netlink/route/link/bridge.h b/include/netlink/route/link/bridge.h
index e606bd4..0de59c4 100644
--- a/include/netlink/route/link/bridge.h
+++ b/include/netlink/route/link/bridge.h
@@ -61,6 +61,7 @@
 extern int	rtnl_link_bridge_get_flags(struct rtnl_link *);
 
 extern int	rtnl_link_bridge_set_self(struct rtnl_link *);
+extern int	rtnl_link_bridge_set_master(struct rtnl_link *);
 
 extern int	rtnl_link_bridge_get_hwmode(struct rtnl_link *, uint16_t *);
 extern int	rtnl_link_bridge_set_hwmode(struct rtnl_link *, uint16_t);
@@ -76,6 +77,10 @@
 
 extern int	rtnl_link_bridge_add(struct nl_sock *sk, const char *name);
 
+extern int	rtnl_link_bridge_enable_vlan(struct rtnl_link *link);
+extern int	rtnl_link_bridge_set_port_vlan_map_range (struct rtnl_link *link, uint16_t start, uint16_t end, int untagged);
+extern int	rtnl_link_bridge_unset_port_vlan_map_range (struct rtnl_link *link, uint16_t start, uint16_t end);
+extern int	rtnl_link_bridge_set_port_vlan_pvid (struct rtnl_link *link, uint16_t pvid);
 extern int	rtnl_link_bridge_pvid(struct rtnl_link *link);
 extern int	rtnl_link_bridge_has_vlan(struct rtnl_link *link);
 
diff --git a/include/netlink/route/link/bridge_info.h b/include/netlink/route/link/bridge_info.h
index 09689b3..bb7bf0b 100644
--- a/include/netlink/route/link/bridge_info.h
+++ b/include/netlink/route/link/bridge_info.h
@@ -13,6 +13,11 @@
 extern "C" {
 #endif
 
+extern void rtnl_link_bridge_set_ageing_time(struct rtnl_link *link,
+					     uint32_t ageing_time);
+extern int rtnl_link_bridge_get_ageing_time(struct rtnl_link *link,
+					    uint32_t *ageing_time);
+
 extern void rtnl_link_bridge_set_vlan_filtering(struct rtnl_link *link,
 						uint8_t vlan_filtering);
 extern int rtnl_link_bridge_get_vlan_filtering(struct rtnl_link *link,
@@ -23,10 +28,21 @@
 extern int rtnl_link_bridge_get_vlan_protocol(struct rtnl_link *link,
 					      uint16_t *vlan_protocol);
 
+extern void rtnl_link_bridge_set_vlan_default_pvid(struct rtnl_link *link,
+						   uint16_t default_pvid);
+extern int rtnl_link_bridge_get_vlan_default_pvid(struct rtnl_link *link,
+						  uint16_t *default_pvid);
+
 extern void rtnl_link_bridge_set_vlan_stats_enabled(struct rtnl_link *link,
 						    uint8_t vlan_stats_enabled);
 extern int rtnl_link_bridge_get_vlan_stats_enabled(struct rtnl_link *link,
 						   uint8_t *vlan_stats_enabled);
+extern void rtnl_link_bridge_set_nf_call_iptables(struct rtnl_link *link,
+						  uint8_t call_enabled);
+extern void rtnl_link_bridge_set_nf_call_ip6tables(struct rtnl_link *link,
+						   uint8_t call_enabled);
+extern void rtnl_link_bridge_set_nf_call_arptables(struct rtnl_link *link,
+						   uint8_t call_enabled);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/link/info-api.h b/include/netlink/route/link/info-api.h
index 11cffcf..88461bb 100644
--- a/include/netlink/route/link/info-api.h
+++ b/include/netlink/route/link/info-api.h
@@ -9,6 +9,8 @@
 #include <netlink/netlink.h>
 #include <netlink/route/link.h>
 
+#ifndef _NL_NO_WARN_DEPRECATED_HEADER
 #warning "You are including a deprecated header file, include <netlink/route/link.h>."
+#endif
 
 #endif
diff --git a/include/netlink/route/nexthop.h b/include/netlink/route/nexthop.h
index c4a2604..1beb9fa 100644
--- a/include/netlink/route/nexthop.h
+++ b/include/netlink/route/nexthop.h
@@ -30,6 +30,9 @@
 					      struct rtnl_nexthop *,
 					      uint32_t, int);
 
+extern int		rtnl_route_nh_identical(struct rtnl_nexthop *,
+						struct rtnl_nexthop *);
+
 extern void		rtnl_route_nh_dump(struct rtnl_nexthop *,
 					   struct nl_dump_params *);
 
diff --git a/include/netlink/route/route.h b/include/netlink/route/route.h
index 3824762..4377062 100644
--- a/include/netlink/route/route.h
+++ b/include/netlink/route/route.h
@@ -94,6 +94,8 @@
 extern void	rtnl_route_set_ttl_propagate(struct rtnl_route *route,
 					     uint8_t ttl_prop);
 extern int	rtnl_route_get_ttl_propagate(struct rtnl_route *route);
+extern void	rtnl_route_set_nhid(struct rtnl_route *, uint32_t);
+extern uint32_t	rtnl_route_get_nhid(struct rtnl_route *);
 
 extern void	rtnl_route_add_nexthop(struct rtnl_route *,
 				       struct rtnl_nexthop *);
diff --git a/include/netlink/route/tc-api.h b/include/netlink/route/tc-api.h
index 3f400ba..36b1b0f 100644
--- a/include/netlink/route/tc-api.h
+++ b/include/netlink/route/tc-api.h
@@ -10,6 +10,8 @@
 #include <netlink/msg.h>
 #include <netlink/route/tc.h>
 
+#ifndef _NL_NO_WARN_DEPRECATED_HEADER
 #warning "You are including a deprecated header file, include <netlink/route/tc.h>."
+#endif
 
 #endif
diff --git a/include/netlink/utils.h b/include/netlink/utils.h
index 11a6b93..b3a5951 100644
--- a/include/netlink/utils.h
+++ b/include/netlink/utils.h
@@ -331,6 +331,24 @@
 	NL_CAPABILITY_VERSION_3_9_0 = 35,
 #define NL_CAPABILITY_VERSION_3_9_0 NL_CAPABILITY_VERSION_3_9_0
 
+	/**
+	 * The library version is libnl3 3.10.0 or newer. This capability should never be backported.
+	 */
+	NL_CAPABILITY_VERSION_3_10_0 = 36,
+#define NL_CAPABILITY_VERSION_3_10_0 NL_CAPABILITY_VERSION_3_10_0
+
+	/**
+	 * The library version is libnl3 3.11.0 or newer. This capability should never be backported.
+	 */
+	NL_CAPABILITY_VERSION_3_11_0 = 37,
+#define NL_CAPABILITY_VERSION_3_11_0 NL_CAPABILITY_VERSION_3_11_0
+
+	/**
+	 * The library version is libnl3 3.12.0 or newer. This capability should never be backported.
+	 */
+	NL_CAPABILITY_VERSION_3_12_0 = 38,
+#define NL_CAPABILITY_VERSION_3_12_0 NL_CAPABILITY_VERSION_3_12_0
+
 	__NL_CAPABILITY_MAX,
 	NL_CAPABILITY_MAX = (__NL_CAPABILITY_MAX - 1),
 #define NL_CAPABILITY_MAX NL_CAPABILITY_MAX
diff --git a/include/nl-aux-core/nl-core.h b/include/nl-aux-core/nl-core.h
index 5b34bcb..79cec27 100644
--- a/include/nl-aux-core/nl-core.h
+++ b/include/nl-aux-core/nl-core.h
@@ -5,21 +5,16 @@
 
 #include "base/nl-base-utils.h"
 
-#ifdef NL_DEBUG
 #define NL_DBG(LVL, FMT, ARG...)                                           \
 	do {                                                               \
-		if (LVL <= nl_debug) {                                     \
-			int _errsv = errno;                                \
+		if ((NL_DEBUG) && (LVL) <= nl_debug) {                     \
+			const int _errsv = errno;                          \
+                                                                           \
 			fprintf(stderr, "DBG<" #LVL ">%20s:%-4u %s: " FMT, \
 				__FILE__, __LINE__, __func__, ##ARG);      \
 			errno = _errsv;                                    \
 		}                                                          \
 	} while (0)
-#else /* NL_DEBUG */
-#define NL_DBG(LVL, FMT, ARG...) \
-	do {                     \
-	} while (0)
-#endif /* NL_DEBUG */
 
 struct nl_addr;
 void nl_addr_put(struct nl_addr *);
@@ -49,6 +44,12 @@
 _NL_AUTO_DEFINE_FCN_TYPED0(struct nl_sock *, _nl_auto_nl_socket_fcn,
 			   nl_socket_free);
 
+struct nl_cache_mngr;
+void nl_cache_mngr_free(struct nl_cache_mngr *mngr);
+#define _nl_auto_nl_cache_mngr _nl_auto(_nl_auto_nl_cache_mngr_fcn)
+_NL_AUTO_DEFINE_FCN_TYPED0(struct nl_cache_mngr *, _nl_auto_nl_cache_mngr_fcn,
+			   nl_cache_mngr_free);
+
 struct nl_addr *nl_addr_build(int, const void *, size_t);
 
 static inline struct nl_addr *_nl_addr_build(int family, const void *buf)
@@ -56,4 +57,12 @@
 	return nl_addr_build(family, buf, _nl_addr_family_to_size(family));
 }
 
+static inline uint16_t _nla_len(const struct nlattr *nla)
+{
+	_nl_assert(nla);
+	_nl_assert(nla->nla_len >= (uint16_t)NLA_HDRLEN);
+
+	return nla->nla_len - (uint16_t)NLA_HDRLEN;
+}
+
 #endif /* NETLINK_NL_AUTO_H_ */
diff --git a/include/nl-priv-dynamic-route/nl-priv-dynamic-route.h b/include/nl-priv-dynamic-route/nl-priv-dynamic-route.h
index c8168a3..b6192a7 100644
--- a/include/nl-priv-dynamic-route/nl-priv-dynamic-route.h
+++ b/include/nl-priv-dynamic-route/nl-priv-dynamic-route.h
@@ -93,4 +93,19 @@
 
 struct rtnl_tc_ops *rtnl_tc_get_ops(struct rtnl_tc *);
 
+struct rtnl_nexthop {
+	uint8_t rtnh_flags;
+	uint8_t rtnh_flag_mask;
+	uint8_t rtnh_weight;
+	/* 1 byte spare */
+	uint32_t rtnh_ifindex;
+	struct nl_addr *rtnh_gateway;
+	uint32_t ce_mask; /* HACK to support attr macros */
+	struct nl_list_head rtnh_list;
+	uint32_t rtnh_realms;
+	struct nl_addr *rtnh_newdst;
+	struct nl_addr *rtnh_via;
+	struct rtnl_nh_encap *rtnh_encap;
+};
+
 #endif /* __NL_PRIVATE_TYPES_NL_ROUTE_H__ */
diff --git a/include/nl-priv-static-route/nl-priv-static-route.h b/include/nl-priv-static-route/nl-priv-static-route.h
index 65ff531..ad9f98f 100644
--- a/include/nl-priv-static-route/nl-priv-static-route.h
+++ b/include/nl-priv-static-route/nl-priv-static-route.h
@@ -3,6 +3,10 @@
 #ifndef NETLINK_ROUTE_UTILS_PRIV_H_
 #define NETLINK_ROUTE_UTILS_PRIV_H_
 
+#include <netlink/route/link/bridge.h>
+
 extern const uint8_t *const _nltst_map_stat_id_from_IPSTATS_MIB_v2;
+extern int _nl_bridge_fill_vlan_info(struct nl_msg *msg,
+				     struct rtnl_link_bridge_vlan *vlan_info);
 
 #endif
diff --git a/lib/addr.c b/lib/addr.c
index 89a3b38..a3e9f99 100644
--- a/lib/addr.c
+++ b/lib/addr.c
@@ -1042,8 +1042,8 @@
 
 prefix:
 	if (addr->a_family != AF_MPLS &&
-	    addr->a_prefixlen != (8 * addr->a_len)) {
-		snprintf(tmp, sizeof(tmp), "/%u", addr->a_prefixlen);
+	    (unsigned)addr->a_prefixlen != (8u * ((size_t)addr->a_len))) {
+		snprintf(tmp, sizeof(tmp), "/%d", addr->a_prefixlen);
 		strncat(buf, tmp, size - strlen(buf) - 1);
 	}
 
diff --git a/lib/attr.c b/lib/attr.c
index b8b9837..a365cf2 100644
--- a/lib/attr.c
+++ b/lib/attr.c
@@ -129,7 +129,7 @@
  */
 int nla_len(const struct nlattr *nla)
 {
-	return nla->nla_len - NLA_HDRLEN;
+	return _nla_len(nla);
 }
 
 /**
@@ -146,6 +146,8 @@
  */
 int nla_ok(const struct nlattr *nla, int remaining)
 {
+	_NL_STATIC_ASSERT(sizeof(*nla) == NLA_HDRLEN);
+
 	return remaining >= (int) sizeof(*nla) &&
 	       nla->nla_len >= sizeof(*nla) &&
 	       nla->nla_len <= remaining;
@@ -204,7 +206,7 @@
 	else if (pt->type != NLA_UNSPEC)
 		minlen = nla_attr_minlen[pt->type];
 
-	if (nla_len(nla) < minlen)
+	if (_nla_len(nla) < minlen)
 		return -NLE_RANGE;
 
 	if (pt->maxlen && nla_len(nla) > pt->maxlen)
@@ -457,14 +459,14 @@
 struct nlattr *nla_reserve(struct nl_msg *msg, int attrtype, int attrlen)
 {
 	struct nlattr *nla;
-	int tlen;
+	size_t tlen;
 
 	if (attrlen < 0)
 		return NULL;
 
 	tlen = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) + nla_total_size(attrlen);
 
-	if (tlen > msg->nm_size)
+	if (tlen > msg->nm_size || tlen > UINT32_MAX)
 		return NULL;
 
 	nla = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
@@ -736,7 +738,7 @@
 {
 	int64_t tmp = 0;
 
-	if (nla && nla_len(nla) >= sizeof(tmp))
+	if (nla && _nla_len(nla) >= sizeof(tmp))
 		memcpy(&tmp, nla_data(nla), sizeof(tmp));
 
 	return tmp;
@@ -766,7 +768,7 @@
 {
 	uint64_t tmp = 0;
 
-	if (nla && nla_len(nla) >= sizeof(tmp))
+	if (nla && _nla_len(nla) >= sizeof(tmp))
 		memcpy(&tmp, nla_data(nla), sizeof(tmp));
 
 	return tmp;
diff --git a/lib/cache.c b/lib/cache.c
index dd059c1..bae641d 100644
--- a/lib/cache.c
+++ b/lib/cache.c
@@ -808,7 +808,7 @@
 			 */
 			if (nl_object_update(old, obj) == 0) {
 				if (cb_v2) {
-					cb_v2(cache, clone, obj, diff,
+					cb_v2(cache, clone, old, diff,
 					      NL_ACT_CHANGE, data);
 					nl_object_put(clone);
 				} else if (cb)
diff --git a/lib/cache_mngr.c b/lib/cache_mngr.c
index f16882f..8d8e262 100644
--- a/lib/cache_mngr.c
+++ b/lib/cache_mngr.c
@@ -37,6 +37,8 @@
 #include "nl-priv-dynamic-core/cache-api.h"
 #include "nl-aux-core/nl-core.h"
 
+#define NL_ALLOCATED_SYNC_SOCK 4
+
 /** @cond SKIP */
 struct nl_cache_mngr
 {
@@ -58,10 +60,9 @@
 	struct nl_cache_ops *ops = ca->ca_cache->c_ops;
 
 	NL_DBG(2, "Including object %p into cache %p\n", obj, ca->ca_cache);
-#ifdef NL_DEBUG
-	if (nl_debug >= 4)
+
+	if (NL_DEBUG && nl_debug >= 4)
 		nl_object_dump(obj, &nl_debug_dp);
-#endif
 
 	if (ops->co_event_filter)
 		if (ops->co_event_filter(ca->ca_cache, obj) != NL_OK)
@@ -93,10 +94,9 @@
 
 	NL_DBG(2, "Cache manager %p, handling new message %p as event\n",
 	       mngr, msg);
-#ifdef NL_DEBUG
-	if (nl_debug >= 4)
+
+	if (NL_DEBUG && nl_debug >= 4)
 		nl_msg_dump(msg, stderr);
-#endif
 
 	if (mngr->cm_protocol != protocol)
 		BUG();
@@ -151,62 +151,75 @@
 int nl_cache_mngr_alloc(struct nl_sock *sk, int protocol, int flags,
 			struct nl_cache_mngr **result)
 {
-	struct nl_cache_mngr *mngr;
-	int err = -NLE_NOMEM;
+	return nl_cache_mngr_alloc_ex(sk, NULL, protocol, flags, result);
+}
+
+/**
+ * Allocate new cache manager, with custom callback on refill socket
+ * @arg sk		Netlink socket or NULL to auto allocate
+ * @arg sync_sk		Blocking Netlink socket for cache refills
+ * @arg protocol	Netlink protocol this manager is used for
+ * @arg flags		Flags (\c NL_AUTO_PROVIDE)
+ * @arg result		Result pointer
+ *
+ * Same as \f nl_cache_mngr_alloc, but sets custom refill socket
+ * Note: ownership of the sync_sk passes to the cache manager
+ */
+int nl_cache_mngr_alloc_ex(struct nl_sock *sk, struct nl_sock *sync_sk, int protocol, int flags,
+			struct nl_cache_mngr **result)
+{
+	_nl_auto_nl_cache_mngr struct nl_cache_mngr *mngr = NULL;
+	int err;
 
 	/* Catch abuse of flags */
 	if (flags & NL_ALLOCATED_SOCK)
 		BUG();
+	flags = flags & NL_AUTO_PROVIDE;
 
 	mngr = calloc(1, sizeof(*mngr));
 	if (!mngr)
 		return -NLE_NOMEM;
 
+	mngr->cm_flags = flags;
+
 	if (!sk) {
 		if (!(sk = nl_socket_alloc()))
-			goto errout;
-
-		flags |= NL_ALLOCATED_SOCK;
+			return -NLE_NOMEM;
+		mngr->cm_flags |= NL_ALLOCATED_SOCK;
 	}
-
 	mngr->cm_sock = sk;
+
+	if(!sync_sk) {
+		if (!(sync_sk = nl_socket_alloc()))
+			return -NLE_NOMEM;
+		mngr->cm_flags |= NL_ALLOCATED_SYNC_SOCK;
+	}
+	mngr->cm_sync_sock = sync_sk;
+
 	mngr->cm_nassocs = NASSOC_INIT;
 	mngr->cm_protocol = protocol;
-	mngr->cm_flags = flags;
 	mngr->cm_assocs = calloc(mngr->cm_nassocs,
 				 sizeof(struct nl_cache_assoc));
 	if (!mngr->cm_assocs)
-		goto errout;
+		return -NLE_NOMEM;
 
 	/* Required to receive async event notifications */
 	nl_socket_disable_seq_check(mngr->cm_sock);
 
 	if ((err = nl_connect(mngr->cm_sock, protocol)) < 0)
-		goto errout;
+		return err;
 
 	if ((err = nl_socket_set_nonblocking(mngr->cm_sock)) < 0)
-		goto errout;
+		return err;
 
-	/* Create and allocate socket for sync cache fills */
-	mngr->cm_sync_sock = nl_socket_alloc();
-	if (!mngr->cm_sync_sock) {
-		err = -NLE_NOMEM;
-		goto errout;
-	}
 	if ((err = nl_connect(mngr->cm_sync_sock, protocol)) < 0)
-		goto errout_free_sync_sock;
+		return err;
 
 	NL_DBG(1, "Allocated cache manager %p, protocol %d, %d caches\n",
 	       mngr, protocol, mngr->cm_nassocs);
 
-	*result = mngr;
+	*result = _nl_steal_pointer(&mngr);
 	return 0;
-
-errout_free_sync_sock:
-	nl_socket_free(mngr->cm_sync_sock);
-errout:
-	nl_cache_mngr_free(mngr);
-	return err;
 }
 
 /**
@@ -624,14 +637,15 @@
 	if (mngr->cm_sock)
 		nl_close(mngr->cm_sock);
 
-	if (mngr->cm_sync_sock) {
+	if (mngr->cm_sync_sock)
 		nl_close(mngr->cm_sync_sock);
-		nl_socket_free(mngr->cm_sync_sock);
-	}
 
 	if (mngr->cm_flags & NL_ALLOCATED_SOCK)
 		nl_socket_free(mngr->cm_sock);
 
+	if (mngr->cm_flags & NL_ALLOCATED_SYNC_SOCK)
+		nl_socket_free(mngr->cm_sync_sock);
+
 	for (i = 0; i < mngr->cm_nassocs; i++) {
 		if (mngr->cm_assocs[i].ca_cache) {
 			nl_cache_mngt_unprovide(mngr->cm_assocs[i].ca_cache);
diff --git a/lib/fib_lookup/lookup.c b/lib/fib_lookup/lookup.c
index 7ff2684..260f1d9 100644
--- a/lib/fib_lookup/lookup.c
+++ b/lib/fib_lookup/lookup.c
@@ -236,7 +236,7 @@
 	fr.fl_fwmark = fwmark != UINT_LEAST64_MAX ? fwmark : 0;
 	fr.fl_tos = tos >= 0 ? tos : 0;
 	fr.fl_scope = scope >= 0 ? scope : RT_SCOPE_UNIVERSE;
-	fr.tb_id_in = table >= 0 ? table : RT_TABLE_UNSPEC;
+	fr.tb_id_in = table >= 0 ? (unsigned)table : (unsigned)RT_TABLE_UNSPEC;
 
 	addr = flnl_request_get_addr(req);
 	if (!addr)
diff --git a/lib/genl/genl.c b/lib/genl/genl.c
index 2e52aae..41add3a 100644
--- a/lib/genl/genl.c
+++ b/lib/genl/genl.c
@@ -116,12 +116,14 @@
 int genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen)
 {
 	struct genlmsghdr *ghdr;
+	int l;
 
 	if (!nlmsg_valid_hdr(nlh, GENL_HDRLEN))
 		return 0;
 
 	ghdr = nlmsg_data(nlh);
-	if (genlmsg_len(ghdr) < NLMSG_ALIGN(hdrlen))
+	l = genlmsg_len(ghdr);
+	if (l < 0 || ((unsigned)l) < NLMSG_ALIGN(hdrlen))
 		return 0;
 
 	return 1;
diff --git a/lib/genl/mngt.c b/lib/genl/mngt.c
index e55256c..06a0253 100644
--- a/lib/genl/mngt.c
+++ b/lib/genl/mngt.c
@@ -248,7 +248,7 @@
 		goto errout;
 	}
 
-	if (ops->co_hdrsize < GENL_HDRSIZE(0)) {
+	if (ops->co_hdrsize < (int)GENL_HDRSIZE(0)) {
 		err = -NLE_INVAL;
 		goto errout;
 	}
diff --git a/lib/mpls.c b/lib/mpls.c
index d0189bf..f5b4f27 100644
--- a/lib/mpls.c
+++ b/lib/mpls.c
@@ -26,7 +26,7 @@
 		uint32_t label = (entry & MPLS_LS_LABEL_MASK) >> MPLS_LS_LABEL_SHIFT;
 		int len = snprintf(dest, destlen, "%u", label);
 
-		if (len >= destlen)
+		if (len < 0 || (unsigned)len >= destlen)
 			break;
 
 		/* Is this the end? */
diff --git a/lib/msg.c b/lib/msg.c
index 0e1592a..64e6b2f 100644
--- a/lib/msg.c
+++ b/lib/msg.c
@@ -36,7 +36,7 @@
 #include "nl-priv-dynamic-core/cache-api.h"
 #include "nl-aux-core/nl-core.h"
 
-static size_t default_msg_size;
+static size_t default_msg_size; /* GLOBAL! */
 
 static void _nl_init init_msg_size(void)
 {
@@ -168,7 +168,10 @@
 
 int nlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen)
 {
-	if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
+	int s;
+
+	s = nlmsg_msg_size(hdrlen);
+	if (s < 0 || nlh->nlmsg_len < ((unsigned)s))
 		return 0;
 
 	return 1;
@@ -183,7 +186,7 @@
 {
 	return (remaining >= (int)sizeof(struct nlmsghdr) &&
 		nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
-		nlh->nlmsg_len <= remaining);
+		nlh->nlmsg_len <= ((unsigned)remaining));
 }
 
 /**
@@ -369,8 +372,11 @@
  */
 void nlmsg_set_default_size(size_t max)
 {
-	if (max < nlmsg_total_size(0))
-		max = nlmsg_total_size(0);
+	size_t s;
+
+	s = nlmsg_total_size(0);
+	if (max < s)
+		max = s;
 
 	default_msg_size = max;
 }
@@ -841,7 +847,7 @@
 {
 	char *data = nlmsg_data(hdr);
 
-	if (*payloadlen < GENL_HDRLEN)
+	if (*payloadlen < (int)GENL_HDRLEN)
 		return data;
 
 	print_genl_hdr(ofd, data);
@@ -917,10 +923,12 @@
 {
 	struct nlmsghdr *hdr = nlmsg_hdr(msg);
 	struct nlmsgerr *err = nlmsg_data(hdr);
+	int l;
 
 	fprintf(ofd, "  [ERRORMSG] %zu octets\n", sizeof(*err));
 
-	if (nlmsg_len(hdr) >= sizeof(*err)) {
+	l = nlmsg_len(hdr);
+	if (l >= 0 && ((unsigned)l) >= sizeof(*err)) {
 		struct nl_msg *errmsg;
 
 		fprintf(ofd, "    .error = %d \"%s\"\n", err->error,
diff --git a/lib/netfilter/log_msg_obj.c b/lib/netfilter/log_msg_obj.c
index 6b6ff07..6e8b554 100644
--- a/lib/netfilter/log_msg_obj.c
+++ b/lib/netfilter/log_msg_obj.c
@@ -377,7 +377,9 @@
 
 void nfnl_log_msg_set_hwaddr(struct nfnl_log_msg *msg, uint8_t *hwaddr, int len)
 {
-	if (len > sizeof(msg->log_msg_hwaddr))
+	if (len < 0)
+		len = 0;
+	else if (((unsigned)len) > sizeof(msg->log_msg_hwaddr))
 		len = sizeof(msg->log_msg_hwaddr);
 	msg->log_msg_hwaddr_len = len;
 	memcpy(msg->log_msg_hwaddr, hwaddr, len);
diff --git a/lib/netfilter/queue_msg_obj.c b/lib/netfilter/queue_msg_obj.c
index 000669b..210351b 100644
--- a/lib/netfilter/queue_msg_obj.c
+++ b/lib/netfilter/queue_msg_obj.c
@@ -378,9 +378,10 @@
 void nfnl_queue_msg_set_hwaddr(struct nfnl_queue_msg *msg, uint8_t *hwaddr,
 			       int len)
 {
-	if (len > sizeof(msg->queue_msg_hwaddr))
+	if (len < 0)
+		len = 0;
+	else if (((unsigned)len) > sizeof(msg->queue_msg_hwaddr))
 		len = sizeof(msg->queue_msg_hwaddr);
-
 	msg->queue_msg_hwaddr_len = len;
 	memcpy(msg->queue_msg_hwaddr, hwaddr, len);
 	msg->ce_mask |= QUEUE_MSG_ATTR_HWADDR;
diff --git a/lib/nl.c b/lib/nl.c
index 7d729a7..a24c026 100644
--- a/lib/nl.c
+++ b/lib/nl.c
@@ -659,7 +659,7 @@
 {
 	ssize_t n;
 	int flags = 0;
-	static int page_size = 0;
+	static int page_size = 0; /* GLOBAL! */
 	struct iovec iov;
 	struct msghdr msg = {
 		.msg_name = (void *) nla,
@@ -680,7 +680,7 @@
 	if (page_size == 0)
 		page_size = getpagesize() * 4;
 
-	iov.iov_len = sk->s_bufsize ? sk->s_bufsize : page_size;
+	iov.iov_len = sk->s_bufsize ? sk->s_bufsize : ((size_t)page_size);
 	iov.iov_base = malloc(iov.iov_len);
 
 	if (!iov.iov_base) {
@@ -734,7 +734,7 @@
 		goto retry;
 	}
 
-	if (iov.iov_len < n || (msg.msg_flags & MSG_TRUNC)) {
+	if (iov.iov_len < ((size_t)n) || (msg.msg_flags & MSG_TRUNC)) {
 		void *tmp;
 
 		/* respond with error to an incomplete message */
@@ -964,7 +964,8 @@
 		else if (hdr->nlmsg_type == NLMSG_ERROR) {
 			struct nlmsgerr *e = nlmsg_data(hdr);
 
-			if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) {
+			if (hdr->nlmsg_len <
+			    ((unsigned)nlmsg_size(sizeof(*e)))) {
 				/* Truncated error message, the default action
 				 * is to stop parsing. The user may overrule
 				 * this action by returning NL_SKIP or
diff --git a/lib/route/addr.c b/lib/route/addr.c
index 1f72c53..34b834e 100644
--- a/lib/route/addr.c
+++ b/lib/route/addr.c
@@ -642,7 +642,7 @@
 		return NULL;
 
 	nl_list_for_each_entry(a, &cache->c_items, ce_list) {
-		if (ifindex && a->a_ifindex != ifindex)
+		if (ifindex != 0 && a->a_ifindex != ((unsigned)ifindex))
 			continue;
 
 		if (a->ce_mask & ADDR_ATTR_LOCAL &&
diff --git a/lib/route/class.c b/lib/route/class.c
index 29ba809..c04b112 100644
--- a/lib/route/class.c
+++ b/lib/route/class.c
@@ -359,7 +359,8 @@
 		return NULL;
 
 	nl_list_for_each_entry(class, &cache->c_items, ce_list) {
-		if (class->c_handle == handle && class->c_ifindex == ifindex) {
+		if (class->c_handle == handle &&
+		    class->c_ifindex == ((unsigned)ifindex)) {
 			nl_object_get((struct nl_object *) class);
 			return class;
 		}
@@ -390,7 +391,8 @@
 		return NULL;
 
 	nl_list_for_each_entry(class, &cache->c_items, ce_list) {
-		if (class->c_parent == parent && class->c_ifindex == ifindex) {
+		if (class->c_parent == parent &&
+		    class->c_ifindex == ((unsigned)ifindex)) {
 			nl_object_get((struct nl_object *) class);
 			return class;
 		}
diff --git a/lib/route/cls.c b/lib/route/cls.c
index 8f970b1..b207a09 100644
--- a/lib/route/cls.c
+++ b/lib/route/cls.c
@@ -393,7 +393,7 @@
 
 	nl_list_for_each_entry(cls, &cache->c_items, ce_list) {
 		if ((cls->c_parent == parent) &&
-		    (cls->c_ifindex == ifindex)&&
+		    cls->c_ifindex == ((unsigned)ifindex) &&
 		    (cls->c_handle == handle)) {
 			nl_object_get((struct nl_object *) cls);
 			return cls;
@@ -429,9 +429,9 @@
 
 	nl_list_for_each_entry(cls, &cache->c_items, ce_list) {
 		if ((cls->c_parent == parent) &&
-		    (cls->c_ifindex == ifindex) &&
+		    cls->c_ifindex == ((unsigned)ifindex) &&
 		    (cls->c_prio == prio)) {
-			nl_object_get((struct nl_object *) cls);
+			nl_object_get((struct nl_object *)cls);
 			return cls;
 		}
 	}
diff --git a/lib/route/cls/ematch.c b/lib/route/cls/ematch.c
index 5a5a210..6b4dfde 100644
--- a/lib/route/cls/ematch.c
+++ b/lib/route/cls/ematch.c
@@ -451,7 +451,7 @@
 		NL_DBG(3, "parsing ematch attribute %d, len=%u\n",
 			  nmatches+1, nla_len(a));
 
-		if (nla_len(a) < sizeof(*hdr)) {
+		if (_nla_len(a) < sizeof(*hdr)) {
 			err = -NLE_INVAL;
 			goto errout;
 		}
diff --git a/lib/route/cls/ematch/container.c b/lib/route/cls/ematch/container.c
index ea2d166..d7e5a3a 100644
--- a/lib/route/cls/ematch/container.c
+++ b/lib/route/cls/ematch/container.c
@@ -10,7 +10,7 @@
 
 #include "nl-route.h"
 
-static int container_parse(struct rtnl_ematch *e, void *data, size_t len __attribute__((unused)))
+static int container_parse(struct rtnl_ematch *e, void *data, size_t len)
 {
 	/*
 	The kernel may provide more than 4 bytes of data in the future and we want
diff --git a/lib/route/cls/ematch_grammar.l b/lib/route/cls/ematch_grammar.l
index 1035079..c78c41c 100644
--- a/lib/route/cls/ematch_grammar.l
+++ b/lib/route/cls/ematch_grammar.l
@@ -3,6 +3,9 @@
  * Copyright (c) 2010-2013 Thomas Graf <tgraf@suug.ch>
  */
 
+%top{
+ #include "nl-default.h"
+}
 %{
  #include <linux/tc_ematch/tc_em_cmp.h>
 
diff --git a/lib/route/cls/ematch_syntax.y b/lib/route/cls/ematch_syntax.y
index 961ee43..bc0cc9c 100644
--- a/lib/route/cls/ematch_syntax.y
+++ b/lib/route/cls/ematch_syntax.y
@@ -4,8 +4,9 @@
  */
 
 %{
-#include <linux/tc_ematch/tc_em_meta.h>
+#include "nl-default.h"
 
+#include <linux/tc_ematch/tc_em_meta.h>
 #include <linux/tc_ematch/tc_em_cmp.h>
 
 #include <netlink/netlink.h>
diff --git a/lib/route/cls/u32.c b/lib/route/cls/u32.c
index 52f6e31..9821c77 100644
--- a/lib/route/cls/u32.c
+++ b/lib/route/cls/u32.c
@@ -154,7 +154,7 @@
 		sel = u->cu_selector->d_data;
 		pcnt_size = sizeof(struct tc_u32_pcnt) +
 				(sel->nkeys * sizeof(uint64_t));
-		if (nla_len(tb[TCA_U32_PCNT]) < pcnt_size) {
+		if (_nla_len(tb[TCA_U32_PCNT]) < pcnt_size) {
 			err = -NLE_INVAL;
 			goto errout;
 		}
diff --git a/lib/route/link.c b/lib/route/link.c
index ce1355b..e634a8b 100644
--- a/lib/route/link.c
+++ b/lib/route/link.c
@@ -440,7 +440,7 @@
 		/* beware: @st might not be the full struct, only fields up to
 		 * tx_compressed are present. See _nl_offsetofend() above. */
 
-		if (nla_len(tb[IFLA_STATS]) >= _nl_offsetofend (struct rtnl_link_stats, rx_nohandler))
+		if (_nla_len(tb[IFLA_STATS]) >= _nl_offsetofend (struct rtnl_link_stats, rx_nohandler))
 			link->l_stats[RTNL_LINK_RX_NOHANDLER] = st->rx_nohandler;
 		else
 			link->l_stats[RTNL_LINK_RX_NOHANDLER] = 0;
@@ -1320,7 +1320,7 @@
 		return NULL;
 
 	nl_list_for_each_entry(link, &cache->c_items, ce_list) {
-		if (link->l_index == ifindex) {
+		if (link->l_index == ((unsigned)ifindex)) {
 			nl_object_get((struct nl_object *) link);
 			return link;
 		}
diff --git a/lib/route/link/bridge.c b/lib/route/link/bridge.c
index 5b44164..a9c7eea 100644
--- a/lib/route/link/bridge.c
+++ b/lib/route/link/bridge.c
@@ -23,6 +23,7 @@
 #include "nl-route.h"
 #include "link-api.h"
 #include "nl-priv-dynamic-core/nl-core.h"
+#include "nl-priv-static-route/nl-priv-static-route.h"
 
 #define VLAN_VID_MASK           0x0fff /* VLAN Identifier */
 
@@ -33,7 +34,7 @@
 #define BRIDGE_ATTR_FLAGS		(1 << 3)
 #define BRIDGE_ATTR_PORT_VLAN           (1 << 4)
 #define BRIDGE_ATTR_HWMODE		(1 << 5)
-#define BRIDGE_ATTR_SELF		(1 << 6)
+#define BRIDGE_ATTR_CONFIG_MODE		(1 << 6)
 
 #define PRIV_FLAG_NEW_ATTRS		(1 << 0)
 
@@ -43,7 +44,7 @@
 	uint8_t			b_priv_flags; /* internal flags */
 	uint16_t		b_hwmode;
 	uint16_t		b_priority;
-	uint16_t		b_self; /* here for comparison reasons */
+	uint16_t		b_config_mode;
 	uint32_t		b_cost;
 	uint32_t		b_flags;
 	uint32_t		b_flags_mask;
@@ -57,6 +58,24 @@
 		addr[nr / 32] |= (((uint32_t) 1) << (nr % 32));
 }
 
+static void unset_bit(unsigned nr, uint32_t *addr)
+{
+	if (nr < RTNL_LINK_BRIDGE_VLAN_BITMAP_MAX)
+		addr[nr / 32] &= ~(((uint32_t) 1) << (nr % 32));
+}
+
+static bool vlan_id_untagged(struct rtnl_link_bridge_vlan *vlan_info, uint16_t vid)
+{
+	uint32_t mask, bit;
+
+	_nl_assert(vid / 32u < ARRAY_SIZE(vlan_info->untagged_bitmap));
+
+	mask = vlan_info->untagged_bitmap[vid / 32];
+	bit = (((uint32_t) 1) << vid % 32);
+
+	return mask & bit;
+}
+
 static int find_next_bit(int i, uint32_t x)
 {
 	int j;
@@ -239,16 +258,149 @@
 	return 0;
 }
 
+int _nl_bridge_fill_vlan_info(struct nl_msg *msg, struct rtnl_link_bridge_vlan * vlan_info)
+{
+	struct bridge_vlan_info vinfo;
+	int i = -1, j, k;
+	int start = -1, prev = -1;
+	int done;
+	bool untagged = false;
+
+	for (k = 0; k < RTNL_LINK_BRIDGE_VLAN_BITMAP_LEN; k++)
+	{
+		int base_bit;
+		uint32_t a = vlan_info->vlan_bitmap[k];
+
+		base_bit = k * 32;
+		i = -1;
+		done = 0;
+		while (!done)
+		{
+			j = find_next_bit(i, a);
+			if (j > 0)
+			{
+				/* Skip if id equal to pvid */
+				if (vlan_info->pvid != 0 && j - 1 + base_bit == vlan_info->pvid)
+					goto nxt;
+				/* first hit of any bit */
+				if (start < 0 && prev < 0)
+				{
+					start = prev = j - 1 + base_bit;
+					/* Start range attribute */
+					untagged = vlan_id_untagged(vlan_info,start);
+					vinfo.flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+					vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+					vinfo.vid = start;
+					goto nxt;
+				}
+				/* this bit is a continuation of prior bits */
+				if (j - 2 + base_bit == prev)
+				{
+					prev++;
+					/* Hit end of untagged/tagged range */
+					if (untagged != vlan_id_untagged(vlan_info,prev))
+					{
+						/* put vlan into attributes */
+						if (start == prev-1)
+						{
+							/* only 1 vid in range */
+							vinfo.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN;
+							NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+						}
+						else
+						{
+							/* end of untagged/tagged range */
+							NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+
+							vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END;
+							vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+							vinfo.vid = prev-1;
+							NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+						}
+						/* start of new range */
+						untagged = !untagged;
+						vinfo.flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+						vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+						vinfo.vid = prev;
+					}
+					goto nxt;
+				}
+			}
+			else
+				done = 1;
+
+			if (start >= 0)
+			{
+				if (done && k < RTNL_LINK_BRIDGE_VLAN_BITMAP_LEN - 1)
+					break;
+
+				if (vinfo.flags & BRIDGE_VLAN_INFO_RANGE_BEGIN && start != prev)
+				{
+					NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+
+					vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END;
+					vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+					vinfo.vid = prev;
+					NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+				}
+				else if (start == prev)
+				{
+					vinfo.flags = untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+					vinfo.vid = start;
+					NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+				}
+
+				if (done)
+					break;
+			}
+			if (j > 0)
+			{
+				start = prev = j - 1 + base_bit;
+				untagged = vlan_id_untagged(vlan_info,start);
+				vinfo.flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+				vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+				vinfo.vid = start;
+			}
+nxt:
+			i = j;
+		}
+	}
+
+	if (vlan_info->pvid != 0)
+	{
+		untagged = vlan_id_untagged(vlan_info,vlan_info->pvid);
+		vinfo.flags = BRIDGE_VLAN_INFO_PVID;
+		vinfo.flags |= untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0;
+		vinfo.vid = vlan_info->pvid;
+		NLA_PUT(msg,IFLA_BRIDGE_VLAN_INFO,sizeof(vinfo),&vinfo);
+	}
+
+	return 0;
+
+nla_put_failure:
+	return -NLE_MSGSIZE;
+}
+
 static int bridge_fill_af(struct rtnl_link *link, struct nl_msg *msg,
 		   void *data)
 {
 	struct bridge_data *bd = data;
 
-	if ((bd->ce_mask & BRIDGE_ATTR_SELF)||(bd->ce_mask & BRIDGE_ATTR_HWMODE))
-		NLA_PUT_U16(msg, IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF);
-
 	if (bd->ce_mask & BRIDGE_ATTR_HWMODE)
+	{
 		NLA_PUT_U16(msg, IFLA_BRIDGE_MODE, bd->b_hwmode);
+		bd->b_config_mode = BRIDGE_FLAGS_SELF;
+		bd->ce_mask |= BRIDGE_ATTR_CONFIG_MODE;
+	}
+
+	if (bd->ce_mask & BRIDGE_ATTR_CONFIG_MODE)
+		NLA_PUT_U16(msg, IFLA_BRIDGE_FLAGS, bd->b_config_mode);
+
+	if (bd->ce_mask & BRIDGE_ATTR_PORT_VLAN) {
+		if (_nl_bridge_fill_vlan_info(msg, &bd->vlan_info)) {
+			goto nla_put_failure;
+		}
+	}
 
 	return 0;
 
@@ -445,7 +597,7 @@
 		      memcmp(&a->vlan_info, &b->vlan_info,
 			     sizeof(struct rtnl_link_bridge_vlan)));
 	diff |= _DIFF(BRIDGE_ATTR_HWMODE, a->b_hwmode != b->b_hwmode);
-	diff |= _DIFF(BRIDGE_ATTR_SELF, a->b_self != b->b_self);
+	diff |= _DIFF(BRIDGE_ATTR_CONFIG_MODE, a->b_config_mode != b->b_config_mode);
 
 	if (flags & LOOSE_COMPARISON)
 		diff |= _DIFF(BRIDGE_ATTR_FLAGS,
@@ -773,8 +925,31 @@
 
 	IS_BRIDGE_LINK_ASSERT(link);
 
-	bd->b_self |= 1;
-	bd->ce_mask |= BRIDGE_ATTR_SELF;
+	bd->b_config_mode = BRIDGE_FLAGS_SELF;
+	bd->ce_mask |= BRIDGE_ATTR_CONFIG_MODE;
+
+	return 0;
+}
+
+/**
+ * Set link change type to master
+ * @arg link		Link Object of type bridge
+ *
+ * This will set the bridge change flag to master, meaning that changes to
+ * be applied with this link object will be applied directly to the virtual
+ * device in a bridge instead of the physical device.
+ *
+ * @return 0 on success or negative error code
+ * @return -NLE_OPNOTSUP Link is not a bridge
+ */
+int rtnl_link_bridge_set_master(struct rtnl_link *link)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	bd->b_config_mode = BRIDGE_FLAGS_MASTER;
+	bd->ce_mask |= BRIDGE_ATTR_CONFIG_MODE;
 
 	return 0;
 }
@@ -915,6 +1090,146 @@
 
 /** @} */
 
+/**
+ * Enable the ability to set vlan info
+ * @arg link		Link object of type bridge
+ *
+ * @return 0 on success or negative error code
+ * @return -NLE_OPNOTSUP 	Link is not a bridge
+ */
+int rtnl_link_bridge_enable_vlan(struct rtnl_link *link)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	bd->ce_mask |= BRIDGE_ATTR_PORT_VLAN;
+
+	return 0;
+}
+
+/**
+ * @name Quality of Service
+ * @{
+ */
+
+/**
+ * Set port vlan membership range
+ * @arg link		Link object of type bridge
+ * @arg start		Start of membership range.
+ * @arg end			End of membership range.
+ * @arg untagged	Set membership range to be untagged.
+ *
+ * This will set the vlan membership range for a bridge port.
+ * This will unset the untagged membership if untagged is false.
+ * Supported range is 1-4094
+ *
+ * @return 0 on success or negative error code
+ * @return -NLE_NOATTR		if port vlan attribute not present
+ * @return -NLE_OPNOTSUP 	Link is not a bridge
+ * @return -NLE_INVAL 		range is not in supported range.
+ */
+int rtnl_link_bridge_set_port_vlan_map_range (struct rtnl_link *link, uint16_t start, uint16_t end, int untagged)
+{
+	struct rtnl_link_bridge_vlan * vinfo;
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	vinfo = rtnl_link_bridge_get_port_vlan(link);
+
+	if (!vinfo)
+		return -NLE_NOATTR;
+
+	if (start == 0 || start > end || end >= VLAN_VID_MASK)
+		return -NLE_INVAL;
+
+	for (uint16_t i = start; i <= end; i++)
+	{
+		set_bit(i,vinfo->vlan_bitmap);
+		if (untagged) {
+			set_bit(i,vinfo->untagged_bitmap);
+		} else {
+			unset_bit(i,vinfo->untagged_bitmap);
+		}
+	}
+	return 0;
+}
+
+/**
+ * Unset port vlan membership range
+ * @arg link		Link object of type bridge
+ * @arg start		Start of membership range.
+ * @arg end			End of membership range.
+ *
+ * This will unset the vlan membership range for a bridge port
+ * for both tagged and untagged membership.
+ * Supported range is 1-4094
+ *
+ * @return 0 on success or negative error code
+ * @return -NLE_NOATTR		if port vlan attribute not present
+ * @return -NLE_OPNOTSUP 	Link is not a bridge
+ * @return -NLE_INVAL 		range is not in supported range.
+ */
+int rtnl_link_bridge_unset_port_vlan_map_range (struct rtnl_link *link, uint16_t start, uint16_t end)
+{
+	struct rtnl_link_bridge_vlan * vinfo;
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	vinfo = rtnl_link_bridge_get_port_vlan(link);
+
+	if (!vinfo)
+		return -NLE_NOATTR;
+
+	if (start == 0 || start > end || end >= VLAN_VID_MASK)
+		return -NLE_INVAL;
+
+	for (uint16_t i = start; i <= end; i++)
+	{
+		unset_bit(i,vinfo->vlan_bitmap);
+		unset_bit(i,vinfo->untagged_bitmap);
+	}
+	return 0;
+}
+
+/**
+ * Set port primary vlan id
+ * @arg link		Link object of type bridge
+ * @arg pvid		PVID to set.
+ * @arg untagged	Set vlan id to be untagged.
+ *
+ * This will set the primary vlan id for a bridge port.
+ * Supported range is 0-4094, Setting pvid to 0 will unset it.
+ * You will most likely want to set/unset pvid in the vlan map.
+ * @see rtnl_link_bridge_set_port_vlan_map_range()
+ * @see rtnl_link_bridge_unset_port_vlan_map_range()
+ *
+ * @return 0 on success or negative error code
+ * @return -NLE_NOATTR		if port vlan attribute not present
+ * @return -NLE_OPNOTSUP 	Link is not a bridge
+ * @return -NLE_INVAL 		PVID is above supported range.
+ */
+int rtnl_link_bridge_set_port_vlan_pvid (struct rtnl_link *link, uint16_t pvid)
+{
+	struct rtnl_link_bridge_vlan * vinfo;
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	vinfo = rtnl_link_bridge_get_port_vlan(link);
+
+	if (!vinfo)
+		return -NLE_NOATTR;
+
+	if (pvid >= VLAN_VID_MASK)
+		return -NLE_INVAL;
+
+	vinfo->pvid = pvid;
+
+	return 0;
+}
+
+/** @} */
+
 int rtnl_link_bridge_pvid(struct rtnl_link *link)
 {
 	struct bridge_data *bd;
diff --git a/lib/route/link/bridge_info.c b/lib/route/link/bridge_info.c
index 61b885f..5d69b5c 100644
--- a/lib/route/link/bridge_info.c
+++ b/lib/route/link/bridge_info.c
@@ -21,18 +21,33 @@
 #define BRIDGE_ATTR_VLAN_FILTERING (1 << 0)
 #define BRIDGE_ATTR_VLAN_PROTOCOL (1 << 1)
 #define BRIDGE_ATTR_VLAN_STATS_ENABLED (1 << 2)
+#define BRIDGE_ATTR_AGEING_TIME (1 << 3)
+#define BRIDGE_ATTR_VLAN_DEFAULT_PVID (1 << 4)
+#define BRIDGE_ATTR_NF_CALL_IPTABLES (1 << 5)
+#define BRIDGE_ATTR_NF_CALL_IP6TABLES (1 << 6)
+#define BRIDGE_ATTR_NF_CALL_ARPTABLES (1 << 7)
 
 struct bridge_info {
 	uint32_t ce_mask; /* to support attr macros */
+	uint32_t b_ageing_time;
 	uint16_t b_vlan_protocol;
+	uint16_t b_vlan_default_pvid;
 	uint8_t b_vlan_filtering;
 	uint8_t b_vlan_stats_enabled;
+	uint8_t b_nf_call_iptables;
+	uint8_t b_nf_call_ip6tables;
+	uint8_t b_nf_call_arptables;
 };
 
 static const struct nla_policy bi_attrs_policy[IFLA_BR_MAX + 1] = {
+	[IFLA_BR_AGEING_TIME] = { .type = NLA_U32 },
+	[IFLA_BR_VLAN_DEFAULT_PVID] = { .type = NLA_U16 },
 	[IFLA_BR_VLAN_FILTERING] = { .type = NLA_U8 },
 	[IFLA_BR_VLAN_PROTOCOL] = { .type = NLA_U16 },
 	[IFLA_BR_VLAN_STATS_ENABLED] = { .type = NLA_U8 },
+	[IFLA_BR_NF_CALL_IPTABLES] = { .type = NLA_U8 },
+	[IFLA_BR_NF_CALL_IP6TABLES] = { .type = NLA_U8 },
+	[IFLA_BR_NF_CALL_ARPTABLES] = { .type = NLA_U8 },
 };
 
 static inline struct bridge_info *bridge_info(struct rtnl_link *link)
@@ -75,6 +90,17 @@
 
 	bi = link->l_info;
 
+	if (tb[IFLA_BR_AGEING_TIME]) {
+		bi->b_ageing_time = nla_get_u32(tb[IFLA_BR_AGEING_TIME]);
+		bi->ce_mask |= BRIDGE_ATTR_AGEING_TIME;
+	}
+
+	if (tb[IFLA_BR_VLAN_DEFAULT_PVID]) {
+		bi->b_vlan_default_pvid =
+			nla_get_u16(tb[IFLA_BR_VLAN_DEFAULT_PVID]);
+		bi->ce_mask |= BRIDGE_ATTR_VLAN_DEFAULT_PVID;
+	}
+
 	if (tb[IFLA_BR_VLAN_FILTERING]) {
 		bi->b_vlan_filtering = nla_get_u8(tb[IFLA_BR_VLAN_FILTERING]);
 		bi->ce_mask |= BRIDGE_ATTR_VLAN_FILTERING;
@@ -92,6 +118,24 @@
 		bi->ce_mask |= BRIDGE_ATTR_VLAN_STATS_ENABLED;
 	}
 
+	if (tb[IFLA_BR_NF_CALL_IPTABLES]) {
+		bi->b_nf_call_iptables =
+			nla_get_u8(tb[IFLA_BR_NF_CALL_IPTABLES]);
+		bi->ce_mask |= BRIDGE_ATTR_NF_CALL_IPTABLES;
+	}
+
+	if (tb[IFLA_BR_NF_CALL_IP6TABLES]) {
+		bi->b_nf_call_ip6tables =
+			nla_get_u8(tb[IFLA_BR_NF_CALL_IP6TABLES]);
+		bi->ce_mask |= BRIDGE_ATTR_NF_CALL_IP6TABLES;
+	}
+
+	if (tb[IFLA_BR_NF_CALL_ARPTABLES]) {
+		bi->b_nf_call_arptables =
+			nla_get_u8(tb[IFLA_BR_NF_CALL_ARPTABLES]);
+		bi->ce_mask |= BRIDGE_ATTR_NF_CALL_ARPTABLES;
+	}
+
 	return 0;
 }
 
@@ -104,9 +148,16 @@
 	if (!data)
 		return -NLE_MSGSIZE;
 
+	if (bi->ce_mask & BRIDGE_ATTR_AGEING_TIME)
+		NLA_PUT_U32(msg, IFLA_BR_AGEING_TIME, bi->b_ageing_time);
+
 	if (bi->ce_mask & BRIDGE_ATTR_VLAN_FILTERING)
 		NLA_PUT_U8(msg, IFLA_BR_VLAN_FILTERING, bi->b_vlan_filtering);
 
+	if (bi->ce_mask & BRIDGE_ATTR_VLAN_DEFAULT_PVID)
+		NLA_PUT_U16(msg, IFLA_BR_VLAN_DEFAULT_PVID,
+			    bi->b_vlan_default_pvid);
+
 	if (bi->ce_mask & BRIDGE_ATTR_VLAN_PROTOCOL)
 		NLA_PUT_U16(msg, IFLA_BR_VLAN_PROTOCOL,
 			    htons(bi->b_vlan_protocol));
@@ -115,6 +166,18 @@
 		NLA_PUT_U8(msg, IFLA_BR_VLAN_STATS_ENABLED,
 			   bi->b_vlan_stats_enabled);
 
+	if (bi->ce_mask & BRIDGE_ATTR_NF_CALL_IPTABLES)
+		NLA_PUT_U8(msg, IFLA_BR_NF_CALL_IPTABLES,
+			   bi->b_nf_call_iptables);
+
+	if (bi->ce_mask & BRIDGE_ATTR_NF_CALL_IP6TABLES)
+		NLA_PUT_U8(msg, IFLA_BR_NF_CALL_IP6TABLES,
+			   bi->b_nf_call_ip6tables);
+
+	if (bi->ce_mask & BRIDGE_ATTR_NF_CALL_ARPTABLES)
+		NLA_PUT_U8(msg, IFLA_BR_NF_CALL_ARPTABLES,
+			   bi->b_nf_call_arptables);
+
 	nla_nest_end(msg, data);
 	return 0;
 
@@ -144,6 +207,53 @@
 	} while (0)
 
 /**
+ * Set ageing time for dynamic forwarding entries
+ * @arg link		Link object of type bridge
+ * @arg ageing_time	Interval to set.
+ *
+ * @return void
+ */
+void rtnl_link_bridge_set_ageing_time(struct rtnl_link *link,
+				      uint32_t ageing_time)
+{
+	struct bridge_info *bi = bridge_info(link);
+
+	IS_BRIDGE_INFO_ASSERT(link);
+
+	bi->b_ageing_time = ageing_time;
+
+	bi->ce_mask |= BRIDGE_ATTR_AGEING_TIME;
+}
+
+/**
+ * Get ageing time for dynamic forwarding entries
+ * @arg link		Link object of type bridge
+ * @arg ageing_time	Output argument.
+ *
+ * @see rtnl_link_bridge_set_ageing_time()
+ * @return Zero on success, otherwise a negative error code.
+ * @retval -NLE_NOATTR
+ * @retval -NLE_INVAL
+ */
+int rtnl_link_bridge_get_ageing_time(struct rtnl_link *link,
+				     uint32_t *ageing_time)
+{
+	struct bridge_info *bi = bridge_info(link);
+
+	IS_BRIDGE_INFO_ASSERT(link);
+
+	if (!(bi->ce_mask & BRIDGE_ATTR_AGEING_TIME))
+		return -NLE_NOATTR;
+
+	if (!ageing_time)
+		return -NLE_INVAL;
+
+	*ageing_time = bi->b_ageing_time;
+
+	return 0;
+}
+
+/**
  * Set VLAN filtering flag
  * @arg link		Link object of type bridge
  * @arg vlan_filtering	VLAN_filtering boolean flag to set.
@@ -244,6 +354,56 @@
 }
 
 /**
+ * Set VLAN default pvid
+ * @arg link			Link object of type bridge
+ * @arg default pvid	VLAN default pvid to set.
+ *
+ * @see rtnl_link_bridge_get_vlan_default_pvid()
+ *
+ * @return void
+ */
+void rtnl_link_bridge_set_vlan_default_pvid(struct rtnl_link *link,
+					    uint16_t default_pvid)
+{
+	struct bridge_info *bi = bridge_info(link);
+
+	IS_BRIDGE_INFO_ASSERT(link);
+
+	bi->b_vlan_default_pvid = default_pvid;
+
+	bi->ce_mask |= BRIDGE_ATTR_VLAN_DEFAULT_PVID;
+}
+
+/**
+ * Get VLAN default pvid
+ * @arg link			Link object of type bridge
+ * @arg default_pvid	Output argument.
+ *
+ * @see rtnl_link_bridge_set_vlan_default_pvid()
+ *
+ * @return Zero on success, otherwise a negative error code.
+ * @retval -NLE_NOATTR
+ * @retval -NLE_INVAL
+ */
+int rtnl_link_bridge_get_vlan_default_pvid(struct rtnl_link *link,
+					   uint16_t *default_pvid)
+{
+	struct bridge_info *bi = bridge_info(link);
+
+	IS_BRIDGE_INFO_ASSERT(link);
+
+	if (!(bi->ce_mask & BRIDGE_ATTR_VLAN_DEFAULT_PVID))
+		return -NLE_NOATTR;
+
+	if (!default_pvid)
+		return -NLE_INVAL;
+
+	*default_pvid = bi->b_vlan_default_pvid;
+
+	return 0;
+}
+
+/**
  * Set VLAN stats enabled flag
  * @arg link		Link object of type bridge
  * @arg vlan_stats_enabled	VLAN stats enabled flag to set
@@ -293,6 +453,63 @@
 	return 0;
 }
 
+/**
+ * Set call enabled flag for passing IPv4 traffic to iptables
+ * @arg link		Link object of type bridge
+ * @arg call_enabled	call enabled boolean flag to set.
+ *
+ * @return void
+ */
+void rtnl_link_bridge_set_nf_call_iptables(struct rtnl_link *link,
+					   uint8_t call_enabled)
+{
+	struct bridge_info *bi = bridge_info(link);
+
+	IS_BRIDGE_INFO_ASSERT(link);
+
+	bi->b_nf_call_iptables = call_enabled;
+
+	bi->ce_mask |= BRIDGE_ATTR_NF_CALL_IPTABLES;
+}
+
+/**
+ * Set call enabled flag for passing IPv6 traffic to ip6tables
+ * @arg link		Link object of type bridge
+ * @arg call_enabled	call enabled boolean flag to set.
+ *
+ * @return void
+ */
+void rtnl_link_bridge_set_nf_call_ip6tables(struct rtnl_link *link,
+					    uint8_t call_enabled)
+{
+	struct bridge_info *bi = bridge_info(link);
+
+	IS_BRIDGE_INFO_ASSERT(link);
+
+	bi->b_nf_call_ip6tables = call_enabled;
+
+	bi->ce_mask |= BRIDGE_ATTR_NF_CALL_IP6TABLES;
+}
+
+/**
+ * Set call enabled flag for passing ARP traffic to arptables
+ * @arg link		Link object of type bridge
+ * @arg call_enabled	call enabled boolean flag to set.
+ *
+ * @return void
+ */
+void rtnl_link_bridge_set_nf_call_arptables(struct rtnl_link *link,
+					    uint8_t call_enabled)
+{
+	struct bridge_info *bi = bridge_info(link);
+
+	IS_BRIDGE_INFO_ASSERT(link);
+
+	bi->b_nf_call_arptables = call_enabled;
+
+	bi->ce_mask |= BRIDGE_ATTR_NF_CALL_ARPTABLES;
+}
+
 static void _nl_init bridge_info_init(void)
 {
 	rtnl_link_register_info(&bridge_info_ops);
diff --git a/lib/route/link/can.c b/lib/route/link/can.c
index da00144..86e6684 100644
--- a/lib/route/link/can.c
+++ b/lib/route/link/can.c
@@ -168,7 +168,7 @@
 		ci->ci_mask |= CAN_HAS_DATA_BITTIMING_CONST;
 	}
 
-	if (xstats && nla_len(xstats) >= sizeof(ci->ci_device_stats)) {
+	if (xstats && _nla_len(xstats) >= sizeof(ci->ci_device_stats)) {
 		nla_memcpy(&ci->ci_device_stats, xstats, sizeof(ci->ci_device_stats));
 		ci->ci_mask |= CAN_HAS_DEVICE_STATS;
 	}
diff --git a/lib/route/link/macsec.c b/lib/route/link/macsec.c
index a989eed..7f4f568 100644
--- a/lib/route/link/macsec.c
+++ b/lib/route/link/macsec.c
@@ -369,7 +369,8 @@
 	struct macsec_info *a = link_a->l_info;
 	struct macsec_info *b = link_b->l_info;
 	int diff = 0;
-	uint32_t attrs = flags & LOOSE_COMPARISON ? b->ce_mask : ~0;
+	uint32_t attrs = flags & LOOSE_COMPARISON ? b->ce_mask :
+						    ~((uint32_t)0u);
 
 #define _DIFF(ATTR, EXPR) ATTR_DIFF(attrs, ATTR, a, b, EXPR)
 	if (a->ce_mask & MACSEC_ATTR_SCI && b->ce_mask & MACSEC_ATTR_SCI)
diff --git a/lib/route/link/macvlan.c b/lib/route/link/macvlan.c
index 5452d9e..fa7d0bb 100644
--- a/lib/route/link/macvlan.c
+++ b/lib/route/link/macvlan.c
@@ -225,7 +225,7 @@
 {
 	struct macvlan_info *mvi = link->l_info;
 	struct nlattr *data, *datamac = NULL;
-	int i, ret;
+	int ret;
 
 	if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
 		return -NLE_MSGSIZE;
@@ -239,6 +239,8 @@
 		NLA_PUT_U16(msg, IFLA_MACVLAN_FLAGS, mvi->mvi_flags);
 
 	if (mvi->mvi_mask & MACVLAN_HAS_MACADDR) {
+		uint32_t i;
+
 		NLA_PUT_U32(msg, IFLA_MACVLAN_MACADDR_MODE, mvi->mvi_macmode);
 		datamac = nla_nest_start(msg, IFLA_MACVLAN_MACADDR_DATA);
 		if (!datamac)
@@ -345,7 +347,6 @@
 int rtnl_link_macvlan_set_mode(struct rtnl_link *link, uint32_t mode)
 {
 	struct macvlan_info *mvi = link->l_info;
-	int i;
 
 	IS_MACVLAN_LINK_ASSERT(link);
 
@@ -353,6 +354,8 @@
 	mvi->mvi_mask |= MACVLAN_HAS_MODE;
 
 	if (mode != MACVLAN_MODE_SOURCE) {
+		uint32_t i;
+
 		for (i = 0; i < mvi->mvi_maccount; i++)
 			nl_addr_put(mvi->mvi_macaddr[i]);
 		free(mvi->mvi_macaddr);
diff --git a/lib/route/link/sriov.c b/lib/route/link/sriov.c
index d47d1dd..98087e2 100644
--- a/lib/route/link/sriov.c
+++ b/lib/route/link/sriov.c
@@ -633,7 +633,7 @@
 		if (t[IFLA_VF_SPOOFCHK]) {
 			vf_spoofchk = nla_data(t[IFLA_VF_SPOOFCHK]);
 
-			if (vf_spoofchk->setting != -1) {
+			if (vf_spoofchk->setting != ((uint32_t)-1)) {
 				vf_data->vf_spoofchk = vf_spoofchk->setting ? 1 : 0;
 				vf_data->ce_mask |= SRIOV_ATTR_SPOOFCHK;
 			}
@@ -662,7 +662,7 @@
 		if (t[IFLA_VF_RSS_QUERY_EN]) {
 			vf_rss_query = nla_data(t[IFLA_VF_RSS_QUERY_EN]);
 
-			if (vf_rss_query->setting != -1) {
+			if (vf_rss_query->setting != ((uint32_t)-1)) {
 				vf_data->vf_rss_query_en = vf_rss_query->setting ? 1 : 0;
 				vf_data->ce_mask |= SRIOV_ATTR_RSS_QUERY_EN;
 			}
@@ -702,7 +702,7 @@
 		if (t[IFLA_VF_TRUST]) {
 			vf_trust = nla_data(t[IFLA_VF_TRUST]);
 
-			if (vf_trust->setting != -1) {
+			if (vf_trust->setting != ((uint32_t)-1)) {
 				vf_data->vf_trust = vf_trust->setting ? 1 : 0;
 				vf_data->ce_mask |= SRIOV_ATTR_TRUST;
 			}
diff --git a/lib/route/link/vlan.c b/lib/route/link/vlan.c
index 60e4358..75842d8 100644
--- a/lib/route/link/vlan.c
+++ b/lib/route/link/vlan.c
@@ -123,7 +123,7 @@
 		memset(vi->vi_ingress_qos, 0, sizeof(vi->vi_ingress_qos));
 
 		nla_for_each_nested(nla, tb[IFLA_VLAN_INGRESS_QOS], remaining) {
-			if (nla_len(nla) < sizeof(*map))
+			if (_nla_len(nla) < sizeof(*map))
 				return -NLE_INVAL;
 
 			map = nla_data(nla);
@@ -154,7 +154,7 @@
 		int remaining, i = 0;
 
 		nla_for_each_nested(nla, tb[IFLA_VLAN_EGRESS_QOS], remaining) {
-			if (nla_len(nla) < sizeof(*map))
+			if (_nla_len(nla) < sizeof(*map))
 				return -NLE_INVAL;
 			i++;
 		}
diff --git a/lib/route/link/vxlan.c b/lib/route/link/vxlan.c
index 4606dd5..0603bf5 100644
--- a/lib/route/link/vxlan.c
+++ b/lib/route/link/vxlan.c
@@ -617,7 +617,8 @@
 	struct vxlan_info *a = link_a->l_info;
 	struct vxlan_info *b = link_b->l_info;
 	int diff = 0;
-	uint32_t attrs = flags & LOOSE_COMPARISON ? b->ce_mask : ~0;
+	uint32_t attrs = flags & LOOSE_COMPARISON ? b->ce_mask :
+						    ~((uint32_t)0u);
 
 #define _DIFF(ATTR, EXPR) ATTR_DIFF(attrs, ATTR, a, b, EXPR)
 	diff |= _DIFF(VXLAN_ATTR_ID, a->vxi_id != b->vxi_id);
diff --git a/lib/route/neigh.c b/lib/route/neigh.c
index 9150024..56c1e09 100644
--- a/lib/route/neigh.c
+++ b/lib/route/neigh.c
@@ -242,9 +242,7 @@
 		uint16_t	n_vlan;
 		char		n_addr[0];
 	} _nl_packed *nkey;
-#ifdef NL_DEBUG
 	char buf[INET6_ADDRSTRLEN+5];
-#endif
 
 	if (neigh->n_family == AF_BRIDGE) {
 		if (neigh->n_lladdr)
@@ -678,8 +676,8 @@
 	struct rtnl_neigh *neigh;
 
 	nl_list_for_each_entry(neigh, &cache->c_items, ce_list) {
-		if (neigh->n_ifindex == ifindex &&
-		    neigh->n_family == dst->a_family &&
+		if (neigh->n_ifindex == ((unsigned)ifindex) &&
+		    neigh->n_family == ((unsigned)dst->a_family) &&
 		    !nl_addr_cmp(neigh->n_dst, dst)) {
 			nl_object_get((struct nl_object *) neigh);
 			return neigh;
@@ -704,9 +702,9 @@
 	struct rtnl_neigh *neigh;
 
 	nl_list_for_each_entry(neigh, &cache->c_items, ce_list) {
-		if (neigh->n_ifindex == ifindex &&
-		    neigh->n_vlan == vlan &&
-		    neigh->n_lladdr && !nl_addr_cmp(neigh->n_lladdr, lladdr)) {
+		if ((neigh->n_ifindex == (unsigned)ifindex) &&
+		    neigh->n_vlan == vlan && neigh->n_lladdr &&
+		    !nl_addr_cmp(neigh->n_lladdr, lladdr)) {
 			nl_object_get((struct nl_object *) neigh);
 			return neigh;
 		}
@@ -1014,7 +1012,7 @@
 {
 	if (!nocheck) {
 		if (neigh->ce_mask & NEIGH_ATTR_FAMILY) {
-			if (new->a_family != neigh->n_family)
+			if (neigh->n_family != ((unsigned)new->a_family))
 				return -NLE_AF_MISMATCH;
 		} else {
 			neigh->n_family = new->a_family;
diff --git a/lib/route/neightbl.c b/lib/route/neightbl.c
index a699867..8d5db8a 100644
--- a/lib/route/neightbl.c
+++ b/lib/route/neightbl.c
@@ -534,8 +534,7 @@
 
 	nl_list_for_each_entry(nt, &cache->c_items, ce_list) {
 		if (!strcasecmp(nt->nt_name, name) &&
-		    ((!ifindex && !nt->nt_parms.ntp_ifindex) ||
-		     (ifindex && ifindex == nt->nt_parms.ntp_ifindex))) {
+		    ((unsigned)ifindex) == nt->nt_parms.ntp_ifindex) {
 			nl_object_get((struct nl_object *)nt);
 			return nt;
 		}
diff --git a/lib/route/nexthop.c b/lib/route/nexthop.c
index 962f2ba..7e0df61 100644
--- a/lib/route/nexthop.c
+++ b/lib/route/nexthop.c
@@ -136,6 +136,23 @@
 	return diff;
 }
 
+/**
+ * Check if the fixed attributes of two nexthops are identical, and may
+ * only differ in flags or weight.
+ *
+ * @arg a		a nexthop
+ * @arg b		another nexthop
+ *
+ * @return true if both nexthop have equal attributes, otherwise false.
+ */
+int rtnl_route_nh_identical(struct rtnl_nexthop *a, struct rtnl_nexthop *b)
+{
+	return !rtnl_route_nh_compare(a, b,
+				      NH_ATTR_IFINDEX | NH_ATTR_REALMS |
+				      NH_ATTR_GATEWAY | NH_ATTR_NEWDST |
+				      NH_ATTR_VIA | NH_ATTR_ENCAP, 0);
+}
+
 static void nh_dump_line(struct rtnl_nexthop *nh, struct nl_dump_params *dp)
 {
 	struct nl_cache *link_cache;
diff --git a/lib/route/nh.c b/lib/route/nh.c
index 1072172..3dfe558 100644
--- a/lib/route/nh.c
+++ b/lib/route/nh.c
@@ -176,7 +176,7 @@
 	unsigned int lkey_sz;
 	struct nexthop_hash_key {
 		uint32_t nh_id;
-	} __attribute__((packed)) lkey;
+	} _nl_packed lkey;
 
 	lkey_sz = sizeof(lkey);
 	lkey.nh_id = nexthop->nh_id;
@@ -336,7 +336,7 @@
 		unsigned len;
 
 		data = nla_data(tb[NHA_GROUP]);
-		len = nla_len(tb[NHA_GROUP]);
+		len = _nla_len(tb[NHA_GROUP]);
 		size = len / sizeof(struct nexthop_grp);
 
 		err = rtnl_nh_grp_info(size, (const struct nexthop_grp *)data,
@@ -454,7 +454,7 @@
 		return NULL;
 
 	nl_list_for_each_entry(nh, &cache->c_items, ce_list) {
-		if (nh->nh_id == nhid) {
+		if (nh->nh_id == ((unsigned)nhid)) {
 			nl_object_get((struct nl_object *)nh);
 			return nh;
 		}
diff --git a/lib/route/nl-route.h b/lib/route/nl-route.h
index 28d0166..3b7679b 100644
--- a/lib/route/nl-route.h
+++ b/lib/route/nl-route.h
@@ -80,21 +80,6 @@
 	void *priv; /* private data for encap type */
 };
 
-struct rtnl_nexthop {
-	uint8_t rtnh_flags;
-	uint8_t rtnh_flag_mask;
-	uint8_t rtnh_weight;
-	/* 1 byte spare */
-	uint32_t rtnh_ifindex;
-	struct nl_addr *rtnh_gateway;
-	uint32_t ce_mask; /* HACK to support attr macros */
-	struct nl_list_head rtnh_list;
-	uint32_t rtnh_realms;
-	struct nl_addr *rtnh_newdst;
-	struct nl_addr *rtnh_via;
-	struct rtnl_nh_encap *rtnh_encap;
-};
-
 struct rtnl_ratespec {
 	uint64_t rs_rate64;
 	uint16_t rs_overhead;
@@ -171,7 +156,7 @@
 	sysconfdir = getenv("NLSYSCONFDIR");
 
 	if (!sysconfdir)
-		sysconfdir = SYSCONFDIR;
+		sysconfdir = _NL_SYSCONFDIR_LIBNL;
 
 	return asprintf(strp, "%s/%s", sysconfdir, filename);
 }
diff --git a/lib/route/pktloc_grammar.l b/lib/route/pktloc_grammar.l
index 2db229d..b2a2236 100644
--- a/lib/route/pktloc_grammar.l
+++ b/lib/route/pktloc_grammar.l
@@ -1,3 +1,6 @@
+%top{
+ #include "nl-default.h"
+}
 %{
  #include <linux/tc_ematch/tc_em_cmp.h>
 
diff --git a/lib/route/pktloc_syntax.y b/lib/route/pktloc_syntax.y
index 661463a..505f4c8 100644
--- a/lib/route/pktloc_syntax.y
+++ b/lib/route/pktloc_syntax.y
@@ -1,4 +1,6 @@
 %{
+#include "nl-default.h"
+
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/pktloc.h>
diff --git a/lib/route/qdisc.c b/lib/route/qdisc.c
index 67ea358..9887e0d 100644
--- a/lib/route/qdisc.c
+++ b/lib/route/qdisc.c
@@ -388,7 +388,8 @@
 		return NULL;
 
 	nl_list_for_each_entry(q, &cache->c_items, ce_list) {
-		if (q->q_parent == parent && q->q_ifindex == ifindex) {
+		if (q->q_parent == parent &&
+		    q->q_ifindex == ((unsigned)ifindex)) {
 			nl_object_get((struct nl_object *) q);
 			return q;
 		}
@@ -420,7 +421,8 @@
 		return NULL;
 
 	nl_list_for_each_entry(q, &cache->c_items, ce_list) {
-		if ((q->q_ifindex == ifindex) && (!strcmp(q->q_kind, kind))) {
+		if ((q->q_ifindex == ((unsigned)ifindex)) &&
+		    (!strcmp(q->q_kind, kind))) {
 			nl_object_get((struct nl_object *) q);
 			return q;
 		}
@@ -452,7 +454,8 @@
 		return NULL;
 
 	nl_list_for_each_entry(q, &cache->c_items, ce_list) {
-		if (q->q_handle == handle && q->q_ifindex == ifindex) {
+		if (q->q_handle == handle &&
+		    q->q_ifindex == ((unsigned)ifindex)) {
 			nl_object_get((struct nl_object *) q);
 			return q;
 		}
diff --git a/lib/route/qdisc/netem.c b/lib/route/qdisc/netem.c
index 6dde4f0..8ced034 100644
--- a/lib/route/qdisc/netem.c
+++ b/lib/route/qdisc/netem.c
@@ -972,7 +972,7 @@
 		return -nl_syserr2nlerr(errno);
 
 	data = (int16_t *) calloc(MAXDIST, sizeof(int16_t));
-	line = (char *) calloc(sizeof(char), len + 1);
+	line = (char *) calloc(len + 1, sizeof(char));
 	if (!data || !line) {
 	    fclose(f);
 	    return -NLE_NOMEM;
diff --git a/lib/route/qdisc/tbf.c b/lib/route/qdisc/tbf.c
index 67996eb..a054a14 100644
--- a/lib/route/qdisc/tbf.c
+++ b/lib/route/qdisc/tbf.c
@@ -139,9 +139,9 @@
 	uint32_t rtab[RTNL_TC_RTABLE_SIZE], ptab[RTNL_TC_RTABLE_SIZE];
 	struct tc_tbf_qopt opts;
 	struct rtnl_tbf *tbf = data;
-	int required = TBF_ATTR_RATE | TBF_ATTR_LIMIT;
+	const uint32_t REQUIRED = TBF_ATTR_RATE | TBF_ATTR_LIMIT;
 
-	if ((tbf->qt_mask & required) != required)
+	if ((tbf->qt_mask & REQUIRED) != REQUIRED)
 		return -NLE_MISSING_ATTR;
 
 	memset(&opts, 0, sizeof(opts));
diff --git a/lib/route/route_obj.c b/lib/route/route_obj.c
index ce68259..094ae53 100644
--- a/lib/route/route_obj.c
+++ b/lib/route/route_obj.c
@@ -48,8 +48,6 @@
 	NLHDR_COMMON
 
 	uint8_t rt_family;
-	uint8_t rt_dst_len;
-	uint8_t rt_src_len;
 	uint8_t rt_tos;
 	uint8_t rt_protocol;
 	uint8_t rt_scope;
@@ -65,6 +63,7 @@
 	uint32_t rt_metrics[RTAX_MAX];
 	uint32_t rt_metrics_mask;
 	uint32_t rt_nr_nh;
+	uint32_t rt_nhid;
 	struct nl_addr *rt_pref_src;
 	struct nl_list_head rt_nexthops;
 	struct rtnl_rtcacheinfo rt_cacheinfo;
@@ -90,6 +89,7 @@
 #define ROUTE_ATTR_REALMS    0x010000
 #define ROUTE_ATTR_CACHEINFO 0x020000
 #define ROUTE_ATTR_TTL_PROPAGATE 0x040000
+#define ROUTE_ATTR_NHID      0x080000
 /** @endcond */
 
 static void route_constructor(struct nl_object *c)
@@ -194,6 +194,9 @@
 	if (r->ce_mask & ROUTE_ATTR_TOS && r->rt_tos != 0)
 		nl_dump(p, "tos %#x ", r->rt_tos);
 
+	if (r->ce_mask & ROUTE_ATTR_NHID)
+		nl_dump(p, "nhid %u ", r->rt_nhid);
+
 	if (r->ce_mask & ROUTE_ATTR_MULTIPATH) {
 		struct rtnl_nexthop *nh;
 
@@ -284,6 +287,9 @@
 			r->rt_ttl_propagate ? "enabled" : "disabled");
 	}
 
+	if (r->ce_mask & ROUTE_ATTR_NHID)
+		nl_dump(p, "nhid %u ", r->rt_nhid);
+
 	nl_dump(p, "\n");
 
 	if (r->ce_mask & ROUTE_ATTR_MULTIPATH) {
@@ -345,9 +351,7 @@
 		uint32_t	rt_prio;
 		char 		rt_addr[0];
 	} _nl_packed *rkey = NULL;
-#ifdef NL_DEBUG
 	char buf[INET6_ADDRSTRLEN+5];
-#endif
 
 	if (route->rt_dst)
 		addr = route->rt_dst;
@@ -371,10 +375,11 @@
 
 	*hashkey = nl_hash(rkey, rkey_sz, 0) % table_sz;
 
-	NL_DBG(5, "route %p key (fam %d tos %d table %d addr %s) keysz %d "
-		"hash 0x%x\n", route, rkey->rt_family, rkey->rt_tos,
-		rkey->rt_table, nl_addr2str(addr, buf, sizeof(buf)),
-		rkey_sz, *hashkey);
+	NL_DBG(5,
+	       "route %p key (fam %d tos %d table %d prio %d addr %s) keysz %d hash 0x%x\n",
+	       route, rkey->rt_family, rkey->rt_tos, rkey->rt_table,
+	       rkey->rt_prio, nl_addr2str(addr, buf, sizeof(buf)), rkey_sz,
+	       *hashkey);
 
 	return;
 }
@@ -416,6 +421,7 @@
 		      nl_addr_cmp(a->rt_pref_src, b->rt_pref_src));
 	diff |= _DIFF(ROUTE_ATTR_TTL_PROPAGATE,
 		      a->rt_ttl_propagate != b->rt_ttl_propagate);
+	diff |= _DIFF(ROUTE_ATTR_NHID, a->rt_nhid != b->rt_nhid);
 
 	if (flags & LOOSE_COMPARISON) {
 		nl_list_for_each_entry(nh_b, &b->rt_nexthops, rtnh_list) {
@@ -451,7 +457,7 @@
 			found = 0;
 			nl_list_for_each_entry(nh_b, &b->rt_nexthops,
 					       rtnh_list) {
-				if (!rtnl_route_nh_compare(nh_a, nh_b, ~0, 0)) {
+				if (rtnl_route_nh_identical(nh_a, nh_b)) {
 					found = 1;
 					break;
 				}
@@ -466,7 +472,7 @@
 			found = 0;
 			nl_list_for_each_entry(nh_a, &a->rt_nexthops,
 					       rtnh_list) {
-				if (!rtnl_route_nh_compare(nh_a, nh_b, ~0, 0)) {
+				if (rtnl_route_nh_identical(nh_a, nh_b)) {
 					found = 1;
 					break;
 				}
@@ -502,9 +508,7 @@
 	struct rtnl_route *old_route = (struct rtnl_route *) old_obj;
 	struct rtnl_nexthop *new_nh;
 	int action = new_obj->ce_msgtype;
-#ifdef NL_DEBUG
 	char buf[INET6_ADDRSTRLEN+5];
-#endif
 
 	/*
 	 * ipv6 ECMP route notifications from the kernel come as
@@ -542,7 +546,7 @@
 		 * Do not add the nexthop to old route if it was already added before
 		 */
 		nl_list_for_each_entry(old_nh, &old_route->rt_nexthops, rtnh_list) {
-			if (!rtnl_route_nh_compare(old_nh, new_nh, ~0, 0)) {
+			if (rtnl_route_nh_identical(old_nh, new_nh)) {
 				return 0;
 			}
 		}
@@ -578,7 +582,7 @@
 		 */
 		nl_list_for_each_entry(old_nh, &old_route->rt_nexthops,
 			rtnh_list) {
-			if (!rtnl_route_nh_compare(old_nh, new_nh, ~0, 0)) {
+			if (rtnl_route_nh_identical(old_nh, new_nh)) {
 
 				rtnl_route_remove_nexthop(old_route, old_nh);
 
@@ -623,6 +627,7 @@
 	__ADD(ROUTE_ATTR_REALMS, realms),
 	__ADD(ROUTE_ATTR_CACHEINFO, cacheinfo),
 	__ADD(ROUTE_ATTR_TTL_PROPAGATE, ttl_propagate),
+	__ADD(ROUTE_ATTR_NHID, nhid),
 };
 
 static char *route_attrs2str(int attrs, char *buf, size_t len)
@@ -943,9 +948,11 @@
 struct rtnl_nexthop *rtnl_route_nexthop_n(struct rtnl_route *r, int n)
 {
 	struct rtnl_nexthop *nh;
-	uint32_t i;
 
-	if (r->ce_mask & ROUTE_ATTR_MULTIPATH && r->rt_nr_nh > n) {
+	if (r->ce_mask & ROUTE_ATTR_MULTIPATH && n >= 0 &&
+	    ((unsigned)n) < r->rt_nr_nh) {
+		int i;
+
 		i = 0;
 		nl_list_for_each_entry(nh, &r->rt_nexthops, rtnh_list) {
 			if (i == n) return nh;
@@ -970,6 +977,21 @@
 	return route->rt_ttl_propagate;
 }
 
+void rtnl_route_set_nhid(struct rtnl_route *route, uint32_t nhid)
+{
+	route->rt_nhid = nhid;
+
+	if (nhid > 0)
+		route->ce_mask |= ROUTE_ATTR_NHID;
+	else
+		route->ce_mask &= ~ROUTE_ATTR_NHID;
+}
+
+uint32_t rtnl_route_get_nhid(struct rtnl_route *route)
+{
+	return route->rt_nhid;
+}
+
 /** @} */
 
 /**
@@ -1006,7 +1028,7 @@
 		 * is not directly connected
 		 */
 		nl_list_for_each_entry(nh, &route->rt_nexthops, rtnh_list) {
-			if (nh->rtnh_gateway)
+			if (nh->rtnh_gateway || nh->rtnh_via)
 				return RT_SCOPE_UNIVERSE;
 		}
 	}
@@ -1052,6 +1074,7 @@
 	[RTA_TTL_PROPAGATE] = { .type = NLA_U8 },
 	[RTA_ENCAP]	= { .type = NLA_NESTED },
 	[RTA_ENCAP_TYPE] = { .type = NLA_U16 },
+	[RTA_NH_ID]	= { .type = NLA_U32 },
 };
 
 static int parse_multipath(struct rtnl_route *route, struct nlattr *attr)
@@ -1249,7 +1272,7 @@
 			return err;
 
 		for (i = 1; i <= RTAX_MAX; i++) {
-			if (mtb[i] && nla_len(mtb[i]) >= sizeof(uint32_t)) {
+			if (mtb[i] && _nla_len(mtb[i]) >= sizeof(uint32_t)) {
 				uint32_t m = nla_get_u32(mtb[i]);
 
 				err = rtnl_route_set_metric(route, i, m);
@@ -1343,6 +1366,10 @@
 			return err;
 	}
 
+	if (tb[RTA_NH_ID]) {
+		rtnl_route_set_nhid(route, nla_get_u32(tb[RTA_NH_ID]));
+	}
+
 	if (old_nh) {
 		rtnl_route_nh_set_flags(old_nh, rtm->rtm_flags & 0xff);
 		if (route->rt_nr_nh == 0) {
@@ -1445,7 +1472,10 @@
 		nla_nest_end(msg, metrics);
 	}
 
-	if (rtnl_route_get_nnexthops(route) == 1) {
+	/* Nexthop specification and nexthop id are mutually exclusive */
+	if (route->ce_mask & ROUTE_ATTR_NHID) {
+		NLA_PUT_U32(msg, RTA_NH_ID, route->rt_nhid);
+	} else if (rtnl_route_get_nnexthops(route) == 1) {
 		struct rtnl_nexthop *nh;
 
 		nh = rtnl_route_nexthop_n(route, 0);
diff --git a/lib/route/tc.c b/lib/route/tc.c
index a2fd567..bbb2f8c 100644
--- a/lib/route/tc.c
+++ b/lib/route/tc.c
@@ -678,7 +678,7 @@
 	int i;
 
 	for (i = 0; i < 32; i++)
-		if ((((uint32_t)1u) << i) == cell_size)
+		if ((((uint32_t)1u) << i) == ((uint32_t)cell_size))
 			return i;
 
 	return -NLE_INVAL;
diff --git a/lib/socket.c b/lib/socket.c
index 9b42f67..742cdac 100644
--- a/lib/socket.c
+++ b/lib/socket.c
@@ -84,7 +84,7 @@
 static uint32_t generate_local_port(void)
 {
 	int i, j, m;
-	uint16_t n;
+	uint32_t n;
 	static uint16_t idx_state = 0;
 	uint32_t pid = getpid() & 0x3FFFFF;
 
diff --git a/lib/utils.c b/lib/utils.c
index 4f5fd1a..679078e 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -52,7 +52,6 @@
 int nl_debug = 0;
 
 /** @cond SKIP */
-#ifdef NL_DEBUG
 struct nl_dump_params nl_debug_dp = {
 	.dp_type = NL_DUMP_DETAILS,
 };
@@ -61,7 +60,7 @@
 {
 	char *nldbg, *end;
 
-	if ((nldbg = getenv("NLDBG"))) {
+	if (NL_DEBUG && (nldbg = getenv("NLDBG"))) {
 		long level = strtol(nldbg, &end, 0);
 		if (nldbg != end)
 			nl_debug = level;
@@ -69,7 +68,6 @@
 
 	nl_debug_dp.dp_fd = stderr;
 }
-#endif
 
 int __nl_read_num_str_file(const char *path, int (*cb)(long, const char *))
 {
@@ -1071,14 +1069,15 @@
 		 const struct trans_tbl *tbl, size_t tbl_len)
 {
 	size_t i;
+
 	for (i = 0; i < tbl_len; i++) {
-		if (tbl[i].i == type) {
+		if (tbl[i].i == ((uint64_t)type)) {
 			snprintf(buf, len, "%s", tbl[i].a);
 			return buf;
 		}
 	}
 
-	snprintf(buf, len, "0x%x", type);
+	snprintf(buf, len, "0x%x", (unsigned)type);
 	return buf;
 }
 
@@ -1171,7 +1170,7 @@
 			p++;
 
 		t = strchr(p, ',');
-		len = t ? t - p : strlen(p);
+		len = t ? ((size_t)(t - p)) : strlen(p);
 		for (i = 0; i < tbl_len; i++)
 			if (len == strlen(tbl[i].a) &&
 			    !strncasecmp(tbl[i].a, p, len))
@@ -1285,9 +1284,9 @@
 			NL_CAPABILITY_VERSION_3_7_0,
 			NL_CAPABILITY_VERSION_3_8_0,
 			NL_CAPABILITY_VERSION_3_9_0,
-			0,
-			0,
-			0,
+			0, /* NL_CAPABILITY_VERSION_3_10_0 */
+			0, /* NL_CAPABILITY_VERSION_3_11_0 */
+			0, /* NL_CAPABILITY_VERSION_3_12_0 */
 			0,
 			0),
 		/* IMPORTANT: these capability numbers are intended to be universal and stable
diff --git a/lib/xfrm/ae.c b/lib/xfrm/ae.c
index 9af73a8..1898038 100644
--- a/lib/xfrm/ae.c
+++ b/lib/xfrm/ae.c
@@ -122,6 +122,7 @@
 
 #include "nl-default.h"
 
+#include <time.h>
 #include <linux/xfrm.h>
 
 #include <netlink/netlink.h>
diff --git a/lib/xfrm/sa.c b/lib/xfrm/sa.c
index 0c89c71..2fe387d 100644
--- a/lib/xfrm/sa.c
+++ b/lib/xfrm/sa.c
@@ -2372,12 +2372,12 @@
  * @{
  */
 
-static void __attribute__ ((constructor)) xfrm_sa_init(void)
+static void _nl_init xfrm_sa_init(void)
 {
 	nl_cache_mngt_register(&xfrmnl_sa_ops);
 }
 
-static void __attribute__ ((destructor)) xfrm_sa_exit(void)
+static void _nl_exit xfrm_sa_exit(void)
 {
 	nl_cache_mngt_unregister(&xfrmnl_sa_ops);
 }
diff --git a/lib/xfrm/sp.c b/lib/xfrm/sp.c
index a996455..814ac48 100644
--- a/lib/xfrm/sp.c
+++ b/lib/xfrm/sp.c
@@ -41,6 +41,7 @@
 
 #include "nl-default.h"
 
+#include <time.h>
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/object.h>
@@ -1280,7 +1281,7 @@
  *
  * @return     0 if sucessfull, else -1
  */
-int xfrmnl_sp_set_sec_ctx (struct xfrmnl_sp* sp, unsigned int len __attribute__((unused)), unsigned int exttype, unsigned int alg, unsigned int doi, unsigned int ctx_len, char* ctx_str)
+int xfrmnl_sp_set_sec_ctx (struct xfrmnl_sp* sp, unsigned int len, unsigned int exttype, unsigned int alg, unsigned int doi, unsigned int ctx_len, char* ctx_str)
 {
 	/* Free up the old context string and allocate new one */
 	if (sp->sec_ctx)
@@ -1367,12 +1368,15 @@
 struct xfrmnl_user_tmpl *xfrmnl_sp_usertemplate_n(struct xfrmnl_sp *r, int n)
 {
 	struct xfrmnl_user_tmpl *utmpl;
-	uint32_t i;
 
-	if (r->ce_mask & XFRM_SP_ATTR_TMPL && r->nr_user_tmpl > n) {
+	if (r->ce_mask & XFRM_SP_ATTR_TMPL && n >= 0 &&
+	    ((unsigned)n) < r->nr_user_tmpl) {
+		uint32_t i;
+
 		i = 0;
 		nl_list_for_each_entry(utmpl, &r->usertmpl_list, utmpl_list) {
-			if (i == n) return utmpl;
+			if (i == ((unsigned)n))
+				return utmpl;
 			i++;
 		}
 	}
@@ -1449,12 +1453,12 @@
  * @{
  */
 
-static void __attribute__ ((constructor)) xfrm_sp_init(void)
+static void _nl_init xfrm_sp_init(void)
 {
 	nl_cache_mngt_register(&xfrmnl_sp_ops);
 }
 
-static void __attribute__ ((destructor)) xfrm_sp_exit(void)
+static void _nl_exit xfrm_sp_exit(void)
 {
 	nl_cache_mngt_unregister(&xfrmnl_sp_ops);
 }
diff --git a/libnl-3.sym b/libnl-3.sym
index bac4fcd..613529c 100644
--- a/libnl-3.sym
+++ b/libnl-3.sym
@@ -372,3 +372,8 @@
 
 libnl_3_6 {
 } libnl_3_5;
+
+libnl_3_10 {
+global:
+	nl_cache_mngr_alloc_ex;
+} libnl_3_6;
diff --git a/libnl-route-3.sym b/libnl-route-3.sym
index fa7af45..9cb21f7 100644
--- a/libnl-route-3.sym
+++ b/libnl-route-3.sym
@@ -1314,3 +1314,21 @@
 	rtnl_link_bond_set_min_links;
 	rtnl_link_can_get_device_stats;
 } libnl_3_8;
+
+libnl_3_10 {
+global:
+	rtnl_link_bridge_enable_vlan;
+	rtnl_link_bridge_get_ageing_time;
+	rtnl_link_bridge_get_vlan_default_pvid;
+	rtnl_link_bridge_set_ageing_time;
+	rtnl_link_bridge_set_master;
+	rtnl_link_bridge_set_nf_call_arptables;
+	rtnl_link_bridge_set_nf_call_iptables;
+	rtnl_link_bridge_set_nf_call_ip6tables;
+	rtnl_link_bridge_set_port_vlan_map_range;
+	rtnl_link_bridge_set_port_vlan_pvid;
+	rtnl_link_bridge_unset_port_vlan_map_range;
+	rtnl_route_get_nhid;
+	rtnl_route_nh_identical;
+	rtnl_route_set_nhid;
+} libnl_3_9;
diff --git a/libnl_blocklist.txt b/libnl_blocklist.txt
new file mode 100644
index 0000000..ed98696
--- /dev/null
+++ b/libnl_blocklist.txt
@@ -0,0 +1,5 @@
+[cfi]
+
+[integer]
+# Intentional overflows in multiple hash functions
+src:*/lib/hash.c
diff --git a/src/lib/utils.c b/src/lib/utils.c
index 2839e27..8afb926 100644
--- a/src/lib/utils.c
+++ b/src/lib/utils.c
@@ -223,7 +223,7 @@
 	char path[FILENAME_MAX+1];
 
 	snprintf(path, sizeof(path), "%s/%s/%s.so",
-		 PKGLIBDIR, prefix, name);
+		 _NL_PKGLIBDIR, prefix, name);
 
 #ifdef HAVE_DLFCN_H
 	{
diff --git a/tests/check-direct.c b/tests/check-direct.c
index db1f48d..33b7742 100644
--- a/tests/check-direct.c
+++ b/tests/check-direct.c
@@ -5,10 +5,16 @@
 #include <check.h>
 
 #include <linux/snmp.h>
+#include <linux/if_bridge.h>
 
 #include <netlink/route/link.h>
+#include <netlink/route/link/bridge.h>
 
 #include "nl-priv-static-route/nl-priv-static-route.h"
+#include "nl-aux-core/nl-core.h"
+
+#define CASES 5
+#define MAX_ATTR 7
 
 START_TEST(static_checks)
 {
@@ -53,12 +59,133 @@
 }
 END_TEST
 
+static void set_bitmap_range(u_int32_t start, u_int32_t end,
+			     struct rtnl_link_bridge_vlan *vlan_info,
+			     int untagged)
+{
+	for (u_int32_t i = start; i <= end; i++) {
+		vlan_info->vlan_bitmap[i / 32] |= (((uint32_t)1) << (i % 32));
+		if (untagged) {
+			vlan_info->untagged_bitmap[i / 32] |=
+				(((uint32_t)1) << (i % 32));
+		}
+	}
+}
+
+START_TEST(vlan_attribute_check)
+{
+	struct nlmsghdr *nlh;
+	struct nlattr *a;
+	int attr_count, rem;
+	struct bridge_vlan_info *vlan_attr;
+	struct rtnl_link_bridge_vlan vlan_info[CASES];
+	struct bridge_vlan_info expected_attr[CASES][MAX_ATTR];
+
+	for (int i = 0; i < CASES; i++) {
+		memset(&vlan_info[i], 0, sizeof(struct rtnl_link_bridge_vlan));
+		memset(&expected_attr[i], 0,
+		       sizeof(struct bridge_vlan_info) * MAX_ATTR);
+	}
+
+	// Case 1 setting pvid untagged.
+	vlan_info[0].pvid = 1;
+	set_bitmap_range(1, 1, &vlan_info[0], 1);
+	expected_attr[0][0].vid = 1;
+	expected_attr[0][0].flags = BRIDGE_VLAN_INFO_PVID |
+				    BRIDGE_VLAN_INFO_UNTAGGED;
+
+	// Case 2 setting vid range.
+	vlan_info[1].pvid = 0;
+	set_bitmap_range(1, 4094, &vlan_info[1], 0);
+	expected_attr[1][0].vid = 1;
+	expected_attr[1][0].flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+	expected_attr[1][1].vid = 4094;
+	expected_attr[1][1].flags = BRIDGE_VLAN_INFO_RANGE_END;
+
+	// Case 3 interweaving pvid with vid range.
+	vlan_info[2].pvid = 7;
+	set_bitmap_range(1, 27, &vlan_info[2], 0);
+	set_bitmap_range(7, 7, &vlan_info[2], 1);
+	expected_attr[2][0].vid = 1;
+	expected_attr[2][0].flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+	expected_attr[2][1].vid = 6;
+	expected_attr[2][1].flags = BRIDGE_VLAN_INFO_RANGE_END;
+	expected_attr[2][2].vid = 8;
+	expected_attr[2][2].flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+	expected_attr[2][3].vid = 27;
+	expected_attr[2][3].flags = BRIDGE_VLAN_INFO_RANGE_END;
+	expected_attr[2][4].vid = 7;
+	expected_attr[2][4].flags = BRIDGE_VLAN_INFO_PVID |
+				    BRIDGE_VLAN_INFO_UNTAGGED;
+
+	// Case 4 interweaving untagged and tagged vid ranges.
+	vlan_info[3].pvid = 1;
+	set_bitmap_range(1, 1, &vlan_info[3], 1);
+	set_bitmap_range(1, 25, &vlan_info[3], 0);
+	set_bitmap_range(26, 50, &vlan_info[3], 1);
+	set_bitmap_range(51, 75, &vlan_info[3], 0);
+	expected_attr[3][0].vid = 2;
+	expected_attr[3][0].flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+	expected_attr[3][1].vid = 25;
+	expected_attr[3][1].flags = BRIDGE_VLAN_INFO_RANGE_END;
+	expected_attr[3][2].vid = 26;
+	expected_attr[3][2].flags = BRIDGE_VLAN_INFO_RANGE_BEGIN |
+				    BRIDGE_VLAN_INFO_UNTAGGED;
+	expected_attr[3][3].vid = 50;
+	expected_attr[3][3].flags = BRIDGE_VLAN_INFO_RANGE_END |
+				    BRIDGE_VLAN_INFO_UNTAGGED;
+	expected_attr[3][4].vid = 51;
+	expected_attr[3][4].flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+	expected_attr[3][5].vid = 75;
+	expected_attr[3][5].flags = BRIDGE_VLAN_INFO_RANGE_END;
+	expected_attr[3][6].vid = 1;
+	expected_attr[3][6].flags = BRIDGE_VLAN_INFO_PVID |
+				    BRIDGE_VLAN_INFO_UNTAGGED;
+
+	// Case 5 individual vid.
+	vlan_info[4].pvid = 0;
+	set_bitmap_range(5, 5, &vlan_info[4], 0);
+	set_bitmap_range(3067, 3067, &vlan_info[4], 1);
+	expected_attr[4][0].vid = 5;
+	expected_attr[4][0].flags = 0;
+	expected_attr[4][1].vid = 3067;
+	expected_attr[4][1].flags = BRIDGE_VLAN_INFO_UNTAGGED;
+
+	for (int i = 0; i < CASES; i++) {
+		_nl_auto_nl_msg struct nl_msg *msg = nlmsg_alloc();
+		attr_count = 0;
+		ck_assert_msg(msg, "Unable to allocate netlink message");
+		ck_assert_int_eq(0,
+				 _nl_bridge_fill_vlan_info(msg, &vlan_info[i]));
+
+		nlh = nlmsg_hdr(msg);
+
+		nlmsg_for_each_attr(a, nlh, 0, rem) {
+			ck_assert_msg(expected_attr[i][attr_count].vid != 0,
+				      "Attribute number %d unexpected",
+				      attr_count);
+			ck_assert_msg(
+				nla_type(a) == IFLA_BRIDGE_VLAN_INFO,
+				"Expected attribute IFLA_BRIDGE_VLAN_INFO %d",
+				IFLA_BRIDGE_VLAN_INFO);
+			vlan_attr = (struct bridge_vlan_info *)nla_data(a);
+			ck_assert_int_eq(vlan_attr->vid,
+					 expected_attr[i][attr_count].vid);
+			ck_assert_int_eq(vlan_attr->flags,
+					 expected_attr[i][attr_count].flags);
+			attr_count++;
+		}
+	}
+}
+END_TEST
+
 static Suite *make_suite(void)
 {
 	Suite *suite = suite_create("Direct");
 	TCase *tc = tcase_create("Core");
 
 	tcase_add_test(tc, static_checks);
+	tcase_add_test(tc, vlan_attribute_check);
 	suite_add_tcase(suite, tc);
 	return suite;
 }
diff --git a/tests/cksuite-all-addr.c b/tests/cksuite-all-addr.c
index f4ee0dd..cc53333 100644
--- a/tests/cksuite-all-addr.c
+++ b/tests/cksuite-all-addr.c
@@ -176,6 +176,14 @@
 		!strcmp(nl_addr2str(addr6, buf, sizeof(buf)), addr_str),
 		"Address translated back to string does not match original");
 
+	_nl_clear_pointer(&addr6, nl_addr_put);
+
+	ck_assert(nl_addr_parse("default", AF_INET6, &addr6) == 0);
+	ck_assert_int_eq(nl_addr_get_len(addr6), 16);
+	ck_assert_int_eq(nl_addr_get_prefixlen(addr6), 0);
+	ck_assert_mem_eq(nl_addr_get_binary_addr(addr6), ((uint8_t[16]){ 0 }),
+			 16);
+
 	nl_addr_put(addr6);
 	nl_addr_put(clone);
 }
diff --git a/tests/cksuite-all-attr.c b/tests/cksuite-all-attr.c
index b4ac61f..86e3c6f 100644
--- a/tests/cksuite-all-attr.c
+++ b/tests/cksuite-all-attr.c
@@ -70,7 +70,7 @@
 	nlmsg_for_each_attr(a, nlh, 0, rem) {
 		ck_assert_msg(nla_type(a) == i, "Expected attribute %d", i);
 		i++;
-		ck_assert_msg(nla_get_u32(a) == i,
+		ck_assert_msg(nla_get_u32(a) == (unsigned)i,
 			      "Expected attribute value %d", i);
 	}
 
@@ -144,6 +144,60 @@
 
 /*****************************************************************************/
 
+START_TEST(test_nltst_select_route)
+{
+	/* This is a unit test for testing the unit-test helper function
+	 * _nltst_select_route_parse(). */
+
+#define _check(str, exp_addr_family, exp_addr_pattern, exp_plen)               \
+	do {                                                                   \
+		const char *_str = (str);                                      \
+		const int _exp_addr_family = (exp_addr_family);                \
+		const char *const _exp_addr_pattern = (exp_addr_pattern);      \
+		const int _exp_plen = (exp_plen);                              \
+		_nltst_auto_clear_select_route NLTstSelectRoute                \
+			_select_route = { 0 };                                 \
+		_nltst_auto_clear_select_route NLTstSelectRoute                \
+			_select_route2 = { 0 };                                \
+		_nl_auto_free char *_str2 = NULL;                              \
+                                                                               \
+		_nltst_select_route_parse(_str, &_select_route);               \
+		ck_assert_int_eq(_exp_addr_family, _select_route.addr_family); \
+		if (_nltst_inet_valid(AF_UNSPEC, _exp_addr_pattern)) {         \
+			ck_assert_str_eq(_exp_addr_pattern,                    \
+					 _select_route.addr);                  \
+			ck_assert_ptr_null(_select_route.addr_pattern);        \
+		} else {                                                       \
+			ck_assert_str_eq(_exp_addr_pattern,                    \
+					 _select_route.addr_pattern);          \
+			ck_assert_ptr_null(_select_route.addr);                \
+		}                                                              \
+		ck_assert_int_eq(_exp_plen, _select_route.plen);               \
+                                                                               \
+		_nltst_assert_select_route(&_select_route);                    \
+                                                                               \
+		_str2 = _nltst_select_route_to_string(&_select_route);         \
+		ck_assert_ptr_nonnull(_str2);                                  \
+                                                                               \
+		_nltst_select_route_parse(_str2, &_select_route2);             \
+                                                                               \
+		ck_assert(_nltst_select_route_equal(&_select_route,            \
+						    &_select_route2));         \
+	} while (0)
+
+	_check("0.0.0.0", AF_INET, "0.0.0.0", -1);
+	_check("4 0.0.0.0/0", AF_INET, "0.0.0.0", 0);
+	_check(" 6\n 0:0::/0", AF_INET6, "::", 0);
+	_check(" \n 0:0::/100", AF_INET6, "::", 100);
+	_check("6 0:0::*/0   ", AF_INET6, "0:0::*", 0);
+	_check("6 0:0::*/128   ", AF_INET6, "0:0::*", 128);
+	_check("6 0:0::*   ", AF_INET6, "0:0::*", -1);
+
+#undef _check
+}
+
+/*****************************************************************************/
+
 Suite *make_nl_attr_suite(void)
 {
 	Suite *suite = suite_create("Netlink attributes");
@@ -153,6 +207,7 @@
 	tcase_add_test(tc, msg_construct);
 	tcase_add_test(tc, clone_cls_u32);
 	tcase_add_test(tc, test_nltst_strtok);
+	tcase_add_test(tc, test_nltst_select_route);
 	suite_add_tcase(suite, tc);
 
 	return suite;
diff --git a/tests/cksuite-all-netns.c b/tests/cksuite-all-netns.c
index c6a5ce2..5b9d3a5 100644
--- a/tests/cksuite-all-netns.c
+++ b/tests/cksuite-all-netns.c
@@ -70,7 +70,7 @@
 			.add = false,
 		},
 	};
-	int i;
+	size_t i;
 	int r;
 
 	for (i = 0; i < _NL_N_ELEMENTS(links); i++) {
@@ -302,6 +302,36 @@
 
 /*****************************************************************************/
 
+static void _route_init(int addr_family, struct nl_sock **sk,
+			struct nl_cache **cache)
+{
+	ck_assert(sk && !*sk);
+	ck_assert(cache && !*cache);
+
+	*sk = _nltst_socket(NETLINK_ROUTE);
+	*cache = _nltst_rtnl_route_alloc_cache(*sk, addr_family);
+}
+
+START_TEST(route_1)
+{
+	_nl_auto_nl_socket struct nl_sock *sk = NULL;
+	_nl_auto_nl_cache struct nl_cache *cache = NULL;
+
+	if (_nltst_skip_no_iproute2("route_1"))
+		return;
+
+	_nltst_add_link(NULL, "v1", "dummy", NULL);
+	_nltst_system("ip -d link set v1 up");
+
+	_route_init(AF_INET6, &sk, &cache);
+
+	_nltst_assert_route_cache(cache, "fe80::/64", "6 fe80::*/128",
+				  "ff00::/8");
+}
+END_TEST
+
+/*****************************************************************************/
+
 Suite *make_nl_netns_suite(void)
 {
 	Suite *suite = suite_create("netns");
@@ -311,7 +341,7 @@
 				  nltst_netns_fixture_teardown);
 	tcase_add_test(tc, cache_and_clone);
 	tcase_add_loop_test(tc, test_create_iface, 0, 17);
-
+	tcase_add_test(tc, route_1);
 	suite_add_tcase(suite, tc);
 
 	return suite;
diff --git a/tests/nl-test-util.c b/tests/nl-test-util.c
index 52188a0..6bbe2ae 100644
--- a/tests/nl-test-util.c
+++ b/tests/nl-test-util.c
@@ -5,6 +5,7 @@
 #include "nl-test-util.h"
 
 #include <fcntl.h>
+#include <fnmatch.h>
 #include <sched.h>
 #include <stdio.h>
 #include <sys/mount.h>
@@ -14,6 +15,8 @@
 #include <netlink/route/route.h>
 #include <netlink/socket.h>
 
+#include "lib/route/nl-route.h"
+
 #include "nl-aux-route/nl-route.h"
 
 /*****************************************************************************/
@@ -30,7 +33,7 @@
 	_nltst_assert_errno(fd >= 0);
 
 	nread = read(fd, ptr, len);
-	_nltst_assert_errno(nread == len);
+	_nltst_assert_errno(nread >= 0 && ((size_t)nread) == len);
 
 	_nltst_close(fd);
 }
@@ -217,11 +220,13 @@
 
 /*****************************************************************************/
 
-char *_nltst_object_to_string(struct nl_object *obj)
+char *_nltst_object_to_string(const struct nl_object *obj)
 {
 	size_t L = 1024;
 	size_t l;
 	char *s;
+	struct nl_dump_params dp;
+	char canary;
 
 	if (!obj)
 		return strdup("(null)");
@@ -229,11 +234,31 @@
 	s = malloc(L);
 	ck_assert_ptr_nonnull(s);
 
-	nl_object_dump_buf(obj, s, L);
+	canary = _nltst_rand_u32();
+	s[L - 1u] = canary;
+
+	dp = (struct nl_dump_params){
+		.dp_type = NL_DUMP_LINE,
+		.dp_buf = s,
+		.dp_buflen = L - 1u,
+	};
+
+	nl_object_dump((struct nl_object *)obj, &dp);
+
 	l = strlen(s);
+	ck_assert_int_ge(l, 2);
 	ck_assert_int_lt(l, L);
+	ck_assert(canary == s[L - 1u]);
 	s = realloc(s, l + 1);
 	ck_assert_ptr_nonnull(s);
+
+	ck_assert_msg(s[l - 1u] == '\n',
+		      "expects newline after dump. Got \"%s\"", s);
+	s[l - 1u] = '\0';
+
+	ck_assert_msg(!strchr(s, '\n'),
+		      "no further newline expected. Got \"%s\"", s);
+
 	return s;
 }
 
@@ -653,3 +678,366 @@
 	/* FIXME: we would expect that the cloned object is identical. It is not. */
 	/* _nltst_object_identical(link, link_clone); */
 }
+
+/*****************************************************************************/
+
+bool _nltst_in_ci(void)
+{
+	return _nl_streq0(getenv("NLTST_IN_CI"), "1");
+}
+
+/*****************************************************************************/
+
+bool _nltst_has_iproute2(void)
+{
+	static int has = -1;
+
+	if (has == -1)
+		has = (system("ip link &>/dev/null") == 0);
+
+	return has;
+}
+
+bool _nltst_skip_no_iproute2(const char *msg)
+{
+	if (_nltst_has_iproute2())
+		return false;
+
+	ck_assert_msg(
+		!_nltst_in_ci(),
+		"We seem to not have iproute2, but we are in NLTST_IN_CI=1. This is fatal.");
+
+	printf("skip test due to missing iproute2%s%s%s\n", msg ? " (" : "",
+	       msg ?: "", msg ? ")" : "");
+	return true;
+}
+
+/*****************************************************************************/
+
+void _nltst_select_route_clear(NLTstSelectRoute *select_route)
+{
+	_nltst_assert_select_route(select_route);
+
+	_nl_clear_free(&select_route->addr);
+	_nl_clear_free(&select_route->addr_pattern);
+}
+
+int _nltst_select_route_cmp(const NLTstSelectRoute *select_route1,
+			    const NLTstSelectRoute *select_route2)
+{
+	_NL_CMP_SELF(select_route1, select_route2);
+	_NL_CMP_FIELD_STR0(select_route1, select_route2, addr);
+	_NL_CMP_FIELD_STR0(select_route1, select_route2, addr_pattern);
+	_NL_CMP_FIELD(select_route1, select_route2, addr_family);
+	_NL_CMP_FIELD(select_route1, select_route2, ifindex);
+	_NL_CMP_FIELD(select_route1, select_route2, plen);
+	return 0;
+}
+
+char *_nltst_select_route_to_string(const NLTstSelectRoute *select_route)
+{
+	char buf[1024];
+	const char *family;
+	char b_plen[100];
+
+	_nltst_assert_select_route(select_route);
+
+	if (select_route->addr_family == AF_INET)
+		family = "4 ";
+	else if (select_route->addr_family == AF_INET6)
+		family = "6 ";
+	else
+		family = "";
+
+	b_plen[0] = '\0';
+	if (select_route->plen != -1)
+		_nltst_sprintf_arr(b_plen, "/%d", select_route->plen);
+
+	_nltst_sprintf_arr(buf,
+			   "%s"
+			   "%s"
+			   "%s"
+			   "",
+			   family,
+			   select_route->addr_pattern ?: select_route->addr,
+			   b_plen);
+	return _nltst_strdup(buf);
+}
+
+void _nltst_select_route_parse(const char *str,
+			       NLTstSelectRoute *out_select_route)
+{
+	_nltst_auto_strfreev char **tokens0 = NULL;
+	const char *const *tokens;
+	int addr_family = AF_UNSPEC;
+	int addr_family2 = AF_UNSPEC;
+	NLTstIPAddr addr;
+	int plen = -1;
+	_nl_auto_free char *addr_free = NULL;
+	const char *s_addr_pattern;
+	const char *s_addr = NULL;
+	const char *s;
+	const char *s1;
+
+	ck_assert_ptr_nonnull(str);
+	_nltst_assert_select_route(out_select_route);
+
+	tokens0 = _nltst_strtokv(str);
+	tokens = (const char *const *)tokens0;
+
+	s = tokens[0];
+	if (!s)
+		ck_abort_msg("invalid empty route pattern \"%s\"", str);
+	if (_nl_streq(s, "4") || _nl_streq(s, "inet") ||
+	    _nl_streq(s, "inet4")) {
+		addr_family = AF_INET;
+		tokens++;
+	} else if (_nl_streq(s, "6") || _nl_streq(s, "inet6")) {
+		addr_family = AF_INET6;
+		tokens++;
+	}
+
+	s_addr_pattern = tokens[0];
+	if (!s_addr_pattern) {
+		ck_abort_msg(
+			"the route pattern \"%s\" is invalid and contains no destination address",
+			str);
+	}
+	tokens++;
+
+	s = strchr(s_addr_pattern, '/');
+	if (s) {
+		long int plen2;
+
+		if (s == s_addr_pattern) {
+			ck_abort_msg(
+				"the route pattern \"%s\" contains no valid destination address",
+				str);
+		}
+		addr_free = strndup(s_addr_pattern, s - s_addr_pattern);
+		s_addr_pattern = addr_free;
+		s++;
+
+		errno = 0;
+		plen2 = strtol(s, (char **)&s1, 10);
+		if (errno != 0 || s1[0] != '\0' || plen2 < 0 || plen2 > 128 ||
+		    ((_nltst_str_find_first_not_from_charset(
+			     s, "0123456789"))[0] != '\0')) {
+			ck_abort_msg(
+				"the route pattern \"%s\" contains no valid destination address",
+				str);
+		}
+		plen = plen2;
+	}
+	if ((_nltst_str_find_first_not_from_charset(
+		    s_addr_pattern, "abcdefABCDEF0123456789:.?*"))[0] != '\0') {
+		ck_abort_msg(
+			"the route pattern \"%s\" contains no valid destination address",
+			str);
+	}
+	if (_nltst_inet_pton(addr_family, s_addr_pattern, &addr_family2,
+			     &addr)) {
+		free(addr_free);
+		addr_free = _nltst_inet_ntop_dup(addr_family2, &addr);
+		s_addr_pattern = addr_free;
+		addr_family = addr_family2;
+	} else {
+		if (addr_family == AF_UNSPEC) {
+			ck_abort_msg(
+				"the route pattern \"%s\" contains a wild card address, it requires the address family",
+				str);
+		}
+	}
+
+	ck_assert(addr_family == AF_INET || addr_family == AF_INET6);
+
+	if (plen > (addr_family == AF_INET ? 32 : 128)) {
+		ck_abort_msg(
+			"the route pattern \"%s\" contains no valid destination address (prefix length too large)",
+			str);
+	}
+	ck_assert_int_ge(plen, -1);
+
+	s = tokens[0];
+	if (s) {
+		ck_abort_msg("the route pattern \"%s\" contains extra tokens",
+			     str);
+	}
+
+	if (_nltst_inet_valid(addr_family, s_addr_pattern))
+		_NL_SWAP(&s_addr, &s_addr_pattern);
+
+	_nltst_select_route_clear(out_select_route);
+	memset(out_select_route, 0, sizeof(*out_select_route));
+	*out_select_route = (NLTstSelectRoute){
+		.addr_family = addr_family,
+		.plen = plen,
+		.ifindex = 0,
+		.addr = s_addr ? strdup(s_addr) : NULL,
+		.addr_pattern = s_addr_pattern ? strdup(s_addr_pattern) : NULL,
+	};
+	ck_assert(!s_addr || out_select_route->addr);
+	ck_assert(!s_addr_pattern || out_select_route->addr_pattern);
+	_nltst_assert_select_route(out_select_route);
+}
+
+bool _nltst_select_route_match(struct nl_object *route,
+			       const NLTstSelectRoute *select_route,
+			       bool do_assert)
+{
+	struct nl_addr *addr;
+	struct rtnl_route *route_;
+	int i;
+	char sbuf1[200];
+
+	ck_assert_ptr_nonnull(route);
+	ck_assert_str_eq(nl_object_get_type(route), "route/route");
+
+	if (!select_route)
+		return true;
+
+	route_ = (struct rtnl_route *)route;
+
+	_nltst_assert_select_route(select_route);
+
+#define _check(cond, msg, ...)                                                                                   \
+	do {                                                                                                     \
+		if (do_assert) {                                                                                 \
+			_nl_auto_free char *s1 = NULL;                                                           \
+			_nl_auto_free char *s2 = NULL;                                                           \
+                                                                                                                 \
+			ck_assert_msg(                                                                           \
+				(cond),                                                                          \
+				"Checking condition \"%s\" for route \"%s\" (expected \"%s\") failed (msg: " msg \
+				")",                                                                             \
+				#cond, (s1 = _nltst_object_to_string(route)),                                    \
+				(s2 = _nltst_select_route_to_string(                                             \
+					 select_route)),                                                         \
+				##__VA_ARGS__);                                                                  \
+		} else if (cond) {                                                                               \
+		} else {                                                                                         \
+			return false;                                                                            \
+		}                                                                                                \
+	} while (0)
+
+	if (select_route->addr_family != AF_UNSPEC) {
+		_check(rtnl_route_get_family(route_) ==
+			       select_route->addr_family,
+		       "mismatching address family");
+	}
+
+	if (select_route->ifindex != 0) {
+		struct nl_list_head *list;
+		struct rtnl_nexthop *nh;
+		size_t n;
+		struct rtnl_nexthop *nh2;
+
+		list = rtnl_route_get_nexthops(route_);
+		_check(list, "no nexthops for ifindex");
+
+		n = 0;
+		nl_list_for_each_entry(nh, list, rtnh_list) {
+			nh2 = nh;
+			n++;
+		}
+		_check(n == 1, "expects one nexthop for ifindex but got %zu",
+		       n);
+		ck_assert_ptr_nonnull(nh2);
+
+		i = rtnl_route_nh_get_ifindex(nh2);
+		_check(i == select_route->ifindex,
+		       "route has unexpected ifindex %d for next hop", i);
+	}
+
+	addr = rtnl_route_get_dst(route_);
+
+	if (addr) {
+		if (select_route->addr_family != AF_UNSPEC) {
+			_check(nl_addr_get_family(addr) ==
+				       select_route->addr_family,
+			       "unexecpted address family of dst");
+		}
+	}
+
+	if (select_route->plen != -1) {
+		_check(addr, "missing address");
+		_check(nl_addr_get_prefixlen(addr) ==
+			       (unsigned)select_route->plen,
+		       "unexpected prefix length");
+	}
+	if (select_route->addr || select_route->addr_pattern) {
+		_check(addr, "missing address");
+
+		_nl_inet_ntop(nl_addr_get_family(addr),
+			      nl_addr_get_binary_addr(addr), sbuf1);
+
+		ck_assert(strlen(sbuf1) > 0);
+		ck_assert(strlen(sbuf1) < sizeof(sbuf1));
+
+		if (select_route->addr) {
+			_check(_nl_streq(sbuf1, select_route->addr),
+			       "unexpected address, \"%s\" does not match \"%s\"",
+			       sbuf1, select_route->addr);
+		}
+		if (select_route->addr_pattern) {
+			_check(fnmatch(select_route->addr_pattern, sbuf1, 0) ==
+				       0,
+			       "unexpected address, \"%s\" does not match pattern \"%s\"",
+			       sbuf1, select_route->addr_pattern);
+		}
+	}
+
+#undef _check
+
+	return false;
+}
+
+/*****************************************************************************/
+
+void _nltst_assert_route_list(struct nl_object *const *objs, ssize_t len,
+			      const char *const *expected_routes)
+{
+	size_t l;
+	size_t i;
+
+	if (len < 0) {
+		l = 0;
+		if (objs) {
+			while (objs[l])
+				l++;
+		}
+	} else
+		l = len;
+
+	for (i = 0; i < l; i++) {
+		struct nl_object *route = objs[i];
+		_nltst_auto_clear_select_route NLTstSelectRoute select_route = {
+			0
+		};
+		_nl_auto_free char *s = _nltst_object_to_string(route);
+
+		if (!expected_routes[i]) {
+			ck_abort_msg(
+				"No more expected route, but have route %zu (of %zu) as %s",
+				i + 1, l, s);
+		}
+
+		_nltst_select_route_parse(expected_routes[i], &select_route);
+
+		_nltst_select_route_match(route, &select_route, true);
+	}
+}
+
+void _nltst_assert_route_cache_v(struct nl_cache *cache,
+				 const char *const *expected_routes)
+{
+	_nl_auto_free struct nl_object **objs = NULL;
+	size_t len;
+
+	ck_assert(cache);
+	ck_assert(expected_routes);
+
+	objs = _nltst_cache_get_all(cache, &len);
+
+	_nltst_assert_route_list(objs, len, expected_routes);
+}
diff --git a/tests/nl-test-util.h b/tests/nl-test-util.h
index ee4bd96..b751cc5 100644
--- a/tests/nl-test-util.h
+++ b/tests/nl-test-util.h
@@ -36,12 +36,12 @@
 #endif
 
 #ifndef ck_assert_pstr_ne
-#define ck_assert_pstr_ne(a, b)                                                \
-	do {                                                                   \
-		const char *_a = (a);                                          \
-		const char *_b = (b);                                          \
-                                                                               \
-		ck_assert(!(_a == _b || (_a && _b && strcmp(_a, _b) == 0)));   \
+#define ck_assert_pstr_ne(a, b)                                              \
+	do {                                                                 \
+		const char *_a = (a);                                        \
+		const char *_b = (b);                                        \
+                                                                             \
+		ck_assert(!(_a == _b || (_a && _b && strcmp(_a, _b) == 0))); \
 	} while (0)
 #endif
 
@@ -51,6 +51,46 @@
 
 /*****************************************************************************/
 
+#define __nltst_assert_nonnull(uniq, x)                      \
+	({                                                   \
+		typeof(x) _NL_UNIQ_T(_x, uniq) = (x);        \
+                                                             \
+		ck_assert_ptr_nonnull(_NL_UNIQ_T(_x, uniq)); \
+                                                             \
+		_NL_UNIQ_T(_x, uniq);                        \
+	})
+
+#define _nltst_assert_nonnull(x) __nltst_assert_nonnull(_NL_UNIQ, x)
+
+static inline char *_nltst_strdup(const char *str)
+{
+	return str ? _nltst_assert_nonnull(strdup(str)) : NULL;
+}
+
+/*****************************************************************************/
+
+#define __nltst_sprintf_arr(uniq, arr, fmt, ...)                      \
+	({                                                            \
+		char *const _NL_UNIQ_T(arr, uniq) = (arr);            \
+		int _NL_UNIQ_T(c, uniq);                              \
+                                                                      \
+		_NL_STATIC_ASSERT(sizeof(arr) >                       \
+				  sizeof(_NL_UNIQ_T(arr, uniq)));     \
+                                                                      \
+		_NL_UNIQ_T(c, uniq) = snprintf(_NL_UNIQ_T(arr, uniq), \
+					       sizeof(arr), fmt,      \
+					       ##__VA_ARGS__);        \
+                                                                      \
+		ck_assert_int_lt(_NL_UNIQ_T(c, uniq), sizeof(arr));   \
+                                                                      \
+		_NL_UNIQ_T(arr, uniq);                                \
+	})
+
+#define _nltst_sprintf_arr(arr, fmt, ...) \
+	__nltst_sprintf_arr(_NL_UNIQ, arr, fmt, ##__VA_ARGS__)
+
+/*****************************************************************************/
+
 void _nltst_get_urandom(void *ptr, size_t len);
 
 uint32_t _nltst_rand_u32(void);
@@ -78,34 +118,34 @@
 	return _nltst_rand_u32() % 2 == 0;
 }
 
-#define _nltst_rand_select(a, ...)                                             \
-	({                                                                     \
-		const typeof(a) _lst[] = { (a), ##__VA_ARGS__ };               \
-                                                                               \
-		_lst[_nltst_rand_u32_range(_NL_N_ELEMENTS(_lst))];             \
+#define _nltst_rand_select(a, ...)                                 \
+	({                                                         \
+		const typeof(a) _lst[] = { (a), ##__VA_ARGS__ };   \
+                                                                   \
+		_lst[_nltst_rand_u32_range(_NL_N_ELEMENTS(_lst))]; \
 	})
 
 /*****************************************************************************/
 
-#define _nltst_assert(expr)                                                    \
-	({                                                                     \
-		typeof(expr) _expr = (expr);                                   \
-                                                                               \
-		if (!_expr) {                                                  \
-			ck_assert_msg(0, "assert(%s) failed", #expr);          \
-		}                                                              \
-		_expr;                                                         \
+#define _nltst_assert(expr)                                           \
+	({                                                            \
+		typeof(expr) _expr = (expr);                          \
+                                                                      \
+		if (!_expr) {                                         \
+			ck_assert_msg(0, "assert(%s) failed", #expr); \
+		}                                                     \
+		_expr;                                                \
 	})
 
-#define _nltst_assert_errno(expr)                                              \
-	do {                                                                   \
-		if (expr) {                                                    \
-		} else {                                                       \
-			const int _errno = (errno);                            \
-                                                                               \
-			ck_assert_msg(0, "assert(%s) failed (errno=%d, %s)",   \
-				      #expr, _errno, strerror(_errno));        \
-		}                                                              \
+#define _nltst_assert_errno(expr)                                            \
+	do {                                                                 \
+		if (expr) {                                                  \
+		} else {                                                     \
+			const int _errno = (errno);                          \
+                                                                             \
+			ck_assert_msg(0, "assert(%s) failed (errno=%d, %s)", \
+				      #expr, _errno, strerror(_errno));      \
+		}                                                            \
 	} while (0)
 
 #define _nltst_assert_retcode(expr)                                                   \
@@ -125,28 +165,28 @@
 		}                                                                     \
 	} while (0)
 
-#define _nltst_close(fd)                                                       \
-	do {                                                                   \
-		int _r;                                                        \
-                                                                               \
-		_r = _nl_close((fd));                                          \
-		_nltst_assert_errno(_r == 0);                                  \
+#define _nltst_close(fd)                      \
+	do {                                  \
+		int _r;                       \
+                                              \
+		_r = _nl_close((fd));         \
+		_nltst_assert_errno(_r == 0); \
 	} while (0)
 
-#define _nltst_fclose(f)                                                       \
-	do {                                                                   \
-		int _r;                                                        \
-                                                                               \
-		_r = fclose((f));                                              \
-		_nltst_assert_errno(_r == 0);                                  \
+#define _nltst_fclose(f)                      \
+	do {                                  \
+		int _r;                       \
+                                              \
+		_r = fclose((f));             \
+		_nltst_assert_errno(_r == 0); \
 	} while (0)
 
 void _nltst_assert_link_exists_full(const char *ifname, bool exists);
 
-#define _nltst_assert_link_exists(ifname)                                      \
+#define _nltst_assert_link_exists(ifname) \
 	_nltst_assert_link_exists_full((ifname), true)
 
-#define _nltst_assert_link_not_exists(ifname)                                  \
+#define _nltst_assert_link_not_exists(ifname) \
 	_nltst_assert_link_exists_full((ifname), false)
 
 /*****************************************************************************/
@@ -167,11 +207,11 @@
 
 	r = (char *)inet_ntop(addr_family, addr, buf,
 			      (addr_family == AF_INET) ? INET_ADDRSTRLEN :
-							       INET6_ADDRSTRLEN);
+							 INET6_ADDRSTRLEN);
 	ck_assert_ptr_eq(r, buf);
 	ck_assert_int_lt(strlen(r), (addr_family == AF_INET) ?
 					    INET_ADDRSTRLEN :
-						  INET6_ADDRSTRLEN);
+					    INET6_ADDRSTRLEN);
 	return r;
 }
 
@@ -180,7 +220,7 @@
 	return (char *)_nltst_inet_ntop(addr_family, addr,
 					malloc((addr_family == AF_INET) ?
 						       INET_ADDRSTRLEN :
-							     INET6_ADDRSTRLEN));
+						       INET6_ADDRSTRLEN));
 }
 
 static inline bool _nltst_inet_pton(int addr_family, const char *str,
@@ -209,7 +249,7 @@
 	if (out_addr) {
 		memcpy(out_addr, &a,
 		       addr_family == AF_INET ? sizeof(in_addr_t) :
-						      sizeof(struct in6_addr));
+						sizeof(struct in6_addr));
 	}
 	if (out_addr_family)
 		*out_addr_family = addr_family;
@@ -270,24 +310,24 @@
 
 char **_nltst_strtokv(const char *str);
 
-#define _nltst_assert_strv_equal(strv1, strv2)                                 \
-	do {                                                                   \
-		typeof(strv1) _strv1 = (strv1);                                \
-		typeof(strv2) _strv2 = (strv2);                                \
-		_nl_unused const void *_strv1_typecheck1 = _strv1;             \
-		_nl_unused const void *_strv2_typecheck1 = _strv2;             \
-		_nl_unused const char *_strv1_typecheck2 =                     \
-			_strv1 ? _strv1[0] : NULL;                             \
-		_nl_unused const char *_strv2_typecheck2 =                     \
-			_strv2 ? _strv2[0] : NULL;                             \
-		size_t _i;                                                     \
-                                                                               \
-		ck_assert_int_eq(!!_strv1, !!_strv2);                          \
-		if (_strv1) {                                                  \
-			for (_i = 0; _strv1[_i] || _strv2[_i]; _i++) {         \
-				ck_assert_str_eq(_strv1[_i], _strv2[_i]);      \
-			}                                                      \
-		}                                                              \
+#define _nltst_assert_strv_equal(strv1, strv2)                            \
+	do {                                                              \
+		typeof(strv1) _strv1 = (strv1);                           \
+		typeof(strv2) _strv2 = (strv2);                           \
+		_nl_unused const void *_strv1_typecheck1 = _strv1;        \
+		_nl_unused const void *_strv2_typecheck1 = _strv2;        \
+		_nl_unused const char *_strv1_typecheck2 =                \
+			_strv1 ? _strv1[0] : NULL;                        \
+		_nl_unused const char *_strv2_typecheck2 =                \
+			_strv2 ? _strv2[0] : NULL;                        \
+		size_t _i;                                                \
+                                                                          \
+		ck_assert_int_eq(!!_strv1, !!_strv2);                     \
+		if (_strv1) {                                             \
+			for (_i = 0; _strv1[_i] || _strv2[_i]; _i++) {    \
+				ck_assert_str_eq(_strv1[_i], _strv2[_i]); \
+			}                                                 \
+		}                                                         \
 	} while (0)
 
 #define _NLTST_CHARSET_SPACE " \n\r\t"
@@ -296,64 +336,64 @@
 
 #define _nltst_char_is_space(ch) _nltst_char_is(ch, _NLTST_CHARSET_SPACE)
 
-#define _nltst_str_skip_predicate(s, ch, predicate)                            \
-	({                                                                     \
-		typeof(s) _s1 = (s);                                           \
-		_nl_unused const char *_s1_typecheck = (_s1);                  \
-                                                                               \
-		if (_s1) {                                                     \
-			while (({                                              \
-				const char ch = _s1[0];                        \
-                                                                               \
-				(ch != '\0') && (predicate);                   \
-			}))                                                    \
-				_s1++;                                         \
-		}                                                              \
-		_s1;                                                           \
+#define _nltst_str_skip_predicate(s, ch, predicate)           \
+	({                                                    \
+		typeof(s) _s1 = (s);                          \
+		_nl_unused const char *_s1_typecheck = (_s1); \
+                                                              \
+		if (_s1) {                                    \
+			while (({                             \
+				const char ch = _s1[0];       \
+                                                              \
+				(ch != '\0') && (predicate);  \
+			}))                                   \
+				_s1++;                        \
+		}                                             \
+		_s1;                                          \
 	})
 
-#define _nltst_str_skip_charset(s, charset)                                    \
+#define _nltst_str_skip_charset(s, charset) \
 	_nltst_str_skip_predicate(s, _ch, _nltst_char_is(_ch, "" charset ""))
 
-#define _nltst_str_skip_space(s)                                               \
+#define _nltst_str_skip_space(s) \
 	_nltst_str_skip_charset(s, _NLTST_CHARSET_SPACE)
 
-#define _nltst_str_has_prefix_and_space(s, prefix)                             \
-	({                                                                     \
-		typeof(s) _s2 = (s);                                           \
-		_nl_unused const char *_s2_typecheck = (_s2);                  \
-		const size_t _l = strlen("" prefix "");                        \
-                                                                               \
-		if (_s2) {                                                     \
-			if ((strncmp(_s2, "" prefix "", _l)) == 0 &&           \
-			    _nltst_char_is_space(_s2[_l]))                     \
-				_s2 = _nltst_str_skip_space(&_s2[_l + 1]);     \
-			else                                                   \
-				_s2 = NULL;                                    \
-		}                                                              \
-		_s2;                                                           \
+#define _nltst_str_has_prefix_and_space(s, prefix)                         \
+	({                                                                 \
+		typeof(s) _s2 = (s);                                       \
+		_nl_unused const char *_s2_typecheck = (_s2);              \
+		const size_t _l = strlen("" prefix "");                    \
+                                                                           \
+		if (_s2) {                                                 \
+			if ((strncmp(_s2, "" prefix "", _l)) == 0 &&       \
+			    _nltst_char_is_space(_s2[_l]))                 \
+				_s2 = _nltst_str_skip_space(&_s2[_l + 1]); \
+			else                                               \
+				_s2 = NULL;                                \
+		}                                                          \
+		_s2;                                                       \
 	})
 
-#define _nltst_str_find_first_not_from_charset(s, charset)                     \
-	({                                                                     \
-		typeof(s) _s3 = (s);                                           \
-		_nl_unused const char *_s3_typecheck = (_s3);                  \
-		size_t _l3;                                                    \
-                                                                               \
-		_l3 = strspn(_s3, "" charset "");                              \
-                                                                               \
-		&_s3[_l3];                                                     \
+#define _nltst_str_find_first_not_from_charset(s, charset)    \
+	({                                                    \
+		typeof(s) _s3 = (s);                          \
+		_nl_unused const char *_s3_typecheck = (_s3); \
+		size_t _l3;                                   \
+                                                              \
+		_l3 = strspn(_s3, "" charset "");             \
+                                                              \
+		&_s3[_l3];                                    \
 	})
 
-#define _nltst_str_find_first_from_charset(s, charset)                         \
-	({                                                                     \
-		typeof(s) _s3 = (s);                                           \
-		_nl_unused const char *_s3_typecheck = (_s3);                  \
-		size_t _l3;                                                    \
-                                                                               \
-		_l3 = strcspn(_s3, "" charset "");                             \
-                                                                               \
-		&_s3[_l3];                                                     \
+#define _nltst_str_find_first_from_charset(s, charset)        \
+	({                                                    \
+		typeof(s) _s3 = (s);                          \
+		_nl_unused const char *_s3_typecheck = (_s3); \
+		size_t _l3;                                   \
+                                                              \
+		_l3 = strcspn(_s3, "" charset "");            \
+                                                              \
+		&_s3[_l3];                                    \
 	})
 
 /*****************************************************************************/
@@ -368,9 +408,82 @@
 
 /*****************************************************************************/
 
+#define _nltst_system(command) _nltst_assert_retcode(system(command))
+
+bool _nltst_in_ci(void);
+
+bool _nltst_has_iproute2(void);
+bool _nltst_skip_no_iproute2(const char *msg);
+
+/*****************************************************************************/
+
+typedef struct {
+	int addr_family;
+	int ifindex;
+	int plen;
+	char *addr;
+	char *addr_pattern;
+} NLTstSelectRoute;
+
+#define _nltst_assert_select_route(select_route)                             \
+	do {                                                                 \
+		const NLTstSelectRoute *_select_route_5 = (select_route);    \
+                                                                             \
+		ck_assert_ptr_nonnull(_select_route_5);                      \
+		_nl_assert_addr_family_or_unspec(                            \
+			_select_route_5->addr_family);                       \
+		ck_assert_int_ge(_select_route_5->ifindex, 0);               \
+		ck_assert_int_ge(_select_route_5->plen, -1);                 \
+		ck_assert_int_le(                                            \
+			_select_route_5->plen,                               \
+			_select_route_5->addr_family == AF_INET ? 32 : 128); \
+		ck_assert(!_select_route_5->addr || ({                       \
+			char _buf[INET6_ADDRSTRLEN];                         \
+			const char *_s2;                                     \
+                                                                             \
+			_s2 = _nltst_inet_normalize(                         \
+				_select_route_5->addr_family,                \
+				_select_route_5->addr, _buf);                \
+			(_select_route_5->addr_family != AF_UNSPEC && _s2 && \
+			 _nl_streq(_s2, _select_route_5->addr));             \
+		}));                                                         \
+		ck_assert(!_select_route_5->addr_pattern ||                  \
+			  !_select_route_5->addr);                           \
+		ck_assert(!_select_route_5->addr_pattern ||                  \
+			  _select_route_5->addr_family != AF_UNSPEC);        \
+	} while (0)
+
+void _nltst_select_route_clear(NLTstSelectRoute *select_route);
+
+#define _nltst_auto_clear_select_route \
+	_nl_auto(_nltst_auto_clear_select_route_fcn)
+_NL_AUTO_DEFINE_FCN_STRUCT(NLTstSelectRoute, _nltst_auto_clear_select_route_fcn,
+			   _nltst_select_route_clear);
+
+int _nltst_select_route_cmp(const NLTstSelectRoute *select_route1,
+			    const NLTstSelectRoute *select_route2);
+
+static inline bool
+_nltst_select_route_equal(const NLTstSelectRoute *select_route1,
+			  const NLTstSelectRoute *select_route2)
+{
+	return _nltst_select_route_cmp(select_route1, select_route2) == 0;
+}
+
+char *_nltst_select_route_to_string(const NLTstSelectRoute *select_route);
+
+void _nltst_select_route_parse(const char *str,
+			       NLTstSelectRoute *out_select_route);
+
+bool _nltst_select_route_match(struct nl_object *route,
+			       const NLTstSelectRoute *select_route,
+			       bool do_assert);
+
+/*****************************************************************************/
+
 void _nltst_object_identical(const void *a, const void *b);
 
-char *_nltst_object_to_string(struct nl_object *obj);
+char *_nltst_object_to_string(const struct nl_object *obj);
 
 struct nl_object **_nltst_cache_get_all(struct nl_cache *cache,
 					size_t *out_len);
@@ -394,4 +507,14 @@
 void _nltst_get_link(struct nl_sock *sk, const char *ifname, int *out_ifindex,
 		     struct rtnl_link **out_link);
 
+void _nltst_assert_route_list(struct nl_object *const *objs, ssize_t len,
+			      const char *const *expected_routes);
+
+void _nltst_assert_route_cache_v(struct nl_cache *cache,
+				 const char *const *expected_routes);
+
+#define _nltst_assert_route_cache(cache, ...) \
+	_nltst_assert_route_cache_v(cache,    \
+				    ((const char *const[200]){ __VA_ARGS__ }))
+
 #endif /* __NL_TEST_UTIL_H__ */
diff --git a/tools/clang-format.sh b/tools/clang-format.sh
index 6a0db2a..40c3f7a 100755
--- a/tools/clang-format.sh
+++ b/tools/clang-format.sh
@@ -334,7 +334,6 @@
     "src/nl-rule-list.c"
     "src/nl-tctree-list.c"
     "src/nl-util-addr.c"
-    "tests/nl-test-util.h"
     "tests/test-cache-mngr.c"
     "tests/test-complex-HTB-with-hash-filters.c"
     "tests/test-create-bridge.c"