Perform range check on len in nlmsg_reserve am: f83d9c1c67 am: d42374324d am: d9f824b744 am: 25edb109fc am: b0a4ed4800 am: 65d4de583a am: 45c4ce4768 am: 642a497f9c am: 170a7d24f4 am: 0a37ab0fdd am: 1ff6ec5e40
am: a96b31573c

Change-Id: I82d5bd8efec6f09becb413ecbcc01e814283243a
diff --git a/.gitignore b/.gitignore
index 17a67a0..5f5f1cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,28 +1,27 @@
 .deps
 .libs
 .dirstamp
-*.in
 *.la
 *.lo
 *.o
 *.swp
+*.patch
 Makefile
+Makefile.in
+defs.h.in
+defs.h.in~
 /lib/stamp-h1
 
 /libnl-1.pc
-/doc/Doxyfile
 /lib/defs.h
 cscope.*
+/tags
 
 /aclocal.m4
 /autom4te.cache
-/compile
+/build-aux/
 /config.*
 /configure
-/depcomp
 /libtool
-/ltmain.sh
-/install-sh
-/missing
-
 /*.pc
+/libnl.sym
diff --git a/Android.mk b/Android.mk
index f407a83..1ee2542 100644
--- a/Android.mk
+++ b/Android.mk
@@ -5,7 +5,6 @@
 LOCAL_SRC_FILES :=  lib/cache.c \
 	lib/data.c \
 	lib/nl.c \
-	lib/doc.c \
 	lib/cache_mngr.c \
 	lib/addr.c \
 	lib/socket.c \
@@ -24,10 +23,16 @@
 	lib/route/rtnl.c \
 	lib/route/route_utils.c \
 	lib/netfilter/nfnl.c \
-	lib/error.c
+	lib/error.c \
+	lib/version.c \
+	lib/hash.c \
+	lib/hashtable.c
 
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/include/linux-private
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_CFLAGS += -D_BSD_SOURCE -Wno-unused-parameter -Wno-sign-compare \
+	-Wno-missing-field-initializers -Wno-tautological-compare \
+	-Wno-pointer-arith -UNDEBUG -D_GNU_SOURCE -DSYSCONFDIR="\"/etc/libnl\""
 LOCAL_MODULE := libnl
 
 include $(BUILD_STATIC_LIBRARY)
diff --git a/COPYING b/COPYING
index 371ec20..4362b49 100644
--- a/COPYING
+++ b/COPYING
@@ -1,9 +1,8 @@
-
                   GNU LESSER GENERAL PUBLIC LICENSE
                        Version 2.1, February 1999
 
  Copyright (C) 1991, 1999 Free Software Foundation, Inc.
-     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
@@ -23,8 +22,7 @@
 Free Software Foundation and other authors who decide to use it.  You
 can use it too, but we suggest you first think carefully about whether
 this license or the ordinary General Public License is the better
-strategy to use in any particular case, based on the explanations
-below.
+strategy to use in any particular case, based on the explanations below.
 
   When we speak of free software, we are referring to freedom of use,
 not price.  Our General Public Licenses are designed to make sure that
@@ -89,9 +87,9 @@
 special circumstances.
 
   For example, on rare occasions, there may be a special need to
-encourage the widest possible use of a certain library, so that it
-becomes a de-facto standard.  To achieve this, non-free programs must
-be allowed to use the library.  A more frequent case is that a free
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
 library does the same job as widely used non-free libraries.  In this
 case, there is little to gain by limiting the free library to free
 software only, so we use the Lesser General Public License.
@@ -138,8 +136,8 @@
   "Source code" for a work means the preferred form of the work for
 making modifications to it.  For a library, complete source code means
 all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control
-compilation and installation of the library.
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
 
   Activities other than copying, distribution and modification are not
 covered by this License; they are outside its scope.  The act of
@@ -305,10 +303,10 @@
     the user installs one, as long as the modified version is
     interface-compatible with the version that the work was made with.
 
-    c) Accompany the work with a written offer, valid for at least
-    three years, to give the same user the materials specified in
-    Subsection 6a, above, for a charge no more than the cost of
-    performing this distribution.
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
 
     d) If distribution of the work is made by offering access to copy
     from a designated place, offer equivalent access to copy the above
@@ -386,10 +384,9 @@
 the only way you could satisfy both it and this License would be to
 refrain entirely from distribution of the Library.
 
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply, and the section as a whole is intended to apply in other
-circumstances.
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
 
 It is not the purpose of this section to induce you to infringe any
 patents or other property right claims or to contest validity of any
@@ -407,11 +404,11 @@
 
   12. If the distribution and/or use of the Library is restricted in
 certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library under this License
-may add an explicit geographical distribution limitation excluding those
-countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
 
   13. The Free Software Foundation may publish revised and/or new
 versions of the Lesser General Public License from time to time.
@@ -459,3 +456,47 @@
 DAMAGES.
 
                      END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/ChangeLog b/ChangeLog
index 6dc334b..cdd78a8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,5 @@
 ChangeLog discontinued, git history can be found here:
-http://git.kernel.org/?p=libs/netlink/libnl.git
+http://git.infradead.org/users/tgr/libnl.git
 
 Summary of Changes from 1.0-pre6 to 1.0-pre7
 ================================================
diff --git a/Makefile.am b/Makefile.am
index 8f9438c..bc4266d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,19 +2,21 @@
 
 ACLOCAL_AMFLAGS = -I m4
 
-OPT_DIRS =
+SUBDIRS = include lib man python tests
+
+pkgconfig_DATA = libnl-3.0.pc \
+		 libnl-route-3.0.pc \
+		 libnl-genl-3.0.pc \
+		 libnl-nf-3.0.pc
 
 if ENABLE_CLI
-OPT_DIRS += src
+SUBDIRS += src
+pkgconfig_DATA += libnl-cli-3.0.pc
 endif
 
-SUBDIRS = include lib doc $(OPT_DIRS)
 
-pkgconfig_DATA = libnl-2.0.pc
+pkgsysconfdir = ${sysconfdir}/libnl
+pkgsysconf_DATA = etc/pktloc etc/classid
 
-sysconfdir = @sysconfdir@/libnl
-sysconf_DATA = etc/pktloc
-
-.PHONY: cscope
-cscope:
-	cscope -b -q -R -Iinclude -slib -ssrc;
+EXTRA_DIST = \
+	$(pkgsysconf_DATA)
diff --git a/autogen.sh b/autogen.sh
index a569614..e498fae 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,4 +1,15 @@
 #!/bin/bash
 
-autoreconf -fi;
+die() {
+    echo "$@" >&2
+    exit 1
+}
+
+BASEDIR="$(dirname "$0")"
+
+cd "$BASEDIR" || die "Could not change into base directory $BASEDIR"
+
+autoreconf -fi || die "Error during autoreconf"
 rm -Rf autom4te.cache;
+
+doc/autogen.sh || die "Error during doc/autogen.sh"
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..c6f064e
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,184 @@
+#
+# configure.in
+#
+# 	This library is free software; you can redistribute it and/or
+#	modify it under the terms of the GNU Lesser General Public
+#	License as published by the Free Software Foundation version 2.1
+#	of the License.
+#
+# Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
+#
+
+
+# copied from glib
+m4_define([libnl_major_version], [3])
+m4_define([libnl_minor_version], [2])
+m4_define([libnl_micro_version], [25])
+m4_define([libnl_git_sha], [m4_esyscmd([ ( [ -d ./.git/ ] && [ "$(readlink -f ./.git/)" = "$(readlink -f "$(git rev-parse --git-dir 2>/dev/null)" 2>/dev/null)" ] && git rev-parse --verify -q HEAD 2>/dev/null ) || true ])])
+
+
+# The following explanation may help to understand the above rules a bit
+# better: consider that there are three possible kinds of reactions from
+# users of your library to changes in a shared library:
+#
+# 1. Programs using the previous version may use the new version as drop-in
+#    replacement, and programs using the new version can also work with the
+#    previous one. In other words, no recompiling nor relinking is needed.
+#    In this case, bump revision only, don't touch current nor age.
+#
+# 2. Programs using the previous version may use the new version as drop-in
+#    replacement, but programs using the new version may use APIs not
+#    present in the previous one. In other words, a program linking against
+#    the new version may fail with “unresolved symbols” if linking against
+#    the old version at runtime: set revision to 0, bump current and age.
+#
+# 3. Programs may need to be changed, recompiled, relinked in order to use
+#    the new version. Bump current, set revision and age to 0.
+
+m4_define([libnl_lt_current],    [220])
+m4_define([libnl_lt_revision],	 [0])
+m4_define([libnl_lt_age],        [20])
+
+m4_define([libnl_version],
+	  [libnl_major_version.libnl_minor_version.libnl_micro_version])
+
+AC_INIT(libnl, [libnl_version], [], [], [http://www.infradead.org/~tgr/libnl/])
+AC_CONFIG_HEADERS([lib/defs.h])
+AC_CONFIG_AUX_DIR([build-aux])
+AC_CONFIG_MACRO_DIR([m4])
+AM_INIT_AUTOMAKE([-Wall foreign subdir-objects])
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES(yes)], [])
+m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
+
+MAJ_VERSION=libnl_major_version
+AC_SUBST(MAJ_VERSION)
+MIN_VERSION=libnl_minor_version
+AC_SUBST(MIN_VERSION)
+MIC_VERSION=libnl_micro_version
+AC_SUBST(MIC_VERSION)
+LIBNL_GIT_SHA=libnl_git_sha
+LIBNL_VERSION=libnl_version
+AC_SUBST(LIBNL_VERSION)
+
+LT_CURRENT=libnl_lt_current
+AC_SUBST(LT_CURRENT)
+LT_REVISION=libnl_lt_revision
+AC_SUBST(LT_REVISION)
+LT_AGE=libnl_lt_age
+AC_SUBST(LT_AGE)
+
+AC_PROG_CC
+AM_PROG_CC_C_O
+AC_PROG_INSTALL
+AM_PROG_LIBTOOL
+AC_PROG_MKDIR_P
+AC_CHECK_PROGS(FLEX, 'flex')
+AC_CHECK_PROGS(YACC, 'bison -y')
+
+AC_C_CONST
+AC_C_INLINE
+
+PKG_CHECK_MODULES([CHECK], [check >= 0.9.0],
+	[enable_unit_tests="yes"],
+	[AC_MSG_WARN([*** Disabling building of unit tests])
+	 enable_unit_tests="no"])
+
+AM_CONDITIONAL([ENABLE_UNIT_TESTS], [test "$enable_unit_tests" = "yes"])
+
+AC_ARG_WITH([pkgconfigdir], AS_HELP_STRING([--with-pkgconfigdir=PATH],
+	[Path to the pkgconfig directory [[LIBDIR/pkgconfig]]]),
+	[pkgconfigdir="$withval"], [pkgconfigdir='${libdir}/pkgconfig'])
+AC_SUBST([pkgconfigdir])
+
+AC_ARG_ENABLE([cli],
+	AS_HELP_STRING([--disable-cli], [Do not build command line interface utils]),
+	[enable_cli="$enableval"], [enable_cli="yes"])
+AM_CONDITIONAL([ENABLE_CLI], [test "$enable_cli" = "yes"])
+
+AC_ARG_ENABLE([pthreads],
+	AS_HELP_STRING([--disable-pthreads], [Disable pthreads support]),
+	[enable_pthreads="$enableval"], [enable_pthreads="yes"])
+AM_CONDITIONAL([DISABLE_PTHREADS], [test "$enable_pthreads" = "no"])
+
+AC_ARG_ENABLE([debug],
+	AS_HELP_STRING([--disable-debug], [Do not include debugging statements]),
+	[enable_debug="$enableval"], [enable_debug="yes"])
+AM_CONDITIONAL([ENABLE_DEBUG], [test "$enable_debug" = "no" ])
+
+AC_CHECK_LIB([m], [pow], [], AC_MSG_ERROR([libm is required]))
+
+if test "x$enable_pthreads" = "xno"; then
+    AC_DEFINE([DISABLE_PTHREADS], [1], [Define to 1 to disable pthreads])
+else
+    AC_CHECK_LIB([pthread], [pthread_mutex_lock], [], AC_MSG_ERROR([libpthread is required]))
+fi
+
+if test "x$enable_debug" = "xyes"; then
+    AC_DEFINE([NL_DEBUG], [1], [Define to 1 to enable debugging])
+fi
+
+AC_CONFIG_SUBDIRS([doc])
+
+AC_CONFIG_FILES([
+Makefile
+libnl.sym
+libnl-3.0.pc
+libnl-route-3.0.pc
+libnl-genl-3.0.pc
+libnl-nf-3.0.pc
+libnl-cli-3.0.pc
+lib/Makefile
+include/Makefile
+src/Makefile
+src/lib/Makefile
+tests/Makefile
+man/Makefile
+python/Makefile
+python/setup.py
+python/doc/Makefile
+python/examples/Makefile
+python/netlink/Makefile
+python/netlink/genl/Makefile
+python/netlink/route/Makefile
+python/tests/Makefile
+include/netlink/version.h
+])
+
+ac_errcount=0
+if test -z "$YACC"; then
+    AC_MSG_WARN(bison not found. Please install before continuing.)
+    ac_errcount=$((ac_errcount + 1))
+fi
+if test -z "$FLEX"; then
+    AC_MSG_WARN(flex not found. Please install before continuing.)
+    ac_errcount=$((ac_errcount + 1))
+fi
+if test $ac_errcount -gt 0; then
+    AC_MSG_ERROR(Required packages are missing. Please install them and rerun ./configure)
+fi
+
+AC_OUTPUT
+
+echo "-------------------------------------------------------------------------------"
+echo "                                  NOTE"
+echo ""
+echo " There have been some changes starting with 3.2 regarding where and how libnl"
+echo " is being installed on the system in order to allow multiple libnl versions"
+echo " to be installed in parallel:"
+echo ""
+echo "    - Headers will be installed in ${includedir}/libnl${MAJ_VERSION}, therefore"
+echo "      you will need to add \"-I/usr/include/libnl${MAJ_VERSION}\" to CFLAGS"
+echo ""
+echo "    - The library basename was renamed to libnl-${MAJ_VERSION}, i.e. the SO names become"
+echo "      libnl-${MAJ_VERSION}.so., libnl-route-${MAJ_VERSION}.so, etc."
+echo ""
+echo "    - libtool versioning was assumed, to ease detection of compatible library"
+echo "      versions."
+echo ""
+echo " If you are using pkg-config for detecting and linking against the library "
+echo " things will continue magically as if nothing every happened. If you are "
+echo " linking manually you need to adapt your Makefiles or switch to using "
+echo " pkg-config files."
+echo ""
+echo "-------------------------------------------------------------------------------"
+
diff --git a/configure.in b/configure.in
deleted file mode 100644
index 18d2716..0000000
--- a/configure.in
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# configure.in
-#
-# 	This library is free software; you can redistribute it and/or
-#	modify it under the terms of the GNU Lesser General Public
-#	License as published by the Free Software Foundation version 2.1
-#	of the License.
-#
-# Copyright (c) 2003-2010 Thomas Graf <tgraf@suug.ch>
-#
-
-AC_INIT(libnl, 2.0, tgraf@suug.ch)
-AC_CONFIG_HEADERS([lib/defs.h])
-AC_CONFIG_MACRO_DIR([m4])
-AM_INIT_AUTOMAKE([-Wall foreign subdir-objects])
-
-AC_PROG_CC
-AM_PROG_CC_C_O
-AC_PROG_INSTALL
-AM_PROG_LIBTOOL
-AM_PROG_LEX
-AC_PROG_YACC
-
-AC_C_CONST
-AC_C_INLINE
-
-AC_ARG_WITH([pkgconfigdir], AS_HELP_STRING([--with-pkgconfigdir=PATH],
-	[Path to the pkgconfig directory [[LIBDIR/pkgconfig]]]),
-	[pkgconfigdir="$withval"], [pkgconfigdir='${libdir}/pkgconfig'])
-AC_SUBST([pkgconfigdir])
-
-AC_ARG_ENABLE([cli],
-	AS_HELP_STRING([--disable-cli], [Do not build command line interface utils]),
-	[enable_cli="$enableval"], [enable_cli="yes"])
-AM_CONDITIONAL([ENABLE_CLI], [test "$enable_cli" = "yes"])
-
-AC_CHECK_LIB([m], [pow], [], AC_MSG_ERROR([libm is required]))
-
-AC_CONFIG_FILES([Makefile doc/Doxyfile doc/Makefile lib/Makefile
-	include/Makefile src/Makefile src/lib/Makefile \
-	libnl-2.0.pc include/netlink/version.h])
-AC_OUTPUT
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644
index 0000000..f7cb70d
--- /dev/null
+++ b/doc/.gitignore
@@ -0,0 +1,8 @@
+*.html
+libnl.dict
+Doxyfile
+/aclocal.m4
+/autom4te.cache/
+/build-aux/
+/config.*
+/configure
diff --git a/doc/AUTHORS b/doc/AUTHORS
new file mode 100644
index 0000000..26e3cb4
--- /dev/null
+++ b/doc/AUTHORS
@@ -0,0 +1 @@
+Thomas Graf <tgraf@suug.ch>
diff --git a/doc/COPYING b/doc/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/doc/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index 8e311e3..50e8f0c 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -1,203 +1,268 @@
-# Doxyfile 1.5.2
+# Doxyfile 1.8.1.1
 
 # This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project
+# doxygen (www.doxygen.org) for a project.
 #
-# All text after a hash (#) is considered a comment and will be ignored
+# All text after a hash (#) is considered a comment and will be ignored.
 # The format is:
 #       TAG = value [value, ...]
 # For lists items can also be appended using:
 #       TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (" ")
+# Values that contain spaces should be placed between quotes (" ").
 
 #---------------------------------------------------------------------------
 # Project related configuration options
 #---------------------------------------------------------------------------
 
-# This tag specifies the encoding used for all characters in the config file that 
-# follow. The default is UTF-8 which is also the encoding used for all text before 
-# the first occurrence of this tag. Doxygen uses libiconv (or the iconv built into 
-# libc) for the transcoding. See http://www.gnu.org/software/libiconv for the list of 
-# possible encodings.
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
 
 DOXYFILE_ENCODING      = UTF-8
 
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
-# by quotes) that should identify the project.
+# The PROJECT_NAME tag is a single word (or sequence of words) that should
+# identify the project. Note that if you do not use Doxywizard you need
+# to put quotes around the project name if it contains spaces.
 
 PROJECT_NAME           = libnl
 
-# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
-# This could be handy for archiving the generated documentation or 
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
 # if some version control system is used.
 
 PROJECT_NUMBER         = @PACKAGE_VERSION@
 
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
-# base path where the generated documentation will be put. 
-# If a relative path is entered, it will be relative to the location 
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          =
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
 # where doxygen was started. If left blank the current directory will be used.
 
 OUTPUT_DIRECTORY       = ./
 
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
-# 4096 sub-directories (in 2 levels) under the output directory of each output 
-# format and will distribute the generated files over these directories. 
-# Enabling this option can be useful when feeding doxygen a huge amount of 
-# source files, where putting all generated files in the same directory would 
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
 # otherwise cause performance problems for the file system.
 
 CREATE_SUBDIRS         = NO
 
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
-# documentation generated by doxygen is written. Doxygen will use this 
-# information to generate all constant output in the proper language. 
-# The default language is English, other supported languages are: 
-# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, 
-# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, 
-# Italian, Japanese, Japanese-en (Japanese with English messages), Korean, 
-# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, 
-# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
 
 OUTPUT_LANGUAGE        = English
 
-# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
-# include brief member descriptions after the members that are listed in 
-# the file and class documentation (similar to JavaDoc). 
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
 # Set to NO to disable this.
 
 BRIEF_MEMBER_DESC      = YES
 
-# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
-# the brief description of a member or function before the detailed description. 
-# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
 # brief descriptions will be completely suppressed.
 
-REPEAT_BRIEF           = NO
+REPEAT_BRIEF           = YES
 
-# This tag implements a quasi-intelligent brief description abbreviator 
-# that is used to form the text in various listings. Each string 
-# in this list, if found as the leading text of the brief description, will be 
-# stripped from the text and the result after processing the whole list, is 
-# used as the annotated text. Otherwise, the brief description is used as-is. 
-# If left blank, the following values are used ("$name" is automatically 
-# replaced with the name of the entity): "The $name class" "The $name widget" 
-# "The $name file" "is" "provides" "specifies" "contains" 
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
 # "represents" "a" "an" "the"
 
-ABBREVIATE_BRIEF       = 
+ABBREVIATE_BRIEF       =
 
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
-# Doxygen will generate a detailed section even if there is only a brief 
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
 # description.
 
-ALWAYS_DETAILED_SEC    = YES
+ALWAYS_DETAILED_SEC    = NO
 
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
-# inherited members of a class in the documentation of that class as if those 
-# members were ordinary class members. Constructors, destructors and assignment 
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
 # operators of the base classes will not be shown.
 
 INLINE_INHERITED_MEMB  = YES
 
-# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
-# path before files name in the file list and in the header files. If set 
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
 # to NO the shortest path that makes the file name unique will be used.
 
 FULL_PATH_NAMES        = YES
 
-# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
-# can be used to strip a user-defined part of the path. Stripping is 
-# only done if one of the specified strings matches the left-hand part of 
-# the path. The tag can be used to show relative paths in the file list. 
-# If left blank the directory from which doxygen is run is used as the 
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
 # path to strip.
 
-STRIP_FROM_PATH        = 
+STRIP_FROM_PATH        =
 
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
-# the path mentioned in the documentation of a class, which tells 
-# the reader which header file to include in order to use a class. 
-# If left blank only the name of the header file containing the class 
-# definition is used. Otherwise one should specify the include paths that 
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
 # are normally passed to the compiler using the -I flag.
 
-STRIP_FROM_INC_PATH    = 
+STRIP_FROM_INC_PATH    =
 
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
-# (but less readable) file names. This can be useful is your file systems 
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
 # doesn't support long names like on DOS, Mac, or CD-ROM.
 
 SHORT_NAMES            = NO
 
-# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
-# will interpret the first line (until the first dot) of a JavaDoc-style 
-# comment as the brief description. If set to NO, the JavaDoc 
-# comments will behave just like the Qt-style comments (thus requiring an 
-# explicit @brief command for a brief description.
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
 
 JAVADOC_AUTOBRIEF      = YES
 
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
-# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
-# comments) as a brief description. This used to be the default behaviour. 
-# The new default is to treat a multi-line C++ comment block as a detailed 
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
 # description. Set this tag to YES if you prefer the old behaviour instead.
 
 MULTILINE_CPP_IS_BRIEF = NO
 
-# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
-# will output the detailed description near the top, like JavaDoc.
-# If set to NO, the detailed description appears after the member 
-# documentation.
-
-DETAILS_AT_TOP         = YES
-
-# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
-# member inherits the documentation from any documented member that it 
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
 # re-implements.
 
 INHERIT_DOCS           = NO
 
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
-# a new page for each member. If set to NO, the documentation of a member will 
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
 # be part of the file/class/namespace that contains it.
 
 SEPARATE_MEMBER_PAGES  = NO
 
-# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
 # Doxygen uses this value to replace tabs by spaces in code fragments.
 
 TAB_SIZE               = 8
 
-# This tag can be used to specify a number of aliases that acts 
-# as commands in the documentation. An alias has the form "name=value". 
-# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
-# put the command \sideeffect (or @sideeffect) in the documentation, which 
-# will result in a user-defined paragraph with heading "Side Effects:". 
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
 # You can put \n's in the value part of an alias to insert newlines.
 
-ALIASES                = arg=\param
+ALIASES                = arg=\param \
+                         "ref_asciidoc{3}=<a href=\"../\1.html#\2\"><b>\3</b></a>" \
+                         "ref_core{2}=\ref_asciidoc{core,\1,\2 (Netlink Core Library Development Guide)}" \
+                         "ref_route{2}=\ref_asciidoc{route,\1,\2 (Netlink Routing Development Guide)}" \
+                         "ref_idiagnl{2}=\ref_asciidoc{idiag,\1,\2 (Netlink Inet Diag Development Guide)}" \
+                         "core_doc{2}=\ref_core{\1,\2}" \
+                         "route_doc{2}=\ref_route{\1,\2}" \
+                         "idiagnl_doc{2}=\ref_idiagnl{\1,\2}" \
+			 "callback=\par Callback Invocation:\n" \
+			 "lowlevel=\copydoc low_level_api"
 
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
-# sources only. Doxygen will then generate output that is more tailored for C. 
-# For instance, some of the names that are used will be different. The list 
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding
+# "class=itcl::class" will allow you to use the command class in the
+# itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
 # of all members will be omitted, etc.
 
 OPTIMIZE_OUTPUT_FOR_C  = YES
 
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
-# sources only. Doxygen will then generate output that is more tailored for Java. 
-# For instance, namespaces will be presented as packages, qualified scopes 
-# will look different, etc.
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
 
 OPTIMIZE_OUTPUT_JAVA   = NO
 
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to 
-# include (a tag file for) the STL sources as input, then you should 
-# set this tag to YES in order to let doxygen match functions declarations and 
-# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
-# func(std::string) {}). This also make the inheritance and collaboration 
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all
+# comments according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you
+# can mix doxygen, HTML, and XML commands with Markdown formatting.
+# Disable only in case of backward compatibilities issues.
+
+MARKDOWN_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
 # diagrams that involve STL classes more complete and accurate.
 
 BUILTIN_STL_SUPPORT    = NO
@@ -207,396 +272,551 @@
 
 CPP_CLI_SUPPORT        = NO
 
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
-# tag is set to YES, then doxygen will reuse the documentation of the first 
-# member in the group (if any) for the other members of the group. By default 
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
 # all members of a group must be documented explicitly.
 
 DISTRIBUTE_GROUP_DOC   = NO
 
-# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
-# the same type (for instance a group of public functions) to be put as a 
-# subgroup of that type (e.g. under the Public Functions section). Set it to 
-# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
 # the \nosubgrouping command.
 
 SUBGROUPING            = YES
 
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
+# unions with only public data fields will be shown inline in the documentation
+# of the scope in which they are defined (i.e. file, namespace, or group
+# documentation), provided this scope is documented. If set to NO (the default),
+# structs, classes, and unions are shown on a separate page (for HTML and Man
+# pages) or section (for LaTeX and RTF).
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+SYMBOL_CACHE_SIZE      = 0
+
+# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
+# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
+# their name and scope. Since this can be an expensive process and often the
+# same symbol appear multiple times in the code, doxygen keeps a cache of
+# pre-resolved symbols. If the cache is too small doxygen will become slower.
+# If the cache is too large, memory is wasted. The cache size is given by this
+# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+LOOKUP_CACHE_SIZE      = 0
+
 #---------------------------------------------------------------------------
 # Build related configuration options
 #---------------------------------------------------------------------------
 
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
-# documentation are documented, even if no documentation was available. 
-# Private class members and static file members will be hidden unless 
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
 # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
 
 EXTRACT_ALL            = NO
 
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
 # will be included in the documentation.
 
 EXTRACT_PRIVATE        = YES
 
-# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
 # will be included in the documentation.
 
 EXTRACT_STATIC         = NO
 
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
-# defined locally in source files will be included in the documentation. 
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
 # If set to NO only classes defined in header files are included.
 
 EXTRACT_LOCAL_CLASSES  = YES
 
-# This flag is only useful for Objective-C code. When set to YES local 
-# methods, which are defined in the implementation section but not in 
-# the interface are included in the documentation. 
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
 # If set to NO (the default) only methods in the interface are included.
 
 EXTRACT_LOCAL_METHODS  = YES
 
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
-# undocumented members of documented classes, files or namespaces. 
-# If set to NO (the default) these members will be included in the 
-# various overviews, but no documentation section is generated. 
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
 # This option has no effect if EXTRACT_ALL is enabled.
 
 HIDE_UNDOC_MEMBERS     = NO
 
-# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
-# undocumented classes that are normally visible in the class hierarchy. 
-# If set to NO (the default) these classes will be included in the various 
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
 # overviews. This option has no effect if EXTRACT_ALL is enabled.
 
 HIDE_UNDOC_CLASSES     = NO
 
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
-# friend (class|struct|union) declarations. 
-# If set to NO (the default) these declarations will be included in the 
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
 # documentation.
 
 HIDE_FRIEND_COMPOUNDS  = NO
 
-# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
-# documentation blocks found inside the body of a function. 
-# If set to NO (the default) these blocks will be appended to the 
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
 # function's detailed documentation block.
 
 HIDE_IN_BODY_DOCS      = NO
 
-# The INTERNAL_DOCS tag determines if documentation 
-# that is typed after a \internal command is included. If the tag is set 
-# to NO (the default) then the documentation will be excluded. 
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
 # Set it to YES to include the internal documentation.
 
 INTERNAL_DOCS          = NO
 
-# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
-# file names in lower-case letters. If set to YES upper-case letters are also 
-# allowed. This is useful if you have classes or files whose names only differ 
-# in case and if your file system supports case sensitive file names. Windows 
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
 # and Mac users are advised to set this option to NO.
 
 CASE_SENSE_NAMES       = YES
 
-# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
-# will show members with their full class and namespace scopes in the 
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
 # documentation. If set to YES the scope will be hidden.
 
 HIDE_SCOPE_NAMES       = NO
 
-# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
-# will put a list of the files that are included by a file in the documentation 
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
 # of that file.
 
 SHOW_INCLUDE_FILES     = YES
 
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
 # is inserted in the documentation for inline members.
 
 INLINE_INFO            = YES
 
-# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
-# will sort the (detailed) documentation of file and class members 
-# alphabetically by member name. If set to NO the members will appear in 
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
 # declaration order.
 
 SORT_MEMBER_DOCS       = NO
 
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
-# brief documentation of file, namespace and class members alphabetically 
-# by member name. If set to NO (the default) the members will appear in 
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
 # declaration order.
 
 SORT_BRIEF_DOCS        = NO
 
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
-# sorted by fully-qualified names, including namespaces. If set to 
-# NO (the default), the class list will be sorted only by class name, 
-# not including the namespace part. 
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = YES
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
 # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the 
+# Note: This option applies only to the class list, not to the
 # alphabetical list.
 
 SORT_BY_SCOPE_NAME     = NO
 
-# The GENERATE_TODOLIST tag can be used to enable (YES) or 
-# disable (NO) the todo list. This list is created by putting \todo 
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
 # commands in the documentation.
 
 GENERATE_TODOLIST      = NO
 
-# The GENERATE_TESTLIST tag can be used to enable (YES) or 
-# disable (NO) the test list. This list is created by putting \test 
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
 # commands in the documentation.
 
 GENERATE_TESTLIST      = NO
 
-# The GENERATE_BUGLIST tag can be used to enable (YES) or 
-# disable (NO) the bug list. This list is created by putting \bug 
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
 # commands in the documentation.
 
-GENERATE_BUGLIST       = NO
+GENERATE_BUGLIST       = YES
 
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
-# disable (NO) the deprecated list. This list is created by putting 
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
 # \deprecated commands in the documentation.
 
-GENERATE_DEPRECATEDLIST= NO
+GENERATE_DEPRECATEDLIST= YES
 
-# The ENABLED_SECTIONS tag can be used to enable conditional 
+# The ENABLED_SECTIONS tag can be used to enable conditional
 # documentation sections, marked by \if sectionname ... \endif.
 
-ENABLED_SECTIONS       = 
+ENABLED_SECTIONS       =
 
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
-# the initial value of a variable or define consists of for it to appear in 
-# the documentation. If the initializer consists of more lines than specified 
-# here it will be hidden. Use a value of 0 to hide initializers completely. 
-# The appearance of the initializer of individual variables and defines in the 
-# documentation can be controlled using \showinitializer or \hideinitializer 
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
 # command in the documentation regardless of this setting.
 
 MAX_INITIALIZER_LINES  = 30
 
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
-# at the bottom of the documentation of classes and structs. If set to YES the 
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
 # list will mention the files that were used to generate the documentation.
 
 SHOW_USED_FILES        = NO
 
-# If the sources in your project are distributed over multiple directories 
-# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
-# in the documentation. The default is NO.
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
 
-SHOW_DIRECTORIES       = NO
+SHOW_FILES             = NO
 
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
-# doxygen should invoke to get the current version for each file (typically from the 
-# version control system). Doxygen will invoke the program by executing (via 
-# popen()) the command <command> <input-file>, where <command> is the value of 
-# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
-# provided by doxygen. Whatever the program writes to standard output 
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
 # is used as the file version. See the manual for examples.
 
-FILE_VERSION_FILTER    = 
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            = DoxygenLayout.xml
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files
+# containing the references data. This must be a list of .bib files. The
+# .bib extension is automatically appended if omitted. Using this command
+# requires the bibtex tool to be installed. See also
+# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
+# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
+# feature you need bibtex and perl available in the search path.
+
+CITE_BIB_FILES         =
 
 #---------------------------------------------------------------------------
 # configuration options related to warning and progress messages
 #---------------------------------------------------------------------------
 
-# The QUIET tag can be used to turn on/off the messages that are generated 
+# The QUIET tag can be used to turn on/off the messages that are generated
 # by doxygen. Possible values are YES and NO. If left blank NO is used.
 
 QUIET                  = YES
 
-# The WARNINGS tag can be used to turn on/off the warning messages that are 
-# generated by doxygen. Possible values are YES and NO. If left blank 
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
 # NO is used.
 
 WARNINGS               = YES
 
-# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
-# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
 # automatically be disabled.
 
 WARN_IF_UNDOCUMENTED   = NO
 
-# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
-# potential errors in the documentation, such as not documenting some 
-# parameters in a documented function, or documenting parameters that 
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
 # don't exist or using markup commands wrongly.
 
 WARN_IF_DOC_ERROR      = YES
 
-# This WARN_NO_PARAMDOC option can be abled to get warnings for 
-# functions that are documented, but have no documentation for their parameters 
-# or return value. If set to NO (the default) doxygen will only warn about 
-# wrong or incomplete parameter documentation, but not about the absence of 
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
 # documentation.
 
 WARN_NO_PARAMDOC       = NO
 
-# The WARN_FORMAT tag determines the format of the warning messages that 
-# doxygen can produce. The string should contain the $file, $line, and $text 
-# tags, which will be replaced by the file and line number from which the 
-# warning originated and the warning text. Optionally the format may contain 
-# $version, which will be replaced by the version of the file (if it could 
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
 # be obtained via FILE_VERSION_FILTER)
 
 WARN_FORMAT            = "$file:$line: $text"
 
-# The WARN_LOGFILE tag can be used to specify a file to which warning 
-# and error messages should be written. If left blank the output is written 
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
 # to stderr.
 
-WARN_LOGFILE           = 
+WARN_LOGFILE           =
 
 #---------------------------------------------------------------------------
 # configuration options related to the input files
 #---------------------------------------------------------------------------
 
-# The INPUT tag can be used to specify the files and/or directories that contain 
-# documented source files. You may enter file names like "myfile.cpp" or 
-# directories like "/usr/src/myproject". Separate the files or directories 
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = ../lib \
-                         ../src/lib \
-                         ../include/netlink
+INPUT                  = @top_srcdir@/../lib \
+                         @top_srcdir@/../src/lib \
+                         @top_srcdir@/../include/netlink \
+                         @top_srcdir@/../src \
+                         @top_srcdir@/../doc/src
 
-# This tag can be used to specify the character encoding of the source files that 
-# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default 
-# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. 
-# See http://www.gnu.org/software/libiconv for the list of possible encodings.
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
 
 INPUT_ENCODING         = UTF-8
 
-# If the value of the INPUT tag contains directories, you can use the 
-# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
-# and *.h) to filter out the source-files in the directories. If left 
-# blank the following patterns are tested: 
-# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
-# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
 
 FILE_PATTERNS          = *.c \
                          *.h
 
-# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
-# should be searched for input files as well. Possible values are YES and NO. 
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
 # If left blank NO is used.
 
 RECURSIVE              = YES
 
-# The EXCLUDE tag can be used to specify files and/or directories that should 
-# excluded from the INPUT source files. This way you can easily exclude a 
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
 # subdirectory from a directory tree whose root is specified with the INPUT tag.
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
 
 EXCLUDE                = SCCS
 
-# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
-# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
 # from the input.
 
 EXCLUDE_SYMLINKS       = NO
 
-# If the value of the INPUT tag contains directories, you can use the 
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
-# certain files from those directories. Note that the wildcards are matched 
-# against the file with absolute path, so to exclude all test directories 
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
 # for example use the pattern */test/*
 
-EXCLUDE_PATTERNS       = 
+EXCLUDE_PATTERNS       =
 
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
-# (namespaces, classes, functions, etc.) that should be excluded from the output. 
-# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, 
-# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
 
-EXCLUDE_SYMBOLS        = 
+EXCLUDE_SYMBOLS        =
 
-# The EXAMPLE_PATH tag can be used to specify one or more files or 
-# directories that contain example code fragments that are included (see 
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
 # the \include command).
 
-EXAMPLE_PATH           = 
+EXAMPLE_PATH           = @top_srcdir@/src
 
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
-# and *.h) to filter out the source-files in the directories. If left 
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
 # blank all files are included.
 
-EXAMPLE_PATTERNS       = 
+EXAMPLE_PATTERNS       =
 
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
-# searched for input files to be used with the \include or \dontinclude 
-# commands irrespective of the value of the RECURSIVE tag. 
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
 # Possible values are YES and NO. If left blank NO is used.
 
 EXAMPLE_RECURSIVE      = NO
 
-# The IMAGE_PATH tag can be used to specify one or more files or 
-# directories that contain image that are included in the documentation (see 
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
 # the \image command).
 
-IMAGE_PATH             = 
+IMAGE_PATH             =
 
-# The INPUT_FILTER tag can be used to specify a program that doxygen should 
-# invoke to filter for each input file. Doxygen will invoke the filter program 
-# by executing (via popen()) the command <filter> <input-file>, where <filter> 
-# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
-# input file. Doxygen will then use the output that the filter program writes 
-# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
 # ignored.
 
-INPUT_FILTER           = 
+INPUT_FILTER           =
 
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
-# basis.  Doxygen will compare the file name with each pattern and apply the 
-# filter if there is a match.  The filters are a list of the form: 
-# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
-# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
-# is applied to all files.
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
 
-FILTER_PATTERNS        = 
+FILTER_PATTERNS        =
 
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
-# INPUT_FILTER) will be used to filter the input files when producing source 
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
 # files to browse (i.e. when SOURCE_BROWSER is set to YES).
 
 FILTER_SOURCE_FILES    = NO
 
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
 #---------------------------------------------------------------------------
 # configuration options related to source browsing
 #---------------------------------------------------------------------------
 
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
-# be generated. Documented entities will be cross-referenced with these sources. 
-# Note: To get rid of all source code in the generated output, make sure also 
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
 # VERBATIM_HEADERS is set to NO.
 
 SOURCE_BROWSER         = YES
 
-# Setting the INLINE_SOURCES tag to YES will include the body 
+# Setting the INLINE_SOURCES tag to YES will include the body
 # of functions and classes directly in the documentation.
 
 INLINE_SOURCES         = NO
 
-# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
-# doxygen to hide any special comment blocks from generated source code 
-# fragments. Normal C and C++ comments will always remain visible.
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C, C++ and Fortran comments will always remain visible.
 
 STRIP_CODE_COMMENTS    = NO
 
-# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
-# then for each documented function all documented 
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
 # functions referencing it will be listed.
 
 REFERENCED_BY_RELATION = YES
 
-# If the REFERENCES_RELATION tag is set to YES (the default) 
-# then for each documented function all documented entities 
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
 # called/used by that function will be listed.
 
 REFERENCES_RELATION    = YES
@@ -604,43 +824,44 @@
 # If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
 # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
 # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
-# link to the source code.  Otherwise they will link to the documentstion.
+# link to the source code.
+# Otherwise they will link to the documentation.
 
 REFERENCES_LINK_SOURCE = YES
 
-# If the USE_HTAGS tag is set to YES then the references to source code 
-# will point to the HTML generated by the htags(1) tool instead of doxygen 
-# built-in source browser. The htags tool is part of GNU's global source 
-# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
 # will need version 4.8.6 or higher.
 
 USE_HTAGS              = NO
 
-# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
-# will generate a verbatim copy of the header file for each class for 
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
 # which an include is specified. Set to NO to disable this.
 
-VERBATIM_HEADERS       = NO
+VERBATIM_HEADERS       = YES
 
 #---------------------------------------------------------------------------
 # configuration options related to the alphabetical class index
 #---------------------------------------------------------------------------
 
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
-# of all compounds will be generated. Enable this if the project 
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
 # contains a lot of classes, structs, unions or interfaces.
 
-ALPHABETICAL_INDEX     = NO
+ALPHABETICAL_INDEX     = YES
 
-# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
-# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
 # in which this list will be split (can be a number in the range [1..20])
 
 COLS_IN_ALPHA_INDEX    = 5
 
-# In case all classes in a project start with a common prefix, all 
-# classes will be put under the same header in the alphabetical index. 
-# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
 # should be ignored while generating the index headers.
 
 IGNORE_PREFIX          = nl_
@@ -649,256 +870,524 @@
 # configuration options related to the HTML output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
 # generate HTML output.
 
 GENERATE_HTML          = YES
 
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
 # put in front of it. If left blank `html' will be used as the default path.
 
-HTML_OUTPUT            = html
+HTML_OUTPUT            = api
 
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
-# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
 # doxygen will generate files with .html extension.
 
 HTML_FILE_EXTENSION    = .html
 
-# The HTML_HEADER tag can be used to specify a personal HTML header for 
-# each generated HTML page. If it is left blank doxygen will generate a 
-# standard header.
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+#  for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when
+# changing the value of configuration settings such as GENERATE_TREEVIEW!
 
-HTML_HEADER            = 
+HTML_HEADER            =
 
-# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
-# each generated HTML page. If it is left blank doxygen will generate a 
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
 # standard footer.
 
-HTML_FOOTER            = 
+HTML_FOOTER            =
 
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
-# style sheet that is used by each HTML page. It can be used to 
-# fine-tune the look of the HTML output. If the tag is left blank doxygen 
-# will generate a default style sheet. Note that doxygen will try to copy 
-# the style sheet file to the HTML output directory, so don't put your own 
-# stylesheet in the HTML output directory as well, or it will be erased!
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# style sheet in the HTML output directory as well, or it will be erased!
 
-HTML_STYLESHEET        = 
+HTML_STYLESHEET        = @srcdir@/libnl.css
 
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
-# files or namespaces will be aligned in HTML using tables. If set to 
-# NO a bullet list will be used.
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
 
-HTML_ALIGN_MEMBERS     = YES
+HTML_EXTRA_FILES       =
 
-# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
-# will be generated that can be used as input for tools like the 
-# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the style sheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+
+HTML_DYNAMIC_SECTIONS  = YES
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of
+# entries shown in the various tree structured indices initially; the user
+# can expand and collapse entries dynamically later on. Doxygen will expand
+# the tree to such a level that at most the specified number of entries are
+# visible (unless a fully collapsed tree already exceeds this amount).
+# So setting the number of entries 1 will produce a full collapsed tree by
+# default. 0 is a special value representing an infinite number of entries
+# and will result in a full expanded tree by default.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
 # of the generated HTML documentation.
 
 GENERATE_HTMLHELP      = NO
 
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
-# be used to specify the file name of the resulting .chm file. You 
-# can add a path in front of the file if the result should not be 
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
 # written to the html output directory.
 
-CHM_FILE               = 
+CHM_FILE               =
 
-# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
-# be used to specify the location (absolute path including file name) of 
-# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
 # the HTML help compiler on the generated index.hhp.
 
-HHC_LOCATION           = 
+HHC_LOCATION           =
 
-# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
-# controls if a separate .chi index file is generated (YES) or that 
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
 # it should be included in the master .chm file (NO).
 
 GENERATE_CHI           = NO
 
-# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
-# controls whether a binary table of contents is generated (YES) or a 
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
 # normal table of contents (NO) in the .chm file.
 
 BINARY_TOC             = NO
 
-# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
 # to the contents of the HTML help documentation and to the tree view.
 
 TOC_EXPAND             = NO
 
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
-# top of each HTML page. The value NO (the default) enables the index and 
-# the value YES disables it.
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.infradead.libnl
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
+# at top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it. Since the tabs have the same information as the
+# navigation tree you can set this option to NO if you already set
+# GENERATE_TREEVIEW to YES.
 
 DISABLE_INDEX          = NO
 
-# This tag can be used to set the number of enum values (range [1..20]) 
-# that doxygen will group on one line in the generated HTML documentation.
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+# Since the tree basically has the same information as the tab index you
+# could consider to set DISABLE_INDEX to NO when enabling this option.
 
-ENUM_VALUES_PER_LINE   = 4
+GENERATE_TREEVIEW      = YES
 
-# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
-# generated containing a tree-like index structure (just like the one that 
-# is generated for HTML Help). For this to work a browser that supports 
-# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
-# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
-# probably better off using the HTML help feature.
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
 
-GENERATE_TREEVIEW      = NO
+ENUM_VALUES_PER_LINE   = 1
 
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
-# used to set the initial width (in pixels) of the frame in which the tree 
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
 # is shown.
 
-TREEVIEW_WIDTH         = 250
+TREEVIEW_WIDTH         = 205
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you may also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to
+# the MathJax Content Delivery Network so you can quickly see the result without
+# installing MathJax.
+# However, it is strongly recommended to install a local
+# copy of MathJax from http://www.mathjax.org before deployment.
+
+MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
+# names that should be enabled during MathJax rendering.
+
+MATHJAX_EXTENSIONS     =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
 
 #---------------------------------------------------------------------------
 # configuration options related to the LaTeX output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
 # generate Latex output.
 
 GENERATE_LATEX         = NO
 
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
 # put in front of it. If left blank `latex' will be used as the default path.
 
 LATEX_OUTPUT           = latex
 
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
 # invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
 
 LATEX_CMD_NAME         = latex
 
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
-# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
 # default command name.
 
 MAKEINDEX_CMD_NAME     = makeindex
 
-# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
-# LaTeX documents. This may be useful for small projects and may help to 
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
 # save some trees in general.
 
 COMPACT_LATEX          = NO
 
-# The PAPER_TYPE tag can be used to set the paper type that is used 
-# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
 # executive. If left blank a4wide will be used.
 
 PAPER_TYPE             = a4wide
 
-# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
 # packages that should be included in the LaTeX output.
 
-EXTRA_PACKAGES         = 
+EXTRA_PACKAGES         =
 
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
-# the generated latex document. The header should contain everything until 
-# the first chapter. If it is left blank doxygen will generate a 
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
 # standard header. Notice: only use this tag if you know what you are doing!
 
-LATEX_HEADER           = 
+LATEX_HEADER           =
 
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
-# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
-# contain links (just like the HTML output) instead of page references 
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
 # This makes the output suitable for online browsing using a pdf viewer.
 
 PDF_HYPERLINKS         = NO
 
-# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
-# plain latex in the generated Makefile. Set this option to YES to get a 
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
 # higher quality PDF documentation.
 
 USE_PDFLATEX           = NO
 
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
-# command to the generated LaTeX files. This will instruct LaTeX to keep 
-# running if errors occur, instead of asking the user for help. 
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
 # This option is also used when generating formulas in HTML.
 
 LATEX_BATCHMODE        = NO
 
-# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
-# include the index chapters (such as File Index, Compound Index, etc.) 
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
 # in the output.
 
 LATEX_HIDE_INDICES     = NO
 
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
+# http://en.wikipedia.org/wiki/BibTeX for more info.
+
+LATEX_BIB_STYLE        = plain
+
 #---------------------------------------------------------------------------
 # configuration options related to the RTF output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
-# The RTF output is optimized for Word 97 and may not look very pretty with 
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
 # other RTF readers or editors.
 
 GENERATE_RTF           = NO
 
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
 # put in front of it. If left blank `rtf' will be used as the default path.
 
 RTF_OUTPUT             = rtf
 
-# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
-# RTF documents. This may be useful for small projects and may help to 
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
 # save some trees in general.
 
 COMPACT_RTF            = NO
 
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
-# will contain hyperlink fields. The RTF file will 
-# contain links (just like the HTML output) instead of page references. 
-# This makes the output suitable for online browsing using WORD or other 
-# programs which support those fields. 
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
 # Note: wordpad (write) and others do not support links.
 
 RTF_HYPERLINKS         = NO
 
-# Load stylesheet definitions from file. Syntax is similar to doxygen's 
-# config file, i.e. a series of assignments. You only have to provide 
+# Load style sheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
 # replacements, missing definitions are set to their default value.
 
-RTF_STYLESHEET_FILE    = 
+RTF_STYLESHEET_FILE    =
 
-# Set optional variables used in the generation of an rtf document. 
+# Set optional variables used in the generation of an rtf document.
 # Syntax is similar to doxygen's config file.
 
-RTF_EXTENSIONS_FILE    = 
+RTF_EXTENSIONS_FILE    =
 
 #---------------------------------------------------------------------------
 # configuration options related to the man page output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
 # generate man pages
 
 GENERATE_MAN           = NO
 
-# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
 # put in front of it. If left blank `man' will be used as the default path.
 
 MAN_OUTPUT             = man
 
-# The MAN_EXTENSION tag determines the extension that is added to 
+# The MAN_EXTENSION tag determines the extension that is added to
 # the generated man pages (default is the subroutine's section .3)
 
 MAN_EXTENSION          = .3
 
-# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
-# then it will generate one additional man file for each entity 
-# documented in the real man page(s). These additional files 
-# only source the real man page, but without them the man command 
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
 # would be unable to find the correct page. The default is NO.
 
 MAN_LINKS              = NO
@@ -907,33 +1396,33 @@
 # configuration options related to the XML output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_XML tag is set to YES Doxygen will 
-# generate an XML file that captures the structure of 
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
 # the code including all documentation.
 
 GENERATE_XML           = NO
 
-# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
 # put in front of it. If left blank `xml' will be used as the default path.
 
 XML_OUTPUT             = xml
 
-# The XML_SCHEMA tag can be used to specify an XML schema, 
-# which can be used by a validating XML parser to check the 
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
 # syntax of the XML files.
 
-XML_SCHEMA             = 
+XML_SCHEMA             =
 
-# The XML_DTD tag can be used to specify an XML DTD, 
-# which can be used by a validating XML parser to check the 
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
 # syntax of the XML files.
 
-XML_DTD                = 
+XML_DTD                =
 
-# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
-# dump the program listings (including syntax highlighting 
-# and cross-referencing information) to the XML output. Note that 
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
 # enabling this will significantly increase the size of the XML output.
 
 XML_PROGRAMLISTING     = YES
@@ -942,10 +1431,10 @@
 # configuration options for the AutoGen Definitions output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
-# generate an AutoGen Definitions (see autogen.sf.net) file 
-# that captures the structure of the code including all 
-# documentation. Note that this feature is still experimental 
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
 # and incomplete at the moment.
 
 GENERATE_AUTOGEN_DEF   = NO
@@ -954,307 +1443,364 @@
 # configuration options related to the Perl module output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
-# generate a Perl module file that captures the structure of 
-# the code including all documentation. Note that this 
-# feature is still experimental and incomplete at the 
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
 # moment.
 
 GENERATE_PERLMOD       = NO
 
-# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
-# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
 # to generate PDF and DVI output from the Perl module output.
 
 PERLMOD_LATEX          = NO
 
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
-# nicely formatted so it can be parsed by a human reader.  This is useful 
-# if you want to understand what is going on.  On the other hand, if this 
-# tag is set to NO the size of the Perl module output will be much smaller 
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
 # and Perl will parse it just the same.
 
 PERLMOD_PRETTY         = YES
 
-# The names of the make variables in the generated doxyrules.make file 
-# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
-# This is useful so different doxyrules.make files included by the same 
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
 # Makefile don't overwrite each other's variables.
 
-PERLMOD_MAKEVAR_PREFIX = 
+PERLMOD_MAKEVAR_PREFIX =
 
 #---------------------------------------------------------------------------
-# Configuration options related to the preprocessor   
+# Configuration options related to the preprocessor
 #---------------------------------------------------------------------------
 
-# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
-# evaluate all C-preprocessor directives found in the sources and include 
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
 # files.
 
 ENABLE_PREPROCESSING   = YES
 
-# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
-# names in the source code. If set to NO (the default) only conditional 
-# compilation will be performed. Macro expansion can be done in a controlled 
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
 # way by setting EXPAND_ONLY_PREDEF to YES.
 
 MACRO_EXPANSION        = NO
 
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
-# then the macro expansion is limited to the macros specified with the 
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
 # PREDEFINED and EXPAND_AS_DEFINED tags.
 
 EXPAND_ONLY_PREDEF     = NO
 
-# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
-# in the INCLUDE_PATH (see below) will be search if a #include is found.
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
 
 SEARCH_INCLUDES        = NO
 
-# The INCLUDE_PATH tag can be used to specify one or more directories that 
-# contain include files that are not input files but should be processed by 
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
 # the preprocessor.
 
-INCLUDE_PATH           = 
+INCLUDE_PATH           =
 
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
-# patterns (like *.h and *.hpp) to filter out the header-files in the 
-# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
 # be used.
 
-INCLUDE_FILE_PATTERNS  = 
+INCLUDE_FILE_PATTERNS  =
 
-# The PREDEFINED tag can be used to specify one or more macro names that 
-# are defined before the preprocessor is started (similar to the -D option of 
-# gcc). The argument of the tag is a list of macros of the form: name 
-# or name=definition (no spaces). If the definition and the = are 
-# omitted =1 is assumed. To prevent a macro definition from being 
-# undefined via #undef or recursively expanded use the := operator 
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
 # instead of the = operator.
 
-PREDEFINED             = 
+PREDEFINED             =
 
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
-# this tag can be used to specify a list of macro names that should be expanded. 
-# The macro definition that is found in the sources will be used. 
-# Use the PREDEFINED tag if you want to use a different macro definition.
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
 
-EXPAND_AS_DEFINED      = 
+EXPAND_AS_DEFINED      =
 
-# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
-# doxygen's preprocessor will remove all function-like macros that are alone 
-# on a line, have an all uppercase name, and do not end with a semicolon. Such 
-# function macros are typically used for boiler-plate code, and will confuse 
-# the parser if not removed.
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
 
 SKIP_FUNCTION_MACROS   = YES
 
 #---------------------------------------------------------------------------
-# Configuration::additions related to external references   
+# Configuration::additions related to external references
 #---------------------------------------------------------------------------
 
-# The TAGFILES option can be used to specify one or more tagfiles. 
-# Optionally an initial location of the external documentation 
-# can be added for each tagfile. The format of a tag file without 
-# this location is as follows: 
-#   TAGFILES = file1 file2 ... 
-# Adding location for the tag files is done as follows: 
-#   TAGFILES = file1=loc1 "file2 = loc2" ... 
-# where "loc1" and "loc2" can be relative or absolute paths or 
-# URLs. If a location is present for each tag, the installdox tool 
-# does not have to be run to correct the links.
-# Note that each tag file must have a unique name
-# (where the name does NOT include the path)
-# If a tag file is not located in the directory in which doxygen 
-# is run, you must also specify the path to the tagfile here.
+# The TAGFILES option can be used to specify one or more tagfiles. For each
+# tag file the location of the external documentation should be added. The
+# format of a tag file without this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths
+# or URLs. Note that each tag file must have a unique name (where the name does
+# NOT include the path). If a tag file is not located in the directory in which
+# doxygen is run, you must also specify the path to the tagfile here.
 
-TAGFILES               = 
+TAGFILES               =
 
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
 # a tag file that is based on the input files it reads.
 
-GENERATE_TAGFILE       = 
+GENERATE_TAGFILE       =
 
-# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
-# in the class index. If set to NO only the inherited external classes 
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
 # will be listed.
 
 ALLEXTERNALS           = YES
 
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
-# in the modules index. If set to NO, only the current project's groups will 
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
 # be listed.
 
 EXTERNAL_GROUPS        = YES
 
-# The PERL_PATH should be the absolute path and name of the perl script 
+# The PERL_PATH should be the absolute path and name of the perl script
 # interpreter (i.e. the result of `which perl').
 
 PERL_PATH              = /usr/bin/perl
 
 #---------------------------------------------------------------------------
-# Configuration options related to the dot tool   
+# Configuration options related to the dot tool
 #---------------------------------------------------------------------------
 
-# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
-# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
-# or super classes. Setting the tag to NO turns the diagrams off. Note that 
-# this option is superseded by the HAVE_DOT option below. This is only a 
-# fallback. It is recommended to install and use dot, since it yields more 
-# powerful graphs.
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
 
 CLASS_DIAGRAMS         = NO
 
-# You can define message sequence charts within doxygen comments using the \msc 
-# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to 
-# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to 
-# specify the directory where the mscgen tool resides. If left empty the tool is assumed to 
-# be found in the default search path.
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
 
-MSCGEN_PATH            = 
+MSCGEN_PATH            =
 
-# If set to YES, the inheritance and collaboration graphs will hide 
-# inheritance and usage relations if the target is undocumented 
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
 # or is not a class.
 
 HIDE_UNDOC_RELATIONS   = YES
 
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
-# available from the path. This tool is part of Graphviz, a graph visualization 
-# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
 # have no effect if this option is set to NO (the default)
 
 HAVE_DOT               = YES
 
-# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
-# will generate a graph for each documented class showing the direct and 
-# indirect inheritance relations. Setting this tag to YES will force the 
-# the CLASS_DIAGRAMS tag to NO.
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will use the Helvetica font for all dot files that
+# doxygen generates. When you want a differently looking font you can specify
+# the font name using DOT_FONTNAME. You need to make sure dot is able to find
+# the font, which can be done by putting it in a standard location or by setting
+# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font.
+
+DOT_FONTNAME           = FreeSans.ttf
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the Helvetica font.
+# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
+# set the path where dot can find it.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# CLASS_DIAGRAMS tag to NO.
 
 CLASS_GRAPH            = NO
 
-# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
-# will generate a graph for each documented class showing the direct and 
-# indirect implementation dependencies (inheritance, containment, and 
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
 # class references variables) of the class with other documented classes.
 
 COLLABORATION_GRAPH    = NO
 
-# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
 # will generate a graph for groups, showing the direct groups dependencies
 
 GROUP_GRAPHS           = NO
 
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
-# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
 # Language.
 
 UML_LOOK               = YES
 
-# If set to YES, the inheritance and collaboration graphs will show the 
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside
+# the class node. If there are many fields or methods and many nodes the
+# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS
+# threshold limits the number of items for each type to make the size more
+# managable. Set this to 0 for no limit. Note that the threshold may be
+# exceeded by 50% before the limit is enforced.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If set to YES, the inheritance and collaboration graphs will show the
 # relations between templates and their instances.
 
 TEMPLATE_RELATIONS     = NO
 
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
-# tags are set to YES then doxygen will generate a graph for each documented 
-# file showing the direct and indirect include dependencies of the file with 
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
 # other documented files.
 
 INCLUDE_GRAPH          = NO
 
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
-# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
-# documented header file showing the documented files that directly or 
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
 # indirectly include this file.
 
 INCLUDED_BY_GRAPH      = NO
 
-# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
-# generate a call dependency graph for every global function or class method. 
-# Note that enabling this option will significantly increase the time of a run. 
-# So in most cases it will be better to enable call graphs for selected 
-# functions only using the \callgraph command.
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
 
-CALL_GRAPH             = NO
+CALL_GRAPH             = YES
 
-# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
-# generate a caller dependency graph for every global function or class method. 
-# Note that enabling this option will significantly increase the time of a run. 
-# So in most cases it will be better to enable caller graphs for selected 
-# functions only using the \callergraph command.
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
 
-CALLER_GRAPH           = NO
+CALLER_GRAPH           = YES
 
-# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
-# will graphical hierarchy of all classes instead of a textual one.
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
 
 GRAPHICAL_HIERARCHY    = YES
 
-# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
-# then doxygen will show the dependencies a directory has on other directories 
+# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
 # in a graphical way. The dependency relations are determined by the #include
 # relations between the files in the directories.
 
-DIRECTORY_GRAPH        = NO
+DIRECTORY_GRAPH        = YES
 
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
-# generated by dot. Possible values are png, jpg, or gif
-# If left blank png will be used.
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used. If you choose svg you need to set
+# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible in IE 9+ (other browsers do not have this requirement).
 
 DOT_IMAGE_FORMAT       = png
 
-# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+# Note that this requires a modern browser other than Internet Explorer.
+# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
+# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible. Older versions of IE do not have SVG support.
+
+INTERACTIVE_SVG        = NO
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
 # found. If left blank, it is assumed the dot tool can be found in the path.
 
-DOT_PATH               = 
+DOT_PATH               =
 
-# The DOTFILE_DIRS tag can be used to specify one or more directories that 
-# contain dot files that are included in the documentation (see the 
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
 # \dotfile command).
 
-DOTFILE_DIRS           = 
+DOTFILE_DIRS           =
 
-# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of 
-# nodes that will be shown in the graph. If the number of nodes in a graph 
-# becomes larger than this value, doxygen will truncate the graph, which is 
-# visualized by representing a node as a red box. Note that doxygen will always 
-# show the root nodes and its direct children regardless of this setting.
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
 
-DOT_GRAPH_MAX_NODES    = 50
+MSCFILE_DIRS           =
 
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
-# background. This is disabled by default, which results in a white background. 
-# Warning: Depending on the platform used, enabling this option may lead to 
-# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
-# read).
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 100
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
 
 DOT_TRANSPARENT        = NO
 
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
-# files in one run (i.e. multiple -o and -T options on the command line). This 
-# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
 # support this, this feature is disabled by default.
 
 DOT_MULTI_TARGETS      = NO
 
-# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
-# generate a legend page explaining the meaning of the various boxes and 
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
 # arrows in the dot generated graphs.
 
 GENERATE_LEGEND        = YES
 
-# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
-# remove the intermediate dot files that are used to generate 
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
 # the various graphs.
 
 DOT_CLEANUP            = YES
-
-#---------------------------------------------------------------------------
-# Configuration::additions related to the search engine   
-#---------------------------------------------------------------------------
-
-# The SEARCHENGINE tag specifies whether or not a search engine should be 
-# used. If set to NO the values of all tags below this one will be ignored.
-
-SEARCHENGINE           = NO
diff --git a/doc/DoxygenLayout.xml b/doc/DoxygenLayout.xml
new file mode 100644
index 0000000..589d0f1
--- /dev/null
+++ b/doc/DoxygenLayout.xml
@@ -0,0 +1,187 @@
+<doxygenlayout version="1.0">
+  <!-- Navigation index tabs for HTML output -->
+  <navindex>
+    <tab type="mainpage" visible="yes" title=""/>
+    <tab type="pages" visible="yes" title="" intro=""/>
+    <tab type="modules" visible="yes" title="" intro=""/>
+    <tab type="namespaces" visible="yes" title="">
+      <tab type="namespacelist" visible="yes" title="" intro=""/>
+      <tab type="namespacemembers" visible="yes" title="" intro=""/>
+    </tab>
+    <tab type="classes" visible="yes" title="">
+      <tab type="classlist" visible="yes" title="" intro=""/>
+      <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/> 
+      <tab type="hierarchy" visible="yes" title="" intro=""/>
+      <tab type="classmembers" visible="yes" title="" intro=""/>
+    </tab>
+    <tab type="files" visible="yes" title="">
+      <tab type="filelist" visible="yes" title="" intro=""/>
+      <tab type="globals" visible="yes" title="" intro=""/>
+    </tab>
+    <tab type="examples" visible="yes" title="" intro=""/>  
+  </navindex>
+
+  <!-- Layout definition for a class page -->
+  <class>
+    <briefdescription visible="yes"/>
+    <includes visible="$SHOW_INCLUDE_FILES"/>
+    <inheritancegraph visible="$CLASS_GRAPH"/>
+    <collaborationgraph visible="$COLLABORATION_GRAPH"/>
+    <allmemberslink visible="yes"/>
+    <memberdecl>
+      <nestedclasses visible="yes" title=""/>
+      <publictypes title=""/>
+      <publicslots title=""/>
+      <signals title=""/>
+      <publicmethods title=""/>
+      <publicstaticmethods title=""/>
+      <publicattributes title=""/>
+      <publicstaticattributes title=""/>
+      <protectedtypes title=""/>
+      <protectedslots title=""/>
+      <protectedmethods title=""/>
+      <protectedstaticmethods title=""/>
+      <protectedattributes title=""/>
+      <protectedstaticattributes title=""/>
+      <packagetypes title=""/>
+      <packagemethods title=""/>
+      <packagestaticmethods title=""/>
+      <packageattributes title=""/>
+      <packagestaticattributes title=""/>
+      <properties title=""/>
+      <events title=""/>
+      <privatetypes title=""/>
+      <privateslots title=""/>
+      <privatemethods title=""/>
+      <privatestaticmethods title=""/>
+      <privateattributes title=""/>
+      <privatestaticattributes title=""/>
+      <friends title=""/>
+      <related title="" subtitle=""/>
+      <membergroups visible="yes"/>
+    </memberdecl>
+    <detaileddescription title=""/>
+    <memberdef>
+      <inlineclasses title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <constructors title=""/>
+      <functions title=""/>
+      <related title=""/>
+      <variables title=""/>
+      <properties title=""/>
+      <events title=""/>
+    </memberdef>
+    <usedfiles visible="$SHOW_USED_FILES"/>
+    <authorsection visible="yes"/>
+  </class>
+
+  <!-- Layout definition for a namespace page -->
+  <namespace>
+    <briefdescription visible="yes"/>
+    <memberdecl>
+      <nestednamespaces visible="yes" title=""/>
+      <classes visible="yes" title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <functions title=""/>
+      <variables title=""/>
+      <membergroups visible="yes"/>
+    </memberdecl>
+    <detaileddescription title=""/>
+    <memberdef>
+      <inlineclasses title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <functions title=""/>
+      <variables title=""/>
+    </memberdef>
+    <authorsection visible="yes"/>
+  </namespace>
+
+  <!-- Layout definition for a file page -->
+  <file>
+    <briefdescription visible="yes"/>
+    <includes visible="$SHOW_INCLUDE_FILES"/>
+    <includegraph visible="$INCLUDE_GRAPH"/>
+    <includedbygraph visible="$INCLUDED_BY_GRAPH"/>
+    <sourcelink visible="yes"/>
+    <memberdecl>
+      <classes visible="yes" title=""/>
+      <namespaces visible="yes" title=""/>
+      <defines title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <functions title=""/>
+      <variables title=""/>
+      <membergroups visible="yes"/>
+    </memberdecl>
+    <detaileddescription title=""/>
+    <memberdef>
+      <inlineclasses title=""/>
+      <defines title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <functions title=""/>
+      <variables title=""/>
+    </memberdef>
+    <authorsection/>
+  </file>
+
+  <!-- Layout definition for a group page -->
+  <group>
+    <briefdescription visible="yes"/>
+    <groupgraph visible="$GROUP_GRAPHS"/>
+    <memberdecl>
+      <nestedgroups visible="yes" title=""/>
+      <dirs visible="yes" title=""/>
+      <files visible="yes" title=""/>
+      <namespaces visible="yes" title=""/>
+      <classes visible="yes" title=""/>
+      <defines title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <enumvalues title=""/>
+      <functions title=""/>
+      <variables title=""/>
+      <signals title=""/>
+      <publicslots title=""/>
+      <protectedslots title=""/>
+      <privateslots title=""/>
+      <events title=""/>
+      <properties title=""/>
+      <friends title=""/>
+      <membergroups visible="yes"/>
+    </memberdecl>
+    <detaileddescription title=""/>
+    <memberdef>
+      <pagedocs/>
+      <inlineclasses title=""/>
+      <defines title=""/>
+      <typedefs title=""/>
+      <enums title=""/>
+      <enumvalues title=""/>
+      <functions title=""/>
+      <variables title=""/>
+      <signals title=""/>
+      <publicslots title=""/>
+      <protectedslots title=""/>
+      <privateslots title=""/>
+      <events title=""/>
+      <properties title=""/>
+      <friends title=""/>
+    </memberdef>
+    <authorsection visible="yes"/>
+  </group>
+
+  <!-- Layout definition for a directory page -->
+  <directory>
+    <briefdescription visible="yes"/>
+    <directorygraph visible="yes"/>
+    <memberdecl>
+      <dirs visible="yes"/>
+      <files visible="yes"/>
+    </memberdecl>
+    <detaileddescription title=""/>
+  </directory>
+</doxygenlayout>
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 040ff87..338f077 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,9 +1,73 @@
 # -*- Makefile -*-
 
-.PHONY: gendoc
+.PHONY: gendoc api_ref asciidoc
 
-gendoc:
+ASCIIDOCOPTS=-a pygments -a language=c -a icons \
+	     -a toc2 \
+	     -a numbered \
+	     -a imagesdir="./images/" \
+	     -a iconsdir="./images/icons" \
+	     -a stylesdir="${abs_srcdir}/stylesheets/"
+
+EXTRA_DIST = \
+	core.txt \
+	core.html \
+	route.txt \
+	route.html \
+	index.txt \
+	index.html \
+	libnl.css \
+	stylesheets \
+	images \
+	api
+
+dist-hook:
+	rm -f $(distdir)/aclocal.m4
+	rm -f $(distdir)/configure
+	rm -f $(distdir)/configure.in
+	rm -rf $(distdir)/m4
+	rm -f $(distdir)/README
+	rm -f $(distdir)/missing
+	rm -f $(distdir)/Doxyfile.in
+	rm -f $(distdir)/Makefile.am
+	rm -f $(distdir)/Makefile.in
+
+link_doc:
+if LINK_DOC
+	./gen-tags.sh > libnl.dict
+else
+	@echo "Warning: Linking to API reference is disabled, check configure output"
+endif
+	
+
+%.html: %.txt link_doc
+	./resolve-asciidoc-refs.py $< > asciidoc.tmp
+	asciidoc $(ASCIIDOCOPTS) -o $@ asciidoc.tmp
+if LINK_DOC
+	./doxygen-link.py libnl.dict $@ > asciidoc.tmp
+	mv asciidoc.tmp $@
+endif
+
+asciidoc: core.html route.html index.html
+
+api_ref:
 	doxygen Doxyfile;
 
-distclean-local:
-	rm -f html/*;
+gendoc:
+if GENERATE_DOC
+if HAVE_DOXYGEN
+	$(MAKE) api_ref
+else
+	@echo "Warning: Building of API reference (doxygen) is disabled, check autoconf logs"
+endif
+if HAVE_ASCIIDOC
+	$(MAKE) asciidoc
+else
+	@echo "Warning: Building of asciidoc files is disabled, check autoconf logs"
+endif
+else
+	@echo "Warning: Building of documentation disabled by user or autoconf"
+endif
+
+clean-local:
+	rm -f api/* libnl.dict *.html;
diff --git a/doc/README b/doc/README
new file mode 100644
index 0000000..ddcdf14
--- /dev/null
+++ b/doc/README
@@ -0,0 +1,13 @@
+Requirements to build documentation
+
+mscgen
+  http://www.mcternan.me.uk/mscgen/
+
+mscgen-filter-1.2
+  http://code.google.com/p/asciidoc-mscgen-filter/
+
+asciidoc > 8.6.x
+doxygen > 1.8.0
+
+Building the documentation:
+make gendoc
diff --git a/doc/api/.gitignore b/doc/api/.gitignore
new file mode 100644
index 0000000..e57ca88
--- /dev/null
+++ b/doc/api/.gitignore
@@ -0,0 +1,8 @@
+*.html
+*.png
+*.css
+*.map
+*.md5
+*.js
+formula.repository
+jquery.js
diff --git a/doc/autogen.sh b/doc/autogen.sh
new file mode 100755
index 0000000..a569614
--- /dev/null
+++ b/doc/autogen.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+autoreconf -fi;
+rm -Rf autom4te.cache;
diff --git a/doc/configure.ac b/doc/configure.ac
new file mode 100644
index 0000000..d4cda85
--- /dev/null
+++ b/doc/configure.ac
@@ -0,0 +1,107 @@
+#
+# configure.in
+#
+# 	This library is free software; you can redistribute it and/or
+#	modify it under the terms of the GNU Lesser General Public
+#	License as published by the Free Software Foundation version 2.1
+#	of the License.
+#
+# Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
+#
+
+AC_INIT(libnl-doc, [3.2.25], [http://www.infradead.org/~tgr/libnl/])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_AUX_DIR([build-aux])
+AM_INIT_AUTOMAKE([foreign])
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES(yes)], [])
+
+m4_include([m4/ax_python.m4])
+
+#
+# Generating the documentation
+#
+AC_ARG_ENABLE([doc],
+	      AS_HELP_STRING([--disable-doc], [Do not generate documentation]),
+	      [generate_doc="$enableval"], [generate_doc=auto])
+
+AX_PYTHON
+
+if test "x$generate_doc" != "xno"; then
+	AC_PROG_SED
+	AC_PROG_EGREP
+
+	AC_CHECK_PROG(HAVE_DOXYGEN, [doxygen], yes, no)
+	if test "x$HAVE_DOXYGEN" = "xno" -a "x$generate_doc" = "xyes"; then
+		AC_MSG_ERROR([*** doxygen package required to generate documentation])
+	fi
+
+	AC_CHECK_PROG(HAVE_DOT, [dot], yes, no)
+	if test "x$HAVE_DOT" = "xno"; then
+		if test "x$generate_doc" = "xyes"; then
+			AC_MSG_ERROR([*** graphviz package required to generate documentation])
+		else
+			AC_MSG_WARN([*** graphviz not found, disabling building of API reference])
+			HAVE_DOXYGEN=no
+		fi
+	fi
+
+	AC_CHECK_PROG(HAVE_ASCIIDOC, [asciidoc], yes, no)
+	if test "x$HAVE_ASCIIDOC" = "xno"; then
+		if test "x$generate_doc" = "xyes"; then
+			AC_MSG_ERROR([*** asciidoc package required to generate documentation])
+		else
+			AC_MSG_WARN([*** asciidoc not found, disabling building of guides])
+		fi
+	fi
+
+	AC_CHECK_PROG(HAVE_SOURCE_HIGHLIGHT, [source-highlight], yes, no)
+	if test "x$HAVE_SOURCE_HIGHLIGHT" = "xno"; then
+		if test "x$generate_doc" = "xyes"; then
+			AC_MSG_ERROR([*** source-highlight required to generate documentation])
+		else
+			AC_MSG_WARN([*** source-highlight not found, disabling building of guides])
+			HAVE_ASCIIDOC=no
+		fi
+	fi
+
+	AC_CHECK_PROG(HAVE_MSCGEN, [mscgen], yes, no)
+	if test "x$HAVE_MSCGEN" = "xno"; then
+		AC_MSG_WARN([*** mscgen not found, get it at http://www.mcternan.me.uk/mscgen/])
+		if test "x$generate_doc" = "xyes"; then
+			AC_MSG_ERROR([*** mscgen package required to generate documentation])
+		else
+			AC_MSG_WARN([*** Disabling building of guides])
+			HAVE_ASCIIDOC=no
+			HAVE_DOXYGEN=no
+		fi
+	fi
+
+	AC_CHECK_PROG(HAVE_PYGMENTIZE, [pygmentize], yes, no)
+	if test "x$HAVE_PYGMENTIZE" = "xno"; then
+		if test "x$generate_doc" = "xyes"; then
+			AC_MSG_ERROR([*** pygmentize package required to generate documentation])
+		else
+			AC_MSG_WARN([*** Disabling building of guides])
+			HAVE_ASCIIDOC=no
+		fi
+	fi
+
+	link_doc=yes
+	if test "x$HAVE_DOXYGEN" = "xno"; then
+		AC_MSG_WARN([*** Disabling API linking due to missing doxygen package])
+		link_doc=no
+	fi
+fi
+
+AM_CONDITIONAL([LINK_DOC], [test "x$link_doc" = "xyes"])
+AM_CONDITIONAL([HAVE_DOXYGEN], [test "x$HAVE_DOXYGEN" = "xyes"])
+AM_CONDITIONAL([HAVE_ASCIIDOC], [test "x$HAVE_ASCIIDOC" = "xyes"])
+
+AM_CONDITIONAL([GENERATE_DOC], [test "x$generate_doc" != "xno"])
+
+AC_CONFIG_FILES([
+Doxyfile
+Makefile
+])
+
+AC_OUTPUT
diff --git a/doc/core.txt b/doc/core.txt
new file mode 100644
index 0000000..042369d
--- /dev/null
+++ b/doc/core.txt
@@ -0,0 +1,3017 @@
+////
+	vim.syntax: asciidoc
+
+	Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+////
+
+Netlink Library (libnl)
+=======================
+Thomas Graf <tgraf@suug.ch>
+3.2, May 9 2011:
+:numbered:
+
+== Introduction
+
+The core library contains the fundamentals required to communicate
+over netlink sockets. It deals with connecting and disconnectng of
+sockets, sending and receiving of data, construction and parsing of
+messages, provides a customizeable receiving state machine, and
+provides a abstract data type framework which eases the implementation
+of object based netlink protocols where objects are added, removed, or
+modified using a netlink based protocol.
+
+.Library Hierarchy
+
+The suite is split into multiple libraries:
+
+image:library_overview.png["Library Hierarchy"]
+
+link:core.html[Netlink Library] (libnl)::
+Socket handling, sending and receiving, message construction and parsing, ...
+
+link:route.html[Routing Family Library] (libnl-route)::
+Adresses, links, neighbours, routing, traffic control, neighbour tables, ...
+
+Netfilter Library (libnl-nf)::
+Connection tracking, logging, queueing
+
+Generic Netlink Library (libnl-genl)::
+Controller API, family and command registration
+
+
+=== How To Read This Documentation
+
+The libraries provide a broad set of APIs of which most applications only
+require a small subset of it. Depending on the type of application, some
+users may only be interested in the low level netlink messaging API while
+others wish to make heavy use of the high level API.
+
+In any case it is recommended to get familiar with the netlink protocol
+first.
+
+- <<core_netlink_fundamentals>>
+
+The low level APIs are described in:
+
+- <<core_sockets>>
+- <<core_send_recv>>
+
+
+=== Linking to this Library
+
+.Checking the presence of the library using autoconf
+
+Projects using autoconf may use +PKG_CHECK_MODULES()+ to check if
+a specific version of libnl is available on the system. The example
+below also shows how to retrieve the +CFLAGS+ and linking dependencies
+required to link against the library.
+
+The following example shows how to check for a specific version of libnl. If
+found, it extends the `CFLAGS` and `LIBS` variable appropriately:
+
+[source]
+----
+PKG_CHECK_MODULES(LIBNL3, libnl-3.0 >= 3.1, [have_libnl3=yes], [have_libnl3=no])
+if (test "${have_libnl3}" = "yes"); then
+	CFLAGS+="$LIBNL3_CFLAGS"
+	LIBS+="$LIBNL3_LIBS"
+fi
+----
+
+NOTE: The pkgconfig file is named +libnl-3.0.pc+ for historic reasons, it also
+      covers library versions >= 3.1.
+
+.Header Files
+
+The main header file is `<netlink/netlink.h>`. Additional headers may need to
+be included in your sources depending on the subsystems and components your
+program makes use of.
+
+[source,c]
+-----
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+#include <netlink/route/link.h>
+-----
+
+.Version Dependent Code
+
+If your code wishes to be capable to link against multiple versions of libnl
+you may have direct the compiler to only include portions on the code depending
+on the version of libnl that it is compiled against.
+
+[source,c]
+-----
+#include <netlink/version.h>
+
+#if LIBNL_VER_NUM >= LIBNL_VER(3,1)
+	/* include code if compiled with libnl version >= 3.1 */
+#endif
+-----
+
+.Linking
+-----
+$ gcc myprogram.c -o myprogram $(pkgconfig --cflags --libs libnl-3.0)
+-----
+
+=== Debugging
+
+The library has been compiled with debugging statements enabled it will
+print debug information to +stderr+ if the environment variable +NLDBG+
+is set to > 0.
+
+-----
+$ NLDBG=2 ./myprogram
+-----
+
+.Debugging Levels
+[options="header", width="80%", cols="1,5", align="center"]
+|===============================================================
+| Level | Description
+| 0     | Debugging disabled (default)
+| 1     | Warnings, important events and notifications
+| 2     | More or less important debugging messages
+| 3     | Repetitive events causing a flood of debugging messages
+| 4     | Even less important messages
+|===============================================================
+
+.Debugging the Netlink Protocol
+
+It is often useful to peek into the stream of netlink messages exchanged
+with other sockets. Setting the environment variable +NLCB=debug+ will
+cause the debugging message handlers to be used which in turn print the
+netlink messages exchanged in a human readable format to to +stderr+:
+
+-----
+$ NLCB=debug ./myprogram
+-- Debug: Sent Message:
+--------------------------   BEGIN NETLINK MESSAGE ---------------------------
+  [HEADER] 16 octets
+    .nlmsg_len = 20
+    .nlmsg_type = 18 <route/link::get>
+    .nlmsg_flags = 773 <REQUEST,ACK,ROOT,MATCH>
+    .nlmsg_seq = 1301410712
+    .nlmsg_pid = 20014
+  [PAYLOAD] 16 octets
+    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00       ................
+---------------------------  END NETLINK MESSAGE   ---------------------------
+-- Debug: Received Message:
+--------------------------   BEGIN NETLINK MESSAGE ---------------------------
+  [HEADER] 16 octets
+    .nlmsg_len = 996
+    .nlmsg_type = 16 <route/link::new>
+    .nlmsg_flags = 2 <MULTI>
+    .nlmsg_seq = 1301410712
+    .nlmsg_pid = 20014
+  [PAYLOAD] 16 octets
+    00 00 04 03 01 00 00 00 49 00 01 00 00 00 00 00       ........I.......
+  [ATTR 03] 3 octets
+    6c 6f 00                                              lo.
+  [PADDING] 1 octets
+    00                                                    .
+  [ATTR 13] 4 octets
+    00 00 00 00                                           ....
+  [ATTR 16] 1 octets
+    00                                                    .
+  [PADDING] 3 octets
+    00 00 00                                              ...
+  [ATTR 17] 1 octets
+    00                                                    .
+  [...]
+---------------------------  END NETLINK MESSAGE   ---------------------------
+
+-----
+
+[[core_netlink_fundamentals]]
+== Netlink Protocol Fundamentals
+
+The netlink protocol is a socket based IPC mechanism used for
+communication between userspace processes and the kernel or between
+userspace processes themselves. The netlink protocol is based on BSD
+sockets and uses the +AF_NETLINK+ address family. Every netlink
+protocol uses its own protocol number (e.g. +NETLINK_ROUTE+,
++NETLINK_NETFILTER+, etc). Its addressing schema is based on a 32 bit
+port number, formerly referred to as PID, which uniquely identifies
+each peer.
+
+[[core_addressing]]
+=== Addressing
+
+The netlink address (port) consists of a 32bit integer. Port 0 (zero)
+is reserved for the kernel and refers to the kernel side socket of each
+netlink protocol family. Other port numbers usually refer to user space
+owned sockets, although this is not enforced.
+
+NOTE: In the beginning, it was common practice to use the process
+      identifier (PID) as the local port number. This became unpractical
+      with the introduction of threaded netlink applications and
+      applications requiring multiple sockets. Therefore libnl generates
+      unique port numbers based on the process identifier and adds an
+      offset to it allowing for multiple sockets to be used. The initial
+      socket will still equal to the process identifier for backwards
+      compatibility reasons.
+
+image:addressing.png["Addressing Example"]
+
+The above figure illustrates three applications and the kernel side
+exposing two kernel side sockets. It shows the common netlink use
+cases:
+
+  * User space to kernel
+  * User space to user space
+  * Listening to kernel multicast notifications
+
+.User Space to Kernel
+
+The most common form of netlink usage is for a user space application
+to send requests to the kernel and process the reply which is either
+an error message or a success notification.
+
+["mscgen"]
+--------
+msc {
+  App1,App2,Kernel;
+  App1=>Kernel [label="request (src=11, dst=0)"];
+  App1<=Kernel [label="reply (src=0, dst=11)"];
+  ...;
+  App2=>Kernel [label="request (src=21, dst=0)"];
+  App2<=Kernel [label="reply (src=0, dst=21)"];
+}
+--------
+
+.User Space to User Space
+
+Netlink may also be used as an IPC mechanism to communicate between user
+space applications directly. Communication is not limited to two peers,
+any number of peers may communicate with each other and multicasting
+capabilities allow to reach multiple peers with a single message.
+
+In order for the sockets to be visible to each other, both sockets must
+be created for the same netlink protocol family.
+
+["mscgen"]
+--------
+msc {
+  App2,App3;
+  App2=>App3 [label="request (src=22, dst=31)"];
+  App2<=App3 [label="reply (src=31, dst=22)"];
+  ...;
+}
+--------
+
+.User space listening to kernel notifications
+
+This form of netlink communication is typically found in user space
+daemons that need to act on certain kernel events. Such daemons will
+typically maintain a netlink socket subscribed to a multicast group that
+is used by the kernel to notify interested user space parties about
+specific events.
+
+["mscgen"]
+--------
+msc {
+  Kernel,App3;
+  Kernel=>App3 [label="notification (src=0, group=foo)"];
+  ...;
+}
+--------
+
+Use of multicasting is preferred over direct addressing due to the
+flexibility in exchanging the user space component at any time without
+the kernel noticing.
+
+[[core_msg_format]]
+=== Message Format
+
+A netlink protocol is typically based on messages and consists of the
+netlink message header (+struct nlmsghdr+) plus the payload attached
+to it.  The payload can consist of arbitrary data but usually contains
+a fixed size protocol specific header followed by a stream of
+attributes.
+
+.Netlink message header (struct nlmsghdr)
+
+image:nlmsghdr.png[align="center", alt="Netlink Message Header"]
+
+Total Length (32bit)::
+Total length of the message in bytes including the netlink message header.
+
+Message Type (16bit)::
+The message type specifies the type of payload the message is carrying.
+Several standard message types are defined by the netlink protocol.
+Additional message types may be defined by each protocol family. See
+<<core_msg_types>> for additional information.
+
+Message Flags (16bit)::
+The message flags may be used to modify the behaviour of a message type.
+See section <<core_msg_flags>> for a list of standard message flags.
+
+Sequence Number (32bit)::
+The sequence number is optional and may be used to allow referring to
+a previous message, e.g. an error message can refer to the original
+request causing the error.
+
+Port Number (32bit)::
+The port number specifies the peer to which the message should be delivered
+to. If not specified, the message will be delivered to the first matching
+kernel side socket of the same protocol family.
+
+[[core_msg_types]]
+=== Message Types
+
+Netlink differs between requests, notifications, and replies. Requests
+are messages which have the +NLM_F_REQUEST+ flag set and are meant to
+request an action from the receiver. A request is typically sent from
+a userspace process to the kernel. While not strictly enforced, requests
+should carry a sequence number incremented for each request sent.
+
+Depending on the nature of the request, the receiver may reply to the
+request with another netlink message. The sequence number of a reply
+must match the sequence number of the request it relates to.
+
+Notifications are of informal nature and no reply is expected, therefore
+the sequence number is typically set to 0.
+
+["mscgen"]
+--------
+msc {
+  A,B;
+  A=>B [label="GET (seq=1, NLM_F_REQUEST)"];
+  A<=B [label="PUT (seq=1)"];
+  ...;
+  A<=B [label="NOTIFY (seq=0)"];
+}
+--------
+
+
+The type of message is primarly identified by its 16 bit message type set
+in the message header. The following standard message types are defined:
+
+- +NLMSG_NOOP+ - No operation, message must be discarded
+- +NLMSG_ERROR+ - Error message or ACK, see <<core_errmsg>>
+  respectively <<core_msg_ack>>
+- +NLMSG_DONE+ - End of multipart sequence, see <<core_multipart>>
+- +NLMSG_OVERRUN+ - Overrun notification (Error)
+
+Every netlink protocol is free to define own message types. Note that
+message type values  +< NLMSG_MIN_TYPE (0x10)+ are reserved and may
+not be used.
+
+It is common practice to use own message types to implement RPC schemas.
+Suppose the goal of the netlink protocol you are implementing is allow
+configuration of a particular network device, therefore you want to
+provide read/write access to various configuration options. The typical
+"netlink way" of doing this would be to define two message types
++MSG_SETCFG+, +MSG_GETCFG+:
+
+[source,c]
+--------
+#define MSG_SETCFG	0x11
+#define MSG_GETCFG	0x12
+--------
+
+Sending a +MSG_GETCFG+ request message will typically trigger a reply
+with the message type +MSG_SETCFG+ containing the current configuration.
+In object oriented terms one would describe this as "the kernel sets
+the local copy of the configuration in userspace".
+
+["mscgen"]
+--------
+msc {
+  A,B;
+  A=>B [label="MSG_GETCFG (seq=1, NLM_F_REQUEST)"];
+  A<=B [label="MSG_SETCFG (seq=1)"];
+}
+--------
+
+The configuration may be changed by sending a +MSG_SETCFG+ which will
+be responded to with either a ACK (see <<core_msg_ack>>)
+or a error message (see <<core_errmsg>>).
+
+["mscgen"]
+--------
+msc {
+  A,B;
+  A=>B [label="MSG_SETCFG (seq=1, NLM_F_REQUEST, NLM_F_ACK)"];
+  A<=B [label="ACK (seq=1)"];
+}
+--------
+
+Optionally, the kernel may send out notifications for configuration
+changes allowing userspace to listen for changes instead of polling
+frequently. Notifications typically reuse an existing message type
+and rely on the application using a separate socket to differ between
+requests and notifications but you may also specify a separate message
+type.
+
+["mscgen"]
+--------
+msc {
+  A,B;
+  A<=B [label="MSG_SETCFG (seq=0)"];
+}
+--------
+
+[[core_multipart]]
+==== Multipart Messages
+
+Although in theory a netlink message can be up to 4GiB in size. The socket
+buffers are very likely not large enough to hold message of such sizes.
+Therefore it is common to limit messages to one page size (PAGE_SIZE) and
+use the multipart mechanism to split large pieces of data into several
+messages.  A multipart message has the flag +NLM_F_MULTI+ set and the
+receiver is expected to continue receiving and parsing until the special
+message type +NLMSG_DONE+ is received.
+
+Multipart messages unlike fragmented ip packets must not be reassmbled
+even though it is perfectly legal to do so if the protocols wishes to
+work this way. Often multipart message are used to send lists or trees
+of objects were each multipart message simply carries multiple objects
+allow for each message to be parsed independently.
+
+["mscgen"]
+--------
+msc {
+  A,B;
+  A=>B [label="GET (seq=1, NLM_F_REQUEST)"];
+  A<=B [label="PUT (seq=1, NLM_F_MULTI)"];
+  ...;
+  A<=B [label="PUT (seq=1, NLM_F_MULTI)"];
+  A<=B [label="NLMSG_DONE (seq=1)"];
+}
+--------
+
+[[core_errmsg]]
+==== Error Message
+
+Error messages can be sent in response to a request. Error messages must
+use the standard message type +NLMSG_ERROR+. The payload consists of a
+error code and the original netlink mesage header of the request. 
+
+image:nlmsgerr.png["Netlink Errror Message header"]
+
+Error messages should set the sequence number to the sequence number
+of the request which caused the error.
+
+["mscgen"]
+--------
+msc {
+  A,B;
+  A=>B [label="GET (seq=1, NLM_F_REQUEST)"];
+  A<=B [label="NLMSG_ERROR code=EINVAL (seq=1)"];
+}
+--------
+
+[[core_msg_ack]]
+==== ACKs
+
+A sender can request an ACK message to be sent back for each request
+processed by setting the +NLM_F_ACK+ flag in the request. This is typically
+used to allow the sender to synchronize further processing until the
+request has been processed by the receiver.
+
+["mscgen"]
+--------
+msc {
+  A,B;
+  A=>B [label="GET (seq=1, NLM_F_REQUEST | NLM_F_ACK)"];
+  A<=B [label="ACK (seq=1)"];
+}
+--------
+
+ACK messages also use the message type +NLMSG_ERROR+ and payload
+format but the error code is set to 0.
+
+[[core_msg_flags]]
+==== Message Flags
+
+The following standard flags are defined
+
+[source,c]
+--------
+#define NLM_F_REQUEST		1
+#define NLM_F_MULTI		2
+#define NLM_F_ACK		4
+#define NLM_F_ECHO		8
+--------
+
+- `NLM_F_REQUEST` - Message is a request, see <<core_msg_types>>.
+- `NLM_F_MULTI` - Multipart message, see <<core_multipart>>
+- `NLM_F_ACK` - ACK message requested, see <<core_msg_ack>>.
+- `NLM_F_ECHO` - Request to echo the request.
+
+The flag +NLM_F_ECHO+ is similar to the `NLM_F_ACK` flag. It can be
+used in combination with `NLM_F_REQUEST` and causes a notification
+which is sent as a result of a request to also be sent to the sender
+regardless of whether the sender has subscribed to the corresponding
+multicast group or not. See <<core_multicast>>
+
+Additional universal message flags are defined which only apply for
++GET+ requests:
+
+[source,c]
+--------
+#define NLM_F_ROOT	0x100
+#define NLM_F_MATCH	0x200
+#define NLM_F_ATOMIC	0x400
+#define NLM_F_DUMP	(NLM_F_ROOT|NLM_F_MATCH)
+--------
+
+- `NLM_F_ROOT` - Return based on root of tree.
+- `NLM_F_MATCH` - Return all matching entries.
+- `NLM_F_ATOMIC` - Obsoleted, once used to request an atomic operation.
+- `NLM_F_DUMP` - Return a list of all objects
+  (`NLM_F_ROOT`|`NLM_F_MATCH`).
+
+Use of these flags is completely optional and many netlink protocols only
+make use of the `NLM_F_DUMP` flag which typically requests the receiver
+to send a list of all objects in the context of the message type as a
+sequence of multipart messages (see <<core_multipart>>).
+
+Another set of flags exist related to `NEW` or `SET` requests. These
+flags are mutually exclusive to the `GET` flags:
+
+[source,c]
+--------
+#define NLM_F_REPLACE	0x100
+#define NLM_F_EXCL	0x200
+#define NLM_F_CREATE	0x400
+#define NLM_F_APPEND	0x800
+--------
+
+- `NLM_F_REPLACE` - Replace an existing object if it exists.
+- `NLM_F_EXCL` - Do not update object if it exists already.
+- `NLM_F_CREATE` - Create object if it does not exist yet.
+- `NLM_F_APPEND` - Add object at end of list.
+
+Behaviour of these flags may differ slightly between different netlink
+protocols.
+
+[[core_seq_num]]
+=== Sequence Numbers
+
+Netlink allows the use of sequence numbers to help relate replies to
+requests. It should be noted that unlike in protocols such as TCP
+there is no strict enforcment of the sequence number. The sole purpose
+of sequence numbers is to assist a sender in relating replies to the
+corresponding requests. See <<core_msg_types>> for more information.
+
+Sequence numbers are managed on a per socket basis, see
+<<core_sk_seq_num>> for more information on how to use sequence numbers.
+
+[[core_multicast]]
+=== Multicast Groups
+
+TODO
+
+See <<core_sk_multicast>>
+
+[[core_sockets]]
+== Netlink Sockets
+
+In order to use the netlink protocol, a netlink socket is required.
+Each socket defines an independent context for sending and receiving of
+messages. An application may make use multiple sockets, e.g. a socket to
+send requests and receive the replies and another socket subscribed to a
+multicast group to receive notifications.
+
+=== Socket structure (struct nl_sock)
+
+The netlink socket and all related attributes including the actual file
+descriptor are represented by +struct nl_sock+.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+struct nl_sock *nl_socket_alloc(void)
+void nl_socket_free(struct nl_sock *sk)
+--------
+
+The application must allocate an instance of +struct nl_sock+ for each
+netlink socket it wishes to use.
+
+[[core_sk_seq_num]]
+=== Sequence Numbers
+
+The library will automatically take care of sequence number handling
+for the application. A sequence number counter is stored in the
+socket structure which is used and incremented automatically when a 
+message needs to be sent which is expected to generate a reply such as
+an error or any other message type that needs to be related to the
+original message.
+
+Alternatively, the counter can be used directly via the function
+nl_socket_use_seq(). It will return the current value of the counter
+and increment it by one afterwards.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+unsigned int nl_socket_use_seq(struct nl_sock *sk);
+--------
+
+Most applications will not want to deal with sequence number handling
+themselves though. When using nl_send_auto() the sequence number is
+filled in automatically and matched again when a reply is received. See
+section <<core_send_recv>> for more information.
+
+This behaviour can and must be disabled if the netlink protocol
+implemented does not use a request/reply model, e.g. when a socket is
+used to receive notification messages.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+void nl_socket_disable_seq_check(struct nl_sock *sk);
+--------
+
+For more information on the theory behind netlink sequence numbers,
+see section <<core_seq_num>>.
+
+[[core_sk_multicast]]
+=== Multicast Group Subscriptions
+
+Each socket can subscribe to any number of multicast groups of the
+netlink protocol it is connected to. The socket will then receive a
+copy of each message sent to any of the groups. Multicast groups are
+commonly used to implement event notifications.
+
+Prior to kernel 2.6.14 the group subscription was performed using a
+bitmask which limited the number of groups per protocol family to 32.
+This outdated interface can still be accessed via the function
+nl_join_groups() even though it is not recommended for new code.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+void nl_join_groups(struct nl_sock *sk, int bitmask);
+--------
+
+Starting with 2.6.14 a new method was introduced which supports subscribing
+to an almost infinite number of multicast groups.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+int nl_socket_add_memberships(struct nl_sock *sk, int group, ...);
+int nl_socket_drop_memberships(struct nl_sock *sk, int group, ...);
+--------
+
+==== Multicast Example
+
+[source,c]
+--------
+#include <netlink/netlink.h>
+#include <netlink/socket.h>
+#include <netlink/msg.h>
+
+/*
+ * This function will be called for each valid netlink message received
+ * in nl_recvmsgs_default()
+ */
+static int my_func(struct nl_msg *msg, void *arg)
+{
+	return 0;
+}
+
+struct nl_sock *sk;
+
+/* Allocate a new socket */
+sk = nl_socket_alloc();
+
+/*
+ * Notifications do not use sequence numbers, disable sequence number
+ * checking.
+ */
+nl_socket_disable_seq_check(sk);
+
+/*
+ * Define a callback function, which will be called for each notification
+ * received
+ */
+nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, my_func, NULL);
+
+/* Connect to routing netlink protocol */
+nl_connect(sk, NETLINK_ROUTE);
+
+/* Subscribe to link notifications group */
+nl_socket_add_memberships(sk, RTNLGRP_LINK, 0);
+
+/*
+ * Start receiving messages. The function nl_recvmsgs_default() will block
+ * until one or more netlink messages (notification) are received which
+ * will be passed on to my_func().
+ */
+while (1)
+	nl_recvmsgs_default(sock);
+--------
+
+[[core_sk_cb]]
+=== Modifiying Socket Callback Configuration
+
+See <<core_cb>> for more information on
+callback hooks and overwriting capabilities.
+
+Each socket is assigned a callback configuration which controls the
+behaviour of the socket. This is f.e. required to have a separate
+message receive function per socket. It is perfectly legal to share
+callback configurations between sockets though.
+
+The following functions can be used to access and set the callback
+configuration of a socket:
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+struct nl_cb *nl_socket_get_cb(const struct nl_sock *sk);
+void nl_socket_set_cb(struct nl_sock *sk, struct nl_cb *cb);
+--------
+
+Additionaly a shortcut exists to modify the callback configuration
+assigned to a socket directly:
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+int nl_socket_modify_cb(struct nl_sock *sk, enum nl_cb_type type, enum nl_cb_kind kind,
+                        nl_recvmsg_msg_cb_t func, void *arg);
+--------
+
+.Example:
+[source,c]
+--------
+#include <netlink/socket.h>
+
+// Call my_input() for all valid messages received in socket sk
+nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, my_input, NULL);
+--------
+
+=== Socket Attributes
+
+.Local Port
+
+The local port number uniquely identifies the socket and is used to
+address it. A unique local port is generated automatically when the
+socket is allocated. It will consist of the Process ID (22 bits) and a
+random number (10 bits) thus allowing up to 1024 sockets per process.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+uint32_t nl_socket_get_local_port(const struct nl_sock *sk);
+void nl_socket_set_local_port(struct nl_sock *sk, uint32_t port);
+--------
+
+See section <<core_addressing>> for more information on port numbers.
+
+CAUTION: Overwriting the local port is possible but you have to ensure
+that the provided value is unique and no other socket in any other
+application is using the same value.
+
+.Peer Port
+
+A peer port can be assigned to the socket which will result in all
+unicast messages sent over the socket to be addresses to the peer. If
+no peer is specified, the message is sent to the kernel which will try
+to automatically bind the socket to a kernel side socket of the same
+netlink protocol family.  It is common practice not to bind the socket
+to a peer port as typically only one kernel side socket exists per
+netlink protocol family.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+uint32_t nl_socket_get_peer_port(const struct nl_sock *sk);
+void nl_socket_set_peer_port(struct nl_sock *sk, uint32_t port);
+--------
+
+See section <<core_addressing>> for more information on port numbers.
+
+.File Descriptor
+
+Netlink uses the BSD socket interface, therefore a file descriptor is
+behind each socket and you may use it directly.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+int nl_socket_get_fd(const struct nl_sock *sk);
+--------
+
+If a socket is used to only receive notifications it usually is best
+to put the socket in non-blocking mode and periodically poll for new
+notifications.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+int nl_socket_set_nonblocking(const struct nl_sock *sk);
+--------
+
+.Send/Receive Buffer Size
+
+The socket buffer is used to queue netlink messages between sender and
+receiver. The size of these buffers specifies the maximum size you
+will be able to write() to a netlink socket, i.e. it will indirectly
+define the maximum message size. The default is 32KiB.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+int nl_socket_set_buffer_size(struct nl_sock *sk, int rx, int tx);
+--------
+
+[[core_sk_cred]]
+.Enable/Disable Credentials
+
+TODO
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+int nl_socket_set_passcred(struct nl_sock *sk, int state);
+--------
+
+.Enable/Disable Auto-ACK Mode
+
+The following functions allow to enable/disable Auto-ACK mode on a socket.
+See <<core_auto_ack>> for more information on what implications that has.
+Auto-ACK mode is enabled by default.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+void nl_socket_enable_auto_ack(struct nl_sock *sk);
+void nl_socket_disable_auto_ack(struct nl_sock *sk);
+--------
+
+.Enable/Disable Message Peeking
+
+If enabled, message peeking causes nl_recv() to try and use MSG_PEEK
+to retrieve the size of the next message received and allocate a
+buffer of that size. Message peeking is enabled by default but can be
+disabled using the following function:
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+void nl_socket_enable_msg_peek(struct nl_sock *sk);
+void nl_socket_disable_msg_peek(struct nl_sock *sk);
+--------
+
+.Enable/Disable Receival of Packet Information
+
+If enabled, each received netlink message from the kernel will include
+an additional struct nl_pktinfo in the control message. The following
+function can be used to enable/disable receival of packet information.
+
+[source,c]
+--------
+#include <netlink/socket.h>
+
+int nl_socket_recv_pktinfo(struct nl_sock *sk, int state);
+--------
+
+CAUTION: Processing of NETLINK_PKTINFO has not been implemented yet.
+
+[[core_send_recv]]
+== Sending and Receiving of Messages / Data
+
+[[core_send]]
+=== Sending Messages
+
+The standard method of sending a netlink message over a netlink socket
+is to use the function nl_send_auto(). It will automatically complete
+the netlink message by filling the missing bits and pieces in the
+netlink message header and will deal with addressing based on the
+options and address set in the netlink socket. The message is then
+passed on to nl_send().
+
+If the default sending semantics implemented by nl_send() do not suit
+the application, it may overwrite the sending function nl_send() by
+specifying an own implementation using the function
+nl_cb_overwrite_send().
+
+[source,c]
+--------
+   nl_send_auto(sk, msg)
+         |
+         |-----> nl_complete_msg(sk, msg)
+         |
+         |
+         |              Own send function specified via nl_cb_overwrite_send()
+         |- - - - - - - - - - - - - - - - - - - -
+         v                                      v
+   nl_send(sk, msg)                         send_func()
+--------
+
+.Using nl_send()
+
+If you do not require any of the automatic message completion
+functionality you may use nl_send() directly but beware that any
+internal calls to nl_send_auto() by the library to send netlink
+messages will still use nl_send(). Therefore if you wish to use any
+higher level interfaces and the behaviour of nl_send() is to your
+dislike then you must overwrite the nl_send() function via
+nl_cb_overwrite_send()
+
+The purpose of nl_send() is to embed the netlink message into a iovec
+structure and pass it on to nl_send_iovec().
+
+[source,c]
+--------
+   nl_send(sk, msg)
+         |
+         v
+   nl_send_iovec(sk, msg, iov, iovlen)
+--------
+
+.Using nl_send_iovec()
+
+nl_send_iovec() expects a finalized netlink message and fills out the
+struct msghdr used for addressing. It will first check if the struct
+nl_msg is addressed to a specific peer (see nlmsg_set_dst()). If not,
+it will try to fall back to the peer address specified in the socket
+(see nl_socket_set_peer_port(). Otherwise the message will be sent
+unaddressed and it is left to the kernel to find the correct peer.
+
+nl_send_iovec() also adds credentials if present and enabled
+(see <<core_sk_cred>>).
+
+The message is then passed on to nl_sendmsg().
+
+[source,c]
+--------
+   nl_send_iovec(sk, msg, iov, iovlen)
+         |
+         v
+   nl_sendmsg(sk, msg, msghdr)
+--------
+
+.Using nl_sendmsg()
+
+nl_sendmsg() expects a finalized netlink message and an optional
+struct msghdr containing the peer address. It will copy the local
+address as defined in the socket (see nl_socket_set_local_port()) into
+the netlink message header.
+
+At this point, construction of the message finished and it is ready to
+be sent.
+
+[source,c]
+--------
+   nl_sendmsg(sk, msg, msghdr)
+         |- - - - - - - - - - - - - - - - - - - - v
+         |                                 NL_CB_MSG_OUT()
+         |<- - - - - - - - - - - - - - - - - - - -+
+         v
+   sendmsg()
+--------
+
+Before sending the application has one last chance to modify the
+message.  It is passed to the NL_CB_MSG_OUT callback function which
+may inspect or modify the message and return an error code. If this
+error code is NL_OK the message is sent using sendmsg() resulting in
+the number of bytes written being returned. Otherwise the message
+sending process is aborted and the error code specified by the
+callback function is returned. See <<core_sk_cb>> for more information
+on how to set callbacks.
+
+.Sending Raw Data with nl_sendto()
+
+If you wish to send raw data over a netlink socket, the following
+function will pass on any buffer provided to it directly to sendto():
+
+[source,c]
+--------
+#include <netlink/netlink.h>
+
+int nl_sendto(struct nl_sock *sk, void *buf, size_t size);
+--------
+
+.Sending of Simple Messages
+
+A special interface exists for sending of trivial messages. The function
+expects the netlink message type, optional netlink message flags, and an
+optional data buffer and data length.  
+[source,c]
+--------
+#include <netlink/netlink.h>
+
+int nl_send_simple(struct nl_sock *sk, int type, int flags,
+                   void *buf, size_t size);
+--------
+
+The function will construct a netlink message header based on the message
+type and flags provided and append the data buffer as message payload. The
+newly constructed message is sent with nl_send_auto().
+
+The following example will send a netlink request message causing the
+kernel to dump a list of all network links to userspace:
+
+[source,c]
+--------
+#include <netlink/netlink.h>
+
+struct nl_sock *sk;
+struct rtgenmsg rt_hdr = {
+	.rtgen_family = AF_UNSPEC,
+};
+
+sk = nl_socket_alloc();
+nl_connect(sk, NETLINK_ROUTE);
+
+nl_send_simple(sock, RTM_GETLINK, NLM_F_DUMP, &rt_hdr, sizeof(rt_hdr));
+--------
+
+[[core_recv]]
+=== Receiving Messages
+
+The easiest method to receive netlink messages is to call nl_recvmsgs_default().
+It will receive messages based on the semantics defined in the socket. The
+application may customize these in detail although the default behaviour will
+probably suit most applications.
+
+nl_recvmsgs_default() will also be called internally by the library whenever
+it needs to receive and parse a netlink message.
+
+The function will fetch the callback configuration stored in the socket and
+call nl_recvmsgs():
+
+[source,c]
+--------
+   nl_recvmsgs_default(sk)
+         |
+         | cb = nl_socket_get_cb(sk)
+         v
+   nl_recvmsgs(sk, cb)
+--------
+
+.Using nl_recvmsgs()
+
+nl_recvmsgs() implements the actual receiving loop, it blocks until a
+netlink message has been received unless the socket has been put into
+non-blocking mode.
+
+For the unlikely scenario that certain required receive characteristics
+can not be achieved by fine tuning the internal recvmsgs function using
+the callback configuration (see <<core_sk_cb>>) the application may provide
+a complete own implementation of it and overwrite all calls to nl_recvmsgs()
+with the function nl_cb_overwrite_recvmsgs().
+
+[source,c]
+--------
+   nl_recvmsgs(sk, cb)
+         |
+         |     Own recvmsgs function specified via nl_cb_overwrite_recvmsgs()
+         |- - - - - - - - - - - - - - - - - - - -
+         v                                      v
+   internal_recvmsgs()                    my_recvmsgs()
+--------
+
+[[core_recv_character]]
+.Receive Characteristics
+
+If the application does not provide its own recvmsgs() implementation
+with the function nl_cb_overwrite_recvmsgs() the following characteristics
+apply while receiving data from a netlink socket:
+
+[source,c]
+--------
+        internal_recvmsgs()
+                |
++-------------->|     Own recv function specified with nl_cb_overwrite_recv()
+|               |- - - - - - - - - - - - - - - -
+|               v                              v
+|           nl_recv()                      my_recv()
+|               |<- - - - - - - - - - - - - - -+
+|               |<-------------+
+|               v              | More data to parse? (nlmsg_next())
+|         Parse Message        | 
+|               |--------------+
+|               v
++------- NLM_F_MULTI set?
+                |
+                v
+            (SUCCESS)
+--------
+
+The function nl_recv() is invoked first to receive data from the
+netlink socket.  This function may be overwritten by the application
+by an own implementation using the function nl_cb_overwrite_recv().
+This may be useful if the netlink byte stream is in fact not received
+from a socket directly but is read from a file or another source.
+
+If data has been read, it will be attemped to parse the data. This
+will be done repeately until the parser returns NL_STOP, an error was
+returned or all data has been parsed.
+
+In case the last message parsed successfully was a multipart message
+(see <<core_multipart>>) and the parser did not
+quit due to either an error or NL_STOP nl_recv() respectively the
+applications own implementation will be called again and the parser
+starts all over.
+
+See <<core_parse_character>> for information on how to extract valid
+netlink messages from the parser and on how to control the behaviour
+of it.
+
+[[core_parse_character]]
+.Parsing Characteristics
+
+The internal parser is invoked for each netlink message received from
+a netlink socket. It is typically fed by nl_recv() (see
+<<core_recv_character>>).
+
+The parser will first ensure that the length of the data stream
+provided is sufficient to contain a netlink message header and that
+the message length as specified in the message header does not exceed
+it.
+
+If this criteria is met, a new struct nl_msg is allocated and the
+message is passed on to the the callback function NL_CB_MSG_IN if one
+is set. Like any other callback function, it may return NL_SKIP to
+skip the current message but continue parsing the next message or
+NL_STOP to stop parsing completely.
+
+The next step is to check the sequence number of the message against
+the currently expected sequence number. The application may provide
+its own sequence number checking algorithm by setting the callback
+function NL_CB_SEQ_CHECK to its own implementation. In fact, calling
+nl_socket_disable_seq_check() to disable sequence number checking will
+do nothing more than set the NL_CB_SEQ_CHECK hook to a function which
+always returns NL_OK.
+
+Another callback hook NL_CB_SEND_ACK exists which is called if the
+message has the NLM_F_ACK flag set. Although I am not aware of any
+userspace netlink socket doing this, the application may want to send
+an ACK message back to the sender (see <<core_msg_ack>>).
+
+[source,c]
+--------
+        parse()
+          |
+          v
+      nlmsg_ok() --> Ignore
+          |
+          |- - - - - - - - - - - - - - - v
+          |                         NL_CB_MSG_IN()
+          |<- - - - - - - - - - - - - - -+
+          |
+          |- - - - - - - - - - - - - - - v
+     Sequence Check                NL_CB_SEQ_CHECK()
+          |<- - - - - - - - - - - - - - -+
+          |
+          |              Message has NLM_F_ACK set
+          |- - - - - - - - - - - - - - - v 
+          |                      NL_CB_SEND_ACK()
+          |<- - - - - - - - - - - - - - -+
+          |
+ Handle Message Type
+--------
+
+[[core_auto_ack]]
+=== Auto-ACK Mode
+
+TODO
+
+== Message Parsing & Construction
+
+=== Message Format
+
+See <<core_netlink_fundamentals>> for an introduction to the netlink
+protocol and its message format.
+
+.Alignment
+
+Most netlink protocols enforce a strict alignment policy for all
+boundries.  The alignment value is defined by NLMSG_ALIGNTO and is
+fixed to 4 bytes.  Therefore all netlink message headers, begin of
+payload sections, protocol specific headers, and attribute sections
+must start at an offset which is a multiple of NLMSG_ALIGNTO.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+int nlmsg_size(int payloadlen);
+int nlmsg_total_size(int payloadlen);
+--------
+
+The library provides a set of function to handle alignment
+requirements automatically. The function nlmsg_total_size() returns
+the total size of a netlink message including the padding to ensure
+the next message header is aligned correctly.
+
+[source,c]
+--------
+     <----------- nlmsg_total_size(len) ------------>
+     <----------- nlmsg_size(len) ------------>
+    +-------------------+- - -+- - - - - - - - +- - -+-------------------+- - -
+    |  struct nlmsghdr  | Pad |     Payload    | Pad |  struct nlsmghdr  |
+    +-------------------+- - -+- - - - - - - - +- - -+-------------------+- - -
+     <---- NLMSG_HDRLEN -----> <- NLMSG_ALIGN(len) -> <---- NLMSG_HDRLEN ---
+--------
+
+If you need to know if padding needs to be added at the end of a
+message, nlmsg_padlen() returns the number of padding bytes that need
+to be added for a specific payload length.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+int nlmsg_padlen(int payloadlen);
+--------
+
+=== Parsing a Message
+
+The library offers two different methods of parsing netlink messages.
+It offers a low level interface for applications which want to do all
+the parsing manually. This method is described below. Alternatively
+the library also offers an interface to implement a parser as part of
+a cache operations set which is especially useful when your protocol
+deals with objects of any sort such as network links, routes, etc.
+This high level interface is described in <<core_cache>>.
+
+.Splitting a byte stream into separate messages
+
+What you receive from a netlink socket is typically a stream of
+messages. You will be given a buffer and its length, the buffer may
+contain any number of netlink messages.
+
+The first message header starts at the beginning of message stream.
+Any subsequent message headers are access by calling nlmsg_next() on
+the previous header.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+struct nlmsghdr *nlmsg_next(struct nlmsghdr *hdr, int *remaining);
+--------
+
+The function nlmsg_next() will automatically substract the size of the
+previous message from the remaining number of bytes.
+
+Please note, there is no indication in the previous message whether
+another message follows or not. You must assume that more messages
+follow until all bytes of the message stream have been processed.
+
+To simplify this, the function nlmsg_ok() exists which returns true if
+another message fits into the remaining number of bytes in the message
+stream. nlmsg_valid_hdr() is similar, it checks whether a specific
+netlink message contains at least a minimum of payload.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+int nlmsg_valid_hdr(const struct nlmsghdr *hdr, int payloadlen);
+int nlmsg_ok(const struct nlmsghdr *hdr, int remaining);
+--------
+
+A typical use of these functions looks like this:
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+void my_parse(void *stream, int length)
+{
+	struct nlmsghdr *hdr = stream;
+
+	while (nlmsg_ok(hdr, length)) {
+		// Parse message here
+		hdr = nlmsg_next(hdr, &length);
+	}
+}
+--------
+
+CAUTION: nlmsg_ok() only returns true if the *complete* message including
+         the message payload fits into the remaining buffer length. It will
+	 return false if only a part of it fits.
+
+The above can also be written using the iterator nlmsg_for_each():
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+struct nlmsghdr *hdr;
+
+nlmsg_for_each(hdr, stream, length) {
+	/* do something with message */
+}
+--------
+
+.Message Payload
+
+The message payload is appended to the message header and is guranteed
+to start at a multiple of +NLMSG_ALIGNTO+. Padding at the end of the
+message header is added if necessary to ensure this. The function
+nlmsg_data() will calculate the necessary offset based on the message
+and returns a pointer to the start of the message payload.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+void *nlmsg_data(const struct nlmsghdr *nlh);
+void *nlmsg_tail(const struct nlmsghdr *nlh);
+int nlmsg_datalen(const struct nlmsghdr *nlh);
+--------
+
+The length of the message payload is returned by nlmsg_datalen().
+
+[source,c]
+--------
+                               <--- nlmsg_datalen(nlh) --->
+    +-------------------+- - -+----------------------------+- - -+
+    |  struct nlmsghdr  | Pad |           Payload          | Pad |
+    +-------------------+- - -+----------------------------+- - -+
+nlmsg_data(nlh) ---------------^                                  ^
+nlmsg_tail(nlh) --------------------------------------------------^
+--------
+
+The payload may consist of arbitary data but may have strict alignment
+and formatting rules depening on the actual netlink protocol.
+
+[[core_msg_attr]]
+.Message Attributes
+
+Most netlink protocols use netlink attributes. It not only makes the
+protocol self documenting but also gives flexibility in expanding the
+protocol at a later point. New attributes can be added at any time and
+older attributes can be obsoleted by newer ones without breaking
+binary compatibility of the protocol.
+
+[source,c]
+--------
+                               <---------------------- payload ------------------------->
+                               <----- hdrlen ---->       <- nlmsg_attrlen(nlh, hdrlen) ->
+    +-------------------+- - -+-----  ------------+- - -+--------------------------------+- - -+
+    |  struct nlmsghdr  | Pad |  Protocol Header  | Pad |           Attributes           | Pad |
+    +-------------------+- - -+-------------------+- - -+--------------------------------+- - -+
+nlmsg_attrdata(nlh, hdrlen) -----------------------------^
+--------
+
+The function nlmsg_attrdata() returns a pointer to the begin of the
+attributes section. The length of the attributes section is returned
+by the function nlmsg_attrlen().
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+struct nlattr *nlmsg_attrdata(const struct nlmsghdr *hdr, int hdrlen);
+int nlmsg_attrlen(const struct nlmsghdr *hdr, int hdrlen);
+--------
+
+See <<core_attr>> for more information on how to use netlink attributes.
+
+.Parsing a Message the Easy Way
+
+The function nlmsg_parse() validate a complete netlink message in one
+step. If +hdrlen > 0+ it will first call nlmsg_valid_hdr() to check
+if the protocol header fits into the message. If there is more payload
+to parse, it will assume it to be attributes and parse the payload
+accordingly. The function behaves exactly like nla_parse() when
+parsing attributes, see <<core_attr_parse_easy>>.
+
+[source,c]
+--------
+int nlmsg_parse(struct nlmsghdr *hdr, int hdrlen, struct nlattr **attrs,
+                int maxtype, struct nla_policy *policy);
+--------
+
+The function nlmsg_validate() is based on nla_validate() and behaves
+exactly the same as nlmsg_parse() except that it only validates and
+will not fill a array with pointers to each attribute.
+
+[source,c]
+--------
+int nlmsg_validate(struct nlmsghdr *hdr, int hdrlen, intmaxtype,
+                   struct nla_policy *policy);
+--------
+
+See <<core_attr_parse_easy>> for an example and more information on
+attribute parsing.
+
+=== Construction of a Message
+
+See <<core_msg_format>> for information on the netlink message format
+and alignment requirements.
+
+Message construction is based on struct nl_msg which uses an internal
+buffer to store the actual netlink message. struct nl_msg +does not+
+point to the netlink message header. Use nlmsg_hdr() to retrieve a
+pointer to the netlink message header.
+
+At allocation time, a maximum message size is specified. It defaults
+to a page (PAGE_SIZE). The application constructing the message will
+reserve space out of this maximum message size repeatedly for each
+header or attribute added. This allows construction of messages across
+various layers of code where lower layers do not need to know about
+the space requirements of upper layers.
+
++Why is setting the maximum message size necessary?+ This
+question is often raised in combination with the proposed solution of
+reallocating the message payload buffer on the fly using realloc().
+While it is possible to reallocate the buffer during construction
+using nlmsg_expand() it will make all pointers into the message buffer
+become stale. This breaks usage of nlmsg_hdr(), nla_nest_start(), and
+nla_nest_end() and is therefore not acceptable as default behaviour.
+
+.Allocating struct nl_msg
+
+The first step in constructing a new netlink message it to allocate a
+`struct nl_msg` to hold the message header and payload. Several
+functions exist to simplify various tasks.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+struct nl_msg *nlmsg_alloc(void);
+void nlmsg_free(struct nl_msg *msg);
+--------
+
+The function nlmsg_alloc() is the default message allocation function.
+It allocates a new message using the default maximum message size which
+equals to one page (PAGE_SIZE). The application can change the default
+size for messages by calling nlmsg_set_default_size():
+
+[source,c]
+--------
+void	  nlmsg_set_default_size(size_t);
+--------
+
+CAUTION: Calling nlmsg_set_default_size() does not change the maximum
+         message size of already allocated messages.
+
+[source,c]
+--------
+struct nl_msg *nlmsg_alloc_size(size_t max);
+--------
+
+Instead of changing the default message size, the function
+nlmsg_alloc_size() can be used to allocate a message with a individual
+maximum message size.
+
+
+If the netlink message header is already known at allocation time, the
+application may sue nlmsg_inherit(). It will allocate a message using
+the default maximum message size and copy the header into the message.
+Calling nlmsg_inherit with +set+ to NULL is equivalent to calling
+nlmsg_alloc().
+
+[source,c]
+--------
+struct nl_msg *nlmsg_inherit(struct nlmsghdr *hdr);
+--------
+
+Alternatively nlmsg_alloc_simple() takes a netlink message type and
+netlink message flags. It is equivalent to nlmsg_inherit() except that it
+takes the two common header fields as arguments instead of a complete
+header.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+struct nl_msg *nlmsg_alloc_simple(int nlmsg_type, int flags);
+--------
+
+.Appending the netlink message header
+
+After allocating struct nl_msg, the netlink message header needs to be
+added unless one of the function nlmsg_alloc_simple() or nlmsg_inherit()
+have been used for allocation in which case this step will replace the
+netlink message header already in place.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+struct nlmsghdr *nlmsg_put(struct nl_msg *msg, uint32_t port, uint32_t seqnr,
+                           int nlmsg_type, int payload, int nlmsg_flags);
+--------
+
+The function nlmsg_put() will build a netlink message header out of
++nlmsg_type+, +nlmsg_flags+, +seqnr+, and +port+ and copy it into the
+netlink message. +seqnr+ can be set to +NL_AUTO_SEQ+ to indiciate
+that the next possible sequence number should be used automatically.
+To use this feature, the message must be sent using the function
+nl_send_auto(). Like +port+, the argument +seqnr+ can be set to
++NL_AUTO_PORT+ indicating that the local port assigned to the socket
+should be used as source port. This is generally a good idea unless
+you are replying to a request. See <<core_netlink_fundamentals>>
+for more information on how to fill the header.
+
+NOTE: The argument +payload+ can be used by the application to reserve
+      room for additional data after the header. A value of > 0 is
+      equivalent to calling +nlmsg_reserve(msg, payload, NLMSG_ALIGNTO)+.
+      See <<core_msg_reserve>> for more information on reserving room for
+      data.
+
+.Example
+[source,c]
+--------
+#include <netlink/msg.h>
+
+struct nlmsghdr *hdr;
+struct nl_msg *msg;
+struct myhdr {
+	uint32_t foo1, foo2;
+} hdr = { 10, 20 };
+
+/* Allocate a message with the default maximum message size */
+msg = nlmsg_alloc();
+
+/*
+ * Add header with message type MY_MSGTYPE, the flag NLM_F_CREATE,
+ * let library fill port and sequence number, and reserve room for
+ * struct myhdr
+ */
+hdr = nlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, MY_MSGTYPE, sizeof(hdr), NLM_F_CREATE);
+
+/* Copy own header into newly reserved payload section */
+memcpy(nlmsg_data(hdr), &hdr, sizeof(hdr));
+
+/*
+ * The message will now look like this:
+ *     +-------------------+- - -+----------------+- - -+
+ *     |  struct nlmsghdr  | Pad |  struct myhdr  | Pad |
+ *     +-------------------+-----+----------------+- - -+
+ * nlh -^                        /                \
+ *                              +--------+---------+
+ *                              |  foo1  |  foo2   |
+ *                              +--------+---------+
+ */
+--------
+
+[[core_msg_reserve]]
+.Reserving room at the end of the message
+
+Most functions described later on will automatically take care of
+reserving room for the data that is added to the end of the netlink
+message. In some situations it may be requried for the application
+to reserve room directly though.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+void *nlmsg_reserve(struct nl_msg *msg, size_t len, int pad);
+--------
+
+The function nlmsg_reserve() reserves +len+ bytes at the end of the
+netlink message and returns a pointer to the start of the reserved area.
+The +pad+ argument can be used to request +len+ to be aligned to any
+number of bytes prior to reservation.
+
+The following example requests to reserve a 17 bytes area at the end of
+message aligned to 4 bytes. Therefore a total of 20 bytes will be
+reserved.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+void *buf = nlmsg_reserve(msg, 17, 4);
+--------
+
+NOTE: `nlmsg_reserve()` will *not* align the start of the buffer. Any
+      alignment requirements must be provided by the owner of the
+      previous message section.
+
+.Appending data at the end of the message
+
+The function `nlmsg_append()` appends `len` bytes at the end of the
+message, padding it if requested and necessary.
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+int nlmsg_append(struct nl_msg *msg, void *data, size_t len, int pad);
+--------
+
+It is equivalent to calling `nlmsg_reserve()` and `memcpy()`ing the
+data into the freshly reserved data section.
+
+NOTE: `nlmsg_append()` will *not* align the start of the data. Any
+      alignment requirements must be provided by the owner of the
+      previous message section.
+
+.Adding attribtues to a message
+
+Construction of attributes and addition of attribtues to the message is
+covereted in section <<core_attr>>.
+
+[[core_attr]]
+== Attributes
+
+Any form of payload should be encoded as netlink attributes whenever
+possible. Use of attributes allows to extend any netlink protocol in
+the future without breaking binary compatibility. F.e. Suppose your
+device may currently be using 32 bit counters for statistics but years
+later the device switches to maintaining 64 bit counters to account
+for faster network hardware. If your protocol is using attributes the
+move to 64 bit counters is trivial and only involves in sending an
+additional attribute containing the 64 bit variants while still
+providing the old legacy 32 bit counters. If your protocol is not using
+attributes you will not be able to switch data types without breaking
+all existing users of the protocol. 
+
+The concept of nested attributes also allows for subsystems of your
+protocol to implement and maintain their own attribute schemas. Suppose
+a new generation of network device is introduced which requires a
+completely new set of configuration settings which was unthinkable when
+the netlink protocol was initially designed. Using attributes the new
+generation of devices may define a new attribute and fill it with its
+own new structure of attributes which extend or even obsolete the old
+attributes.
+
+Therefore, _always_ use attributes even if you are almost certain that
+the message format will never ever change in the future.
+
+[[core_attr_format]]
+=== Attribute Format
+
+Netlink attributes allow for any number of data chunks of arbitary
+length to be attached to a netlink message. See <<core_msg_attr>>
+for more information on where attributes are stored in the message.
+
+The format of the attributes data returned by nlmsg_attrdata() is as
+follows:
+
+[source,c]
+--------
+     <----------- nla_total_size(payload) ----------->
+     <---------- nla_size(payload) ----------->
+    +-----------------+- - -+- - - - - - - - - +- - -+-----------------+- - -
+    |  struct nlattr  | Pad |     Payload      | Pad |  struct nlattr  |
+    +-----------------+- - -+- - - - - - - - - +- - -+-----------------+- - -
+     <---- NLA_HDRLEN -----> <--- NLA_ALIGN(len) ---> <---- NLA_HDRLEN ---
+--------
+
+Every attribute must start at an offset which is a multiple of
++NLA_ALIGNTO+ (4 bytes). If you need to know whether an attribute needs
+to be padded at the end, the function nla_padlen() returns the number
+of padding bytes that will or need to be added.
+
+image:attribute_hdr.png["Netlink Attribute Header"]
+
+Every attribute is encoded with a type and length field, both 16 bits,
+stored in the attribute header (struct nlattr) preceding the attribute
+payload. The length of an attribute is used to calculate the offset to
+the next attribute.
+
+[[core_attr_parse]]
+=== Parsing Attributes
+
+[[core_attr_parse_split]]
+.Splitting an Attributes Stream into Attributes
+
+Although most applications will use one of the functions from the
+nlmsg_parse() family (See <<core_attr_parse_easy>>) an interface exists
+to split the attributes stream manually.
+
+As described in <<core_attr_format>> the attributes section contains a
+infinite sequence or stream of attributes. The pointer returned by
+nlmsg_attrdata() (See <<core_msg_attr>>) points to the first attribute
+header. Any subsequent attribute is accessed with the function nla_next()
+based on the previous header.
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+struct nlattr *nla_next(const struct nlattr *attr, int *remaining);
+--------
+
+The semantics are equivalent to nlmsg_next() and thus nla_next() will also
+subtract the size of the previous attribute from the remaining number of
+bytes in the attributes stream.
+
+Like messages, attributes do not contain an indicator whether another
+attribute follows or not. The only indication is the number of bytes left
+in the attribute stream. The function nla_ok() exists to determine whether
+another attribute fits into the remaining number of bytes or not.
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+int nla_ok(const struct nlattr *attr, int remaining);
+--------
+
+A typical use of nla_ok() and nla_next() looks like this:
+
+.nla_ok()/nla_next() usage
+[source,c]
+--------
+#include <netlink/msg.h>
+#include <netlink/attr.h>
+
+struct nlattr *hdr = nlmsg_attrdata(msg, 0);
+int remaining = nlmsg_attrlen(msg, 0);
+
+while (nla_ok(hdr, remaining)) {
+	/* parse attribute here */
+	hdr = nla_next(hdr, &remaining);
+};
+--------
+
+NOTE: `nla_ok()` only returns true if the *complete* attributes
+      including the attribute payload fits into the remaining number
+      of bytes.
+
+.Accessing Attribute Header and Payload
+
+Once the individual attributes have been sorted out by either splitting
+the attributes stream or using another interface the attribute header
+and payload can be accessed.
+
+[source,c]
+--------
+                             <- nla_len(hdr) ->
+    +-----------------+- - -+- - - - - - - - - +- - -+
+    |  struct nlattr  | Pad |     Payload      | Pad |
+    +-----------------+- - -+- - - - - - - - - +- - -+
+nla_data(hdr) ---------------^
+--------
+
+The functions nla_len() and nla_type() can be used to access the attribute
+header. nla_len() will return the length of the payload not including
+eventual padding bytes. nla_type returns the attribute type.
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+int nla_len(const struct nlattr *hdr);
+int nla_type(const struct nlattr *hdr);
+--------
+
+The function nla_data() will return a pointer to the attribute
+payload. Please note that due to +NLA_ALIGNTO+ being 4 bytes it may
+not be safe to cast and dereference the pointer for any datatype
+larger than 32 bit depending on the architecture the application is
+run on.
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+void *nla_data(const struct nlattr *hdr);
+--------
+
+[NOTE]
+Never rely on the size of a payload being what you expect it to be.
+_Always_ verify the payload size and make sure that it matches your
+expectations. See <<core_attr_validation>>
+
+[[core_attr_validation]]
+.Attribute Validation
+
+When receiving netlink attributes, the receiver has certain expections
+on how the attributes should look like. These expectations must be
+defined to make sure the sending side meets our expecations. For this
+purpose, a attribute validation interface exists which must be used
+prior to accessing any payload.
+
+All functions providing attribute validation functionality are based
+on struct nla_policy:
+
+[source,c]
+--------
+struct nla_policy {
+	uint16_t	type;
+	uint16_t	minlen;
+	uint16_t	maxlen;
+};
+--------
+
+The +type+ member specifies the datatype of the attribute, e.g.
++NLA_U32+, +NLA_STRING+, +NLA_FLAG+. The default is +NLA_UNSPEC+. The
++minlen+ member defines the minmum payload length of an attribute to
+be considered a valid attribute. The value for +minlen+ is implicit
+for most basic datatypes such as integers or flags. The +maxlen+
+member can be used to define a maximum payload length for an
+attribute to still be considered valid.
+
+NOTE: Specyfing a maximum payload length is not recommended when
+      encoding structures in an attribute as it will prevent any
+      extension of the structure in the future. Something that is
+      frequently done in netlink protocols and does not break
+      backwards compatibility.
+
+One of the functions which use struct nla_policy is nla_validate().
+The function expects an array of struct nla_policy and will access the
+array using the attribute type as index. If an attribute type is out
+of bounds the attribute is assumed to be valid. This is intentional
+behaviour to allow older applications not yet aware of recently
+introduced attributes to continue functioning.
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+int nla_validate(struct nlattr *head, int len, int maxtype, struct nla_policy *policy);
+--------
+
+The function nla_validate() returns 0 if all attributes are valid,
+otherwise a validation failure specific error code is returned.
+
+Most applications will rarely use nla_validate() directly but use
+nla_parse() instead which takes care of validation in the same way but
+also parses the the attributes in the same step. See
+<<core_attr_parse_easy>> for an example and more information.
+
+The validation process in detail:
+
+. If attribute type is 0 or exceeds +maxtype+ attribute is 
+  considered valid, 0 is returned.
+. If payload length is < +minlen+, +-NLE_ERANGE+ is returned.
+. If +maxlen+ is defined and payload exceeds it, +-NLE_ERANGE+
+  is returned.
+. Datatype specific requirements rules, see <<core_attr_types>>
+. If all is ok, 0 is returned.
+
+[[core_attr_parse_easy]]
+.Parsing Attributes the Easy Way
+
+Most applications will not want to deal with splitting attribute
+streams themselves as described in <<core_attr_parse_split>>
+A much easier method is to use nla_parse().
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+int nla_parse(struct nlattr **attrs, int maxtype, struct nlattr *head,
+              int len, struct nla_policy *policy);
+--------
+
+The function nla_parse() will iterate over a stream of attributes,
+validate each attribute as described in <<core_attr_validation>>
+If the validation of all attributes succeeds, a pointer to each attribute
+is stored in the +attrs+ array at `attrs[nla_type(attr)]`.
+
+As an alernative to nla_parse() the function nlmsg_parse() can be used
+to parse the message and its attributes in one step. See
+<<core_attr_parse_easy>> for information on how to use these functions.
+
+.Example:
+
+The following example demonstrates how to parse a netlink message sent
+over a netlink protocol which does not use protocol headers. The example
+does enforce a attribute policy however, the attribute MY_ATTR_FOO must
+be a 32 bit integer, and the attribute MY_ATTR_BAR must be a string with
+a maximum length of 16 characters.
+
+[source,c]
+---------
+#include <netlink/msg.h>
+#include <netlink/attr.h>
+
+enum {
+	MY_ATTR_FOO = 1,
+	MY_ATTR_BAR,
+	__MY_ATTR_MAX,
+};
+
+#define MY_ATTR_MAX (__MY_ATTR_MAX - 1)
+
+static struct nla_policy my_policy[MY_ATTR_MAX+1] = {
+	[MY_ATTR_FOO] = { .type = NLA_U32 },
+	[MY_ATTR_BAR] = { .type = NLA_STRING,
+			  .maxlen = 16 },
+};
+
+void parse_msg(struct nlmsghdr *nlh)
+{
+	struct nlattr *attrs[MY_ATTR_MAX+1];
+
+	if (nlmsg_parse(nlh, 0, attrs, MY_ATTR_MAX, my_policy) < 0)
+		/* error */
+
+	if (attrs[MY_ATTR_FOO]) {
+		/* MY_ATTR_FOO is present in message */
+		printf("value: %u\n", nla_get_u32(attrs[MY_ATTR_FOO]));
+	}
+}
+---------
+
+.Locating a Single Attribute
+
+An application only interested in a single attribute can use one of the
+functions nla_find() or  nlmsg_find_attr(). These function will iterate
+over all attributes, search for a matching attribute and return a pointer
+to the corresponding attribute header.
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+struct nlattr *nla_find(struct nlattr *head, int len, int attrtype);
+--------
+
+[source,c]
+--------
+#include <netlink/msg.h>
+
+struct nlattr *nlmsg_find_attr(struct nlmsghdr *hdr, int hdrlen, int attrtype);
+--------
+
+NOTE: `nla_find()` and `nlmsg_find_attr()` will *not* search in nested
+      attributes recursively, see <<core_attr_nested>>.
+
+==== Iterating over a Stream of Attributes
+
+In some situations it does not make sense to assign a unique attribute
+type to each attribute in the attribute stream. For example a list may
+be transferd using a stream of attributes and even if the attribute type
+is incremented for each attribute it may not make sense to use the
+nlmsg_parse() or nla_parse() function to fill an array.
+
+Therefore methods exist to iterate over a stream of attributes:
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+nla_for_each_attr(attr, head, len, remaining)
+--------
+
+nla_for_each_attr() is a macro which can be used in front of a code
+block:
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+struct nalttr *nla;
+int rem;
+
+nla_for_each_attr(nla, attrstream, streamlen, rem) {
+	/* validate & parse attribute */
+}
+
+if (rem > 0)
+	/* unparsed attribute data */
+--------
+
+[[core_attr_constr]]
+=== Attribute Construction
+
+The interface to add attributes to a netlink message is based on the
+regular message construction interface. It assumes that the message
+header and an eventual protocol header has been added to the message
+already.
+
+[source,c]
+--------
+struct nlattr *nla_reserve(struct nl_msg *msg, int attrtype, int len);
+--------
+
+The function nla_reserve() adds an attribute header at the end of the
+message and reserves room for +len+ bytes of payload. The function
+returns a pointer to the attribute payload section inside the message.
+Padding is added at the end of the attribute to ensure the next
+attribute is properly aligned.
+
+[source,c]
+--------
+int nla_put(struct nl_msg *msg, int attrtype, int attrlen, const void *data);
+--------
+
+The function nla_put() is base don nla_reserve() but takes an additional
+pointer +data+ pointing to a buffer containing the attribute payload.
+It will copy the buffer into the message automatically.
+
+.Example:
+
+[source,c]
+--------
+struct my_attr_struct {
+	uint32_t a;
+	uint32_t b;
+};
+
+int my_put(struct nl_msg *msg)
+{
+	struct my_attr_struct obj = {
+		.a = 10,
+		.b = 20,
+	};
+
+	return nla_put(msg, ATTR_MY_STRUCT, sizeof(obj), &obj);
+}
+--------
+
+See <<core_attr_types>> for datatype specific attribute construction
+functions.
+
+.Exception Based Attribute Construction
+
+Like in the kernel API an exception based construction interface is
+provided. The behaviour of the macros is identical to their regular
+function counterparts except that in case of an error, the target
+`nla_put_failure` is jumped.
+
+.Example:
+[source,c]
+--------
+#include <netlink/msg.h>
+#include <netlink/attr.h>
+
+void construct_attrs(struct nl_msg *msg)
+{
+	NLA_PUT_STRING(msg, MY_ATTR_FOO1, "some text");
+	NLA_PUT_U32(msg, MY_ATTR_FOO1, 0x1010);
+	NLA_PUT_FLAG(msg, MY_ATTR_FOO3, 1);
+
+	return 0;
+
+nla_put_failure:
+	/* NLA_PUT* macros jump here in case of an error */
+	return -EMSGSIZE;
+}
+--------
+
+See <<core_attr_types>> for more information on the datatype specific
+exception based variants.
+
+[[core_attr_types]]
+=== Attribute Data Types
+
+A number of basic data types have been defined to simplify access and
+validation of attributes. The datatype is not encoded in the
+attribute, therefore bthe sender and receiver are required to use the
+same definition on what attribute is of what type.
+
+[options="header", cols="1m,5"]
+|================================================
+| Type             | Description
+| NLA_UNSPEC       | Unspecified attribute
+| NLA_U{8\|16\|32} | Integers
+| NLA_STRING       | String
+| NLA_FLAG         | Flag
+| NLA_NESTED       | Nested attribute
+|================================================
+
+Besides simplified access to the payload of such datatypes, the major
+advantage is the automatic validation of each attribute based on a
+policy. The validation ensures safe access to the payload by checking
+for minimal payload size and can also be used to enforce maximum
+payload size for some datatypes.
+
+==== Integer Attributes
+
+The most frequently used datatypes are integers. Integers come in four
+different sizes:
+[horizontal]
+NLA_U8::  8bit integer
+NLA_U16:: 16bit integer
+NLA_U32:: 32bit integer
+NLA_U64:: 64bit integer
+
+Note that due to the alignment requirements of attributes the integer
+attribtue +NLA_u8+ and +NLA_U16+ will not result in space savings in
+the netlink message. Their use is intended to limit the range of
+values.
+
+.Parsing Integer Attributes
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+uint8_t  nla_get_u8(struct nlattr *hdr);
+uint16_t nla_get_u16(struct nlattr *hdr);
+uint32_t nla_get_u32(struct nlattr *hdr);
+uint64_t nla_get_u64(struct nlattr *hdr);
+--------
+
+Example:
+
+[source,c]
+--------
+if (attrs[MY_ATTR_FOO])
+	uint32_t val = nla_get_u32(attrs[MY_ATTR_FOO]);
+--------
+
+.Constructing Integer Attributes
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+int nla_put_u8(struct nl_msg *msg, int attrtype, uint8_t value);
+int nla_put_u16(struct nl_msg *msg, int attrtype, uint16_t value);
+int nla_put_u32(struct nl_msg *msg, int attrtype, uint32_t value);
+int nla_put_u64(struct nl_msg *msg, int attrtype, uint64_t value);
+--------
+
+Exception based:
+
+[source,c]
+--------
+NLA_PUT_U8(msg, attrtype, value)
+NLA_PUT_U16(msg, attrtype, value)
+NLA_PUT_U32(msg, attrtype, value)
+NLA_PUT_U64(msg, attrtype, value)
+--------
+
+.Validation
+
+Use +NLA_U8+, +NLA_U16+, +NLA_U32+, or +NLA_U64+ to define the type of
+integer when filling out a struct nla_policy array. It will
+automatically enforce the correct minimum payload length policy.
+
+Validation does not differ between signed and unsigned integers, only
+the size matters. If the appliaction wishes to enforce particular value
+ranges it must do so itself.
+
+[source,c]
+--------
+static struct nla_policy my_policy[ATTR_MAX+1] = {
+	[ATTR_FOO] = { .type = NLA_U32 },
+	[ATTR_BAR] = { .type = NLA_U8 },
+};
+--------
+
+The above is equivalent to:
+[source,c]
+--------
+static struct nla_policy my_policy[ATTR_MAX+1] = {
+	[ATTR_FOO] = { .minlen = sizeof(uint32_t) },
+	[ATTR_BAR] = { .minlen = sizeof(uint8_t) },
+};
+--------
+
+==== String Attributes
+
+The string datatype represents a NUL termianted character string of
+variable length. It is not intended for binary data streams.
+
+The payload of string attributes can be accessed with the function
+nla_get_string(). nla_strdup() calls strdup() on the payload and returns
+the newly allocated string.
+
+[source,c]
+--------
+#include <netlink/attr.h>
+
+char *nla_get_string(struct nlattr *hdr);
+char *nla_strdup(struct nlattr *hdr);
+--------
+
+String attributes are constructed with the function +nla_put_string()+
+respectively +NLA_PUT_STRING()+. The length of the payload will be
+strlen()+1, the trailing NUL byte is included.
+
+[source,c]
+--------
+int nla_put_string(struct nl_msg *msg, int attrtype, const char *data);
+
+NLA_PUT_STRING(msg, attrtype, data)
+--------
+
+For validation purposes the type +NLA_STRING+ can be used in
++struct nla_policy+ definitions. It implies a minimum payload length
+of 1 byte and checks for a trailing NUL byte. Optionally the +maxlen+
+member defines the maximum length of a character string (including the
+trailing NUL byte).
+
+[source,c]
+--------
+static struct nla_policy my_policy[] = {
+	[ATTR_FOO] = { .type = NLA_STRING,
+		       .maxlen = IFNAMSIZ },
+};
+--------
+
+==== Flag Attributes
+
+The flag attribute represents a boolean datatype. The presence of the
+attribute implies a value of +true+, the absence of the attribute
+implies the value +false+. Therefore the payload length of flag
+attributes is always 0.
+
+[source,c]
+--------
+int nla_get_flag(struct nlattr *hdr);
+int nla_put_flag(struct nl_msg *msg, int attrtype);
+--------
+
+The type +NLA_FLAG+ is used for validation purposes. It implies a 
++maxlen+ value of 0 and thus enforces a maximum payload length of 0.
+
+.Example:
+[source,c]
+--------
+/* nla_put_flag() appends a zero sized attribute to the message. */
+nla_put_flag(msg, ATTR_FLAG);
+
+/* There is no need for a receival function, the presence is the value. */
+if (attrs[ATTR_FLAG])
+	/* flag is present */
+--------
+
+[[core_attr_nested]]
+==== Nested Attributes
+
+As described in <<core_attr>>, attributes can be nested allowing for
+complex tree structures of attributes. It is commonly used to delegate
+the responsibility of a subsection of the message to a subsystem.
+Nested attributes are also commonly used for transmitting list of objects.
+
+When nesting attributes, the nested attributes are included as payload
+of a container attribute.
+
+NOTE: When validating the attributes using nlmsg_validate(),
+      nlmsg_parse(), nla_validate(), or nla_parse() only the
+      attributes on the first level are being validated. None of these
+      functions will validate attributes recursively. Therefore you
+      must explicitely call nla_validate() or use nla_parse_nested()
+      for each level of nested attributes.
+
+The type +NLA_NESTED+ should be used when defining nested attributes
+in a struct nla_policy definition. It will not enforce any minimum
+payload length unless +minlen+ is specified explicitely. This is
+because some netlink protocols implicitely allow empty container
+attributes.
+
+[source,c]
+--------
+static struct nla_policy my_policy[] = {
+	[ATTR_OPTS] = { .type = NLA_NESTED },
+};
+--------
+
+.Parsing of Nested Attributes
+
+The function nla_parse_nested() is used to parse nested attributes.
+Its behaviour is identical to nla_parse() except that it takes a
+struct nlattr as argument and will use the payload as stream of
+attributes.
+
+[source,c]
+--------
+if (attrs[ATTR_OPTS]) {
+	struct nlattr *nested[NESTED_MAX+1];
+	struct nla_policy nested_policy[] = {
+		[NESTED_FOO] = { .type = NLA_U32 },
+	};
+
+	if (nla_parse_nested(nested, NESTED_MAX, attrs[ATTR_OPTS], nested_policy) < 0)
+		/* error */
+	
+	if (nested[NESTED_FOO])
+		uint32_t val = nla_get_u32(nested[NESTED_FOO]);
+}
+--------
+
+.Construction of Nested Attributes
+
+Attributes are nested by surrounding them with calls to nla_nest_start()
+and nla_nest_end(). nla_nest_start() will add a attribute header to
+the message but no actual payload. All data added to the message from
+this point on will be part of the container attribute until nla_nest_end()
+is called which "closes" the attribute, correcting its payload length to
+include all data length.
+
+[source,c]
+--------
+int put_opts(struct nl_msg *msg)
+{
+	struct nlattr *opts;
+
+	if (!(opts = nla_nest_start(msg, ATTR_OPTS)))
+		goto nla_put_failure;
+
+	NLA_PUT_U32(msg, NESTED_FOO, 123);
+	NLA_PUT_STRING(msg, NESTED_BAR, "some text");
+
+	nla_nest_end(msg, opts);
+	return 0;
+
+nla_put_failure:
+	nla_nest_cancel(msg, opts);
+	return -EMSGSIZE;
+}
+--------
+
+==== Unspecified Attribute
+
+This is the default attribute type and used when none of the basic
+datatypes is suitable. It represents data of arbitary type and length.
+
+See <<core_addr_alloc, Address Allocation>> for a more information on
+a special interface allowing the allocation of abstract address object
+based on netlink attributes which carry some form of network address.
+
+See <<core_data_alloc, Abstract Data Allocation>> for more information
+on how to allocate abstract data objects based on netlink attributes.
+
+Use the function nla_get() and nla_put() to access the payload and
+construct attributes. See <<core_attr_constr, Attribute Construction>>
+for an example.
+
+=== Examples
+
+==== Constructing a Netlink Message with Attributes
+
+[source,c]
+--------
+struct nl_msg *build_msg(int ifindex, struct nl_addr *lladdr, int mtu)
+{
+	struct nl_msg *msg;
+	struct nlattr *info, *vlan;
+	struct ifinfomsg ifi = {
+		.ifi_family = AF_INET,
+		.ifi_index = ifindex,
+	};
+
+	/* Allocate a default sized netlink message */
+	if (!(msg = nlmsg_alloc_simple(RTM_SETLINK, 0)))
+		return NULL;
+
+	/* Append the protocol specific header (struct ifinfomsg)*/
+	if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure
+
+	/* Append a 32 bit integer attribute to carry the MTU */
+	NLA_PUT_U32(msg, IFLA_MTU, mtu);
+
+	/* Append a unspecific attribute to carry the link layer address */
+	NLA_PUT_ADDR(msg, IFLA_ADDRESS, lladdr);
+
+	/* Append a container for nested attributes to carry link information */
+	if (!(info = nla_nest_start(msg, IFLA_LINKINFO)))
+		goto nla_put_failure;
+
+	/* Put a string attribute into the container */
+	NLA_PUT_STRING(msg, IFLA_INFO_KIND, "vlan");
+
+	/*
+	 * Append another container inside the open container to carry
+	 * vlan specific attributes
+	 */
+	if (!(vlan = nla_nest_start(msg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
+
+	/* add vlan specific info attributes here... */
+
+	/* Finish nesting the vlan attributes and close the second container. */
+	nla_nest_end(msg, vlan);
+
+	/* Finish nesting the link info attribute and close the first container. */
+	nla_nest_end(msg, info);
+
+	return msg;
+
+nla_put_failure:
+	nlmsg_free(msg);
+	return NULL;
+}
+------
+
+==== Parsing a Netlink Message with Attributes
+
+[source,c]
+--------
+int parse_message(struct nlmsghdr *hdr)
+{
+	/*
+	 * The policy defines two attributes: a 32 bit integer and a container
+	 * for nested attributes.
+	 */
+	struct nla_policy attr_policy[] = {
+		[ATTR_FOO] = { .type = NLA_U32 },
+		[ATTR_BAR] = { .type = NLA_NESTED },
+	};
+	struct nlattr *attrs[ATTR_MAX+1];
+	int err;
+
+	/*
+	 * The nlmsg_parse() function will make sure that the message contains
+	 * enough payload to hold the header (struct my_hdr), validates any
+	 * attributes attached to the messages and stores a pointer to each
+	 * attribute in the attrs[] array accessable by attribute type.
+	 */
+	if ((err = nlmsg_parse(hdr, sizeof(struct my_hdr), attrs, ATTR_MAX,
+			       attr_policy)) < 0)
+		goto errout;
+
+	if (attrs[ATTR_FOO]) {
+		/*
+		 * It is safe to directly access the attribute payload without
+		 * any further checks since nlmsg_parse() enforced the policy.
+		 */
+		uint32_t foo = nla_get_u32(attrs[ATTR_FOO]);
+	}
+
+	if (attrs[ATTR_BAR]) {
+		struct *nested[NESTED_MAX+1];
+
+		/*
+		 * Attributes nested in a container can be parsed the same way
+		 * as top level attributes.
+		 */
+		err = nla_parse_nested(nested, NESTED_MAX, attrs[ATTR_BAR],
+                		       nested_policy);
+		if (err < 0)
+			goto errout;
+
+		// Process nested attributes here.
+	}
+
+	err = 0;
+errout:
+	return err;
+}
+--------
+
+[[core_cb]]
+== Callback Configurations
+
+Callback hooks and overwriting capabilities are provided in various places
+inside library to control the behaviour of several functions. All the
+callback and overwrite functions are packed together in struct nl_cb which
+is attached to a netlink socket or passed on to functions directly.
+
+=== Callback Hooks
+
+Callback hooks are spread across the library to provide entry points for
+message processing and to take action upon certain events.
+
+Callback functions may return the following return codes:
+[options="header", cols="1m,4"]
+|========================================================================
+| Return Code      | Description
+| NL_OK            | Proceed.
+| NL_SKIP          | Skip message currently being processed and continue
+                     parsing the receive buffer.
+| NL_STOP          | Stop parsing and discard all remaining data in the
+                    receive buffer.
+|========================================================================
+
+.Default Callback Implementations
+
+The library provides three sets of default callback implementations:
+* +NL_CB_DEFAULT+ This is the default set. It implets the default behaviour.
+     See the table below for more information on the return codes of each
+     function.
+* +NL_CB_VERBOSE+ This set is based on the default set but will cause an
+     error message to be printed to stderr for error messages, invalid
+     messages, message overruns and unhandled valid messages. The
+     +arg+ pointer in nl_cb_set() and nl_cb_err() can be used to
+     provide a FILE * which overwrites stderr.
+* +NL_CB_DEBUG+ This set is intended for debugging purposes. It is
+  based on the verbose set but will decode and dump each message sent
+  or received to the console.
+
+.Message Processing Callbacks
+
+.nl_sendmsg() callback hooks:
+[cols="2m,4e,1m", options="header"]
+|============================================================================
+| Callback ID        | Description                       | Default Return Value
+| NL_CB_MSG_OUT      | Each message sent                 | NL_OK
+|============================================================================
+
+Any function called by NL_CB_MSG_OUT may return a negative error code to
+prevent the message from being sent and the error code being returned.
+
+nl_recvmsgs() callback hooks (ordered by priority):
+[cols="2m,4e,1m", options="header"]
+|============================================================================
+| Callback ID        | Description                       | Default Return Value
+| NL_CB_MSG_IN       | Each message received             | NL_OK
+| NL_CB_SEQ_CHECK    | May overwrite sequence check algo | NL_OK
+| NL_CB_INVALID      | Invalid messages                  | NL_STOP
+| NL_CB_SEND_ACK     | Messages with NLM_F_ACK flag set  | NL_OK
+| NL_CB_FINISH       | Messages of type NLMSG_DONE       | NL_STOP
+| NL_CB_SKIPPED      | Messages of type NLMSG_NOOP       | NL_SKIP
+| NL_CB_OVERRUN      | Messages of type NLMSG_OVERRUN    | NL_STOP
+| NL_CB_ACK          | ACK Messages                      | NL_STOP
+| NL_CB_VALID        | Each valid message                | NL_OK
+|============================================================================
+
+Any of these functions may return NL_OK, NL_SKIP, or NL_STOP.
+
+Message processing callback functions are set with nl_cb_set():
+[source,c]
+--------
+#include <netlink/handlers.h>
+
+int nl_cb_set(struct nl_cb *cb, enum nl_cb_type type, enum nl_cb_kind kind,
+              nl_recvmsg_msg_cb_t func, void *arg);
+
+typedef int (*nl_recvmsg_msg_cb_t)(struct nl_msg *msg, void *arg);
+--------
+
+.Callback for Error Messages
+
+A special function prototype is used for the error message callback hook:
+
+[source,c]
+--------
+#include <netlink/handlers.h>
+
+int nl_cb_err(struct nl_cb *cb, enum nl_cb_kind kind, nl_recvmsg_err_cb_t func, void *arg);
+
+typedef int(* nl_recvmsg_err_cb_t)(struct sockaddr_nl *nla, struct nlmsgerr *nlerr, void *arg);
+--------
+
+.Example: Setting up a callback set
+[source,c]
+--------
+#include <netlink/handlers.h>
+
+/* Allocate a callback set and initialize it to the verbose default set */
+struct nl_cb *cb = nl_cb_alloc(NL_CB_VERBOSE);
+
+/* Modify the set to call my_func() for all valid messages */
+nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, my_func, NULL);
+
+/*
+ * Set the error message handler to the verbose default implementation
+ * and direct it to print all errors to the given file descriptor.
+ */
+FILE *file = fopen(...);
+nl_cb_err(cb, NL_CB_VERBOSE, NULL, file);
+--------
+
+=== Overwriting of Internal Functions
+
+When the library needs to send or receive netlink messages in high level
+interfaces it does so by calling its own low level API. In the case the
+default characteristics are not sufficient for the application, it may
+overwrite several internal function calls with own implementations.
+
+.Overwriting recvmsgs()
+
+See <<core_recv, Receiving Netlink Messages>> for more information on
+how and when recvmsgs() is called internally.
+
+[source,c]
+--------
+#include <netlink/handlers.h>
+
+void nl_cb_overwrite_recvmsgs(struct nl_cb *cb,
+                              int (*func)(struct nl_sock *sk, struct nl_cb *cb));
+--------
+
+The following criteras must be met if a recvmsgs() implementation is
+supposed to work with high level interfaces:
+
+- MUST respect the callback configuration +cb+, therefore:
+  - MUST call +NL_CB_VALID+ for all valid messages, passing on 
+  - MUST call +NL_CB_ACK+ for all ACK messages
+  - MUST correctly handle multipart messages, calling NL_CB_VALID for
+    each message until a NLMSG_DONE message is received.
+- MUST report error code if a NLMSG_ERROR or NLMSG_OVERRUN mesasge is
+  received.
+
+.Overwriting nl_recv()
+
+Often it is sufficient to overwrite `nl_recv()` which is responsible
+from receiving the actual data from the socket instead of replacing
+the complete `recvmsgs()` logic.
+
+See <<core_recv_character, Receive Characteristics>> for more
+information on how and when `nl_recv()` is called internally.
+
+[source,c]
+--------
+#include <netlink/handlers.h>
+
+void nl_cb_overwrite_recv(struct nl_cb *cb,
+                          int (*func)(struct nl_sock * sk,
+                                      struct sockaddr_nl *addr,
+                                      unsigned char **buf,
+                                      struct ucred **cred));
+--------
+
+The following criteras must be met for an own `nl_recv()`
+implementation:
+
+ - *MUST* return the number of bytes read or a negative error code if
+   an error occured. The function may also return 0 to indicate that
+   no data has been read.
+ - *MUST* set `*buf` to a buffer containing the data read. It must be
+   safe for the caller to access the number of bytes read returned as
+   return code.
+ - *MAY* fill out `*addr` with the netlink address of the peer the
+   data has been received from.
+ - *MAY* set `*cred` to a newly allocated struct ucred containg
+   credentials.
+
+.Overwriting nl_send()
+
+See <<core_send, Sending Netlink Messages>> for more information on
+how and when nl_send() is called internally.
+
+[source,c]
+--------
+#include <netlink/handlers.h>
+
+void nl_cb_overwrite_send(struct nl_cb *cb, int (*func)(struct nl_sock *sk,
+                                                        struct nl_msg *msg));
+--------
+
+Own implementations must send the netlink message and return 0 on success
+or a negative error code.
+
+[[core_cache]]
+== Cache System
+
+=== Allocation of Caches
+
+Almost all subsystem provide a function to allocate a new cache
+of some form. The function usually looks like this:
+[source,c]
+--------
+struct nl_cache *<object name>_alloc_cache(struct nl_sock *sk);
+--------
+
+These functions allocate a new cache for the own object type,
+initializes it properly and updates it to represent the current
+state of their master, e.g. a link cache would include all
+links currently configured in the kernel.
+
+Some of the allocation functions may take additional arguments
+to further specify what will be part of the cache.
+
+All such functions return a newly allocated cache or NULL
+in case of an error.
+
+=== Cache Manager
+
+The purpose of a cache manager is to keep track of caches and
+automatically receive event notifications to keep the caches
+up to date with the kernel state. Each manager has exactly one
+netlink socket assigned which limits the scope of each manager
+to exactly one netlink family. Therefore all caches committed
+to a manager must be part of the same netlink family. Due to the
+nature of a manager, it is not possible to have a cache maintain
+two instances of the same cache type. The socket is subscribed
+to the event notification group of each cache and also put into
+non-blocking mode. Functions exist to poll() on the socket to
+wait for new events to be received.
+
+
+----
+ App       libnl                        Kernel
+        |                            |
+            +-----------------+        [ notification, link change ]
+        |   |  Cache Manager  |      | [   (IFF_UP | IFF_RUNNING)  ]
+            |                 |                |
+        |   |   +------------+|      |         |  [ notification, new addr ]
+    <-------|---| route/link |<-------(async)--+  [  10.0.1.1/32 dev eth1  ]
+        |   |   +------------+|      |                      |
+            |   +------------+|                             |
+    <---|---|---| route/addr |<------|-(async)--------------+
+            |   +------------+|
+        |   |   +------------+|      |
+    <-------|---| ...        ||
+        |   |   +------------+|      |
+            +-----------------+
+        |                            |
+----
+
+.Creating a new cache manager
+
+[source,c]
+----
+struct nl_cache_mngr *mngr;
+
+// Allocate a new cache manager for RTNETLINK and automatically
+// provide the caches added to the manager.
+err = nl_cache_mngr_alloc(NULL, NETLINK_ROUTE, NL_AUTO_PROVIDE, &mngr);
+----
+
+.Keep track of a cache
+
+[source,c]
+----
+struct nl_cache *cache;
+
+// Create a new cache for links/interfaces and ask the manager to
+// keep it up to date for us. This will trigger a full dump request
+// to initially fill the cache.
+cache = nl_cache_mngr_add(mngr, "route/link");
+-----
+
+.Make the manager receive updates
+
+[source,c]
+----
+// Give the manager the ability to receive updates, will call poll()
+// with a timeout of 5 seconds.
+if (nl_cache_mngr_poll(mngr, 5000) > 0) {
+        // Manager received at least one update, dump cache?
+        nl_cache_dump(cache, ...);
+}
+----
+
+.Release cache manager
+
+[source,c]
+----
+nl_cache_mngr_free(mngr);
+----
+
+== Abstract Data Types
+
+A few high level abstract data types which are used by a majority netlink
+protocols are implemented in the core library. More may be added in the
+future if the need arises.
+
+=== Abstract Address
+
+Most netlink protocols deal with networking related topics and thus
+dealing with network addresses is a common task.
+
+Currently the following address families are supported:
+
+[options="compact"]
+ * `AF_INET`
+ * `AF_INET6`
+ * `AF_LLC`
+ * `AF_DECnet`
+ * `AF_UNSPEC`
+
+[[core_addr_alloc]]
+.Address Allocation
+
+The function nl_addr_alloc() allocates a new empty address. The
++maxsize+ argument defines the maximum length of an address in bytes.
+The size of an address is address family specific. If the address
+family and address data are known at allocation time the function
+nl_addr_build() can be used alternatively. You may also clone
+an address by calling nl_addr_clone()
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+struct nl_addr *nl_addr_alloc(size_t maxsize);
+struct nl_addr *nl_addr_clone(struct nl_addr *addr);
+struct nl_addr *nl_addr_build(int family, void *addr, size_t size);
+--------
+
+If the address is transported in a netlink attribute, the function
+nl_addr_alloc_attr() allocates a new address based on the payload
+of the attribute provided. The +family+ argument is used to specify
+the address family of the address, set to +AF_UNSPEC+ if unknown.
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+struct nl_addr *nl_addr_alloc_attr(struct nlattr *attr, int family);
+--------
+
+If the address is provided by a user, it is usually stored in a human
+readable format. The function nl_addr_parse() parses a character
+string representing an address and allocates a new address based on
+it.
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+int nl_addr_parse(const char *addr, int hint, struct nl_addr **result);
+--------
+
+If parsing succeeds the function returns 0 and the allocated address
+is stored in +*result+.
+
+NOTE: Make sure to return the reference to an address using
+      `nl_addr_put()` after usage to allow memory being freed.
+
+.Example: Transform character string to abstract address
+[source,c]
+-----
+struct nl_addr *a = nl_addr_parse("::1", AF_UNSPEC);
+printf("Address family: %s\n", nl_af2str(nl_addr_get_family(a)));
+nl_addr_put(a);
+a = nl_addr_parse("11:22:33:44:55:66", AF_UNSPEC);
+printf("Address family: %s\n", nl_af2str(nl_addr_get_family(a)));
+nl_addr_put(a);
+-----
+
+.Address References
+
+Abstract addresses use reference counting to account for all users of
+a particular address. After the last user has returned the reference
+the address is freed.
+
+If you pass on a address object to another function and you are not
+sure how long it will be used, make sure to call nl_addr_get() to
+acquire an additional reference and have that function or code path
+call nl_addr_put() as soon as it has finished using the address.
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+struct nl_addr *nl_addr_get(struct nl_addr *addr);
+void nl_addr_put(struct nl_addr *addr);
+int nl_addr_shared(struct nl_addr *addr);
+--------
+
+You may call nl_addr_shared() at any time to check if you are the only
+user of an address.
+
+.Address Attributes
+
+The address is usually set at allocation time. If it was unknown at that
+time it can be specified later by calling nl_addr_set_family() and is
+accessed with the function nl_addr_get_family().
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+void nl_addr_set_family(struct nl_addr *addr, int family);
+int nl_addr_get_family(struct nl_addr *addr);
+--------
+
+The same is true for the actual address data. It is typically present
+at allocation time. For exceptions it can be specified later or
+overwritten with the function `nl_addr_set_binary_addr()`. Beware that
+the length of the address may not exceed `maxlen` specified at
+allocation time. The address data is returned by the function
+`nl_addr_get_binary_addr()` and its length by the function
+`nl_addr_get_len()`.
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+int nl_addr_set_binary_addr(struct nl_addr *addr, void *data, size_t size);
+void *nl_addr_get_binary_addr(struct nl_addr *addr);
+unsigned int nl_addr_get_len(struct nl_addr *addr);
+--------
+
+If you only want to check if the address data consists of all zeros
+the function `nl_addr_iszero()` is a shortcut to that.
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+int nl_addr_iszero(struct nl_addr *addr);
+--------
+
+==== Address Prefix Length
+
+Although this functionality is somewhat specific to routing it has
+been implemented here. Addresses can have a prefix length assigned
+which implies that only the first n bits are of importance. This is
+f.e. used to implement subnets.
+
+Use set functions `nl_addr_set_prefixlen()` and
+`nl_addr_get_prefixlen()` to work with prefix lengths.
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+void nl_addr_set_prefixlen(struct nl_addr *addr, int n);
+unsigned int nl_addr_get_prefixlen(struct nl_addr *addr);
+--------
+
+NOTE: The default prefix length is set to (address length * 8)
+
+.Address Helpers
+
+Several functions exist to help when dealing with addresses. The
+function `nl_addr_cmp()` compares two addresses and returns an integer
+less than, equal to or greater than zero without considering the
+prefix length at all. If you want to consider the prefix length, use
+the function `nl_addr_cmp_prefix()`.
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+int nl_addr_cmp(struct nl_addr *addr, struct nl_addr *addr);
+int nl_addr_cmp_prefix(struct nl_addr *addr, struct nl_addr *addr);
+--------
+
+If an abstract address needs to presented to the user it should be
+done in a human readable format which differs depending on the address
+family. The function `nl_addr2str()` takes care of this by calling the
+appropriate conversion functions internaly. It expects a `buf` of
+length `size` to write the character string into and returns a pointer
+to `buf` for easy `printf()` usage.
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+char *nl_addr2str(struct nl_addr *addr, char *buf, size_t size);
+--------
+
+If the address family is unknown, the address data will be printed in
+hexadecimal format `AA:BB:CC:DD:...`
+
+Often the only way to figure out the address family is by looking at
+the length of the address. The function `nl_addr_guess_family()` does
+just this and returns the address family guessed based on the address
+size.
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+int nl_addr_guess_family(struct nl_addr *addr);
+--------
+
+Before allocating an address you may want to check if the character
+string actually represents a valid address of the address family you
+are expecting. The function `nl_addr_valid()` can be used for that, it
+returns 1 if the supplised `addr` is a valid address in the context of
+`family`.  See `inet_pton(3)`, `dnet_pton(3)` for more information on
+valid adddress formats.
+
+[source,c]
+--------
+#include <netlink/addr.h>
+
+int nl_addr_valid(char *addr, int family);
+--------
+
+=== Abstract Data
+
+The abstract data type is a trivial datatype with the primary purpose
+to simplify usage of netlink attributes of arbitary length.
+
+[[core_data_alloc]]
+.Allocation of a Data Object
+The function `nl_data_alloc()` alloctes a new abstract data object and
+fill it with the provided data. `nl_data_alloc_attr()` does the same
+but bases the data on the payload of a netlink attribute. New data
+objects can also be allocated by cloning existing ones by using
+`nl_data_clone()`.
+
+[source,c]
+--------
+struct nl_data *nl_data_alloc(void *buf, size_t size);
+struct nl_data *nl_data_alloc_attr(struct nlattr *attr);
+struct nl_data *nl_data_clone(struct nl_data *data);
+void nl_data_free(struct nl_data *data);
+--------
+
+.Access to Data
+
+The function `nl_data_get()` returns a pointer to the data, the size
+of data is returned by `nl_data_get_size()`.
+
+[source,c]
+--------
+void *nl_data_get(struct nl_data *data);
+size_t nl_data_get_size(struct nl_data *data);
+--------
+
+.Data Helpers
+
+The function nl_data_append() reallocates the internal data buffers
+and appends the specified `buf` to the existing data.
+
+[source,c]
+--------
+int nl_data_append(struct nl_data *data, void *buf, size_t size);
+--------
+
+CAUTION: Any call to `nl_data_append()` invalidates all pointers
+         returned by `nl_data_get()` of the same data object.
+
+[source,c]
+--------
+int nl_data_cmp(struct nl_data *data, struct nl_data *data);
+--------
diff --git a/doc/doxygen-link.py b/doc/doxygen-link.py
new file mode 100755
index 0000000..910b8f8
--- /dev/null
+++ b/doc/doxygen-link.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import fileinput
+import re
+import sys
+
+
+rc_script = re.compile(r'\s*(.*\S)?\s*')
+
+def parse_dict(filename):
+	links = {}
+	for line in open(filename, 'r'):
+		m = re.match('^([^=]+)=([^\n]+)$', line);
+		if not m:
+			continue
+		name = m.group(1)
+		value = m.group(2)
+
+		# strip leading and trailing whitespace
+		m = rc_script.match(name)
+		if m:
+			name = m.group(1)
+
+		# skip special names
+		if name == '':
+			continue
+		if name == '\\':
+			continue
+
+		links[name] = "<a href=\"" + value + "\" class=\"dg\">" + name + "</a>"
+	return links
+
+links = parse_dict(sys.argv[1])
+
+def translate(match):
+	return links[match.group(1)]
+
+# match for all names, with word boundaries \b
+rc = re.compile(r'\b(' + '|'.join(map(re.escape, sorted(links, reverse=True))) + r')\b')
+
+for line in open(sys.argv[2], 'r'):
+	print(rc.sub(translate, line), end='')
diff --git a/doc/gen-tags.sh b/doc/gen-tags.sh
new file mode 100755
index 0000000..862ec09
--- /dev/null
+++ b/doc/gen-tags.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+#
+# Based on a script found on the englinemtn-devel mailinglist
+# written by Carsten Haitzler <ras...@rasterman.com>
+#
+
+for f in api/group__*.html
+do
+	bf=$(basename $f)
+
+	grep -oE "href=\"$bf#[a-z0-9]+\">[^<]+</a>" $f |
+		sed 's/href="\([^"]*\)">\([^<]*\)<\/a>/\2=api\/\1/'
+done
diff --git a/doc/html/.gitignore b/doc/html/.gitignore
deleted file mode 100644
index 72e8ffc..0000000
--- a/doc/html/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*
diff --git a/doc/images/.gitignore b/doc/images/.gitignore
new file mode 100644
index 0000000..efcc7e2
--- /dev/null
+++ b/doc/images/.gitignore
@@ -0,0 +1,3 @@
+core__*
+asciidoc__*.png
+*.odg
diff --git a/doc/images/addressing.png b/doc/images/addressing.png
new file mode 100644
index 0000000..9dcaaff
--- /dev/null
+++ b/doc/images/addressing.png
Binary files differ
diff --git a/doc/images/attribute_hdr.png b/doc/images/attribute_hdr.png
new file mode 100644
index 0000000..0e6cfda
--- /dev/null
+++ b/doc/images/attribute_hdr.png
Binary files differ
diff --git a/doc/images/classful_qdisc.png b/doc/images/classful_qdisc.png
new file mode 100644
index 0000000..7e77350
--- /dev/null
+++ b/doc/images/classful_qdisc.png
Binary files differ
diff --git a/doc/images/classless_qdisc.png b/doc/images/classless_qdisc.png
new file mode 100644
index 0000000..bcf2c1c
--- /dev/null
+++ b/doc/images/classless_qdisc.png
Binary files differ
diff --git a/doc/images/classless_qdisc_nbands.png b/doc/images/classless_qdisc_nbands.png
new file mode 100644
index 0000000..14cb026
--- /dev/null
+++ b/doc/images/classless_qdisc_nbands.png
Binary files differ
diff --git a/doc/images/icons/README b/doc/images/icons/README
new file mode 100644
index 0000000..f12b2a7
--- /dev/null
+++ b/doc/images/icons/README
@@ -0,0 +1,5 @@
+Replaced the plain DocBook XSL admonition icons with Jimmac's DocBook
+icons (http://jimmac.musichall.cz/ikony.php3). I dropped transparency
+from the Jimmac icons to get round MS IE and FOP PNG incompatibilies.
+
+Stuart Rackham
diff --git a/doc/images/icons/callouts/1.png b/doc/images/icons/callouts/1.png
new file mode 100644
index 0000000..7d47343
--- /dev/null
+++ b/doc/images/icons/callouts/1.png
Binary files differ
diff --git a/doc/images/icons/callouts/10.png b/doc/images/icons/callouts/10.png
new file mode 100644
index 0000000..997bbc8
--- /dev/null
+++ b/doc/images/icons/callouts/10.png
Binary files differ
diff --git a/doc/images/icons/callouts/11.png b/doc/images/icons/callouts/11.png
new file mode 100644
index 0000000..ce47dac
--- /dev/null
+++ b/doc/images/icons/callouts/11.png
Binary files differ
diff --git a/doc/images/icons/callouts/12.png b/doc/images/icons/callouts/12.png
new file mode 100644
index 0000000..31daf4e
--- /dev/null
+++ b/doc/images/icons/callouts/12.png
Binary files differ
diff --git a/doc/images/icons/callouts/13.png b/doc/images/icons/callouts/13.png
new file mode 100644
index 0000000..14021a8
--- /dev/null
+++ b/doc/images/icons/callouts/13.png
Binary files differ
diff --git a/doc/images/icons/callouts/14.png b/doc/images/icons/callouts/14.png
new file mode 100644
index 0000000..64014b7
--- /dev/null
+++ b/doc/images/icons/callouts/14.png
Binary files differ
diff --git a/doc/images/icons/callouts/15.png b/doc/images/icons/callouts/15.png
new file mode 100644
index 0000000..0d65765
--- /dev/null
+++ b/doc/images/icons/callouts/15.png
Binary files differ
diff --git a/doc/images/icons/callouts/2.png b/doc/images/icons/callouts/2.png
new file mode 100644
index 0000000..5d09341
--- /dev/null
+++ b/doc/images/icons/callouts/2.png
Binary files differ
diff --git a/doc/images/icons/callouts/3.png b/doc/images/icons/callouts/3.png
new file mode 100644
index 0000000..ef7b700
--- /dev/null
+++ b/doc/images/icons/callouts/3.png
Binary files differ
diff --git a/doc/images/icons/callouts/4.png b/doc/images/icons/callouts/4.png
new file mode 100644
index 0000000..adb8364
--- /dev/null
+++ b/doc/images/icons/callouts/4.png
Binary files differ
diff --git a/doc/images/icons/callouts/5.png b/doc/images/icons/callouts/5.png
new file mode 100644
index 0000000..4d7eb46
--- /dev/null
+++ b/doc/images/icons/callouts/5.png
Binary files differ
diff --git a/doc/images/icons/callouts/6.png b/doc/images/icons/callouts/6.png
new file mode 100644
index 0000000..0ba694a
--- /dev/null
+++ b/doc/images/icons/callouts/6.png
Binary files differ
diff --git a/doc/images/icons/callouts/7.png b/doc/images/icons/callouts/7.png
new file mode 100644
index 0000000..472e96f
--- /dev/null
+++ b/doc/images/icons/callouts/7.png
Binary files differ
diff --git a/doc/images/icons/callouts/8.png b/doc/images/icons/callouts/8.png
new file mode 100644
index 0000000..5e60973
--- /dev/null
+++ b/doc/images/icons/callouts/8.png
Binary files differ
diff --git a/doc/images/icons/callouts/9.png b/doc/images/icons/callouts/9.png
new file mode 100644
index 0000000..a0676d2
--- /dev/null
+++ b/doc/images/icons/callouts/9.png
Binary files differ
diff --git a/doc/images/icons/caution.png b/doc/images/icons/caution.png
new file mode 100644
index 0000000..9a8c515
--- /dev/null
+++ b/doc/images/icons/caution.png
Binary files differ
diff --git a/doc/images/icons/example.png b/doc/images/icons/example.png
new file mode 100644
index 0000000..1199e86
--- /dev/null
+++ b/doc/images/icons/example.png
Binary files differ
diff --git a/doc/images/icons/home.png b/doc/images/icons/home.png
new file mode 100644
index 0000000..37a5231
--- /dev/null
+++ b/doc/images/icons/home.png
Binary files differ
diff --git a/doc/images/icons/important.png b/doc/images/icons/important.png
new file mode 100644
index 0000000..be685cc
--- /dev/null
+++ b/doc/images/icons/important.png
Binary files differ
diff --git a/doc/images/icons/next.png b/doc/images/icons/next.png
new file mode 100644
index 0000000..64e126b
--- /dev/null
+++ b/doc/images/icons/next.png
Binary files differ
diff --git a/doc/images/icons/note.png b/doc/images/icons/note.png
new file mode 100644
index 0000000..7c1f3e2
--- /dev/null
+++ b/doc/images/icons/note.png
Binary files differ
diff --git a/doc/images/icons/prev.png b/doc/images/icons/prev.png
new file mode 100644
index 0000000..3e8f12f
--- /dev/null
+++ b/doc/images/icons/prev.png
Binary files differ
diff --git a/doc/images/icons/tip.png b/doc/images/icons/tip.png
new file mode 100644
index 0000000..f087c73
--- /dev/null
+++ b/doc/images/icons/tip.png
Binary files differ
diff --git a/doc/images/icons/up.png b/doc/images/icons/up.png
new file mode 100644
index 0000000..2db1ce6
--- /dev/null
+++ b/doc/images/icons/up.png
Binary files differ
diff --git a/doc/images/icons/warning.png b/doc/images/icons/warning.png
new file mode 100644
index 0000000..d41edb9
--- /dev/null
+++ b/doc/images/icons/warning.png
Binary files differ
diff --git a/doc/images/ifinfomsg.png b/doc/images/ifinfomsg.png
new file mode 100644
index 0000000..fb94cf7
--- /dev/null
+++ b/doc/images/ifinfomsg.png
Binary files differ
diff --git a/doc/images/library_overview.png b/doc/images/library_overview.png
new file mode 100644
index 0000000..dd9d5fe
--- /dev/null
+++ b/doc/images/library_overview.png
Binary files differ
diff --git a/doc/images/nlmsgerr.png b/doc/images/nlmsgerr.png
new file mode 100644
index 0000000..58e53d5
--- /dev/null
+++ b/doc/images/nlmsgerr.png
Binary files differ
diff --git a/doc/images/nlmsghdr.png b/doc/images/nlmsghdr.png
new file mode 100644
index 0000000..dd39b9c
--- /dev/null
+++ b/doc/images/nlmsghdr.png
Binary files differ
diff --git a/doc/images/qdisc_default.png b/doc/images/qdisc_default.png
new file mode 100644
index 0000000..a7ba167
--- /dev/null
+++ b/doc/images/qdisc_default.png
Binary files differ
diff --git a/doc/images/qdisc_mq.png b/doc/images/qdisc_mq.png
new file mode 100644
index 0000000..c6318b2
--- /dev/null
+++ b/doc/images/qdisc_mq.png
Binary files differ
diff --git a/doc/images/tc_obj.png b/doc/images/tc_obj.png
new file mode 100644
index 0000000..bfc8145
--- /dev/null
+++ b/doc/images/tc_obj.png
Binary files differ
diff --git a/doc/images/tc_overview.png b/doc/images/tc_overview.png
new file mode 100644
index 0000000..ce23e67
--- /dev/null
+++ b/doc/images/tc_overview.png
Binary files differ
diff --git a/doc/index.txt b/doc/index.txt
new file mode 100644
index 0000000..c207b44
--- /dev/null
+++ b/doc/index.txt
@@ -0,0 +1,22 @@
+Documentation Overview - libnl Suite
+====================================
+
+== Libraries
+
+image:library_overview.png["Library Hierarchy"]
+
+link:core.html[Netlink Library] (libnl)::
+Socket handling, sending and receiving, message construction and parsing, ...
+
+link:route.html[Routing Family Library] (libnl-route)::
+Adresses, links, neighbours, routing, traffic control, neighbour tables, ...
+
+Netfilter Library (libnl-nf)::
+Connection tracking, logging, queueing
+
+Generic Netlink Library (libnl-genl)::
+Controller API, family and command registration
+
+== Python Packages
+ - netlink.core
+ - netlink.route.link
diff --git a/doc/libnl.css b/doc/libnl.css
index 22c4843..8589450 100644
--- a/doc/libnl.css
+++ b/doc/libnl.css
@@ -1,473 +1,1156 @@
-BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV {
-	font-family: Geneva, Arial, Helvetica, sans-serif;
+/* The standard CSS for doxygen */
+
+body, table, div, p, dl {
+	font-family: Lucida Grande, Verdana, Geneva, Arial, sans-serif;
+	font-size: 13px;
+	line-height: 1.3;
 }
-BODY,TD {
-	font-size: 90%;
+
+/* @group Heading Levels */
+
+h1 {
+	font-size: 150%;
 }
-H1 {
-	text-align: center;
-	font-size: 160%;
+
+.title {
+	font-size: 150%;
+	font-weight: bold;
+	margin: 10px 2px;
 }
-H2 {
+
+h2 {
 	font-size: 120%;
 }
-H3 {
+
+h3 {
 	font-size: 100%;
 }
-CAPTION { 
-	font-weight: bold 
+
+h1, h2, h3, h4, h5, h6 {
+	-webkit-transition: text-shadow 0.5s linear;
+	-moz-transition: text-shadow 0.5s linear;
+	-ms-transition: text-shadow 0.5s linear;
+	-o-transition: text-shadow 0.5s linear;
+	transition: text-shadow 0.5s linear;
+	margin-right: 15px;
 }
-DIV.qindex {
-	width: 100%;
-	background-color: #e8eef2;
-	border: 1px solid #84b0c7;
-	text-align: center;
-	margin: 2px;
-	padding: 2px;
-	line-height: 140%;
+
+h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow {
+	text-shadow: 0 0 15px cyan;
 }
-DIV.navpath {
-	width: 100%;
-	background-color: #e8eef2;
-	border: 1px solid #84b0c7;
-	text-align: center;
-	margin: 2px;
-	padding: 2px;
-	line-height: 140%;
-}
-DIV.navtab {
-       background-color: #e8eef2;
-       border: 1px solid #84b0c7;
-       text-align: center;
-       margin: 2px;
-       margin-right: 15px;
-       padding: 2px;
-}
-TD.navtab {
-       font-size: 70%;
-}
-A.qindex {
-       text-decoration: none;
-       font-weight: bold;
-       color: #1A419D;
-}
-A.qindex:visited {
-       text-decoration: none;
-       font-weight: bold;
-       color: #1A419D
-}
-A.qindex:hover {
-	text-decoration: none;
-	background-color: #ddddff;
-}
-A.qindexHL {
-	text-decoration: none;
+
+dt {
 	font-weight: bold;
-	background-color: #6666cc;
-	color: #ffffff;
-	border: 1px double #9295C2;
 }
-A.qindexHL:hover {
-	text-decoration: none;
-	background-color: #6666cc;
-	color: #ffffff;
+
+div.multicol {
+	-moz-column-gap: 1em;
+	-webkit-column-gap: 1em;
+	-moz-column-count: 3;
+	-webkit-column-count: 3;
 }
-A.qindexHL:visited { 
-	text-decoration: none; 
-	background-color: #6666cc; 
-	color: #ffffff 
+
+p.startli, p.startdd, p.starttd {
+	margin-top: 2px;
 }
-A.el { 
-	text-decoration: none; 
-	font-weight: bold 
+
+p.endli {
+	margin-bottom: 0px;
 }
-A.elRef { 
-	font-weight: bold 
-}
-A.code:link { 
-	text-decoration: none; 
-	font-weight: normal; 
-	color: #0000FF
-}
-A.code:visited { 
-	text-decoration: none; 
-	font-weight: normal; 
-	color: #0000FF
-}
-A.codeRef:link { 
-	font-weight: normal; 
-	color: #0000FF
-}
-A.codeRef:visited { 
-	font-weight: normal; 
-	color: #0000FF
-}
-A:hover { 
-	text-decoration: none; 	
-	background-color: #f2f2ff 
-}
-DL.el { 
-	margin-left: -1cm 
-}
-.fragment {
-       font-family: monospace, fixed;
-       font-size: 95%;
-}
-PRE.fragment {
-	border: 1px solid #CCCCCC;
-	background-color: #f5f5f5;
-	margin-top: 4px;
+
+p.enddd {
 	margin-bottom: 4px;
-	margin-left: 2px;
-	margin-right: 8px;
-	padding-left: 6px;
-	padding-right: 6px;
-	padding-top: 4px;
-	padding-bottom: 4px;
-}
-DIV.ah { 
-	background-color: black; 
-	font-weight: bold; 
-	color: #ffffff; 
-	margin-bottom: 3px; 
-	margin-top: 3px 
 }
 
-DIV.groupHeader {
-       margin-left: 16px;
-       margin-top: 12px;
-       margin-bottom: 6px;
-       font-weight: bold;
+p.endtd {
+	margin-bottom: 2px;
 }
-DIV.groupText { 
-	margin-left: 16px; 
-	font-style: italic; 
-	font-size: 90% 
-}
-BODY {
-	background: white;
-	color: black;
-	margin-right: 20px;
-	margin-left: 20px;
-}
-TD.indexkey {
-	background-color: #e8eef2;
+
+/* @end */
+
+caption {
 	font-weight: bold;
-	padding-right  : 10px;
-	padding-top    : 2px;
-	padding-left   : 10px;
-	padding-bottom : 2px;
-	margin-left    : 0px;
-	margin-right   : 0px;
-	margin-top     : 2px;
-	margin-bottom  : 2px;
-	border: 1px solid #CCCCCC;
 }
-TD.indexvalue {
-	background-color: #e8eef2;
-	font-style: italic;
-	padding-right  : 10px;
-	padding-top    : 2px;
-	padding-left   : 10px;
-	padding-bottom : 2px;
-	margin-left    : 0px;
-	margin-right   : 0px;
-	margin-top     : 2px;
-	margin-bottom  : 2px;
-	border: 1px solid #CCCCCC;
-}
-TR.memlist {
-	background-color: #f0f0f0; 
-}
-P.formulaDsp { 
-	text-align: center; 
-}
-IMG.formulaDsp {
-}
-IMG.formulaInl { 
-	vertical-align: middle; 
-}
-SPAN.keyword       { color: #008000 }
-SPAN.keywordtype   { color: #604020 }
-SPAN.keywordflow   { color: #e08000 }
-SPAN.comment       { color: #800000 }
-SPAN.preprocessor  { color: #806020 }
-SPAN.stringliteral { color: #002080 }
-SPAN.charliteral   { color: #008080 }
-SPAN.vhdldigit     { color: #ff00ff }
-SPAN.vhdlchar      { color: #000000 }
-SPAN.vhdlkeyword   { color: #700070 }
-SPAN.vhdllogic     { color: #ff0000 }
 
-.mdescLeft {
-	padding: 0px 8px 4px 8px;
-	font-size: 80%;
-	font-style: italic;
-	background-color: #FAFAFA;
-	border-top: 1px none #E0E0E0;
-	border-right: 1px none #E0E0E0;
-	border-bottom: 1px none #E0E0E0;
-	border-left: 1px none #E0E0E0;
+span.legend {
+        font-size: 70%;
+        text-align: center;
+}
+
+h3.version {
+        font-size: 90%;
+        text-align: center;
+}
+
+div.qindex, div.navtab{
+	background-color: #EBEFF6;
+	border: 1px solid #A3B4D7;
+	text-align: center;
+}
+
+div.qindex, div.navpath {
+	width: 100%;
+	line-height: 140%;
+}
+
+div.navtab {
+	margin-right: 15px;
+}
+
+/* @group Link Styling */
+
+a {
+	color: #3D578C;
+	font-weight: normal;
+	text-decoration: none;
+}
+
+.contents a:visited {
+	color: #4665A2;
+}
+
+a:hover {
+	text-decoration: underline;
+}
+
+a.qindex {
+	font-weight: bold;
+}
+
+a.qindexHL {
+	font-weight: bold;
+	background-color: #9CAFD4;
+	color: #ffffff;
+	border: 1px double #869DCA;
+}
+
+.contents a.qindexHL:visited {
+        color: #ffffff;
+}
+
+a.el {
+	font-weight: bold;
+}
+
+a.elRef {
+}
+
+a.code, a.code:visited {
+	color: #4665A2; 
+}
+
+a.codeRef, a.codeRef:visited {
+	color: #4665A2; 
+}
+
+/* @end */
+
+dl.el {
+	margin-left: -1cm;
+}
+
+pre.fragment {
+        border: 1px solid #C4CFE5;
+        background-color: #FBFCFD;
+        padding: 4px 6px;
+        margin: 4px 8px 4px 2px;
+        overflow: auto;
+        word-wrap: break-word;
+        font-size:  9pt;
+        line-height: 125%;
+        font-family: monospace, fixed;
+        font-size: 105%;
+}
+
+div.fragment {
+        padding: 4px;
+        margin: 4px;
+	background-color: #FBFCFD;
+	border: 1px solid #C4CFE5;
+}
+
+div.line {
+	font-family: monospace, fixed;
+        font-size: 13px;
+	min-height: 13px;
+	line-height: 1.0;
+	text-wrap: unrestricted;
+	white-space: -moz-pre-wrap; /* Moz */
+	white-space: -pre-wrap;     /* Opera 4-6 */
+	white-space: -o-pre-wrap;   /* Opera 7 */
+	white-space: pre-wrap;      /* CSS3  */
+	word-wrap: break-word;      /* IE 5.5+ */
+	text-indent: -53px;
+	padding-left: 53px;
+	padding-bottom: 0px;
 	margin: 0px;
+	-webkit-transition-property: background-color, box-shadow;
+	-webkit-transition-duration: 0.5s;
+	-moz-transition-property: background-color, box-shadow;
+	-moz-transition-duration: 0.5s;
+	-ms-transition-property: background-color, box-shadow;
+	-ms-transition-duration: 0.5s;
+	-o-transition-property: background-color, box-shadow;
+	-o-transition-duration: 0.5s;
+	transition-property: background-color, box-shadow;
+	transition-duration: 0.5s;
 }
-.mdescRight {
-        padding: 0px 8px 4px 8px;
-	font-size: 80%;
+
+div.line.glow {
+	background-color: cyan;
+	box-shadow: 0 0 10px cyan;
+}
+
+
+span.lineno {
+	padding-right: 4px;
+	text-align: right;
+	border-right: 2px solid #0F0;
+	background-color: #E8E8E8;
+        white-space: pre;
+}
+span.lineno a {
+	background-color: #D8D8D8;
+}
+
+span.lineno a:hover {
+	background-color: #C8C8C8;
+}
+
+div.ah {
+	background-color: black;
+	font-weight: bold;
+	color: #ffffff;
+	margin-bottom: 3px;
+	margin-top: 3px;
+	padding: 0.2em;
+	border: solid thin #333;
+	border-radius: 0.5em;
+	-webkit-border-radius: .5em;
+	-moz-border-radius: .5em;
+	box-shadow: 2px 2px 3px #999;
+	-webkit-box-shadow: 2px 2px 3px #999;
+	-moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px;
+	background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#000),color-stop(0.3, #444));
+	background-image: -moz-linear-gradient(center top, #eee 0%, #444 40%, #000);
+}
+
+div.groupHeader {
+	margin-left: 16px;
+	margin-top: 12px;
+	font-weight: bold;
+}
+
+div.groupText {
+	margin-left: 16px;
 	font-style: italic;
-	background-color: #FAFAFA;
-	border-top: 1px none #E0E0E0;
-	border-right: 1px none #E0E0E0;
-	border-bottom: 1px none #E0E0E0;
-	border-left: 1px none #E0E0E0;
-	margin: 0px;
 }
-.memItemLeft {
-	padding: 1px 0px 0px 8px;
-	margin: 4px;
-	border-top-width: 1px;
-	border-right-width: 1px;
-	border-bottom-width: 1px;
-	border-left-width: 1px;
-	border-top-color: #E0E0E0;
-	border-right-color: #E0E0E0;
-	border-bottom-color: #E0E0E0;
-	border-left-color: #E0E0E0;
-	border-top-style: solid;
-	border-right-style: none;
-	border-bottom-style: none;
-	border-left-style: none;
-	background-color: #FAFAFA;
-	font-size: 80%;
+
+body {
+	background-color: white;
+	color: black;
+        margin: 0;
 }
-.memItemRight {
-	padding: 1px 8px 0px 8px;
-	margin: 4px;
-	border-top-width: 1px;
-	border-right-width: 1px;
-	border-bottom-width: 1px;
-	border-left-width: 1px;
-	border-top-color: #E0E0E0;
-	border-right-color: #E0E0E0;
-	border-bottom-color: #E0E0E0;
-	border-left-color: #E0E0E0;
-	border-top-style: solid;
-	border-right-style: none;
-	border-bottom-style: none;
-	border-left-style: none;
-	background-color: #FAFAFA;
-	font-size: 80%;
+
+div.contents {
+	margin-top: 10px;
+	margin-left: 12px;
+	margin-right: 8px;
 }
-.memTemplItemLeft {
-	padding: 1px 0px 0px 8px;
-	margin: 4px;
-	border-top-width: 1px;
-	border-right-width: 1px;
-	border-bottom-width: 1px;
-	border-left-width: 1px;
-	border-top-color: #E0E0E0;
-	border-right-color: #E0E0E0;
-	border-bottom-color: #E0E0E0;
-	border-left-color: #E0E0E0;
-	border-top-style: none;
-	border-right-style: none;
-	border-bottom-style: none;
-	border-left-style: none;
-	background-color: #FAFAFA;
-	font-size: 80%;
+
+td.indexkey {
+	background-color: #EBEFF6;
+	font-weight: bold;
+	border: 1px solid #C4CFE5;
+	margin: 2px 0px 2px 0;
+	padding: 2px 10px;
+        white-space: nowrap;
+        vertical-align: top;
 }
-.memTemplItemRight {
-	padding: 1px 8px 0px 8px;
-	margin: 4px;
-	border-top-width: 1px;
-	border-right-width: 1px;
-	border-bottom-width: 1px;
-	border-left-width: 1px;
-	border-top-color: #E0E0E0;
-	border-right-color: #E0E0E0;
-	border-bottom-color: #E0E0E0;
-	border-left-color: #E0E0E0;
-	border-top-style: none;
-	border-right-style: none;
-	border-bottom-style: none;
-	border-left-style: none;
-	background-color: #FAFAFA;
-	font-size: 80%;
+
+td.indexvalue {
+	background-color: #EBEFF6;
+	border: 1px solid #C4CFE5;
+	padding: 2px 10px;
+	margin: 2px 0px;
 }
-.memTemplParams {
-	padding: 1px 0px 0px 8px;
-	margin: 4px;
-	border-top-width: 1px;
-	border-right-width: 1px;
-	border-bottom-width: 1px;
-	border-left-width: 1px;
-	border-top-color: #E0E0E0;
-	border-right-color: #E0E0E0;
-	border-bottom-color: #E0E0E0;
-	border-left-color: #E0E0E0;
-	border-top-style: solid;
-	border-right-style: none;
-	border-bottom-style: none;
-	border-left-style: none;
-	color: #606060;
-	background-color: #FAFAFA;
-	font-size: 80%;
+
+tr.memlist {
+	background-color: #EEF1F7;
 }
-.search { 
+
+p.formulaDsp {
+	text-align: center;
+}
+
+img.formulaDsp {
+	
+}
+
+img.formulaInl {
+	vertical-align: middle;
+}
+
+div.center {
+	text-align: center;
+        margin-top: 0px;
+        margin-bottom: 0px;
+        padding: 0px;
+}
+
+div.center img {
+	border: 0px;
+}
+
+address.footer {
+	text-align: right;
+	padding-right: 12px;
+}
+
+img.footer {
+	border: 0px;
+	vertical-align: middle;
+}
+
+/* @group Code Colorization */
+
+span.keyword {
+	color: #008000
+}
+
+span.keywordtype {
+	color: #604020
+}
+
+span.keywordflow {
+	color: #e08000
+}
+
+span.comment {
+	color: #800000
+}
+
+span.preprocessor {
+	color: #806020
+}
+
+span.stringliteral {
+	color: #002080
+}
+
+span.charliteral {
+	color: #008080
+}
+
+span.vhdldigit { 
+	color: #ff00ff 
+}
+
+span.vhdlchar { 
+	color: #000000 
+}
+
+span.vhdlkeyword { 
+	color: #700070 
+}
+
+span.vhdllogic { 
+	color: #ff0000 
+}
+
+blockquote {
+        background-color: #F7F8FB;
+        border-left: 2px solid #9CAFD4;
+        margin: 0 24px 0 4px;
+        padding: 0 12px 0 16px;
+}
+
+/* @end */
+
+/*
+.search {
 	color: #003399;
 	font-weight: bold;
 }
-FORM.search {
+
+form.search {
 	margin-bottom: 0px;
 	margin-top: 0px;
 }
-INPUT.search { 
+
+input.search {
 	font-size: 75%;
 	color: #000080;
 	font-weight: normal;
 	background-color: #e8eef2;
 }
-TD.tiny { 
+*/
+
+td.tiny {
 	font-size: 75%;
 }
-a {
-	color: #1A41A8;
-}
-a:visited {
-	color: #2A3798;
-}
-.dirtab { 
+
+.dirtab {
 	padding: 4px;
 	border-collapse: collapse;
-	border: 1px solid #84b0c7;
-}
-TH.dirtab { 
-	background: #e8eef2;
-	font-weight: bold;
-}
-HR { 
-	height: 1px;
-	border: none;
-	border-top: 1px solid black;
+	border: 1px solid #A3B4D7;
 }
 
-/* Style for detailed member documentation */
+th.dirtab {
+	background: #EBEFF6;
+	font-weight: bold;
+}
+
+hr {
+	height: 0px;
+	border: none;
+	border-top: 1px solid #4A6AAA;
+}
+
+hr.footer {
+	height: 1px;
+}
+
+/* @group Member Descriptions */
+
+table.memberdecls {
+	border-spacing: 0px;
+	padding: 0px;
+}
+
+.memberdecls td {
+	-webkit-transition-property: background-color, box-shadow;
+	-webkit-transition-duration: 0.5s;
+	-moz-transition-property: background-color, box-shadow;
+	-moz-transition-duration: 0.5s;
+	-ms-transition-property: background-color, box-shadow;
+	-ms-transition-duration: 0.5s;
+	-o-transition-property: background-color, box-shadow;
+	-o-transition-duration: 0.5s;
+	transition-property: background-color, box-shadow;
+	transition-duration: 0.5s;
+}
+
+.memberdecls td.glow {
+	background-color: cyan;
+	box-shadow: 0 0 15px cyan;
+}
+
+.mdescLeft, .mdescRight,
+.memItemLeft, .memItemRight,
+.memTemplItemLeft, .memTemplItemRight, .memTemplParams {
+	background-color: #F9FAFC;
+	border: none;
+	margin: 4px;
+	padding: 1px 0 0 8px;
+}
+
+.mdescLeft, .mdescRight {
+	padding: 0px 8px 4px 8px;
+	color: #555;
+}
+
+.memItemLeft, .memItemRight, .memTemplParams {
+	border-top: 1px solid #C4CFE5;
+}
+
+.memItemLeft, .memTemplItemLeft {
+        white-space: nowrap;
+}
+
+.memItemRight {
+	width: 100%;
+}
+
+.memTemplParams {
+	color: #4665A2;
+        white-space: nowrap;
+}
+
+/* @end */
+
+/* @group Member Details */
+
+/* Styles for detailed member documentation */
+
 .memtemplate {
 	font-size: 80%;
-	color: #606060;
+	color: #4665A2;
 	font-weight: normal;
-	margin-left: 3px;
-} 
-.memnav { 
-	background-color: #e8eef2;
-	border: 1px solid #84b0c7;
+	margin-left: 9px;
+}
+
+.memnav {
+	background-color: #EBEFF6;
+	border: 1px solid #A3B4D7;
 	text-align: center;
 	margin: 2px;
 	margin-right: 15px;
 	padding: 2px;
 }
-.memitem {
-	padding: 4px;
-	background-color: #eef3f5;
-	border-width: 1px;
-	border-style: solid;
-	border-color: #dedeee;
-	-moz-border-radius: 8px 8px 8px 8px;
-}
-.memname {
-	white-space: nowrap;
-	font-weight: bold;
-}
-.memdoc{
-	padding-left: 10px;
-}
-.memproto {
-	background-color: #d5e1e8;
+
+.mempage {
 	width: 100%;
-	border-width: 1px;
-	border-style: solid;
-	border-color: #84b0c7;
-	font-weight: bold;
-	-moz-border-radius: 8px 8px 8px 8px;
 }
+
+.memitem {
+	padding: 0;
+	margin-bottom: 10px;
+	margin-right: 5px;
+        -webkit-transition: box-shadow 0.5s linear;
+        -moz-transition: box-shadow 0.5s linear;
+        -ms-transition: box-shadow 0.5s linear;
+        -o-transition: box-shadow 0.5s linear;
+        transition: box-shadow 0.5s linear;
+        display: table !important;
+        width: 100%;
+}
+
+.memitem.glow {
+         box-shadow: 0 0 15px cyan;
+}
+
+.memname {
+        font-weight: bold;
+        margin-left: 6px;
+}
+
+.memname td {
+	vertical-align: bottom;
+}
+
+.memproto, dl.reflist dt {
+        border-top: 1px solid #A8B8D9;
+        border-left: 1px solid #A8B8D9;
+        border-right: 1px solid #A8B8D9;
+        padding: 6px 0px 6px 0px;
+        color: #253555;
+        font-weight: bold;
+        text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9);
+        background-image:url('nav_f.png');
+        background-repeat:repeat-x;
+        background-color: #E2E8F2;
+        /* opera specific markup */
+        box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);
+        border-top-right-radius: 4px;
+        border-top-left-radius: 4px;
+        /* firefox specific markup */
+        -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px;
+        -moz-border-radius-topright: 4px;
+        -moz-border-radius-topleft: 4px;
+        /* webkit specific markup */
+        -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);
+        -webkit-border-top-right-radius: 4px;
+        -webkit-border-top-left-radius: 4px;
+
+}
+
+.memdoc, dl.reflist dd {
+        border-bottom: 1px solid #A8B8D9;      
+        border-left: 1px solid #A8B8D9;      
+        border-right: 1px solid #A8B8D9; 
+        padding: 6px 10px 2px 10px;
+        background-color: #FBFCFD;
+        border-top-width: 0;
+        background-image:url('nav_g.png');
+        background-repeat:repeat-x;
+        background-color: #FFFFFF;
+        /* opera specific markup */
+        border-bottom-left-radius: 4px;
+        border-bottom-right-radius: 4px;
+        box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);
+        /* firefox specific markup */
+        -moz-border-radius-bottomleft: 4px;
+        -moz-border-radius-bottomright: 4px;
+        -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px;
+        /* webkit specific markup */
+        -webkit-border-bottom-left-radius: 4px;
+        -webkit-border-bottom-right-radius: 4px;
+        -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);
+}
+
+dl.reflist dt {
+        padding: 5px;
+}
+
+dl.reflist dd {
+        margin: 0px 0px 10px 0px;
+        padding: 5px;
+}
+
 .paramkey {
 	text-align: right;
 }
+
 .paramtype {
 	white-space: nowrap;
 }
+
 .paramname {
 	color: #602020;
-	font-style: italic;
 	white-space: nowrap;
 }
-/* End Styling for detailed member documentation */
-
-/* for the tree view */
-.ftvtree {
-	font-family: sans-serif;
-	margin:0.5em;
+.paramname em {
+	font-style: normal;
 }
-/* these are for tree view when used as main index */
-.directory { 
-	font-size: 9pt; 
-	font-weight: bold; 
-}
-.directory h3 { 
-	margin: 0px; 
-	margin-top: 1em; 
-	font-size: 11pt; 
+.paramname code {
+        line-height: 14px;
 }
 
-/* The following two styles can be used to replace the root node title */
-/* with an image of your choice.  Simply uncomment the next two styles, */
-/* specify the name of your image and be sure to set 'height' to the */
-/* proper pixel height of your image. */
+.params, .retval, .exception, .tparams {
+        margin-left: 0px;
+        padding-left: 0px;
+}       
 
-/* .directory h3.swap { */
-/* 	height: 61px; */
-/* 	background-repeat: no-repeat; */
-/* 	background-image: url("yourimage.gif"); */
-/* } */
-/* .directory h3.swap span { */
-/* 	display: none; */
-/* } */
+.params .paramname, .retval .paramname {
+        font-weight: bold;
+        vertical-align: top;
+}
+        
+.params .paramtype {
+        font-style: italic;
+        vertical-align: top;
+}       
+        
+.params .paramdir {
+        font-family: "courier new",courier,monospace;
+        vertical-align: top;
+}
 
-.directory > h3 { 
-	margin-top: 0; 
+table.mlabels {
+	border-spacing: 0px;
 }
-.directory p { 
-	margin: 0px; 
-	white-space: nowrap; 
+
+td.mlabels-left {
+	width: 100%;
+	padding: 0px;
 }
-.directory div { 
-	display: none; 
-	margin: 0px; 
+
+td.mlabels-right {
+	vertical-align: bottom;
+	padding: 0px;
+	white-space: nowrap;
 }
-.directory img { 
-	vertical-align: -30%; 
+
+span.mlabels {
+        margin-left: 8px;
 }
+
+span.mlabel {
+        background-color: #728DC1;
+        border-top:1px solid #5373B4;
+        border-left:1px solid #5373B4;
+        border-right:1px solid #C4CFE5;
+        border-bottom:1px solid #C4CFE5;
+	text-shadow: none;
+        color: white;
+        margin-right: 4px;
+        padding: 2px 3px;
+        border-radius: 3px;
+        font-size: 7pt;
+	white-space: nowrap;
+}
+
+
+
+/* @end */
+
 /* these are for tree view when not used as main index */
-.directory-alt { 
-	font-size: 100%; 
-	font-weight: bold; 
+
+div.directory {
+        margin: 10px 0px;
+        border-top: 1px solid #A8B8D9;
+        border-bottom: 1px solid #A8B8D9;
+        width: 100%;
 }
-.directory-alt h3 { 
-	margin: 0px; 
-	margin-top: 1em; 
-	font-size: 11pt; 
+
+.directory table {
+        border-collapse:collapse;
 }
-.directory-alt > h3 { 
-	margin-top: 0; 
+
+.directory td {
+        margin: 0px;
+        padding: 0px;
+	vertical-align: top;
 }
-.directory-alt p { 
-	margin: 0px; 
-	white-space: nowrap; 
+
+.directory td.entry {
+        white-space: nowrap;
+        padding-right: 6px;
 }
-.directory-alt div { 
-	display: none; 
-	margin: 0px; 
+
+.directory td.entry a {
+        outline:none;
 }
-.directory-alt img { 
-	vertical-align: -30%; 
+
+.directory td.entry a img {
+        border: none;
+}
+
+.directory td.desc {
+        width: 100%;
+        padding-left: 6px;
+	padding-right: 6px;
+	border-left: 1px solid rgba(0,0,0,0.05);
+}
+
+.directory tr.even {
+	padding-left: 6px;
+	background-color: #F7F8FB;
+}
+
+.directory img {
+	vertical-align: -30%;
+}
+
+.directory .levels {
+        white-space: nowrap;
+        width: 100%;
+        text-align: right;
+        font-size: 9pt;
+}
+
+.directory .levels span {
+        cursor: pointer;
+        padding-left: 2px;
+        padding-right: 2px;
+	color: #3D578C;
+}
+
+div.dynheader {
+        margin-top: 8px;
+	-webkit-touch-callout: none;
+	-webkit-user-select: none;
+	-khtml-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	user-select: none;
+}
+
+address {
+	font-style: normal;
+	color: #2A3D61;
+}
+
+table.doxtable {
+	border-collapse:collapse;
+        margin-top: 4px;
+        margin-bottom: 4px;
+}
+
+table.doxtable td, table.doxtable th {
+	border: 1px solid #2D4068;
+	padding: 3px 7px 2px;
+}
+
+table.doxtable th {
+	background-color: #374F7F;
+	color: #FFFFFF;
+	font-size: 110%;
+	padding-bottom: 4px;
+	padding-top: 5px;
+}
+
+table.fieldtable {
+        width: 100%;
+        margin-bottom: 10px;
+        border: 1px solid #A8B8D9;
+        border-spacing: 0px;
+        -moz-border-radius: 4px;
+        -webkit-border-radius: 4px;
+        border-radius: 4px;
+        -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px;
+        -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15);
+        box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15);
+}
+
+.fieldtable td, .fieldtable th {
+        padding: 3px 7px 2px;
+}
+
+.fieldtable td.fieldtype, .fieldtable td.fieldname {
+        white-space: nowrap;
+        border-right: 1px solid #A8B8D9;
+        border-bottom: 1px solid #A8B8D9;
+        vertical-align: top;
+}
+
+.fieldtable td.fielddoc {
+        border-bottom: 1px solid #A8B8D9;
+        width: 100%;
+}
+
+.fieldtable tr:last-child td {
+        border-bottom: none;
+}
+
+.fieldtable th {
+        background-image:url('nav_f.png');
+        background-repeat:repeat-x;
+        background-color: #E2E8F2;
+        font-size: 90%;
+        color: #253555;
+        padding-bottom: 4px;
+        padding-top: 5px;
+        text-align:left;
+        -moz-border-radius-topleft: 4px;
+        -moz-border-radius-topright: 4px;
+        -webkit-border-top-left-radius: 4px;
+        -webkit-border-top-right-radius: 4px;
+        border-top-left-radius: 4px;
+        border-top-right-radius: 4px;
+        border-bottom: 1px solid #A8B8D9;
+}
+
+
+.tabsearch {
+	top: 0px;
+	left: 10px;
+	height: 36px;
+	background-image: url('tab_b.png');
+	z-index: 101;
+	overflow: hidden;
+	font-size: 13px;
+}
+
+.navpath ul
+{
+	font-size: 11px;
+	background-image:url('tab_b.png');
+	background-repeat:repeat-x;
+	height:30px;
+	line-height:30px;
+	color:#8AA0CC;
+	border:solid 1px #C2CDE4;
+	overflow:hidden;
+	margin:0px;
+	padding:0px;
+}
+
+.navpath li
+{
+	list-style-type:none;
+	float:left;
+	padding-left:10px;
+	padding-right:15px;
+	background-image:url('bc_s.png');
+	background-repeat:no-repeat;
+	background-position:right;
+	color:#364D7C;
+}
+
+.navpath li.navelem a
+{
+	height:32px;
+	display:block;
+	text-decoration: none;
+	outline: none;
+}
+
+.navpath li.navelem a:hover
+{
+	color:#6884BD;
+}
+
+.navpath li.footer
+{
+        list-style-type:none;
+        float:right;
+        padding-left:10px;
+        padding-right:15px;
+        background-image:none;
+        background-repeat:no-repeat;
+        background-position:right;
+        color:#364D7C;
+        font-size: 8pt;
+}
+
+
+div.summary
+{
+	float: right;
+	font-size: 8pt;
+	padding-right: 5px;
+	width: 50%;
+	text-align: right;
+}       
+
+div.summary a
+{
+	white-space: nowrap;
+}
+
+div.ingroups
+{
+	font-size: 8pt;
+	width: 50%;
+	text-align: left;
+}
+
+div.ingroups a
+{
+	white-space: nowrap;
+}
+
+div.header
+{
+        background-image:url('nav_h.png');
+        background-repeat:repeat-x;
+	background-color: #F9FAFC;
+	margin:  0px;
+	border-bottom: 1px solid #C4CFE5;
+}
+
+div.headertitle
+{
+	padding: 5px 5px 5px 7px;
+}
+
+dl
+{
+        padding: 0 0 0 10px;
+}
+
+/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug */
+dl.section
+{
+	margin-left: 0px;
+	padding-left: 0px;
+}
+
+dl.note
+{
+        margin-left:-7px;
+        padding-left: 3px;
+        border-left:4px solid;
+        border-color: #D0C000;
+}
+
+dl.warning, dl.attention
+{
+        margin-left:-7px;
+        padding-left: 3px;
+        border-left:4px solid;
+        border-color: #FF0000;
+}
+
+dl.pre, dl.post, dl.invariant
+{
+        margin-left:-7px;
+        padding-left: 3px;
+        border-left:4px solid;
+        border-color: #00D000;
+}
+
+dl.deprecated
+{
+        margin-left:-7px;
+        padding-left: 3px;
+        border-left:4px solid;
+        border-color: #505050;
+}
+
+dl.todo
+{
+        margin-left:-7px;
+        padding-left: 3px;
+        border-left:4px solid;
+        border-color: #00C0E0;
+}
+
+dl.test
+{
+        margin-left:-7px;
+        padding-left: 3px;
+        border-left:4px solid;
+        border-color: #3030E0;
+}
+
+dl.bug
+{
+        margin-left:-7px;
+        padding-left: 3px;
+        border-left:4px solid;
+        border-color: #C08050;
+}
+
+dl.section dd {
+	margin-bottom: 6px;
+}
+
+
+#projectlogo
+{
+	text-align: center;
+	vertical-align: bottom;
+	border-collapse: separate;
+}
+ 
+#projectlogo img
+{ 
+	border: 0px none;
+}
+ 
+#projectname
+{
+	font: 300% Tahoma, Arial,sans-serif;
+	margin: 0px;
+	padding: 2px 0px;
+}
+    
+#projectbrief
+{
+	font: 120% Tahoma, Arial,sans-serif;
+	margin: 0px;
+	padding: 0px;
+}
+
+#projectnumber
+{
+	font: 50% Tahoma, Arial,sans-serif;
+	margin: 0px;
+	padding: 0px;
+}
+
+#titlearea
+{
+	padding: 0px;
+	margin: 0px;
+	width: 100%;
+	border-bottom: 1px solid #5373B4;
+}
+
+.image
+{
+        text-align: center;
+}
+
+.dotgraph
+{
+        text-align: center;
+}
+
+.mscgraph
+{
+        text-align: center;
+}
+
+.caption
+{
+	font-weight: bold;
+}
+
+div.zoom
+{
+	border: 1px solid #90A5CE;
+}
+
+dl.citelist {
+        margin-bottom:50px;
+}
+
+dl.citelist dt {
+        color:#334975;
+        float:left;
+        font-weight:bold;
+        margin-right:10px;
+        padding:5px;
+}
+
+dl.citelist dd {
+        margin:2px 0;
+        padding:5px 0;
+}
+
+div.toc {
+        padding: 14px 25px;
+        background-color: #F4F6FA;
+        border: 1px solid #D8DFEE;
+        border-radius: 7px 7px 7px 7px;
+        float: right;
+        height: auto;
+        margin: 0 20px 10px 10px;
+        width: 200px;
+}
+
+div.toc li {
+        background: url("bdwn.png") no-repeat scroll 0 5px transparent;
+        font: 10px/1.2 Verdana,DejaVu Sans,Geneva,sans-serif;
+        margin-top: 5px;
+        padding-left: 10px;
+        padding-top: 2px;
+}
+
+div.toc h3 {
+        font: bold 12px/1.2 Arial,FreeSans,sans-serif;
+	color: #4665A2;
+        border-bottom: 0 none;
+        margin: 0;
+}
+
+div.toc ul {
+        list-style: none outside none;
+        border: medium none;
+        padding: 0px;
+}       
+
+div.toc li.level1 {
+        margin-left: 0px;
+}
+
+div.toc li.level2 {
+        margin-left: 15px;
+}
+
+div.toc li.level3 {
+        margin-left: 30px;
+}
+
+div.toc li.level4 {
+        margin-left: 45px;
+}
+
+.inherit_header {
+        font-weight: bold;
+        color: gray;
+        cursor: pointer;
+	-webkit-touch-callout: none;
+	-webkit-user-select: none;
+	-khtml-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	user-select: none;
+}
+
+.inherit_header td {
+        padding: 6px 0px 2px 5px;
+}
+
+.inherit {
+        display: none;
+}
+
+tr.heading h2 {
+        margin-top: 12px;
+        margin-bottom: 4px;
+}
+
+@media print
+{
+  #top { display: none; }
+  #side-nav { display: none; }
+  #nav-path { display: none; }
+  body { overflow:visible; }
+  h1, h2, h3, h4, h5, h6 { page-break-after: avoid; }
+  .summary { display: none; }
+  .memitem { page-break-inside: avoid; }
+  #doc-content
+  {
+    margin-left:0 !important;
+    height:auto !important;
+    width:auto !important;
+    overflow:inherit;
+    display:inline;
+  }
 }
 
diff --git a/doc/m4/ax_python.m4 b/doc/m4/ax_python.m4
new file mode 100644
index 0000000..f9a5135
--- /dev/null
+++ b/doc/m4/ax_python.m4
@@ -0,0 +1,97 @@
+# ===========================================================================
+#         http://www.gnu.org/software/autoconf-archive/ax_python.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_PYTHON
+#
+# DESCRIPTION
+#
+#   This macro does a complete Python development environment check.
+#
+#   It recurses through several python versions (from 2.1 to 2.6 in this
+#   version), looking for an executable. When it finds an executable, it
+#   looks to find the header files and library.
+#
+#   It sets PYTHON_BIN to the name of the python executable,
+#   PYTHON_INCLUDE_DIR to the directory holding the header files, and
+#   PYTHON_LIB to the name of the Python library.
+#
+#   This macro calls AC_SUBST on PYTHON_BIN (via AC_CHECK_PROG),
+#   PYTHON_INCLUDE_DIR and PYTHON_LIB.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Michael Tindal
+#
+#   This program is free software; you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation; either version 2 of the License, or (at your
+#   option) any later version.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+#   Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License along
+#   with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 9
+
+AC_DEFUN([AX_PYTHON],
+[AC_MSG_CHECKING(for python build information)
+AC_MSG_RESULT([])
+for python in python3.3 python3.2 python3.1 python3.0 python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python; do
+AC_CHECK_PROGS(PYTHON_BIN, [$python])
+ax_python_bin=$PYTHON_BIN
+if test x$ax_python_bin != x; then
+   AC_CHECK_LIB($ax_python_bin, main, ax_python_lib=$ax_python_bin, ax_python_lib=no)
+   AC_CHECK_HEADER([$ax_python_bin/Python.h],
+   [[ax_python_header=`locate $ax_python_bin/Python.h | sed -e s,/Python.h,,`]],
+   ax_python_header=no)
+   if test x$ax_python_lib != xno; then
+     if test x$ax_python_header != xno; then
+       break;
+     fi
+   fi
+fi
+done
+if test x$ax_python_bin = x; then
+   ax_python_bin=no
+fi
+if test x$ax_python_header = x; then
+   ax_python_header=no
+fi
+if test x$ax_python_lib = x; then
+   ax_python_lib=no
+fi
+
+AC_MSG_RESULT([  results of the Python check:])
+AC_MSG_RESULT([    Binary:      $ax_python_bin])
+AC_MSG_RESULT([    Library:     $ax_python_lib])
+AC_MSG_RESULT([    Include Dir: $ax_python_header])
+
+if test x$ax_python_header != xno; then
+  PYTHON_INCLUDE_DIR=$ax_python_header
+  AC_SUBST(PYTHON_INCLUDE_DIR)
+fi
+if test x$ax_python_lib != xno; then
+  PYTHON_LIB=$ax_python_lib
+  AC_SUBST(PYTHON_LIB)
+fi
+])dnl
diff --git a/doc/resolve-asciidoc-refs.py b/doc/resolve-asciidoc-refs.py
new file mode 100755
index 0000000..5418747
--- /dev/null
+++ b/doc/resolve-asciidoc-refs.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+import fileinput
+import re
+import sys
+
+refs = {}
+complete_file = ""
+
+for line in open(sys.argv[1], 'r'):
+	complete_file += line
+
+for m in re.findall('\[\[(.+)\]\]\n=+ ([^\n]+)', complete_file):
+	ref, title = m
+	refs["<<" + ref + ">>"] = "<<" + ref + ", " + title + ">>"
+
+def translate(match):
+	try:
+		return refs[match.group(0)]
+	except KeyError:
+		return ""
+
+rc = re.compile('|'.join(map(re.escape, sorted(refs, reverse=True))))
+for line in open(sys.argv[1], 'r'):
+	print rc.sub(translate, line),
diff --git a/doc/route.txt b/doc/route.txt
new file mode 100644
index 0000000..d9f88e1
--- /dev/null
+++ b/doc/route.txt
@@ -0,0 +1,1889 @@
+////
+	vim.syntax: asciidoc
+
+	Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+////
+
+Routing Family Netlink Library (libnl-route)
+============================================
+Thomas Graf <tgraf@suug.ch>
+3.1, Aug 11 2011:
+
+== Introduction
+
+This library provides APIs to the kernel interfaces of the routing family.
+
+
+NOTE: Work in progress.
+
+== Addresses
+
+[[route_link]]
+== Links (Network Devices)
+
+The link configuration interface is part of the +NETLINK_ROUTE+ protocol
+family and implements the following netlink message types:
+
+- View and modify the configuration of physical and virtual network devices.
+- Create and delete virtual network devices (e.g. dummy devices, VLAN devices,
+  tun devices, bridging devices, ...)
+- View and modify per link network configuration settings (e.g.
+  +net.ipv6.conf.eth0.accept_ra+, +net.ipv4.conf.eth1.forwarding+, ...)
+
+.Naming Convention (network device, link, interface)
+
+In networking several terms are commonly used to refer to network devices.
+While they have distinct meanings they have been used interchangeably in
+the past. Within the Linux kernel, the term _network device_ or _netdev_ is
+commonly used In user space the term _network interface_ is very common.
+The routing netlink protocol uses the term _link_ and so does the _iproute2_
+utility and most routing daemons.
+
+=== Netlink Protocol
+
+This section describes the protocol semantics of the netlink based link
+configuration interface. The following messages are defined:
+
+[options="header", cols="1,2,2"]
+|==============================================================================
+| Message Type   | User -> Kernel                    | Kernel -> User
+| +RTM_NEWLINK+  | Create or update virtual network device
+| Reply to +RTM_GETLINK+ request or notification of link added or updated
+| +RTM_DELLINK+  | Delete virtual network device
+| Notification of link deleted or disappeared
+| +RTM_GETLINK+  | Retrieve link configuration and statistics | 
+| +RTM_SETLINK+  | Modify link configuration | 
+|==============================================================================
+
+See link:core.html#core_msg_types[Netlink Library - Message Types] for more
+information on common semantics of these message types.
+
+==== Link Message Format
+
+All netlink link messages share a common header (+struct ifinfomsg+) which
+is appended after the netlink header (+struct nlmsghdr+).
+
+image:ifinfomsg.png["Link Message Header"]
+
+The meaning of each field may differ depending on the message type. A
++struct ifinfomsg+ is defined in +<linux/rtnetlink.h>+ to represent the
+header.
+
+Address Family (8bit)::
+The address family is usually set to +AF_UNSPEC+ but may be specified in
++RTM_GETLINK+ requests to limit the returned links to a specific address
+family.
+
+Link Layer Type (16bit)::
+Currently only used in kernel->user messages to report the link layer type
+of a link. The value corresponds to the +ARPHRD_*+ defines found in
++<linux/if_arp.h>+. Translation from/to strings can be done using the
+functions nl_llproto2str()/nl_str2llproto().
+
+Link Index (32bit)::
+Carries the interface index and is used to identify existing links.
+
+Flags (32bit)::
+In kernel->user messages the value of this field represents the current
+state of the link flags. In user->kernel messages this field is used to
+change flags or set the initial flag state of new links. Note that in order
+to change a flag, the flag must also be set in the _Flags Change Mask_ field.
+
+Flags Change Mask (32bit)::
+The primary use of this field is to specify a mask of flags that should be
+changed based on the value of the _Flags_ field. A special meaning is given
+to this field when present in link notifications, see TODO.
+
+Attributes (variable)::
+All link message types may carry netlink attributes. They are defined in the
+header file <linux/if_link.h> and share the prefix +IFLA_+.
+
+==== Link Message Types
+
+.RTM_GETLINK (user->kernel)
+
+Lookup link by 1. interface index or 2. link name (+IFLA_IFNAME+) and return
+a single +RTM_NEWLINK+ message containing the link configuration and statistics
+or a netlink error message if no such link was found.
+
+*Parameters:*
+
+* *Address family*
+** If the address family is set to +PF_BRIDGE+, only bridging devices will be
+   returned.
+** If the address family is set to +PF_INET6+, only ipv6 enabled devices will
+   be returned.
+
+*Flags:*
+
+* +NLM_F_DUMP+ If set, all links will be returned in form of a multipart
+  message.
+
+*Returns:*
+
+* +EINVAL+ if neither interface nor link name are set
+* +ENODEV+ if no link was found
+* +ENOBUFS+ if allocation failed
+
+.RTM_NEWLINK (user->kernel)
+
+Creates a new or updates an existing link. Only virtual links may be created
+but all links may be updated.
+
+*Flags:*
+
+- +NLM_F_CREATE+ Create link if it does not exist
+- +NLM_F_EXCL+ Return +EEXIST+ if link already exists
+
+*Returns:*
+
+- +EINVAL+ malformed message or invalid configuration parameters
+- +EAFNOSUPPORT+ if a address family specific configuration (+IFLA_AF_SPEC+)
+  is not supported.
+- +EOPNOTSUPP+ if the link does not support modification of parameters
+- +EEXIST+ if +NLM_F_EXCL+ was set and the link exists alraedy
+- +ENODEV+ if the link does not exist and +NLM_F_CREATE+ is not set
+
+.RTM_NEWLINK (kernel->user)
+
+This message type is used in reply to a +RTM_GETLINK+ request and carries
+the configuration and statistics of a link. If multiple links need to
+be sent, the messages will be sent in form of a multipart message.
+
+The message type is also used for notifications sent by the kernel to the
+multicast group +RTNLGRP_LINK+ to inform about various link events. It is
+therefore recommended to always use a separate link socket for link
+notifications in order to separate between the two message types.
+
+TODO: document how to detect different notifications
+
+.RTM_DELLINK (user->kernel)
+
+Lookup link by 1. interface index or 2. link name (+IFLA_IFNAME+) and delete
+the virtual link.
+
+*Returns:*
+
+* +EINVAL+ if neither interface nor link name are set
+* +ENODEV+ if no link was found
+* +ENOTSUPP+ if the operation is not supported (not a virtual link)
+
+.RTM_DELLINK (kernel->user)
+
+Notification sent by the kernel to the multicast group +RTNLGRP_LINK+ when
+
+a. a network device was unregistered (change == ~0)
+b. a bridging device was deleted (address family will be +PF_BRIDGE+)
+
+=== Get / List
+
+[[link_list]]
+==== Get list of links
+
+To retrieve the list of links in the kernel, allocate a new link cache
+using +rtnl_link_alloc_cache()+ to hold the links. It will automatically
+construct and send a +RTM_GETLINK+ message requesting a dump of all links
+from the kernel and feed the returned +RTM_NEWLINK+ to the internal link
+message parser which adds the returned links to the cache.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+int rtnl_link_alloc_cache(struct nl_sock *sk, int family, struct nl_cache **result)
+-----
+
+The cache will contain link objects (+struct rtnl_link+, see <<link_object>>)
+and can be accessed using the standard cache functions. By setting the
++family+ parameter to an address familly other than +AF_UNSPEC+, the resulting
+cache will only contain links supporting the specified address family.
+
+The following direct search functions are provided to search by interface
+index and by link name:
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+struct rtnl_link *rtnl_link_get(struct nl_cache *cache, int ifindex);
+struct rtnl_link *rtnl_link_get_by_name(struct nl_cache *cache, const char *name);
+-----
+
+.Example: Link Cache
+
+[source,c]
+-----
+struct nl_cache *cache;
+struct rtnl_link *link;
+
+if (rtnl_link_alloc_cache(sock, AF_UNSPEC, &cache)) < 0)
+	/* error */
+
+if (!(link = rtnl_link_get_by_name(cache, "eth1")))
+	/* link does not exist */
+
+/* do something with link */
+
+rtnl_link_put(link);
+nl_cache_put(cache);
+-----
+
+[[link_direct_lookup]]
+==== Lookup Single Link (Direct Lookup)
+
+If only a single link is of interest, the link can be looked up directly
+without the use of a link cache using the function +rtnl_link_get_kernel()+.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+int rtnl_link_get_kernel(struct nl_sock *sk, int ifindex, const char *name, struct rtnl_link **result);
+-----
+
+It will construct and send a +RTM_GETLINK+ request using the parameters
+provided and wait for a +RTM_NEWLINK+ or netlink error message sent in
+return. If the link exists, the link is returned as link object
+(see <<link_object>>).
+
+.Example: Direct link lookup
+[source,c]
+-----
+struct rtnl_link *link;
+
+if (rtnl_link_get_kernel(sock, 0, "eth1", &link) < 0)
+	/* error */
+
+/* do something with link */
+
+rtnl_link_put(link);
+-----
+
+NOTE: While using this function can save a substantial amount of bandwidth
+      on the netlink socket, the result will not be cached, subsequent calls
+      to rtnl_link_get_kernel() will always trigger sending a +RTM_GETLINK+
+      request.
+
+[[link_translate_ifindex]]
+==== Translating interface index to link name
+
+Applications which require to translate interface index to a link name or
+vice verase may use the following functions to do so. Both functions require
+a filled link cache to work with.
+
+[source,c]
+-----
+char *rtnl_link_i2name (struct nl_cache *cache, int ifindex, char *dst, size_t len);
+int rtnl_link_name2i (struct nl_cache *cache, const char *name);
+-----
+
+=== Add / Modify
+
+Several types of virtual link can be added on the fly using the function
++rtnl_link_add()+.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+int rtnl_link_add(struct nl_sock *sk, struct rtnl_link *link, int flags);
+-----
+
+=== Delete
+
+The deletion of virtual links such as VLAN devices or dummy devices is done
+using the function +rtnl_link_delete()+. The link passed on to the function
+can be a link from a link cache or it can be construct with the minimal
+attributes needed to identify the link.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+int rtnl_link_delete(struct nl_sock *sk, const struct rtnl_link *link);
+-----
+
+The function will construct and send a +RTM_DELLINK+ request message and
+returns any errors returned by the kernel.
+
+.Example: Delete link by name
+[source,c]
+-----
+struct rtnl_link *link;
+
+if (!(link = rtnl_link_alloc()))
+	/* error */
+
+rtnl_link_set_name(link, "my_vlan");
+
+if (rtnl_link_delete(sock, link) < 0)
+	/* error */
+
+rtnl_link_put(link);
+-----
+
+[[link_object]]
+=== Link Object
+
+A link is represented by the structure +struct rtnl_link+. Instances may be
+created with the function +rtnl_link_alloc()+ or via a link cache (see
+<<link_list>>) and are freed again using the function +rtnl_link_put()+.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+struct rtnl_link *rtnl_link_alloc(void);
+void rtnl_link_put(struct rtnl_link *link);
+-----
+
+[[link_attr_name]]
+==== Name
+The name serves as unique, human readable description of the link. By
+default, links are named based on their type and then enumerated, e.g.
+eth0, eth1, ethn but they may be renamed at any time.
+
+Kernels >= 2.6.11 support identification by link name.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_name(struct rtnl_link *link, const char *name);
+char *rtnl_link_get_name(struct rtnl_link *link);
+-----
+
+*Accepted link name format:* +[^ /]*+ (maximum length: 15 characters)
+
+[[link_attr_ifindex]]
+==== Interface Index (Identifier)
+The interface index is an integer uniquely identifying a link. If present
+in any link message, it will be used to identify an existing link.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_ifindex(struct rtnl_link *link, int ifindex);
+int rtnl_link_get_ifindex(struct rtnl_link *link);
+-----
+
+[[link_attr_group]]
+==== Group
+Each link can be assigned a numeric group identifier to group a bunch of links
+together and apply a set of changes to a group instead of just a single link.
+
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_group(struct rtnl_link *link, uint32_t group);
+uint32_t rtnl_link_get_group(struct rtnl_link *link);
+-----
+
+[[link_attr_address]]
+==== Link Layer Address
+The link layer address (e.g. MAC address).
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_addr(struct rtnl_link *link, struct nl_addr *addr);
+struct nl_addr *rtnl_link_get_addr(struct rtnl_link *link);
+-----
+
+[[link_attr_broadcast]]
+==== Broadcast Address
+The link layer broadcast address
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_broadcast(struct rtnl_link *link, struct nl_addr *addr);
+struct nl_addr *rtnl_link_get_broadcast(struct rtnl_link *link);
+-----
+
+[[link_attr_mtu]]
+==== MTU (Maximum Transmission Unit)
+The maximum transmission unit specifies the maximum packet size a network
+device can transmit or receive. This value may be lower than the capability
+of the physical network device.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_mtu(struct rtnl_link *link, unsigned int mtu);
+unsigned int rtnl_link_get_mtu(struct rtnl_link *link);
+-----
+
+[[link_attr_flags]]
+==== Flags
+The flags of a link enable or disable various link features or inform about
+the state of the link.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_flags(struct rtnl_link *link, unsigned int flags);
+void rtnl_link_unset_flags(struct rtnl_link *link, unsigned int flags);
+unsigned int rtnl_link_get_flags(struct rtnl_link *link);
+-----
+
+[options="compact"]
+[horizontal]
+IFF_UP::           Link is up (administratively)
+IFF_RUNNING::      Link is up and carrier is OK (RFC2863 OPER_UP)
+IFF_LOWER_UP::     Link layer is operational
+IFF_DORMANT::      Driver signals dormant
+IFF_BROADCAST::    Link supports broadcasting
+IFF_MULTICAST::    Link supports multicasting
+IFF_ALLMULTI::     Link supports multicast routing
+IFF_DEBUG::        Tell driver to do debugging (currently unused)
+IFF_LOOPBACK::     Link loopback network
+IFF_POINTOPOINT::  Point-to-point link
+IFF_NOARP::        ARP is not supported
+IFF_PROMISC::      Status of promiscious mode
+IFF_MASTER::       Master of a load balancer (bonding)
+IFF_SLAVE::        Slave to a master link
+IFF_PORTSEL::      Driver supports setting media type (only used by ARM ethernet)
+IFF_AUTOMEDIA::    Link selects port automatically (only used by ARM ethernet)
+IFF_ECHO::         Echo sent packets (testing feature, CAN only)
+IFF_DYNAMIC::      Unused (BSD compatibility)
+IFF_NOTRAILERS::   Unused (BSD compatibility)
+
+To translate a link flag to a link flag name or vice versa:
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+char *rtnl_link_flags2str(int flags, char *buf, size_t size);
+int rtnl_link_str2flags(const char *flag_name);
+-----
+
+[[link_attr_txqlen]]
+==== Transmission Queue Length
+
+The transmission queue holds packets before packets are delivered to
+the driver for transmission. It is usually specified in number of
+packets but the unit may be specific to the link type.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_txqlen(struct rtnl_link *link, unsigned int txqlen);
+unsigned int rtnl_link_get_txqlen(struct rtnl_link *link);
+-----
+
+[[link_attr_operstate]]
+==== Operational Status
+The operational status has been introduced to provide extended information
+on the link status. Traditionally the link state has been described using
+the link flags +IFF_UP, IFF_RUNNING, IFF_LOWER_UP+, and +IFF_DORMANT+ which
+was no longer sufficient for some link types.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_operstate(struct rtnl_link *link, uint8_t state);
+uint8_t rtnl_link_get_operstate(struct rtnl_link *link);
+-----
+
+[options="compact"]
+[horizontal]
+IF_OPER_UNKNOWN::          Unknown state
+IF_OPER_NOTPRESENT::       Link not present
+IF_OPER_DOWN::             Link down
+IF_OPER_LOWERLAYERDOWN::   L1 down
+IF_OPER_TESTING::          Testing
+IF_OPER_DORMANT::          Dormant
+IF_OPER_UP::               Link up
+
+Translation of operational status code to string and vice versa:
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+char *rtnl_link_operstate2str(uint8_t state, char *buf, size_t size);
+int rtnl_link_str2operstate(const char *name);
+-----
+
+[[link_attr_mode]]
+==== Mode
+Currently known link modes are:
+
+[options="compact"]
+[horizontal]
+IF_LINK_MODE_DEFAULT::   Default link mode
+IF_LINK_MODE_DORMANT::   Limit upward transition to dormant
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_linkmode(struct rtnl_link *link, uint8_t mode);
+uint8_t rtnl_link_get_linkmode(struct rtnl_link *link);
+-----
+
+Translation of link mode to string and vice versa:
+
+[source,c]
+-----
+char *rtnl_link_mode2str(uint8_t mode, char *buf, size_t len);
+uint8_t rtnl_link_str2mode(const char *name);
+-----
+
+[[link_attr_alias]]
+==== IfAlias
+Alternative name for the link, primarly used for SNMP IfAlias.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+const char *rtnl_link_get_ifalias(struct rtnl_link *link);
+void rtnl_link_set_ifalias(struct rtnl_link *link, const char *alias);
+-----
+
+*Length limit:* 256
+
+[[link_attr_arptype]]
+==== Hardware Type
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+#include <linux/if_arp.h>
+
+void rtnl_link_set_arptype(struct rtnl_link *link, unsigned int arptype);
+unsigned int rtnl_link_get_arptype(struct rtnl_link *link);
+----
+
+Translation of hardware type to character string and vice versa:
+
+[source,c]
+-----
+#include <netlink/utils.h>
+
+char *nl_llproto2str(int arptype, char *buf, size_t len);
+int nl_str2llproto(const char *name);
+-----
+
+[[link_attr_qdisc]]
+==== Qdisc
+The name of the queueing discipline used by the link is of informational
+nature only. It is a read-only attribute provided by the kernel and cannot
+be modified. The set function is provided solely for the purpose of creating
+link objects to be used for comparison.
+
+For more information on how to modify the qdisc of a link, see section
+<<route_tc>>.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_qdisc(struct rtnl_link *link, const char *name);
+char *rtnl_link_get_qdisc(struct rtnl_link *link);
+-----
+
+[[link_attr_promiscuity]]
+==== Promiscuity
+The number of subsystem currently depending on the link being promiscuous mode.
+A value of 0 indicates that the link is not in promiscuous mode. It is a
+read-only attribute provided by the kernel and cannot be modified. The set
+function is provided solely for the purpose of creating link objects to be
+used for comparison.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_promiscuity(struct rtnl_link *link, uint32_t count);
+uint32_t rtnl_link_get_promiscuity(struct rtnl_link *link);
+-----
+
+[[link_num_rxtx_queues]]
+==== RX/TX Queues
+The number of RX/TX queues the link provides. The attribute is writable but
+will only be considered when creating a new network device via netlink.
+
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+void rtnl_link_set_num_tx_queues(struct rtnl_link *link, uint32_t nqueues);
+uint32_t rtnl_link_get_num_tx_queues(struct rtnl_link *link);
+
+void rtnl_link_set_num_rx_queues(struct rtnl_link *link, uint32_t nqueues);
+uint32_t rtnl_link_get_num_rx_queues(struct rtnl_link *link);
+-----
+
+[[link_attr_weight]]
+==== Weight
+This attribute is unused and obsoleted in all recent kernels.
+
+
+[[link_modules]]
+=== Modules
+
+[[link_bonding]]
+==== Bonding
+
+.Example: Add bonding link
+[source,c]
+-----
+#include <netlink/route/link.h>
+
+struct rtnl_link *link;
+
+link = rtnl_link_bond_alloc();
+rtnl_link_set_name(link, "my_bond");
+
+/* requires admin privileges */
+if (rtnl_link_add(sk, link, NLM_F_CREATE) < 0)
+	/* error */
+
+rtnl_link_put(link);
+-----
+
+[[link_vlan]]
+==== VLAN
+
+[source,c]
+-----
+extern char *		rtnl_link_vlan_flags2str(int, char *, size_t);
+extern int		rtnl_link_vlan_str2flags(const char *);
+
+extern int		rtnl_link_vlan_set_id(struct rtnl_link *, int);
+extern int		rtnl_link_vlan_get_id(struct rtnl_link *);
+
+extern int		rtnl_link_vlan_set_flags(struct rtnl_link *,
+						 unsigned int);
+extern int		rtnl_link_vlan_unset_flags(struct rtnl_link *,
+						   unsigned int);
+extern unsigned int	rtnl_link_vlan_get_flags(struct rtnl_link *);
+
+extern int		rtnl_link_vlan_set_ingress_map(struct rtnl_link *,
+						       int, uint32_t);
+extern uint32_t *	rtnl_link_vlan_get_ingress_map(struct rtnl_link *);
+
+extern int		rtnl_link_vlan_set_egress_map(struct rtnl_link *,
+						      uint32_t, int);
+extern struct vlan_map *rtnl_link_vlan_get_egress_map(struct rtnl_link *,
+						      int *);
+-----
+
+.Example: Add a VLAN device
+[source,c]
+-----
+struct rtnl_link *link;
+int master_index;
+
+/* lookup interface index of eth0 */
+if (!(master_index = rtnl_link_name2i(link_cache, "eth0")))
+	/* error */
+
+/* allocate new link object of type vlan */
+link = rtnl_link_vlan_alloc();
+
+/* set eth0 to be our master device */
+rtnl_link_set_link(link, master_index);
+
+rtnl_link_vlan_set_id(link, 10);
+
+if ((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0)
+	/* error */
+
+rtnl_link_put(link);
+-----
+
+[[link_macvlan]]
+==== MACVLAN
+
+[source,c]
+-----
+extern struct rtnl_link *rtnl_link_macvlan_alloc(void);
+
+extern int		rtnl_link_is_macvlan(struct rtnl_link *);
+
+extern char *		rtnl_link_macvlan_mode2str(int, char *, size_t);
+extern int		rtnl_link_macvlan_str2mode(const char *);
+
+extern char *		rtnl_link_macvlan_flags2str(int, char *, size_t);
+extern int		rtnl_link_macvlan_str2flags(const char *);
+
+extern int		rtnl_link_macvlan_set_mode(struct rtnl_link *,
+			                           uint32_t);
+extern uint32_t		rtnl_link_macvlan_get_mode(struct rtnl_link *);
+
+extern int		rtnl_link_macvlan_set_flags(struct rtnl_link *,
+						 uint16_t);
+extern int		rtnl_link_macvlan_unset_flags(struct rtnl_link *,
+						   uint16_t);
+extern uint16_t		rtnl_link_macvlan_get_flags(struct rtnl_link *);
+-----
+
+.Example: Add a MACVLAN device
+[source,c]
+-----
+struct rtnl_link *link;
+int master_index;
+struct nl_addr* addr;
+
+/* lookup interface index of eth0 */
+if (!(master_index = rtnl_link_name2i(link_cache, "eth0")))
+	/* error */
+
+/* allocate new link object of type macvlan */
+link = rtnl_link_macvlan_alloc();
+
+/* set eth0 to be our master device */
+rtnl_link_set_link(link, master_index);
+
+/* set address of virtual interface */
+addr = nl_addr_build(AF_LLC, ether_aton("00:11:22:33:44:55"), ETH_ALEN);
+rtnl_link_set_addr(link, addr);
+nl_addr_put(addr);
+
+/* set mode of virtual interface */
+rtnl_link_macvlan_set_mode(link, rtnl_link_macvlan_str2mode("bridge"));
+
+if ((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0)
+	/* error */
+
+rtnl_link_put(link);
+-----
+
+[[link_vxlan]]
+==== VXLAN
+
+[source,c]
+-----
+extern struct rtnl_link *rtnl_link_vxlan_alloc(void);
+
+extern int	rtnl_link_is_vxlan(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_id(struct rtnl_link *, uint32_t);
+extern int	rtnl_link_vxlan_get_id(struct rtnl_link *, uint32_t *);
+
+extern int	rtnl_link_vxlan_set_group(struct rtnl_link *, struct nl_addr *);
+extern int	rtnl_link_vxlan_get_group(struct rtnl_link *, struct nl_addr **);
+
+extern int	rtnl_link_vxlan_set_link(struct rtnl_link *, uint32_t);
+extern int	rtnl_link_vxlan_get_link(struct rtnl_link *, uint32_t *);
+
+extern int	rtnl_link_vxlan_set_local(struct rtnl_link *, struct nl_addr *);
+extern int	rtnl_link_vxlan_get_local(struct rtnl_link *, struct nl_addr **);
+
+extern int	rtnl_link_vxlan_set_ttl(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_ttl(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_tos(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_tos(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_learning(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_learning(struct rtnl_link *);
+extern int	rtnl_link_vxlan_enable_learning(struct rtnl_link *);
+extern int	rtnl_link_vxlan_disable_learning(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_ageing(struct rtnl_link *, uint32_t);
+extern int	rtnl_link_vxlan_get_ageing(struct rtnl_link *, uint32_t *);
+
+extern int	rtnl_link_vxlan_set_limit(struct rtnl_link *, uint32_t);
+extern int	rtnl_link_vxlan_get_limit(struct rtnl_link *, uint32_t *);
+
+extern int	rtnl_link_vxlan_set_port_range(struct rtnl_link *,
+					       struct ifla_vxlan_port_range *);
+extern int	rtnl_link_vxlan_get_port_range(struct rtnl_link *,
+					       struct ifla_vxlan_port_range *);
+
+extern int	rtnl_link_vxlan_set_proxy(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_proxy(struct rtnl_link *);
+extern int	rtnl_link_vxlan_enable_proxy(struct rtnl_link *);
+extern int	rtnl_link_vxlan_disable_proxy(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_rsc(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_rsc(struct rtnl_link *);
+extern int	rtnl_link_vxlan_enable_rsc(struct rtnl_link *);
+extern int	rtnl_link_vxlan_disable_rsc(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_l2miss(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_l2miss(struct rtnl_link *);
+extern int	rtnl_link_vxlan_enable_l2miss(struct rtnl_link *);
+extern int	rtnl_link_vxlan_disable_l2miss(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_l3miss(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_l3miss(struct rtnl_link *);
+extern int	rtnl_link_vxlan_enable_l3miss(struct rtnl_link *);
+extern int	rtnl_link_vxlan_disable_l3miss(struct rtnl_link *);
+-----
+
+.Example: Add a VXLAN device
+[source,c]
+-----
+struct rtnl_link *link;
+struct nl_addr* addr;
+
+/* allocate new link object of type vxlan */
+link = rtnl_link_vxlan_alloc();
+
+/* set interface name */
+rtnl_link_set_name(link, "vxlan128");
+
+/* set VXLAN network identifier */
+if ((err = rtnl_link_vxlan_set_id(link, 128)) < 0)
+	/* error */
+
+/* set multicast address to join */
+if ((err = nl_addr_parse("239.0.0.1", AF_INET, &addr)) < 0)
+	/* error */
+
+if ((err = rtnl_link_set_group(link, addr)) < 0)
+	/* error */
+
+nl_addr_put(addr);
+
+if ((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0)
+	/* error */
+
+rtnl_link_put(link);
+-----
+
+[[link_ipip]]
+==== IPIP
+
+[source,c]
+-----
+extern struct rtnl_link *rtnl_link_ipip_alloc(void);
+extern int rtnl_link_ipip_add(struct nl_sock *sk, const char *name);
+
+extern int rtnl_link_ipip_set_link(struct rtnl_link *link,  uint32_t index);
+extern uint32_t rtnl_link_ipip_get_link(struct rtnl_link *link);
+
+extern int rtnl_link_ipip_set_local(struct rtnl_link *link, uint32_t addr);
+extern uint32_t rtnl_link_ipip_get_local(struct rtnl_link *link);
+
+extern int rtnl_link_ipip_set_remote(struct rtnl_link *link, uint32_t addr);
+extern uint32_t rtnl_link_ipip_get_remote(struct rtnl_link *link);
+
+extern int rtnl_link_ipip_set_ttl(struct rtnl_link *link, uint8_t ttl);
+extern uint8_t rtnl_link_ipip_get_ttl(struct rtnl_link *link);
+
+extern int rtnl_link_ipip_set_tos(struct rtnl_link *link, uint8_t tos);
+extern uint8_t rtnl_link_ipip_get_tos(struct rtnl_link *link);
+
+extern int rtnl_link_ipip_set_pmtudisc(struct rtnl_link *link, uint8_t pmtudisc);
+extern uint8_t rtnl_link_ipip_get_pmtudisc(struct rtnl_link *link);
+
+-----
+
+.Example: Add a ipip tunnel device
+[source,c]
+-----
+struct rtnl_link *link
+struct in_addr addr
+
+/* allocate new link object of type vxlan */
+if(!(link = rtnl_link_ipip_alloc()))
+        /* error */
+
+/* set ipip tunnel name */
+if ((err = rtnl_link_set_name(link, "ipip-tun")) < 0)
+         /* error */
+
+/* set link index  */
+if ((err = rtnl_link_ipip_set_link(link, if_index)) < 0)
+        /* error */
+
+/* set local address */
+inet_pton(AF_INET, "192.168.254.12", &addr.s_addr);
+if ((err = rtnl_link_ipip_set_local(link, addr.s_addr)) < 0)
+        /* error */
+
+/* set remote address */
+inet_pton(AF_INET, "192.168.254.13", &addr.s_addr
+if ((err = rtnl_link_ipip_set_remote(link, addr.s_addr)) < 0)
+        /* error */
+
+/* set tunnel ttl  */
+if ((err = rtnl_link_ipip_set_ttl(link, 64)) < 0)
+        /* error */
+
+if((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0)
+        /* error */
+
+rtnl_link_put(link);
+-----
+
+[[link_ipgre]]
+==== IPGRE
+
+[source,c]
+-----
+extern struct rtnl_link *rtnl_link_ipgre_alloc(void);
+extern int rtnl_link_ipgre_add(struct nl_sock *sk, const char *name);
+
+extern int rtnl_link_ipgre_set_link(struct rtnl_link *link,  uint32_t index);
+extern uint32_t rtnl_link_ipgre_get_link(struct rtnl_link *link);
+
+extern int rtnl_link_ipgre_set_iflags(struct rtnl_link *link, uint16_t iflags);
+extern uint16_t rtnl_link_get_iflags(struct rtnl_link *link);
+
+extern int rtnl_link_ipgre_set_oflags(struct rtnl_link *link, uint16_t oflags);
+extern uint16_t rtnl_link_get_oflags(struct rtnl_link *link);
+
+extern int rtnl_link_ipgre_set_ikey(struct rtnl_link *link, uint32_t ikey);
+extern uint32_t rtnl_link_get_ikey(struct rtnl_link *link);
+
+extern int rtnl_link_ipgre_set_okey(struct rtnl_link *link, uint32_t okey);
+extern uint32_t rtnl_link_get_okey(struct rtnl_link *link)
+
+extern int rtnl_link_ipgre_set_local(struct rtnl_link *link, uint32_t addr);
+extern uint32_t rtnl_link_ipgre_get_local(struct rtnl_link *link);
+
+extern int rtnl_link_ipgre_set_remote(struct rtnl_link *link, uint32_t addr);
+extern uint32_t rtnl_link_ipgre_get_remote(struct rtnl_link *link);
+
+extern int rtnl_link_ipgre_set_ttl(struct rtnl_link *link, uint8_t ttl);
+extern uint8_t rtnl_link_ipgre_get_ttl(struct rtnl_link *link);
+
+extern int rtnl_link_ipgre_set_tos(struct rtnl_link *link, uint8_t tos);
+extern uint8_t rtnl_link_ipgre_get_tos(struct rtnl_link *link);
+
+extern int rtnl_link_ipgre_set_pmtudisc(struct rtnl_link *link, uint8_t pmtudisc);
+extern uint8_t rtnl_link_ipgre_get_pmtudisc(struct rtnl_link *link);
+
+-----
+
+.Example: Add a ipgre tunnel device
+[source,c]
+-----
+struct rtnl_link *link
+struct in_addr addr
+
+/* allocate new link object of type vxlan */
+if(!(link = rtnl_link_ipgre_alloc()))
+	/* error */
+
+/* set ipgre tunnel name */
+if ((err = rtnl_link_set_name(link, "ipgre-tun")) < 0)
+	/* error */
+
+/* set link index  */
+if ((err = rtnl_link_ipgre_set_link(link, if_index)) < 0)
+	/* error */
+
+/* set local address */
+inet_pton(AF_INET, "192.168.254.12", &addr.s_addr);
+if ((err = rtnl_link_ipgre_set_local(link, addr.s_addr)) < 0)
+	/* error */
+
+/* set remote address */
+inet_pton(AF_INET, "192.168.254.13", &addr.s_addr
+if ((err = rtnl_link_ipgre_set_remote(link, addr.s_addr)) < 0)
+	/* error */
+
+/* set tunnel ttl  */
+if ((err = rtnl_link_ipgre_set_ttl(link, 64)) < 0)
+	/* error */
+
+if((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0)
+	/* error */
+
+rtnl_link_put(link);
+-----
+
+[[link_sit]]
+==== SIT
+
+[source,c]
+-----
+extern struct rtnl_link *rtnl_link_sit_alloc(void);
+extern int rtnl_link_sit_add(struct nl_sock *sk, const char *name);
+
+extern int rtnl_link_sit_set_link(struct rtnl_link *link,  uint32_t index);
+extern uint32_t rtnl_link_sit_get_link(struct rtnl_link *link);
+
+extern int rtnl_link_sit_set_iflags(struct rtnl_link *link, uint16_t iflags);
+extern uint16_t rtnl_link_get_iflags(struct rtnl_link *link);
+
+extern int rtnl_link_sit_set_oflags(struct rtnl_link *link, uint16_t oflags);
+extern uint16_t rtnl_link_get_oflags(struct rtnl_link *link);
+
+extern int rtnl_link_sit_set_ikey(struct rtnl_link *link, uint32_t ikey);
+extern uint32_t rtnl_link_get_ikey(struct rtnl_link *link);
+
+extern int rtnl_link_sit_set_okey(struct rtnl_link *link, uint32_t okey);
+extern uint32_t rtnl_link_get_okey(struct rtnl_link *link)
+
+extern int rtnl_link_sit_set_local(struct rtnl_link *link, uint32_t addr);
+extern uint32_t rtnl_link_sit_get_local(struct rtnl_link *link);
+
+extern int rtnl_link_sit_set_remote(struct rtnl_link *link, uint32_t addr);
+extern uint32_t rtnl_link_sit_get_remote(struct rtnl_link *link);
+
+extern int rtnl_link_sit_set_ttl(struct rtnl_link *link, uint8_t ttl);
+extern uint8_t rtnl_link_sit_get_ttl(struct rtnl_link *link);
+
+extern int rtnl_link_sit_set_tos(struct rtnl_link *link, uint8_t tos);
+extern uint8_t rtnl_link_sit_get_tos(struct rtnl_link *link);
+
+extern int rtnl_link_sit_set_pmtudisc(struct rtnl_link *link, uint8_t pmtudisc);
+extern uint8_t rtnl_link_sit_get_pmtudisc(struct rtnl_link *link);
+
+-----
+
+.Example: Add a sit tunnel device
+[source,c]
+-----
+struct rtnl_link *link
+struct in_addr addr
+
+/* allocate new link object of type vxlan */
+if(!(link = rtnl_link_sit_alloc()))
+	/* error */
+
+/* set sit tunnel name */
+if ((err = rtnl_link_set_name(link, "sit-tun")) < 0)
+	/* error */
+
+/* set link index  */
+if ((err = rtnl_link_sit_set_link(link, if_index)) < 0)
+	/* error */
+
+/* set local address */
+inet_pton(AF_INET, "192.168.254.12", &addr.s_addr);
+if ((err = rtnl_link_sit_set_local(link, addr.s_addr)) < 0)
+	/* error */
+
+/* set remote address */
+inet_pton(AF_INET, "192.168.254.13", &addr.s_addr
+if ((err = rtnl_link_sit_set_remote(link, addr.s_addr)) < 0)
+	/* error */
+
+/* set tunnel ttl  */
+if ((err = rtnl_link_sit_set_ttl(link, 64)) < 0)
+	/* error */
+
+if((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0)
+        /* error */
+
+rtnl_link_put(link);
+-----
+
+
+[[link_ipvti]]
+==== IPVTI
+
+[source,c]
+-----
+extern struct rtnl_link *rtnl_link_ipvti_alloc(void);
+extern int rtnl_link_ipvti_add(struct nl_sock *sk, const char *name);
+
+extern int rtnl_link_ipvti_set_link(struct rtnl_link *link,  uint32_t index);
+extern uint32_t rtnl_link_ipvti_get_link(struct rtnl_link *link);
+
+extern int rtnl_link_ipvti_set_ikey(struct rtnl_link *link, uint32_t ikey);
+extern uint32_t rtnl_link_get_ikey(struct rtnl_link *link);
+
+extern int rtnl_link_ipvti_set_okey(struct rtnl_link *link, uint32_t okey);
+extern uint32_t rtnl_link_get_okey(struct rtnl_link *link)
+
+extern int rtnl_link_ipvti_set_local(struct rtnl_link *link, uint32_t addr);
+extern uint32_t rtnl_link_ipvti_get_local(struct rtnl_link *link);
+
+extern int rtnl_link_ipvti_set_remote(struct rtnl_link *link, uint32_t addr);
+extern uint32_t rtnl_link_ipvti_get_remote(struct rtnl_link *link);
+
+-----
+
+.Example: Add a ipvti tunnel device
+[source,c]
+-----
+struct rtnl_link *link
+struct in_addr addr
+
+/* allocate new link object of type vxlan */
+if(!(link = rtnl_link_ipvti_alloc()))
+	/* error */
+
+/* set ipvti tunnel name */
+if ((err = rtnl_link_set_name(link, "ipvti-tun")) < 0)
+	/* error */
+
+/* set link index  */
+if ((err = rtnl_link_ipvti_set_link(link, if_index)) < 0)
+	/* error */
+
+/* set local address */
+inet_pton(AF_INET, "192.168.254.12", &addr.s_addr);
+if ((err = rtnl_link_ipvti_set_local(link, addr.s_addr)) < 0)
+	/* error */
+
+/* set remote address */
+inet_pton(AF_INET, "192.168.254.13", &addr.s_addr
+if ((err = rtnl_link_ipvti_set_remote(link, addr.s_addr)) < 0)
+	/* error */
+
+if((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0)
+	/* error */
+
+rtnl_link_put(link);
+-----
+
+[[link_ip6tnl]]
+==== IP6TNL
+
+[source,c]
+-----
+extern struct rtnl_link *rtnl_link_ip6_tnl_alloc(void);
+extern int rtnl_link_ip6_tnl_add(struct nl_sock *sk, const char *name);
+
+extern int rtnl_link_ip6_tnl_set_link(struct rtnl_link *link,  uint32_t index);
+extern uint32_t rtnl_link_ip6_tnl_get_link(struct rtnl_link *link);
+
+extern int rtnl_link_ip6_tnl_set_local(struct rtnl_link *link, struct in6_addr *);
+extern int rtnl_link_ip6_tnl_get_local(struct rtnl_link *link, struct in6_addr *);
+
+extern int rtnl_link_ip6_tnl_set_remote(struct rtnl_link *link, struct in6_addr *);
+extern int rtnl_link_ip6_tnl_get_remote(struct rtnl_link *link, struct in6_addr *);
+
+extern int rtnl_link_ip6_tnl_set_ttl(struct rtnl_link *link, uint8_t ttl);
+extern uint8_t rtnl_link_ip6_tnl_get_ttl(struct rtnl_link *link);
+
+extern int rtnl_link_ip6_tnl_set_tos(struct rtnl_link *link, uint8_t tos);
+extern uint8_t rtnl_link_ip6_tnl_get_tos(struct rtnl_link *link);
+
+extern int rtnl_link_ip6_tnl_set_encaplimit(struct rtnl_link *link, uint8_t encap_limit);
+extern uint8_t rtnl_link_ip6_tnl_get_encaplimit(struct rtnl_link *link);
+
+extern int rtnl_link_ip6_tnl_set_flags(struct rtnl_link *link, uint32_t flags);
+extern uint32_t rtnl_link_ip6_tnl_get_flags(struct rtnl_link *link);
+
+extern uint32_t rtnl_link_ip6_tnl_get_flowinfo(struct rtnl_link *link);
+extern int rtnl_link_ip6_tnl_set_flowinfo(struct rtnl_link *link, uint32_t flowinfo);
+
+extern int rtnl_link_ip6_tnl_set_proto(struct rtnl_link *link, uint8_t proto);
+extern uint8_t rtnl_link_ip6_tnl_get_proto(struct rtnl_link *link);
+
+-----
+
+.Example: Add a ip6tnl tunnel device
+[source,c]
+-----
+struct rtnl_link *link
+struct in6_addr addr
+
+link = rtnl_link_ip6_tnl_alloc();
+
+rtnl_link_set_name(link, "ip6tnl-tun");
+rtnl_link_ip6_tnl_set_link(link, if_index);
+
+inet_pton(AF_INET6, "2607:f0d0:1002:51::4", &addr);
+rtnl_link_ip6_tnl_set_local(link, &addr);
+
+inet_pton(AF_INET6, "2607:f0d0:1002:52::5", &addr);
+rtnl_link_ip6_tnl_set_remote(link, &addr);
+
+rtnl_link_add(sk, link, NLM_F_CREATE);
+rtnl_link_put(link);
+
+-----
+
+
+== Neighbouring
+
+== Routing
+
+[[route_tc]]
+== Traffic Control
+
+The traffic control architecture allows the queueing and
+prioritization of packets before they are enqueued to the network
+driver. To a limited degree it is also possible to take control of
+network traffic as it enters the network stack.
+
+The architecture consists of three different types of modules:
+
+- *Queueing disciplines (qdisc)* provide a mechanism to enqueue packets
+  in different forms. They may be used to implement fair queueing,
+  prioritization of differentiated services, enforce bandwidth
+  limitations, or even to simulate network behaviour such as packet
+  loss and packet delay. Qdiscs can be classful in which case they
+  allow traffic classes described in the next paragraph to be attached
+  to them.
+
+- *Traffic classes (class)* are supported by several qdiscs to build
+  a tree structure for different types of traffic. Each class may be
+  assigned its own set of attributes such as bandwidth limits or
+  queueing priorities. Some qdiscs even allow borrowing of bandwidth
+  between classes.
+
+- *Classifiers (cls)* are used to decide which qdisc/class the packet
+  should be enqueued to. Different types of classifiers exists,
+  ranging from classification based on protocol header values to
+  classification based on packet priority or firewall marks.
+  Additionally most classifiers support *extended matches (ematch)*
+  which allow extending classifiers by a set of matcher modules, and
+  *actions* which allow classifiers to take actions such as mangling,
+  mirroring, or even rerouting of packets.
+
+.Default Qdisc
+
+The default qdisc used on all network devices is `pfifo_fast`.
+Network devices which do not require a transmit queue such as the
+loopback device do not have a default qdisc attached. The `pfifo_fast`
+qdisc provides three bands to prioritize interactive traffic over bulk
+traffic. Classification is based on the packet priority (diffserv).
+
+image:qdisc_default.png["Default Qdisc"]
+
+.Multiqueue Default Qdisc
+
+If the network device provides multiple transmit queues the `mq`
+qdisc is used by default. It will automatically create a separate
+class for each transmit queue available and will also replace
+the single per device tx lock with a per queue lock.
+
+image:qdisc_mq.png["Multiqueue default Qdisc"]
+
+.Example of a customized classful qdisc setup
+
+The following figure illustrates a possible combination of different
+queueing and classification modules to implement quality of service
+needs.
+
+image:tc_overview.png["Classful Qdisc diagram"]
+
+=== Traffic Control Object
+
+Each type traffic control module (qdisc, class, classifier) is
+represented by its own structure. All of them are based on the traffic
+control object represented by `struct rtnl_tc` which itself is based
+on the generic object `struct nl_object` to make it cacheable. The
+traffic control object contains all attributes, implementation details
+and statistics that are shared by all of the traffic control object
+types.
+
+image:tc_obj.png["struct rtnl_tc hierarchy"]
+
+It is not possible to allocate a `struct rtnl_tc` object, instead the
+actual tc object types must be allocated directly using
+`rtnl_qdisc_alloc()`, `rtnl_class_alloc()`, `rtnl_cls_alloc()` and
+then casted to `struct rtnl_tc` using the `TC_CAST()` macro.
+
+.Usage Example: Allocation, Casting, Freeing
+[source,c]
+-----
+#include <netlink/route/tc.h>
+#include <netlink/route/qdisc.h>
+
+struct rtnl_qdisc *qdisc;
+
+/* Allocation of a qdisc object */
+qdisc = rtnl_qdisc_alloc();
+
+/* Cast the qdisc to a tc object using TC_CAST() to use rtnl_tc_ functions. */
+rtnl_tc_set_mpu(TC_CAST(qdisc), 64);
+
+/* Free the qdisc object */
+rtnl_qdisc_put(qdisc);
+-----
+
+[[tc_attr]]
+==== Attributes
+
+Handle::
+The handle uniquely identifies a tc object and is used to refer
+to other tc objects when constructing tc trees.
++
+[source,c]
+-----
+void rtnl_tc_set_handle(struct rtnl_tc *tc, uint32_t handle);
+uint32_t rtnl_tc_get_handle(struct rtnl_tc *tc);
+-----
+
+Interface Index::
+The interface index specifies the network device the traffic object
+is attached to. The function `rtnl_tc_set_link()` should be preferred
+when setting the interface index. It stores the reference to the link
+object in the tc object and allows retrieving the `mtu` and `linktype`
+automatically.
++
+[source,c]
+-----
+void rtnl_tc_set_ifindex(struct rtnl_tc *tc, int ifindex);
+void rtnl_tc_set_link(struct rtnl_tc *tc, struct rtnl_link *link);
+int rtnl_tc_get_ifindex(struct rtnl_tc *tc);
+-----
+
+Link Type::
+The link type specifies the kind of link that is used by the network
+device (e.g. ethernet, ATM, ...). It is derived automatically when
+the network device is specified with `rtnl_tc_set_link()`.
+The default fallback is `ARPHRD_ETHER` (ethernet).
++
+[source,c]
+-----
+void rtnl_tc_set_linktype(struct rtnl_tc *tc, uint32_t type);
+uint32_t rtnl_tc_get_linktype(struct rtnl_tc *tc);
+-----
+
+Kind::
+The kind character string specifies the type of qdisc, class,
+classifier. Setting the kind results in the module specific
+structure being allocated. Therefore it is imperative to call 
+`rtnl_tc_set_kind()` before using any type specific API functions
+such as `rtnl_htb_set_rate()`.
++
+[source,c]
+-----
+int rtnl_tc_set_kind(struct rtnl_tc *tc, const char *kind);
+char *rtnl_tc_get_kind(struct rtnl_tc *tc);
+-----
+
+MPU::
+The Minimum Packet Unit specifies the minimum packet size which will
+be transmitted
+ever be seen by this traffic control object. This value is used for
+rate calculations. Not all object implementations will make use of
+this value. The default value is 0.
++
+[source,c]
+-----
+void rtnl_tc_set_mpu(struct rtnl_tc *tc, uint32_t mpu);
+uint32_t rtnl_tc_get_mpu(struct rtnl_tc *tc);
+-----
+
+MTU::
+The Maximum Transmission Unit specifies the maximum packet size which
+will be transmitted. The value is derived from the link specified
+with `rtnl_tc_set_link()` if not overwritten with `rtnl_tc_set_mtu()`.
+If no link and MTU is specified, the value defaults to 1500
+(ethernet).
++
+[source,c]
+-----
+void rtnl_tc_set_mtu(struct rtnl_tc *tc, uint32_t mtu);
+uint32_t rtnl_tc_get_mtu(struct rtnl_tc *tc);
+-----
+
+Overhead::
+The overhead specifies the additional overhead per packet caused by
+the network layer. This value can be used to correct packet size
+calculations if the packet size on the wire does not match the packet
+size seen by the kernel. The default value is 0.
++
+[source,c]
+-----
+void rtnl_tc_set_overhead(struct rtnl_tc *tc, uint32_t overhead);
+uint32_t rtnl_tc_get_overhead(struct rtnl_tc *tc);
+-----
+
+Parent::
+Specifies the parent traffic control object. The parent is identifier
+by its handle. Special values are:
+- `TC_H_ROOT`: attach tc object directly to network device (root
+  qdisc, root classifier)
+- `TC_H_INGRESS`: same as `TC_H_ROOT` but on the ingress side of the
+  network stack.
++
+[source,c]
+-----
+void rtnl_tc_set_parent(struct rtnl_tc *tc, uint32_t parent);
+uint32_t rtnl_tc_get_parent(struct rtnl_tc *tc);
+-----
+
+Statistics::
+Generic statistics, see <<tc_stats>> for additional information.
++
+[source,c]
+-----
+uint64_t rtnl_tc_get_stat(struct rtnl_tc *tc, enum rtnl_tc_stat id);
+-----
+
+[[tc_stats]]
+==== Accessing Statistics
+
+The traffic control object holds a set of generic statistics. Not all
+traffic control modules will make use of all of these statistics. Some
+modules may provide additional statistics via their own APIs.
+
+.Statistic identifiers `(enum rtnl_tc_stat)`
+[cols="m,,", options="header", frame="topbot"]
+|====================================================================
+| ID                 | Type    | Description
+| RTNL_TC_PACKETS    | Counter | Total # of packets transmitted
+| RTNL_TC_BYTES      | Counter | Total # of bytes transmitted
+| RTNL_TC_RATE_BPS   | Rate    | Current bytes/s rate
+| RTNL_TC_RATE_PPS   | Rate    | Current packets/s rate
+| RTNL_TC_QLEN       | Rate    | Current length of the queue
+| RTNL_TC_BACKLOG    | Rate    | # of packets currently backloged
+| RTNL_TC_DROPS      | Counter | # of packets dropped
+| RTNL_TC_REQUEUES   | Counter | # of packets requeued
+| RTNL_TC_OVERLIMITS | Counter | # of packets that exceeded the limit
+|====================================================================
+
+NOTE: `RTNL_TC_RATE_BPS` and `RTNL_TC_RATE_PPS` only return meaningful
+      values if a rate estimator has been configured.
+
+.Usage Example: Retrieving tc statistics
+[source,c]
+-------
+#include <netlink/route/tc.h>
+
+uint64_t drops, qlen;
+
+drops = rtnl_tc_get_stat(TC_CAST(qdisc), RTNL_TC_DROPS);
+qlen  = rtnl_tc_get_stat(TC_CAST(qdisc), RTNL_TC_QLEN);
+-------
+
+==== Rate Table Calculations
+
+[[tc_qdisc]]
+=== Queueing Discipline (qdisc)
+
+.Classless Qdisc
+
+The queueing discipline (qdisc) is used to implement fair queueing,
+priorization or rate control. It provides a _enqueue()_ and
+_dequeue()_ operation. Whenever a network packet leaves the networking
+stack over a network device, be it a physical or virtual device, it
+will be enqueued to a qdisc unless the device is queueless. The
+_enqueue()_ operation is followed by an immediate call to _dequeue()_
+for the same qdisc to eventually retrieve a packet which can be
+scheduled for transmission by the driver. Additionally, the networking
+stack runs a watchdog which polls the qdisc regularly to dequeue and
+send packets even if no new packets are being enqueued.
+
+This additional watchdog is required due to the fact that qdiscs may
+hold on to packets and not return any packets upon _dequeue()_ in
+order to enforce bandwidth restrictions.
+
+image:classless_qdisc_nbands.png[alt="Multiband Qdisc", float="right"]
+
+The figure illustrates a trivial example of a classless qdisc
+consisting of three bands (queues). Use of multiple bands is a common
+technique in qdiscs to implement fair queueing between flows or
+prioritize differentiated services.
+
+Classless qdiscs can be regarded as a blackbox, their inner workings
+can only be steered using the configuration parameters provided by the
+qdisc. There is no way of taking influence on the structure of its
+internal queues itself.
+
+.Classful Qdisc
+
+Classful qdiscs allow for the queueing structure and classification
+process to be created by the user. 
+
+image:classful_qdisc.png["Classful Qdisc"]
+
+The figure above shows a classful qdisc with a classifier attached to
+it which will make the decision whether to enqueue a packet to traffic
+class +1:1+ or +1:2+. Unlike with classless qdiscs, classful qdiscs
+allow the classification process and the structure of the queues to be
+defined by the user. This allows for complex traffic class rules to
+be applied.
+
+.List of Qdisc Implementations
+[options="header", frame="topbot", cols="2,1^,8"]
+|======================================================================
+| Qdisc     | Classful | Description
+| ATM       | Yes      | FIXME
+| Blackhole | No       | This qdisc will drop all packets passed to it.
+| CBQ       | Yes      |
+The CBQ (Class Based Queueing) is a classful qdisc which allows
+creating traffic classes and enforce bandwidth limitations for each
+class.
+| DRR       | Yes      |
+The DRR (Deficit Round Robin) scheduler is a classful qdisc
+impelemting fair queueing. Each class is assigned a quantum specyfing
+the maximum number of bytes that can be served per round.  Unused
+quantum at the end of the round is carried over to the next round.
+| DSMARK   | Yes       | FIXME
+| FIFO     | No        | FIXME
+| GRED     | No        | FIXME
+| HFSC     | Yes       | FIXME
+| HTB      | Yes       | FIXME
+| mq       | Yes       | FIXME
+| multiq   | Yes       | FIXME
+| netem    | No        | FIXME
+| Prio     | Yes       | FIXME
+| RED      | Yes       | FIXME
+| SFQ      | Yes       | FIXME
+| TBF      | Yes       | FIXME
+| teql     | No        | FIXME
+|======================================================================
+
+
+.QDisc API Overview
+[cols="a,a", options="header", frame="topbot"]
+|====================================================================
+| Attribute | C Interface
+|
+Allocation / Freeing::
+|
+[source,c]
+-----
+struct rtnl_qdisc *rtnl_qdisc_alloc(void);
+void rtnl_qdisc_put(struct rtnl_qdisc *qdisc);
+-----
+|
+Addition::
+|
+[source,c]
+-----
+int rtnl_qdisc_build_add_request(struct rtnl_qdisc *qdisc, int flags,
+				 struct nl_msg **result);
+int rtnl_qdisc_add(struct nl_sock *sock, struct rtnl_qdisc *qdisc,
+                   int flags);
+-----
+|
+Modification::
+|
+[source,c]
+-----
+int rtnl_qdisc_build_change_request(struct rtnl_qdisc *old,
+				    struct rtnl_qdisc *new,
+				    struct nl_msg **result);
+int rtnl_qdisc_change(struct nl_sock *sock, struct rtnl_qdisc *old,
+		      struct rtnl_qdisc *new);
+-----
+|
+Deletion::
+|
+[source,c]
+-----
+int rtnl_qdisc_build_delete_request(struct rtnl_qdisc *qdisc,
+				    struct nl_msg **result);
+int rtnl_qdisc_delete(struct nl_sock *sock, struct rtnl_qdisc *qdisc);
+-----
+|
+Cache::
+|
+[source,c]
+-----
+int rtnl_qdisc_alloc_cache(struct nl_sock *sock,
+			   struct nl_cache **cache);
+struct rtnl_qdisc *rtnl_qdisc_get(struct nl_cache *cache, int, uint32_t);
+
+struct rtnl_qdisc *rtnl_qdisc_get_by_parent(struct nl_cache *, int, uint32_t);
+-----
+|====================================================================
+
+[[qdisc_get]]
+==== Retrieving Qdisc Configuration
+
+The function rtnl_qdisc_alloc_cache() is used to retrieve the current
+qdisc configuration in the kernel. It will construct a +RTM_GETQDISC+
+netlink message, requesting the complete list of qdiscs configured in
+the kernel.
+
+[source,c]
+-------
+#include <netlink/route/qdisc.h>
+
+struct nl_cache *all_qdiscs;
+
+if (rtnl_link_alloc_cache(sock, &all_qdiscs) < 0)
+	/* error while retrieving qdisc cfg */
+-------
+
+The cache can be accessed using the following functions:
+
+- Search qdisc with matching ifindex and handle:
++
+[source,c]
+--------
+struct rtnl_qdisc *rtnl_qdisc_get(struct nl_cache *cache, int ifindex, uint32_t handle);
+--------
+- Search qdisc with matching ifindex and parent:
++
+[source,c]
+--------
+struct rtnl_qdisc *rtnl_qdisc_get_by_parent(struct nl_cache *cache, int ifindex , uint32_t parent);
+--------
+- Or any of the generic cache functions (e.g. nl_cache_search(), nl_cache_dump(), etc.)
+
+.Example: Search and print qdisc
+[source,c]
+-------
+struct rtnl_qdisc *qdisc;
+int ifindex;
+
+ifindex = rtnl_link_get_ifindex(eth0_obj);
+
+/* search for qdisc on eth0 with handle 1:0 */
+if (!(qdisc = rtnl_qdisc_get(all_qdiscs, ifindex, TC_HANDLE(1, 0))))
+	/* no such qdisc found */
+
+nl_object_dump(OBJ_CAST(qdisc), NULL);
+
+rtnl_qdisc_put(qdisc);
+-------
+
+[[qdisc_add]]
+==== Adding a Qdisc
+
+In order to add a new qdisc to the kernel, a qdisc object needs to be
+allocated. It will hold all attributes of the new qdisc.
+
+[source,c]
+-----
+#include <netlink/route/qdisc.h>
+
+struct rtnl_qdisc *qdisc;
+
+if (!(qdisc = rtnl_qdisc_alloc()))
+	/* OOM error */
+-----
+
+The next step is to specify all generic qdisc attributes using the tc
+object interface described in the section <<tc_attr>>.
+
+The following attributes must be specified:
+- IfIndex
+- Parent
+- Kind
+
+[source,c]
+-----
+/* Attach qdisc to device eth0 */
+rtnl_tc_set_link(TC_CAST(qdisc), eth0_obj);
+
+/* Make this the root qdisc */
+rtnl_tc_set_parent(TC_CAST(qdisc), TC_H_ROOT);
+
+/* Set qdisc identifier to 1:0, if left unspecified, a handle will be generated by the kernel. */
+rtnl_tc_set_handle(TC_CAST(qdisc), TC_HANDLE(1, 0));
+
+/* Make this a HTB qdisc */
+rtnl_tc_set_kind(TC_CAST(qdisc), "htb");
+-----
+
+After specyfing the qdisc kind (rtnl_tc_set_kind()) the qdisc type
+specific interface can be used to set attributes which are specific
+to the respective qdisc implementations:
+
+[source,c]
+------
+/* HTB feature: Make unclassified packets go to traffic class 1:5 */
+rtnl_htb_set_defcls(qdisc, TC_HANDLE(1, 5));
+------
+
+Finally, the qdisc is ready to be added and can be passed on to the
+function rntl_qdisc_add() which takes care of constructing a netlink
+message requesting the addition of the new qdisc, sends the message to
+the kernel and waits for the response by the kernel. The function
+returns 0 if the qdisc has been added or updated successfully or a
+negative error code if an error occured.
+
+CAUTION: The kernel operation for updating and adding a qdisc is the
+         same. Therefore when calling rtnl_qdisc_add() any existing
+         qdisc with matching handle will be updated unless the flag
+         NLM_F_EXCL is specified.
+
+The following flags may be specified:
+[horizontal]
+NLM_F_CREATE::  Create qdisc if it does not exist, otherwise
+                -NLE_OBJ_NOTFOUND is returned.
+NLM_F_REPLACE:: If another qdisc is already attached to the same
+                parent and their handles mismatch, replace the qdisc
+                instead of returning -EEXIST.
+NLM_F_EXCL::    Return -NLE_EXISTS if a qdisc with matching handles
+                exists already.
+
+WARNING: The function rtnl_qdisc_add() requires administrator
+         privileges.
+
+[source,c]
+------
+/* Submit request to kernel and wait for response */
+err = rtnl_qdisc_add(sock, qdisc, NLM_F_CREATE);
+
+/* Return the qdisc object to free memory resources */
+rtnl_qdisc_put(qdisc);
+
+if (err < 0) {
+	fprintf(stderr, "Unable to add qdisc: %s\n", nl_geterror(err));
+	return err;
+}
+------
+
+==== Deleting a qdisc
+
+[source,c]
+------
+#include <netlink/route/qdisc.h>
+
+struct rtnl_qdisc *qdisc;
+
+qdisc = rtnl_qdisc_alloc();
+
+rtnl_tc_set_link(TC_CAST(qdisc), eth0_obj);
+rtnl_tc_set_parent(TC_CAST(qdisc), TC_H_ROOT);
+
+rtnl_qdisc_delete(sock, qdisc)
+
+rtnl_qdisc_put(qdisc);
+------
+
+WARNING: The function rtnl_qdisc_delete() requires administrator
+         privileges.
+
+
+[[qdisc_htb]]
+==== HTB - Hierarchical Token Bucket
+
+.HTB Qdisc Attributes
+
+Default Class::
+The default class is the fallback class to which all traffic which
+remained unclassified is directed to. If no default class or an
+invalid default class is specified, packets are transmitted directly
+to the next layer (direct transmissions).
++
+[source,c]
+-----
+uint32_t rtnl_htb_get_defcls(struct rtnl_qdisc *qdisc);
+int rtnl_htb_set_defcls(struct rtnl_qdisc *qdisc, uint32_t defcls);
+-----
+
+Rate to Quantum (r2q)::
+TODO
++
+[source,c]
+-----
+uint32_t rtnl_htb_get_rate2quantum(struct rtnl_qdisc *qdisc);
+int rtnl_htb_set_rate2quantum(struct rtnl_qdisc *qdisc, uint32_t rate2quantum);
+-----
+
+
+.HTB Class Attributes
+
+Priority::
++
+[source,c]
+-----
+uint32_t rtnl_htb_get_prio(struct rtnl_class *class);
+int rtnl_htb_set_prio(struct rtnl_class *class, uint32_t prio);
+-----
+
+Rate::
+The rate (bytes/s) specifies the maximum bandwidth an invidivual class
+can use without borrowing. The rate of a class should always be greater
+or erqual than the rate of its children.
++
+[source,c]
+-----
+uint32_t rtnl_htb_get_rate(struct rtnl_class *class);
+int rtnl_htb_set_rate(struct rtnl_class *class, uint32_t ceil);
+-----
+
+Ceil Rate::
+The ceil rate specifies the maximum bandwidth an invidivual class
+can use. This includes bandwidth that is being borrowed from other
+classes. Ceil defaults to the class rate implying that by default
+the class will not borrow. The ceil rate of a class should always
+be greater or erqual than the ceil rate of its children.
++
+[source,c]
+-----
+uint32_t rtnl_htb_get_ceil(struct rtnl_class *class);
+int rtnl_htb_set_ceil(struct rtnl_class *class, uint32_t ceil);
+-----
+
+Burst::
+TODO
++
+[source,c]
+-----
+uint32_t rtnl_htb_get_rbuffer(struct rtnl_class *class);
+int rtnl_htb_set_rbuffer(struct rtnl_class *class, uint32_t burst);
+-----
+
+Ceil Burst::
+TODO
++
+[source,c]
+-----
+uint32_t rtnl_htb_get_bbuffer(struct rtnl_class *class);
+int rtnl_htb_set_bbuffer(struct rtnl_class *class, uint32_t burst);
+-----
+
+Quantum::
+TODO
++
+[source,c]
+-----
+int rtnl_htb_set_quantum(struct rtnl_class *class, uint32_t quantum);
+-----
+
+extern int	rtnl_htb_set_cbuffer(struct rtnl_class *, uint32_t);
+
+
+
+
+[[tc_class]]
+=== Class
+
+[options="header", cols="s,a,a,a,a"]
+|=======================================================================
+|        | UNSPEC             | TC_H_ROOT          | 0:pY  | pX:pY
+| UNSPEC 3+^|
+[horizontal]
+qdisc =:: root-qdisc
+class =:: root-qdisc:0
+|
+[horizontal]
+qdisc =:: pX:0
+class =:: pX:0
+| 0:hY 3+^|
+[horizontal]
+qdisc =:: root-qdisc
+class =:: root-qdisc:hY
+|
+[horizontal]
+qdisc =:: pX:0
+class =:: pX:hY
+| hX:hY 3+^|
+[horizontal]
+qdisc =:: hX:
+class =:: hX:hY
+|
+if pX != hX
+    return -EINVAL
+[horizontal]
+qdisc =:: hX:
+class =:: hX:hY
+|=======================================================================
+
+[[tc_cls]]
+=== Classifier (cls)
+
+TODO
+
+[[tc_classid_mngt]]
+=== ClassID Management
+
+TODO
+
+[[tc_pktloc]]
+=== Packet Location Aliasing (pktloc)
+
+TODO
+
+[[tc_api]]
+=== Traffic Control Module API
+
+TODO
diff --git a/doc/src/hidden.c b/doc/src/hidden.c
new file mode 100644
index 0000000..a63621d
--- /dev/null
+++ b/doc/src/hidden.c
@@ -0,0 +1,42 @@
+/**
+ * \cond skip
+ * vim:syntax=doxygen
+ * \endcond
+
+\page auto_ack_warning Disabling Auto-ACK
+
+\attention Disabling Auto-ACK (nl_socket_disable_auto_ack()) will cause this
+           function to return immediately after sending the netlink message.
+	   The function will not wait for an eventual error message. It is
+	   the responsibility of the caller to handle any error messages or
+	   ACKs returned.
+
+\page pointer_lifetime_warning Pointer Lifetime
+
+\attention The reference counter of the returned object is not incremented.
+           Therefore, the returned pointer is only valid during the lifetime
+	   of the parent object. Increment the reference counter if the object
+	   is supposed to stay around after the parent object was freed.
+
+\page private_struct Private Structure
+
+\note The definition of this structure is private to allow modification
+      without breaking API. Use the designated accessor functions to
+      access individual object attributes.
+
+\page read_only_attribute Read-Only Attribute
+
+\note The attribute this accessor is modifying is a read-only attribute
+      which can not be modified in the kernel. Any changes to the
+      attribute only have an effect on the local copy of the object. The
+      accessor function is provided solely for the purpose of creating
+      objects for comparison and filtering.
+
+\page low_level_api Low Level API
+
+\note This is a low level API function. A high level function implementing
+      the same functionality with a simplified usage pattern exists. This
+      function is available as an alternative if the default library
+      behaviour is not desirable.
+
+*/
diff --git a/doc/src/toc.c b/doc/src/toc.c
new file mode 100644
index 0000000..a91c2a7
--- /dev/null
+++ b/doc/src/toc.c
@@ -0,0 +1,54 @@
+/**
+ * \cond skip
+ * vim:syntax=doxygen
+ * \endcond
+
+\mainpage
+
+\section main_intro Introduction
+
+libnl is a set of libraries to deal with the netlink protocol and some
+of the high level protocols implemented on top of it. The goal is to
+provide APIs on different levels of abstraction. The core library libnl
+provides a fundamental set of functions to deal with sockets, construct
+messages, and send/receive those messages. Additional high level interfaces
+for several individual netlink protocols are provided in separate
+libraries (e.g. "nl-route", "nl-genl", ...).
+
+The library is designed to ensure that all components are optional, i.e.
+even though the core library provides a caching system which allows to
+easly manage objects of any kind, no application is required to use this
+caching system if it has no need for it.
+
+The library was developed and tested on 2.6.x and 3.x kernel releases. It
+may or may not work with older kernel series. Also, although all netlink
+protocols are required to maintain backwards compatibility, this has not
+always achieved and undesired side effects can occur if a recent libnl
+version is used with a considerably older kernel.
+
+\section main_toc Table of Contents
+
+\section main_trees GIT Trees
+
+\subsection tree_dev Development Tree
+
+@code
+git://git.infradead.org/users/tgr/libnl.git
+@endcode
+- Web: http://git.infradead.org/users/tgr/libnl.git
+
+\section main_website Website
+
+- http://www.infradead.org/~tgr/libnl/
+
+\section main_mailinglist Mailinglist
+
+Please post questions and patches to the libnl mailinglist:
+
+@code
+libnl@lists.infradead.org
+@endcode
+
+- Archives: http://canuck.infradead.org/pipermail/libnl/
+
+*/
diff --git a/doc/stylesheets/asciidoc-manpage.css b/doc/stylesheets/asciidoc-manpage.css
new file mode 100644
index 0000000..45eba23
--- /dev/null
+++ b/doc/stylesheets/asciidoc-manpage.css
@@ -0,0 +1,18 @@
+/* Overrides for manpage documents */
+h1 {
+  padding-top: 0.5em;
+  padding-bottom: 0.5em;
+  border-top: 2px solid silver;
+  border-bottom: 2px solid silver;
+}
+h2 {
+  border-style: none;
+}
+div.sectionbody {
+  margin-left: 3em;
+}
+
+@media print {
+  div#toc { display: none; }
+}
+
diff --git a/doc/stylesheets/asciidoc.css b/doc/stylesheets/asciidoc.css
new file mode 100644
index 0000000..2852168
--- /dev/null
+++ b/doc/stylesheets/asciidoc.css
@@ -0,0 +1,526 @@
+/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
+
+/* Default font. */
+body {
+  font-family: Georgia,serif;
+}
+
+/* Title font. */
+h1, h2, h3, h4, h5, h6,
+div.title, caption.title,
+thead, p.table.header,
+#toctitle,
+#author, #revnumber, #revdate, #revremark,
+#footer {
+  /* OLD: font-family: Arial,Helvetica,sans-serif; */
+  font-family: Lucida Grande, Verdana, Geneva, Arial, sans-serif;
+}
+
+body {
+  margin: 1em 5% 1em 5%;
+}
+
+a {
+  /* color: blue; */
+  color: #990000;
+  text-decoration: none;
+}
+a:visited {
+  /* color: fuchsia; */
+}
+
+a:hover {
+	text-decoration: underline;
+}
+
+em {
+  font-style: italic;
+  /* color: navy; */
+}
+
+strong {
+  font-weight: bold;
+  color: black;
+  /* color: #083194; */
+}
+
+h1, h2, h3, h4, h5, h6 {
+  /* color: #527bbd; */
+  color: #990000;
+  margin-top: 1.2em;
+  margin-bottom: 0.5em;
+  line-height: 1.3;
+}
+
+h1, h2, h3 {
+  border-bottom: 2px solid silver;
+}
+h2 {
+  padding-top: 0.5em;
+}
+h3 {
+  float: left;
+}
+h3 + * {
+  clear: left;
+}
+h5 {
+  font-size: 1.0em;
+}
+
+div.sectionbody {
+  margin-left: 0;
+}
+
+hr {
+  border: 1px solid silver;
+}
+
+p {
+  margin-top: 0.5em;
+  margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+  margin-top: 0;
+}
+ul > li     { color: #aaa; }
+ul > li > * { color: black; }
+
+pre {
+  padding: 0;
+  margin: 0;
+}
+
+#author {
+  /* color: #527bbd; */
+  font-weight: bold;
+  font-size: 1.1em;
+}
+#email {
+}
+#revnumber, #revdate, #revremark {
+}
+
+#footer {
+  font-size: small;
+  border-top: 2px solid silver;
+  padding-top: 0.5em;
+  margin-top: 4.0em;
+}
+#footer-text {
+  float: left;
+  padding-bottom: 0.5em;
+}
+#footer-badges {
+  float: right;
+  padding-bottom: 0.5em;
+}
+
+#preamble {
+  margin-top: 1.5em;
+  margin-bottom: 1.5em;
+}
+div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+  margin-top: 1.0em;
+  margin-bottom: 1.5em;
+}
+div.admonitionblock {
+  margin-top: 2.0em;
+  margin-bottom: 2.0em;
+  margin-right: 10%;
+  /* color: #606060; */
+}
+
+div.content { /* Block element content. */
+  padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+  /* OLD: color: #527bbd; */
+  color: #990000;
+  font-weight: bold;
+  text-align: left;
+  margin-top: 1.0em;
+  margin-bottom: 0.5em;
+}
+div.title + * {
+  margin-top: 0;
+}
+
+td div.title:first-child {
+  margin-top: 0.0em;
+}
+div.content div.title:first-child {
+  margin-top: 0.0em;
+}
+div.content + div.title {
+  margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+  background: #ffffee;
+  border: 1px solid #dddddd;
+  border-left: 4px solid #f0f0f0;
+  padding: 0.5em;
+}
+
+div.listingblock > div.content {
+  border: 1px solid #dddddd;
+  /* border-left: 5px solid #f0f0f0; */
+  background: #f8f8f8;
+  padding: 0.5em;
+}
+
+div.quoteblock, div.verseblock {
+  padding-left: 1.0em;
+  margin-left: 1.0em;
+  margin-right: 10%;
+  border-left: 5px solid #f0f0f0;
+  color: #777777;
+}
+
+div.quoteblock > div.attribution {
+  padding-top: 0.5em;
+  text-align: right;
+}
+
+div.verseblock > pre.content {
+  font-family: inherit;
+  font-size: inherit;
+}
+div.verseblock > div.attribution {
+  padding-top: 0.75em;
+  text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+  text-align: left;
+}
+
+div.admonitionblock .icon {
+  vertical-align: top;
+  font-size: 1.1em;
+  font-weight: bold;
+  text-decoration: underline;
+  /* OLD: color: #527bbd; */
+  color: #990000;
+  padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+  padding-left: 0.5em;
+  border-left: 3px solid #dddddd;
+}
+
+div.exampleblock > div.content {
+  border-left: 3px solid #dddddd;
+  padding-left: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; }
+a.image:visited { color: white; }
+
+dl {
+  margin-top: 0.8em;
+  margin-bottom: 0.8em;
+}
+dt {
+  margin-top: 0.5em;
+  margin-bottom: 0;
+  font-style: normal;
+  color: navy;
+}
+dd > *:first-child {
+  margin-top: 0.1em;
+}
+
+ul, ol {
+    list-style-position: outside;
+}
+ol.arabic {
+  list-style-type: decimal;
+}
+ol.loweralpha {
+  list-style-type: lower-alpha;
+}
+ol.upperalpha {
+  list-style-type: upper-alpha;
+}
+ol.lowerroman {
+  list-style-type: lower-roman;
+}
+ol.upperroman {
+  list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+  margin-top: 0.1em;
+  margin-bottom: 0.1em;
+}
+
+tfoot {
+  font-weight: bold;
+}
+td > div.verse {
+  white-space: pre;
+}
+
+div.hdlist {
+  margin-top: 0.8em;
+  margin-bottom: 0.8em;
+}
+div.hdlist tr {
+  padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+  font-weight: bold;
+}
+td.hdlist1 {
+  vertical-align: top;
+  font-style: normal;
+  padding-right: 0.8em;
+  /* color: navy; */
+  color: #990000;
+}
+td.hdlist2 {
+  vertical-align: top;
+}
+div.hdlist.compact tr {
+  margin: 0;
+  padding-bottom: 0;
+}
+
+.comment {
+  background: yellow;
+}
+
+.footnote, .footnoteref {
+  font-size: 0.8em;
+}
+
+span.footnote, span.footnoteref {
+  vertical-align: super;
+}
+
+#footnotes {
+  margin: 20px 0 20px 0;
+  padding: 7px 0 0 0;
+}
+
+#footnotes div.footnote {
+  margin: 0 0 5px 0;
+}
+
+#footnotes hr {
+  border: none;
+  border-top: 1px solid silver;
+  height: 1px;
+  text-align: left;
+  margin-left: 0;
+  width: 20%;
+  min-width: 100px;
+}
+
+div.colist td {
+  padding-right: 0.5em;
+  padding-bottom: 0.3em;
+  vertical-align: top;
+}
+div.colist td img {
+  margin-top: 0.3em;
+}
+
+@media print {
+  #footer-badges { display: none; }
+}
+
+#toc {
+  margin-bottom: 2.5em;
+}
+
+#toctitle {
+  /* color: #527bbd; */
+  color: #990000;
+  font-size: 1.1em;
+  font-weight: bold;
+  margin-top: 1.0em;
+  margin-bottom: 0.1em;
+}
+
+div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+div.toclevel2 {
+  margin-left: 2em;
+  font-size: 0.9em;
+}
+div.toclevel3 {
+  margin-left: 4em;
+  font-size: 0.9em;
+}
+div.toclevel4 {
+  margin-left: 6em;
+  font-size: 0.9em;
+}
+
+span.aqua { color: aqua; }
+span.black { color: black; }
+span.blue { color: blue; }
+span.fuchsia { color: fuchsia; }
+span.gray { color: gray; }
+span.green { color: green; }
+span.lime { color: lime; }
+span.maroon { color: maroon; }
+span.navy { color: navy; }
+span.olive { color: olive; }
+span.purple { color: purple; }
+span.red { color: red; }
+span.silver { color: silver; }
+span.teal { color: teal; }
+span.white { color: white; }
+span.yellow { color: yellow; }
+
+span.aqua-background { background: aqua; }
+span.black-background { background: black; }
+span.blue-background { background: blue; }
+span.fuchsia-background { background: fuchsia; }
+span.gray-background { background: gray; }
+span.green-background { background: green; }
+span.lime-background { background: lime; }
+span.maroon-background { background: maroon; }
+span.navy-background { background: navy; }
+span.olive-background { background: olive; }
+span.purple-background { background: purple; }
+span.red-background { background: red; }
+span.silver-background { background: silver; }
+span.teal-background { background: teal; }
+span.white-background { background: white; }
+span.yellow-background { background: yellow; }
+
+span.big { font-size: 2em; }
+span.small { font-size: 0.6em; }
+
+span.underline { text-decoration: underline; }
+span.overline { text-decoration: overline; }
+span.line-through { text-decoration: line-through; }
+
+
+/*
+ * xhtml11 specific
+ *
+ * */
+
+tt {
+  font-family: monospace;
+  font-size: inherit;
+  /* color: navy; */
+  color: black;
+}
+
+div.tableblock {
+  margin-top: 1.0em;
+  margin-bottom: 1.5em;
+}
+div.tableblock > table {
+  /* border: 3px solid #527bbd; */
+  border: 2px solid #990000;
+}
+thead, p.table.header {
+  font-weight: bold;
+  /* color: #527bbd; */
+  color: #990000;
+}
+p.table {
+  margin-top: 0;
+}
+/* Because the table frame attribute is overriden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+  border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+  border-left-style: none;
+  border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+  border-top-style: none;
+  border-bottom-style: none;
+}
+
+
+/*
+ * html5 specific
+ *
+ * */
+
+.monospaced {
+  font-family: monospace;
+  font-size: inherit;
+  color: navy;
+}
+
+table.tableblock {
+  margin-top: 1.0em;
+  margin-bottom: 1.5em;
+}
+thead, p.tableblock.header {
+  font-weight: bold;
+  /* color: #527bbd; */
+  color: #990000;
+}
+p.tableblock {
+  margin-top: 0;
+}
+table.tableblock {
+  border-width: 3px;
+  border-spacing: 0px;
+  border-style: solid;
+  /* border-color: #527bbd; */
+  border-color: #990000;
+  border-collapse: collapse;
+}
+th.tableblock, td.tableblock {
+  border-width: 1px;
+  padding: 4px;
+  border-style: solid;
+  /* border-color: #527bbd; */
+  border-color: #990000;
+}
+
+table.tableblock.frame-topbot {
+  border-left-style: hidden;
+  border-right-style: hidden;
+}
+table.tableblock.frame-sides {
+  border-top-style: hidden;
+  border-bottom-style: hidden;
+}
+table.tableblock.frame-none {
+  border-style: hidden;
+}
+
+th.tableblock.halign-left, td.tableblock.halign-left {
+  text-align: left;
+}
+th.tableblock.halign-center, td.tableblock.halign-center {
+  text-align: center;
+}
+th.tableblock.halign-right, td.tableblock.halign-right {
+  text-align: right;
+}
+
+th.tableblock.valign-top, td.tableblock.valign-top {
+  vertical-align: top;
+}
+th.tableblock.valign-middle, td.tableblock.valign-middle {
+  vertical-align: middle;
+}
+th.tableblock.valign-bottom, td.tableblock.valign-bottom {
+  vertical-align: bottom;
+}
diff --git a/doc/stylesheets/docbook-xsl.css b/doc/stylesheets/docbook-xsl.css
new file mode 100644
index 0000000..6df2944
--- /dev/null
+++ b/doc/stylesheets/docbook-xsl.css
@@ -0,0 +1,322 @@
+/*
+  CSS stylesheet for XHTML produced by DocBook XSL stylesheets.
+  Tested with XSL stylesheets 1.61.2, 1.67.2
+*/
+
+span.strong {
+  font-weight: bold;
+}
+
+body blockquote {
+  margin-top: .75em;
+  line-height: 1.5;
+  margin-bottom: .75em;
+}
+
+html body {
+  margin: 1em 5% 1em 5%;
+  line-height: 1.2;
+}
+
+body div {
+  margin: 0;
+}
+
+h1, h2, h3, h4, h5, h6
+{
+  color: #527bbd;
+  font-family: tahoma, verdana, sans-serif;
+}
+
+div.toc p:first-child,
+div.list-of-figures p:first-child,
+div.list-of-tables p:first-child,
+div.list-of-examples p:first-child,
+div.example p.title,
+div.sidebar p.title
+{
+  font-weight: bold;
+  color: #527bbd;
+  font-family: tahoma, verdana, sans-serif;
+  margin-bottom: 0.2em;
+}
+
+body h1 {
+  margin: .0em 0 0 -4%;
+  line-height: 1.3;
+  border-bottom: 2px solid silver;
+}
+
+body h2 {
+  margin: 0.5em 0 0 -4%;
+  line-height: 1.3;
+  border-bottom: 2px solid silver;
+}
+
+body h3 {
+  margin: .8em 0 0 -3%;
+  line-height: 1.3;
+}
+
+body h4 {
+  margin: .8em 0 0 -3%;
+  line-height: 1.3;
+}
+
+body h5 {
+  margin: .8em 0 0 -2%;
+  line-height: 1.3;
+}
+
+body h6 {
+  margin: .8em 0 0 -1%;
+  line-height: 1.3;
+}
+
+body hr {
+  border: none; /* Broken on IE6 */
+}
+div.footnotes hr {
+  border: 1px solid silver;
+}
+
+div.navheader th, div.navheader td, div.navfooter td {
+  font-family: sans-serif;
+  font-size: 0.9em;
+  font-weight: bold;
+  color: #527bbd;
+}
+div.navheader img, div.navfooter img {
+  border-style: none;
+}
+div.navheader a, div.navfooter a {
+  font-weight: normal;
+}
+div.navfooter hr {
+  border: 1px solid silver;
+}
+
+body td {
+  line-height: 1.2
+}
+
+body th {
+  line-height: 1.2;
+}
+
+ol {
+  line-height: 1.2;
+}
+
+ul, body dir, body menu {
+  line-height: 1.2;
+}
+
+html {
+  margin: 0; 
+  padding: 0;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6 {
+  margin-left: 0
+} 
+
+body pre {
+  margin: 0.5em 10% 0.5em 1em;
+  line-height: 1.0;
+  color: navy;
+}
+
+tt.literal, code.literal {
+  color: navy;
+}
+
+.programlisting, .screen {
+  border: 1px solid silver;
+  background: #f4f4f4;
+  margin: 0.5em 10% 0.5em 0;
+  padding: 0.5em 1em;
+}
+
+div.sidebar {
+  background: #ffffee;
+  margin: 1.0em 10% 0.5em 0;
+  padding: 0.5em 1em;
+  border: 1px solid silver;
+}
+div.sidebar * { padding: 0; }
+div.sidebar div { margin: 0; }
+div.sidebar p.title {
+  margin-top: 0.5em;
+  margin-bottom: 0.2em;
+}
+
+div.bibliomixed {
+  margin: 0.5em 5% 0.5em 1em;
+}
+
+div.glossary dt {
+  font-weight: bold;
+}
+div.glossary dd p {
+  margin-top: 0.2em;
+}
+
+dl {
+  margin: .8em 0;
+  line-height: 1.2;
+}
+
+dt {
+  margin-top: 0.5em;
+}
+
+dt span.term {
+  font-style: normal;
+  color: navy;
+}
+
+div.variablelist dd p {
+  margin-top: 0;
+}
+
+div.itemizedlist li, div.orderedlist li {
+  margin-left: -0.8em;
+  margin-top: 0.5em;
+}
+
+ul, ol {
+    list-style-position: outside;
+}
+
+div.sidebar ul, div.sidebar ol {
+    margin-left: 2.8em;
+}
+
+div.itemizedlist p.title,
+div.orderedlist p.title,
+div.variablelist p.title
+{
+  margin-bottom: -0.8em;
+}
+
+div.revhistory table {
+  border-collapse: collapse;
+  border: none;
+}
+div.revhistory th {
+  border: none;
+  color: #527bbd;
+  font-family: tahoma, verdana, sans-serif;
+}
+div.revhistory td {
+  border: 1px solid silver;
+}
+
+/* Keep TOC and index lines close together. */
+div.toc dl, div.toc dt,
+div.list-of-figures dl, div.list-of-figures dt,
+div.list-of-tables dl, div.list-of-tables dt,
+div.indexdiv dl, div.indexdiv dt
+{
+  line-height: normal;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+/*
+  Table styling does not work because of overriding attributes in
+  generated HTML.
+*/
+div.table table,
+div.informaltable table
+{
+    margin-left: 0;
+    margin-right: 5%;
+    margin-bottom: 0.8em;
+}
+div.informaltable table
+{
+    margin-top: 0.4em
+}
+div.table thead,
+div.table tfoot,
+div.table tbody,
+div.informaltable thead,
+div.informaltable tfoot,
+div.informaltable tbody
+{
+    /* No effect in IE6. */
+    border-top: 3px solid #527bbd;
+    border-bottom: 3px solid #527bbd;
+}
+div.table thead, div.table tfoot,
+div.informaltable thead, div.informaltable tfoot
+{
+    font-weight: bold;
+}
+
+div.mediaobject img {
+    margin-bottom: 0.8em;
+}
+div.figure p.title,
+div.table p.title
+{
+  margin-top: 1em;
+  margin-bottom: 0.4em;
+}
+
+div.calloutlist p
+{
+  margin-top: 0em;
+  margin-bottom: 0.4em;
+}
+
+a img {
+  border-style: none;
+}
+
+@media print {
+  div.navheader, div.navfooter { display: none; }
+}
+
+span.aqua { color: aqua; }
+span.black { color: black; }
+span.blue { color: blue; }
+span.fuchsia { color: fuchsia; }
+span.gray { color: gray; }
+span.green { color: green; }
+span.lime { color: lime; }
+span.maroon { color: maroon; }
+span.navy { color: navy; }
+span.olive { color: olive; }
+span.purple { color: purple; }
+span.red { color: red; }
+span.silver { color: silver; }
+span.teal { color: teal; }
+span.white { color: white; }
+span.yellow { color: yellow; }
+
+span.aqua-background { background: aqua; }
+span.black-background { background: black; }
+span.blue-background { background: blue; }
+span.fuchsia-background { background: fuchsia; }
+span.gray-background { background: gray; }
+span.green-background { background: green; }
+span.lime-background { background: lime; }
+span.maroon-background { background: maroon; }
+span.navy-background { background: navy; }
+span.olive-background { background: olive; }
+span.purple-background { background: purple; }
+span.red-background { background: red; }
+span.silver-background { background: silver; }
+span.teal-background { background: teal; }
+span.white-background { background: white; }
+span.yellow-background { background: yellow; }
+
+span.big { font-size: 2em; }
+span.small { font-size: 0.6em; }
+
+span.underline { text-decoration: underline; }
+span.overline { text-decoration: overline; }
+span.line-through { text-decoration: line-through; }
diff --git a/doc/stylesheets/flask-manpage.css b/doc/stylesheets/flask-manpage.css
new file mode 100644
index 0000000..75a2dda
--- /dev/null
+++ b/doc/stylesheets/flask-manpage.css
@@ -0,0 +1 @@
+/* Empty placeholder file */
diff --git a/doc/stylesheets/flask.css b/doc/stylesheets/flask.css
new file mode 100644
index 0000000..8d33bc4
--- /dev/null
+++ b/doc/stylesheets/flask.css
@@ -0,0 +1,584 @@
+/*
+ * AsciiDoc 'flask' theme for xhtml11 and html5 backends. A shameless knock-off
+ * of the Flask website styling (http://flask.pocoo.org/docs/).
+ *
+ * The implementation is straight-forward, consisting of the asciidoc.css file
+ * followed by theme specific overrides.
+ *
+ * */
+
+
+/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
+
+/* Default font. */
+body {
+  font-family: Georgia,serif;
+}
+
+/* Title font. */
+h1, h2, h3, h4, h5, h6,
+div.title, caption.title,
+thead, p.table.header,
+#toctitle,
+#author, #revnumber, #revdate, #revremark,
+#footer {
+  font-family: Arial,Helvetica,sans-serif;
+}
+
+body {
+  margin: 1em 5% 1em 5%;
+}
+
+a {
+  color: blue;
+  text-decoration: underline;
+}
+a:visited {
+  color: fuchsia;
+}
+
+em {
+  font-style: italic;
+  color: navy;
+}
+
+strong {
+  font-weight: bold;
+  color: #083194;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  color: #527bbd;
+  margin-top: 1.2em;
+  margin-bottom: 0.5em;
+  line-height: 1.3;
+}
+
+h1, h2, h3 {
+  border-bottom: 2px solid silver;
+}
+h2 {
+  padding-top: 0.5em;
+}
+h3 {
+  float: left;
+}
+h3 + * {
+  clear: left;
+}
+h5 {
+  font-size: 1.0em;
+}
+
+div.sectionbody {
+  margin-left: 0;
+}
+
+hr {
+  border: 1px solid silver;
+}
+
+p {
+  margin-top: 0.5em;
+  margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+  margin-top: 0;
+}
+ul > li     { color: #aaa; }
+ul > li > * { color: black; }
+
+pre {
+  padding: 0;
+  margin: 0;
+}
+
+#author {
+  color: #527bbd;
+  font-weight: bold;
+  font-size: 1.1em;
+}
+#email {
+}
+#revnumber, #revdate, #revremark {
+}
+
+#footer {
+  font-size: small;
+  border-top: 2px solid silver;
+  padding-top: 0.5em;
+  margin-top: 4.0em;
+}
+#footer-text {
+  float: left;
+  padding-bottom: 0.5em;
+}
+#footer-badges {
+  float: right;
+  padding-bottom: 0.5em;
+}
+
+#preamble {
+  margin-top: 1.5em;
+  margin-bottom: 1.5em;
+}
+div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+  margin-top: 1.0em;
+  margin-bottom: 1.5em;
+}
+div.admonitionblock {
+  margin-top: 2.0em;
+  margin-bottom: 2.0em;
+  margin-right: 10%;
+  color: #606060;
+}
+
+div.content { /* Block element content. */
+  padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+  color: #527bbd;
+  font-weight: bold;
+  text-align: left;
+  margin-top: 1.0em;
+  margin-bottom: 0.5em;
+}
+div.title + * {
+  margin-top: 0;
+}
+
+td div.title:first-child {
+  margin-top: 0.0em;
+}
+div.content div.title:first-child {
+  margin-top: 0.0em;
+}
+div.content + div.title {
+  margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+  background: #ffffee;
+  border: 1px solid #dddddd;
+  border-left: 4px solid #f0f0f0;
+  padding: 0.5em;
+}
+
+div.listingblock > div.content {
+  border: 1px solid #dddddd;
+  border-left: 5px solid #f0f0f0;
+  background: #f8f8f8;
+  padding: 0.5em;
+}
+
+div.quoteblock, div.verseblock {
+  padding-left: 1.0em;
+  margin-left: 1.0em;
+  margin-right: 10%;
+  border-left: 5px solid #f0f0f0;
+  color: #777777;
+}
+
+div.quoteblock > div.attribution {
+  padding-top: 0.5em;
+  text-align: right;
+}
+
+div.verseblock > pre.content {
+  font-family: inherit;
+  font-size: inherit;
+}
+div.verseblock > div.attribution {
+  padding-top: 0.75em;
+  text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+  text-align: left;
+}
+
+div.admonitionblock .icon {
+  vertical-align: top;
+  font-size: 1.1em;
+  font-weight: bold;
+  text-decoration: underline;
+  color: #527bbd;
+  padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+  padding-left: 0.5em;
+  border-left: 3px solid #dddddd;
+}
+
+div.exampleblock > div.content {
+  border-left: 3px solid #dddddd;
+  padding-left: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; }
+a.image:visited { color: white; }
+
+dl {
+  margin-top: 0.8em;
+  margin-bottom: 0.8em;
+}
+dt {
+  margin-top: 0.5em;
+  margin-bottom: 0;
+  font-style: normal;
+  color: navy;
+}
+dd > *:first-child {
+  margin-top: 0.1em;
+}
+
+ul, ol {
+    list-style-position: outside;
+}
+ol.arabic {
+  list-style-type: decimal;
+}
+ol.loweralpha {
+  list-style-type: lower-alpha;
+}
+ol.upperalpha {
+  list-style-type: upper-alpha;
+}
+ol.lowerroman {
+  list-style-type: lower-roman;
+}
+ol.upperroman {
+  list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+  margin-top: 0.1em;
+  margin-bottom: 0.1em;
+}
+
+tfoot {
+  font-weight: bold;
+}
+td > div.verse {
+  white-space: pre;
+}
+
+div.hdlist {
+  margin-top: 0.8em;
+  margin-bottom: 0.8em;
+}
+div.hdlist tr {
+  padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+  font-weight: bold;
+}
+td.hdlist1 {
+  vertical-align: top;
+  font-style: normal;
+  padding-right: 0.8em;
+  color: navy;
+}
+td.hdlist2 {
+  vertical-align: top;
+}
+div.hdlist.compact tr {
+  margin: 0;
+  padding-bottom: 0;
+}
+
+.comment {
+  background: yellow;
+}
+
+.footnote, .footnoteref {
+  font-size: 0.8em;
+}
+
+span.footnote, span.footnoteref {
+  vertical-align: super;
+}
+
+#footnotes {
+  margin: 20px 0 20px 0;
+  padding: 7px 0 0 0;
+}
+
+#footnotes div.footnote {
+  margin: 0 0 5px 0;
+}
+
+#footnotes hr {
+  border: none;
+  border-top: 1px solid silver;
+  height: 1px;
+  text-align: left;
+  margin-left: 0;
+  width: 20%;
+  min-width: 100px;
+}
+
+div.colist td {
+  padding-right: 0.5em;
+  padding-bottom: 0.3em;
+  vertical-align: top;
+}
+div.colist td img {
+  margin-top: 0.3em;
+}
+
+@media print {
+  #footer-badges { display: none; }
+}
+
+#toc {
+  margin-bottom: 2.5em;
+}
+
+#toctitle {
+  color: #527bbd;
+  font-size: 1.1em;
+  font-weight: bold;
+  margin-top: 1.0em;
+  margin-bottom: 0.1em;
+}
+
+div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+div.toclevel2 {
+  margin-left: 2em;
+  font-size: 0.9em;
+}
+div.toclevel3 {
+  margin-left: 4em;
+  font-size: 0.9em;
+}
+div.toclevel4 {
+  margin-left: 6em;
+  font-size: 0.9em;
+}
+
+span.aqua { color: aqua; }
+span.black { color: black; }
+span.blue { color: blue; }
+span.fuchsia { color: fuchsia; }
+span.gray { color: gray; }
+span.green { color: green; }
+span.lime { color: lime; }
+span.maroon { color: maroon; }
+span.navy { color: navy; }
+span.olive { color: olive; }
+span.purple { color: purple; }
+span.red { color: red; }
+span.silver { color: silver; }
+span.teal { color: teal; }
+span.white { color: white; }
+span.yellow { color: yellow; }
+
+span.aqua-background { background: aqua; }
+span.black-background { background: black; }
+span.blue-background { background: blue; }
+span.fuchsia-background { background: fuchsia; }
+span.gray-background { background: gray; }
+span.green-background { background: green; }
+span.lime-background { background: lime; }
+span.maroon-background { background: maroon; }
+span.navy-background { background: navy; }
+span.olive-background { background: olive; }
+span.purple-background { background: purple; }
+span.red-background { background: red; }
+span.silver-background { background: silver; }
+span.teal-background { background: teal; }
+span.white-background { background: white; }
+span.yellow-background { background: yellow; }
+
+span.big { font-size: 2em; }
+span.small { font-size: 0.6em; }
+
+span.underline { text-decoration: underline; }
+span.overline { text-decoration: overline; }
+span.line-through { text-decoration: line-through; }
+
+
+/*
+ * xhtml11 specific
+ *
+ * */
+
+tt {
+  font-family: monospace;
+  font-size: inherit;
+  color: navy;
+}
+
+div.tableblock {
+  margin-top: 1.0em;
+  margin-bottom: 1.5em;
+}
+div.tableblock > table {
+  border: 3px solid #527bbd;
+}
+thead, p.table.header {
+  font-weight: bold;
+  color: #527bbd;
+}
+p.table {
+  margin-top: 0;
+}
+/* Because the table frame attribute is overriden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+  border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+  border-left-style: none;
+  border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+  border-top-style: none;
+  border-bottom-style: none;
+}
+
+
+/*
+ * html5 specific
+ *
+ * */
+
+.monospaced {
+  font-family: monospace;
+  font-size: inherit;
+  color: navy;
+}
+
+table.tableblock {
+  margin-top: 1.0em;
+  margin-bottom: 1.5em;
+}
+thead, p.tableblock.header {
+  font-weight: bold;
+  color: #527bbd;
+}
+p.tableblock {
+  margin-top: 0;
+}
+table.tableblock {
+  border-width: 3px;
+  border-spacing: 0px;
+  border-style: solid;
+  border-color: #527bbd;
+  border-collapse: collapse;
+}
+th.tableblock, td.tableblock {
+  border-width: 1px;
+  padding: 4px;
+  border-style: solid;
+  border-color: #527bbd;
+}
+
+table.tableblock.frame-topbot {
+  border-left-style: hidden;
+  border-right-style: hidden;
+}
+table.tableblock.frame-sides {
+  border-top-style: hidden;
+  border-bottom-style: hidden;
+}
+table.tableblock.frame-none {
+  border-style: hidden;
+}
+
+th.tableblock.halign-left, td.tableblock.halign-left {
+  text-align: left;
+}
+th.tableblock.halign-center, td.tableblock.halign-center {
+  text-align: center;
+}
+th.tableblock.halign-right, td.tableblock.halign-right {
+  text-align: right;
+}
+
+th.tableblock.valign-top, td.tableblock.valign-top {
+  vertical-align: top;
+}
+th.tableblock.valign-middle, td.tableblock.valign-middle {
+  vertical-align: middle;
+}
+th.tableblock.valign-bottom, td.tableblock.valign-bottom {
+  vertical-align: bottom;
+}
+
+
+/*
+ * Theme specific overrides of the preceding (asciidoc.css) CSS.
+ *
+ */
+body {
+  font-family: Garamond, Georgia, serif;
+  font-size: 17px;
+  color: #3E4349;
+  line-height: 1.3em;
+}
+h1, h2, h3, h4, h5, h6,
+div.title, caption.title,
+thead, p.table.header,
+#toctitle,
+#author, #revnumber, #revdate, #revremark,
+#footer {
+  font-family: Garmond, Georgia, serif;
+  font-weight: normal;
+  border-bottom-width: 0;
+  color: #3E4349;
+}
+div.title, caption.title { color: #596673; font-weight: bold; }
+h1 { font-size: 240%; }
+h2 { font-size: 180%; }
+h3 { font-size: 150%; }
+h4 { font-size: 130%; }
+h5 { font-size: 100%; }
+h6 { font-size: 100%; }
+#header h1 { margin-top: 0; }
+#toc {
+  color: #444444;
+  line-height: 1.5;
+  padding-top: 1.5em;
+}
+#toctitle {
+  font-size: 20px;
+}
+#toc a {
+    border-bottom: 1px dotted #999999;
+    color: #444444 !important;
+    text-decoration: none !important;
+}
+#toc a:hover {
+    border-bottom: 1px solid #6D4100;
+    color: #6D4100 !important;
+    text-decoration: none !important;
+}
+div.toclevel1 { margin-top: 0.2em; font-size: 16px; }
+div.toclevel2 { margin-top: 0.15em; font-size: 14px; }
+em, dt, td.hdlist1 { color: black; }
+strong { color: #3E4349; }
+a { color: #004B6B; text-decoration: none; border-bottom: 1px dotted #004B6B; }
+a:visited { color: #615FA0; border-bottom: 1px dotted #615FA0; }
+a:hover { color: #6D4100; border-bottom: 1px solid #6D4100; }
+div.tableblock > table, table.tableblock { border: 3px solid #E8E8E8; }
+th.tableblock, td.tableblock { border: 1px solid #E8E8E8; }
+ul > li > * { color: #3E4349; }
+pre, tt, .monospaced { font-family: Consolas,Menlo,'Deja Vu Sans Mono','Bitstream Vera Sans Mono',monospace; }
+tt, .monospaced { font-size: 0.9em; color: black;
+}
+div.exampleblock > div.content, div.sidebarblock > div.content, div.listingblock > div.content { border-width: 0 0 0 3px; border-color: #E8E8E8; }
+div.verseblock { border-left-width: 0; margin-left: 3em; }
+div.quoteblock { border-left-width: 3px; margin-left: 0; margin-right: 0;}
+div.admonitionblock td.content { border-left: 3px solid #E8E8E8; }
diff --git a/doc/stylesheets/pygments.css b/doc/stylesheets/pygments.css
new file mode 100644
index 0000000..9ca3659
--- /dev/null
+++ b/doc/stylesheets/pygments.css
@@ -0,0 +1,66 @@
+/* 
+  pygmentize filter
+*/
+.highlight .hll { background-color: #ffffcc }
+.highlight  { background: #f4f4f4; }
+.highlight .c { color: #008800; font-style: italic } /* Comment */
+.highlight .err { border: 1px solid #FF0000 } /* Error */
+.highlight .k { color: #AA22FF; font-weight: bold } /* Keyword */
+.highlight .o { color: #666666 } /* Operator */
+.highlight .cm { color: #008800; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #008800 } /* Comment.Preproc */
+.highlight .c1 { color: #008800; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #008800; font-weight: bold } /* Comment.Special */
+.highlight .gd { color: #A00000 } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gr { color: #FF0000 } /* Generic.Error */
+.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.highlight .gi { color: #00A000 } /* Generic.Inserted */
+.highlight .go { color: #808080 } /* Generic.Output */
+.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.highlight .gt { color: #0040D0 } /* Generic.Traceback */
+.highlight .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */
+.highlight .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */
+.highlight .kn { color: #AA22FF; font-weight: bold } /* Keyword.Namespace */
+.highlight .kp { color: #AA22FF } /* Keyword.Pseudo */
+.highlight .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #00BB00; font-weight: bold } /* Keyword.Type */
+.highlight .m { color: #666666 } /* Literal.Number */
+.highlight .s { color: #BB4444 } /* Literal.String */
+.highlight .na { color: #BB4444 } /* Name.Attribute */
+.highlight .nb { color: #AA22FF } /* Name.Builtin */
+.highlight .nc { color: #0000FF } /* Name.Class */
+.highlight .no { color: #880000 } /* Name.Constant */
+.highlight .nd { color: #AA22FF } /* Name.Decorator */
+.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
+.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #00A000 } /* Name.Function */
+.highlight .nl { color: #A0A000 } /* Name.Label */
+.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
+.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.highlight .nv { color: #B8860B } /* Name.Variable */
+.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
+.highlight .mf { color: #666666 } /* Literal.Number.Float */
+.highlight .mh { color: #666666 } /* Literal.Number.Hex */
+.highlight .mi { color: #666666 } /* Literal.Number.Integer */
+.highlight .mo { color: #666666 } /* Literal.Number.Oct */
+.highlight .sb { color: #BB4444 } /* Literal.String.Backtick */
+.highlight .sc { color: #BB4444 } /* Literal.String.Char */
+.highlight .sd { color: #BB4444; font-style: italic } /* Literal.String.Doc */
+.highlight .s2 { color: #BB4444 } /* Literal.String.Double */
+.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
+.highlight .sh { color: #BB4444 } /* Literal.String.Heredoc */
+.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
+.highlight .sx { color: #008000 } /* Literal.String.Other */
+.highlight .sr { color: #BB6688 } /* Literal.String.Regex */
+.highlight .s1 { color: #BB4444 } /* Literal.String.Single */
+.highlight .ss { color: #B8860B } /* Literal.String.Symbol */
+.highlight .bp { color: #AA22FF } /* Name.Builtin.Pseudo */
+.highlight .vc { color: #B8860B } /* Name.Variable.Class */
+.highlight .vg { color: #B8860B } /* Name.Variable.Global */
+.highlight .vi { color: #B8860B } /* Name.Variable.Instance */
+.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
+
diff --git a/doc/stylesheets/slidy.css b/doc/stylesheets/slidy.css
new file mode 100644
index 0000000..bbb790e
--- /dev/null
+++ b/doc/stylesheets/slidy.css
@@ -0,0 +1,445 @@
+/* slidy.css
+
+   Copyright (c) 2005-2010 W3C (MIT, ERCIM, Keio), All Rights Reserved.
+   W3C liability, trademark, document use and software licensing
+   rules apply, see:
+
+   http://www.w3.org/Consortium/Legal/copyright-documents
+   http://www.w3.org/Consortium/Legal/copyright-software
+*/
+
+/*
+   SJR: 2010-09-29: Modified for AsciiDoc slidy backend.
+   Mostly just commented out stuff that is handled by AsciiDoc's CSS files.
+*/
+
+body
+{
+  margin: 0 0 0 0;
+  padding: 0 0 0 0;
+  width: 100%;
+  height: 100%;
+  color: black;
+  background-color: white;
+/*
+  font-family: "Gill Sans MT", "Gill Sans", GillSans, sans-serif;
+*/
+  font-size: 14pt;
+}
+
+div.toolbar {
+  position: fixed; z-index: 200;
+  top: auto; bottom: 0; left: 0; right: 0;
+  height: 1.2em; text-align: right;
+  padding-left: 1em;
+  padding-right: 1em; 
+  font-size: 60%;
+  color: red;
+  background-color: rgb(240,240,240);
+  border-top: solid 1px rgb(180,180,180);
+}
+
+div.toolbar span.copyright {
+  color: black;
+  margin-left: 0.5em;
+}
+
+div.initial_prompt {
+  position: absolute;
+  z-index: 1000;
+  bottom: 1.2em;
+  width: 90%;
+  background-color: rgb(200,200,200);
+  opacity: 0.35;
+  background-color: rgb(200,200,200, 0.35);
+  cursor: pointer;
+}
+
+div.initial_prompt p.help {
+  text-align: center;
+}
+
+div.initial_prompt p.close {
+  text-align: right;
+  font-style: italic;
+}
+
+div.slidy_toc {
+  position: absolute;
+  z-index: 300;
+  width: 60%;
+  max-width: 30em;
+  height: 30em;
+  overflow: auto;
+  top: auto;
+  right: auto;
+  left: 4em;
+  bottom: 4em;
+  padding: 1em;
+  background: rgb(240,240,240);
+  border-style: solid;
+  border-width: 2px;
+  font-size: 60%;
+}
+
+div.slidy_toc .toc_heading {
+  text-align: center;
+  width: 100%;
+  margin: 0;
+  margin-bottom: 1em;
+  border-bottom-style: solid;
+  border-bottom-color: rgb(180,180,180);
+  border-bottom-width: 1px;
+}
+
+div.slide {
+  z-index: 20;
+  margin: 0 0 0 0;
+  padding-top: 0;
+  padding-bottom: 0;
+  padding-left: 20px;
+  padding-right: 20px;
+  border-width: 0;
+  clear: both;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  line-height: 120%;
+  background-color: transparent;
+}
+
+div.background {
+  display: none;
+}
+
+div.handout {
+  margin-left: 20px;
+  margin-right: 20px;
+}
+
+div.slide.titlepage {
+  text-align: center;
+}
+
+div.slide.titlepage.h1 {
+  padding-top: 10%;
+}
+
+div.slide h1 {
+  padding-left: 0;
+  padding-right: 20pt;
+  padding-top: 4pt;
+  padding-bottom: 4pt;
+  margin-top: 0;
+  margin-left: 0;
+  margin-right: 60pt;
+  margin-bottom: 0.5em;
+  display: block; 
+  font-size: 160%;
+  line-height: 1.2em;
+  background: transparent;
+}
+
+div.toc {
+  position: absolute;
+  top: auto;
+  bottom: 4em;
+  left: 4em;
+  right: auto;
+  width: 60%;
+  max-width: 30em;
+  height: 30em;
+  border: solid thin black;
+  padding: 1em;
+  background: rgb(240,240,240);
+  color: black;
+  z-index: 300;
+  overflow: auto;
+  display: block;
+  visibility: visible;
+}
+
+div.toc-heading {
+  width: 100%;
+  border-bottom: solid 1px rgb(180,180,180);
+  margin-bottom: 1em;
+  text-align: center;
+}
+
+/*
+pre {
+ font-size: 80%;
+ font-weight: bold;
+ line-height: 120%;
+ padding-top: 0.2em;
+ padding-bottom: 0.2em;
+ padding-left: 1em;
+ padding-right: 1em;
+ border-style: solid;
+ border-left-width: 1em;
+ border-top-width: thin;
+ border-right-width: thin;
+ border-bottom-width: thin;
+ border-color: #95ABD0;
+ color: #00428C;
+ background-color: #E4E5E7;
+}
+*/
+
+/*
+li pre { margin-left: 0; }
+
+blockquote { font-style: italic }
+
+img { background-color: transparent }
+
+p.copyright { font-size: smaller }
+*/
+
+.center { text-align: center }
+.footnote { font-size: smaller; margin-left: 2em; }
+
+/*
+a img { border-width: 0; border-style: none }
+*/
+
+a:visited { color: navy }
+a:link { color: navy }
+a:hover { color: red; text-decoration: underline }
+a:active { color: red; text-decoration: underline }
+
+a {text-decoration: none}
+.navbar a:link {color: white}
+.navbar a:visited {color: yellow}
+.navbar a:active {color: red}
+.navbar a:hover {color: red}
+
+/*
+ul { list-style-type: square; }
+ul ul { list-style-type: disc; }
+ul ul ul { list-style-type: circle; }
+ul ul ul ul { list-style-type: disc; }
+li { margin-left: 0.5em; margin-top: 0.5em; }
+li li { font-size: 85%; font-style: italic }
+li li li { font-size: 85%; font-style: normal }
+*/
+
+div dt
+{
+  margin-left: 0;
+  margin-top: 1em;
+  margin-bottom: 0.5em;
+  font-weight: bold;
+}
+div dd
+{
+  margin-left: 2em;
+  margin-bottom: 0.5em;
+}
+
+
+/*
+p,pre,ul,ol,blockquote,h2,h3,h4,h5,h6,dl,table {
+  margin-left: 1em;
+  margin-right: 1em;
+}
+*/
+
+p.subhead { font-weight: bold; margin-top: 2em; }
+
+.smaller { font-size: smaller }
+.bigger { font-size: 130% }
+
+/*
+td,th { padding: 0.2em }
+*/
+
+ul {
+  margin: 0.5em 1.5em 0.5em 1.5em;
+  padding: 0;
+}
+
+ol {
+  margin: 0.5em 1.5em 0.5em 1.5em;
+  padding: 0;
+}
+
+ul { list-style-type: square; }
+ul ul { list-style-type: disc; }
+ul ul ul { list-style-type: circle; }
+ul ul ul ul { list-style-type: disc; }
+
+/*
+ul li { 
+  list-style: square;
+  margin: 0.1em 0em 0.6em 0;
+  padding: 0 0 0 0;
+  line-height: 140%;
+}
+
+ol li { 
+  margin: 0.1em 0em 0.6em 1.5em;
+  padding: 0 0 0 0px;
+  line-height: 140%;
+  list-style-type: decimal;
+}
+
+li ul li { 
+  font-size: 85%; 
+  font-style: italic;
+  list-style-type: disc;
+  background: transparent;
+  padding: 0 0 0 0;
+}
+li li ul li { 
+  font-size: 85%; 
+  font-style: normal;
+  list-style-type: circle;
+  background: transparent;
+  padding: 0 0 0 0;
+}
+li li li ul li {
+  list-style-type: disc;
+  background: transparent;
+  padding: 0 0 0 0;
+}
+
+li ol li {
+  list-style-type: decimal;
+}
+
+
+li li ol li {
+  list-style-type: decimal;
+}
+*/
+
+/*
+ setting class="outline" on ol or ul makes it behave as an
+ ouline list where blocklevel content in li elements is
+ hidden by default and can be expanded or collapsed with
+ mouse click. Set class="expand" on li to override default
+*/
+
+ol.outline li:hover { cursor: pointer }
+ol.outline li.nofold:hover { cursor: default }
+
+ul.outline li:hover { cursor: pointer }
+ul.outline li.nofold:hover { cursor: default }
+
+ol.outline { list-style:decimal; }
+ol.outline ol { list-style-type:lower-alpha }
+
+ol.outline li.nofold {
+  padding: 0 0 0 20px;
+  background: transparent url(../graphics/nofold-dim.gif) no-repeat 0px 0.5em;
+}
+ol.outline li.unfolded {
+  padding: 0 0 0 20px;
+  background: transparent url(../graphics/fold-dim.gif) no-repeat 0px 0.5em;
+}
+ol.outline li.folded {
+  padding: 0 0 0 20px;
+  background: transparent url(../graphics/unfold-dim.gif) no-repeat 0px 0.5em;
+}
+ol.outline li.unfolded:hover {
+  padding: 0 0 0 20px;
+  background: transparent url(../graphics/fold.gif) no-repeat 0px 0.5em;
+}
+ol.outline li.folded:hover {
+  padding: 0 0 0 20px;
+  background: transparent url(../graphics/unfold.gif) no-repeat 0px 0.5em;
+}
+
+ul.outline li.nofold {
+  padding: 0 0 0 20px;
+  background: transparent url(../graphics/nofold-dim.gif) no-repeat 0px 0.5em;
+}
+ul.outline li.unfolded {
+  padding: 0 0 0 20px;
+  background: transparent url(../graphics/fold-dim.gif) no-repeat 0px 0.5em;
+}
+ul.outline li.folded {
+  padding: 0 0 0 20px;
+  background: transparent url(../graphics/unfold-dim.gif) no-repeat 0px 0.5em;
+}
+ul.outline li.unfolded:hover {
+  padding: 0 0 0 20px;
+  background: transparent url(../graphics/fold.gif) no-repeat 0px 0.5em;
+}
+ul.outline li.folded:hover {
+  padding: 0 0 0 20px;
+  background: transparent url(../graphics/unfold.gif) no-repeat 0px 0.5em;
+}
+
+/* for slides with class "title" in table of contents */
+a.titleslide { font-weight: bold; font-style: italic }
+
+/*
+ hide images for work around for save as bug
+ where browsers fail to save images used by CSS
+*/
+img.hidden { display: none; visibility: hidden }
+div.initial_prompt { display: none; visibility: hidden }
+
+  div.slide {
+     visibility: visible;
+     position: inherit;
+  }
+  div.handout {
+     border-top-style: solid;
+     border-top-width: thin;
+     border-top-color: black;
+  }
+
+@media screen {
+  .hidden { display: none; visibility: visible }
+
+  div.slide.hidden { display: block; visibility: visible }
+  div.handout.hidden { display: block; visibility: visible }
+  div.background { display: none; visibility: hidden }
+  body.single_slide div.initial_prompt { display: block; visibility: visible }
+  body.single_slide div.background { display: block; visibility: visible }
+  body.single_slide div.background.hidden { display: none; visibility: hidden }
+  body.single_slide .invisible { visibility: hidden }
+  body.single_slide .hidden { display: none; visibility: hidden }
+  body.single_slide div.slide { position: absolute }
+  body.single_slide div.handout { display: none; visibility: hidden }
+}
+
+@media print {
+  .hidden { display: block; visibility: visible }
+
+/*
+  div.slide pre { font-size: 60%; padding-left: 0.5em; }
+*/
+  div.toolbar { display: none; visibility: hidden; }
+  div.slidy_toc { display: none; visibility: hidden; }
+  div.background { display: none; visibility: hidden; }
+  div.slide { page-break-before: always }
+  /* :first-child isn't reliable for print media */
+  div.slide.first-slide { page-break-before: avoid }
+}
+
+
+/* SJR: AsciiDoc slidy backend tweaks */
+
+ol, ul {
+  margin: 0.8em 1.5em 0.8em 1.8em;
+}
+li > ul, li > ol {
+  margin-top: 0.5em;
+}
+
+.outline > li.folded,
+.outline > li.unfolded {
+  color: #527bbd;
+}
+ul > li{ color: #aaa; }
+ul > li > *, ol > li > * { color: black; }
+
+li {
+  margin-top: 0.5em;
+  margin-bottom: 0.5em;
+}
diff --git a/doc/stylesheets/toc2.css b/doc/stylesheets/toc2.css
new file mode 100644
index 0000000..a1e368b
--- /dev/null
+++ b/doc/stylesheets/toc2.css
@@ -0,0 +1,34 @@
+@media screen {
+  body {
+    max-width: 50em; /* approximately 80 characters wide */
+    margin-left: 16em;
+  }
+
+  #toc {
+    position: fixed;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    width: 13em;
+    padding: 0.5em;
+    padding-bottom: 1.5em;
+    margin: 0;
+    overflow: auto;
+    border-right: 3px solid #f8f8f8;
+  }
+
+  #toc .toclevel1 {
+    margin-top: 0.5em;
+  }
+
+  #toc .toclevel2 {
+    margin-top: 0.25em;
+    display: list-item;
+    /* OLD color: #aaaaaa; */
+    color: #990000;
+  }
+
+  #toctitle {
+    margin-top: 0.5em;
+  }
+}
diff --git a/doc/stylesheets/volnitsky-manpage.css b/doc/stylesheets/volnitsky-manpage.css
new file mode 100644
index 0000000..75a2dda
--- /dev/null
+++ b/doc/stylesheets/volnitsky-manpage.css
@@ -0,0 +1 @@
+/* Empty placeholder file */
diff --git a/doc/stylesheets/volnitsky.css b/doc/stylesheets/volnitsky.css
new file mode 100644
index 0000000..b6c4a15
--- /dev/null
+++ b/doc/stylesheets/volnitsky.css
@@ -0,0 +1,435 @@
+/*
+ * AsciiDoc 'volnitsky' theme for xhtml11 and html5 backends.
+ * Based on css from http://volnitsky.com, which was in turn based on default
+ * theme from AsciiDoc
+ *
+ * FIXME: The stlying is still a bit rough in places.
+ *
+ */
+
+/* Default font. */
+body {
+  font-family: Georgia,"Times New Roman",Times,serif;
+}
+
+/* Title font. */
+h1, h2, h3, h4, h5, h6,
+div.title, caption.title,
+thead, p.table.header,
+#toctitle,
+#author, #revnumber, #revdate, #revremark,
+#footer {
+  font-family: Candara,Arial,sans-serif;
+}
+
+
+#toc a {
+    border-bottom: 1px dotted #999999;
+    color: #3A3A4D !important;
+    text-decoration: none !important;
+}
+#toc a:hover {
+    border-bottom: 1px solid #6D4100;
+    color: #6D4100 !important;
+    text-decoration: none !important;
+}
+a { color: #666688; text-decoration: none; border-bottom: 1px dotted #666688; }
+a:visited { color: #615FA0; border-bottom: 1px dotted #615FA0; }
+a:hover { color: #6D4100; border-bottom: 1px solid #6D4100; }
+
+em {
+  font-style: italic;
+  color: #444466;
+}
+
+strong {
+  font-weight: bold;
+  color: #444466;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  color: #666688;
+  margin-bottom: 0.5em;
+  line-height: 1.3;
+  letter-spacing:+0.15em;
+}
+
+h1, h2, h3 { border-bottom: 2px solid #ccd; }
+h2 { padding-top: 0.5em; }
+h3 { float: left; }
+h3 + * { clear: left; }
+
+div.sectionbody {
+  margin-left: 0;
+}
+
+hr {
+  border: 1px solid #444466;
+}
+
+p {
+  margin-top: 0.5em;
+  margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+  margin-top: 0;
+}
+
+pre {
+  padding: 0;
+  margin: 0;
+}
+
+#author {
+  color: #444466;
+  font-weight: bold;
+  font-size: 1.1em;
+}
+
+#footer {
+  font-size: small;
+  border-top: 2px solid silver;
+  padding-top: 0.5em;
+  margin-top: 4.0em;
+}
+
+#footer-text {
+  float: left;
+  padding-bottom: 0.5em;
+}
+
+#footer-badges {
+  float: right;
+  padding-bottom: 0.5em;
+}
+
+#preamble {
+  margin-top: 1.5em;
+  margin-bottom: 1.5em;
+}
+
+div.tableblock, div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+  margin-top: 1.5em;
+  margin-bottom: 1.5em;
+}
+
+div.admonitionblock {
+  margin-top: 2.5em;
+  margin-bottom: 2.5em;
+}
+
+div.content { /* Block element content. */
+  padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+  color: #444466;
+  font-weight: bold;
+  text-align: left;
+  margin-top: 1.0em;
+  margin-bottom: 0.5em;
+}
+div.title + * {
+  margin-top: 0;
+}
+
+td div.title:first-child {
+  margin-top: 0.0em;
+}
+div.content div.title:first-child {
+  margin-top: 0.0em;
+}
+div.content + div.title {
+  margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+  background: #ffffee;
+  border: 1px solid silver;
+  padding: 0.5em;
+}
+
+div.listingblock > div.content {
+  border: 1px solid silver;
+  background: #f4f4f4;
+  padding: 0.5em;
+}
+
+div.quoteblock {
+  padding-left: 2.0em;
+  margin-right: 10%;
+}
+div.quoteblock > div.attribution {
+  padding-top: 0.5em;
+  text-align: right;
+}
+
+div.verseblock {
+  padding-left: 2.0em;
+  margin-right: 10%;
+}
+div.verseblock > pre.content {
+  font-family: inherit;
+}
+div.verseblock > div.attribution {
+  padding-top: 0.75em;
+  text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+  text-align: left;
+}
+
+div.admonitionblock .icon {
+  vertical-align: top;
+  font-size: 1.1em;
+  font-weight: bold;
+  text-decoration: underline;
+  color: #444466;
+  padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+  padding-left: 0.5em;
+  border-left: 2px solid silver;
+}
+
+div.exampleblock > div.content {
+  border-left: 2px solid silver;
+  padding: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; }
+a.image:visited { color: white; }
+
+dl {
+  margin-top: 0.8em;
+  margin-bottom: 0.8em;
+}
+dt {
+  margin-top: 0.5em;
+  margin-bottom: 0;
+  font-style: normal;
+  color: #444466;
+}
+dd > *:first-child {
+  margin-top: 0.1em;
+}
+
+ul, ol {
+    list-style-position: outside;
+}
+ol.arabic {
+  list-style-type: decimal;
+}
+ol.loweralpha {
+  list-style-type: lower-alpha;
+}
+ol.upperalpha {
+  list-style-type: upper-alpha;
+}
+ol.lowerroman {
+  list-style-type: lower-roman;
+}
+ol.upperroman {
+  list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+  margin-top: 0.1em;
+  margin-bottom: 0.1em;
+}
+
+div.tableblock > table {
+  border: 3px solid #444466;
+}
+thead {
+  font-weight: bold;
+  color: #444466;
+}
+tfoot {
+  font-weight: bold;
+}
+td > div.verse {
+  white-space: pre;
+}
+p.table {
+  margin-top: 0;
+}
+/* Because the table frame attribute is overriden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+  border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+  border-left-style: none;
+  border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+  border-top-style: none;
+  border-bottom-style: none;
+}
+
+
+div.hdlist {
+  margin-top: 0.8em;
+  margin-bottom: 0.8em;
+}
+div.hdlist tr {
+  padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+  font-weight: bold;
+}
+td.hdlist1 {
+  vertical-align: top;
+  font-style: normal;
+  padding-right: 0.8em;
+  color: #444466;
+}
+td.hdlist2 {
+  vertical-align: top;
+}
+div.hdlist.compact tr {
+  margin: 0;
+  padding-bottom: 0;
+}
+
+.comment {
+  background: yellow;
+}
+
+@media print {
+  #footer-badges { display: none; }
+}
+
+#toctitle {
+  color: #666688;
+  font-size: 1.2em;
+  font-weight: bold;
+  margin-top: 1.0em;
+  margin-bottom: 0.1em;
+}
+
+div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 { margin-top: 0; margin-bottom: 0; }
+div.toclevel1 { margin-top: 0.3em; margin-left: 0; font-size: 1.0em; }
+div.toclevel2 { margin-top: 0.25em; margin-left: 2em; font-size: 0.9em; }
+div.toclevel3 { margin-left: 4em; font-size: 0.8em; }
+div.toclevel4 { margin-left: 6em; font-size: 0.8em; }
+
+body {
+  margin: 1em 5%;
+  max-width:  55em;
+  padding-left: 0;
+
+}
+
+.monospaced, tt, div.listingblock > div.content {
+  font-family:  Consolas, "Andale Mono", "Courier New", monospace;
+  color: #004400;
+  background: #f4f4f4;
+  max-width: 80em;
+  line-height: 1.2em;
+}
+
+.paragraph p  {
+  line-height: 1.5em;
+  margin-top: 1em;
+}
+
+.paragraph p, li, dd, .content { max-width: 45em; }
+.admonitionblock               { max-width: 35em; }
+
+div.sectionbody div.ulist > ul > li {
+  list-style-type: square;
+  color: #aaa;
+}
+  div.sectionbody div.ulist >  ul > li > * {
+    color: black;
+    /*font-size: 50%;*/
+  }
+
+
+div.sectionbody div.ulist > ul > li div.ulist > ul > li {
+  color: #ccd ;
+}
+  div.sectionbody div.ulist > ul > li div.ulist > ul > li > * {
+    color: black ;
+  }
+
+em {
+  font-style: normal ! important;
+  font-weight: bold ! important;
+  color: #662222   ! important;
+  letter-spacing:+0.08em ! important;
+}
+
+
+/*
+ * html5 specific
+ *
+ * */
+
+table.tableblock {
+  margin-top: 1.0em;
+  margin-bottom: 1.5em;
+}
+thead, p.tableblock.header {
+  font-weight: bold;
+  color: #666688;
+}
+p.tableblock {
+  margin-top: 0;
+}
+table.tableblock {
+  border-width: 3px;
+  border-spacing: 0px;
+  border-style: solid;
+  border-color: #444466;
+  border-collapse: collapse;
+}
+th.tableblock, td.tableblock {
+  border-width: 1px;
+  padding: 4px;
+  border-style: solid;
+  border-color: #444466;
+}
+
+table.tableblock.frame-topbot {
+  border-left-style: hidden;
+  border-right-style: hidden;
+}
+table.tableblock.frame-sides {
+  border-top-style: hidden;
+  border-bottom-style: hidden;
+}
+table.tableblock.frame-none {
+  border-style: hidden;
+}
+
+th.tableblock.halign-left, td.tableblock.halign-left {
+  text-align: left;
+}
+th.tableblock.halign-center, td.tableblock.halign-center {
+  text-align: center;
+}
+th.tableblock.halign-right, td.tableblock.halign-right {
+  text-align: right;
+}
+
+th.tableblock.valign-top, td.tableblock.valign-top {
+  vertical-align: top;
+}
+th.tableblock.valign-middle, td.tableblock.valign-middle {
+  vertical-align: middle;
+}
+th.tableblock.valign-bottom, td.tableblock.valign-bottom {
+  vertical-align: bottom;
+}
+
+
diff --git a/doc/stylesheets/xhtml11-quirks.css b/doc/stylesheets/xhtml11-quirks.css
new file mode 100644
index 0000000..b3b46d2
--- /dev/null
+++ b/doc/stylesheets/xhtml11-quirks.css
@@ -0,0 +1,43 @@
+/* Workarounds for IE6's broken and incomplete CSS2. */
+
+div.sidebar-content {
+  background: #ffffee;
+  border: 1px solid silver;
+  padding: 0.5em;
+}
+div.sidebar-title, div.image-title {
+  color: #527bbd;
+  font-family: sans-serif;
+  font-weight: bold;
+  margin-top: 0.0em;
+  margin-bottom: 0.5em;
+}
+
+div.listingblock div.content {
+  border: 1px solid silver;
+  background: #f4f4f4;
+  padding: 0.5em;
+}
+
+div.quoteblock-attribution {
+  padding-top: 0.5em;
+  text-align: right;
+}
+
+pre.verseblock-content {
+  font-family: inherit;
+}
+div.verseblock-attribution {
+  padding-top: 0.75em;
+  text-align: left;
+}
+
+div.exampleblock-content {
+  border-left: 3px solid #dddddd;
+  padding-left: 0.5em;
+}
+
+div.imageblock.latex div.image-title { margin-top: 0.5em; }
+
+/* IE6 sets dynamically generated links as visited. */
+div#toc a:visited { color: blue; }
diff --git a/doc/stylesheets/xhtml11.css b/doc/stylesheets/xhtml11.css
new file mode 100644
index 0000000..1e6bf5a
--- /dev/null
+++ b/doc/stylesheets/xhtml11.css
@@ -0,0 +1,333 @@
+/* Debug borders */
+p, li, dt, dd, div, pre, h1, h2, h3, h4, h5, h6 {
+/*
+  border: 1px solid red;
+*/
+}
+
+body {
+  margin: 1em 5% 1em 5%;
+}
+
+a {
+  color: blue;
+  text-decoration: underline;
+}
+a:visited {
+  color: fuchsia;
+}
+
+em {
+  font-style: italic;
+  color: navy;
+}
+
+strong {
+  font-weight: bold;
+  color: #083194;
+}
+
+tt {
+  color: navy;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  color: #527bbd;
+  font-family: sans-serif;
+  margin-top: 1.2em;
+  margin-bottom: 0.5em;
+  line-height: 1.3;
+}
+
+h1, h2, h3 {
+  border-bottom: 2px solid silver;
+}
+h2 {
+  padding-top: 0.5em;
+}
+h3 {
+  float: left;
+}
+h3 + * {
+  clear: left;
+}
+
+div.sectionbody {
+  font-family: serif;
+  margin-left: 0;
+}
+
+hr {
+  border: 1px solid silver;
+}
+
+p {
+  margin-top: 0.5em;
+  margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+  margin-top: 0;
+}
+
+pre {
+  padding: 0;
+  margin: 0;
+}
+
+span#author {
+  color: #527bbd;
+  font-family: sans-serif;
+  font-weight: bold;
+  font-size: 1.1em;
+}
+span#email {
+}
+span#revnumber, span#revdate, span#revremark {
+  font-family: sans-serif;
+}
+
+div#footer {
+  font-family: sans-serif;
+  font-size: small;
+  border-top: 2px solid silver;
+  padding-top: 0.5em;
+  margin-top: 4.0em;
+}
+div#footer-text {
+  float: left;
+  padding-bottom: 0.5em;
+}
+div#footer-badges {
+  float: right;
+  padding-bottom: 0.5em;
+}
+
+div#preamble {
+  margin-top: 1.5em;
+  margin-bottom: 1.5em;
+}
+div.tableblock, div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+  margin-top: 1.5em;
+  margin-bottom: 1.5em;
+}
+div.admonitionblock {
+  margin-top: 2.5em;
+  margin-bottom: 2.5em;
+}
+
+div.content { /* Block element content. */
+  padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+  color: #527bbd;
+  font-family: sans-serif;
+  font-weight: bold;
+  text-align: left;
+  margin-top: 1.0em;
+  margin-bottom: 0.5em;
+}
+div.title + * {
+  margin-top: 0;
+}
+
+td div.title:first-child {
+  margin-top: 0.0em;
+}
+div.content div.title:first-child {
+  margin-top: 0.0em;
+}
+div.content + div.title {
+  margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+  background: #ffffee;
+  border: 1px solid silver;
+  padding: 0.5em;
+}
+
+div.listingblock > div.content {
+  border: 1px solid silver;
+  background: #f4f4f4;
+  padding: 0.5em;
+}
+
+div.quoteblock {
+  padding-left: 2.0em;
+  margin-right: 10%;
+}
+div.quoteblock > div.attribution {
+  padding-top: 0.5em;
+  text-align: right;
+}
+
+div.verseblock {
+  padding-left: 2.0em;
+  margin-right: 10%;
+}
+div.verseblock > div.content {
+  white-space: pre;
+}
+div.verseblock > div.attribution {
+  padding-top: 0.75em;
+  text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+  text-align: left;
+}
+
+div.admonitionblock .icon {
+  vertical-align: top;
+  font-size: 1.1em;
+  font-weight: bold;
+  text-decoration: underline;
+  color: #527bbd;
+  padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+  padding-left: 0.5em;
+  border-left: 2px solid silver;
+}
+
+div.exampleblock > div.content {
+  border-left: 2px solid silver;
+  padding: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; }
+a.image:visited { color: white; }
+
+dl {
+  margin-top: 0.8em;
+  margin-bottom: 0.8em;
+}
+dt {
+  margin-top: 0.5em;
+  margin-bottom: 0;
+  font-style: normal;
+  color: navy;
+}
+dd > *:first-child {
+  margin-top: 0.1em;
+}
+
+ul, ol {
+    list-style-position: outside;
+}
+ol.arabic {
+  list-style-type: decimal;
+}
+ol.loweralpha {
+  list-style-type: lower-alpha;
+}
+ol.upperalpha {
+  list-style-type: upper-alpha;
+}
+ol.lowerroman {
+  list-style-type: lower-roman;
+}
+ol.upperroman {
+  list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+  margin-top: 0.1em;
+  margin-bottom: 0.1em;
+}
+
+div.tableblock > table {
+  border: 3px solid #527bbd;
+}
+thead {
+  font-family: sans-serif;
+  font-weight: bold;
+}
+tfoot {
+  font-weight: bold;
+}
+td > div.verse {
+  white-space: pre;
+}
+p.table {
+  margin-top: 0;
+}
+/* Because the table frame attribute is overriden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+  border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+  border-left-style: none;
+  border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+  border-top-style: none;
+  border-bottom-style: none;
+}
+
+
+div.hdlist {
+  margin-top: 0.8em;
+  margin-bottom: 0.8em;
+}
+div.hdlist tr {
+  padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+  font-weight: bold;
+}
+td.hdlist1 {
+  vertical-align: top;
+  font-style: normal;
+  padding-right: 0.8em;
+  color: navy;
+}
+td.hdlist2 {
+  vertical-align: top;
+}
+div.hdlist.compact tr {
+  margin: 0;
+  padding-bottom: 0;
+}
+
+.comment {
+  background: yellow;
+}
+
+@media print {
+  div#footer-badges { display: none; }
+}
+
+div#toctitle {
+  color: #527bbd;
+  font-family: sans-serif;
+  font-size: 1.1em;
+  font-weight: bold;
+  margin-top: 1.0em;
+  margin-bottom: 0.1em;
+}
+
+div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+div.toclevel2 {
+  margin-left: 2em;
+  font-size: 0.9em;
+}
+div.toclevel3 {
+  margin-left: 4em;
+  font-size: 0.9em;
+}
+div.toclevel4 {
+  margin-left: 6em;
+  font-size: 0.9em;
+}
diff --git a/etc/classid b/etc/classid
new file mode 100644
index 0000000..2203243
--- /dev/null
+++ b/etc/classid
@@ -0,0 +1,45 @@
+###############################################################################
+#
+# ClassID <-> Name Translation Table
+#
+# This file can be used to assign names to classids for easier reference
+# in all libnl tools.
+#
+# Format:
+#   <MAJ:>		<NAME>		# qdisc definition
+#   <MAJ:MIN>		<NAME>		# class deifnition
+#   <NAME:MIN>		<NAME>		# class definition referencing an
+#					  existing qdisc definition.
+#
+# Example:
+#   1:			top		# top -> 1:0
+#   top:1		interactive	# interactive -> 1:1
+#   top:2		www		# www -> 1:2
+#   top:3		bulk		# bulk -> 1:3
+#   2:1			test_class	# test_class -> 2:1
+#
+# Illegal Example:
+#   30:1                classD
+#   classD:2            invalidClass    # classD refers to a class, not a qdisc
+#
+###############################################################################
+
+# <CLASSID>		<NAME>
+
+# Reserved default classids
+0:0			none
+ffff:ffff		root
+ffff:fff1		ingress
+
+#
+# List your classid definitions here:
+#
+
+
+
+###############################################################################
+# List of auto-generated classids
+#
+# DO NOT ADD CLASSID DEFINITIONS BELOW THIS LINE
+#
+# <CLASSID>		<NAME>
diff --git a/etc/pktloc b/etc/pktloc
index db36d40..505c44e 100644
--- a/etc/pktloc
+++ b/etc/pktloc
@@ -2,14 +2,15 @@
 # Location definitions for packet matching
 #
 
-# name		alignment	offset		mask
-ip.version	u8		net+0		0xF0
+# name		alignment	offset		mask		shift
+ip.version	u8		net+0		0xF0		4
 ip.hdrlen	u8		net+0		0x0F
 ip.diffserv	u8		net+1
 ip.length	u16		net+2
 ip.id		u16		net+4
-ip.df		u8		net+6		0x40
-ip.mf		u8		net+6		0x20
+ip.flag.res	u8		net+6		0xff		7
+ip.df		u8		net+6		0x40		6
+ip.mf		u8		net+6		0x20		5
 ip.offset	u16		net+6		0x1FFF
 ip.ttl		u8		net+8
 ip.proto	u8		net+9
@@ -17,18 +18,49 @@
 ip.src		u32		net+12
 ip.dst		u32		net+16
 
+# if ip.ihl > 5
+ip.opts		u32		net+20
+
+
+#
+# IP version 6
+#
+# name		alignment	offset		mask		shift
+ip6.version	u8		net+0		0xF0		4
+ip6.tc		u16		net+0		0xFF0		4
+ip6.flowlabel	u32		net+0		0xFFFFF
+ip6.length	u16		net+4
+ip6.nexthdr	u8		net+6
+ip6.hoplimit	u8		net+7
+ip6.src		16		net+8
+ip6.dst		16		net+24
 
 #
 # Transmission Control Protocol (TCP)
 #
-# name		alignment	offset		mask
+# name		alignment	offset		mask		shift
 tcp.sport	u16		tcp+0
 tcp.dport	u16		tcp+2
 tcp.seq		u32		tcp+4
 tcp.ack		u32		tcp+8
-tcp.off		u8		tcp+12		0xF0
-tcp.reserved	u8		tcp+12		0x0F
-# FLAGS
+
+# Data offset (4 bits)
+tcp.off		u8		tcp+12		0xF0		4
+
+# Reserved [0 0 0] (3 bits)
+tcp.reserved	u8		tcp+12		0x04		1
+
+# ECN [N C E] (3 bits)
+tcp.ecn		u16		tcp+12		0x01C00		6
+
+# Individual TCP flags (0|1) (6 bits in total)
+tcp.flag.urg	u8		tcp+13		0x20		5
+tcp.flag.ack	u8		tcp+13		0x10		4
+tcp.flag.psh	u8		tcp+13		0x08		3
+tcp.flag.rst	u8		tcp+13		0x04		2
+tpc.flag.syn	u8		tcp+13		0x02		1
+tcp.flag.fin	u8		tcp+13		0x01
+
 tcp.win		u16		tcp+14
 tcp.csum	u16		tcp+16
 tcp.urg		u16		tcp+18
@@ -37,7 +69,7 @@
 #
 # User Datagram Protocol (UDP)
 #
-# name		alignment	offset		mask
+# name		alignment	offset		mask		shift
 udp.sport	u16		tcp+0
 udp.dport	u16		tcp+2
 udp.length	u16		tcp+4
diff --git a/include/Makefile.am b/include/Makefile.am
index 8815a6e..765cf5a 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,6 +1,8 @@
 # -*- Makefile -*-
 
-nobase_include_HEADERS = \
+libnlincludedir = $(includedir)/libnl@MAJ_VERSION@
+
+nobase_libnlinclude_HEADERS = \
 	netlink/fib_lookup/lookup.h \
 	netlink/fib_lookup/request.h \
 	netlink/genl/ctrl.h \
@@ -8,6 +10,7 @@
 	netlink/genl/genl.h \
 	netlink/genl/mngt.h \
 	netlink/netfilter/ct.h \
+	netlink/netfilter/exp.h \
 	netlink/netfilter/log.h \
 	netlink/netfilter/log_msg.h \
 	netlink/netfilter/netfilter.h \
@@ -16,42 +19,64 @@
 	netlink/netfilter/queue_msg.h \
 	netlink/addr.h \
 	netlink/attr.h \
-	netlink/cache-api.h \
 	netlink/cache.h \
 	netlink/data.h \
 	netlink/errno.h \
 	netlink/handlers.h \
+	netlink/hash.h \
+	netlink/hashtable.h \
 	netlink/list.h \
 	netlink/msg.h \
 	netlink/netlink-compat.h \
 	netlink/netlink-kernel.h \
 	netlink/netlink.h \
-	netlink/object-api.h \
 	netlink/object.h \
+	netlink/route/action.h \
+	netlink/route/act/mirred.h \
+	netlink/route/cls/ematch/cmp.h \
+	netlink/route/cls/ematch/meta.h \
+	netlink/route/cls/ematch/nbyte.h \
+	netlink/route/cls/ematch/text.h \
+	netlink/route/cls/basic.h \
+	netlink/route/cls/cgroup.h \
+	netlink/route/cls/ematch.h \
 	netlink/route/cls/fw.h \
 	netlink/route/cls/police.h \
 	netlink/route/cls/u32.h \
+	netlink/route/link/api.h \
+	netlink/route/link/bonding.h \
+	netlink/route/link/bridge.h \
+	netlink/route/link/can.h \
+	netlink/route/link/inet.h \
 	netlink/route/link/info-api.h \
+	netlink/route/link/macvlan.h \
 	netlink/route/link/vlan.h \
-	netlink/route/sch/cbq.h \
-	netlink/route/sch/dsmark.h \
-	netlink/route/sch/fifo.h \
-	netlink/route/sch/htb.h \
-	netlink/route/sch/netem.h \
-	netlink/route/sch/prio.h \
-	netlink/route/sch/red.h \
-	netlink/route/sch/sfq.h \
-	netlink/route/sch/tbf.h \
+	netlink/route/link/vxlan.h \
+	netlink/route/link/veth.h \
+	netlink/route/link/ip6tnl.h \
+	netlink/route/link/ipgre.h \
+	netlink/route/link/ipip.h \
+	netlink/route/link/ipvti.h \
+	netlink/route/link/sit.h \
+	netlink/route/qdisc/cbq.h \
+	netlink/route/qdisc/dsmark.h \
+	netlink/route/qdisc/fifo.h \
+	netlink/route/qdisc/htb.h \
+	netlink/route/qdisc/netem.h \
+	netlink/route/qdisc/prio.h \
+	netlink/route/qdisc/red.h \
+	netlink/route/qdisc/sfq.h \
+	netlink/route/qdisc/tbf.h \
+	netlink/route/qdisc/plug.h \
+	netlink/route/qdisc/fq_codel.h \
 	netlink/route/addr.h \
-	netlink/route/class-modules.h \
 	netlink/route/class.h \
-	netlink/route/classifier-modules.h \
 	netlink/route/classifier.h \
 	netlink/route/link.h \
 	netlink/route/neighbour.h \
 	netlink/route/neightbl.h \
 	netlink/route/nexthop.h \
-	netlink/route/qdisc-modules.h \
+	netlink/route/pktloc.h \
 	netlink/route/qdisc.h \
 	netlink/route/route.h \
 	netlink/route/rtnl.h \
@@ -60,4 +85,69 @@
 	netlink/socket.h \
 	netlink/types.h \
 	netlink/utils.h \
-	netlink/version.h
+	netlink/version.h \
+	netlink/cache-api.h \
+	netlink/object-api.h \
+	netlink/route/tc-api.h \
+	netlink/idiag/idiagnl.h \
+	netlink/idiag/meminfo.h \
+	netlink/idiag/msg.h \
+	netlink/idiag/req.h \
+	netlink/idiag/vegasinfo.h
+
+if ENABLE_CLI
+nobase_libnlinclude_HEADERS += \
+	netlink/cli/addr.h \
+	netlink/cli/class.h \
+	netlink/cli/cls.h \
+	netlink/cli/ct.h \
+	netlink/cli/exp.h \
+	netlink/cli/link.h \
+	netlink/cli/neigh.h \
+	netlink/cli/qdisc.h \
+	netlink/cli/route.h \
+	netlink/cli/rule.h \
+	netlink/cli/tc.h \
+	netlink/cli/utils.h
+endif
+
+noinst_HEADERS = \
+	linux/fib_rules.h \
+	linux/genetlink.h \
+	linux/gen_stats.h \
+	linux/if_addr.h \
+	linux/if_arp.h \
+	linux/if_ether.h \
+	linux/if.h \
+	linux/if_bridge.h \
+	linux/if_link.h \
+	linux/if_tunnel.h \
+	linux/if_vlan.h \
+	linux/ip.h \
+	linux/ip_mp_alg.h \
+	linux/ipv6.h \
+	linux/can/netlink.h \
+	linux/neighbour.h \
+	linux/netfilter.h \
+	linux/netfilter/nf_conntrack_common.h \
+	linux/netfilter/nfnetlink_compat.h \
+	linux/netfilter/nfnetlink_conntrack.h \
+	linux/netfilter/nfnetlink.h \
+	linux/netfilter/nfnetlink_log.h \
+	linux/netfilter/nfnetlink_queue.h \
+	linux/netlink.h \
+	linux/pkt_cls.h \
+	linux/tc_act/tc_mirred.h \
+	linux/pkt_sched.h \
+	linux/rtnetlink.h \
+	linux/snmp.h \
+	linux/tc_ematch/tc_em_meta.h \
+	netlink-private/genl.h \
+	netlink-private/netlink.h \
+	netlink-private/socket.h \
+	netlink-private/tc.h \
+	netlink-private/types.h \
+	netlink-private/cache-api.h \
+	netlink-private/object-api.h \
+	netlink-private/route/link/api.h \
+	netlink-private/route/tc-api.h
diff --git a/include/defs.h b/include/defs.h
new file mode 100644
index 0000000..62785cb
--- /dev/null
+++ b/include/defs.h
@@ -0,0 +1,86 @@
+/* lib/defs.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define to 1 to disable pthreads */
+#undef DISABLE_PTHREADS
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the `m' library (-lm). */
+#undef HAVE_LIBM
+
+/* Define to 1 if you have the `pthread' library (-lpthread). */
+#undef HAVE_LIBPTHREAD
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+   */
+#undef LT_OBJDIR
+
+/* Define to 1 to enable debugging */
+#undef NL_DEBUG
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+#undef NO_MINUS_C_MINUS_O
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Version number of package */
+#undef VERSION
+
+/* Define to empty if `const' does not conform to ANSI C. */
+#undef const
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+   calls it, or to nothing if 'inline' is not supported under any name.  */
+#ifndef __cplusplus
+#undef inline
+#endif
diff --git a/include/linux-private/linux/can/netlink.h b/include/linux-private/linux/can/netlink.h
new file mode 100644
index 0000000..14966dd
--- /dev/null
+++ b/include/linux-private/linux/can/netlink.h
@@ -0,0 +1,122 @@
+/*
+ * linux/can/netlink.h
+ *
+ * Definitions for the CAN netlink interface
+ *
+ * Copyright (c) 2009 Wolfgang Grandegger <wg@grandegger.com>
+ *
+ */
+
+#ifndef CAN_NETLINK_H
+#define CAN_NETLINK_H
+
+#include <linux/types.h>
+
+/*
+ * CAN bit-timing parameters
+ *
+ * For further information, please read chapter "8 BIT TIMING
+ * REQUIREMENTS" of the "Bosch CAN Specification version 2.0"
+ * at http://www.semiconductors.bosch.de/pdf/can2spec.pdf.
+ */
+struct can_bittiming {
+	__u32 bitrate;		/* Bit-rate in bits/second */
+	__u32 sample_point;	/* Sample point in one-tenth of a percent */
+	__u32 tq;		/* Time quanta (TQ) in nanoseconds */
+	__u32 prop_seg;		/* Propagation segment in TQs */
+	__u32 phase_seg1;	/* Phase buffer segment 1 in TQs */
+	__u32 phase_seg2;	/* Phase buffer segment 2 in TQs */
+	__u32 sjw;		/* Synchronisation jump width in TQs */
+	__u32 brp;		/* Bit-rate prescaler */
+};
+
+/*
+ * CAN harware-dependent bit-timing constant
+ *
+ * Used for calculating and checking bit-timing parameters
+ */
+struct can_bittiming_const {
+	char name[16];		/* Name of the CAN controller hardware */
+	__u32 tseg1_min;	/* Time segement 1 = prop_seg + phase_seg1 */
+	__u32 tseg1_max;
+	__u32 tseg2_min;	/* Time segement 2 = phase_seg2 */
+	__u32 tseg2_max;
+	__u32 sjw_max;		/* Synchronisation jump width */
+	__u32 brp_min;		/* Bit-rate prescaler */
+	__u32 brp_max;
+	__u32 brp_inc;
+};
+
+/*
+ * CAN clock parameters
+ */
+struct can_clock {
+	__u32 freq;		/* CAN system clock frequency in Hz */
+};
+
+/*
+ * CAN operational and error states
+ */
+enum can_state {
+	CAN_STATE_ERROR_ACTIVE = 0,	/* RX/TX error count < 96 */
+	CAN_STATE_ERROR_WARNING,	/* RX/TX error count < 128 */
+	CAN_STATE_ERROR_PASSIVE,	/* RX/TX error count < 256 */
+	CAN_STATE_BUS_OFF,		/* RX/TX error count >= 256 */
+	CAN_STATE_STOPPED,		/* Device is stopped */
+	CAN_STATE_SLEEPING,		/* Device is sleeping */
+	CAN_STATE_MAX
+};
+
+/*
+ * CAN bus error counters
+ */
+struct can_berr_counter {
+	__u16 txerr;
+	__u16 rxerr;
+};
+
+/*
+ * CAN controller mode
+ */
+struct can_ctrlmode {
+	__u32 mask;
+	__u32 flags;
+};
+
+#define CAN_CTRLMODE_LOOPBACK		0x01	/* Loopback mode */
+#define CAN_CTRLMODE_LISTENONLY		0x02 	/* Listen-only mode */
+#define CAN_CTRLMODE_3_SAMPLES		0x04	/* Triple sampling mode */
+#define CAN_CTRLMODE_ONE_SHOT		0x08	/* One-Shot mode */
+#define CAN_CTRLMODE_BERR_REPORTING	0x10	/* Bus-error reporting */
+
+/*
+ * CAN device statistics
+ */
+struct can_device_stats {
+	__u32 bus_error;	/* Bus errors */
+	__u32 error_warning;	/* Changes to error warning state */
+	__u32 error_passive;	/* Changes to error passive state */
+	__u32 bus_off;		/* Changes to bus off state */
+	__u32 arbitration_lost; /* Arbitration lost errors */
+	__u32 restarts;		/* CAN controller re-starts */
+};
+
+/*
+ * CAN netlink interface
+ */
+enum {
+	IFLA_CAN_UNSPEC,
+	IFLA_CAN_BITTIMING,
+	IFLA_CAN_BITTIMING_CONST,
+	IFLA_CAN_CLOCK,
+	IFLA_CAN_STATE,
+	IFLA_CAN_CTRLMODE,
+	IFLA_CAN_RESTART_MS,
+	IFLA_CAN_RESTART,
+	IFLA_CAN_BERR_COUNTER,
+	__IFLA_CAN_MAX
+};
+
+#define IFLA_CAN_MAX	(__IFLA_CAN_MAX - 1)
+
+#endif /* CAN_NETLINK_H */
diff --git a/include/linux-private/linux/fib_rules.h b/include/linux-private/linux/fib_rules.h
new file mode 100644
index 0000000..ed4504a
--- /dev/null
+++ b/include/linux-private/linux/fib_rules.h
@@ -0,0 +1,69 @@
+#ifndef __LINUX_FIB_RULES_H
+#define __LINUX_FIB_RULES_H
+
+/* rule is permanent, and cannot be deleted */
+#define FIB_RULE_PERMANENT	0x00000001
+#define FIB_RULE_INVERT		0x00000002
+#define FIB_RULE_UNRESOLVED	0x00000004
+#define FIB_RULE_IIF_DETACHED	0x00000008
+#define FIB_RULE_DEV_DETACHED	FIB_RULE_IIF_DETACHED
+#define FIB_RULE_OIF_DETACHED	0x00000010
+
+/* try to find source address in routing lookups */
+#define FIB_RULE_FIND_SADDR	0x00010000
+
+struct fib_rule_hdr {
+	__u8		family;
+	__u8		dst_len;
+	__u8		src_len;
+	__u8		tos;
+
+	__u8		table;
+	__u8		res1;	/* reserved */
+	__u8		res2;	/* reserved */
+	__u8		action;
+
+	__u32		flags;
+};
+
+enum {
+	FRA_UNSPEC,
+	FRA_DST,	/* destination address */
+	FRA_SRC,	/* source address */
+	FRA_IIFNAME,	/* interface name */
+#define FRA_IFNAME	FRA_IIFNAME
+	FRA_GOTO,	/* target to jump to (FR_ACT_GOTO) */
+	FRA_UNUSED2,
+	FRA_PRIORITY,	/* priority/preference */
+	FRA_UNUSED3,
+	FRA_UNUSED4,
+	FRA_UNUSED5,
+	FRA_FWMARK,	/* mark */
+	FRA_FLOW,	/* flow/class id */
+	FRA_UNUSED6,
+	FRA_UNUSED7,
+	FRA_UNUSED8,
+	FRA_TABLE,	/* Extended table id */
+	FRA_FWMASK,	/* mask for netfilter mark */
+	FRA_OIFNAME,
+	__FRA_MAX
+};
+
+#define FRA_MAX (__FRA_MAX - 1)
+
+enum {
+	FR_ACT_UNSPEC,
+	FR_ACT_TO_TBL,		/* Pass to fixed table */
+	FR_ACT_GOTO,		/* Jump to another rule */
+	FR_ACT_NOP,		/* No operation */
+	FR_ACT_RES3,
+	FR_ACT_RES4,
+	FR_ACT_BLACKHOLE,	/* Drop without notification */
+	FR_ACT_UNREACHABLE,	/* Drop with ENETUNREACH */
+	FR_ACT_PROHIBIT,	/* Drop with EACCES */
+	__FR_ACT_MAX,
+};
+
+#define FR_ACT_MAX (__FR_ACT_MAX - 1)
+
+#endif
diff --git a/include/linux/gen_stats.h b/include/linux-private/linux/gen_stats.h
similarity index 62%
rename from include/linux/gen_stats.h
rename to include/linux-private/linux/gen_stats.h
index ca60fc1..552c8a0 100644
--- a/include/linux/gen_stats.h
+++ b/include/linux-private/linux/gen_stats.h
@@ -1,6 +1,8 @@
 #ifndef __LINUX_GEN_STATS_H
 #define __LINUX_GEN_STATS_H
 
+#include <linux/types.h>
+
 enum {
 	TCA_STATS_UNSPEC,
 	TCA_STATS_BASIC,
@@ -12,33 +14,38 @@
 #define TCA_STATS_MAX (__TCA_STATS_MAX - 1)
 
 /**
+ * struct gnet_stats_basic - byte/packet throughput statistics
  * @bytes: number of seen bytes
  * @packets: number of seen packets
  */
-struct gnet_stats_basic
-{
+struct gnet_stats_basic {
 	__u64	bytes;
 	__u32	packets;
 };
+struct gnet_stats_basic_packed {
+	__u64	bytes;
+	__u32	packets;
+} __attribute__ ((packed));
 
 /**
+ * struct gnet_stats_rate_est - rate estimator
  * @bps: current byte rate
  * @pps: current packet rate
  */
-struct gnet_stats_rate_est
-{
+struct gnet_stats_rate_est {
 	__u32	bps;
 	__u32	pps;
 };
 
 /**
+ * struct gnet_stats_queue - queuing statistics
  * @qlen: queue length
  * @backlog: backlog size of queue
  * @drops: number of dropped packets
  * @requeues: number of requeues
+ * @overlimits: number of enqueues over the limit
  */
-struct gnet_stats_queue
-{
+struct gnet_stats_queue {
 	__u32	qlen;
 	__u32	backlog;
 	__u32	drops;
@@ -47,11 +54,11 @@
 };
 
 /**
+ * struct gnet_estimator - rate estimator configuration
  * @interval: sampling period
  * @ewma_log: the log of measurement window weight
  */
-struct gnet_estimator
-{
+struct gnet_estimator {
 	signed char	interval;
 	unsigned char	ewma_log;
 };
diff --git a/include/linux/genetlink.h b/include/linux-private/linux/genetlink.h
similarity index 98%
rename from include/linux/genetlink.h
rename to include/linux-private/linux/genetlink.h
index 7da02c9..b834ef6 100644
--- a/include/linux/genetlink.h
+++ b/include/linux-private/linux/genetlink.h
@@ -1,6 +1,7 @@
 #ifndef __LINUX_GENERIC_NETLINK_H
 #define __LINUX_GENERIC_NETLINK_H
 
+#include <linux/types.h>
 #include <linux/netlink.h>
 
 #define GENL_NAMSIZ	16	/* length of family name */
diff --git a/include/linux/if_addr.h b/include/linux-private/linux/if_addr.h
similarity index 78%
rename from include/linux/if_addr.h
rename to include/linux-private/linux/if_addr.h
index 43f3bed..7d4de85 100644
--- a/include/linux/if_addr.h
+++ b/include/linux-private/linux/if_addr.h
@@ -1,10 +1,10 @@
 #ifndef __LINUX_IF_ADDR_H
 #define __LINUX_IF_ADDR_H
 
+#include <linux/types.h>
 #include <linux/netlink.h>
 
-struct ifaddrmsg
-{
+struct ifaddrmsg {
 	__u8		ifa_family;
 	__u8		ifa_prefixlen;	/* The prefix length		*/
 	__u8		ifa_flags;	/* Flags			*/
@@ -18,9 +18,11 @@
  * It makes no difference for normally configured broadcast interfaces,
  * but for point-to-point IFA_ADDRESS is DESTINATION address,
  * local address is supplied in IFA_LOCAL attribute.
+ *
+ * IFA_FLAGS is a u32 attribute that extends the u8 field ifa_flags.
+ * If present, the value from struct ifaddrmsg will be ignored.
  */
-enum
-{
+enum {
 	IFA_UNSPEC,
 	IFA_ADDRESS,
 	IFA_LOCAL,
@@ -29,6 +31,7 @@
 	IFA_ANYCAST,
 	IFA_CACHEINFO,
 	IFA_MULTICAST,
+	IFA_FLAGS,
 	__IFA_MAX,
 };
 
@@ -40,23 +43,19 @@
 
 #define	IFA_F_NODAD		0x02
 #define IFA_F_OPTIMISTIC	0x04
+#define IFA_F_DADFAILED		0x08
 #define	IFA_F_HOMEADDRESS	0x10
 #define IFA_F_DEPRECATED	0x20
 #define IFA_F_TENTATIVE		0x40
 #define IFA_F_PERMANENT		0x80
+#define IFA_F_MANAGETEMPADDR	0x100
+#define IFA_F_NOPREFIXROUTE	0x200
 
-struct ifa_cacheinfo
-{
+struct ifa_cacheinfo {
 	__u32	ifa_prefered;
 	__u32	ifa_valid;
 	__u32	cstamp; /* created timestamp, hundredths of seconds */
 	__u32	tstamp; /* updated timestamp, hundredths of seconds */
 };
 
-/* backwards compatibility for userspace */
-#ifndef __KERNEL__
-#define IFA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))))
-#define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg))
-#endif
-
 #endif
diff --git a/include/linux/if_arp.h b/include/linux-private/linux/if_arp.h
similarity index 92%
rename from include/linux/if_arp.h
rename to include/linux-private/linux/if_arp.h
index 1c7417b..e04cd2c 100644
--- a/include/linux/if_arp.h
+++ b/include/linux-private/linux/if_arp.h
@@ -23,6 +23,8 @@
 #ifndef _LINUX_IF_ARP_H
 #define _LINUX_IF_ARP_H
 
+#include <linux/netdevice.h>
+
 /* ARP protocol HARDWARE identifiers. */
 #define ARPHRD_NETROM	0		/* from KA9Q: NET/ROM pseudo	*/
 #define ARPHRD_ETHER 	1		/* Ethernet 10Mbps		*/
@@ -50,6 +52,7 @@
 #define ARPHRD_ROSE	270
 #define ARPHRD_X25	271		/* CCITT X.25			*/
 #define ARPHRD_HWX25	272		/* Boards with X.25 in firmware	*/
+#define ARPHRD_CAN	280		/* Controller Area Network      */
 #define ARPHRD_PPP	512
 #define ARPHRD_CISCO	513		/* Cisco HDLC	 		*/
 #define ARPHRD_HDLC	ARPHRD_CISCO
@@ -83,6 +86,11 @@
 #define ARPHRD_IEEE80211 801		/* IEEE 802.11			*/
 #define ARPHRD_IEEE80211_PRISM 802	/* IEEE 802.11 + Prism2 header  */
 #define ARPHRD_IEEE80211_RADIOTAP 803	/* IEEE 802.11 + radiotap header */
+#define ARPHRD_IEEE802154	  804
+
+#define ARPHRD_PHONET	820		/* PhoNet media type		*/
+#define ARPHRD_PHONET_PIPE 821		/* PhoNet pipe header		*/
+#define ARPHRD_CAIF	822		/* CAIF media type		*/
 
 #define ARPHRD_VOID	  0xFFFF	/* Void type, nothing is known */
 #define ARPHRD_NONE	  0xFFFE	/* zero header length */
@@ -126,13 +134,12 @@
  *	This structure defines an ethernet arp header.
  */
 
-struct arphdr
-{
-	unsigned short	ar_hrd;		/* format of hardware address	*/
-	unsigned short	ar_pro;		/* format of protocol address	*/
+struct arphdr {
+	__be16		ar_hrd;		/* format of hardware address	*/
+	__be16		ar_pro;		/* format of protocol address	*/
 	unsigned char	ar_hln;		/* length of hardware address	*/
 	unsigned char	ar_pln;		/* length of protocol address	*/
-	unsigned short	ar_op;		/* ARP opcode (command)		*/
+	__be16		ar_op;		/* ARP opcode (command)		*/
 
 #if 0
 	 /*
diff --git a/include/linux/if_bad.h b/include/linux-private/linux/if_bad.h
similarity index 86%
rename from include/linux/if_bad.h
rename to include/linux-private/linux/if_bad.h
index 857ce1e..bbb3ea3 100644
--- a/include/linux/if_bad.h
+++ b/include/linux-private/linux/if_bad.h
@@ -19,9 +19,8 @@
 #ifndef _LINUX_IF_H
 #define _LINUX_IF_H
 
-#include <linux/types.h>		/* for "__kernel_caddr_t" et al	*/
-
 #define	IFNAMSIZ	16
+#define	IFALIASZ	256
 
 /* Standard interface flags (netdevice->flags). */
 #define	IFF_UP		0x1		/* interface is up		*/
@@ -61,6 +60,18 @@
 #define IFF_BONDING	0x20		/* bonding master or slave	*/
 #define IFF_SLAVE_NEEDARP 0x40		/* need ARPs for validation	*/
 #define IFF_ISATAP	0x80		/* ISATAP interface (RFC4214)	*/
+#define IFF_MASTER_ARPMON 0x100		/* bonding master, ARP mon in use */
+#define IFF_WAN_HDLC	0x200		/* WAN HDLC device		*/
+#define IFF_XMIT_DST_RELEASE 0x400	/* dev_hard_start_xmit() is allowed to
+					 * release skb->dst
+					 */
+#define IFF_DONT_BRIDGE 0x800		/* disallow bridging this ether dev */
+#define IFF_IN_NETPOLL	0x1000		/* whether we are processing netpoll */
+#define IFF_DISABLE_NETPOLL	0x2000	/* disable netpoll at run-time */
+#define IFF_MACVLAN_PORT	0x4000	/* device used as macvlan port */
+#define IFF_BRIDGE_PORT	0x8000		/* device used as bridge port */
+#define IFF_OVS_DATAPATH	0x10000	/* device used as Open vSwitch
+					 * datapath port */
 
 #define IF_GET_IFACE	0x0001		/* for querying only */
 #define IF_GET_PROTO	0x0002
@@ -106,6 +117,12 @@
 	IF_LINK_MODE_DORMANT,	/* limit upward transition to dormant */
 };
 
+/* carrier state */
+enum {
+	IF_CARRIER_DOWN,
+	IF_CARRIER_UP
+};
+
 /*
  *	Device mapping structure. I'd just gone off and designed a
  *	beautiful scheme using only loadable modules with arguments
@@ -116,8 +133,7 @@
  *	being very small might be worth keeping for clean configuration.
  */
 
-struct ifmap
-{
+struct ifmap {
 	unsigned long mem_start;
 	unsigned long mem_end;
 	unsigned short base_addr;
@@ -127,5 +143,4 @@
 	/* 3 bytes spare */
 };
 
-
 #endif /* _LINUX_IF_H */
diff --git a/include/linux-private/linux/if_bridge.h b/include/linux-private/linux/if_bridge.h
new file mode 100644
index 0000000..5db2975
--- /dev/null
+++ b/include/linux-private/linux/if_bridge.h
@@ -0,0 +1,185 @@
+/*
+ *	Linux ethernet bridge
+ *
+ *	Authors:
+ *	Lennert Buytenhek		<buytenh@gnu.org>
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version
+ *	2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _UAPI_LINUX_IF_BRIDGE_H
+#define _UAPI_LINUX_IF_BRIDGE_H
+
+#include <linux/types.h>
+
+#define SYSFS_BRIDGE_ATTR	"bridge"
+#define SYSFS_BRIDGE_FDB	"brforward"
+#define SYSFS_BRIDGE_PORT_SUBDIR "brif"
+#define SYSFS_BRIDGE_PORT_ATTR	"brport"
+#define SYSFS_BRIDGE_PORT_LINK	"bridge"
+
+#define BRCTL_VERSION 1
+
+#define BRCTL_GET_VERSION 0
+#define BRCTL_GET_BRIDGES 1
+#define BRCTL_ADD_BRIDGE 2
+#define BRCTL_DEL_BRIDGE 3
+#define BRCTL_ADD_IF 4
+#define BRCTL_DEL_IF 5
+#define BRCTL_GET_BRIDGE_INFO 6
+#define BRCTL_GET_PORT_LIST 7
+#define BRCTL_SET_BRIDGE_FORWARD_DELAY 8
+#define BRCTL_SET_BRIDGE_HELLO_TIME 9
+#define BRCTL_SET_BRIDGE_MAX_AGE 10
+#define BRCTL_SET_AGEING_TIME 11
+#define BRCTL_SET_GC_INTERVAL 12
+#define BRCTL_GET_PORT_INFO 13
+#define BRCTL_SET_BRIDGE_STP_STATE 14
+#define BRCTL_SET_BRIDGE_PRIORITY 15
+#define BRCTL_SET_PORT_PRIORITY 16
+#define BRCTL_SET_PATH_COST 17
+#define BRCTL_GET_FDB_ENTRIES 18
+
+#define BR_STATE_DISABLED 0
+#define BR_STATE_LISTENING 1
+#define BR_STATE_LEARNING 2
+#define BR_STATE_FORWARDING 3
+#define BR_STATE_BLOCKING 4
+
+struct __bridge_info {
+	__u64 designated_root;
+	__u64 bridge_id;
+	__u32 root_path_cost;
+	__u32 max_age;
+	__u32 hello_time;
+	__u32 forward_delay;
+	__u32 bridge_max_age;
+	__u32 bridge_hello_time;
+	__u32 bridge_forward_delay;
+	__u8 topology_change;
+	__u8 topology_change_detected;
+	__u8 root_port;
+	__u8 stp_enabled;
+	__u32 ageing_time;
+	__u32 gc_interval;
+	__u32 hello_timer_value;
+	__u32 tcn_timer_value;
+	__u32 topology_change_timer_value;
+	__u32 gc_timer_value;
+};
+
+struct __port_info {
+	__u64 designated_root;
+	__u64 designated_bridge;
+	__u16 port_id;
+	__u16 designated_port;
+	__u32 path_cost;
+	__u32 designated_cost;
+	__u8 state;
+	__u8 top_change_ack;
+	__u8 config_pending;
+	__u8 unused0;
+	__u32 message_age_timer_value;
+	__u32 forward_delay_timer_value;
+	__u32 hold_timer_value;
+};
+
+struct __fdb_entry {
+	__u8 mac_addr[6];
+	__u8 port_no;
+	__u8 is_local;
+	__u32 ageing_timer_value;
+	__u8 port_hi;
+	__u8 pad0;
+	__u16 unused;
+};
+
+/* Bridge Flags */
+#define BRIDGE_FLAGS_MASTER	1	/* Bridge command to/from master */
+#define BRIDGE_FLAGS_SELF	2	/* Bridge command to/from lowerdev */
+
+#define BRIDGE_MODE_VEB		0	/* Default loopback mode */
+#define BRIDGE_MODE_VEPA	1	/* 802.1Qbg defined VEPA mode */
+
+/* Bridge management nested attributes
+ * [IFLA_AF_SPEC] = {
+ *     [IFLA_BRIDGE_FLAGS]
+ *     [IFLA_BRIDGE_MODE]
+ * }
+ */
+enum {
+	IFLA_BRIDGE_FLAGS,
+	IFLA_BRIDGE_MODE,
+	__IFLA_BRIDGE_MAX,
+};
+#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1)
+
+/* Bridge multicast database attributes
+ * [MDBA_MDB] = {
+ *     [MDBA_MDB_ENTRY] = {
+ *         [MDBA_MDB_ENTRY_INFO]
+ *     }
+ * }
+ * [MDBA_ROUTER] = {
+ *    [MDBA_ROUTER_PORT]
+ * }
+ */
+enum {
+	MDBA_UNSPEC,
+	MDBA_MDB,
+	MDBA_ROUTER,
+	__MDBA_MAX,
+};
+#define MDBA_MAX (__MDBA_MAX - 1)
+
+enum {
+	MDBA_MDB_UNSPEC,
+	MDBA_MDB_ENTRY,
+	__MDBA_MDB_MAX,
+};
+#define MDBA_MDB_MAX (__MDBA_MDB_MAX - 1)
+
+enum {
+	MDBA_MDB_ENTRY_UNSPEC,
+	MDBA_MDB_ENTRY_INFO,
+	__MDBA_MDB_ENTRY_MAX,
+};
+#define MDBA_MDB_ENTRY_MAX (__MDBA_MDB_ENTRY_MAX - 1)
+
+enum {
+	MDBA_ROUTER_UNSPEC,
+	MDBA_ROUTER_PORT,
+	__MDBA_ROUTER_MAX,
+};
+#define MDBA_ROUTER_MAX (__MDBA_ROUTER_MAX - 1)
+
+struct br_port_msg {
+	__u8  family;
+	__u32 ifindex;
+};
+
+struct br_mdb_entry {
+	__u32 ifindex;
+#define MDB_TEMPORARY 0
+#define MDB_PERMANENT 1
+	__u8 state;
+	struct {
+		union {
+			__be32	ip4;
+			struct in6_addr ip6;
+		} u;
+		__be16		proto;
+	} addr;
+};
+
+enum {
+	MDBA_SET_ENTRY_UNSPEC,
+	MDBA_SET_ENTRY,
+	__MDBA_SET_ENTRY_MAX,
+};
+#define MDBA_SET_ENTRY_MAX (__MDBA_SET_ENTRY_MAX - 1)
+
+#endif /* _UAPI_LINUX_IF_BRIDGE_H */
diff --git a/include/linux/if_ether.h b/include/linux-private/linux/if_ether.h
similarity index 79%
rename from include/linux/if_ether.h
rename to include/linux-private/linux/if_ether.h
index ab7df16..a6af32d 100644
--- a/include/linux/if_ether.h
+++ b/include/linux-private/linux/if_ether.h
@@ -9,7 +9,7 @@
  *
  * Author:	Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
  *		Donald Becker, <becker@super.org>
- *		Alan Cox, <alan@redhat.com>
+ *		Alan Cox, <alan@lxorguk.ukuu.org.uk>
  *		Steve Whitehouse, <gw7rrm@eeshack3.swan.ac.uk>
  *
  *		This program is free software; you can redistribute it and/or
@@ -17,13 +17,15 @@
  *		as published by the Free Software Foundation; either version
  *		2 of the License, or (at your option) any later version.
  */
- 
+
 #ifndef _LINUX_IF_ETHER_H
 #define _LINUX_IF_ETHER_H
 
+#include <linux/types.h>
+
 /*
  *	IEEE 802.3 Ethernet magic constants.  The frame sizes omit the preamble
- *	and FCS/CRC (frame check sequence). 
+ *	and FCS/CRC (frame check sequence).
  */
 
 #define ETH_ALEN	6		/* Octets in one ethernet addr	 */
@@ -31,6 +33,7 @@
 #define ETH_ZLEN	60		/* Min. octets in frame sans FCS */
 #define ETH_DATA_LEN	1500		/* Max. octets in payload	 */
 #define ETH_FRAME_LEN	1514		/* Max. octets in frame sans FCS */
+#define ETH_FCS_LEN	4		/* Octets in the FCS		 */
 
 /*
  *	These are the defined Ethernet Protocol ID's.
@@ -53,12 +56,15 @@
 #define ETH_P_DIAG      0x6005          /* DEC Diagnostics              */
 #define ETH_P_CUST      0x6006          /* DEC Customer use             */
 #define ETH_P_SCA       0x6007          /* DEC Systems Comms Arch       */
+#define ETH_P_TEB	0x6558		/* Trans Ether Bridging		*/
 #define ETH_P_RARP      0x8035		/* Reverse Addr Res packet	*/
 #define ETH_P_ATALK	0x809B		/* Appletalk DDP		*/
 #define ETH_P_AARP	0x80F3		/* Appletalk AARP		*/
 #define ETH_P_8021Q	0x8100          /* 802.1Q VLAN Extended Header  */
 #define ETH_P_IPX	0x8137		/* IPX over DIX			*/
 #define ETH_P_IPV6	0x86DD		/* IPv6 over bluebook		*/
+#define ETH_P_PAUSE	0x8808		/* IEEE Pause frames. See 802.3 31B */
+#define ETH_P_SLOW	0x8809		/* Slow Protocol. See 802.3ad 43B */
 #define ETH_P_WCCP	0x883E		/* Web-cache coordination protocol
 					 * defined in draft-wilson-wrec-wccp-v2-00.txt */
 #define ETH_P_PPP_DISC	0x8863		/* PPPoE discovery messages     */
@@ -66,15 +72,22 @@
 #define ETH_P_MPLS_UC	0x8847		/* MPLS Unicast traffic		*/
 #define ETH_P_MPLS_MC	0x8848		/* MPLS Multicast traffic	*/
 #define ETH_P_ATMMPOA	0x884c		/* MultiProtocol Over ATM	*/
+#define ETH_P_LINK_CTL	0x886c		/* HPNA, wlan link local tunnel */
 #define ETH_P_ATMFATE	0x8884		/* Frame-based ATM Transport
 					 * over Ethernet
 					 */
+#define ETH_P_PAE	0x888E		/* Port Access Entity (IEEE 802.1X) */
 #define ETH_P_AOE	0x88A2		/* ATA over Ethernet		*/
+#define ETH_P_TIPC	0x88CA		/* TIPC 			*/
+#define ETH_P_1588	0x88F7		/* IEEE 1588 Timesync */
+#define ETH_P_FCOE	0x8906		/* Fibre Channel over Ethernet  */
+#define ETH_P_FIP	0x8914		/* FCoE Initialization Protocol */
+#define ETH_P_EDSA	0xDADA		/* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
 
 /*
  *	Non DIX types. Won't clash for 1500 types.
  */
- 
+
 #define ETH_P_802_3	0x0001		/* Dummy type for 802.3 frames  */
 #define ETH_P_AX25	0x0002		/* Dummy protocol id for AX.25  */
 #define ETH_P_ALL	0x0003		/* Every packet (be careful!!!) */
@@ -84,6 +97,7 @@
 #define ETH_P_WAN_PPP   0x0007          /* Dummy type for WAN PPP frames*/
 #define ETH_P_PPP_MP    0x0008          /* Dummy type for PPP MP frames */
 #define ETH_P_LOCALTALK 0x0009		/* Localtalk pseudo type 	*/
+#define ETH_P_CAN	0x000C		/* Controller Area Network      */
 #define ETH_P_PPPTALK	0x0010		/* Dummy type for Atalk over PPP*/
 #define ETH_P_TR_802_2	0x0011		/* 802.2 frames 		*/
 #define ETH_P_MOBITEX	0x0015		/* Mobitex (kaz@cafe.net)	*/
@@ -92,15 +106,20 @@
 #define ETH_P_ECONET	0x0018		/* Acorn Econet			*/
 #define ETH_P_HDLC	0x0019		/* HDLC frames			*/
 #define ETH_P_ARCNET	0x001A		/* 1A for ArcNet :-)            */
+#define ETH_P_DSA	0x001B		/* Distributed Switch Arch.	*/
+#define ETH_P_TRAILER	0x001C		/* Trailer switch tagging	*/
+#define ETH_P_PHONET	0x00F5		/* Nokia Phonet frames          */
+#define ETH_P_IEEE802154 0x00F6		/* IEEE802.15.4 frame		*/
+#define ETH_P_CAIF	0x00F7		/* ST-Ericsson CAIF protocol	*/
 
 /*
  *	This is an Ethernet frame header.
  */
- 
+
 struct ethhdr {
 	unsigned char	h_dest[ETH_ALEN];	/* destination eth addr	*/
 	unsigned char	h_source[ETH_ALEN];	/* source ether addr	*/
-	unsigned short	h_proto;		/* packet type ID field	*/
+	__be16		h_proto;		/* packet type ID field	*/
 } __attribute__((packed));
 
 #endif	/* _LINUX_IF_ETHER_H */
diff --git a/include/linux-private/linux/if_link.h b/include/linux-private/linux/if_link.h
new file mode 100644
index 0000000..8b84939
--- /dev/null
+++ b/include/linux-private/linux/if_link.h
@@ -0,0 +1,456 @@
+#ifndef _UAPI_LINUX_IF_LINK_H
+#define _UAPI_LINUX_IF_LINK_H
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+/* This struct should be in sync with struct rtnl_link_stats64 */
+struct rtnl_link_stats {
+	__u32	rx_packets;		/* total packets received	*/
+	__u32	tx_packets;		/* total packets transmitted	*/
+	__u32	rx_bytes;		/* total bytes received 	*/
+	__u32	tx_bytes;		/* total bytes transmitted	*/
+	__u32	rx_errors;		/* bad packets received		*/
+	__u32	tx_errors;		/* packet transmit problems	*/
+	__u32	rx_dropped;		/* no space in linux buffers	*/
+	__u32	tx_dropped;		/* no space available in linux	*/
+	__u32	multicast;		/* multicast packets received	*/
+	__u32	collisions;
+
+	/* detailed rx_errors: */
+	__u32	rx_length_errors;
+	__u32	rx_over_errors;		/* receiver ring buff overflow	*/
+	__u32	rx_crc_errors;		/* recved pkt with crc error	*/
+	__u32	rx_frame_errors;	/* recv'd frame alignment error */
+	__u32	rx_fifo_errors;		/* recv'r fifo overrun		*/
+	__u32	rx_missed_errors;	/* receiver missed packet	*/
+
+	/* detailed tx_errors */
+	__u32	tx_aborted_errors;
+	__u32	tx_carrier_errors;
+	__u32	tx_fifo_errors;
+	__u32	tx_heartbeat_errors;
+	__u32	tx_window_errors;
+
+	/* for cslip etc */
+	__u32	rx_compressed;
+	__u32	tx_compressed;
+};
+
+/* The main device statistics structure */
+struct rtnl_link_stats64 {
+	__u64	rx_packets;		/* total packets received	*/
+	__u64	tx_packets;		/* total packets transmitted	*/
+	__u64	rx_bytes;		/* total bytes received 	*/
+	__u64	tx_bytes;		/* total bytes transmitted	*/
+	__u64	rx_errors;		/* bad packets received		*/
+	__u64	tx_errors;		/* packet transmit problems	*/
+	__u64	rx_dropped;		/* no space in linux buffers	*/
+	__u64	tx_dropped;		/* no space available in linux	*/
+	__u64	multicast;		/* multicast packets received	*/
+	__u64	collisions;
+
+	/* detailed rx_errors: */
+	__u64	rx_length_errors;
+	__u64	rx_over_errors;		/* receiver ring buff overflow	*/
+	__u64	rx_crc_errors;		/* recved pkt with crc error	*/
+	__u64	rx_frame_errors;	/* recv'd frame alignment error */
+	__u64	rx_fifo_errors;		/* recv'r fifo overrun		*/
+	__u64	rx_missed_errors;	/* receiver missed packet	*/
+
+	/* detailed tx_errors */
+	__u64	tx_aborted_errors;
+	__u64	tx_carrier_errors;
+	__u64	tx_fifo_errors;
+	__u64	tx_heartbeat_errors;
+	__u64	tx_window_errors;
+
+	/* for cslip etc */
+	__u64	rx_compressed;
+	__u64	tx_compressed;
+};
+
+/* The struct should be in sync with struct ifmap */
+struct rtnl_link_ifmap {
+	__u64	mem_start;
+	__u64	mem_end;
+	__u64	base_addr;
+	__u16	irq;
+	__u8	dma;
+	__u8	port;
+};
+
+/*
+ * IFLA_AF_SPEC
+ *   Contains nested attributes for address family specific attributes.
+ *   Each address family may create a attribute with the address family
+ *   number as type and create its own attribute structure in it.
+ *
+ *   Example:
+ *   [IFLA_AF_SPEC] = {
+ *       [AF_INET] = {
+ *           [IFLA_INET_CONF] = ...,
+ *       },
+ *       [AF_INET6] = {
+ *           [IFLA_INET6_FLAGS] = ...,
+ *           [IFLA_INET6_CONF] = ...,
+ *       }
+ *   }
+ */
+
+enum {
+	IFLA_UNSPEC,
+	IFLA_ADDRESS,
+	IFLA_BROADCAST,
+	IFLA_IFNAME,
+	IFLA_MTU,
+	IFLA_LINK,
+	IFLA_QDISC,
+	IFLA_STATS,
+	IFLA_COST,
+#define IFLA_COST IFLA_COST
+	IFLA_PRIORITY,
+#define IFLA_PRIORITY IFLA_PRIORITY
+	IFLA_MASTER,
+#define IFLA_MASTER IFLA_MASTER
+	IFLA_WIRELESS,		/* Wireless Extension event - see wireless.h */
+#define IFLA_WIRELESS IFLA_WIRELESS
+	IFLA_PROTINFO,		/* Protocol specific information for a link */
+#define IFLA_PROTINFO IFLA_PROTINFO
+	IFLA_TXQLEN,
+#define IFLA_TXQLEN IFLA_TXQLEN
+	IFLA_MAP,
+#define IFLA_MAP IFLA_MAP
+	IFLA_WEIGHT,
+#define IFLA_WEIGHT IFLA_WEIGHT
+	IFLA_OPERSTATE,
+	IFLA_LINKMODE,
+	IFLA_LINKINFO,
+#define IFLA_LINKINFO IFLA_LINKINFO
+	IFLA_NET_NS_PID,
+	IFLA_IFALIAS,
+	IFLA_NUM_VF,		/* Number of VFs if device is SR-IOV PF */
+	IFLA_VFINFO_LIST,
+	IFLA_STATS64,
+	IFLA_VF_PORTS,
+	IFLA_PORT_SELF,
+	IFLA_AF_SPEC,
+	IFLA_GROUP,		/* Group the device belongs to */
+	IFLA_NET_NS_FD,
+	IFLA_EXT_MASK,		/* Extended info mask, VFs, etc */
+	IFLA_PROMISCUITY,	/* Promiscuity count: > 0 means acts PROMISC */
+#define IFLA_PROMISCUITY IFLA_PROMISCUITY
+	IFLA_NUM_TX_QUEUES,
+	IFLA_NUM_RX_QUEUES,
+	IFLA_CARRIER,
+	IFLA_PHYS_PORT_ID,
+	__IFLA_MAX
+};
+
+
+#define IFLA_MAX (__IFLA_MAX - 1)
+
+enum {
+	IFLA_INET_UNSPEC,
+	IFLA_INET_CONF,
+	__IFLA_INET_MAX,
+};
+
+#define IFLA_INET_MAX (__IFLA_INET_MAX - 1)
+
+/* ifi_flags.
+
+   IFF_* flags.
+
+   The only change is:
+   IFF_LOOPBACK, IFF_BROADCAST and IFF_POINTOPOINT are
+   more not changeable by user. They describe link media
+   characteristics and set by device driver.
+
+   Comments:
+   - Combination IFF_BROADCAST|IFF_POINTOPOINT is invalid
+   - If neither of these three flags are set;
+     the interface is NBMA.
+
+   - IFF_MULTICAST does not mean anything special:
+   multicasts can be used on all not-NBMA links.
+   IFF_MULTICAST means that this media uses special encapsulation
+   for multicast frames. Apparently, all IFF_POINTOPOINT and
+   IFF_BROADCAST devices are able to use multicasts too.
+ */
+
+/* IFLA_LINK.
+   For usual devices it is equal ifi_index.
+   If it is a "virtual interface" (f.e. tunnel), ifi_link
+   can point to real physical interface (f.e. for bandwidth calculations),
+   or maybe 0, what means, that real media is unknown (usual
+   for IPIP tunnels, when route to endpoint is allowed to change)
+ */
+
+/* Subtype attributes for IFLA_PROTINFO */
+enum {
+	IFLA_INET6_UNSPEC,
+	IFLA_INET6_FLAGS,	/* link flags			*/
+	IFLA_INET6_CONF,	/* sysctl parameters		*/
+	IFLA_INET6_STATS,	/* statistics			*/
+	IFLA_INET6_MCAST,	/* MC things. What of them?	*/
+	IFLA_INET6_CACHEINFO,	/* time values and max reasm size */
+	IFLA_INET6_ICMP6STATS,	/* statistics (icmpv6)		*/
+	__IFLA_INET6_MAX
+};
+
+#define IFLA_INET6_MAX	(__IFLA_INET6_MAX - 1)
+
+enum {
+	BRIDGE_MODE_UNSPEC,
+	BRIDGE_MODE_HAIRPIN,
+};
+
+enum {
+	IFLA_BRPORT_UNSPEC,
+	IFLA_BRPORT_STATE,	/* Spanning tree state     */
+	IFLA_BRPORT_PRIORITY,	/* "             priority  */
+	IFLA_BRPORT_COST,	/* "             cost      */
+	IFLA_BRPORT_MODE,	/* mode (hairpin)          */
+	IFLA_BRPORT_GUARD,	/* bpdu guard              */
+	IFLA_BRPORT_PROTECT,	/* root port protection    */
+	IFLA_BRPORT_FAST_LEAVE,	/* multicast fast leave    */
+	__IFLA_BRPORT_MAX
+};
+#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
+
+struct ifla_cacheinfo {
+	__u32	max_reasm_len;
+	__u32	tstamp;		/* ipv6InterfaceTable updated timestamp */
+	__u32	reachable_time;
+	__u32	retrans_time;
+};
+
+enum {
+	IFLA_INFO_UNSPEC,
+	IFLA_INFO_KIND,
+	IFLA_INFO_DATA,
+	IFLA_INFO_XSTATS,
+	__IFLA_INFO_MAX,
+};
+
+#define IFLA_INFO_MAX	(__IFLA_INFO_MAX - 1)
+
+/* VLAN section */
+
+enum {
+	IFLA_VLAN_UNSPEC,
+	IFLA_VLAN_ID,
+	IFLA_VLAN_FLAGS,
+	IFLA_VLAN_EGRESS_QOS,
+	IFLA_VLAN_INGRESS_QOS,
+	IFLA_VLAN_PROTOCOL,
+	__IFLA_VLAN_MAX,
+};
+
+#define IFLA_VLAN_MAX	(__IFLA_VLAN_MAX - 1)
+
+struct ifla_vlan_flags {
+	__u32	flags;
+	__u32	mask;
+};
+
+enum {
+	IFLA_VLAN_QOS_UNSPEC,
+	IFLA_VLAN_QOS_MAPPING,
+	__IFLA_VLAN_QOS_MAX
+};
+
+#define IFLA_VLAN_QOS_MAX	(__IFLA_VLAN_QOS_MAX - 1)
+
+struct ifla_vlan_qos_mapping {
+	__u32 from;
+	__u32 to;
+};
+
+/* MACVLAN section */
+enum {
+	IFLA_MACVLAN_UNSPEC,
+	IFLA_MACVLAN_MODE,
+	IFLA_MACVLAN_FLAGS,
+	__IFLA_MACVLAN_MAX,
+};
+
+#define IFLA_MACVLAN_MAX (__IFLA_MACVLAN_MAX - 1)
+
+enum macvlan_mode {
+	MACVLAN_MODE_PRIVATE = 1, /* don't talk to other macvlans */
+	MACVLAN_MODE_VEPA    = 2, /* talk to other ports through ext bridge */
+	MACVLAN_MODE_BRIDGE  = 4, /* talk to bridge ports directly */
+	MACVLAN_MODE_PASSTHRU = 8,/* take over the underlying device */
+};
+
+#define MACVLAN_FLAG_NOPROMISC	1
+
+/* VXLAN section */
+enum {
+	IFLA_VXLAN_UNSPEC,
+	IFLA_VXLAN_ID,
+	IFLA_VXLAN_GROUP,
+	IFLA_VXLAN_LINK,
+	IFLA_VXLAN_LOCAL,
+	IFLA_VXLAN_TTL,
+	IFLA_VXLAN_TOS,
+	IFLA_VXLAN_LEARNING,
+	IFLA_VXLAN_AGEING,
+	IFLA_VXLAN_LIMIT,
+	IFLA_VXLAN_PORT_RANGE,
+	IFLA_VXLAN_PROXY,
+	IFLA_VXLAN_RSC,
+	IFLA_VXLAN_L2MISS,
+	IFLA_VXLAN_L3MISS,
+	__IFLA_VXLAN_MAX
+};
+#define IFLA_VXLAN_MAX	(__IFLA_VXLAN_MAX - 1)
+
+struct ifla_vxlan_port_range {
+	__be16	low;
+	__be16	high;
+};
+
+enum {
+	VETH_INFO_UNSPEC,
+	VETH_INFO_PEER,
+
+	__VETH_INFO_MAX
+#define VETH_INFO_MAX   (__VETH_INFO_MAX - 1)
+};
+
+/* SR-IOV virtual function management section */
+
+enum {
+	IFLA_VF_INFO_UNSPEC,
+	IFLA_VF_INFO,
+	__IFLA_VF_INFO_MAX,
+};
+
+#define IFLA_VF_INFO_MAX (__IFLA_VF_INFO_MAX - 1)
+
+enum {
+	IFLA_VF_UNSPEC,
+	IFLA_VF_MAC,		/* Hardware queue specific attributes */
+	IFLA_VF_VLAN,
+	IFLA_VF_TX_RATE,	/* TX Bandwidth Allocation */
+	IFLA_VF_SPOOFCHK,	/* Spoof Checking on/off switch */
+	__IFLA_VF_MAX,
+};
+
+#define IFLA_VF_MAX (__IFLA_VF_MAX - 1)
+
+struct ifla_vf_mac {
+	__u32 vf;
+	__u8 mac[32]; /* MAX_ADDR_LEN */
+};
+
+struct ifla_vf_vlan {
+	__u32 vf;
+	__u32 vlan; /* 0 - 4095, 0 disables VLAN filter */
+	__u32 qos;
+};
+
+struct ifla_vf_tx_rate {
+	__u32 vf;
+	__u32 rate; /* Max TX bandwidth in Mbps, 0 disables throttling */
+};
+
+struct ifla_vf_spoofchk {
+	__u32 vf;
+	__u32 setting;
+};
+
+/* VF ports management section
+ *
+ *	Nested layout of set/get msg is:
+ *
+ *		[IFLA_NUM_VF]
+ *		[IFLA_VF_PORTS]
+ *			[IFLA_VF_PORT]
+ *				[IFLA_PORT_*], ...
+ *			[IFLA_VF_PORT]
+ *				[IFLA_PORT_*], ...
+ *			...
+ *		[IFLA_PORT_SELF]
+ *			[IFLA_PORT_*], ...
+ */
+
+enum {
+	IFLA_VF_PORT_UNSPEC,
+	IFLA_VF_PORT,			/* nest */
+	__IFLA_VF_PORT_MAX,
+};
+
+#define IFLA_VF_PORT_MAX (__IFLA_VF_PORT_MAX - 1)
+
+enum {
+	IFLA_PORT_UNSPEC,
+	IFLA_PORT_VF,			/* __u32 */
+	IFLA_PORT_PROFILE,		/* string */
+	IFLA_PORT_VSI_TYPE,		/* 802.1Qbg (pre-)standard VDP */
+	IFLA_PORT_INSTANCE_UUID,	/* binary UUID */
+	IFLA_PORT_HOST_UUID,		/* binary UUID */
+	IFLA_PORT_REQUEST,		/* __u8 */
+	IFLA_PORT_RESPONSE,		/* __u16, output only */
+	__IFLA_PORT_MAX,
+};
+
+#define IFLA_PORT_MAX (__IFLA_PORT_MAX - 1)
+
+#define PORT_PROFILE_MAX	40
+#define PORT_UUID_MAX		16
+#define PORT_SELF_VF		-1
+
+enum {
+	PORT_REQUEST_PREASSOCIATE = 0,
+	PORT_REQUEST_PREASSOCIATE_RR,
+	PORT_REQUEST_ASSOCIATE,
+	PORT_REQUEST_DISASSOCIATE,
+};
+
+enum {
+	PORT_VDP_RESPONSE_SUCCESS = 0,
+	PORT_VDP_RESPONSE_INVALID_FORMAT,
+	PORT_VDP_RESPONSE_INSUFFICIENT_RESOURCES,
+	PORT_VDP_RESPONSE_UNUSED_VTID,
+	PORT_VDP_RESPONSE_VTID_VIOLATION,
+	PORT_VDP_RESPONSE_VTID_VERSION_VIOALTION,
+	PORT_VDP_RESPONSE_OUT_OF_SYNC,
+	/* 0x08-0xFF reserved for future VDP use */
+	PORT_PROFILE_RESPONSE_SUCCESS = 0x100,
+	PORT_PROFILE_RESPONSE_INPROGRESS,
+	PORT_PROFILE_RESPONSE_INVALID,
+	PORT_PROFILE_RESPONSE_BADSTATE,
+	PORT_PROFILE_RESPONSE_INSUFFICIENT_RESOURCES,
+	PORT_PROFILE_RESPONSE_ERROR,
+};
+
+struct ifla_port_vsi {
+	__u8 vsi_mgr_id;
+	__u8 vsi_type_id[3];
+	__u8 vsi_type_version;
+	__u8 pad[3];
+};
+
+
+/* IPoIB section */
+
+enum {
+	IFLA_IPOIB_UNSPEC,
+	IFLA_IPOIB_PKEY,
+	IFLA_IPOIB_MODE,
+	IFLA_IPOIB_UMCAST,
+	__IFLA_IPOIB_MAX
+};
+
+enum {
+	IPOIB_MODE_DATAGRAM  = 0, /* using unreliable datagram QPs */
+	IPOIB_MODE_CONNECTED = 1, /* using connected QPs */
+};
+
+#define IFLA_IPOIB_MAX (__IFLA_IPOIB_MAX - 1)
+
+#endif /* _UAPI_LINUX_IF_LINK_H */
diff --git a/include/linux-private/linux/if_tunnel.h b/include/linux-private/linux/if_tunnel.h
new file mode 100644
index 0000000..aee73d0
--- /dev/null
+++ b/include/linux-private/linux/if_tunnel.h
@@ -0,0 +1,116 @@
+#ifndef _UAPI_IF_TUNNEL_H_
+#define _UAPI_IF_TUNNEL_H_
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+
+#define SIOCGETTUNNEL   (SIOCDEVPRIVATE + 0)
+#define SIOCADDTUNNEL   (SIOCDEVPRIVATE + 1)
+#define SIOCDELTUNNEL   (SIOCDEVPRIVATE + 2)
+#define SIOCCHGTUNNEL   (SIOCDEVPRIVATE + 3)
+#define SIOCGETPRL      (SIOCDEVPRIVATE + 4)
+#define SIOCADDPRL      (SIOCDEVPRIVATE + 5)
+#define SIOCDELPRL      (SIOCDEVPRIVATE + 6)
+#define SIOCCHGPRL      (SIOCDEVPRIVATE + 7)
+#define SIOCGET6RD      (SIOCDEVPRIVATE + 8)
+#define SIOCADD6RD      (SIOCDEVPRIVATE + 9)
+#define SIOCDEL6RD      (SIOCDEVPRIVATE + 10)
+#define SIOCCHG6RD      (SIOCDEVPRIVATE + 11)
+
+#define GRE_CSUM	__cpu_to_be16(0x8000)
+#define GRE_ROUTING	__cpu_to_be16(0x4000)
+#define GRE_KEY		__cpu_to_be16(0x2000)
+#define GRE_SEQ		__cpu_to_be16(0x1000)
+#define GRE_STRICT	__cpu_to_be16(0x0800)
+#define GRE_REC		__cpu_to_be16(0x0700)
+#define GRE_FLAGS	__cpu_to_be16(0x00F8)
+#define GRE_VERSION	__cpu_to_be16(0x0007)
+
+struct ip_tunnel_parm {
+	char			name[IFNAMSIZ];
+	int			link;
+	__be16			i_flags;
+	__be16			o_flags;
+	__be32			i_key;
+	__be32			o_key;
+	struct iphdr		iph;
+};
+
+enum {
+	IFLA_IPTUN_UNSPEC,
+	IFLA_IPTUN_LINK,
+	IFLA_IPTUN_LOCAL,
+	IFLA_IPTUN_REMOTE,
+	IFLA_IPTUN_TTL,
+	IFLA_IPTUN_TOS,
+	IFLA_IPTUN_ENCAP_LIMIT,
+	IFLA_IPTUN_FLOWINFO,
+	IFLA_IPTUN_FLAGS,
+	IFLA_IPTUN_PROTO,
+	IFLA_IPTUN_PMTUDISC,
+	IFLA_IPTUN_6RD_PREFIX,
+	IFLA_IPTUN_6RD_RELAY_PREFIX,
+	IFLA_IPTUN_6RD_PREFIXLEN,
+	IFLA_IPTUN_6RD_RELAY_PREFIXLEN,
+	__IFLA_IPTUN_MAX,
+};
+#define IFLA_IPTUN_MAX	(__IFLA_IPTUN_MAX - 1)
+
+/* SIT-mode i_flags */
+#define	SIT_ISATAP	0x0001
+
+struct ip_tunnel_prl {
+	__be32			addr;
+	__u16			flags;
+	__u16			__reserved;
+	__u32			datalen;
+	__u32			__reserved2;
+	/* data follows */
+};
+
+/* PRL flags */
+#define	PRL_DEFAULT		0x0001
+
+struct ip_tunnel_6rd {
+	struct in6_addr		prefix;
+	__be32			relay_prefix;
+	__u16			prefixlen;
+	__u16			relay_prefixlen;
+};
+
+enum {
+	IFLA_GRE_UNSPEC,
+	IFLA_GRE_LINK,
+	IFLA_GRE_IFLAGS,
+	IFLA_GRE_OFLAGS,
+	IFLA_GRE_IKEY,
+	IFLA_GRE_OKEY,
+	IFLA_GRE_LOCAL,
+	IFLA_GRE_REMOTE,
+	IFLA_GRE_TTL,
+	IFLA_GRE_TOS,
+	IFLA_GRE_PMTUDISC,
+	IFLA_GRE_ENCAP_LIMIT,
+	IFLA_GRE_FLOWINFO,
+	IFLA_GRE_FLAGS,
+	__IFLA_GRE_MAX,
+};
+
+#define IFLA_GRE_MAX	(__IFLA_GRE_MAX - 1)
+
+/* VTI-mode i_flags */
+#define VTI_ISVTI 0x0001
+
+enum {
+	IFLA_VTI_UNSPEC,
+	IFLA_VTI_LINK,
+	IFLA_VTI_IKEY,
+	IFLA_VTI_OKEY,
+	IFLA_VTI_LOCAL,
+	IFLA_VTI_REMOTE,
+	__IFLA_VTI_MAX,
+};
+
+#define IFLA_VTI_MAX	(__IFLA_VTI_MAX - 1)
+#endif /* _UAPI_IF_TUNNEL_H_ */
diff --git a/include/linux/if_vlan.h b/include/linux-private/linux/if_vlan.h
similarity index 95%
rename from include/linux/if_vlan.h
rename to include/linux-private/linux/if_vlan.h
index 068cd7b..67affd1 100644
--- a/include/linux/if_vlan.h
+++ b/include/linux-private/linux/if_vlan.h
@@ -13,7 +13,6 @@
 #ifndef _LINUX_IF_VLAN_H_
 #define _LINUX_IF_VLAN_H_
 
-
 /* VLAN IOCTLs are found in sockios.h */
 
 /* Passed in vlan_ioctl_args structure to determine behaviour. */
@@ -32,6 +31,8 @@
 
 enum vlan_flags {
 	VLAN_FLAG_REORDER_HDR	= 0x1,
+	VLAN_FLAG_GVRP		= 0x2,
+	VLAN_FLAG_LOOSE_BINDING	= 0x4,
 };
 
 enum vlan_name_types {
@@ -55,7 +56,7 @@
 		unsigned int flag; /* Matches vlan_dev_info flags */
         } u;
 
-	short vlan_qos;
+	short vlan_qos;   
 };
 
 #endif /* !(_LINUX_IF_VLAN_H_) */
diff --git a/include/linux-private/linux/ip.h b/include/linux-private/linux/ip.h
new file mode 100644
index 0000000..4119594
--- /dev/null
+++ b/include/linux-private/linux/ip.h
@@ -0,0 +1,172 @@
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Definitions for the IP protocol.
+ *
+ * Version:	@(#)ip.h	1.0.2	04/28/93
+ *
+ * Authors:	Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ */
+#ifndef _UAPI_LINUX_IP_H
+#define _UAPI_LINUX_IP_H
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+#define IPTOS_TOS_MASK		0x1E
+#define IPTOS_TOS(tos)		((tos)&IPTOS_TOS_MASK)
+#define	IPTOS_LOWDELAY		0x10
+#define	IPTOS_THROUGHPUT	0x08
+#define	IPTOS_RELIABILITY	0x04
+#define	IPTOS_MINCOST		0x02
+
+#define IPTOS_PREC_MASK		0xE0
+#define IPTOS_PREC(tos)		((tos)&IPTOS_PREC_MASK)
+#define IPTOS_PREC_NETCONTROL           0xe0
+#define IPTOS_PREC_INTERNETCONTROL      0xc0
+#define IPTOS_PREC_CRITIC_ECP           0xa0
+#define IPTOS_PREC_FLASHOVERRIDE        0x80
+#define IPTOS_PREC_FLASH                0x60
+#define IPTOS_PREC_IMMEDIATE            0x40
+#define IPTOS_PREC_PRIORITY             0x20
+#define IPTOS_PREC_ROUTINE              0x00
+
+
+/* IP options */
+#define IPOPT_COPY		0x80
+#define IPOPT_CLASS_MASK	0x60
+#define IPOPT_NUMBER_MASK	0x1f
+
+#define	IPOPT_COPIED(o)		((o)&IPOPT_COPY)
+#define	IPOPT_CLASS(o)		((o)&IPOPT_CLASS_MASK)
+#define	IPOPT_NUMBER(o)		((o)&IPOPT_NUMBER_MASK)
+
+#define	IPOPT_CONTROL		0x00
+#define	IPOPT_RESERVED1		0x20
+#define	IPOPT_MEASUREMENT	0x40
+#define	IPOPT_RESERVED2		0x60
+
+#define IPOPT_END	(0 |IPOPT_CONTROL)
+#define IPOPT_NOOP	(1 |IPOPT_CONTROL)
+#define IPOPT_SEC	(2 |IPOPT_CONTROL|IPOPT_COPY)
+#define IPOPT_LSRR	(3 |IPOPT_CONTROL|IPOPT_COPY)
+#define IPOPT_TIMESTAMP	(4 |IPOPT_MEASUREMENT)
+#define IPOPT_CIPSO	(6 |IPOPT_CONTROL|IPOPT_COPY)
+#define IPOPT_RR	(7 |IPOPT_CONTROL)
+#define IPOPT_SID	(8 |IPOPT_CONTROL|IPOPT_COPY)
+#define IPOPT_SSRR	(9 |IPOPT_CONTROL|IPOPT_COPY)
+#define IPOPT_RA	(20|IPOPT_CONTROL|IPOPT_COPY)
+
+#define IPVERSION	4
+#define MAXTTL		255
+#define IPDEFTTL	64
+
+#define IPOPT_OPTVAL 0
+#define IPOPT_OLEN   1
+#define IPOPT_OFFSET 2
+#define IPOPT_MINOFF 4
+#define MAX_IPOPTLEN 40
+#define IPOPT_NOP IPOPT_NOOP
+#define IPOPT_EOL IPOPT_END
+#define IPOPT_TS  IPOPT_TIMESTAMP
+
+#define	IPOPT_TS_TSONLY		0		/* timestamps only */
+#define	IPOPT_TS_TSANDADDR	1		/* timestamps and addresses */
+#define	IPOPT_TS_PRESPEC	3		/* specified modules only */
+
+#define IPV4_BEET_PHMAXLEN 8
+
+struct iphdr {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	__u8	ihl:4,
+		version:4;
+#elif defined (__BIG_ENDIAN_BITFIELD)
+	__u8	version:4,
+  		ihl:4;
+#else
+#error	"Please fix <asm/byteorder.h>"
+#endif
+	__u8	tos;
+	__be16	tot_len;
+	__be16	id;
+	__be16	frag_off;
+	__u8	ttl;
+	__u8	protocol;
+	__sum16	check;
+	__be32	saddr;
+	__be32	daddr;
+	/*The options start here. */
+};
+
+
+struct ip_auth_hdr {
+	__u8  nexthdr;
+	__u8  hdrlen;		/* This one is measured in 32 bit units! */
+	__be16 reserved;
+	__be32 spi;
+	__be32 seq_no;		/* Sequence number */
+	__u8  auth_data[0];	/* Variable len but >=4. Mind the 64 bit alignment! */
+};
+
+struct ip_esp_hdr {
+	__be32 spi;
+	__be32 seq_no;		/* Sequence number */
+	__u8  enc_data[0];	/* Variable len but >=8. Mind the 64 bit alignment! */
+};
+
+struct ip_comp_hdr {
+	__u8 nexthdr;
+	__u8 flags;
+	__be16 cpi;
+};
+
+struct ip_beet_phdr {
+	__u8 nexthdr;
+	__u8 hdrlen;
+	__u8 padlen;
+	__u8 reserved;
+};
+
+/* index values for the variables in ipv4_devconf */
+enum
+{
+	IPV4_DEVCONF_FORWARDING=1,
+	IPV4_DEVCONF_MC_FORWARDING,
+	IPV4_DEVCONF_PROXY_ARP,
+	IPV4_DEVCONF_ACCEPT_REDIRECTS,
+	IPV4_DEVCONF_SECURE_REDIRECTS,
+	IPV4_DEVCONF_SEND_REDIRECTS,
+	IPV4_DEVCONF_SHARED_MEDIA,
+	IPV4_DEVCONF_RP_FILTER,
+	IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE,
+	IPV4_DEVCONF_BOOTP_RELAY,
+	IPV4_DEVCONF_LOG_MARTIANS,
+	IPV4_DEVCONF_TAG,
+	IPV4_DEVCONF_ARPFILTER,
+	IPV4_DEVCONF_MEDIUM_ID,
+	IPV4_DEVCONF_NOXFRM,
+	IPV4_DEVCONF_NOPOLICY,
+	IPV4_DEVCONF_FORCE_IGMP_VERSION,
+	IPV4_DEVCONF_ARP_ANNOUNCE,
+	IPV4_DEVCONF_ARP_IGNORE,
+	IPV4_DEVCONF_PROMOTE_SECONDARIES,
+	IPV4_DEVCONF_ARP_ACCEPT,
+	IPV4_DEVCONF_ARP_NOTIFY,
+	IPV4_DEVCONF_ACCEPT_LOCAL,
+	IPV4_DEVCONF_SRC_VMARK,
+	IPV4_DEVCONF_PROXY_ARP_PVLAN,
+	IPV4_DEVCONF_ROUTE_LOCALNET,
+	IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL,
+	IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL,
+	__IPV4_DEVCONF_MAX
+};
+
+#define IPV4_DEVCONF_MAX (__IPV4_DEVCONF_MAX - 1)
+
+#endif /* _UAPI_LINUX_IP_H */
diff --git a/include/linux/ip_mp_alg.h b/include/linux-private/linux/ip_mp_alg.h
similarity index 100%
rename from include/linux/ip_mp_alg.h
rename to include/linux-private/linux/ip_mp_alg.h
diff --git a/include/linux-private/linux/ipv6.h b/include/linux-private/linux/ipv6.h
new file mode 100644
index 0000000..f16349d
--- /dev/null
+++ b/include/linux-private/linux/ipv6.h
@@ -0,0 +1,146 @@
+#ifndef _IPV6_H
+#define _IPV6_H
+
+#include <asm/byteorder.h>
+
+/* The latest drafts declared increase in minimal mtu up to 1280. */
+
+#define IPV6_MIN_MTU	1280
+
+/*
+ *	Advanced API
+ *	source interface/address selection, source routing, etc...
+ *	*under construction*
+ */
+
+
+#define IPV6_SRCRT_STRICT	0x01	/* Deprecated; will be removed */
+#define IPV6_SRCRT_TYPE_0	0	/* Deprecated; will be removed */
+#define IPV6_SRCRT_TYPE_2	2	/* IPv6 type 2 Routing Header	*/
+
+/*
+ *	routing header
+ */
+struct ipv6_rt_hdr {
+	__u8		nexthdr;
+	__u8		hdrlen;
+	__u8		type;
+	__u8		segments_left;
+
+	/*
+	 *	type specific data
+	 *	variable length field
+	 */
+};
+
+
+struct ipv6_opt_hdr {
+	__u8 		nexthdr;
+	__u8 		hdrlen;
+	/* 
+	 * TLV encoded option data follows.
+	 */
+} __attribute__((packed));	/* required for some archs */
+
+#define ipv6_destopt_hdr ipv6_opt_hdr
+#define ipv6_hopopt_hdr  ipv6_opt_hdr
+
+
+/*
+ *	routing header type 0 (used in cmsghdr struct)
+ */
+
+struct rt0_hdr {
+	struct ipv6_rt_hdr	rt_hdr;
+	__u32			reserved;
+	struct in6_addr		addr[0];
+
+#define rt0_type		rt_hdr.type
+};
+
+/*
+ *	routing header type 2
+ */
+
+struct rt2_hdr {
+	struct ipv6_rt_hdr	rt_hdr;
+	__u32			reserved;
+	struct in6_addr		addr;
+
+#define rt2_type		rt_hdr.type
+};
+
+/*
+ *	home address option in destination options header
+ */
+
+struct ipv6_destopt_hao {
+	__u8			type;
+	__u8			length;
+	struct in6_addr		addr;
+} __attribute__((packed));
+
+/*
+ *	IPv6 fixed header
+ *
+ *	BEWARE, it is incorrect. The first 4 bits of flow_lbl
+ *	are glued to priority now, forming "class".
+ */
+
+struct ipv6hdr {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	__u8			priority:4,
+				version:4;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+	__u8			version:4,
+				priority:4;
+#else
+#error	"Please fix <asm/byteorder.h>"
+#endif
+	__u8			flow_lbl[3];
+
+	__be16			payload_len;
+	__u8			nexthdr;
+	__u8			hop_limit;
+
+	struct	in6_addr	saddr;
+	struct	in6_addr	daddr;
+};
+
+
+/* index values for the variables in ipv6_devconf */
+enum {
+	DEVCONF_FORWARDING = 0,
+	DEVCONF_HOPLIMIT,
+	DEVCONF_MTU6,
+	DEVCONF_ACCEPT_RA,
+	DEVCONF_ACCEPT_REDIRECTS,
+	DEVCONF_AUTOCONF,
+	DEVCONF_DAD_TRANSMITS,
+	DEVCONF_RTR_SOLICITS,
+	DEVCONF_RTR_SOLICIT_INTERVAL,
+	DEVCONF_RTR_SOLICIT_DELAY,
+	DEVCONF_USE_TEMPADDR,
+	DEVCONF_TEMP_VALID_LFT,
+	DEVCONF_TEMP_PREFERED_LFT,
+	DEVCONF_REGEN_MAX_RETRY,
+	DEVCONF_MAX_DESYNC_FACTOR,
+	DEVCONF_MAX_ADDRESSES,
+	DEVCONF_FORCE_MLD_VERSION,
+	DEVCONF_ACCEPT_RA_DEFRTR,
+	DEVCONF_ACCEPT_RA_PINFO,
+	DEVCONF_ACCEPT_RA_RTR_PREF,
+	DEVCONF_RTR_PROBE_INTERVAL,
+	DEVCONF_ACCEPT_RA_RT_INFO_MAX_PLEN,
+	DEVCONF_PROXY_NDP,
+	DEVCONF_OPTIMISTIC_DAD,
+	DEVCONF_ACCEPT_SOURCE_ROUTE,
+	DEVCONF_MC_FORWARDING,
+	DEVCONF_DISABLE_IPV6,
+	DEVCONF_ACCEPT_DAD,
+	DEVCONF_FORCE_TLLAO,
+	DEVCONF_MAX
+};
+
+
+#endif /* _IPV6_H */
diff --git a/include/linux/neighbour.h b/include/linux-private/linux/neighbour.h
similarity index 96%
rename from include/linux/neighbour.h
rename to include/linux-private/linux/neighbour.h
index bd3bbf6..a7003b7 100644
--- a/include/linux/neighbour.h
+++ b/include/linux-private/linux/neighbour.h
@@ -1,10 +1,10 @@
 #ifndef __LINUX_NEIGHBOUR_H
 #define __LINUX_NEIGHBOUR_H
 
+#include <linux/types.h>
 #include <linux/netlink.h>
 
-struct ndmsg
-{
+struct ndmsg {
 	__u8		ndm_family;
 	__u8		ndm_pad1;
 	__u16		ndm_pad2;
@@ -14,8 +14,7 @@
 	__u8		ndm_type;
 };
 
-enum
-{
+enum {
 	NDA_UNSPEC,
 	NDA_DST,
 	NDA_LLADDR,
@@ -30,6 +29,7 @@
  *	Neighbor Cache Entry Flags
  */
 
+#define NTF_USE		0x01
 #define NTF_PROXY	0x08	/* == ATF_PUBL */
 #define NTF_ROUTER	0x80
 
@@ -54,8 +54,7 @@
    NUD_PERMANENT is also cannot be deleted by garbage collectors.
  */
 
-struct nda_cacheinfo
-{
+struct nda_cacheinfo {
 	__u32		ndm_confirmed;
 	__u32		ndm_used;
 	__u32		ndm_updated;
@@ -87,8 +86,7 @@
  * device.
  ****/
 
-struct ndt_stats
-{
+struct ndt_stats {
 	__u64		ndts_allocs;
 	__u64		ndts_destroys;
 	__u64		ndts_hash_grows;
@@ -122,15 +120,13 @@
 };
 #define NDTPA_MAX (__NDTPA_MAX - 1)
 
-struct ndtmsg
-{
+struct ndtmsg {
 	__u8		ndtm_family;
 	__u8		ndtm_pad1;
 	__u16		ndtm_pad2;
 };
 
-struct ndt_config
-{
+struct ndt_config {
 	__u16		ndtc_key_len;
 	__u16		ndtc_entry_size;
 	__u32		ndtc_entries;
diff --git a/include/linux/netfilter.h b/include/linux-private/linux/netfilter.h
similarity index 67%
rename from include/linux/netfilter.h
rename to include/linux-private/linux/netfilter.h
index 0750ca6..7999885 100644
--- a/include/linux/netfilter.h
+++ b/include/linux-private/linux/netfilter.h
@@ -1,6 +1,7 @@
 #ifndef __LINUX_NETFILTER_H
 #define __LINUX_NETFILTER_H
 
+#include <linux/types.h>
 
 /* Responses from hook functions. */
 #define NF_DROP 0
@@ -19,9 +20,8 @@
 #define NF_VERDICT_QMASK 0xffff0000
 #define NF_VERDICT_QBITS 16
 
-#define NF_QUEUE_NR(x) (((x << NF_VERDICT_QBITS) & NF_VERDICT_QMASK) | NF_QUEUE)
+#define NF_QUEUE_NR(x) ((((x) << NF_VERDICT_BITS) & NF_VERDICT_QMASK) | NF_QUEUE)
 
-/* only for userspace compatibility */
 /* Generic cache responses from hook functions.
    <= 0x2000 is used for protocol-flags. */
 #define NFC_UNKNOWN 0x4000
@@ -33,7 +33,25 @@
 	NF_INET_FORWARD,
 	NF_INET_LOCAL_OUT,
 	NF_INET_POST_ROUTING,
-	NF_INET_NUMHOOKS,
+	NF_INET_NUMHOOKS
+};
+
+enum {
+	NFPROTO_UNSPEC =  0,
+	NFPROTO_IPV4   =  2,
+	NFPROTO_ARP    =  3,
+	NFPROTO_BRIDGE =  7,
+	NFPROTO_IPV6   = 10,
+	NFPROTO_DECNET = 12,
+	NFPROTO_NUMPROTO,
+};
+
+union nf_inet_addr {
+	__u32		all[4];
+	__be32		ip;
+	__be32		ip6[4];
+	struct in_addr	in;
+	struct in6_addr	in6;
 };
 
 #endif /*__LINUX_NETFILTER_H*/
diff --git a/include/linux-private/linux/netfilter/nf_conntrack_common.h b/include/linux-private/linux/netfilter/nf_conntrack_common.h
new file mode 100644
index 0000000..1644cdd
--- /dev/null
+++ b/include/linux-private/linux/netfilter/nf_conntrack_common.h
@@ -0,0 +1,117 @@
+#ifndef _UAPI_NF_CONNTRACK_COMMON_H
+#define _UAPI_NF_CONNTRACK_COMMON_H
+/* Connection state tracking for netfilter.  This is separated from,
+   but required by, the NAT layer; it can also be used by an iptables
+   extension. */
+enum ip_conntrack_info {
+	/* Part of an established connection (either direction). */
+	IP_CT_ESTABLISHED,
+
+	/* Like NEW, but related to an existing connection, or ICMP error
+	   (in either direction). */
+	IP_CT_RELATED,
+
+	/* Started a new connection to track (only
+           IP_CT_DIR_ORIGINAL); may be a retransmission. */
+	IP_CT_NEW,
+
+	/* >= this indicates reply direction */
+	IP_CT_IS_REPLY,
+
+	IP_CT_ESTABLISHED_REPLY = IP_CT_ESTABLISHED + IP_CT_IS_REPLY,
+	IP_CT_RELATED_REPLY = IP_CT_RELATED + IP_CT_IS_REPLY,
+	IP_CT_NEW_REPLY = IP_CT_NEW + IP_CT_IS_REPLY,	
+	/* Number of distinct IP_CT types (no NEW in reply dirn). */
+	IP_CT_NUMBER = IP_CT_IS_REPLY * 2 - 1
+};
+
+/* Bitset representing status of connection. */
+enum ip_conntrack_status {
+	/* It's an expected connection: bit 0 set.  This bit never changed */
+	IPS_EXPECTED_BIT = 0,
+	IPS_EXPECTED = (1 << IPS_EXPECTED_BIT),
+
+	/* We've seen packets both ways: bit 1 set.  Can be set, not unset. */
+	IPS_SEEN_REPLY_BIT = 1,
+	IPS_SEEN_REPLY = (1 << IPS_SEEN_REPLY_BIT),
+
+	/* Conntrack should never be early-expired. */
+	IPS_ASSURED_BIT = 2,
+	IPS_ASSURED = (1 << IPS_ASSURED_BIT),
+
+	/* Connection is confirmed: originating packet has left box */
+	IPS_CONFIRMED_BIT = 3,
+	IPS_CONFIRMED = (1 << IPS_CONFIRMED_BIT),
+
+	/* Connection needs src nat in orig dir.  This bit never changed. */
+	IPS_SRC_NAT_BIT = 4,
+	IPS_SRC_NAT = (1 << IPS_SRC_NAT_BIT),
+
+	/* Connection needs dst nat in orig dir.  This bit never changed. */
+	IPS_DST_NAT_BIT = 5,
+	IPS_DST_NAT = (1 << IPS_DST_NAT_BIT),
+
+	/* Both together. */
+	IPS_NAT_MASK = (IPS_DST_NAT | IPS_SRC_NAT),
+
+	/* Connection needs TCP sequence adjusted. */
+	IPS_SEQ_ADJUST_BIT = 6,
+	IPS_SEQ_ADJUST = (1 << IPS_SEQ_ADJUST_BIT),
+
+	/* NAT initialization bits. */
+	IPS_SRC_NAT_DONE_BIT = 7,
+	IPS_SRC_NAT_DONE = (1 << IPS_SRC_NAT_DONE_BIT),
+
+	IPS_DST_NAT_DONE_BIT = 8,
+	IPS_DST_NAT_DONE = (1 << IPS_DST_NAT_DONE_BIT),
+
+	/* Both together */
+	IPS_NAT_DONE_MASK = (IPS_DST_NAT_DONE | IPS_SRC_NAT_DONE),
+
+	/* Connection is dying (removed from lists), can not be unset. */
+	IPS_DYING_BIT = 9,
+	IPS_DYING = (1 << IPS_DYING_BIT),
+
+	/* Connection has fixed timeout. */
+	IPS_FIXED_TIMEOUT_BIT = 10,
+	IPS_FIXED_TIMEOUT = (1 << IPS_FIXED_TIMEOUT_BIT),
+
+	/* Conntrack is a template */
+	IPS_TEMPLATE_BIT = 11,
+	IPS_TEMPLATE = (1 << IPS_TEMPLATE_BIT),
+
+	/* Conntrack is a fake untracked entry */
+	IPS_UNTRACKED_BIT = 12,
+	IPS_UNTRACKED = (1 << IPS_UNTRACKED_BIT),
+
+	/* Conntrack got a helper explicitly attached via CT target. */
+	IPS_HELPER_BIT = 13,
+	IPS_HELPER = (1 << IPS_HELPER_BIT),
+};
+
+/* Connection tracking event types */
+enum ip_conntrack_events {
+	IPCT_NEW,		/* new conntrack */
+	IPCT_RELATED,		/* related conntrack */
+	IPCT_DESTROY,		/* destroyed conntrack */
+	IPCT_REPLY,		/* connection has seen two-way traffic */
+	IPCT_ASSURED,		/* connection status has changed to assured */
+	IPCT_PROTOINFO,		/* protocol information has changed */
+	IPCT_HELPER,		/* new helper has been set */
+	IPCT_MARK,		/* new mark has been set */
+	IPCT_NATSEQADJ,		/* NAT is doing sequence adjustment */
+	IPCT_SECMARK,		/* new security mark has been set */
+};
+
+enum ip_conntrack_expect_events {
+	IPEXP_NEW,		/* new expectation */
+	IPEXP_DESTROY,		/* destroyed expectation */
+};
+
+/* expectation flags */
+#define NF_CT_EXPECT_PERMANENT		0x1
+#define NF_CT_EXPECT_INACTIVE		0x2
+#define NF_CT_EXPECT_USERSPACE		0x4
+
+
+#endif /* _UAPI_NF_CONNTRACK_COMMON_H */
diff --git a/include/linux/netfilter/nfnetlink.h b/include/linux-private/linux/netfilter/nfnetlink.h
similarity index 85%
rename from include/linux/netfilter/nfnetlink.h
rename to include/linux-private/linux/netfilter/nfnetlink.h
index 4bb4c44..1fe2972 100644
--- a/include/linux/netfilter/nfnetlink.h
+++ b/include/linux-private/linux/netfilter/nfnetlink.h
@@ -1,5 +1,5 @@
-#ifndef _NFNETLINK_H
-#define _NFNETLINK_H
+#ifndef _UAPI_NFNETLINK_H
+#define _UAPI_NFNETLINK_H
 #include <linux/types.h>
 
 #ifndef __KERNEL__
@@ -39,8 +39,8 @@
 /* General form of address family dependent message.
  */
 struct nfgenmsg {
-	u_int8_t  nfgen_family;		/* AF_xxx */
-	u_int8_t  version;		/* nfnetlink version */
+	__u8  nfgen_family;		/* AF_xxx */
+	__u8  version;		/* nfnetlink version */
 	__be16    res_id;		/* resource id */
 };
 
@@ -60,7 +60,11 @@
 #define NFNL_SUBSYS_CTNETLINK_EXP	2
 #define NFNL_SUBSYS_QUEUE		3
 #define NFNL_SUBSYS_ULOG		4
-#define NFNL_SUBSYS_COUNT		5
+#define NFNL_SUBSYS_OSF			5
+#define NFNL_SUBSYS_IPSET		6
 #define NFNL_SUBSYS_ACCT		7
+#define NFNL_SUBSYS_CTNETLINK_TIMEOUT	8
+#define NFNL_SUBSYS_CTHELPER		9
+#define NFNL_SUBSYS_COUNT		10
 
-#endif	/* _NFNETLINK_H */
+#endif /* _UAPI_NFNETLINK_H */
diff --git a/include/linux/netfilter/nfnetlink_acct.h b/include/linux-private/linux/netfilter/nfnetlink_acct.h
similarity index 100%
rename from include/linux/netfilter/nfnetlink_acct.h
rename to include/linux-private/linux/netfilter/nfnetlink_acct.h
diff --git a/include/linux-private/linux/netfilter/nfnetlink_compat.h b/include/linux-private/linux/netfilter/nfnetlink_compat.h
new file mode 100644
index 0000000..ffb9503
--- /dev/null
+++ b/include/linux-private/linux/netfilter/nfnetlink_compat.h
@@ -0,0 +1,63 @@
+#ifndef _NFNETLINK_COMPAT_H
+#define _NFNETLINK_COMPAT_H
+
+#include <linux/types.h>
+
+#ifndef __KERNEL__
+/* Old nfnetlink macros for userspace */
+
+/* nfnetlink groups: Up to 32 maximum */
+#define NF_NETLINK_CONNTRACK_NEW 		0x00000001
+#define NF_NETLINK_CONNTRACK_UPDATE		0x00000002
+#define NF_NETLINK_CONNTRACK_DESTROY		0x00000004
+#define NF_NETLINK_CONNTRACK_EXP_NEW		0x00000008
+#define NF_NETLINK_CONNTRACK_EXP_UPDATE		0x00000010
+#define NF_NETLINK_CONNTRACK_EXP_DESTROY	0x00000020
+
+/* Generic structure for encapsulation optional netfilter information.
+ * It is reminiscent of sockaddr, but with sa_family replaced
+ * with attribute type.
+ * ! This should someday be put somewhere generic as now rtnetlink and
+ * ! nfnetlink use the same attributes methods. - J. Schulist.
+ */
+
+struct nfattr {
+	__u16 nfa_len;
+	__u16 nfa_type;	/* we use 15 bits for the type, and the highest
+				 * bit to indicate whether the payload is nested */
+};
+
+/* FIXME: Apart from NFNL_NFA_NESTED shamelessly copy and pasted from
+ * rtnetlink.h, it's time to put this in a generic file */
+
+#define NFNL_NFA_NEST	0x8000
+#define NFA_TYPE(attr) 	((attr)->nfa_type & 0x7fff)
+
+#define NFA_ALIGNTO     4
+#define NFA_ALIGN(len)	(((len) + NFA_ALIGNTO - 1) & ~(NFA_ALIGNTO - 1))
+#define NFA_OK(nfa,len)	((len) > 0 && (nfa)->nfa_len >= sizeof(struct nfattr) \
+	&& (nfa)->nfa_len <= (len))
+#define NFA_NEXT(nfa,attrlen)	((attrlen) -= NFA_ALIGN((nfa)->nfa_len), \
+	(struct nfattr *)(((char *)(nfa)) + NFA_ALIGN((nfa)->nfa_len)))
+#define NFA_LENGTH(len)	(NFA_ALIGN(sizeof(struct nfattr)) + (len))
+#define NFA_SPACE(len)	NFA_ALIGN(NFA_LENGTH(len))
+#define NFA_DATA(nfa)   ((void *)(((char *)(nfa)) + NFA_LENGTH(0)))
+#define NFA_PAYLOAD(nfa) ((int)((nfa)->nfa_len) - NFA_LENGTH(0))
+#define NFA_NEST(skb, type) \
+({	struct nfattr *__start = (struct nfattr *)skb_tail_pointer(skb); \
+	NFA_PUT(skb, (NFNL_NFA_NEST | type), 0, NULL); \
+	__start;  })
+#define NFA_NEST_END(skb, start) \
+({      (start)->nfa_len = skb_tail_pointer(skb) - (unsigned char *)(start); \
+        (skb)->len; })
+#define NFA_NEST_CANCEL(skb, start) \
+({      if (start) \
+                skb_trim(skb, (unsigned char *) (start) - (skb)->data); \
+        -1; })
+
+#define NFM_NFA(n)      ((struct nfattr *)(((char *)(n)) \
+        + NLMSG_ALIGN(sizeof(struct nfgenmsg))))
+#define NFM_PAYLOAD(n)  NLMSG_PAYLOAD(n, sizeof(struct nfgenmsg))
+
+#endif /* ! __KERNEL__ */
+#endif /* _NFNETLINK_COMPAT_H */
diff --git a/include/linux-private/linux/netfilter/nfnetlink_conntrack.h b/include/linux-private/linux/netfilter/nfnetlink_conntrack.h
new file mode 100644
index 0000000..43bfe3e
--- /dev/null
+++ b/include/linux-private/linux/netfilter/nfnetlink_conntrack.h
@@ -0,0 +1,248 @@
+#ifndef _IPCONNTRACK_NETLINK_H
+#define _IPCONNTRACK_NETLINK_H
+#include <linux/netfilter/nfnetlink.h>
+
+enum cntl_msg_types {
+	IPCTNL_MSG_CT_NEW,
+	IPCTNL_MSG_CT_GET,
+	IPCTNL_MSG_CT_DELETE,
+	IPCTNL_MSG_CT_GET_CTRZERO,
+	IPCTNL_MSG_CT_GET_STATS_CPU,
+	IPCTNL_MSG_CT_GET_STATS,
+
+	IPCTNL_MSG_MAX
+};
+
+enum ctnl_exp_msg_types {
+	IPCTNL_MSG_EXP_NEW,
+	IPCTNL_MSG_EXP_GET,
+	IPCTNL_MSG_EXP_DELETE,
+	IPCTNL_MSG_EXP_GET_STATS_CPU,
+
+	IPCTNL_MSG_EXP_MAX
+};
+
+
+enum ctattr_type {
+	CTA_UNSPEC,
+	CTA_TUPLE_ORIG,
+	CTA_TUPLE_REPLY,
+	CTA_STATUS,
+	CTA_PROTOINFO,
+	CTA_HELP,
+	CTA_NAT_SRC,
+#define CTA_NAT	CTA_NAT_SRC	/* backwards compatibility */
+	CTA_TIMEOUT,
+	CTA_MARK,
+	CTA_COUNTERS_ORIG,
+	CTA_COUNTERS_REPLY,
+	CTA_USE,
+	CTA_ID,
+	CTA_NAT_DST,
+	CTA_TUPLE_MASTER,
+	CTA_NAT_SEQ_ADJ_ORIG,
+	CTA_NAT_SEQ_ADJ_REPLY,
+	CTA_SECMARK,		/* obsolete */
+	CTA_ZONE,
+	CTA_SECCTX,
+	CTA_TIMESTAMP,
+	CTA_MARK_MASK,
+	__CTA_MAX
+};
+#define CTA_MAX (__CTA_MAX - 1)
+
+enum ctattr_tuple {
+	CTA_TUPLE_UNSPEC,
+	CTA_TUPLE_IP,
+	CTA_TUPLE_PROTO,
+	__CTA_TUPLE_MAX
+};
+#define CTA_TUPLE_MAX (__CTA_TUPLE_MAX - 1)
+
+enum ctattr_ip {
+	CTA_IP_UNSPEC,
+	CTA_IP_V4_SRC,
+	CTA_IP_V4_DST,
+	CTA_IP_V6_SRC,
+	CTA_IP_V6_DST,
+	__CTA_IP_MAX
+};
+#define CTA_IP_MAX (__CTA_IP_MAX - 1)
+
+enum ctattr_l4proto {
+	CTA_PROTO_UNSPEC,
+	CTA_PROTO_NUM,
+	CTA_PROTO_SRC_PORT,
+	CTA_PROTO_DST_PORT,
+	CTA_PROTO_ICMP_ID,
+	CTA_PROTO_ICMP_TYPE,
+	CTA_PROTO_ICMP_CODE,
+	CTA_PROTO_ICMPV6_ID,
+	CTA_PROTO_ICMPV6_TYPE,
+	CTA_PROTO_ICMPV6_CODE,
+	__CTA_PROTO_MAX
+};
+#define CTA_PROTO_MAX (__CTA_PROTO_MAX - 1)
+
+enum ctattr_protoinfo {
+	CTA_PROTOINFO_UNSPEC,
+	CTA_PROTOINFO_TCP,
+	CTA_PROTOINFO_DCCP,
+	CTA_PROTOINFO_SCTP,
+	__CTA_PROTOINFO_MAX
+};
+#define CTA_PROTOINFO_MAX (__CTA_PROTOINFO_MAX - 1)
+
+enum ctattr_protoinfo_tcp {
+	CTA_PROTOINFO_TCP_UNSPEC,
+	CTA_PROTOINFO_TCP_STATE,
+	CTA_PROTOINFO_TCP_WSCALE_ORIGINAL,
+	CTA_PROTOINFO_TCP_WSCALE_REPLY,
+	CTA_PROTOINFO_TCP_FLAGS_ORIGINAL,
+	CTA_PROTOINFO_TCP_FLAGS_REPLY,
+	__CTA_PROTOINFO_TCP_MAX
+};
+#define CTA_PROTOINFO_TCP_MAX (__CTA_PROTOINFO_TCP_MAX - 1)
+
+enum ctattr_protoinfo_dccp {
+	CTA_PROTOINFO_DCCP_UNSPEC,
+	CTA_PROTOINFO_DCCP_STATE,
+	CTA_PROTOINFO_DCCP_ROLE,
+	CTA_PROTOINFO_DCCP_HANDSHAKE_SEQ,
+	__CTA_PROTOINFO_DCCP_MAX,
+};
+#define CTA_PROTOINFO_DCCP_MAX (__CTA_PROTOINFO_DCCP_MAX - 1)
+
+enum ctattr_protoinfo_sctp {
+	CTA_PROTOINFO_SCTP_UNSPEC,
+	CTA_PROTOINFO_SCTP_STATE,
+	CTA_PROTOINFO_SCTP_VTAG_ORIGINAL,
+	CTA_PROTOINFO_SCTP_VTAG_REPLY,
+	__CTA_PROTOINFO_SCTP_MAX
+};
+#define CTA_PROTOINFO_SCTP_MAX (__CTA_PROTOINFO_SCTP_MAX - 1)
+
+enum ctattr_counters {
+	CTA_COUNTERS_UNSPEC,
+	CTA_COUNTERS_PACKETS,		/* 64bit counters */
+	CTA_COUNTERS_BYTES,		/* 64bit counters */
+	CTA_COUNTERS32_PACKETS,		/* old 32bit counters, unused */
+	CTA_COUNTERS32_BYTES,		/* old 32bit counters, unused */
+	__CTA_COUNTERS_MAX
+};
+#define CTA_COUNTERS_MAX (__CTA_COUNTERS_MAX - 1)
+
+enum ctattr_tstamp {
+	CTA_TIMESTAMP_UNSPEC,
+	CTA_TIMESTAMP_START,
+	CTA_TIMESTAMP_STOP,
+	__CTA_TIMESTAMP_MAX
+};
+#define CTA_TIMESTAMP_MAX (__CTA_TIMESTAMP_MAX - 1)
+
+enum ctattr_nat {
+	CTA_NAT_UNSPEC,
+	CTA_NAT_V4_MINIP,
+#define CTA_NAT_MINIP CTA_NAT_V4_MINIP
+	CTA_NAT_V4_MAXIP,
+#define CTA_NAT_MAXIP CTA_NAT_V4_MAXIP
+	CTA_NAT_PROTO,
+	CTA_NAT_V6_MINIP,
+	CTA_NAT_V6_MAXIP,
+	__CTA_NAT_MAX
+};
+#define CTA_NAT_MAX (__CTA_NAT_MAX - 1)
+
+enum ctattr_protonat {
+	CTA_PROTONAT_UNSPEC,
+	CTA_PROTONAT_PORT_MIN,
+	CTA_PROTONAT_PORT_MAX,
+	__CTA_PROTONAT_MAX
+};
+#define CTA_PROTONAT_MAX (__CTA_PROTONAT_MAX - 1)
+
+enum ctattr_natseq {
+	CTA_NAT_SEQ_UNSPEC,
+	CTA_NAT_SEQ_CORRECTION_POS,
+	CTA_NAT_SEQ_OFFSET_BEFORE,
+	CTA_NAT_SEQ_OFFSET_AFTER,
+	__CTA_NAT_SEQ_MAX
+};
+#define CTA_NAT_SEQ_MAX (__CTA_NAT_SEQ_MAX - 1)
+
+enum ctattr_expect {
+	CTA_EXPECT_UNSPEC,
+	CTA_EXPECT_MASTER,
+	CTA_EXPECT_TUPLE,
+	CTA_EXPECT_MASK,
+	CTA_EXPECT_TIMEOUT,
+	CTA_EXPECT_ID,
+	CTA_EXPECT_HELP_NAME,
+	CTA_EXPECT_ZONE,
+	CTA_EXPECT_FLAGS,
+	CTA_EXPECT_CLASS,
+	CTA_EXPECT_NAT,
+	CTA_EXPECT_FN,
+	__CTA_EXPECT_MAX
+};
+#define CTA_EXPECT_MAX (__CTA_EXPECT_MAX - 1)
+
+enum ctattr_expect_nat {
+	CTA_EXPECT_NAT_UNSPEC,
+	CTA_EXPECT_NAT_DIR,
+	CTA_EXPECT_NAT_TUPLE,
+	__CTA_EXPECT_NAT_MAX
+};
+#define CTA_EXPECT_NAT_MAX (__CTA_EXPECT_NAT_MAX - 1)
+
+enum ctattr_help {
+	CTA_HELP_UNSPEC,
+	CTA_HELP_NAME,
+	CTA_HELP_INFO,
+	__CTA_HELP_MAX
+};
+#define CTA_HELP_MAX (__CTA_HELP_MAX - 1)
+
+enum ctattr_secctx {
+	CTA_SECCTX_UNSPEC,
+	CTA_SECCTX_NAME,
+	__CTA_SECCTX_MAX
+};
+#define CTA_SECCTX_MAX (__CTA_SECCTX_MAX - 1)
+
+enum ctattr_stats_cpu {
+	CTA_STATS_UNSPEC,
+	CTA_STATS_SEARCHED,
+	CTA_STATS_FOUND,
+	CTA_STATS_NEW,
+	CTA_STATS_INVALID,
+	CTA_STATS_IGNORE,
+	CTA_STATS_DELETE,
+	CTA_STATS_DELETE_LIST,
+	CTA_STATS_INSERT,
+	CTA_STATS_INSERT_FAILED,
+	CTA_STATS_DROP,
+	CTA_STATS_EARLY_DROP,
+	CTA_STATS_ERROR,
+	CTA_STATS_SEARCH_RESTART,
+	__CTA_STATS_MAX,
+};
+#define CTA_STATS_MAX (__CTA_STATS_MAX - 1)
+
+enum ctattr_stats_global {
+	CTA_STATS_GLOBAL_UNSPEC,
+	CTA_STATS_GLOBAL_ENTRIES,
+	__CTA_STATS_GLOBAL_MAX,
+};
+#define CTA_STATS_GLOBAL_MAX (__CTA_STATS_GLOBAL_MAX - 1)
+
+enum ctattr_expect_stats {
+	CTA_STATS_EXP_UNSPEC,
+	CTA_STATS_EXP_NEW,
+	CTA_STATS_EXP_CREATE,
+	CTA_STATS_EXP_DELETE,
+	__CTA_STATS_EXP_MAX,
+};
+#define CTA_STATS_EXP_MAX (__CTA_STATS_EXP_MAX - 1)
+
+#endif /* _IPCONNTRACK_NETLINK_H */
diff --git a/include/linux/netfilter/nfnetlink_log.h b/include/linux-private/linux/netfilter/nfnetlink_log.h
similarity index 69%
rename from include/linux/netfilter/nfnetlink_log.h
rename to include/linux-private/linux/netfilter/nfnetlink_log.h
index 38fafc1..2cfbf13 100644
--- a/include/linux/netfilter/nfnetlink_log.h
+++ b/include/linux-private/linux/netfilter/nfnetlink_log.h
@@ -5,8 +5,8 @@
  * and not any kind of function definitions.  It is shared between kernel and
  * userspace.  Don't put kernel specific stuff in here */
 
-#ifndef aligned_be64
-#define aligned_be64 u_int64_t __attribute__((aligned(8)))
+#ifndef __aligned_be64
+#define __aligned_be64 u_int64_t __attribute__((aligned(8)))
 #endif
 
 #include <linux/types.h>
@@ -21,30 +21,30 @@
 
 struct nfulnl_msg_packet_hdr {
 	__be16		hw_protocol;	/* hw protocol (network order) */
-	u_int8_t	hook;		/* netfilter hook */
-	u_int8_t	_pad;
+	__u8	hook;		/* netfilter hook */
+	__u8	_pad;
 };
 
 struct nfulnl_msg_packet_hw {
 	__be16		hw_addrlen;
-	u_int16_t	_pad;
-	u_int8_t	hw_addr[8];
+	__u16	_pad;
+	__u8	hw_addr[8];
 };
 
 struct nfulnl_msg_packet_timestamp {
-	aligned_be64	sec;
-	aligned_be64	usec;
+	__aligned_be64	sec;
+	__aligned_be64	usec;
 };
 
 enum nfulnl_attr_type {
 	NFULA_UNSPEC,
 	NFULA_PACKET_HDR,
-	NFULA_MARK,			/* u_int32_t nfmark */
+	NFULA_MARK,			/* __u32 nfmark */
 	NFULA_TIMESTAMP,		/* nfulnl_msg_packet_timestamp */
-	NFULA_IFINDEX_INDEV,		/* u_int32_t ifindex */
-	NFULA_IFINDEX_OUTDEV,		/* u_int32_t ifindex */
-	NFULA_IFINDEX_PHYSINDEV,	/* u_int32_t ifindex */
-	NFULA_IFINDEX_PHYSOUTDEV,	/* u_int32_t ifindex */
+	NFULA_IFINDEX_INDEV,		/* __u32 ifindex */
+	NFULA_IFINDEX_OUTDEV,		/* __u32 ifindex */
+	NFULA_IFINDEX_PHYSINDEV,	/* __u32 ifindex */
+	NFULA_IFINDEX_PHYSOUTDEV,	/* __u32 ifindex */
 	NFULA_HWADDR,			/* nfulnl_msg_packet_hw */
 	NFULA_PAYLOAD,			/* opaque data payload */
 	NFULA_PREFIX,			/* string prefix */
@@ -52,6 +52,9 @@
 	NFULA_SEQ,			/* instance-local sequence number */
 	NFULA_SEQ_GLOBAL,		/* global sequence number */
 	NFULA_GID,			/* group id of socket */
+	NFULA_HWTYPE,			/* hardware type */
+	NFULA_HWHEADER,			/* hardware header */
+	NFULA_HWLEN,			/* hardware header length */
 
 	__NFULA_MAX
 };
@@ -66,23 +69,23 @@
 };
 
 struct nfulnl_msg_config_cmd {
-	u_int8_t	command;	/* nfulnl_msg_config_cmds */
+	__u8	command;	/* nfulnl_msg_config_cmds */
 } __attribute__ ((packed));
 
 struct nfulnl_msg_config_mode {
 	__be32		copy_range;
-	u_int8_t	copy_mode;
-	u_int8_t	_pad;
+	__u8	copy_mode;
+	__u8	_pad;
 } __attribute__ ((packed));
 
 enum nfulnl_attr_config {
 	NFULA_CFG_UNSPEC,
 	NFULA_CFG_CMD,			/* nfulnl_msg_config_cmd */
 	NFULA_CFG_MODE,			/* nfulnl_msg_config_mode */
-	NFULA_CFG_NLBUFSIZ,		/* u_int32_t buffer size */
-	NFULA_CFG_TIMEOUT,		/* u_int32_t in 1/100 s */
-	NFULA_CFG_QTHRESH,		/* u_int32_t */
-	NFULA_CFG_FLAGS,		/* u_int16_t */
+	NFULA_CFG_NLBUFSIZ,		/* __u32 buffer size */
+	NFULA_CFG_TIMEOUT,		/* __u32 in 1/100 s */
+	NFULA_CFG_QTHRESH,		/* __u32 */
+	NFULA_CFG_FLAGS,		/* __u16 */
 	__NFULA_CFG_MAX
 };
 #define NFULA_CFG_MAX (__NFULA_CFG_MAX -1)
@@ -90,6 +93,7 @@
 #define NFULNL_COPY_NONE	0x00
 #define NFULNL_COPY_META	0x01
 #define NFULNL_COPY_PACKET	0x02
+/* 0xff is reserved, don't use it for new copy modes. */
 
 #define NFULNL_CFG_F_SEQ	0x0001
 #define NFULNL_CFG_F_SEQ_GLOBAL	0x0002
diff --git a/include/linux/netfilter/nfnetlink_queue.h b/include/linux-private/linux/netfilter/nfnetlink_queue.h
similarity index 61%
rename from include/linux/netfilter/nfnetlink_queue.h
rename to include/linux-private/linux/netfilter/nfnetlink_queue.h
index bf7cfb6..95af967 100644
--- a/include/linux/netfilter/nfnetlink_queue.h
+++ b/include/linux-private/linux/netfilter/nfnetlink_queue.h
@@ -4,14 +4,15 @@
 #include <linux/types.h>
 #include <linux/netfilter/nfnetlink.h>
 
-#ifndef aligned_be64
-#define aligned_be64 u_int64_t __attribute__((aligned(8)))
+#ifndef __aligned_be64
+#define __aligned_be64 u_int64_t __attribute__((aligned(8)))
 #endif
 
 enum nfqnl_msg_types {
 	NFQNL_MSG_PACKET,		/* packet from kernel to userspace */
 	NFQNL_MSG_VERDICT,		/* verdict from userspace to kernel */
 	NFQNL_MSG_CONFIG,		/* connect to a particular queue */
+	NFQNL_MSG_VERDICT_BATCH,	/* batchv from userspace to kernel */
 
 	NFQNL_MSG_MAX
 };
@@ -19,32 +20,35 @@
 struct nfqnl_msg_packet_hdr {
 	__be32		packet_id;	/* unique ID of packet in queue */
 	__be16		hw_protocol;	/* hw protocol (network order) */
-	u_int8_t	hook;		/* netfilter hook */
+	__u8	hook;		/* netfilter hook */
 } __attribute__ ((packed));
 
 struct nfqnl_msg_packet_hw {
 	__be16		hw_addrlen;
-	u_int16_t	_pad;
-	u_int8_t	hw_addr[8];
+	__u16	_pad;
+	__u8	hw_addr[8];
 };
 
 struct nfqnl_msg_packet_timestamp {
-	aligned_be64	sec;
-	aligned_be64	usec;
+	__aligned_be64	sec;
+	__aligned_be64	usec;
 };
 
 enum nfqnl_attr_type {
 	NFQA_UNSPEC,
 	NFQA_PACKET_HDR,
 	NFQA_VERDICT_HDR,		/* nfqnl_msg_verdict_hrd */
-	NFQA_MARK,			/* u_int32_t nfmark */
+	NFQA_MARK,			/* __u32 nfmark */
 	NFQA_TIMESTAMP,			/* nfqnl_msg_packet_timestamp */
-	NFQA_IFINDEX_INDEV,		/* u_int32_t ifindex */
-	NFQA_IFINDEX_OUTDEV,		/* u_int32_t ifindex */
-	NFQA_IFINDEX_PHYSINDEV,		/* u_int32_t ifindex */
-	NFQA_IFINDEX_PHYSOUTDEV,	/* u_int32_t ifindex */
+	NFQA_IFINDEX_INDEV,		/* __u32 ifindex */
+	NFQA_IFINDEX_OUTDEV,		/* __u32 ifindex */
+	NFQA_IFINDEX_PHYSINDEV,		/* __u32 ifindex */
+	NFQA_IFINDEX_PHYSOUTDEV,	/* __u32 ifindex */
 	NFQA_HWADDR,			/* nfqnl_msg_packet_hw */
 	NFQA_PAYLOAD,			/* opaque data payload */
+	NFQA_CT,			/* nf_conntrack_netlink.h */
+	NFQA_CT_INFO,			/* enum ip_conntrack_info */
+	NFQA_CAP_LEN,			/* __u32 length of captured packet */
 
 	__NFQA_MAX
 };
@@ -65,8 +69,8 @@
 };
 
 struct nfqnl_msg_config_cmd {
-	u_int8_t	command;	/* nfqnl_msg_config_cmds */
-	u_int8_t	_pad;
+	__u8	command;	/* nfqnl_msg_config_cmds */
+	__u8	_pad;
 	__be16		pf;		/* AF_xxx for PF_[UN]BIND */
 };
 
@@ -78,7 +82,7 @@
 
 struct nfqnl_msg_config_params {
 	__be32		copy_range;
-	u_int8_t	copy_mode;	/* enum nfqnl_config_mode */
+	__u8	copy_mode;	/* enum nfqnl_config_mode */
 } __attribute__ ((packed));
 
 
@@ -86,9 +90,16 @@
 	NFQA_CFG_UNSPEC,
 	NFQA_CFG_CMD,			/* nfqnl_msg_config_cmd */
 	NFQA_CFG_PARAMS,		/* nfqnl_msg_config_params */
-	NFQA_CFG_QUEUE_MAXLEN,		/* u_int32_t */
+	NFQA_CFG_QUEUE_MAXLEN,		/* __u32 */
+	NFQA_CFG_MASK,			/* identify which flags to change */
+	NFQA_CFG_FLAGS,			/* value of these flags (__u32) */
 	__NFQA_CFG_MAX
 };
 #define NFQA_CFG_MAX (__NFQA_CFG_MAX-1)
 
+/* Flags for NFQA_CFG_FLAGS */
+#define NFQA_CFG_F_FAIL_OPEN			(1 << 0)
+#define NFQA_CFG_F_CONNTRACK			(1 << 1)
+#define NFQA_CFG_F_MAX				(1 << 2)
+
 #endif /* _NFNETLINK_QUEUE_H */
diff --git a/include/linux/netlink.h b/include/linux-private/linux/netlink.h
similarity index 93%
rename from include/linux/netlink.h
rename to include/linux-private/linux/netlink.h
index d252103..3925254 100644
--- a/include/linux/netlink.h
+++ b/include/linux-private/linux/netlink.h
@@ -24,19 +24,18 @@
 /* leave room for NETLINK_DM (DM Events) */
 #define NETLINK_SCSITRANSPORT	18	/* SCSI Transports */
 #define NETLINK_ECRYPTFS	19
+#define NETLINK_RDMA		20
 
 #define MAX_LINKS 32		
 
-struct sockaddr_nl
-{
+struct sockaddr_nl {
 	sa_family_t	nl_family;	/* AF_NETLINK	*/
 	unsigned short	nl_pad;		/* zero		*/
 	__u32		nl_pid;		/* port ID	*/
        	__u32		nl_groups;	/* multicast groups mask */
 };
 
-struct nlmsghdr
-{
+struct nlmsghdr {
 	__u32		nlmsg_len;	/* Length of message including header */
 	__u16		nlmsg_type;	/* Message content */
 	__u16		nlmsg_flags;	/* Additional flags */
@@ -50,6 +49,7 @@
 #define NLM_F_MULTI		2	/* Multipart message, terminated by NLMSG_DONE */
 #define NLM_F_ACK		4	/* Reply with ack, with zero or error code */
 #define NLM_F_ECHO		8	/* Echo this request 		*/
+#define NLM_F_DUMP_INTR		16	/* Dump was inconsistent due to sequence change */
 
 /* Modifiers to GET request */
 #define NLM_F_ROOT	0x100	/* specify tree	root	*/
@@ -72,7 +72,7 @@
    Check		NLM_F_EXCL
  */
 
-#define NLMSG_ALIGNTO	4
+#define NLMSG_ALIGNTO	4U
 #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
 #define NLMSG_HDRLEN	 ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
 #define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(NLMSG_HDRLEN))
@@ -92,8 +92,7 @@
 
 #define NLMSG_MIN_TYPE		0x10	/* < 0x10: reserved control messages */
 
-struct nlmsgerr
-{
+struct nlmsgerr {
 	int		error;
 	struct nlmsghdr msg;
 };
@@ -101,9 +100,10 @@
 #define NETLINK_ADD_MEMBERSHIP	1
 #define NETLINK_DROP_MEMBERSHIP	2
 #define NETLINK_PKTINFO		3
+#define NETLINK_BROADCAST_ERROR	4
+#define NETLINK_NO_ENOBUFS	5
 
-struct nl_pktinfo
-{
+struct nl_pktinfo {
 	__u32	group;
 };
 
@@ -123,8 +123,7 @@
  *  <-------------- nlattr->nla_len -------------->
  */
 
-struct nlattr
-{
+struct nlattr {
 	__u16           nla_len;
 	__u16           nla_type;
 };
diff --git a/include/linux/pkt_cls.h b/include/linux-private/linux/pkt_cls.h
similarity index 95%
rename from include/linux/pkt_cls.h
rename to include/linux-private/linux/pkt_cls.h
index 3c842ed..defbde2 100644
--- a/include/linux/pkt_cls.h
+++ b/include/linux-private/linux/pkt_cls.h
@@ -75,8 +75,7 @@
 #define SET_TC_AT(v,n)   ((V_TC_AT(n)) | (v & ~M_TC_AT))
 
 /* Action attributes */
-enum
-{
+enum {
 	TCA_ACT_UNSPEC,
 	TCA_ACT_KIND,
 	TCA_ACT_OPTIONS,
@@ -108,8 +107,7 @@
 #define TC_ACT_JUMP		0x10000000
 
 /* Action type identifiers*/
-enum
-{
+enum {
 	TCA_ID_UNSPEC=0,
 	TCA_ID_POLICE=1,
 	/* other actions go here */
@@ -118,8 +116,7 @@
 
 #define TCA_ID_MAX __TCA_ID_MAX
 
-struct tc_police
-{
+struct tc_police {
 	__u32			index;
 	int			action;
 #define TC_POLICE_UNSPEC	TC_ACT_UNSPEC
@@ -138,15 +135,13 @@
 	__u32			capab;
 };
 
-struct tcf_t
-{
+struct tcf_t {
 	__u64   install;
 	__u64   lastuse;
 	__u64   expires;
 };
 
-struct tc_cnt
-{
+struct tc_cnt {
 	int                   refcnt; 
 	int                   bindcnt;
 };
@@ -158,8 +153,7 @@
 	int                   refcnt; \
 	int                   bindcnt
 
-enum
-{
+enum {
 	TCA_POLICE_UNSPEC,
 	TCA_POLICE_TBF,
 	TCA_POLICE_RATE,
@@ -182,8 +176,7 @@
 #define TC_U32_UNSPEC	0
 #define TC_U32_ROOT	(0xFFF00000)
 
-enum
-{
+enum {
 	TCA_U32_UNSPEC,
 	TCA_U32_CLASSID,
 	TCA_U32_HASH,
@@ -200,16 +193,14 @@
 
 #define TCA_U32_MAX (__TCA_U32_MAX - 1)
 
-struct tc_u32_key
-{
+struct tc_u32_key {
 	__be32		mask;
 	__be32		val;
 	int		off;
 	int		offmask;
 };
 
-struct tc_u32_sel
-{
+struct tc_u32_sel {
 	unsigned char		flags;
 	unsigned char		offshift;
 	unsigned char		nkeys;
@@ -223,15 +214,13 @@
 	struct tc_u32_key	keys[0];
 };
 
-struct tc_u32_mark
-{
+struct tc_u32_mark {
 	__u32		val;
 	__u32		mask;
 	__u32		success;
 };
 
-struct tc_u32_pcnt
-{
+struct tc_u32_pcnt {
 	__u64 rcnt;
 	__u64 rhit;
 	__u64 kcnts[0];
@@ -249,8 +238,7 @@
 
 /* RSVP filter */
 
-enum
-{
+enum {
 	TCA_RSVP_UNSPEC,
 	TCA_RSVP_CLASSID,
 	TCA_RSVP_DST,
@@ -263,15 +251,13 @@
 
 #define TCA_RSVP_MAX (__TCA_RSVP_MAX - 1 )
 
-struct tc_rsvp_gpi
-{
+struct tc_rsvp_gpi {
 	__u32	key;
 	__u32	mask;
 	int	offset;
 };
 
-struct tc_rsvp_pinfo
-{
+struct tc_rsvp_pinfo {
 	struct tc_rsvp_gpi dpi;
 	struct tc_rsvp_gpi spi;
 	__u8	protocol;
@@ -282,8 +268,7 @@
 
 /* ROUTE filter */
 
-enum
-{
+enum {
 	TCA_ROUTE4_UNSPEC,
 	TCA_ROUTE4_CLASSID,
 	TCA_ROUTE4_TO,
@@ -299,8 +284,7 @@
 
 /* FW filter */
 
-enum
-{
+enum {
 	TCA_FW_UNSPEC,
 	TCA_FW_CLASSID,
 	TCA_FW_POLICE,
@@ -314,8 +298,7 @@
 
 /* TC index filter */
 
-enum
-{
+enum {
 	TCA_TCINDEX_UNSPEC,
 	TCA_TCINDEX_HASH,
 	TCA_TCINDEX_MASK,
@@ -331,8 +314,7 @@
 
 /* Flow filter */
 
-enum
-{
+enum {
 	FLOW_KEY_SRC,
 	FLOW_KEY_DST,
 	FLOW_KEY_PROTO,
@@ -350,19 +332,18 @@
 	FLOW_KEY_SKUID,
 	FLOW_KEY_SKGID,
 	FLOW_KEY_VLAN_TAG,
+	FLOW_KEY_RXHASH,
 	__FLOW_KEY_MAX,
 };
 
 #define FLOW_KEY_MAX	(__FLOW_KEY_MAX - 1)
 
-enum
-{
+enum {
 	FLOW_MODE_MAP,
 	FLOW_MODE_HASH,
 };
 
-enum
-{
+enum {
 	TCA_FLOW_UNSPEC,
 	TCA_FLOW_KEYS,
 	TCA_FLOW_MODE,
@@ -383,8 +364,7 @@
 
 /* Basic filter */
 
-enum
-{
+enum {
 	TCA_BASIC_UNSPEC,
 	TCA_BASIC_CLASSID,
 	TCA_BASIC_EMATCHES,
@@ -398,8 +378,7 @@
 
 /* Cgroup classifier */
 
-enum
-{
+enum {
 	TCA_CGROUP_UNSPEC,
 	TCA_CGROUP_ACT,
 	TCA_CGROUP_POLICE,
@@ -411,14 +390,12 @@
 
 /* Extended Matches */
 
-struct tcf_ematch_tree_hdr
-{
+struct tcf_ematch_tree_hdr {
 	__u16		nmatches;
 	__u16		progid;
 };
 
-enum
-{
+enum {
 	TCA_EMATCH_TREE_UNSPEC,
 	TCA_EMATCH_TREE_HDR,
 	TCA_EMATCH_TREE_LIST,
@@ -426,8 +403,7 @@
 };
 #define TCA_EMATCH_TREE_MAX (__TCA_EMATCH_TREE_MAX - 1)
 
-struct tcf_ematch_hdr
-{
+struct tcf_ematch_hdr {
 	__u16		matchid;
 	__u16		kind;
 	__u16		flags;
@@ -457,8 +433,7 @@
 #define TCF_EM_REL_MASK	3
 #define TCF_EM_REL_VALID(v) (((v) & TCF_EM_REL_MASK) != TCF_EM_REL_MASK)
 
-enum
-{
+enum {
 	TCF_LAYER_LINK,
 	TCF_LAYER_NETWORK,
 	TCF_LAYER_TRANSPORT,
@@ -479,13 +454,11 @@
 #define        TCF_EM_VLAN		6
 #define	TCF_EM_MAX		6
 
-enum
-{
+enum {
 	TCF_EM_PROG_TC
 };
 
-enum
-{
+enum {
 	TCF_EM_OPND_EQ,
 	TCF_EM_OPND_GT,
 	TCF_EM_OPND_LT
diff --git a/include/linux/pkt_sched.h b/include/linux-private/linux/pkt_sched.h
similarity index 63%
rename from include/linux/pkt_sched.h
rename to include/linux-private/linux/pkt_sched.h
index 268c515..a0837a0 100644
--- a/include/linux/pkt_sched.h
+++ b/include/linux-private/linux/pkt_sched.h
@@ -1,6 +1,8 @@
 #ifndef __LINUX_PKT_SCHED_H
 #define __LINUX_PKT_SCHED_H
 
+#include <linux/types.h>
+
 /* Logical priority bands not depending on specific packet scheduler.
    Every scheduler will map them to real traffic classes, if it has
    no more precise mechanism to classify packets.
@@ -27,8 +29,7 @@
    Particular schedulers may have also their private records.
  */
 
-struct tc_stats
-{
+struct tc_stats {
 	__u64	bytes;			/* NUmber of enqueues bytes */
 	__u32	packets;		/* Number of enqueued packets	*/
 	__u32	drops;			/* Packets dropped because of lack of resources */
@@ -40,8 +41,7 @@
 	__u32	backlog;
 };
 
-struct tc_estimator
-{
+struct tc_estimator {
 	signed char	interval;
 	unsigned char	ewma_log;
 };
@@ -73,20 +73,40 @@
 #define TC_H_ROOT	(0xFFFFFFFFU)
 #define TC_H_INGRESS    (0xFFFFFFF1U)
 
-struct tc_ratespec
-{
+struct tc_ratespec {
 	unsigned char	cell_log;
 	unsigned char	__reserved;
-	unsigned short	feature;
-	short		addend;
+	unsigned short	overhead;
+	short		cell_align;
 	unsigned short	mpu;
 	__u32		rate;
 };
 
+#define TC_RTAB_SIZE	1024
+
+struct tc_sizespec {
+	unsigned char	cell_log;
+	unsigned char	size_log;
+	short		cell_align;
+	int		overhead;
+	unsigned int	linklayer;
+	unsigned int	mpu;
+	unsigned int	mtu;
+	unsigned int	tsize;
+};
+
+enum {
+	TCA_STAB_UNSPEC,
+	TCA_STAB_BASE,
+	TCA_STAB_DATA,
+	__TCA_STAB_MAX
+};
+
+#define TCA_STAB_MAX (__TCA_STAB_MAX - 1)
+
 /* FIFO section */
 
-struct tc_fifo_qopt
-{
+struct tc_fifo_qopt {
 	__u32	limit;	/* Queue length: bytes for bfifo, packets for pfifo */
 };
 
@@ -95,25 +115,42 @@
 #define TCQ_PRIO_BANDS	16
 #define TCQ_MIN_PRIO_BANDS 2
 
-struct tc_prio_qopt
-{
+struct tc_prio_qopt {
 	int	bands;			/* Number of bands */
 	__u8	priomap[TC_PRIO_MAX+1];	/* Map: logical priority -> PRIO band */
 };
 
-enum
-{
-	TCA_PRIO_UNSPEC,
-	TCA_PRIO_MQ,
-	__TCA_PRIO_MAX
+/* MULTIQ section */
+
+struct tc_multiq_qopt {
+	__u16	bands;			/* Number of bands */
+	__u16	max_bands;		/* Maximum number of queues */
 };
 
-#define TCA_PRIO_MAX    (__TCA_PRIO_MAX - 1)
+/* PLUG section */
+
+#define TCQ_PLUG_BUFFER                0
+#define TCQ_PLUG_RELEASE_ONE           1
+#define TCQ_PLUG_RELEASE_INDEFINITE    2
+#define TCQ_PLUG_LIMIT                 3
+
+struct tc_plug_qopt {
+        /* TCQ_PLUG_BUFFER: Inset a plug into the queue and
+         *  buffer any incoming packets
+         * TCQ_PLUG_RELEASE_ONE: Dequeue packets from queue head
+         *   to beginning of the next plug.
+         * TCQ_PLUG_RELEASE_INDEFINITE: Dequeue all packets from queue.
+         *   Stop buffering packets until the next TCQ_PLUG_BUFFER
+         *   command is received (just act as a pass-thru queue).
+         * TCQ_PLUG_LIMIT: Increase/decrease queue size
+         */
+        int             action;
+        __u32           limit;
+};
 
 /* TBF section */
 
-struct tc_tbf_qopt
-{
+struct tc_tbf_qopt {
 	struct tc_ratespec rate;
 	struct tc_ratespec peakrate;
 	__u32		limit;
@@ -121,8 +158,7 @@
 	__u32		mtu;
 };
 
-enum
-{
+enum {
 	TCA_TBF_UNSPEC,
 	TCA_TBF_PARMS,
 	TCA_TBF_RTAB,
@@ -139,8 +175,7 @@
 
 /* SFQ section */
 
-struct tc_sfq_qopt
-{
+struct tc_sfq_qopt {
 	unsigned	quantum;	/* Bytes per round allocated to flow */
 	int		perturb_period;	/* Period of hash perturbation */
 	__u32		limit;		/* Maximal packets in queue */
@@ -148,6 +183,10 @@
 	unsigned	flows;		/* Maximal number of flows  */
 };
 
+struct tc_sfq_xstats {
+	__s32		allot;
+};
+
 /*
  *  NOTE: limit, divisor and flows are hardwired to code at the moment.
  *
@@ -159,8 +198,7 @@
 
 /* RED section */
 
-enum
-{
+enum {
 	TCA_RED_UNSPEC,
 	TCA_RED_PARMS,
 	TCA_RED_STAB,
@@ -169,8 +207,7 @@
 
 #define TCA_RED_MAX (__TCA_RED_MAX - 1)
 
-struct tc_red_qopt
-{
+struct tc_red_qopt {
 	__u32		limit;		/* HARD maximal queue length (bytes)	*/
 	__u32		qth_min;	/* Min average length threshold (bytes) */
 	__u32		qth_max;	/* Max average length threshold (bytes) */
@@ -182,8 +219,7 @@
 #define TC_RED_HARDDROP	2
 };
 
-struct tc_red_xstats
-{
+struct tc_red_xstats {
 	__u32           early;          /* Early drops */
 	__u32           pdrop;          /* Drops due to queue limits */
 	__u32           other;          /* Drops due to drop() calls */
@@ -194,8 +230,7 @@
 
 #define MAX_DPs 16
 
-enum
-{
+enum {
        TCA_GRED_UNSPEC,
        TCA_GRED_PARMS,
        TCA_GRED_STAB,
@@ -205,12 +240,11 @@
 
 #define TCA_GRED_MAX (__TCA_GRED_MAX - 1)
 
-struct tc_gred_qopt
-{
+struct tc_gred_qopt {
 	__u32		limit;        /* HARD maximal queue length (bytes)    */
 	__u32		qth_min;      /* Min average length threshold (bytes) */
 	__u32		qth_max;      /* Max average length threshold (bytes) */
-	__u32		DP;           /* upto 2^32 DPs */
+	__u32		DP;           /* up to 2^32 DPs */
 	__u32		backlog;
 	__u32		qave;
 	__u32		forced;
@@ -226,8 +260,7 @@
 };
 
 /* gred setup */
-struct tc_gred_sopt
-{
+struct tc_gred_sopt {
 	__u32		DPs;
 	__u32		def_DP;
 	__u8		grio;
@@ -235,13 +268,41 @@
 	__u16		pad1;
 };
 
+/* CHOKe section */
+
+enum {
+	TCA_CHOKE_UNSPEC,
+	TCA_CHOKE_PARMS,
+	TCA_CHOKE_STAB,
+	__TCA_CHOKE_MAX,
+};
+
+#define TCA_CHOKE_MAX (__TCA_CHOKE_MAX - 1)
+
+struct tc_choke_qopt {
+	__u32		limit;		/* Hard queue length (packets)	*/
+	__u32		qth_min;	/* Min average threshold (packets) */
+	__u32		qth_max;	/* Max average threshold (packets) */
+	unsigned char   Wlog;		/* log(W)		*/
+	unsigned char   Plog;		/* log(P_max/(qth_max-qth_min))	*/
+	unsigned char   Scell_log;	/* cell size for idle damping */
+	unsigned char	flags;		/* see RED flags */
+};
+
+struct tc_choke_xstats {
+	__u32		early;          /* Early drops */
+	__u32		pdrop;          /* Drops due to queue limits */
+	__u32		other;          /* Drops due to drop() calls */
+	__u32		marked;         /* Marked packets */
+	__u32		matched;	/* Drops due to flow match */
+};
+
 /* HTB section */
 #define TC_HTB_NUMPRIO		8
 #define TC_HTB_MAXDEPTH		8
 #define TC_HTB_PROTOVER		3 /* the same as HTB and TC's major */
 
-struct tc_htb_opt
-{
+struct tc_htb_opt {
 	struct tc_ratespec 	rate;
 	struct tc_ratespec 	ceil;
 	__u32	buffer;
@@ -250,8 +311,7 @@
 	__u32	level;		/* out only */
 	__u32	prio;
 };
-struct tc_htb_glob
-{
+struct tc_htb_glob {
 	__u32 version;		/* to match HTB/TC */
     	__u32 rate2quantum;	/* bps->quantum divisor */
     	__u32 defcls;		/* default class number */
@@ -260,8 +320,7 @@
 	/* stats */
 	__u32 direct_pkts; /* count of non shapped packets */
 };
-enum
-{
+enum {
 	TCA_HTB_UNSPEC,
 	TCA_HTB_PARMS,
 	TCA_HTB_INIT,
@@ -272,8 +331,7 @@
 
 #define TCA_HTB_MAX (__TCA_HTB_MAX - 1)
 
-struct tc_htb_xstats
-{
+struct tc_htb_xstats {
 	__u32 lends;
 	__u32 borrows;
 	__u32 giants;	/* too big packets (rate will not be accurate) */
@@ -283,28 +341,24 @@
 
 /* HFSC section */
 
-struct tc_hfsc_qopt
-{
+struct tc_hfsc_qopt {
 	__u16	defcls;		/* default class */
 };
 
-struct tc_service_curve
-{
+struct tc_service_curve {
 	__u32	m1;		/* slope of the first segment in bps */
 	__u32	d;		/* x-projection of the first segment in us */
 	__u32	m2;		/* slope of the second segment in bps */
 };
 
-struct tc_hfsc_stats
-{
+struct tc_hfsc_stats {
 	__u64	work;		/* total work done */
 	__u64	rtwork;		/* work done by real-time criteria */
 	__u32	period;		/* current period */
 	__u32	level;		/* class level in hierarchy */
 };
 
-enum
-{
+enum {
 	TCA_HFSC_UNSPEC,
 	TCA_HFSC_RSC,
 	TCA_HFSC_FSC,
@@ -321,8 +375,7 @@
 #define TC_CBQ_MAXLEVEL		8
 #define TC_CBQ_DEF_EWMA		5
 
-struct tc_cbq_lssopt
-{
+struct tc_cbq_lssopt {
 	unsigned char	change;
 	unsigned char	flags;
 #define TCF_CBQ_LSS_BOUNDED	1
@@ -341,8 +394,7 @@
 	__u32		avpkt;
 };
 
-struct tc_cbq_wrropt
-{
+struct tc_cbq_wrropt {
 	unsigned char	flags;
 	unsigned char	priority;
 	unsigned char	cpriority;
@@ -351,8 +403,7 @@
 	__u32		weight;
 };
 
-struct tc_cbq_ovl
-{
+struct tc_cbq_ovl {
 	unsigned char	strategy;
 #define	TC_CBQ_OVL_CLASSIC	0
 #define	TC_CBQ_OVL_DELAY	1
@@ -364,30 +415,26 @@
 	__u32		penalty;
 };
 
-struct tc_cbq_police
-{
+struct tc_cbq_police {
 	unsigned char	police;
 	unsigned char	__res1;
 	unsigned short	__res2;
 };
 
-struct tc_cbq_fopt
-{
+struct tc_cbq_fopt {
 	__u32		split;
 	__u32		defmap;
 	__u32		defchange;
 };
 
-struct tc_cbq_xstats
-{
+struct tc_cbq_xstats {
 	__u32		borrows;
 	__u32		overactions;
 	__s32		avgidle;
 	__s32		undertime;
 };
 
-enum
-{
+enum {
 	TCA_CBQ_UNSPEC,
 	TCA_CBQ_LSSOPT,
 	TCA_CBQ_WRROPT,
@@ -415,6 +462,21 @@
 
 #define TCA_DSMARK_MAX (__TCA_DSMARK_MAX - 1)
 
+/* fq_codel section */
+
+enum {
+        TCA_FQ_CODEL_UNSPEC,
+        TCA_FQ_CODEL_TARGET,
+        TCA_FQ_CODEL_LIMIT,
+        TCA_FQ_CODEL_INTERVAL,
+        TCA_FQ_CODEL_ECN,
+        TCA_FQ_CODEL_FLOWS,
+        TCA_FQ_CODEL_QUANTUM,
+        __TCA_FQ_CODEL_MAX
+};
+
+#define TCA_FQ_CODEL_MAX        (__TCA_FQ_CODEL_MAX - 1)
+
 /* ATM  section */
 
 enum {
@@ -432,20 +494,19 @@
 
 /* Network emulator */
 
-enum
-{
+enum {
 	TCA_NETEM_UNSPEC,
 	TCA_NETEM_CORR,
 	TCA_NETEM_DELAY_DIST,
 	TCA_NETEM_REORDER,
 	TCA_NETEM_CORRUPT,
+	TCA_NETEM_LOSS,
 	__TCA_NETEM_MAX,
 };
 
 #define TCA_NETEM_MAX (__TCA_NETEM_MAX - 1)
 
-struct tc_netem_qopt
-{
+struct tc_netem_qopt {
 	__u32	latency;	/* added delay (us) */
 	__u32   limit;		/* fifo limit (packets) */
 	__u32	loss;		/* random packet loss (0=none ~0=100%) */
@@ -454,25 +515,128 @@
 	__u32	jitter;		/* random jitter in latency (us) */
 };
 
-struct tc_netem_corr
-{
+struct tc_netem_corr {
 	__u32	delay_corr;	/* delay correlation */
 	__u32	loss_corr;	/* packet loss correlation */
 	__u32	dup_corr;	/* duplicate correlation  */
 };
 
-struct tc_netem_reorder
-{
+struct tc_netem_reorder {
 	__u32	probability;
 	__u32	correlation;
 };
 
-struct tc_netem_corrupt
-{
+struct tc_netem_corrupt {
 	__u32	probability;
 	__u32	correlation;
 };
 
+enum {
+	NETEM_LOSS_UNSPEC,
+	NETEM_LOSS_GI,		/* General Intuitive - 4 state model */
+	NETEM_LOSS_GE,		/* Gilbert Elliot models */
+	__NETEM_LOSS_MAX
+};
+#define NETEM_LOSS_MAX (__NETEM_LOSS_MAX - 1)
+
+/* State transition probablities for 4 state model */
+struct tc_netem_gimodel {
+	__u32	p13;
+	__u32	p31;
+	__u32	p32;
+	__u32	p14;
+	__u32	p23;
+};
+
+/* Gilbert-Elliot models */
+struct tc_netem_gemodel {
+	__u32 p;
+	__u32 r;
+	__u32 h;
+	__u32 k1;
+};
+
 #define NETEM_DIST_SCALE	8192
+#define NETEM_DIST_MAX		16384
+
+/* DRR */
+
+enum {
+	TCA_DRR_UNSPEC,
+	TCA_DRR_QUANTUM,
+	__TCA_DRR_MAX
+};
+
+#define TCA_DRR_MAX	(__TCA_DRR_MAX - 1)
+
+struct tc_drr_stats {
+	__u32	deficit;
+};
+
+/* MQPRIO */
+#define TC_QOPT_BITMASK 15
+#define TC_QOPT_MAX_QUEUE 16
+
+struct tc_mqprio_qopt {
+	__u8	num_tc;
+	__u8	prio_tc_map[TC_QOPT_BITMASK + 1];
+	__u8	hw;
+	__u16	count[TC_QOPT_MAX_QUEUE];
+	__u16	offset[TC_QOPT_MAX_QUEUE];
+};
+
+/* SFB */
+
+enum {
+	TCA_SFB_UNSPEC,
+	TCA_SFB_PARMS,
+	__TCA_SFB_MAX,
+};
+
+#define TCA_SFB_MAX (__TCA_SFB_MAX - 1)
+
+/*
+ * Note: increment, decrement are Q0.16 fixed-point values.
+ */
+struct tc_sfb_qopt {
+	__u32 rehash_interval;	/* delay between hash move, in ms */
+	__u32 warmup_time;	/* double buffering warmup time in ms (warmup_time < rehash_interval) */
+	__u32 max;		/* max len of qlen_min */
+	__u32 bin_size;		/* maximum queue length per bin */
+	__u32 increment;	/* probability increment, (d1 in Blue) */
+	__u32 decrement;	/* probability decrement, (d2 in Blue) */
+	__u32 limit;		/* max SFB queue length */
+	__u32 penalty_rate;	/* inelastic flows are rate limited to 'rate' pps */
+	__u32 penalty_burst;
+};
+
+struct tc_sfb_xstats {
+	__u32 earlydrop;
+	__u32 penaltydrop;
+	__u32 bucketdrop;
+	__u32 queuedrop;
+	__u32 childdrop; /* drops in child qdisc */
+	__u32 marked;
+	__u32 maxqlen;
+	__u32 maxprob;
+	__u32 avgprob;
+};
+
+#define SFB_MAX_PROB 0xFFFF
+
+/* QFQ */
+enum {
+	TCA_QFQ_UNSPEC,
+	TCA_QFQ_WEIGHT,
+	TCA_QFQ_LMAX,
+	__TCA_QFQ_MAX
+};
+
+#define TCA_QFQ_MAX	(__TCA_QFQ_MAX - 1)
+
+struct tc_qfq_stats {
+	__u32 weight;
+	__u32 lmax;
+};
 
 #endif
diff --git a/include/linux/rtnetlink.h b/include/linux-private/linux/rtnetlink.h
similarity index 93%
rename from include/linux/rtnetlink.h
rename to include/linux-private/linux/rtnetlink.h
index 2473dbe..2363c18 100644
--- a/include/linux/rtnetlink.h
+++ b/include/linux-private/linux/rtnetlink.h
@@ -1,11 +1,19 @@
 #ifndef __LINUX_RTNETLINK_H
 #define __LINUX_RTNETLINK_H
 
+#include <linux/types.h>
 #include <linux/netlink.h>
 #include <linux/if_link.h>
 #include <linux/if_addr.h>
 #include <linux/neighbour.h>
 
+/* rtnetlink families. Values up to 127 are reserved for real address
+ * families, values above 128 may be used arbitrarily.
+ */
+#define RTNL_FAMILY_IPMR		128
+#define RTNL_FAMILY_IP6MR		129
+#define RTNL_FAMILY_MAX			129
+
 /****
  *		Routing/neighbour discovery messages.
  ****/
@@ -103,10 +111,15 @@
 	RTM_NEWADDRLABEL = 72,
 #define RTM_NEWADDRLABEL RTM_NEWADDRLABEL
 	RTM_DELADDRLABEL,
-#define RTM_NEWADDRLABEL RTM_NEWADDRLABEL
+#define RTM_DELADDRLABEL RTM_DELADDRLABEL
 	RTM_GETADDRLABEL,
 #define RTM_GETADDRLABEL RTM_GETADDRLABEL
 
+	RTM_GETDCB = 78,
+#define RTM_GETDCB RTM_GETDCB
+	RTM_SETDCB,
+#define RTM_SETDCB RTM_SETDCB
+
 	__RTM_MAX,
 #define RTM_MAX		(((__RTM_MAX + 3) & ~3) - 1)
 };
@@ -121,8 +134,7 @@
    with attribute type.
  */
 
-struct rtattr
-{
+struct rtattr {
 	unsigned short	rta_len;
 	unsigned short	rta_type;
 };
@@ -148,8 +160,7 @@
  *		Definitions used in routing table administration.
  ****/
 
-struct rtmsg
-{
+struct rtmsg {
 	unsigned char		rtm_family;
 	unsigned char		rtm_dst_len;
 	unsigned char		rtm_src_len;
@@ -165,8 +176,7 @@
 
 /* rtm_type */
 
-enum
-{
+enum {
 	RTN_UNSPEC,
 	RTN_UNICAST,		/* Gateway or direct route	*/
 	RTN_LOCAL,		/* Accept locally		*/
@@ -211,6 +221,7 @@
 #define RTPROT_DNROUTED	13	/* DECnet routing daemon */
 #define RTPROT_XORP	14	/* XORP */
 #define RTPROT_NTK	15	/* Netsukuku */
+#define RTPROT_DHCP	16      /* DHCP client */
 
 /* rtm_scope
 
@@ -223,8 +234,7 @@
    could be assigned a value between UNIVERSE and LINK.
 */
 
-enum rt_scope_t
-{
+enum rt_scope_t {
 	RT_SCOPE_UNIVERSE=0,
 /* User defined values  */
 	RT_SCOPE_SITE=200,
@@ -242,10 +252,10 @@
 
 /* Reserved table identifiers */
 
-enum rt_class_t
-{
+enum rt_class_t {
 	RT_TABLE_UNSPEC=0,
 /* User defined values */
+	RT_TABLE_COMPAT=252,
 	RT_TABLE_DEFAULT=253,
 	RT_TABLE_MAIN=254,
 	RT_TABLE_LOCAL=255,
@@ -255,8 +265,7 @@
 
 /* Routing message attributes */
 
-enum rtattr_type_t
-{
+enum rtattr_type_t {
 	RTA_UNSPEC,
 	RTA_DST,
 	RTA_SRC,
@@ -273,7 +282,7 @@
 	RTA_SESSION, /* no longer used */
 	RTA_MP_ALGO, /* no longer used */
 	RTA_TABLE,
-	RTA_GENERATION,
+	RTA_MARK,
 	__RTA_MAX
 };
 
@@ -291,8 +300,7 @@
  * and rtt for different paths from multipath.
  */
 
-struct rtnexthop
-{
+struct rtnexthop {
 	unsigned short		rtnh_len;
 	unsigned char		rtnh_flags;
 	unsigned char		rtnh_hops;
@@ -318,8 +326,7 @@
 
 /* RTM_CACHEINFO */
 
-struct rta_cacheinfo
-{
+struct rta_cacheinfo {
 	__u32	rta_clntref;
 	__u32	rta_lastuse;
 	__s32	rta_expires;
@@ -334,8 +341,7 @@
 
 /* RTM_METRICS --- array of struct rtattr with types of RTAX_* */
 
-enum
-{
+enum {
 	RTAX_UNSPEC,
 #define RTAX_UNSPEC RTAX_UNSPEC
 	RTAX_LOCK,
@@ -364,6 +370,8 @@
 #define RTAX_FEATURES RTAX_FEATURES
 	RTAX_RTO_MIN,
 #define RTAX_RTO_MIN RTAX_RTO_MIN
+	RTAX_INITRWND,
+#define RTAX_INITRWND RTAX_INITRWND
 	__RTAX_MAX
 };
 
@@ -374,8 +382,7 @@
 #define RTAX_FEATURE_TIMESTAMP	0x00000004
 #define RTAX_FEATURE_ALLFRAG	0x00000008
 
-struct rta_session
-{
+struct rta_session {
 	__u8	proto;
 	__u8	pad1;
 	__u16	pad2;
@@ -400,8 +407,7 @@
  *		General form of address family dependent message.
  ****/
 
-struct rtgenmsg
-{
+struct rtgenmsg {
 	unsigned char		rtgen_family;
 };
 
@@ -414,8 +420,7 @@
  * on network protocol.
  */
 
-struct ifinfomsg
-{
+struct ifinfomsg {
 	unsigned char	ifi_family;
 	unsigned char	__ifi_pad;
 	unsigned short	ifi_type;		/* ARPHRD_* */
@@ -428,8 +433,7 @@
  *		prefix information 
  ****/
 
-struct prefixmsg
-{
+struct prefixmsg {
 	unsigned char	prefix_family;
 	unsigned char	prefix_pad1;
 	unsigned short	prefix_pad2;
@@ -450,8 +454,7 @@
 
 #define PREFIX_MAX	(__PREFIX_MAX - 1)
 
-struct prefix_cacheinfo
-{
+struct prefix_cacheinfo {
 	__u32	preferred_time;
 	__u32	valid_time;
 };
@@ -461,8 +464,7 @@
  *		Traffic control messages.
  ****/
 
-struct tcmsg
-{
+struct tcmsg {
 	unsigned char	tcm_family;
 	unsigned char	tcm__pad1;
 	unsigned short	tcm__pad2;
@@ -472,8 +474,7 @@
 	__u32		tcm_info;
 };
 
-enum
-{
+enum {
 	TCA_UNSPEC,
 	TCA_KIND,
 	TCA_OPTIONS,
@@ -482,6 +483,7 @@
 	TCA_RATE,
 	TCA_FCNT,
 	TCA_STATS2,
+	TCA_STAB,
 	__TCA_MAX
 };
 
@@ -494,8 +496,7 @@
  *		Neighbor Discovery userland options
  ****/
 
-struct nduseroptmsg
-{
+struct nduseroptmsg {
 	unsigned char	nduseropt_family;
 	unsigned char	nduseropt_pad1;
 	unsigned short	nduseropt_opts_len;	/* Total length of options */
@@ -507,8 +508,7 @@
 	/* Followed by one or more ND options */
 };
 
-enum
-{
+enum {
 	NDUSEROPT_UNSPEC,
 	NDUSEROPT_SRCADDR,
 	__NDUSEROPT_MAX
@@ -581,13 +581,16 @@
 #define RTNLGRP_IPV6_RULE	RTNLGRP_IPV6_RULE
 	RTNLGRP_ND_USEROPT,
 #define RTNLGRP_ND_USEROPT	RTNLGRP_ND_USEROPT
+	RTNLGRP_PHONET_IFADDR,
+#define RTNLGRP_PHONET_IFADDR	RTNLGRP_PHONET_IFADDR
+	RTNLGRP_PHONET_ROUTE,
+#define RTNLGRP_PHONET_ROUTE	RTNLGRP_PHONET_ROUTE
 	__RTNLGRP_MAX
 };
 #define RTNLGRP_MAX	(__RTNLGRP_MAX - 1)
 
 /* TC action piece */
-struct tcamsg
-{
+struct tcamsg {
 	unsigned char	tca_family;
 	unsigned char	tca__pad1;
 	unsigned short	tca__pad2;
diff --git a/include/linux-private/linux/snmp.h b/include/linux-private/linux/snmp.h
new file mode 100644
index 0000000..1bdb4a3
--- /dev/null
+++ b/include/linux-private/linux/snmp.h
@@ -0,0 +1,299 @@
+/*
+ * Definitions for MIBs
+ *
+ * Author: Hideaki YOSHIFUJI <yoshfuji@linux-ipv6.org>
+ */
+
+#ifndef _LINUX_SNMP_H
+#define _LINUX_SNMP_H
+
+/* ipstats mib definitions */
+/*
+ * RFC 1213:  MIB-II
+ * RFC 2011 (updates 1213):  SNMPv2-MIB-IP
+ * RFC 2863:  Interfaces Group MIB
+ * RFC 2465:  IPv6 MIB: General Group
+ * draft-ietf-ipv6-rfc2011-update-10.txt: MIB for IP: IP Statistics Tables
+ */
+enum
+{
+	IPSTATS_MIB_NUM = 0,
+/* frequently written fields in fast path, kept in same cache line */
+	IPSTATS_MIB_INPKTS,			/* InReceives */
+	IPSTATS_MIB_INOCTETS,			/* InOctets */
+	IPSTATS_MIB_INDELIVERS,			/* InDelivers */
+	IPSTATS_MIB_OUTFORWDATAGRAMS,		/* OutForwDatagrams */
+	IPSTATS_MIB_OUTPKTS,			/* OutRequests */
+	IPSTATS_MIB_OUTOCTETS,			/* OutOctets */
+/* other fields */
+	IPSTATS_MIB_INHDRERRORS,		/* InHdrErrors */
+	IPSTATS_MIB_INTOOBIGERRORS,		/* InTooBigErrors */
+	IPSTATS_MIB_INNOROUTES,			/* InNoRoutes */
+	IPSTATS_MIB_INADDRERRORS,		/* InAddrErrors */
+	IPSTATS_MIB_INUNKNOWNPROTOS,		/* InUnknownProtos */
+	IPSTATS_MIB_INTRUNCATEDPKTS,		/* InTruncatedPkts */
+	IPSTATS_MIB_INDISCARDS,			/* InDiscards */
+	IPSTATS_MIB_OUTDISCARDS,		/* OutDiscards */
+	IPSTATS_MIB_OUTNOROUTES,		/* OutNoRoutes */
+	IPSTATS_MIB_REASMTIMEOUT,		/* ReasmTimeout */
+	IPSTATS_MIB_REASMREQDS,			/* ReasmReqds */
+	IPSTATS_MIB_REASMOKS,			/* ReasmOKs */
+	IPSTATS_MIB_REASMFAILS,			/* ReasmFails */
+	IPSTATS_MIB_FRAGOKS,			/* FragOKs */
+	IPSTATS_MIB_FRAGFAILS,			/* FragFails */
+	IPSTATS_MIB_FRAGCREATES,		/* FragCreates */
+	IPSTATS_MIB_INMCASTPKTS,		/* InMcastPkts */
+	IPSTATS_MIB_OUTMCASTPKTS,		/* OutMcastPkts */
+	IPSTATS_MIB_INBCASTPKTS,		/* InBcastPkts */
+	IPSTATS_MIB_OUTBCASTPKTS,		/* OutBcastPkts */
+	IPSTATS_MIB_INMCASTOCTETS,		/* InMcastOctets */
+	IPSTATS_MIB_OUTMCASTOCTETS,		/* OutMcastOctets */
+	IPSTATS_MIB_INBCASTOCTETS,		/* InBcastOctets */
+	IPSTATS_MIB_OUTBCASTOCTETS,		/* OutBcastOctets */
+	IPSTATS_MIB_CSUMERRORS,			/* InCsumErrors */
+	IPSTATS_MIB_NOECTPKTS,			/* InNoECTPkts */
+	IPSTATS_MIB_ECT1PKTS,			/* InECT1Pkts */
+	IPSTATS_MIB_ECT0PKTS,			/* InECT0Pkts */
+	IPSTATS_MIB_CEPKTS,			/* InCEPkts */
+	__IPSTATS_MIB_MAX
+};
+
+/* icmp mib definitions */
+/*
+ * RFC 1213:  MIB-II ICMP Group
+ * RFC 2011 (updates 1213):  SNMPv2 MIB for IP: ICMP group
+ */
+enum
+{
+	ICMP_MIB_NUM = 0,
+	ICMP_MIB_INMSGS,			/* InMsgs */
+	ICMP_MIB_INERRORS,			/* InErrors */
+	ICMP_MIB_INDESTUNREACHS,		/* InDestUnreachs */
+	ICMP_MIB_INTIMEEXCDS,			/* InTimeExcds */
+	ICMP_MIB_INPARMPROBS,			/* InParmProbs */
+	ICMP_MIB_INSRCQUENCHS,			/* InSrcQuenchs */
+	ICMP_MIB_INREDIRECTS,			/* InRedirects */
+	ICMP_MIB_INECHOS,			/* InEchos */
+	ICMP_MIB_INECHOREPS,			/* InEchoReps */
+	ICMP_MIB_INTIMESTAMPS,			/* InTimestamps */
+	ICMP_MIB_INTIMESTAMPREPS,		/* InTimestampReps */
+	ICMP_MIB_INADDRMASKS,			/* InAddrMasks */
+	ICMP_MIB_INADDRMASKREPS,		/* InAddrMaskReps */
+	ICMP_MIB_OUTMSGS,			/* OutMsgs */
+	ICMP_MIB_OUTERRORS,			/* OutErrors */
+	ICMP_MIB_OUTDESTUNREACHS,		/* OutDestUnreachs */
+	ICMP_MIB_OUTTIMEEXCDS,			/* OutTimeExcds */
+	ICMP_MIB_OUTPARMPROBS,			/* OutParmProbs */
+	ICMP_MIB_OUTSRCQUENCHS,			/* OutSrcQuenchs */
+	ICMP_MIB_OUTREDIRECTS,			/* OutRedirects */
+	ICMP_MIB_OUTECHOS,			/* OutEchos */
+	ICMP_MIB_OUTECHOREPS,			/* OutEchoReps */
+	ICMP_MIB_OUTTIMESTAMPS,			/* OutTimestamps */
+	ICMP_MIB_OUTTIMESTAMPREPS,		/* OutTimestampReps */
+	ICMP_MIB_OUTADDRMASKS,			/* OutAddrMasks */
+	ICMP_MIB_OUTADDRMASKREPS,		/* OutAddrMaskReps */
+	ICMP_MIB_CSUMERRORS,			/* InCsumErrors */
+	__ICMP_MIB_MAX
+};
+
+#define __ICMPMSG_MIB_MAX 512	/* Out+In for all 8-bit ICMP types */
+
+/* icmp6 mib definitions */
+/*
+ * RFC 2466:  ICMPv6-MIB
+ */
+enum
+{
+	ICMP6_MIB_NUM = 0,
+	ICMP6_MIB_INMSGS,			/* InMsgs */
+	ICMP6_MIB_INERRORS,			/* InErrors */
+	ICMP6_MIB_OUTMSGS,			/* OutMsgs */
+	ICMP6_MIB_OUTERRORS,			/* OutErrors */
+	ICMP6_MIB_CSUMERRORS,			/* InCsumErrors */
+	__ICMP6_MIB_MAX
+};
+
+#define __ICMP6MSG_MIB_MAX 512 /* Out+In for all 8-bit ICMPv6 types */
+
+/* tcp mib definitions */
+/*
+ * RFC 1213:  MIB-II TCP group
+ * RFC 2012 (updates 1213):  SNMPv2-MIB-TCP
+ */
+enum
+{
+	TCP_MIB_NUM = 0,
+	TCP_MIB_RTOALGORITHM,			/* RtoAlgorithm */
+	TCP_MIB_RTOMIN,				/* RtoMin */
+	TCP_MIB_RTOMAX,				/* RtoMax */
+	TCP_MIB_MAXCONN,			/* MaxConn */
+	TCP_MIB_ACTIVEOPENS,			/* ActiveOpens */
+	TCP_MIB_PASSIVEOPENS,			/* PassiveOpens */
+	TCP_MIB_ATTEMPTFAILS,			/* AttemptFails */
+	TCP_MIB_ESTABRESETS,			/* EstabResets */
+	TCP_MIB_CURRESTAB,			/* CurrEstab */
+	TCP_MIB_INSEGS,				/* InSegs */
+	TCP_MIB_OUTSEGS,			/* OutSegs */
+	TCP_MIB_RETRANSSEGS,			/* RetransSegs */
+	TCP_MIB_INERRS,				/* InErrs */
+	TCP_MIB_OUTRSTS,			/* OutRsts */
+	TCP_MIB_CSUMERRORS,			/* InCsumErrors */
+	__TCP_MIB_MAX
+};
+
+/* udp mib definitions */
+/*
+ * RFC 1213:  MIB-II UDP group
+ * RFC 2013 (updates 1213):  SNMPv2-MIB-UDP
+ */
+enum
+{
+	UDP_MIB_NUM = 0,
+	UDP_MIB_INDATAGRAMS,			/* InDatagrams */
+	UDP_MIB_NOPORTS,			/* NoPorts */
+	UDP_MIB_INERRORS,			/* InErrors */
+	UDP_MIB_OUTDATAGRAMS,			/* OutDatagrams */
+	UDP_MIB_RCVBUFERRORS,			/* RcvbufErrors */
+	UDP_MIB_SNDBUFERRORS,			/* SndbufErrors */
+	UDP_MIB_CSUMERRORS,			/* InCsumErrors */
+	__UDP_MIB_MAX
+};
+
+/* linux mib definitions */
+enum
+{
+	LINUX_MIB_NUM = 0,
+	LINUX_MIB_SYNCOOKIESSENT,		/* SyncookiesSent */
+	LINUX_MIB_SYNCOOKIESRECV,		/* SyncookiesRecv */
+	LINUX_MIB_SYNCOOKIESFAILED,		/* SyncookiesFailed */
+	LINUX_MIB_EMBRYONICRSTS,		/* EmbryonicRsts */
+	LINUX_MIB_PRUNECALLED,			/* PruneCalled */
+	LINUX_MIB_RCVPRUNED,			/* RcvPruned */
+	LINUX_MIB_OFOPRUNED,			/* OfoPruned */
+	LINUX_MIB_OUTOFWINDOWICMPS,		/* OutOfWindowIcmps */
+	LINUX_MIB_LOCKDROPPEDICMPS,		/* LockDroppedIcmps */
+	LINUX_MIB_ARPFILTER,			/* ArpFilter */
+	LINUX_MIB_TIMEWAITED,			/* TimeWaited */
+	LINUX_MIB_TIMEWAITRECYCLED,		/* TimeWaitRecycled */
+	LINUX_MIB_TIMEWAITKILLED,		/* TimeWaitKilled */
+	LINUX_MIB_PAWSPASSIVEREJECTED,		/* PAWSPassiveRejected */
+	LINUX_MIB_PAWSACTIVEREJECTED,		/* PAWSActiveRejected */
+	LINUX_MIB_PAWSESTABREJECTED,		/* PAWSEstabRejected */
+	LINUX_MIB_DELAYEDACKS,			/* DelayedACKs */
+	LINUX_MIB_DELAYEDACKLOCKED,		/* DelayedACKLocked */
+	LINUX_MIB_DELAYEDACKLOST,		/* DelayedACKLost */
+	LINUX_MIB_LISTENOVERFLOWS,		/* ListenOverflows */
+	LINUX_MIB_LISTENDROPS,			/* ListenDrops */
+	LINUX_MIB_TCPPREQUEUED,			/* TCPPrequeued */
+	LINUX_MIB_TCPDIRECTCOPYFROMBACKLOG,	/* TCPDirectCopyFromBacklog */
+	LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE,	/* TCPDirectCopyFromPrequeue */
+	LINUX_MIB_TCPPREQUEUEDROPPED,		/* TCPPrequeueDropped */
+	LINUX_MIB_TCPHPHITS,			/* TCPHPHits */
+	LINUX_MIB_TCPHPHITSTOUSER,		/* TCPHPHitsToUser */
+	LINUX_MIB_TCPPUREACKS,			/* TCPPureAcks */
+	LINUX_MIB_TCPHPACKS,			/* TCPHPAcks */
+	LINUX_MIB_TCPRENORECOVERY,		/* TCPRenoRecovery */
+	LINUX_MIB_TCPSACKRECOVERY,		/* TCPSackRecovery */
+	LINUX_MIB_TCPSACKRENEGING,		/* TCPSACKReneging */
+	LINUX_MIB_TCPFACKREORDER,		/* TCPFACKReorder */
+	LINUX_MIB_TCPSACKREORDER,		/* TCPSACKReorder */
+	LINUX_MIB_TCPRENOREORDER,		/* TCPRenoReorder */
+	LINUX_MIB_TCPTSREORDER,			/* TCPTSReorder */
+	LINUX_MIB_TCPFULLUNDO,			/* TCPFullUndo */
+	LINUX_MIB_TCPPARTIALUNDO,		/* TCPPartialUndo */
+	LINUX_MIB_TCPDSACKUNDO,			/* TCPDSACKUndo */
+	LINUX_MIB_TCPLOSSUNDO,			/* TCPLossUndo */
+	LINUX_MIB_TCPLOSTRETRANSMIT,		/* TCPLostRetransmit */
+	LINUX_MIB_TCPRENOFAILURES,		/* TCPRenoFailures */
+	LINUX_MIB_TCPSACKFAILURES,		/* TCPSackFailures */
+	LINUX_MIB_TCPLOSSFAILURES,		/* TCPLossFailures */
+	LINUX_MIB_TCPFASTRETRANS,		/* TCPFastRetrans */
+	LINUX_MIB_TCPFORWARDRETRANS,		/* TCPForwardRetrans */
+	LINUX_MIB_TCPSLOWSTARTRETRANS,		/* TCPSlowStartRetrans */
+	LINUX_MIB_TCPTIMEOUTS,			/* TCPTimeouts */
+	LINUX_MIB_TCPLOSSPROBES,		/* TCPLossProbes */
+	LINUX_MIB_TCPLOSSPROBERECOVERY,		/* TCPLossProbeRecovery */
+	LINUX_MIB_TCPRENORECOVERYFAIL,		/* TCPRenoRecoveryFail */
+	LINUX_MIB_TCPSACKRECOVERYFAIL,		/* TCPSackRecoveryFail */
+	LINUX_MIB_TCPSCHEDULERFAILED,		/* TCPSchedulerFailed */
+	LINUX_MIB_TCPRCVCOLLAPSED,		/* TCPRcvCollapsed */
+	LINUX_MIB_TCPDSACKOLDSENT,		/* TCPDSACKOldSent */
+	LINUX_MIB_TCPDSACKOFOSENT,		/* TCPDSACKOfoSent */
+	LINUX_MIB_TCPDSACKRECV,			/* TCPDSACKRecv */
+	LINUX_MIB_TCPDSACKOFORECV,		/* TCPDSACKOfoRecv */
+	LINUX_MIB_TCPABORTONDATA,		/* TCPAbortOnData */
+	LINUX_MIB_TCPABORTONCLOSE,		/* TCPAbortOnClose */
+	LINUX_MIB_TCPABORTONMEMORY,		/* TCPAbortOnMemory */
+	LINUX_MIB_TCPABORTONTIMEOUT,		/* TCPAbortOnTimeout */
+	LINUX_MIB_TCPABORTONLINGER,		/* TCPAbortOnLinger */
+	LINUX_MIB_TCPABORTFAILED,		/* TCPAbortFailed */
+	LINUX_MIB_TCPMEMORYPRESSURES,		/* TCPMemoryPressures */
+	LINUX_MIB_TCPSACKDISCARD,		/* TCPSACKDiscard */
+	LINUX_MIB_TCPDSACKIGNOREDOLD,		/* TCPSACKIgnoredOld */
+	LINUX_MIB_TCPDSACKIGNOREDNOUNDO,	/* TCPSACKIgnoredNoUndo */
+	LINUX_MIB_TCPSPURIOUSRTOS,		/* TCPSpuriousRTOs */
+	LINUX_MIB_TCPMD5NOTFOUND,		/* TCPMD5NotFound */
+	LINUX_MIB_TCPMD5UNEXPECTED,		/* TCPMD5Unexpected */
+	LINUX_MIB_SACKSHIFTED,
+	LINUX_MIB_SACKMERGED,
+	LINUX_MIB_SACKSHIFTFALLBACK,
+	LINUX_MIB_TCPBACKLOGDROP,
+	LINUX_MIB_TCPMINTTLDROP, /* RFC 5082 */
+	LINUX_MIB_TCPDEFERACCEPTDROP,
+	LINUX_MIB_IPRPFILTER, /* IP Reverse Path Filter (rp_filter) */
+	LINUX_MIB_TCPTIMEWAITOVERFLOW,		/* TCPTimeWaitOverflow */
+	LINUX_MIB_TCPREQQFULLDOCOOKIES,		/* TCPReqQFullDoCookies */
+	LINUX_MIB_TCPREQQFULLDROP,		/* TCPReqQFullDrop */
+	LINUX_MIB_TCPRETRANSFAIL,		/* TCPRetransFail */
+	LINUX_MIB_TCPRCVCOALESCE,		/* TCPRcvCoalesce */
+	LINUX_MIB_TCPOFOQUEUE,			/* TCPOFOQueue */
+	LINUX_MIB_TCPOFODROP,			/* TCPOFODrop */
+	LINUX_MIB_TCPOFOMERGE,			/* TCPOFOMerge */
+	LINUX_MIB_TCPCHALLENGEACK,		/* TCPChallengeACK */
+	LINUX_MIB_TCPSYNCHALLENGE,		/* TCPSYNChallenge */
+	LINUX_MIB_TCPFASTOPENACTIVE,		/* TCPFastOpenActive */
+	LINUX_MIB_TCPFASTOPENPASSIVE,		/* TCPFastOpenPassive*/
+	LINUX_MIB_TCPFASTOPENPASSIVEFAIL,	/* TCPFastOpenPassiveFail */
+	LINUX_MIB_TCPFASTOPENLISTENOVERFLOW,	/* TCPFastOpenListenOverflow */
+	LINUX_MIB_TCPFASTOPENCOOKIEREQD,	/* TCPFastOpenCookieReqd */
+	LINUX_MIB_TCPSPURIOUS_RTX_HOSTQUEUES, /* TCPSpuriousRtxHostQueues */
+	LINUX_MIB_BUSYPOLLRXPACKETS,		/* BusyPollRxPackets */
+	__LINUX_MIB_MAX
+};
+
+/* linux Xfrm mib definitions */
+enum
+{
+	LINUX_MIB_XFRMNUM = 0,
+	LINUX_MIB_XFRMINERROR,			/* XfrmInError */
+	LINUX_MIB_XFRMINBUFFERERROR,		/* XfrmInBufferError */
+	LINUX_MIB_XFRMINHDRERROR,		/* XfrmInHdrError */
+	LINUX_MIB_XFRMINNOSTATES,		/* XfrmInNoStates */
+	LINUX_MIB_XFRMINSTATEPROTOERROR,	/* XfrmInStateProtoError */
+	LINUX_MIB_XFRMINSTATEMODEERROR,		/* XfrmInStateModeError */
+	LINUX_MIB_XFRMINSTATESEQERROR,		/* XfrmInStateSeqError */
+	LINUX_MIB_XFRMINSTATEEXPIRED,		/* XfrmInStateExpired */
+	LINUX_MIB_XFRMINSTATEMISMATCH,		/* XfrmInStateMismatch */
+	LINUX_MIB_XFRMINSTATEINVALID,		/* XfrmInStateInvalid */
+	LINUX_MIB_XFRMINTMPLMISMATCH,		/* XfrmInTmplMismatch */
+	LINUX_MIB_XFRMINNOPOLS,			/* XfrmInNoPols */
+	LINUX_MIB_XFRMINPOLBLOCK,		/* XfrmInPolBlock */
+	LINUX_MIB_XFRMINPOLERROR,		/* XfrmInPolError */
+	LINUX_MIB_XFRMOUTERROR,			/* XfrmOutError */
+	LINUX_MIB_XFRMOUTBUNDLEGENERROR,	/* XfrmOutBundleGenError */
+	LINUX_MIB_XFRMOUTBUNDLECHECKERROR,	/* XfrmOutBundleCheckError */
+	LINUX_MIB_XFRMOUTNOSTATES,		/* XfrmOutNoStates */
+	LINUX_MIB_XFRMOUTSTATEPROTOERROR,	/* XfrmOutStateProtoError */
+	LINUX_MIB_XFRMOUTSTATEMODEERROR,	/* XfrmOutStateModeError */
+	LINUX_MIB_XFRMOUTSTATESEQERROR,		/* XfrmOutStateSeqError */
+	LINUX_MIB_XFRMOUTSTATEEXPIRED,		/* XfrmOutStateExpired */
+	LINUX_MIB_XFRMOUTPOLBLOCK,		/* XfrmOutPolBlock */
+	LINUX_MIB_XFRMOUTPOLDEAD,		/* XfrmOutPolDead */
+	LINUX_MIB_XFRMOUTPOLERROR,		/* XfrmOutPolError */
+	LINUX_MIB_XFRMFWDHDRERROR,		/* XfrmFwdHdrError*/
+	LINUX_MIB_XFRMOUTSTATEINVALID,		/* XfrmOutStateInvalid */
+	LINUX_MIB_XFRMACQUIREERROR,		/* XfrmAcquireError */
+	__LINUX_MIB_XFRMMAX
+};
+
+#endif	/* _LINUX_SNMP_H */
diff --git a/include/linux-private/linux/tc_act/tc_mirred.h b/include/linux-private/linux/tc_act/tc_mirred.h
new file mode 100644
index 0000000..7561750
--- /dev/null
+++ b/include/linux-private/linux/tc_act/tc_mirred.h
@@ -0,0 +1,27 @@
+#ifndef __LINUX_TC_MIR_H
+#define __LINUX_TC_MIR_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+#define TCA_ACT_MIRRED 8
+#define TCA_EGRESS_REDIR 1  /* packet redirect to EGRESS*/
+#define TCA_EGRESS_MIRROR 2 /* mirror packet to EGRESS */
+#define TCA_INGRESS_REDIR 3  /* packet redirect to INGRESS*/
+#define TCA_INGRESS_MIRROR 4 /* mirror packet to INGRESS */
+                                                                                
+struct tc_mirred {
+	tc_gen;
+	int                     eaction;   /* one of IN/EGRESS_MIRROR/REDIR */
+	__u32                   ifindex;  /* ifindex of egress port */
+};
+                                                                                
+enum {
+	TCA_MIRRED_UNSPEC,
+	TCA_MIRRED_TM,
+	TCA_MIRRED_PARMS,
+	__TCA_MIRRED_MAX
+};
+#define TCA_MIRRED_MAX (__TCA_MIRRED_MAX - 1)
+                                                                                
+#endif
diff --git a/include/linux-private/linux/tc_ematch/tc_em_meta.h b/include/linux-private/linux/tc_ematch/tc_em_meta.h
new file mode 100644
index 0000000..fe815e2
--- /dev/null
+++ b/include/linux-private/linux/tc_ematch/tc_em_meta.h
@@ -0,0 +1,89 @@
+#ifndef __LINUX_TC_EM_META_H
+#define __LINUX_TC_EM_META_H
+
+enum {
+	TCA_EM_META_UNSPEC,
+	TCA_EM_META_HDR,
+	TCA_EM_META_LVALUE,
+	TCA_EM_META_RVALUE,
+	__TCA_EM_META_MAX
+};
+#define TCA_EM_META_MAX (__TCA_EM_META_MAX - 1)
+
+struct tcf_meta_val {
+	__u16			kind;
+	__u8			shift;
+	__u8			op;
+};
+
+#define TCF_META_TYPE_MASK	(0xf << 12)
+#define TCF_META_TYPE(kind)	(((kind) & TCF_META_TYPE_MASK) >> 12)
+#define TCF_META_ID_MASK	0x7ff
+#define TCF_META_ID(kind)	((kind) & TCF_META_ID_MASK)
+
+enum {
+	TCF_META_TYPE_VAR,
+	TCF_META_TYPE_INT,
+	__TCF_META_TYPE_MAX
+};
+#define TCF_META_TYPE_MAX (__TCF_META_TYPE_MAX - 1)
+
+enum {
+	TCF_META_ID_VALUE,
+	TCF_META_ID_RANDOM,
+	TCF_META_ID_LOADAVG_0,
+	TCF_META_ID_LOADAVG_1,
+	TCF_META_ID_LOADAVG_2,
+	TCF_META_ID_DEV,
+	TCF_META_ID_PRIORITY,
+	TCF_META_ID_PROTOCOL,
+	TCF_META_ID_PKTTYPE,
+	TCF_META_ID_PKTLEN,
+	TCF_META_ID_DATALEN,
+	TCF_META_ID_MACLEN,
+	TCF_META_ID_NFMARK,
+	TCF_META_ID_TCINDEX,
+	TCF_META_ID_RTCLASSID,
+	TCF_META_ID_RTIIF,
+	TCF_META_ID_SK_FAMILY,
+	TCF_META_ID_SK_STATE,
+	TCF_META_ID_SK_REUSE,
+	TCF_META_ID_SK_BOUND_IF,
+	TCF_META_ID_SK_REFCNT,
+	TCF_META_ID_SK_SHUTDOWN,
+	TCF_META_ID_SK_PROTO,
+	TCF_META_ID_SK_TYPE,
+	TCF_META_ID_SK_RCVBUF,
+	TCF_META_ID_SK_RMEM_ALLOC,
+	TCF_META_ID_SK_WMEM_ALLOC,
+	TCF_META_ID_SK_OMEM_ALLOC,
+	TCF_META_ID_SK_WMEM_QUEUED,
+	TCF_META_ID_SK_RCV_QLEN,
+	TCF_META_ID_SK_SND_QLEN,
+ 	TCF_META_ID_SK_ERR_QLEN,
+	TCF_META_ID_SK_FORWARD_ALLOCS,
+	TCF_META_ID_SK_SNDBUF,
+ 	TCF_META_ID_SK_ALLOCS,
+ 	TCF_META_ID_SK_ROUTE_CAPS,
+ 	TCF_META_ID_SK_HASH,
+ 	TCF_META_ID_SK_LINGERTIME,
+ 	TCF_META_ID_SK_ACK_BACKLOG,
+ 	TCF_META_ID_SK_MAX_ACK_BACKLOG,
+ 	TCF_META_ID_SK_PRIO,
+ 	TCF_META_ID_SK_RCVLOWAT,
+ 	TCF_META_ID_SK_RCVTIMEO,
+ 	TCF_META_ID_SK_SNDTIMEO,
+ 	TCF_META_ID_SK_SENDMSG_OFF,
+ 	TCF_META_ID_SK_WRITE_PENDING,
+	TCF_META_ID_VLAN_TAG,
+	TCF_META_ID_RXHASH,
+	__TCF_META_ID_MAX
+};
+#define TCF_META_ID_MAX (__TCF_META_ID_MAX - 1)
+
+struct tcf_meta_hdr {
+	struct tcf_meta_val	left;
+	struct tcf_meta_val	right;
+};
+
+#endif
diff --git a/include/linux/if_link.h b/include/linux/if_link.h
deleted file mode 100644
index 84c3492..0000000
--- a/include/linux/if_link.h
+++ /dev/null
@@ -1,191 +0,0 @@
-#ifndef _LINUX_IF_LINK_H
-#define _LINUX_IF_LINK_H
-
-#include <linux/netlink.h>
-
-/* The struct should be in sync with struct net_device_stats */
-struct rtnl_link_stats
-{
-	__u32	rx_packets;		/* total packets received	*/
-	__u32	tx_packets;		/* total packets transmitted	*/
-	__u32	rx_bytes;		/* total bytes received 	*/
-	__u32	tx_bytes;		/* total bytes transmitted	*/
-	__u32	rx_errors;		/* bad packets received		*/
-	__u32	tx_errors;		/* packet transmit problems	*/
-	__u32	rx_dropped;		/* no space in linux buffers	*/
-	__u32	tx_dropped;		/* no space available in linux	*/
-	__u32	multicast;		/* multicast packets received	*/
-	__u32	collisions;
-
-	/* detailed rx_errors: */
-	__u32	rx_length_errors;
-	__u32	rx_over_errors;		/* receiver ring buff overflow	*/
-	__u32	rx_crc_errors;		/* recved pkt with crc error	*/
-	__u32	rx_frame_errors;	/* recv'd frame alignment error */
-	__u32	rx_fifo_errors;		/* recv'r fifo overrun		*/
-	__u32	rx_missed_errors;	/* receiver missed packet	*/
-
-	/* detailed tx_errors */
-	__u32	tx_aborted_errors;
-	__u32	tx_carrier_errors;
-	__u32	tx_fifo_errors;
-	__u32	tx_heartbeat_errors;
-	__u32	tx_window_errors;
-
-	/* for cslip etc */
-	__u32	rx_compressed;
-	__u32	tx_compressed;
-};
-
-/* The struct should be in sync with struct ifmap */
-struct rtnl_link_ifmap
-{
-	__u64	mem_start;
-	__u64	mem_end;
-	__u64	base_addr;
-	__u16	irq;
-	__u8	dma;
-	__u8	port;
-};
-
-enum
-{
-	IFLA_UNSPEC,
-	IFLA_ADDRESS,
-	IFLA_BROADCAST,
-	IFLA_IFNAME,
-	IFLA_MTU,
-	IFLA_LINK,
-	IFLA_QDISC,
-	IFLA_STATS,
-	IFLA_COST,
-#define IFLA_COST IFLA_COST
-	IFLA_PRIORITY,
-#define IFLA_PRIORITY IFLA_PRIORITY
-	IFLA_MASTER,
-#define IFLA_MASTER IFLA_MASTER
-	IFLA_WIRELESS,		/* Wireless Extension event - see wireless.h */
-#define IFLA_WIRELESS IFLA_WIRELESS
-	IFLA_PROTINFO,		/* Protocol specific information for a link */
-#define IFLA_PROTINFO IFLA_PROTINFO
-	IFLA_TXQLEN,
-#define IFLA_TXQLEN IFLA_TXQLEN
-	IFLA_MAP,
-#define IFLA_MAP IFLA_MAP
-	IFLA_WEIGHT,
-#define IFLA_WEIGHT IFLA_WEIGHT
-	IFLA_OPERSTATE,
-	IFLA_LINKMODE,
-	IFLA_LINKINFO,
-#define IFLA_LINKINFO IFLA_LINKINFO
-	IFLA_NET_NS_PID,
-	__IFLA_MAX
-};
-
-
-#define IFLA_MAX (__IFLA_MAX - 1)
-
-/* backwards compatibility for userspace */
-#ifndef __KERNEL__
-#define IFLA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg))))
-#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg))
-#endif
-
-/* ifi_flags.
-
-   IFF_* flags.
-
-   The only change is:
-   IFF_LOOPBACK, IFF_BROADCAST and IFF_POINTOPOINT are
-   more not changeable by user. They describe link media
-   characteristics and set by device driver.
-
-   Comments:
-   - Combination IFF_BROADCAST|IFF_POINTOPOINT is invalid
-   - If neither of these three flags are set;
-     the interface is NBMA.
-
-   - IFF_MULTICAST does not mean anything special:
-   multicasts can be used on all not-NBMA links.
-   IFF_MULTICAST means that this media uses special encapsulation
-   for multicast frames. Apparently, all IFF_POINTOPOINT and
-   IFF_BROADCAST devices are able to use multicasts too.
- */
-
-/* IFLA_LINK.
-   For usual devices it is equal ifi_index.
-   If it is a "virtual interface" (f.e. tunnel), ifi_link
-   can point to real physical interface (f.e. for bandwidth calculations),
-   or maybe 0, what means, that real media is unknown (usual
-   for IPIP tunnels, when route to endpoint is allowed to change)
- */
-
-/* Subtype attributes for IFLA_PROTINFO */
-enum
-{
-	IFLA_INET6_UNSPEC,
-	IFLA_INET6_FLAGS,	/* link flags			*/
-	IFLA_INET6_CONF,	/* sysctl parameters		*/
-	IFLA_INET6_STATS,	/* statistics			*/
-	IFLA_INET6_MCAST,	/* MC things. What of them?	*/
-	IFLA_INET6_CACHEINFO,	/* time values and max reasm size */
-	IFLA_INET6_ICMP6STATS,	/* statistics (icmpv6)		*/
-	__IFLA_INET6_MAX
-};
-
-#define IFLA_INET6_MAX	(__IFLA_INET6_MAX - 1)
-
-struct ifla_cacheinfo
-{
-	__u32	max_reasm_len;
-	__u32	tstamp;		/* ipv6InterfaceTable updated timestamp */
-	__u32	reachable_time;
-	__u32	retrans_time;
-};
-
-enum
-{
-	IFLA_INFO_UNSPEC,
-	IFLA_INFO_KIND,
-	IFLA_INFO_DATA,
-	IFLA_INFO_XSTATS,
-	__IFLA_INFO_MAX,
-};
-
-#define IFLA_INFO_MAX	(__IFLA_INFO_MAX - 1)
-
-/* VLAN section */
-
-enum
-{
-	IFLA_VLAN_UNSPEC,
-	IFLA_VLAN_ID,
-	IFLA_VLAN_FLAGS,
-	IFLA_VLAN_EGRESS_QOS,
-	IFLA_VLAN_INGRESS_QOS,
-	__IFLA_VLAN_MAX,
-};
-
-#define IFLA_VLAN_MAX	(__IFLA_VLAN_MAX - 1)
-
-struct ifla_vlan_flags {
-	__u32	flags;
-	__u32	mask;
-};
-
-enum
-{
-	IFLA_VLAN_QOS_UNSPEC,
-	IFLA_VLAN_QOS_MAPPING,
-	__IFLA_VLAN_QOS_MAX
-};
-
-#define IFLA_VLAN_QOS_MAX	(__IFLA_VLAN_QOS_MAX - 1)
-
-struct ifla_vlan_qos_mapping
-{
-	__u32 from;
-	__u32 to;
-};
-
-#endif /* _LINUX_IF_LINK_H */
diff --git a/include/linux/netfilter/nfnetlink_conntrack.h b/include/linux/netfilter/nfnetlink_conntrack.h
deleted file mode 100644
index d7c3503..0000000
--- a/include/linux/netfilter/nfnetlink_conntrack.h
+++ /dev/null
@@ -1,140 +0,0 @@
-#ifndef _IPCONNTRACK_NETLINK_H
-#define _IPCONNTRACK_NETLINK_H
-#include <linux/netfilter/nfnetlink.h>
-
-enum cntl_msg_types {
-	IPCTNL_MSG_CT_NEW,
-	IPCTNL_MSG_CT_GET,
-	IPCTNL_MSG_CT_DELETE,
-	IPCTNL_MSG_CT_GET_CTRZERO,
-
-	IPCTNL_MSG_MAX
-};
-
-enum ctnl_exp_msg_types {
-	IPCTNL_MSG_EXP_NEW,
-	IPCTNL_MSG_EXP_GET,
-	IPCTNL_MSG_EXP_DELETE,
-
-	IPCTNL_MSG_EXP_MAX
-};
-
-
-enum ctattr_type {
-	CTA_UNSPEC,
-	CTA_TUPLE_ORIG,
-	CTA_TUPLE_REPLY,
-	CTA_STATUS,
-	CTA_PROTOINFO,
-	CTA_HELP,
-	CTA_NAT_SRC,
-#define CTA_NAT	CTA_NAT_SRC	/* backwards compatibility */
-	CTA_TIMEOUT,
-	CTA_MARK,
-	CTA_COUNTERS_ORIG,
-	CTA_COUNTERS_REPLY,
-	CTA_USE,
-	CTA_ID,
-	CTA_NAT_DST,
-	__CTA_MAX
-};
-#define CTA_MAX (__CTA_MAX - 1)
-
-enum ctattr_tuple {
-	CTA_TUPLE_UNSPEC,
-	CTA_TUPLE_IP,
-	CTA_TUPLE_PROTO,
-	__CTA_TUPLE_MAX
-};
-#define CTA_TUPLE_MAX (__CTA_TUPLE_MAX - 1)
-
-enum ctattr_ip {
-	CTA_IP_UNSPEC,
-	CTA_IP_V4_SRC,
-	CTA_IP_V4_DST,
-	CTA_IP_V6_SRC,
-	CTA_IP_V6_DST,
-	__CTA_IP_MAX
-};
-#define CTA_IP_MAX (__CTA_IP_MAX - 1)
-
-enum ctattr_l4proto {
-	CTA_PROTO_UNSPEC,
-	CTA_PROTO_NUM,
-	CTA_PROTO_SRC_PORT,
-	CTA_PROTO_DST_PORT,
-	CTA_PROTO_ICMP_ID,
-	CTA_PROTO_ICMP_TYPE,
-	CTA_PROTO_ICMP_CODE,
-	CTA_PROTO_ICMPV6_ID,
-	CTA_PROTO_ICMPV6_TYPE,
-	CTA_PROTO_ICMPV6_CODE,
-	__CTA_PROTO_MAX
-};
-#define CTA_PROTO_MAX (__CTA_PROTO_MAX - 1)
-
-enum ctattr_protoinfo {
-	CTA_PROTOINFO_UNSPEC,
-	CTA_PROTOINFO_TCP,
-	__CTA_PROTOINFO_MAX
-};
-#define CTA_PROTOINFO_MAX (__CTA_PROTOINFO_MAX - 1)
-
-enum ctattr_protoinfo_tcp {
-	CTA_PROTOINFO_TCP_UNSPEC,
-	CTA_PROTOINFO_TCP_STATE,
-	CTA_PROTOINFO_TCP_WSCALE_ORIGINAL,
-	CTA_PROTOINFO_TCP_WSCALE_REPLY,
-	CTA_PROTOINFO_TCP_FLAGS_ORIGINAL,
-	CTA_PROTOINFO_TCP_FLAGS_REPLY,
-	__CTA_PROTOINFO_TCP_MAX
-};
-#define CTA_PROTOINFO_TCP_MAX (__CTA_PROTOINFO_TCP_MAX - 1)
-
-enum ctattr_counters {
-	CTA_COUNTERS_UNSPEC,
-	CTA_COUNTERS_PACKETS,		/* old 64bit counters */
-	CTA_COUNTERS_BYTES,		/* old 64bit counters */
-	CTA_COUNTERS32_PACKETS,
-	CTA_COUNTERS32_BYTES,
-	__CTA_COUNTERS_MAX
-};
-#define CTA_COUNTERS_MAX (__CTA_COUNTERS_MAX - 1)
-
-enum ctattr_nat {
-	CTA_NAT_UNSPEC,
-	CTA_NAT_MINIP,
-	CTA_NAT_MAXIP,
-	CTA_NAT_PROTO,
-	__CTA_NAT_MAX
-};
-#define CTA_NAT_MAX (__CTA_NAT_MAX - 1)
-
-enum ctattr_protonat {
-	CTA_PROTONAT_UNSPEC,
-	CTA_PROTONAT_PORT_MIN,
-	CTA_PROTONAT_PORT_MAX,
-	__CTA_PROTONAT_MAX
-};
-#define CTA_PROTONAT_MAX (__CTA_PROTONAT_MAX - 1)
-
-enum ctattr_expect {
-	CTA_EXPECT_UNSPEC,
-	CTA_EXPECT_MASTER,
-	CTA_EXPECT_TUPLE,
-	CTA_EXPECT_MASK,
-	CTA_EXPECT_TIMEOUT,
-	CTA_EXPECT_ID,
-	CTA_EXPECT_HELP_NAME,
-	__CTA_EXPECT_MAX
-};
-#define CTA_EXPECT_MAX (__CTA_EXPECT_MAX - 1)
-
-enum ctattr_help {
-	CTA_HELP_UNSPEC,
-	CTA_HELP_NAME,
-	__CTA_HELP_MAX
-};
-#define CTA_HELP_MAX (__CTA_HELP_MAX - 1)
-
-#endif /* _IPCONNTRACK_NETLINK_H */
diff --git a/include/netlink-local.h b/include/netlink-local.h
deleted file mode 100644
index 7e33119..0000000
--- a/include/netlink-local.h
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * netlink-local.h		Local Netlink Interface
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
- */
-
-#ifndef NETLINK_LOCAL_H_
-#define NETLINK_LOCAL_H_
-
-#include <stdio.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <math.h>
-#include <time.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-#include <inttypes.h>
-#include <assert.h>
-#include <limits.h>
-
-#include <arpa/inet.h>
-#include <netdb.h>
-
-#ifndef SOL_NETLINK
-#define SOL_NETLINK 270
-#endif
-
-#include <linux/types.h>
-
-/* local header copies */
-#include <linux/if.h>
-#include <linux/if_arp.h>
-#include <linux/if_ether.h>
-#include <linux/pkt_sched.h>
-#include <linux/pkt_cls.h>
-#include <linux/gen_stats.h>
-#include <linux/ip_mp_alg.h>
-
-#include <netlink/netlink.h>
-#include <netlink/handlers.h>
-#include <netlink/cache.h>
-#include <netlink/route/tc.h>
-#include <netlink/object-api.h>
-#include <netlink/cache-api.h>
-#include <netlink-types.h>
-
-struct trans_tbl {
-	int i;
-	const char *a;
-};
-
-#define __ADD(id, name) { .i = id, .a = #name },
-
-struct trans_list {
-	int i;
-	char *a;
-	struct nl_list_head list;
-};
-
-#define NL_DEBUG	1
-
-#define NL_DBG(LVL,FMT,ARG...) \
-	do {	\
-		if (LVL <= nl_debug) \
-			fprintf(stderr, "DBG<" #LVL ">: " FMT, ##ARG); \
-	} while (0)
-
-#define BUG()                            \
-	do {                                 \
-		fprintf(stderr, "BUG: %s:%d\n",  \
-			__FILE__, __LINE__);         \
-		assert(0);	\
-	} while (0)
-
-extern int __nl_read_num_str_file(const char *path,
-				  int (*cb)(long, const char *));
-
-extern int __trans_list_add(int, const char *, struct nl_list_head *);
-extern void __trans_list_clear(struct nl_list_head *);
-
-extern char *__type2str(int, char *, size_t, struct trans_tbl *, size_t);
-extern int __str2type(const char *, struct trans_tbl *, size_t);
-
-extern char *__list_type2str(int, char *, size_t, struct nl_list_head *);
-extern int __list_str2type(const char *, struct nl_list_head *);
-
-extern char *__flags2str(int, char *, size_t, struct trans_tbl *, size_t);
-extern int __str2flags(const char *, struct trans_tbl *, size_t);
-
-extern void dump_from_ops(struct nl_object *, struct nl_dump_params *);
-
-static inline struct nl_cache *dp_cache(struct nl_object *obj)
-{
-	if (obj->ce_cache == NULL)
-		return nl_cache_mngt_require(obj->ce_ops->oo_name);
-
-	return obj->ce_cache;
-}
-
-static inline int nl_cb_call(struct nl_cb *cb, int type, struct nl_msg *msg)
-{
-	return cb->cb_set[type](msg, cb->cb_args[type]);
-}
-
-#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
-
-#define __init __attribute__ ((constructor))
-#define __exit __attribute__ ((destructor))
-#undef __deprecated
-#define __deprecated __attribute__ ((deprecated))
-
-#define min(x,y) ({ \
-	typeof(x) _x = (x);	\
-	typeof(y) _y = (y);	\
-	(void) (&_x == &_y);		\
-	_x < _y ? _x : _y; })
-
-#define max(x,y) ({ \
-	typeof(x) _x = (x);	\
-	typeof(y) _y = (y);	\
-	(void) (&_x == &_y);		\
-	_x > _y ? _x : _y; })
-
-#define min_t(type,x,y) \
-	({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })
-#define max_t(type,x,y) \
-	({ type __x = (x); type __y = (y); __x > __y ? __x: __y; })
-
-extern int nl_cache_parse(struct nl_cache_ops *, struct sockaddr_nl *,
-			  struct nlmsghdr *, struct nl_parser_param *);
-
-
-static inline void rtnl_copy_ratespec(struct rtnl_ratespec *dst,
-				      struct tc_ratespec *src)
-{
-	dst->rs_cell_log = src->cell_log;
-	dst->rs_feature = src->feature;
-	dst->rs_addend = src->addend;
-	dst->rs_mpu = src->mpu;
-	dst->rs_rate = src->rate;
-}
-
-static inline void rtnl_rcopy_ratespec(struct tc_ratespec *dst,
-				       struct rtnl_ratespec *src)
-{
-	dst->cell_log = src->rs_cell_log;
-	dst->feature = src->rs_feature;
-	dst->addend = src->rs_addend;
-	dst->mpu = src->rs_mpu;
-	dst->rate = src->rs_rate;
-}
-
-static inline char *nl_cache_name(struct nl_cache *cache)
-{
-	return cache->c_ops ? cache->c_ops->co_name : "unknown";
-}
-
-#define GENL_FAMILY(id, name) \
-	{ \
-		{ id, NL_ACT_UNSPEC, name }, \
-		END_OF_MSGTYPES_LIST, \
-	}
-
-static inline int wait_for_ack(struct nl_sock *sk)
-{
-	if (sk->s_flags & NL_NO_AUTO_ACK)
-		return 0;
-	else
-		return nl_wait_for_ack(sk);
-}
-
-#endif
diff --git a/include/netlink-private/cache-api.h b/include/netlink-private/cache-api.h
new file mode 100644
index 0000000..f3d9f01
--- /dev/null
+++ b/include/netlink-private/cache-api.h
@@ -0,0 +1,270 @@
+/*
+ * netlink-private/cache-api.h		Caching API
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_CACHE_API_H_
+#define NETLINK_CACHE_API_H_
+
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @ingroup cache
+ * @defgroup cache_api Cache Implementation
+ * @brief
+ *
+ * @par 1) Cache Definition
+ * @code
+ * struct nl_cache_ops my_cache_ops = {
+ * 	.co_name		= "route/link",
+ * 	.co_protocol		= NETLINK_ROUTE,
+ * 	.co_hdrsize		= sizeof(struct ifinfomsg),
+ * 	.co_obj_ops		= &my_obj_ops,
+ * };
+ * @endcode
+ *
+ * @par 2) 
+ * @code
+ * // The simplest way to fill a cache is by providing a request-update
+ * // function which must trigger a complete dump on the kernel-side of
+ * // whatever the cache covers.
+ * static int my_request_update(struct nl_cache *cache,
+ * 				struct nl_sock *socket)
+ * {
+ * 	// In this example, we request a full dump of the interface table
+ * 	return nl_rtgen_request(socket, RTM_GETLINK, AF_UNSPEC, NLM_F_DUMP);
+ * }
+ *
+ * // The resulting netlink messages sent back will be fed into a message
+ * // parser one at a time. The message parser has to extract all relevant
+ * // information from the message and create an object reflecting the
+ * // contents of the message and pass it on to the parser callback function
+ * // provide which will add the object to the cache.
+ * static int my_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
+ * 			    struct nlmsghdr *nlh, struct nl_parser_param *pp)
+ * {
+ * 	struct my_obj *obj;
+ *
+ * 	obj = my_obj_alloc();
+ * 	obj->ce_msgtype = nlh->nlmsg_type;
+ *
+ * 	// Parse the netlink message and continue creating the object.
+ *
+ * 	err = pp->pp_cb((struct nl_object *) obj, pp);
+ * 	if (err < 0)
+ * 		goto errout;
+ * }
+ *
+ * struct nl_cache_ops my_cache_ops = {
+ * 	...
+ * 	.co_request_update	= my_request_update,
+ * 	.co_msg_parser		= my_msg_parser,
+ * };
+ * @endcode
+ *
+ * @par 3) Notification based Updates
+ * @code
+ * // Caches can be kept up-to-date based on notifications if the kernel
+ * // sends out notifications whenever an object is added/removed/changed.
+ * //
+ * // It is trivial to support this, first a list of groups needs to be
+ * // defined which are required to join in order to receive all necessary
+ * // notifications. The groups are separated by address family to support
+ * // the common situation where a separate group is used for each address
+ * // family. If there is only one group, simply specify AF_UNSPEC.
+ * static struct nl_af_group addr_groups[] = {
+ * 	{ AF_INET,	RTNLGRP_IPV4_IFADDR },
+ * 	{ AF_INET6,	RTNLGRP_IPV6_IFADDR },
+ * 	{ END_OF_GROUP_LIST },
+ * };
+ *
+ * // In order for the caching system to know the meaning of each message
+ * // type it requires a table which maps each supported message type to
+ * // a cache action, e.g. RTM_NEWADDR means address has been added or
+ * // updated, RTM_DELADDR means address has been removed.
+ * static struct nl_cache_ops rtnl_addr_ops = {
+ * 	...
+ * 	.co_msgtypes		= {
+ * 					{ RTM_NEWADDR, NL_ACT_NEW, "new" },
+ * 					{ RTM_DELADDR, NL_ACT_DEL, "del" },
+ * 					{ RTM_GETADDR, NL_ACT_GET, "get" },
+ * 					END_OF_MSGTYPES_LIST,
+ * 				},
+ * 	.co_groups		= addr_groups,
+ * };
+ *
+ * // It is now possible to keep the cache up-to-date using the cache manager.
+ * @endcode
+ * @{
+ */
+
+#define END_OF_MSGTYPES_LIST	{ -1, -1, NULL }
+
+/**
+ * Message type to cache action association
+ */
+struct nl_msgtype
+{
+	/** Netlink message type */
+	int			mt_id;
+
+	/** Cache action to take */
+	int			mt_act;
+
+	/** Name of operation for human-readable printing */
+	char *			mt_name;
+};
+
+/**
+ * Address family to netlink group association
+ */
+struct nl_af_group
+{
+	/** Address family */
+	int			ag_family;
+
+	/** Netlink group identifier */
+	int			ag_group;
+};
+
+#define END_OF_GROUP_LIST AF_UNSPEC, 0
+
+/**
+ * Parser parameters
+ *
+ * This structure is used to configure what kind of parser to use
+ * when parsing netlink messages to create objects.
+ */
+struct nl_parser_param
+{
+	/** Function to parse netlink messages into objects */
+	int             (*pp_cb)(struct nl_object *, struct nl_parser_param *);
+
+	/** Arbitary argument to be passed to the parser */
+	void *            pp_arg;
+};
+
+/**
+ * Cache Operations
+ *
+ * This structure defines the characterstics of a cache type. It contains
+ * pointers to functions which implement the specifics of the object type
+ * the cache can hold.
+ */
+struct nl_cache_ops
+{
+	/** Name of cache type (must be unique) */
+	char  *			co_name;
+
+	/** Size of family specific netlink header */
+	int			co_hdrsize;
+
+	/** Netlink protocol */
+	int			co_protocol;
+
+	/** cache object hash size **/
+	int			co_hash_size;
+
+	/** cache flags */
+	unsigned int		co_flags;
+
+	/** Reference counter */
+	unsigned int		co_refcnt;
+
+	/** Group definition */
+	struct nl_af_group *	co_groups;
+	
+	/**
+	 * Called whenever an update of the cache is required. Must send
+	 * a request message to the kernel requesting a complete dump.
+	 */
+	int   (*co_request_update)(struct nl_cache *, struct nl_sock *);
+
+	/**
+	 * Called whenever a message was received that needs to be parsed.
+	 * Must parse the message and call the paser callback function
+	 * (nl_parser_param) provided via the argument.
+	 */
+	int   (*co_msg_parser)(struct nl_cache_ops *, struct sockaddr_nl *,
+			       struct nlmsghdr *, struct nl_parser_param *);
+
+	/**
+	 * The function registered under this callback is called after a
+	 * netlink notification associated with this cache type has been
+	 * parsed into an object and is being considered for inclusio into
+	 * the specified cache.
+	 *
+	 * The purpose of this function is to filter out notifications
+	 * which should be ignored when updating caches.
+	 *
+	 * The function must return NL_SKIP to prevent the object from
+	 * being included, or NL_OK to include it.
+	 *
+	 * @code
+	 * int my_filter(struct nl_cache *cache, struct nl_object *obj)
+	 * {
+	 * 	if (reason_to_not_include_obj(obj))
+	 * 		return NL_SKIP;
+	 *
+	 * 	return NL_OK;
+	 * }
+	 * @endcode
+	 */
+	int   (*co_event_filter)(struct nl_cache *, struct nl_object *obj);
+
+	/**
+	 * The function registered under this callback is called when an
+	 * object formed from a notification event needs to be included in
+	 * a cache.
+	 *
+	 * For each modified object, the change callback \c change_cb must
+	 * be called with the \c data argument provided.
+	 *
+	 * If no function is registered, the function nl_cache_include()
+	 * will be used for this purpose.
+	 *
+	 * @see nl_cache_include()
+	 */
+	int   (*co_include_event)(struct nl_cache *cache, struct nl_object *obj,
+				  change_func_t change_cb, void *data);
+
+	void (*reserved_1)(void);
+	void (*reserved_2)(void);
+	void (*reserved_3)(void);
+	void (*reserved_4)(void);
+	void (*reserved_5)(void);
+	void (*reserved_6)(void);
+	void (*reserved_7)(void);
+	void (*reserved_8)(void);
+
+	/** Object operations */
+	struct nl_object_ops *	co_obj_ops;
+
+	/** Internal, do not touch! */
+	struct nl_cache_ops *co_next;
+
+	struct nl_cache *co_major_cache;
+	struct genl_ops *	co_genl;
+
+	/* Message type definition */
+	struct nl_msgtype	co_msgtypes[];
+};
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink-generic.h b/include/netlink-private/genl.h
similarity index 64%
rename from include/netlink-generic.h
rename to include/netlink-private/genl.h
index 10aa2f0..5b93db3 100644
--- a/include/netlink-generic.h
+++ b/include/netlink-private/genl.h
@@ -1,20 +1,22 @@
 /*
- * netlink-generic.h	Local Generic Netlink Interface
+ * netlink-private/genl.h	Local Generic Netlink Interface
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_GENL_PRIV_H_
 #define NETLINK_GENL_PRIV_H_
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 
 #define GENL_HDRSIZE(hdrlen) (GENL_HDRLEN + (hdrlen))
 
+extern int		genl_resolve_id(struct genl_ops *ops);
+
 #endif
diff --git a/include/netlink-private/netlink.h b/include/netlink-private/netlink.h
new file mode 100644
index 0000000..e366d1e
--- /dev/null
+++ b/include/netlink-private/netlink.h
@@ -0,0 +1,276 @@
+/*
+ * netlink-private/netlink.h	Local Netlink Interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_LOCAL_H_
+#define NETLINK_LOCAL_H_
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+#include <time.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <inttypes.h>
+#include <assert.h>
+#include <limits.h>
+#include <search.h>
+
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <defs.h>
+
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+#include <linux/types.h>
+
+/* local header copies */
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/ethtool.h>
+#include <linux/pkt_sched.h>
+#include <linux/pkt_cls.h>
+#include <linux/gen_stats.h>
+#include <linux/ip_mp_alg.h>
+#include <linux/atm.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/snmp.h>
+
+#ifndef DISABLE_PTHREADS
+#include <pthread.h>
+#endif
+
+#include <netlink/netlink.h>
+#include <netlink/handlers.h>
+#include <netlink/cache.h>
+#include <netlink/route/tc.h>
+#include <netlink-private/object-api.h>
+#include <netlink-private/cache-api.h>
+#include <netlink-private/types.h>
+
+#define NSEC_PER_SEC	1000000000L
+
+struct trans_tbl {
+	int i;
+	const char *a;
+};
+
+#define __ADD(id, name) { .i = id, .a = #name },
+
+struct trans_list {
+	int i;
+	char *a;
+	struct nl_list_head list;
+};
+
+#ifdef NL_DEBUG
+#define NL_DBG(LVL,FMT,ARG...)						\
+	do {								\
+		if (LVL <= nl_debug)					\
+			fprintf(stderr,					\
+				"DBG<" #LVL ">%20s:%-4u %s: " FMT,	\
+				__FILE__, __LINE__,			\
+				__PRETTY_FUNCTION__, ##ARG);		\
+	} while (0)
+#else /* NL_DEBUG */
+#define NL_DBG(LVL,FMT,ARG...) do { } while(0)
+#endif /* NL_DEBUG */
+
+#define BUG()                            				\
+	do {                                 				\
+		fprintf(stderr, "BUG at file position %s:%d:%s\n",  	\
+			__FILE__, __LINE__, __PRETTY_FUNCTION__); 	\
+		assert(0);						\
+	} while (0)
+
+#define BUG_ON(condition)						\
+	do {								\
+		if (condition)						\
+			BUG();						\
+	} while (0)
+
+
+#define APPBUG(msg)							\
+	do {								\
+		fprintf(stderr, "APPLICATION BUG: %s:%d:%s: %s\n",	\
+			__FILE__, __LINE__, __PRETTY_FUNCTION__, msg);	\
+		assert(0);						\
+	} while(0)
+
+extern int __nl_read_num_str_file(const char *path,
+				  int (*cb)(long, const char *));
+
+extern int __trans_list_add(int, const char *, struct nl_list_head *);
+extern void __trans_list_clear(struct nl_list_head *);
+
+extern char *__type2str(int, char *, size_t, const struct trans_tbl *, size_t);
+extern int __str2type(const char *, const struct trans_tbl *, size_t);
+
+extern char *__list_type2str(int, char *, size_t, struct nl_list_head *);
+extern int __list_str2type(const char *, struct nl_list_head *);
+
+extern char *__flags2str(int, char *, size_t, const struct trans_tbl *, size_t);
+extern int __str2flags(const char *, const struct trans_tbl *, size_t);
+
+extern void dump_from_ops(struct nl_object *, struct nl_dump_params *);
+
+static inline int nl_cb_call(struct nl_cb *cb, int type, struct nl_msg *msg)
+{
+	int ret;
+
+	cb->cb_active = type;
+	ret = cb->cb_set[type](msg, cb->cb_args[type]);
+	cb->cb_active = __NL_CB_TYPE_MAX;
+	return ret;
+}
+
+#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
+
+/* This is also defined in stddef.h */
+#ifndef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#endif
+
+#define __init __attribute__ ((constructor))
+#define __exit __attribute__ ((destructor))
+#undef __deprecated
+#define __deprecated __attribute__ ((deprecated))
+
+#define min(x,y) ({ \
+	typeof(x) _x = (x);	\
+	typeof(y) _y = (y);	\
+	(void) (&_x == &_y);		\
+	_x < _y ? _x : _y; })
+
+#define max(x,y) ({ \
+	typeof(x) _x = (x);	\
+	typeof(y) _y = (y);	\
+	(void) (&_x == &_y);		\
+	_x > _y ? _x : _y; })
+
+#define min_t(type,x,y) \
+	({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })
+#define max_t(type,x,y) \
+	({ type __x = (x); type __y = (y); __x > __y ? __x: __y; })
+
+extern int nl_cache_parse(struct nl_cache_ops *, struct sockaddr_nl *,
+			  struct nlmsghdr *, struct nl_parser_param *);
+
+
+static inline void rtnl_copy_ratespec(struct rtnl_ratespec *dst,
+				      struct tc_ratespec *src)
+{
+	dst->rs_cell_log = src->cell_log;
+	dst->rs_overhead = src->overhead;
+	dst->rs_cell_align = src->cell_align;
+	dst->rs_mpu = src->mpu;
+	dst->rs_rate = src->rate;
+}
+
+static inline void rtnl_rcopy_ratespec(struct tc_ratespec *dst,
+				       struct rtnl_ratespec *src)
+{
+	dst->cell_log = src->rs_cell_log;
+	dst->overhead = src->rs_overhead;
+	dst->cell_align = src->rs_cell_align;
+	dst->mpu = src->rs_mpu;
+	dst->rate = src->rs_rate;
+}
+
+static inline char *nl_cache_name(struct nl_cache *cache)
+{
+	return cache->c_ops ? cache->c_ops->co_name : "unknown";
+}
+
+#define GENL_FAMILY(id, name) \
+	{ \
+		{ id, NL_ACT_UNSPEC, name }, \
+		END_OF_MSGTYPES_LIST, \
+	}
+
+static inline int wait_for_ack(struct nl_sock *sk)
+{
+	if (sk->s_flags & NL_NO_AUTO_ACK)
+		return 0;
+	else
+		return nl_wait_for_ack(sk);
+}
+
+static inline int build_sysconf_path(char **strp, const char *filename)
+{
+	char *sysconfdir;
+
+	sysconfdir = getenv("NLSYSCONFDIR");
+
+	if (!sysconfdir)
+		sysconfdir = SYSCONFDIR;
+
+	return asprintf(strp, "%s/%s", sysconfdir, filename);
+}
+
+#ifndef DISABLE_PTHREADS
+#define NL_LOCK(NAME) pthread_mutex_t (NAME) = PTHREAD_MUTEX_INITIALIZER
+#define NL_RW_LOCK(NAME) pthread_rwlock_t (NAME) = PTHREAD_RWLOCK_INITIALIZER
+
+static inline void nl_lock(pthread_mutex_t *lock)
+{
+	pthread_mutex_lock(lock);
+}
+
+static inline void nl_unlock(pthread_mutex_t *lock)
+{
+	pthread_mutex_unlock(lock);
+}
+
+static inline void nl_read_lock(pthread_rwlock_t *lock)
+{
+	pthread_rwlock_rdlock(lock);
+}
+
+static inline void nl_read_unlock(pthread_rwlock_t *lock)
+{
+	pthread_rwlock_unlock(lock);
+}
+
+static inline void nl_write_lock(pthread_rwlock_t *lock)
+{
+	pthread_rwlock_wrlock(lock);
+}
+
+static inline void nl_write_unlock(pthread_rwlock_t *lock)
+{
+	pthread_rwlock_unlock(lock);
+}
+
+#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(LOCK) do { } while(0)
+#define nl_unlock(LOCK) do { } while(0)
+#define nl_read_lock(LOCK) do { } while(0)
+#define nl_read_unlock(LOCK) do { } while(0)
+#define nl_write_lock(LOCK) do { } while(0)
+#define nl_write_unlock(LOCK) do { } while(0)
+#endif
+
+#endif
diff --git a/include/netlink-private/object-api.h b/include/netlink-private/object-api.h
new file mode 100644
index 0000000..f4fd71e
--- /dev/null
+++ b/include/netlink-private/object-api.h
@@ -0,0 +1,376 @@
+/*
+ * netlink-private/object-api.c		Object API
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_OBJECT_API_H_
+#define NETLINK_OBJECT_API_H_
+
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/object.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @ingroup object
+ * @defgroup object_api Object API
+ * @brief
+ *
+ * @par 1) Object Definition
+ * @code
+ * // Define your object starting with the common object header
+ * struct my_obj {
+ * 	NLHDR_COMMON
+ * 	int		my_data;
+ * };
+ *
+ * // Fill out the object operations structure
+ * struct nl_object_ops my_ops = {
+ * 	.oo_name	= "my_obj",
+ * 	.oo_size	= sizeof(struct my_obj),
+ * };
+ *
+ * // At this point the object can be allocated, you may want to provide a
+ * // separate _alloc() function to ease allocting objects of this kind.
+ * struct nl_object *obj = nl_object_alloc(&my_ops);
+ *
+ * // And release it again...
+ * nl_object_put(obj);
+ * @endcode
+ *
+ * @par 2) Allocating additional data
+ * @code
+ * // You may require to allocate additional data and store it inside
+ * // object, f.e. assuming there is a field `ptr'.
+ * struct my_obj {
+ * 	NLHDR_COMMON
+ * 	void *		ptr;
+ * };
+ *
+ * // And at some point you may assign allocated data to this field:
+ * my_obj->ptr = calloc(1, ...);
+ *
+ * // In order to not introduce any memory leaks you have to release
+ * // this data again when the last reference is given back.
+ * static void my_obj_free_data(struct nl_object *obj)
+ * {
+ * 	struct my_obj *my_obj = nl_object_priv(obj);
+ *
+ * 	free(my_obj->ptr);
+ * }
+ *
+ * // Also when the object is cloned, you must ensure for your pointer
+ * // stay valid even if one of the clones is freed by either making
+ * // a clone as well or increase the reference count.
+ * static int my_obj_clone(struct nl_object *src, struct nl_object *dst)
+ * {
+ * 	struct my_obj *my_src = nl_object_priv(src);
+ * 	struct my_obj *my_dst = nl_object_priv(dst);
+ *
+ * 	if (src->ptr) {
+ * 		dst->ptr = calloc(1, ...);
+ * 		memcpy(dst->ptr, src->ptr, ...);
+ * 	}
+ * }
+ *
+ * struct nl_object_ops my_ops = {
+ * 	...
+ * 	.oo_free_data	= my_obj_free_data,
+ * 	.oo_clone	= my_obj_clone,
+ * };
+ * @endcode
+ *
+ * @par 3) Object Dumping
+ * @code
+ * static int my_obj_dump_detailed(struct nl_object *obj,
+ * 				   struct nl_dump_params *params)
+ * {
+ * 	struct my_obj *my_obj = nl_object_priv(obj);
+ *
+ * 	// It is absolutely essential to use nl_dump() when printing
+ *	// any text to make sure the dumping parameters are respected.
+ * 	nl_dump(params, "Obj Integer: %d\n", my_obj->my_int);
+ *
+ * 	// Before we can dump the next line, make sure to prefix
+ *	// this line correctly.
+ * 	nl_new_line(params);
+ *
+ * 	// You may also split a line into multiple nl_dump() calls.
+ * 	nl_dump(params, "String: %s ", my_obj->my_string);
+ * 	nl_dump(params, "String-2: %s\n", my_obj->another_string);
+ * }
+ *
+ * struct nl_object_ops my_ops = {
+ * 	...
+ * 	.oo_dump[NL_DUMP_FULL]	= my_obj_dump_detailed,
+ * };
+ * @endcode
+ *
+ * @par 4) Object Attributes
+ * @code
+ * // The concept of object attributes is optional but can ease the typical
+ * // case of objects that have optional attributes, e.g. a route may have a
+ * // nexthop assigned but it is not required to.
+ *
+ * // The first step to define your object specific bitmask listing all
+ * // attributes
+ * #define MY_ATTR_FOO		(1<<0)
+ * #define MY_ATTR_BAR		(1<<1)
+ *
+ * // When assigning an optional attribute to the object, make sure
+ * // to mark its availability.
+ * my_obj->foo = 123123;
+ * my_obj->ce_mask |= MY_ATTR_FOO;
+ *
+ * // At any time you may use this mask to check for the availability
+ * // of the attribute, e.g. while dumping
+ * if (my_obj->ce_mask & MY_ATTR_FOO)
+ * 	nl_dump(params, "foo %d ", my_obj->foo);
+ *
+ * // One of the big advantages of this concept is that it allows for
+ * // standardized comparisons which make it trivial for caches to
+ * // identify unique objects by use of unified comparison functions.
+ * // In order for it to work, your object implementation must provide
+ * // a comparison function and define a list of attributes which
+ * // combined together make an object unique.
+ *
+ * static int my_obj_compare(struct nl_object *_a, struct nl_object *_b,
+ * 			     uint32_t attrs, int flags)
+ * {
+ * 	struct my_obj *a = nl_object_priv(_a):
+ * 	struct my_obj *b = nl_object_priv(_b):
+ * 	int diff = 0;
+ *
+ * 	// We help ourselves in defining our own DIFF macro which will
+ *	// call ATTR_DIFF() on both objects which will make sure to only
+ *	// compare the attributes if required.
+ * 	#define MY_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, MY_ATTR_##ATTR, a, b, EXPR)
+ *
+ * 	// Call our own diff macro for each attribute to build a bitmask
+ *	// representing the attributes which mismatch.
+ * 	diff |= MY_DIFF(FOO, a->foo != b->foo)
+ * 	diff |= MY_DIFF(BAR, strcmp(a->bar, b->bar))
+ *
+ * 	return diff;
+ * }
+ *
+ * // In order to identify identical objects with differing attributes
+ * // you must specify the attributes required to uniquely identify
+ * // your object. Make sure to not include too many attributes, this
+ * // list is used when caches look for an old version of an object.
+ * struct nl_object_ops my_ops = {
+ * 	...
+ * 	.oo_id_attrs		= MY_ATTR_FOO,
+ * 	.oo_compare		= my_obj_compare,
+ * };
+ * @endcode
+ * @{
+ */
+
+/**
+ * Common Object Header
+ *
+ * This macro must be included as first member in every object
+ * definition to allow objects to be cached.
+ */
+#define NLHDR_COMMON				\
+	int			ce_refcnt;	\
+	struct nl_object_ops *	ce_ops;		\
+	struct nl_cache *	ce_cache;	\
+	struct nl_list_head	ce_list;	\
+	int			ce_msgtype;	\
+	int			ce_flags;	\
+	uint32_t		ce_mask;
+
+struct nl_object
+{
+	NLHDR_COMMON
+};
+
+
+/**
+ * Return true if attribute is available in both objects
+ * @arg A		an object
+ * @arg B		another object
+ * @arg ATTR		attribute bit
+ *
+ * @return True if the attribute is available, otherwise false is returned.
+ */
+#define AVAILABLE(A, B, ATTR)		(((A)->ce_mask & (B)->ce_mask) & (ATTR))
+
+/**
+ * Return true if attribute is available in only one of both objects
+ * @arg A		an object
+ * @arg B		another object
+ * @arg ATTR		attribute bit
+ *
+ * @return True if the attribute is available in only one of both objects,
+ * otherwise false is returned.
+ */
+#define AVAILABLE_MISMATCH(A, B, ATTR)	(((A)->ce_mask ^ (B)->ce_mask) & (ATTR))
+
+/**
+ * Return true if attributes mismatch
+ * @arg A		an object
+ * @arg B		another object
+ * @arg ATTR		attribute bit
+ * @arg EXPR		Comparison expression
+ *
+ * This function will check if the attribute in question is available
+ * in both objects, if not this will count as a mismatch.
+ *
+ * If available the function will execute the expression which must
+ * return true if the attributes mismatch.
+ *
+ * @return True if the attribute mismatch, or false if they match.
+ */
+#define ATTR_MISMATCH(A, B, ATTR, EXPR)	(AVAILABLE_MISMATCH(A, B, ATTR) || \
+					 (AVAILABLE(A, B, ATTR) && (EXPR)))
+
+/**
+ * Return attribute bit if attribute does not match
+ * @arg LIST		list of attributes to be compared
+ * @arg ATTR		attribute bit
+ * @arg A		an object
+ * @arg B		another object
+ * @arg EXPR		Comparison expression
+ *
+ * This function will check if the attribute in question is available
+ * in both objects, if not this will count as a mismatch.
+ *
+ * If available the function will execute the expression which must
+ * return true if the attributes mismatch.
+ *
+ * In case the attributes mismatch, the attribute is returned, otherwise
+ * 0 is returned.
+ *
+ * @code
+ * diff |= ATTR_DIFF(attrs, MY_ATTR_FOO, a, b, a->foo != b->foo);
+ * @endcode
+ */
+#define ATTR_DIFF(LIST, ATTR, A, B, EXPR) \
+({	int diff = 0; \
+	if (((LIST) & (ATTR)) && ATTR_MISMATCH(A, B, ATTR, EXPR)) \
+		diff = ATTR; \
+	diff; })
+
+/**
+ * Object Operations
+ */
+struct nl_object_ops
+{
+	/**
+	 * Unique name of object type
+	 *
+	 * Must be in the form family/name, e.g. "route/addr"
+	 */
+	char *		oo_name;
+
+	/** Size of object including its header */
+	size_t		oo_size;
+
+	/* List of attributes needed to uniquely identify the object */
+	uint32_t	oo_id_attrs;
+
+	/**
+	 * Constructor function
+	 *
+	 * Will be called when a new object of this type is allocated.
+	 * Can be used to initialize members such as lists etc.
+	 */
+	void  (*oo_constructor)(struct nl_object *);
+
+	/**
+	 * Destructor function
+	 *
+	 * Will be called when an object is freed. Must free all
+	 * resources which may have been allocated as part of this
+	 * object.
+	 */
+	void  (*oo_free_data)(struct nl_object *);
+
+	/**
+	 * Cloning function
+	 *
+	 * Will be called when an object needs to be cloned. Please
+	 * note that the generic object code will make an exact
+	 * copy of the object first, therefore you only need to take
+	 * care of members which require reference counting etc.
+	 *
+	 * May return a negative error code to abort cloning.
+	 */
+	int  (*oo_clone)(struct nl_object *, struct nl_object *);
+
+	/**
+	 * Dumping functions
+	 *
+	 * Will be called when an object is dumped. The implementations
+	 * have to use nl_dump(), nl_dump_line(), and nl_new_line() to
+	 * dump objects.
+	 *
+	 * The functions must return the number of lines printed.
+	 */
+	void (*oo_dump[NL_DUMP_MAX+1])(struct nl_object *,
+				       struct nl_dump_params *);
+
+	/**
+	 * Comparison function
+	 *
+	 * Will be called when two objects of the same type are
+	 * compared. It takes the two objects in question, an object
+	 * specific bitmask defining which attributes should be
+	 * compared and flags to control the behaviour.
+	 *
+	 * The function must return a bitmask with the relevant bit
+	 * set for each attribute that mismatches.
+	 */
+	int   (*oo_compare)(struct nl_object *, struct nl_object *,
+			    uint32_t, int);
+
+
+	/**
+	 * update function
+	 *
+	 * Will be called when the object given by first argument
+	 * needs to be updated with the contents of the second object
+	 *
+	 * The function must return 0 for success and error for failure
+	 * to update. In case of failure its assumed that the original
+	 * object is not touched
+	 */
+	int   (*oo_update)(struct nl_object *, struct nl_object *);
+
+	/**
+	 * Hash Key generator function
+	 *
+	 * When called returns a hash key for the object being
+	 * referenced. This key will be used by higher level hash functions
+	 * to build association lists. Each object type gets to specify
+	 * it's own key formulation
+	 */
+	void   (*oo_keygen)(struct nl_object *, uint32_t *, uint32_t);
+
+	char *(*oo_attrs2str)(int, char *, size_t);
+
+	/**
+	 * Get key attributes by family function
+	 */
+	uint32_t   (*oo_id_attrs_get)(struct nl_object *);
+};
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink-private/route/link/api.h b/include/netlink-private/route/link/api.h
new file mode 100644
index 0000000..bb98ccc
--- /dev/null
+++ b/include/netlink-private/route/link/api.h
@@ -0,0 +1,153 @@
+/*
+ * netlink-private/route/link/api.h	Link Modules API
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_LINK_API_H_
+#define NETLINK_LINK_API_H_
+
+#include <netlink/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @ingroup link_api
+ *
+ * Available operations to modules implementing a link info type.
+ */
+struct rtnl_link_info_ops
+{
+	/** Name of link info type, must match name on kernel side */
+	char *		io_name;
+
+	/** Reference count, DO NOT MODIFY */
+	int		io_refcnt;
+
+	/** Called to assign an info type to a link.
+	 * Has to allocate enough resources to hold attributes. Can
+	 * use link->l_info to store a pointer. */
+	int	      (*io_alloc)(struct rtnl_link *);
+
+	/** Called to parse the link info attribute.
+	 * Must parse the attribute and assign all values to the link.
+	 */
+	int	      (*io_parse)(struct rtnl_link *,
+				  struct nlattr *,
+				  struct nlattr *);
+
+	/** Called when the link object is dumped.
+	 * Must dump the info type specific attributes. */
+	void	      (*io_dump[NL_DUMP_MAX+1])(struct rtnl_link *,
+						struct nl_dump_params *);
+
+	/** Called when a link object is cloned.
+	 * Must clone all info type specific attributes. */
+	int	      (*io_clone)(struct rtnl_link *, struct rtnl_link *);
+
+	/** Called when construction a link netlink message.
+	 * Must append all info type specific attributes to the message. */
+	int	      (*io_put_attrs)(struct nl_msg *, struct rtnl_link *);
+
+	/** Called to release all resources previously allocated
+	 * in either io_alloc() or io_parse(). */
+	void	      (*io_free)(struct rtnl_link *);
+
+	struct nl_list_head		io_list;
+};
+
+extern struct rtnl_link_info_ops *rtnl_link_info_ops_lookup(const char *);
+extern void			rtnl_link_info_ops_put(struct rtnl_link_info_ops *);
+extern int			rtnl_link_register_info(struct rtnl_link_info_ops *);
+extern int			rtnl_link_unregister_info(struct rtnl_link_info_ops *);
+
+
+/**
+ * @ingroup link_api
+ *
+ * Available operations to modules implementing a link address family.
+ */
+struct rtnl_link_af_ops
+{
+	/** The address family this operations set implements */
+	const unsigned int	ao_family;
+
+	/** Number of users of this operations, DO NOT MODIFY. */
+	int			ao_refcnt;
+
+	/** Validation policy for IFLA_PROTINFO attribute. This pointer
+	 * can be set to a nla_policy structure describing the minimal
+	 * requirements the attribute must meet. Failure of meeting these
+	 * requirements will result in a parsing error. */
+	const struct nla_policy *ao_protinfo_policy;
+
+	/** Called after address family has been assigned to link. Must
+	 * allocate data buffer to hold address family specific data and
+	 * store it in link->l_af_data. */
+	void *		      (*ao_alloc)(struct rtnl_link *);
+
+	/** Called when the link is cloned, must allocate a clone of the
+	 * address family specific buffer and return it. */
+	void *		      (*ao_clone)(struct rtnl_link *, void *);
+
+	/** Called when the link gets freed. Must free all allocated data */
+	void		      (*ao_free)(struct rtnl_link *, void *);
+
+	/** Called if a IFLA_PROTINFO attribute needs to be parsed. Typically
+	 * stores the parsed data in the address family specific buffer. */
+	int		      (*ao_parse_protinfo)(struct rtnl_link *,
+						   struct nlattr *, void *);
+
+	/** Called if a IFLA_AF_SPEC attribute needs to be parsed. Typically
+	 * stores the parsed data in the address family specific buffer. */
+	int		      (*ao_parse_af)(struct rtnl_link *,
+					     struct nlattr *, void *);
+
+	/** Called if a link message is sent to the kernel. Must append the
+	 * link address family specific attributes to the message. */
+	int		      (*ao_fill_af)(struct rtnl_link *,
+					    struct nl_msg *msg, void *);
+
+	/** Dump address family specific link attributes */
+	void		      (*ao_dump[NL_DUMP_MAX+1])(struct rtnl_link *,
+							struct nl_dump_params *,
+							void *);
+
+	/** Comparison function
+	 *
+	 * Will be called when two links are compared for their af data. It
+	 * takes two link objects in question, an object specific bitmask
+	 * defining which attributes should be compared and flags to control
+	 * the behaviour
+	 *
+	 * The function must return a bitmask with the relevant bit set for
+	 * each attribute that mismatches
+	 */
+	int		      (*ao_compare)(struct rtnl_link *,
+					    struct rtnl_link *, int, uint32_t, int);
+};
+
+extern struct rtnl_link_af_ops *rtnl_link_af_ops_lookup(unsigned int);
+extern void			rtnl_link_af_ops_put(struct rtnl_link_af_ops *);
+extern void *			rtnl_link_af_alloc(struct rtnl_link *,
+						const struct rtnl_link_af_ops *);
+extern void *			rtnl_link_af_data(const struct rtnl_link *,
+						const struct rtnl_link_af_ops *);
+extern int			rtnl_link_af_register(struct rtnl_link_af_ops *);
+extern int			rtnl_link_af_unregister(struct rtnl_link_af_ops *);
+extern int			rtnl_link_af_data_compare(struct rtnl_link *a,
+							  struct rtnl_link *b,
+							  int family);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink-private/route/tc-api.h b/include/netlink-private/route/tc-api.h
new file mode 100644
index 0000000..bf0c8a3
--- /dev/null
+++ b/include/netlink-private/route/tc-api.h
@@ -0,0 +1,134 @@
+/*
+ * netlink-private/route/tc-api.h	Traffic Control API
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2011-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_TC_API_H_
+#define NETLINK_TC_API_H_
+
+#include <netlink/netlink.h>
+#include <netlink/msg.h>
+#include <netlink/route/tc.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Traffic control object operations
+ * @ingroup tc
+ *
+ * This structure holds function pointers and settings implementing
+ * the features of each traffic control object implementation.
+ */
+struct rtnl_tc_ops
+{
+	/**
+	 * Name of traffic control module
+	 */
+	char *to_kind;
+
+	/**
+	 * Type of traffic control object
+	 */
+	enum rtnl_tc_type to_type;
+
+
+	/**
+	 * Size of private data
+	 */
+	size_t to_size;
+
+	/**
+	 * Dump callbacks
+	 */
+	void (*to_dump[NL_DUMP_MAX+1])(struct rtnl_tc *, void *,
+				       struct nl_dump_params *);
+	/**
+	 * Used to fill the contents of TCA_OPTIONS
+	 */
+	int (*to_msg_fill)(struct rtnl_tc *, void *, struct nl_msg *);
+
+	/**
+	 * Uesd to to fill tc related messages, unlike with to_msg_fill,
+	 * the contents is not encapsulated with a TCA_OPTIONS nested
+	 * attribute.
+	 */
+	int (*to_msg_fill_raw)(struct rtnl_tc *, void *, struct nl_msg *);
+
+	/**
+	 * TCA_OPTIONS message parser
+	 */
+	int (*to_msg_parser)(struct rtnl_tc *, void *);
+
+	/**
+	 * Called before a tc object is destroyed
+	 */
+	void (*to_free_data)(struct rtnl_tc *, void *);
+
+	/**
+	 * Called whenever a classifier object needs to be cloned
+	 */
+	int (*to_clone)(void *, void *);
+
+	/**
+	 * Internal, don't touch
+	 */
+	struct nl_list_head to_list;
+};
+
+struct rtnl_tc_type_ops
+{
+	enum rtnl_tc_type tt_type;
+
+	char *tt_dump_prefix;
+
+	/**
+	 * Dump callbacks
+	 */
+	void (*tt_dump[NL_DUMP_MAX+1])(struct rtnl_tc *,
+				        struct nl_dump_params *);
+};
+
+extern int			rtnl_tc_msg_parse(struct nlmsghdr *,
+						  struct rtnl_tc *);
+extern int			rtnl_tc_msg_build(struct rtnl_tc *, int,
+						  int, struct nl_msg **);
+
+extern void			rtnl_tc_free_data(struct nl_object *);
+extern int			rtnl_tc_clone(struct nl_object *,
+					      struct nl_object *);
+extern void			rtnl_tc_dump_line(struct nl_object *,
+						  struct nl_dump_params *);
+extern void			rtnl_tc_dump_details(struct nl_object *,
+						     struct nl_dump_params *);
+extern void			rtnl_tc_dump_stats(struct nl_object *,
+						   struct nl_dump_params *);
+extern int			rtnl_tc_compare(struct nl_object *,
+						struct nl_object *,
+						uint32_t, int);
+
+extern void *			rtnl_tc_data(struct rtnl_tc *);
+extern void *			rtnl_tc_data_check(struct rtnl_tc *,
+						   struct rtnl_tc_ops *);
+
+extern struct rtnl_tc_ops *	rtnl_tc_lookup_ops(enum rtnl_tc_type,
+						   const char *);
+extern struct rtnl_tc_ops *	rtnl_tc_get_ops(struct rtnl_tc *);
+extern int 			rtnl_tc_register(struct rtnl_tc_ops *);
+extern void 			rtnl_tc_unregister(struct rtnl_tc_ops *);
+
+extern void			rtnl_tc_type_register(struct rtnl_tc_type_ops *);
+extern void			rtnl_tc_type_unregister(struct rtnl_tc_type_ops *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink-private/socket.h b/include/netlink-private/socket.h
new file mode 100644
index 0000000..86a440c
--- /dev/null
+++ b/include/netlink-private/socket.h
@@ -0,0 +1,31 @@
+/*
+ * netlink-private/socket.h		Private declarations for socket
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2014 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_SOCKET_PRIV_H_
+#define NETLINK_SOCKET_PRIV_H_
+
+#include <netlink-private/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int _nl_socket_is_local_port_unspecified (struct nl_sock *sk);
+uint32_t _nl_socket_generate_local_port_no_release(struct nl_sock *sk);
+
+void _nl_socket_used_ports_release_all(const uint32_t *used_ports);
+void _nl_socket_used_ports_set(uint32_t *used_ports, uint32_t port);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink-private/tc.h b/include/netlink-private/tc.h
new file mode 100644
index 0000000..d0cb283
--- /dev/null
+++ b/include/netlink-private/tc.h
@@ -0,0 +1,57 @@
+/*
+ * netlink-private/tc.h		Local Traffic Control Interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_TC_PRIV_H_
+#define NETLINK_TC_PRIV_H_
+
+#include <netlink-private/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TCA_ATTR_HANDLE		0x0001
+#define TCA_ATTR_PARENT		0x0002
+#define TCA_ATTR_IFINDEX	0x0004
+#define TCA_ATTR_KIND		0x0008
+#define TCA_ATTR_FAMILY		0x0010
+#define TCA_ATTR_INFO		0x0020
+#define TCA_ATTR_OPTS		0x0040
+#define TCA_ATTR_STATS		0x0080
+#define TCA_ATTR_XSTATS		0x0100
+#define TCA_ATTR_LINK		0x0200
+#define TCA_ATTR_MTU		0x0400
+#define TCA_ATTR_MPU		0x0800
+#define TCA_ATTR_OVERHEAD	0x1000
+#define TCA_ATTR_LINKTYPE	0x2000
+#define TCA_ATTR_MAX		TCA_ATTR_LINKTYPE
+
+extern int tca_parse(struct nlattr **, int, struct rtnl_tc *,
+		     struct nla_policy *);
+
+#define RTNL_TC_RTABLE_SIZE	256
+
+extern int rtnl_tc_build_rate_table(struct rtnl_tc *tc, struct rtnl_ratespec *,
+				    uint32_t *);
+
+
+static inline void *tca_xstats(struct rtnl_tc *tca)
+{
+	return tca->tc_xstats->d_data;
+}
+
+extern struct nl_af_group tc_groups[];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink-types.h b/include/netlink-private/types.h
similarity index 71%
rename from include/netlink-types.h
rename to include/netlink-private/types.h
index ff699bb..3ff4fe1 100644
--- a/include/netlink-types.h
+++ b/include/netlink-private/types.h
@@ -1,12 +1,13 @@
 /*
- * netlink-types.h	Netlink Types (Private)
+ * netlink-private/types.h	Netlink Types (Private)
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
  */
 
 #ifndef NETLINK_LOCAL_TYPES_H_
@@ -17,6 +18,10 @@
 #include <netlink/route/qdisc.h>
 #include <netlink/route/rtnl.h>
 #include <netlink/route/route.h>
+#include <netlink/idiag/idiagnl.h>
+#include <netlink/netfilter/ct.h>
+#include <netlink-private/route/tc-api.h>
+#include <linux/tc_act/tc_mirred.h>
 
 #define NL_SOCK_BUFSIZE_SET	(1<<0)
 #define NL_SOCK_PASSCRED	(1<<1)
@@ -29,12 +34,13 @@
 struct nl_cache_ops;
 struct nl_sock;
 struct nl_object;
+struct nl_hash_table;
 
 struct nl_cb
 {
 	nl_recvmsg_msg_cb_t	cb_set[NL_CB_TYPE_MAX+1];
 	void *			cb_args[NL_CB_TYPE_MAX+1];
-	
+
 	nl_recvmsg_err_cb_t	cb_err;
 	void *			cb_err_arg;
 
@@ -56,6 +62,8 @@
 					      struct nl_msg *);
 
 	int			cb_refcnt;
+	/** indicates the callback that is currently active */
+	enum nl_cb_type		cb_active;
 };
 
 struct nl_sock
@@ -68,6 +76,7 @@
 	unsigned int		s_seq_expect;
 	int			s_flags;
 	struct nl_cb *		s_cb;
+	size_t			s_bufsize;
 };
 
 struct nl_cache
@@ -76,6 +85,9 @@
 	int			c_nitems;
 	int                     c_iarg1;
 	int                     c_iarg2;
+	int			c_refcnt;
+	unsigned int		c_flags;
+	struct nl_hash_table *	hashtable;
 	struct nl_cache_ops *   c_ops;
 };
 
@@ -91,7 +103,8 @@
 	int			cm_protocol;
 	int			cm_flags;
 	int			cm_nassocs;
-	struct nl_sock *	cm_handle;
+	struct nl_sock *	cm_sock;
+	struct nl_sock *	cm_sync_sock;
 	struct nl_cache_assoc *	cm_assocs;
 };
 
@@ -101,11 +114,6 @@
 
 #define NL_OBJ_MARK		1
 
-struct nl_object
-{
-	NLHDR_COMMON
-};
-
 struct nl_data
 {
 	size_t			d_size;
@@ -150,29 +158,42 @@
 {
 	NLHDR_COMMON
 
-	char		l_name[IFNAMSIZ];
-
-	uint32_t	l_family;
-	uint32_t	l_arptype;
-	uint32_t	l_index;
-	uint32_t	l_flags;
-	uint32_t	l_change;
-	uint32_t 	l_mtu;
-	uint32_t	l_link;
-	uint32_t	l_txqlen;
-	uint32_t	l_weight;
-	uint32_t	l_master;
-	struct nl_addr *l_addr;	
-	struct nl_addr *l_bcast;
-	char		l_qdisc[IFQDISCSIZ];
-	struct rtnl_link_map l_map;
-	uint64_t	l_stats[RTNL_LINK_STATS_MAX+1];
-	uint32_t	l_flag_mask;
-	uint8_t		l_operstate;
-	uint8_t		l_linkmode;
+	char				l_name[IFNAMSIZ];
+	uint32_t			l_family;
+	uint32_t			l_arptype;
+	uint32_t			l_index;
+	uint32_t			l_flags;
+	uint32_t			l_change;
+	uint32_t 			l_mtu;
+	uint32_t			l_link;
+	uint32_t			l_txqlen;
+	uint32_t			l_weight;
+	uint32_t			l_master;
+	struct nl_addr *		l_addr;
+	struct nl_addr *		l_bcast;
+	char				l_qdisc[IFQDISCSIZ];
+	struct rtnl_link_map		l_map;
+	uint64_t			l_stats[RTNL_LINK_STATS_MAX+1];
+	uint32_t			l_flag_mask;
+	uint32_t			l_num_vf;
+	uint8_t				l_operstate;
+	uint8_t				l_linkmode;
 	/* 2 byte hole */
-	struct rtnl_link_info_ops *l_info_ops;
-	void *		l_info;
+	char *				l_info_kind;
+	struct rtnl_link_info_ops *	l_info_ops;
+	void *				l_af_data[AF_MAX];
+	void *				l_info;
+	char *				l_ifalias;
+	uint32_t			l_promiscuity;
+	uint32_t			l_num_tx_queues;
+	uint32_t			l_num_rx_queues;
+	uint32_t			l_group;
+	uint8_t				l_carrier;
+	/* 3 byte hole */
+	struct rtnl_link_af_ops *	l_af_ops;
+	struct nl_data *		l_phys_port_id;
+	int				l_ns_fd;
+	pid_t				l_ns_pid;
 };
 
 struct rtnl_ncacheinfo
@@ -191,28 +212,29 @@
 	uint32_t	n_ifindex;
 	uint16_t	n_state;
 	uint8_t		n_flags;
-	uint8_t		n_type;	
+	uint8_t		n_type;
 	struct nl_addr *n_lladdr;
-	struct nl_addr *n_dst;	
+	struct nl_addr *n_dst;
 	uint32_t	n_probes;
 	struct rtnl_ncacheinfo n_cacheinfo;
 	uint32_t                n_state_mask;
 	uint32_t                n_flag_mask;
+	uint32_t		n_master;
 };
 
 
 struct rtnl_addr_cacheinfo
 {
-	/* Preferred lifetime in seconds */
+	/* Preferred lifetime in seconds, ticking from when the message gets constructed */
 	uint32_t aci_prefered;
 
-	/* Valid lifetime in seconds */
+	/* Valid lifetime in seconds, ticking from when the message gets constructed */
 	uint32_t aci_valid;
 
-	/* Timestamp of creation in 1/100s seince boottime */
+	/* Timestamp of creation in 1/100s since boottime, clock_gettime(CLOCK_MONOTONIC) */
 	uint32_t aci_cstamp;
 
-	/* Timestamp of last update in 1/100s since boottime */
+	/* Timestamp of last update in 1/100s since boottime, clock_gettime(CLOCK_MONOTONIC) */
 	uint32_t aci_tstamp;
 };
 
@@ -222,20 +244,21 @@
 
 	uint8_t		a_family;
 	uint8_t		a_prefixlen;
-	uint8_t		a_flags;
 	uint8_t		a_scope;
+	uint32_t	a_flags;
 	uint32_t	a_ifindex;
 
-	struct nl_addr *a_peer;	
+	struct nl_addr *a_peer;
 	struct nl_addr *a_local;
 	struct nl_addr *a_bcast;
 	struct nl_addr *a_anycast;
 	struct nl_addr *a_multicast;
 
 	struct rtnl_addr_cacheinfo a_cacheinfo;
-	
+
 	char a_label[IFNAMSIZ];
 	uint32_t a_flag_mask;
+	struct rtnl_link *a_link;
 };
 
 struct rtnl_nexthop
@@ -281,20 +304,21 @@
 struct rtnl_rule
 {
 	NLHDR_COMMON
-
-	uint64_t	r_mark;
-	uint32_t	r_prio;
-	uint32_t	r_realms;
-	uint32_t	r_table;
-	uint8_t		r_dsfield;
-	uint8_t		r_type;
 	uint8_t		r_family;
-	uint8_t		r_src_len;
-	uint8_t		r_dst_len;
-	char		r_iif[IFNAMSIZ];
+	uint8_t		r_action;
+	uint8_t		r_dsfield; /* ipv4 only */
+	uint8_t		r_unused;
+	uint32_t	r_table;
+	uint32_t	r_flags;
+	uint32_t	r_prio;
+	uint32_t	r_mark;
+	uint32_t	r_mask;
+	uint32_t	r_goto;
+	uint32_t	r_flow; /* ipv4 only */
 	struct nl_addr *r_src;
 	struct nl_addr *r_dst;
-	struct nl_addr *r_srcmap;
+	char		r_iifname[IFNAMSIZ];
+	char		r_oifname[IFNAMSIZ];
 };
 
 struct rtnl_neightbl_parms
@@ -383,7 +407,7 @@
 	 * Queue length for the delayed proxy arp requests.
 	 */
 	uint32_t		ntp_proxy_qlen;
-	
+
 	/**
 	 * Mask of available parameter attributes
 	 */
@@ -414,8 +438,8 @@
 struct rtnl_ratespec
 {
 	uint8_t			rs_cell_log;
-	uint16_t		rs_feature;
-	uint16_t		rs_addend;
+	uint16_t		rs_overhead;
+	int16_t			rs_cell_align;
 	uint16_t		rs_mpu;
 	uint32_t		rs_rate;
 };
@@ -443,43 +467,57 @@
 
 #define TCKINDSIZ	32
 
-#define NL_TCA_GENERIC(pre)				\
+#define NL_TC_GENERIC(pre)				\
 	NLHDR_COMMON					\
 	uint32_t		pre ##_family;		\
 	uint32_t		pre ##_ifindex;		\
 	uint32_t		pre ##_handle;		\
 	uint32_t		pre ##_parent;		\
 	uint32_t		pre ##_info;		\
+	uint32_t		pre ##_mtu;		\
+	uint32_t		pre ##_mpu;		\
+	uint32_t		pre ##_overhead;	\
+	uint32_t		pre ##_linktype;	\
 	char			pre ##_kind[TCKINDSIZ];	\
 	struct nl_data *	pre ##_opts;		\
 	uint64_t		pre ##_stats[RTNL_TC_STATS_MAX+1]; \
 	struct nl_data *	pre ##_xstats;		\
 	struct nl_data *	pre ##_subdata;		\
+	struct rtnl_link *	pre ##_link;		\
+	struct rtnl_tc_ops *	pre ##_ops;		\
+	enum rtnl_tc_type	pre ##_type
 
-
-struct rtnl_tca
+struct rtnl_tc
 {
-	NL_TCA_GENERIC(tc);
+	NL_TC_GENERIC(tc);
 };
 
 struct rtnl_qdisc
 {
-	NL_TCA_GENERIC(q);
-	struct rtnl_qdisc_ops	*q_ops;
+	NL_TC_GENERIC(q);
 };
 
 struct rtnl_class
 {
-	NL_TCA_GENERIC(c);
-	struct rtnl_class_ops	*c_ops;
+	NL_TC_GENERIC(c);
 };
 
 struct rtnl_cls
 {
-	NL_TCA_GENERIC(c);
+	NL_TC_GENERIC(c);
 	uint16_t		c_prio;
 	uint16_t		c_protocol;
-	struct rtnl_cls_ops	*c_ops;
+};
+
+struct rtnl_act
+{
+	NL_TC_GENERIC(c);
+	struct rtnl_act *	a_next;
+};
+
+struct rtnl_mirred
+{
+	struct tc_mirred m_parm;
 };
 
 struct rtnl_u32
@@ -490,7 +528,7 @@
 	uint32_t		cu_link;
 	struct nl_data *	cu_pcnt;
 	struct nl_data *	cu_selector;
-	struct nl_data *	cu_act;
+	struct rtnl_act*	cu_act;
 	struct nl_data *	cu_police;
 	char			cu_indev[IFNAMSIZ];
 	int			cu_mask;
@@ -508,6 +546,7 @@
 	struct nl_data *	cf_act;
 	struct nl_data *	cf_police;
 	char			cf_indev[IFNAMSIZ];
+	uint32_t		cf_fwmask;
 	int			cf_mask;
 };
 
@@ -516,12 +555,14 @@
 	uint16_t		e_id;
 	uint16_t		e_kind;
 	uint16_t		e_flags;
+	uint16_t		e_index;
+	size_t			e_datalen;
 
 	struct nl_list_head	e_childs;
 	struct nl_list_head	e_list;
 	struct rtnl_ematch_ops *e_ops;
 
-	char			e_data[0];
+	void *			e_data;
 };
 
 struct rtnl_ematch_tree
@@ -562,7 +603,6 @@
 struct rtnl_tbf
 {
 	uint32_t		qt_limit;
-	uint32_t		qt_mpu;
 	struct rtnl_ratespec	qt_rate;
 	uint32_t		qt_rate_bucket;
 	uint32_t		qt_rate_txtime;
@@ -627,20 +667,19 @@
 	uint32_t		qh_rate2quantum;
 	uint32_t		qh_defcls;
 	uint32_t		qh_mask;
+	uint32_t		qh_direct_pkts;
 };
 
 struct rtnl_htb_class
 {
 	uint32_t		ch_prio;
-	uint32_t		ch_mtu;
 	struct rtnl_ratespec	ch_rate;
 	struct rtnl_ratespec	ch_ceil;
 	uint32_t		ch_rbuffer;
 	uint32_t		ch_cbuffer;
 	uint32_t		ch_quantum;
-	uint8_t			ch_overhead;
-	uint8_t			ch_mpu;
 	uint32_t		ch_mask;
+	uint32_t		ch_level;
 };
 
 struct rtnl_cbq
@@ -665,6 +704,23 @@
 	uint32_t	qr_mask;
 };
 
+struct rtnl_plug
+{
+	int             action;
+	uint32_t        limit;
+};
+
+struct rtnl_fq_codel
+{
+	int		fq_limit;
+	uint32_t	fq_target;
+	uint32_t	fq_interval;
+	int		fq_flows;
+	uint32_t	fq_quantum;
+	int		fq_ecn;
+	uint32_t	fq_mask;
+};
+
 struct flnl_request
 {
 	NLHDR_COMMON
@@ -702,6 +758,13 @@
 	struct nl_list_head	o_list;
 };
 
+struct genl_family_grp {
+        struct genl_family      *family;        /* private */
+        struct nl_list_head     list;           /* private */
+        char                    name[GENL_NAMSIZ];
+        u_int32_t               id;
+};
+
 struct genl_family
 {
 	NLHDR_COMMON
@@ -713,6 +776,7 @@
 	uint32_t		gf_maxattr;
 
 	struct nl_list_head	gf_ops;
+	struct nl_list_head	gf_mc_grps;
 };
 
 union nfnl_ct_proto
@@ -755,9 +819,56 @@
 	uint32_t		ct_mark;
 	uint32_t		ct_use;
 	uint32_t		ct_id;
+	uint16_t		ct_zone;
 
 	struct nfnl_ct_dir	ct_orig;
 	struct nfnl_ct_dir	ct_repl;
+
+	struct nfnl_ct_timestamp ct_tstamp;
+};
+
+union nfnl_exp_protodata {
+	struct {
+		uint16_t	src;
+		uint16_t	dst;
+	} port;
+	struct {
+		uint16_t	id;
+		uint8_t		type;
+		uint8_t		code;
+	} icmp;
+};
+
+// Allow for different master/expect l4 protocols
+struct nfnl_exp_proto
+{
+	uint8_t						l4protonum;
+	union nfnl_exp_protodata	l4protodata;
+};
+
+struct nfnl_exp_dir {
+	struct nl_addr *		src;
+	struct nl_addr *		dst;
+	struct nfnl_exp_proto	proto;
+};
+
+struct nfnl_exp {
+	NLHDR_COMMON
+
+	uint8_t			exp_family;
+	uint32_t		exp_timeout;
+	uint32_t		exp_id;
+	uint16_t		exp_zone;
+	uint32_t		exp_class;
+	uint32_t		exp_flags;
+	char *			exp_helper_name;
+	char *			exp_fn;
+	uint8_t			exp_nat_dir;
+
+	struct nfnl_exp_dir		exp_expect;
+	struct nfnl_exp_dir		exp_master;
+	struct nfnl_exp_dir		exp_mask;
+	struct nfnl_exp_dir		exp_nat;
 };
 
 struct nfnl_log {
@@ -826,4 +937,67 @@
 	uint32_t		queue_msg_verdict;
 };
 
+struct ematch_quoted {
+	char *	data;
+	size_t	len;
+	int	index;
+};
+
+struct idiagnl_meminfo {
+	NLHDR_COMMON
+
+	uint32_t idiag_rmem;
+	uint32_t idiag_wmem;
+	uint32_t idiag_fmem;
+	uint32_t idiag_tmem;
+};
+
+struct idiagnl_vegasinfo {
+	NLHDR_COMMON
+
+	uint32_t tcpv_enabled;
+	uint32_t tcpv_rttcnt;
+	uint32_t tcpv_rtt;
+	uint32_t tcpv_minrtt;
+};
+
+struct idiagnl_msg {
+	NLHDR_COMMON
+
+	uint8_t			    idiag_family;
+	uint8_t			    idiag_state;
+	uint8_t			    idiag_timer;
+	uint8_t			    idiag_retrans;
+	uint16_t		    idiag_sport;
+	uint16_t		    idiag_dport;
+	struct nl_addr *	    idiag_src;
+	struct nl_addr *	    idiag_dst;
+	uint32_t		    idiag_ifindex;
+	uint32_t		    idiag_expires;
+	uint32_t		    idiag_rqueue;
+	uint32_t		    idiag_wqueue;
+	uint32_t		    idiag_uid;
+	uint32_t		    idiag_inode;
+
+	uint8_t			    idiag_tos;
+	uint8_t			    idiag_tclass;
+	uint8_t			    idiag_shutdown;
+	char *			    idiag_cong;
+	struct idiagnl_meminfo *    idiag_meminfo;
+	struct idiagnl_vegasinfo *  idiag_vegasinfo;
+	struct tcp_info		    idiag_tcpinfo;
+	uint32_t		    idiag_skmeminfo[IDIAG_SK_MEMINFO_VARS];
+};
+
+struct idiagnl_req {
+	NLHDR_COMMON
+
+	uint8_t			idiag_family;
+	uint8_t			idiag_ext;
+	struct nl_addr *	idiag_src;
+	struct nl_addr *	idiag_dst;
+	uint32_t		idiag_ifindex;
+	uint32_t		idiag_states;
+	uint32_t		idiag_dbs;
+};
 #endif
diff --git a/include/netlink-tc.h b/include/netlink-tc.h
deleted file mode 100644
index 71a20ff..0000000
--- a/include/netlink-tc.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * netlink-tc.h		Local Traffic Control Interface
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
- */
-
-#ifndef NETLINK_TC_PRIV_H_
-#define NETLINK_TC_PRIV_H_
-
-#include <netlink-local.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define TCA_ATTR_HANDLE    0x001
-#define TCA_ATTR_PARENT    0x002
-#define TCA_ATTR_IFINDEX   0x004
-#define TCA_ATTR_KIND      0x008
-#define TCA_ATTR_FAMILY    0x010
-#define TCA_ATTR_INFO      0x020
-#define TCA_ATTR_OPTS      0x040
-#define TCA_ATTR_STATS     0x080
-#define TCA_ATTR_XSTATS    0x100
-#define TCA_ATTR_MAX	  TCA_ATTR_XSTATS
-
-extern int tca_parse(struct nlattr **, int, struct rtnl_tca *,
-		     struct nla_policy *);
-extern int  tca_msg_parser(struct nlmsghdr *, struct rtnl_tca *);
-extern void tca_free_data(struct rtnl_tca *);
-extern int  tca_clone(struct rtnl_tca *, struct rtnl_tca *);
-extern void tca_dump_line(struct rtnl_tca *, const char *,
-			  struct nl_dump_params *);
-extern void tca_dump_details(struct rtnl_tca *, struct nl_dump_params *);
-extern void tca_dump_stats(struct rtnl_tca *, struct nl_dump_params *);
-extern int  tca_compare(struct nl_object *, struct nl_object *, uint32_t, int);
-
-extern void tca_set_ifindex(struct rtnl_tca *, int);
-extern int  tca_get_ifindex(struct rtnl_tca *);
-extern void tca_set_handle(struct rtnl_tca *, uint32_t);
-extern uint32_t tca_get_handle(struct rtnl_tca *);
-extern void tca_set_parent(struct rtnl_tca *, uint32_t);
-extern uint32_t tca_get_parent(struct rtnl_tca *);
-extern void tca_set_kind(struct rtnl_tca *, const char *);
-extern char *tca_get_kind(struct rtnl_tca *);
-extern uint64_t tca_get_stat(struct rtnl_tca *, int );
-
-extern int tca_build_msg(struct rtnl_tca *, int, int, struct nl_msg **);
-
-static inline void *tca_priv(struct rtnl_tca *tca)
-{
-	return tca->tc_subdata;
-}
-
-static inline void *tca_xstats(struct rtnl_tca *tca)
-{
-	return tca->tc_xstats->d_data;
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/include/netlink/addr.h b/include/netlink/addr.h
index cc3d201..db3e4c2 100644
--- a/include/netlink/addr.h
+++ b/include/netlink/addr.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_ADDR_H_
@@ -27,9 +27,6 @@
 extern int		nl_addr_parse(const char *, int, struct nl_addr **);
 extern struct nl_addr *	nl_addr_clone(struct nl_addr *);
 
-/* Destroyage */
-extern void		nl_addr_destroy(struct nl_addr *);
-
 /* Usage Management */
 extern struct nl_addr *	nl_addr_get(struct nl_addr *);
 extern void		nl_addr_put(struct nl_addr *);
@@ -43,7 +40,7 @@
 extern int		nl_addr_fill_sockaddr(struct nl_addr *,
 					      struct sockaddr *, socklen_t *);
 extern int		nl_addr_info(struct nl_addr *, struct addrinfo **);
-extern int		nl_addr_resolve(struct nl_addr *addr, char *host, size_t hostlen);
+extern int		nl_addr_resolve(struct nl_addr *, char *, size_t);
 
 /* Access Functions */
 extern void		nl_addr_set_family(struct nl_addr *, int);
diff --git a/include/netlink/attr.h b/include/netlink/attr.h
index 8479c23..82e4c38 100644
--- a/include/netlink/attr.h
+++ b/include/netlink/attr.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_ATTR_H_
@@ -28,12 +28,12 @@
  * @{
  */
 
- /**
-  * @ingroup attr
-  * Basic attribute data types
-  *
-  * See \ref attr_datatypes for more details.
-  */
+/**
+ * @ingroup attr
+ * Basic attribute data types
+ *
+ * See section @core_doc{core_attr_parse,Attribute Parsing} for more details.
+ */
 enum {
 	NLA_UNSPEC,	/**< Unspecified type, binary data chunk */
 	NLA_U8,		/**< 8 bit integer */
@@ -55,7 +55,7 @@
  * @ingroup attr
  * Attribute validation policy.
  *
- * See \ref attr_datatypes for more details.
+ * See section @core_doc{core_attr_parse,Attribute Parsing} for more details.
  */
 struct nla_policy {
 	/** Type of attribute or NLA_UNSPEC */
@@ -124,8 +124,10 @@
 extern int		nla_put_nested(struct nl_msg *, int, struct nl_msg *);
 extern struct nlattr *	nla_nest_start(struct nl_msg *, int);
 extern int		nla_nest_end(struct nl_msg *, struct nlattr *);
+extern void		nla_nest_cancel(struct nl_msg *, struct nlattr *);
 extern int		nla_parse_nested(struct nlattr **, int, struct nlattr *,
 					 struct nla_policy *);
+extern int		nla_is_nested(struct nlattr *);
 
 /**
  * @name Attribute Construction (Exception Based)
@@ -203,7 +205,7 @@
  * @arg value		NUL terminated character string.
  */
 #define NLA_PUT_STRING(msg, attrtype, value) \
-	NLA_PUT(msg, attrtype, strlen(value) + 1, value)
+	NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value)
 
 /**
  * Add flag attribute to netlink message.
diff --git a/include/netlink/cache-api.h b/include/netlink/cache-api.h
index 22fc449..e43c7ca 100644
--- a/include/netlink/cache-api.h
+++ b/include/netlink/cache-api.h
@@ -6,194 +6,15 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2013 Thomas Graf <tgraf@suug.ch>
  */
 
-#ifndef NETLINK_CACHE_API_H_
-#define NETLINK_CACHE_API_H_
+#ifndef NETLINK_DUMMY_CACHE_API_H_
+#define NETLINK_DUMMY_CACHE_API_H_
 
 #include <netlink/netlink.h>
+#include <netlink/cache.h>
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * @ingroup cache
- * @defgroup cache_api Cache Implementation
- * @brief
- *
- * @par 1) Cache Definition
- * @code
- * struct nl_cache_ops my_cache_ops = {
- * 	.co_name		= "route/link",
- * 	.co_protocol		= NETLINK_ROUTE,
- * 	.co_hdrsize		= sizeof(struct ifinfomsg),
- * 	.co_obj_ops		= &my_obj_ops,
- * };
- * @endcode
- *
- * @par 2) 
- * @code
- * // The simplest way to fill a cache is by providing a request-update
- * // function which must trigger a complete dump on the kernel-side of
- * // whatever the cache covers.
- * static int my_request_update(struct nl_cache *cache,
- * 				struct nl_sock *socket)
- * {
- * 	// In this example, we request a full dump of the interface table
- * 	return nl_rtgen_request(socket, RTM_GETLINK, AF_UNSPEC, NLM_F_DUMP);
- * }
- *
- * // The resulting netlink messages sent back will be fed into a message
- * // parser one at a time. The message parser has to extract all relevant
- * // information from the message and create an object reflecting the
- * // contents of the message and pass it on to the parser callback function
- * // provide which will add the object to the cache.
- * static int my_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
- * 			    struct nlmsghdr *nlh, struct nl_parser_param *pp)
- * {
- * 	struct my_obj *obj;
- *
- * 	obj = my_obj_alloc();
- * 	obj->ce_msgtype = nlh->nlmsg_type;
- *
- * 	// Parse the netlink message and continue creating the object.
- *
- * 	err = pp->pp_cb((struct nl_object *) obj, pp);
- * 	if (err < 0)
- * 		goto errout;
- * }
- *
- * struct nl_cache_ops my_cache_ops = {
- * 	...
- * 	.co_request_update	= my_request_update,
- * 	.co_msg_parser		= my_msg_parser,
- * };
- * @endcode
- *
- * @par 3) Notification based Updates
- * @code
- * // Caches can be kept up-to-date based on notifications if the kernel
- * // sends out notifications whenever an object is added/removed/changed.
- * //
- * // It is trivial to support this, first a list of groups needs to be
- * // defined which are required to join in order to receive all necessary
- * // notifications. The groups are separated by address family to support
- * // the common situation where a separate group is used for each address
- * // family. If there is only one group, simply specify AF_UNSPEC.
- * static struct nl_af_group addr_groups[] = {
- * 	{ AF_INET,	RTNLGRP_IPV4_IFADDR },
- * 	{ AF_INET6,	RTNLGRP_IPV6_IFADDR },
- * 	{ END_OF_GROUP_LIST },
- * };
- *
- * // In order for the caching system to know the meaning of each message
- * // type it requires a table which maps each supported message type to
- * // a cache action, e.g. RTM_NEWADDR means address has been added or
- * // updated, RTM_DELADDR means address has been removed.
- * static struct nl_cache_ops rtnl_addr_ops = {
- * 	...
- * 	.co_msgtypes		= {
- * 					{ RTM_NEWADDR, NL_ACT_NEW, "new" },
- * 					{ RTM_DELADDR, NL_ACT_DEL, "del" },
- * 					{ RTM_GETADDR, NL_ACT_GET, "get" },
- * 					END_OF_MSGTYPES_LIST,
- * 				},
- * 	.co_groups		= addr_groups,
- * };
- *
- * // It is now possible to keep the cache up-to-date using the cache manager.
- * @endcode
- * @{
- */
-
-enum {
-	NL_ACT_UNSPEC,
-	NL_ACT_NEW,
-	NL_ACT_DEL,
-	NL_ACT_GET,
-	NL_ACT_SET,
-	NL_ACT_CHANGE,
-	__NL_ACT_MAX,
-};
-
-#define NL_ACT_MAX (__NL_ACT_MAX - 1)
-
-#define END_OF_MSGTYPES_LIST	{ -1, -1, NULL }
-
-/**
- * Message type to cache action association
- */
-struct nl_msgtype
-{
-	/** Netlink message type */
-	int			mt_id;
-
-	/** Cache action to take */
-	int			mt_act;
-
-	/** Name of operation for human-readable printing */
-	char *			mt_name;
-};
-
-/**
- * Address family to netlink group association
- */
-struct nl_af_group
-{
-	/** Address family */
-	int			ag_family;
-
-	/** Netlink group identifier */
-	int			ag_group;
-};
-
-#define END_OF_GROUP_LIST AF_UNSPEC, 0
-
-struct nl_parser_param
-{
-	int             (*pp_cb)(struct nl_object *, struct nl_parser_param *);
-	void *            pp_arg;
-};
-
-/**
- * Cache Operations
- */
-struct nl_cache_ops
-{
-	char  *			co_name;
-
-	int			co_hdrsize;
-	int			co_protocol;
-	struct nl_af_group *	co_groups;
-	
-	/**
-	 * Called whenever an update of the cache is required. Must send
-	 * a request message to the kernel requesting a complete dump.
-	 */
-	int   (*co_request_update)(struct nl_cache *, struct nl_sock *);
-
-	/**
-	 * Called whenever a message was received that needs to be parsed.
-	 * Must parse the message and call the paser callback function
-	 * (nl_parser_param) provided via the argument.
-	 */
-	int   (*co_msg_parser)(struct nl_cache_ops *, struct sockaddr_nl *,
-			       struct nlmsghdr *, struct nl_parser_param *);
-
-	struct nl_object_ops *	co_obj_ops;
-
-	struct nl_cache_ops *co_next;
-	struct nl_cache *co_major_cache;
-	struct genl_ops *	co_genl;
-	struct nl_msgtype	co_msgtypes[];
-};
-
-/** @} */
-
-#ifdef __cplusplus
-}
-#endif
+#warning "You are including a deprecated header file, include <netlink/cache.h>."
 
 #endif
diff --git a/include/netlink/cache.h b/include/netlink/cache.h
index c752920..e21aa1c 100644
--- a/include/netlink/cache.h
+++ b/include/netlink/cache.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_CACHE_H_
@@ -16,16 +16,32 @@
 #include <netlink/msg.h>
 #include <netlink/utils.h>
 #include <netlink/object.h>
-#include <netlink/cache-api.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-struct nl_cache;
+enum {
+	NL_ACT_UNSPEC,
+	NL_ACT_NEW,
+	NL_ACT_DEL,
+	NL_ACT_GET,
+	NL_ACT_SET,
+	NL_ACT_CHANGE,
+	__NL_ACT_MAX,
+};
 
+#define NL_ACT_MAX (__NL_ACT_MAX - 1)
+
+struct nl_cache;
 typedef void (*change_func_t)(struct nl_cache *, struct nl_object *, int, void *);
 
+/**
+ * @ingroup cache
+ * Explicitely iterate over all address families when updating the cache
+ */
+#define NL_CACHE_AF_ITER	0x0001
+
 /* Access Functions */
 extern int			nl_cache_nitems(struct nl_cache *);
 extern int			nl_cache_nitems_filter(struct nl_cache *,
@@ -44,14 +60,19 @@
 						    struct nl_cache **);
 extern struct nl_cache *	nl_cache_subset(struct nl_cache *,
 						struct nl_object *);
+extern struct nl_cache *	nl_cache_clone(struct nl_cache *);
 extern void			nl_cache_clear(struct nl_cache *);
+extern void			nl_cache_get(struct nl_cache *);
 extern void			nl_cache_free(struct nl_cache *);
+extern void			nl_cache_put(struct nl_cache *cache);
 
 /* Cache modification */
 extern int			nl_cache_add(struct nl_cache *,
 					     struct nl_object *);
 extern int			nl_cache_parse_and_add(struct nl_cache *,
 						       struct nl_msg *);
+extern int			nl_cache_move(struct nl_cache *,
+					      struct nl_object *);
 extern void			nl_cache_remove(struct nl_object *);
 extern int			nl_cache_refill(struct nl_sock *,
 						struct nl_cache *);
@@ -65,9 +86,16 @@
 						 struct nl_object *,
 						 change_func_t,
 						 void *);
+extern void			nl_cache_set_arg1(struct nl_cache *, int);
+extern void			nl_cache_set_arg2(struct nl_cache *, int);
+extern void			nl_cache_set_flags(struct nl_cache *, unsigned int);
 
 /* General */
 extern int			nl_cache_is_empty(struct nl_cache *);
+extern struct nl_object *	nl_cache_search(struct nl_cache *,
+						struct nl_object *);
+extern struct nl_object *nl_cache_find(struct nl_cache *,
+				       struct nl_object *);
 extern void			nl_cache_mark_all(struct nl_cache *);
 
 /* Dumping */
@@ -93,7 +121,9 @@
 
 /* Cache type management */
 extern struct nl_cache_ops *	nl_cache_ops_lookup(const char *);
+extern struct nl_cache_ops *	nl_cache_ops_lookup_safe(const char *);
 extern struct nl_cache_ops *	nl_cache_ops_associate(int, int);
+extern struct nl_cache_ops *	nl_cache_ops_associate_safe(int, int);
 extern struct nl_msgtype *	nl_msgtype_lookup(struct nl_cache_ops *, int);
 extern void			nl_cache_ops_foreach(void (*cb)(struct nl_cache_ops *, void *), void *);
 extern int			nl_cache_mngt_register(struct nl_cache_ops *);
@@ -103,10 +133,13 @@
 extern void			nl_cache_mngt_provide(struct nl_cache *);
 extern void			nl_cache_mngt_unprovide(struct nl_cache *);
 extern struct nl_cache *	nl_cache_mngt_require(const char *);
+extern struct nl_cache *	nl_cache_mngt_require_safe(const char *);
+extern struct nl_cache *	__nl_cache_mngt_require(const char *);
 
 struct nl_cache_mngr;
 
 #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,
@@ -116,12 +149,20 @@
 						  change_func_t,
 						  void *,
 						  struct nl_cache **);
+extern int			nl_cache_mngr_add_cache(struct nl_cache_mngr *mngr,
+							struct nl_cache *cache,
+							change_func_t cb, void *data);
 extern int			nl_cache_mngr_get_fd(struct nl_cache_mngr *);
 extern int			nl_cache_mngr_poll(struct nl_cache_mngr *,
 						   int);
 extern int			nl_cache_mngr_data_ready(struct nl_cache_mngr *);
+extern void			nl_cache_mngr_info(struct nl_cache_mngr *,
+						   struct nl_dump_params *);
 extern void			nl_cache_mngr_free(struct nl_cache_mngr *);
 
+extern void			nl_cache_ops_get(struct nl_cache_ops *);
+extern void			nl_cache_ops_put(struct nl_cache_ops *);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/netlink/cli/class.h b/include/netlink/cli/class.h
new file mode 100644
index 0000000..5001e42
--- /dev/null
+++ b/include/netlink/cli/class.h
@@ -0,0 +1,21 @@
+/*
+ * netlink/cli/class.h     CLI Class Helpers
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef __NETLINK_CLI_CLASS_H_
+#define __NETLINK_CLI_CLASS_H_
+
+#include <netlink/route/class.h>
+#include <netlink/cli/tc.h>
+
+extern struct rtnl_class *nl_cli_class_alloc(void);
+extern struct nl_cache *nl_cli_class_alloc_cache(struct nl_sock *, int);
+
+#endif
diff --git a/include/netlink/cli/cls.h b/include/netlink/cli/cls.h
new file mode 100644
index 0000000..a2707b8
--- /dev/null
+++ b/include/netlink/cli/cls.h
@@ -0,0 +1,24 @@
+/*
+ * netlink/cli/cls.h		CLI Classifier Helpers
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef __NETLINK_CLI_CLS_H_
+#define __NETLINK_CLI_CLS_H_
+
+#include <netlink/route/classifier.h>
+#include <netlink/cli/tc.h>
+
+extern struct rtnl_cls *	nl_cli_cls_alloc(void);
+extern struct nl_cache *	nl_cli_cls_alloc_cache(struct nl_sock *,
+						       int, uint32_t);
+extern void			nl_cli_cls_parse_proto(struct rtnl_cls *, char *);
+extern struct rtnl_ematch_tree *nl_cli_cls_parse_ematch(struct rtnl_cls *, char *);
+
+#endif
diff --git a/include/netlink/cli/ct.h b/include/netlink/cli/ct.h
index bed776b..ebe7c9d 100644
--- a/include/netlink/cli/ct.h
+++ b/include/netlink/cli/ct.h
@@ -30,5 +30,6 @@
 extern void nl_cli_ct_parse_dst_port(struct nfnl_ct *, int, char *);
 extern void nl_cli_ct_parse_tcp_state(struct nfnl_ct *, char *);
 extern void nl_cli_ct_parse_status(struct nfnl_ct *, char *);
+extern void nl_cli_ct_parse_zone(struct nfnl_ct *, char *);
 
 #endif
diff --git a/include/netlink/cli/exp.h b/include/netlink/cli/exp.h
new file mode 100644
index 0000000..b2418f8
--- /dev/null
+++ b/include/netlink/cli/exp.h
@@ -0,0 +1,42 @@
+/*
+ * netlink/cli/exp.h	CLI Expectation Helper
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2012 Rich Fought <Rich.Fought@watchguard.com>
+ * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef __NETLINK_CLI_EXP_H_
+#define __NETLINK_CLI_EXP_H_
+
+#include <netlink/netfilter/exp.h>
+#include <linux/netfilter/nf_conntrack_common.h>
+
+extern struct nfnl_exp *nl_cli_exp_alloc(void);
+extern struct nl_cache *nl_cli_exp_alloc_cache(struct nl_sock *);
+
+extern void nl_cli_exp_parse_family(struct nfnl_exp *, char *);
+extern void nl_cli_exp_parse_timeout(struct nfnl_exp *, char *);
+extern void nl_cli_exp_parse_id(struct nfnl_exp *, char *);
+extern void nl_cli_exp_parse_helper_name(struct nfnl_exp *, char *);
+extern void nl_cli_exp_parse_zone(struct nfnl_exp *, char *);
+extern void nl_cli_exp_parse_flags(struct nfnl_exp *, char *);
+extern void nl_cli_exp_parse_class(struct nfnl_exp *, char *);
+extern void nl_cli_exp_parse_nat_dir(struct nfnl_exp *, char *);
+extern void nl_cli_exp_parse_fn(struct nfnl_exp *, char *);
+
+extern void nl_cli_exp_parse_src(struct nfnl_exp *, int, char *);
+extern void nl_cli_exp_parse_dst(struct nfnl_exp *, int, char *);
+extern void nl_cli_exp_parse_l4protonum(struct nfnl_exp *, int, char *);
+extern void nl_cli_exp_parse_src_port(struct nfnl_exp *, int, char *);
+extern void nl_cli_exp_parse_dst_port(struct nfnl_exp *, int, char *);
+extern void nl_cli_exp_parse_icmp_id(struct nfnl_exp *, int, char *);
+extern void nl_cli_exp_parse_icmp_type(struct nfnl_exp *, int, char *);
+extern void nl_cli_exp_parse_icmp_code(struct nfnl_exp *, int, char *);
+
+
+#endif
diff --git a/include/netlink/cli/link.h b/include/netlink/cli/link.h
index c404019..3f37948 100644
--- a/include/netlink/cli/link.h
+++ b/include/netlink/cli/link.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef __NETLINK_CLI_LINK_H_
@@ -15,10 +15,9 @@
 #include <netlink/route/link.h>
 #include <netlink/cli/utils.h>
 
-#define nl_cli_link_alloc_cache(sk) \
-		nl_cli_alloc_cache((sk), "link", rtnl_link_alloc_cache)
-
 extern struct rtnl_link *nl_cli_link_alloc(void);
+extern struct nl_cache *nl_cli_link_alloc_cache_family(struct nl_sock *, int);
+extern struct nl_cache *nl_cli_link_alloc_cache(struct nl_sock *);
 
 extern void nl_cli_link_parse_family(struct rtnl_link *, char *);
 extern void nl_cli_link_parse_name(struct rtnl_link *, char *);
@@ -26,5 +25,6 @@
 extern void nl_cli_link_parse_ifindex(struct rtnl_link *, char *);
 extern void nl_cli_link_parse_txqlen(struct rtnl_link *, char *);
 extern void nl_cli_link_parse_weight(struct rtnl_link *, char *);
+extern void nl_cli_link_parse_ifalias(struct rtnl_link *, char *);
 
 #endif
diff --git a/include/netlink/cli/qdisc.h b/include/netlink/cli/qdisc.h
index 9fc4506..b102da4 100644
--- a/include/netlink/cli/qdisc.h
+++ b/include/netlink/cli/qdisc.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef __NETLINK_CLI_QDISC_H_
@@ -20,9 +20,4 @@
 
 extern struct rtnl_qdisc *nl_cli_qdisc_alloc(void);
 
-extern void nl_cli_qdisc_parse_dev(struct rtnl_qdisc *, struct nl_cache *, char *);
-extern void nl_cli_qdisc_parse_parent(struct rtnl_qdisc *, char *);
-extern void nl_cli_qdisc_parse_handle(struct rtnl_qdisc *, char *);
-extern void nl_cli_qdisc_parse_kind(struct rtnl_qdisc *, char *);
-
 #endif
diff --git a/include/netlink/cli/tc.h b/include/netlink/cli/tc.h
new file mode 100644
index 0000000..77042c7
--- /dev/null
+++ b/include/netlink/cli/tc.h
@@ -0,0 +1,41 @@
+/*
+ * netlink/cli/tc.h     CLI Traffic Control Helpers
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef __NETLINK_CLI_TC_H_
+#define __NETLINK_CLI_TC_H_
+
+#include <netlink/route/tc.h>
+
+struct rtnl_tc_ops;
+
+extern void nl_cli_tc_parse_dev(struct rtnl_tc *, struct nl_cache *, char *);
+extern void nl_cli_tc_parse_parent(struct rtnl_tc *, char *);
+extern void nl_cli_tc_parse_handle(struct rtnl_tc *, char *, int);
+extern void nl_cli_tc_parse_mtu(struct rtnl_tc *, char *);
+extern void nl_cli_tc_parse_mpu(struct rtnl_tc *, char *);
+extern void nl_cli_tc_parse_overhead(struct rtnl_tc *, char *);
+extern void nl_cli_tc_parse_linktype(struct rtnl_tc *, char *);
+extern void nl_cli_tc_parse_kind(struct rtnl_tc *, char *);
+
+struct nl_cli_tc_module
+{
+	const char *		tm_name;
+	enum rtnl_tc_type	tm_type;
+	struct rtnl_tc_ops *	tm_ops;
+	void		      (*tm_parse_argv)(struct rtnl_tc *, int, char **);
+	struct nl_list_head	tm_list;
+};
+
+extern struct nl_cli_tc_module *nl_cli_tc_lookup(struct rtnl_tc_ops *);
+extern void nl_cli_tc_register(struct nl_cli_tc_module *);
+extern void nl_cli_tc_unregister(struct nl_cli_tc_module *);
+
+#endif
diff --git a/include/netlink/cli/utils.h b/include/netlink/cli/utils.h
index 2a23208..da41c10 100644
--- a/include/netlink/cli/utils.h
+++ b/include/netlink/cli/utils.h
@@ -73,6 +73,8 @@
 extern struct nl_cache *nl_cli_alloc_cache(struct nl_sock *, const char *,
 			     int (*ac)(struct nl_sock *, struct nl_cache **));
 
+extern void		nl_cli_load_module(const char *, const char *);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/netlink/errno.h b/include/netlink/errno.h
index c8a376e..f8b5130 100644
--- a/include/netlink/errno.h
+++ b/include/netlink/errno.h
@@ -46,8 +46,12 @@
 #define NLE_NOACCESS		27
 #define NLE_PERM		28
 #define NLE_PKTLOC_FILE		29
+#define NLE_PARSE_ERR		30
+#define NLE_NODEV		31
+#define NLE_IMMUTABLE		32
+#define NLE_DUMP_INTR		33
 
-#define NLE_MAX			NLE_PKTLOC_FILE
+#define NLE_MAX			NLE_DUMP_INTR
 
 extern const char *	nl_geterror(int);
 extern void		nl_perror(int, const char *);
diff --git a/include/netlink/genl/ctrl.h b/include/netlink/genl/ctrl.h
index 1ae62f4..017b8fd 100644
--- a/include/netlink/genl/ctrl.h
+++ b/include/netlink/genl/ctrl.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_GENL_CTRL_H_
@@ -29,6 +29,9 @@
 							 const char *);
 extern int			genl_ctrl_resolve(struct nl_sock *,
 						  const char *);
+extern int 			genl_ctrl_resolve_grp(struct nl_sock *sk,
+						      const char *family,
+						      const char *grp);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/genl/family.h b/include/netlink/genl/family.h
index 74319e5..5432b59 100644
--- a/include/netlink/genl/family.h
+++ b/include/netlink/genl/family.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_GENL_FAMILY_H_
@@ -25,23 +25,20 @@
 extern void			genl_family_put(struct genl_family *);
 
 extern unsigned int		genl_family_get_id(struct genl_family *);
-extern void			genl_family_set_id(struct genl_family *,
-						   unsigned int);
+extern void			genl_family_set_id(struct genl_family *, unsigned int);
 extern char *			genl_family_get_name(struct genl_family *);
-extern void			genl_family_set_name(struct genl_family *,
-						     const char *name);
+extern void			genl_family_set_name(struct genl_family *, const char *);
 extern uint8_t			genl_family_get_version(struct genl_family *);
-extern void			genl_family_set_version(struct genl_family *,
-							uint8_t);
+extern void			genl_family_set_version(struct genl_family *, uint8_t);
 extern uint32_t			genl_family_get_hdrsize(struct genl_family *);
-extern void			genl_family_set_hdrsize(struct genl_family *,
-							uint32_t);
+extern void			genl_family_set_hdrsize(struct genl_family *, uint32_t);
 extern uint32_t			genl_family_get_maxattr(struct genl_family *);
-extern void			genl_family_set_maxattr(struct genl_family *,
-							uint32_t);
+extern void			genl_family_set_maxattr(struct genl_family *, uint32_t);
 
-extern int			genl_family_add_op(struct genl_family *,
-						   int, int);
+extern int			genl_family_add_op(struct genl_family *, int, int);
+extern int 			genl_family_add_grp(struct genl_family *, uint32_t ,
+						    const char *);
+
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/genl/genl.h b/include/netlink/genl/genl.h
index 3f3340c..e455581 100644
--- a/include/netlink/genl/genl.h
+++ b/include/netlink/genl/genl.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_GENL_H_
@@ -21,7 +21,6 @@
 #endif
 
 extern int		genl_connect(struct nl_sock *);
-
 extern int		genl_send_simple(struct nl_sock *, int, int,
 					 int, int);
 
@@ -33,7 +32,13 @@
 					 struct nla_policy *);
 extern int		genlmsg_parse(struct nlmsghdr *, int, struct nlattr **,
 				      int, struct nla_policy *);
+extern struct genlmsghdr *
+			genlmsg_hdr(struct nlmsghdr *);
 extern void *		genlmsg_data(const struct genlmsghdr *);
+extern void *		genlmsg_user_hdr(const struct genlmsghdr *);
+extern void *		genlmsg_user_data(const struct genlmsghdr *, const int);
+extern int		genlmsg_user_datalen(const struct genlmsghdr *,
+					     const int);
 extern int		genlmsg_len(const struct genlmsghdr *);
 extern struct nlattr *	genlmsg_attrdata(const struct genlmsghdr *, int);
 extern int		genlmsg_attrlen(const struct genlmsghdr *, int);
diff --git a/include/netlink/genl/mngt.h b/include/netlink/genl/mngt.h
index 8b0244f..8a51ccd 100644
--- a/include/netlink/genl/mngt.h
+++ b/include/netlink/genl/mngt.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_GENL_MNGT_H_
@@ -22,64 +22,153 @@
 
 struct nl_cache_ops;
 
+/**
+ * @ingroup genl_mngt
+ * @struct genl_info netlink/genl/mngt.h
+ *
+ * Informative structure passed on to message parser callbacks
+ *
+ * This structure is passed on to all message parser callbacks and contains
+ * information about the sender of the message as well as pointers to all
+ * relevant sections of the parsed message.
+ *
+ * @see genl_cmd::c_msg_parser
+ */
 struct genl_info
 {
+	/** Socket address of sender */
 	struct sockaddr_nl *    who;
+
+	/** Pointer to Netlink message header */
 	struct nlmsghdr *       nlh;
+
+	/** Pointer to Generic Netlink message header */
 	struct genlmsghdr *     genlhdr;
+
+	/** Pointer to user header */
 	void *                  userhdr;
+
+	/** Pointer to array of parsed attributes */
 	struct nlattr **        attrs;
 };
 
 /**
  * @ingroup genl_mngt
- * Generic Netlink Command
+ * @struct genl_cmd netlink/genl/mngt.h
+ *
+ * Definition of a Generic Netlink command.
+ *
+ * This structure is used to define the list of available commands on the
+ * receiving side.
+ *
+ * @par Example:
+ * @code
+ * static struct genl_cmd foo_cmds[] = {
+ * 	{
+ * 		.c_id		= FOO_CMD_NEW,
+ * 		.c_name		= "NEWFOO" ,
+ * 		.c_maxattr	= FOO_ATTR_MAX,
+ * 		.c_attr_policy	= foo_policy,
+ * 		.c_msg_parser	= foo_msg_parser,
+ * 	},
+ * 	{
+ * 		.c_id		= FOO_CMD_DEL,
+ * 		.c_name		= "DELFOO" ,
+ * 	},
+ * };
+ *
+ * static struct genl_ops my_genl_ops = {
+ * 	[...]
+ * 	.o_cmds			= foo_cmds,
+ * 	.o_ncmds		= ARRAY_SIZE(foo_cmds),
+ * };
+ * @endcode
  */
 struct genl_cmd
 {
-	/** Unique command identifier */
+	/** Numeric command identifier (required) */
 	int			c_id;
 
-	/** Name/description of command */
+	/** Human readable name  (required) */
 	char *			c_name;
 
-	/**
-	 * Maximum attribute identifier, must be provided if
-	 * a message parser is available.
-	 */
+	/** Maximum attribute identifier that the command is prepared to handle. */
 	int			c_maxattr;
 
+	/** Called whenever a message for this command is received */
 	int		      (*c_msg_parser)(struct nl_cache_ops *,
 					      struct genl_cmd *,
 					      struct genl_info *, void *);
 
-	/**
-	 * Attribute validation policy (optional)
-	 */
+	/** Attribute validation policy, enforced before the callback is called */
 	struct nla_policy *	c_attr_policy;
 };
 
 /**
  * @ingroup genl_mngt
- * Generic Netlink Operations
+ * @struct genl_ops netlink/genl/mngt.h
+ *
+ * Definition of a Generic Netlink family
+ *
+ * @par Example:
+ * @code
+ * static struct genl_cmd foo_cmds[] = {
+ * 	[...]
+ * };
+ *
+ * static struct genl_ops my_genl_ops = {
+ * 	.o_name			= "foo",
+ * 	.o_hdrsize		= sizeof(struct my_hdr),
+ * 	.o_cmds			= foo_cmds,
+ * 	.o_ncmds		= ARRAY_SIZE(foo_cmds),
+ * };
+ *
+ * if ((err = genl_register_family(&my_genl_ops)) < 0)
+ * 	// ERROR
+ * @endcode
+ *
+ * @see genl_cmd
  */
 struct genl_ops
 {
-	int			o_family;
+	/** Length of user header */
+	unsigned int		o_hdrsize;
+
+	/** Numeric identifier, automatically filled in by genl_ops_resolve() */
 	int			o_id;
+
+	/** Human readable name, used by genl_ops_resolve() to resolve numeric id */
 	char *			o_name;
+
+	/**
+	 * If registered via genl_register(), will point to the related
+	 * cache operations.
+	 */
 	struct nl_cache_ops *	o_cache_ops;
+
+	/** Optional array defining the available Generic Netlink commands */
 	struct genl_cmd	*	o_cmds;
+
+	/** Number of elements in \c o_cmds array */
 	int			o_ncmds;
 
-	/* linked list of all genl cache operations */
+	/**
+	 * @private
+	 * Used internally to link together all registered operations.
+	 */
 	struct nl_list_head	o_list;
 };
 
+extern int		genl_register_family(struct genl_ops *);
+extern int		genl_unregister_family(struct genl_ops *);
+extern int		genl_handle_msg(struct nl_msg *, void *);
 
 extern int		genl_register(struct nl_cache_ops *);
 extern void		genl_unregister(struct nl_cache_ops *);
 
+extern int		genl_ops_resolve(struct nl_sock *, struct genl_ops *);
+extern int		genl_mngt_resolve(struct nl_sock *);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/netlink/handlers.h b/include/netlink/handlers.h
index f373f58..e94cd34 100644
--- a/include/netlink/handlers.h
+++ b/include/netlink/handlers.h
@@ -108,6 +108,8 @@
 	NL_CB_SEQ_CHECK,
 	/** Sending of an acknowledge message has been requested */
 	NL_CB_SEND_ACK,
+	/** Flag NLM_F_DUMP_INTR is set in message */
+	NL_CB_DUMP_INTR,
 	__NL_CB_TYPE_MAX,
 };
 
@@ -137,6 +139,8 @@
 				 int (*func)(struct nl_sock *,
 					     struct nl_msg *));
 
+extern enum nl_cb_type nl_cb_active_type(struct nl_cb *cb);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/netlink/hash.h b/include/netlink/hash.h
new file mode 100644
index 0000000..0bda74e
--- /dev/null
+++ b/include/netlink/hash.h
@@ -0,0 +1,69 @@
+/*
+ * This file was taken from http://ccodearchive.net/info/hash.html
+ * Changes to the original file include cleanups and removal of unwanted code
+ * and also code that depended on build_asert
+ */
+#ifndef CCAN_HASH_H
+#define CCAN_HASH_H
+#include <stdint.h>
+#include <stdlib.h>
+#include <endian.h>
+
+/* Stolen mostly from: lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+ *
+ * http://burtleburtle.net/bob/c/lookup3.c
+ */
+
+#ifdef __LITTLE_ENDIAN
+#   define HAVE_LITTLE_ENDIAN 1
+#elif __BIG_ENDIAN
+#   define HAVE_BIG_ENDIAN 1
+#else
+#error Unknown endianness.  Failure in endian.h
+#endif
+
+/**
+ * hash - fast hash of an array for internal use
+ * @p: the array or pointer to first element
+ * @num: the number of elements to hash
+ * @base: the base number to roll into the hash (usually 0)
+ *
+ * The memory region pointed to by p is combined with the base to form
+ * a 32-bit hash.
+ *
+ * This hash will have different results on different machines, so is
+ * only useful for internal hashes (ie. not hashes sent across the
+ * network or saved to disk).
+ *
+ * It may also change with future versions: it could even detect at runtime
+ * what the fastest hash to use is.
+ *
+ * See also: hash64, hash_stable.
+ *
+ * Example:
+ *	#include <ccan/hash/hash.h>
+ *	#include <err.h>
+ *	#include <stdio.h>
+ *	#include <string.h>
+ *
+ *	// Simple demonstration: idential strings will have the same hash, but
+ *	// two different strings will probably not.
+ *	int main(int argc, char *argv[])
+ *	{
+ *		uint32_t hash1, hash2;
+ *
+ *		if (argc != 3)
+ *			err(1, "Usage: %s <string1> <string2>", argv[0]);
+ *
+ *		hash1 = __nl_hash(argv[1], strlen(argv[1]), 0);
+ *		hash2 = __nl_hash(argv[2], strlen(argv[2]), 0);
+ *		printf("Hash is %s\n", hash1 == hash2 ? "same" : "different");
+ *		return 0;
+ *	}
+ */
+#define __nl_hash(p, num, base) nl_hash_any((p), (num)*sizeof(*(p)), (base))
+
+/* Our underlying operations. */
+uint32_t nl_hash_any(const void *key, size_t length, uint32_t base);
+
+#endif /* HASH_H */
diff --git a/include/netlink/hashtable.h b/include/netlink/hashtable.h
new file mode 100644
index 0000000..d9e6ee4
--- /dev/null
+++ b/include/netlink/hashtable.h
@@ -0,0 +1,52 @@
+/*
+ * netlink/hashtable.h       Netlink hashtable Utilities
+ *
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation version 2.1
+ *      of the License.
+ *
+ * Copyright (c) 2012 Cumulus Networks, Inc
+ */
+
+#ifndef NETLINK_HASHTABLE_H_
+#define NETLINK_HASHTABLE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct nl_hash_node {
+    uint32_t			key;
+    uint32_t			key_size;
+    struct nl_object *		obj;
+    struct nl_hash_node *	next;
+} nl_hash_node_t;
+
+typedef struct nl_hash_table {
+    int 			size;
+    nl_hash_node_t **		nodes;
+} nl_hash_table_t;
+
+/* Default hash table size */
+#define NL_MAX_HASH_ENTRIES 1024
+
+/* Access Functions */
+extern nl_hash_table_t *	nl_hash_table_alloc(int size);
+extern void 			nl_hash_table_free(nl_hash_table_t *ht);
+
+extern int			nl_hash_table_add(nl_hash_table_t *ht,
+						  struct nl_object *obj);
+extern int			nl_hash_table_del(nl_hash_table_t *ht,
+						  struct nl_object *obj);
+
+extern struct nl_object *	nl_hash_table_lookup(nl_hash_table_t *ht,
+						     struct nl_object *obj);
+extern uint32_t 		nl_hash(void *k, size_t length,
+					uint32_t initval);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NETLINK_HASHTABLE_H_ */
diff --git a/include/netlink/idiag/idiagnl.h b/include/netlink/idiag/idiagnl.h
new file mode 100644
index 0000000..d7434cd
--- /dev/null
+++ b/include/netlink/idiag/idiagnl.h
@@ -0,0 +1,126 @@
+/*
+ * netlink/idiag/idiagnl.h		Inetdiag Netlink
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+#ifndef NETLINK_IDIAGNL_H_
+#define NETLINK_IDIAGNL_H_
+
+#include <netlink/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Inet Diag message types
+ */
+#define IDIAG_TCPDIAG_GETSOCK	18
+#define IDIAG_DCCPDIAG_GETSOCK	19
+#define IDIAG_GETSOCK_MAX	24
+
+/**
+ * Socket state identifiers
+ * @ingroup idiag
+ */
+enum {
+	IDIAG_SS_UNKNOWN,
+	IDIAG_SS_ESTABLISHED,
+	IDIAG_SS_SYN_SENT,
+	IDIAG_SS_SYN_RECV,
+	IDIAG_SS_FIN_WAIT1,
+	IDIAG_SS_FIN_WAIT2,
+	IDIAG_SS_TIME_WAIT,
+	IDIAG_SS_CLOSE,
+	IDIAG_SS_CLOSE_WAIT,
+	IDIAG_SS_LAST_ACK,
+	IDIAG_SS_LISTEN,
+	IDIAG_SS_CLOSING,
+	IDIAG_SS_MAX
+};
+
+/**
+ * Macro to represent all socket states.
+ * @ingroup idiag
+ */
+#define IDIAG_SS_ALL ((1<<IDIAG_SS_MAX)-1)
+
+/**
+ * Inet Diag extended attributes
+ * @ingroup idiag
+ */
+enum {
+	IDIAG_ATTR_NONE,
+	IDIAG_ATTR_MEMINFO,
+	IDIAG_ATTR_INFO,
+	IDIAG_ATTR_VEGASINFO,
+	IDIAG_ATTR_CONG,
+	IDIAG_ATTR_TOS,
+	IDIAG_ATTR_TCLASS,
+	IDIAG_ATTR_SKMEMINFO,
+	IDIAG_ATTR_SHUTDOWN,
+	IDIAG_ATTR_MAX,
+};
+
+/**
+ * Macro to represent all socket attributes.
+ * @ingroup idiag
+ */
+#define IDIAG_ATTR_ALL ((1<<IDIAG_ATTR_MAX)-1)
+
+/**
+ * Socket memory info identifiers
+ * @ingroup idiag
+ */
+enum {
+	IDIAG_SK_MEMINFO_RMEM_ALLOC,
+	IDIAG_SK_MEMINFO_RCVBUF,
+	IDIAG_SK_MEMINFO_WMEM_ALLOC,
+	IDIAG_SK_MEMINFO_SNDBUF,
+	IDIAG_SK_MEMINFO_FWD_ALLOC,
+	IDIAG_SK_MEMINFO_WMEM_QUEUED,
+	IDIAG_SK_MEMINFO_OPTMEM,
+	IDIAG_SK_MEMINFO_BACKLOG,
+
+	IDIAG_SK_MEMINFO_VARS,
+};
+
+/**
+ * Socket timer indentifiers
+ * @ingroupd idiag
+ */
+enum {
+	IDIAG_TIMER_OFF,
+	IDIAG_TIMER_ON,
+	IDIAG_TIMER_KEEPALIVE,
+	IDIAG_TIMER_TIMEWAIT,
+	IDIAG_TIMER_PERSIST,
+	IDIAG_TIMER_UNKNOWN,
+};
+
+extern char *	idiagnl_state2str(int, char *, size_t);
+extern int	idiagnl_str2state(const char *);
+
+extern int	idiagnl_connect(struct nl_sock *);
+extern int	idiagnl_send_simple(struct nl_sock *, int, uint8_t, uint16_t,
+                                    uint16_t);
+
+extern char *		idiagnl_timer2str(int, char *, size_t);
+extern int		idiagnl_str2timer(const char *);
+extern char *		idiagnl_attrs2str(int, char *, size_t);
+extern char *		idiagnl_tcpstate2str(uint8_t, char *, size_t);
+extern char *		idiagnl_tcpopts2str(uint8_t, char *, size_t);
+extern char *		idiagnl_shutdown2str(uint8_t, char *, size_t);
+extern char *		idiagnl_exts2str(uint8_t, char *, size_t);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* NETLINK_IDIAGNL_H_ */
diff --git a/include/netlink/idiag/meminfo.h b/include/netlink/idiag/meminfo.h
new file mode 100644
index 0000000..1922395
--- /dev/null
+++ b/include/netlink/idiag/meminfo.h
@@ -0,0 +1,41 @@
+/*
+ * netlink/idiag/meminfo.h		Inetdiag Netlink Memory Info
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+#ifndef NETLINK_IDIAGNL_MEMINFO_H_
+#define NETLINK_IDIAGNL_MEMINFO_H_
+
+#include <netlink/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern struct nl_object_ops	idiagnl_meminfo_obj_ops;
+
+extern struct idiagnl_meminfo *idiagnl_meminfo_alloc(void);
+extern void idiagnl_meminfo_get(struct idiagnl_meminfo *);
+extern void idiagnl_meminfo_put(struct idiagnl_meminfo *);
+
+extern uint32_t	  idiagnl_meminfo_get_rmem(const struct idiagnl_meminfo *);
+extern uint32_t   idiagnl_meminfo_get_wmem(const struct idiagnl_meminfo *);
+extern uint32_t   idiagnl_meminfo_get_fmem(const struct idiagnl_meminfo *);
+extern uint32_t   idiagnl_meminfo_get_tmem(const struct idiagnl_meminfo *);
+
+extern void	  idiagnl_meminfo_set_rmem(struct idiagnl_meminfo *, uint32_t);
+extern void	  idiagnl_meminfo_set_wmem(struct idiagnl_meminfo *, uint32_t);
+extern void	  idiagnl_meminfo_set_fmem(struct idiagnl_meminfo *, uint32_t);
+extern void	  idiagnl_meminfo_set_tmem(struct idiagnl_meminfo *, uint32_t);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* NETLINK_IDIAGNL_MEMINFO_H_ */
diff --git a/include/netlink/idiag/msg.h b/include/netlink/idiag/msg.h
new file mode 100644
index 0000000..4aae606
--- /dev/null
+++ b/include/netlink/idiag/msg.h
@@ -0,0 +1,83 @@
+/*
+ * netlink/idiag/msg.h		Inetdiag Netlink Message
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+#ifndef NETLINK_IDIAGNL_MSG_H_
+#define NETLINK_IDIAGNL_MSG_H_
+
+#include <netlink/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+struct idiagnl_msg;
+extern struct nl_object_ops  idiagnl_msg_obj_ops;
+
+extern struct idiagnl_msg * idiagnl_msg_alloc(void);
+extern int		idiagnl_msg_alloc_cache(struct nl_sock *, int, int,
+                                                struct nl_cache**);
+extern void		idiagnl_msg_get(struct idiagnl_msg *);
+extern void		idiagnl_msg_put(struct idiagnl_msg *);
+extern uint8_t		idiagnl_msg_get_family(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_family(struct idiagnl_msg *, uint8_t);
+extern uint8_t		idiagnl_msg_get_state(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_state(struct idiagnl_msg *, uint8_t);
+extern uint8_t		idiagnl_msg_get_timer(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_timer(struct idiagnl_msg *, uint8_t);
+extern uint8_t		idiagnl_msg_get_retrans(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_retrans(struct idiagnl_msg *, uint8_t);
+extern uint16_t		idiagnl_msg_get_sport(struct idiagnl_msg *);
+extern void		idiagnl_msg_set_sport(struct idiagnl_msg *, uint16_t);
+extern uint16_t		idiagnl_msg_get_dport(struct idiagnl_msg *);
+extern void		idiagnl_msg_set_dport(struct idiagnl_msg *, uint16_t);
+extern struct nl_addr *	idiagnl_msg_get_src(const struct idiagnl_msg *);
+extern int		idiagnl_msg_set_src(struct idiagnl_msg *,
+                                            struct nl_addr *);
+extern struct nl_addr *	idiagnl_msg_get_dst(const struct idiagnl_msg *);
+extern int		idiagnl_msg_set_dst(struct idiagnl_msg *,
+		                            struct nl_addr *);
+extern uint32_t		idiagnl_msg_get_ifindex(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_ifindex(struct idiagnl_msg *, uint32_t);
+extern uint32_t		idiagnl_msg_get_expires(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_expires(struct idiagnl_msg *, uint32_t);
+extern uint32_t		idiagnl_msg_get_rqueue(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_rqueue(struct idiagnl_msg *, uint32_t);
+extern uint32_t		idiagnl_msg_get_wqueue(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_wqueue(struct idiagnl_msg *, uint32_t);
+extern uint32_t		idiagnl_msg_get_uid(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_uid(struct idiagnl_msg *, uint32_t);
+extern uint32_t		idiagnl_msg_get_inode(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_inode(struct idiagnl_msg *, uint32_t);
+extern uint8_t		idiagnl_msg_get_tos(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_tos(struct idiagnl_msg *, uint8_t);
+extern uint8_t		idiagnl_msg_get_tclass(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_tclass(struct idiagnl_msg *, uint8_t);
+extern uint8_t		idiagnl_msg_get_shutdown(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_shutdown(struct idiagnl_msg *, uint8_t);
+extern char *		idiagnl_msg_get_cong(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_cong(struct idiagnl_msg *, char *);
+extern struct idiagnl_meminfo *idiagnl_msg_get_meminfo(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_meminfo(struct idiagnl_msg *,
+                                                struct idiagnl_meminfo *);
+extern struct idiagnl_vegasinfo *idiagnl_msg_get_vegasinfo(const struct idiagnl_msg *);
+extern void		idiagnl_msg_set_vegasinfo(struct idiagnl_msg *,
+                                                  struct idiagnl_vegasinfo *);
+extern struct tcp_info idiagnl_msg_get_tcpinfo(const struct idiagnl_msg *);
+extern void	       idiagnl_msg_set_tcpinfo(struct idiagnl_msg *,
+                                               struct tcp_info *);
+
+extern int		idiagnl_msg_parse(struct nlmsghdr *,
+                                          struct idiagnl_msg **);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* NETLINK_IDIAGNL_MSG_H_ */
diff --git a/include/netlink/idiag/req.h b/include/netlink/idiag/req.h
new file mode 100644
index 0000000..3c9f8ac
--- /dev/null
+++ b/include/netlink/idiag/req.h
@@ -0,0 +1,50 @@
+/*
+ * netlink/idiag/req.h		Inetdiag Netlink Request
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+#ifndef NETLINK_IDIAGNL_REQ_H_
+#define NETLINK_IDIAGNL_REQ_H_
+
+#include <netlink/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+struct idiagnl_req;
+extern struct nl_object_ops	idiagnl_req_obj_ops;
+
+extern struct idiagnl_req * idiagnl_req_alloc(void);
+extern void		    idiagnl_req_get(struct idiagnl_req *);
+extern void		    idiagnl_req_put(struct idiagnl_req *);
+extern uint8_t		    idiagnl_req_get_family(const struct idiagnl_req *);
+extern void		    idiagnl_req_set_family(struct idiagnl_req *,
+                                                   uint8_t);
+extern uint8_t		    idiagnl_req_get_ext(const struct idiagnl_req *);
+extern void		    idiagnl_req_set_ext(struct idiagnl_req *, uint8_t);
+extern uint32_t		    idiagnl_req_get_ifindex(const struct idiagnl_req *);
+extern void		    idiagnl_req_set_ifindex(struct idiagnl_req *,
+                                                    uint32_t);
+extern uint32_t		    idiagnl_req_get_states(const struct idiagnl_req *);
+extern void		    idiagnl_req_set_states(struct idiagnl_req *,
+                                                   uint32_t);
+extern uint32_t		    idiagnl_req_get_dbs(const struct idiagnl_req *);
+extern void		    idiagnl_req_set_dbs(struct idiagnl_req *, uint32_t);
+extern struct nl_addr *	    idiagnl_req_get_src(const struct idiagnl_req *);
+extern int		    idiagnl_req_set_src(struct idiagnl_req *,
+                                                struct nl_addr *);
+extern struct nl_addr *	    idiagnl_req_get_dst(const struct idiagnl_req *);
+extern int		    idiagnl_req_set_dst(struct idiagnl_req *,
+                                                struct nl_addr *);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* NETLINK_IDIAGNL_REQ_H_ */
diff --git a/include/netlink/idiag/vegasinfo.h b/include/netlink/idiag/vegasinfo.h
new file mode 100644
index 0000000..792b5c1
--- /dev/null
+++ b/include/netlink/idiag/vegasinfo.h
@@ -0,0 +1,43 @@
+/*
+ * netlink/idiag/vegasinfo.h		Inetdiag Netlink TCP Vegas Info
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+#ifndef NETLINK_IDIAGNL_VEGASINFO_H_
+#define NETLINK_IDIAGNL_VEGASINFO_H_
+
+#include <netlink/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+extern struct nl_object_ops	  idiagnl_vegasinfo_obj_ops;
+extern struct idiagnl_vegasinfo * idiagnl_vegasinfo_alloc(void);
+extern void	  idiagnl_vegasinfo_get(struct idiagnl_vegasinfo *);
+extern void	  idiagnl_vegasinfo_put(struct idiagnl_vegasinfo *);
+
+extern uint32_t idiagnl_vegasinfo_get_enabled(const struct idiagnl_vegasinfo *);
+extern uint32_t	idiagnl_vegasinfo_get_rttcnt(const struct idiagnl_vegasinfo *);
+extern uint32_t idiagnl_vegasinfo_get_rtt(const struct idiagnl_vegasinfo *);
+extern uint32_t idiagnl_vegasinfo_get_minrtt(const struct idiagnl_vegasinfo *);
+
+extern void	idiagnl_vegasinfo_set_enabled(struct idiagnl_vegasinfo *,
+                                              uint32_t);
+extern void	idiagnl_vegasinfo_set_rttcnt(struct idiagnl_vegasinfo *,
+                                             uint32_t);
+extern void	idiagnl_vegasinfo_set_rtt(struct idiagnl_vegasinfo *, uint32_t);
+extern void	idiagnl_vegasinfo_set_minrtt(struct idiagnl_vegasinfo *,
+                                             uint32_t);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* NETLINK_IDIAGNL_VEGASINFO_H_ */
diff --git a/include/netlink/msg.h b/include/netlink/msg.h
index e331f42..f3d50ae 100644
--- a/include/netlink/msg.h
+++ b/include/netlink/msg.h
@@ -25,18 +25,21 @@
 /**
  * @ingroup msg
  * @brief
- * Will cause the netlink pid to be set to the pid assigned to
- * the netlink handle (socket) just before sending the message off.
- * @note Requires the use of nl_send_auto_complete()!
+ * Will cause the netlink port to be set to the port assigned to
+ * the netlink icoket ust before sending the message off.
+ *
+ * @note Requires the use of nl_send_auto()!
  */
-#define NL_AUTO_PID	0
+#define NL_AUTO_PORT	0
+#define NL_AUTO_PID	NL_AUTO_PORT
 
 /**
  * @ingroup msg
  * @brief
  * May be used to refer to a sequence number which should be
  * automatically set just before sending the message off.
- * @note Requires the use of nl_send_auto_complete()!
+ *
+ * @note Requires the use of nl_send_auto()!
  */
 #define NL_AUTO_SEQ	0
 
@@ -44,15 +47,13 @@
 struct nl_tree;
 struct ucred;
 
-/* size calculations */
-extern int		  nlmsg_msg_size(int);
-extern int		  nlmsg_total_size(int);
-extern int		  nlmsg_padlen(int);
+extern int			nlmsg_size(int);
+extern int			nlmsg_total_size(int);
+extern int			nlmsg_padlen(int);
 
-/* payload access */
-extern void *		  nlmsg_data(const struct nlmsghdr *);
-extern int		  nlmsg_len(const struct nlmsghdr *);
-extern void *		  nlmsg_tail(const struct nlmsghdr *);
+extern void *			nlmsg_data(const struct nlmsghdr *);
+extern int			nlmsg_datalen(const struct nlmsghdr *);
+extern void *			nlmsg_tail(const struct nlmsghdr *);
 
 /* attribute access */
 extern struct nlattr *	  nlmsg_attrdata(const struct nlmsghdr *, int);
@@ -128,12 +129,14 @@
  * @arg pos	loop counter, set to current message
  * @arg head	head of message stream
  * @arg len	length of message stream
- * @arg rem	initialized to len, holds bytes currently remaining in stream
  */
+#define nlmsg_for_each(pos, head, len) \
+	for (int rem = len, pos = head; \
+		nlmsg_ok(pos, rem); \
+		pos = nlmsg_next(pos, &rem))
+
 #define nlmsg_for_each_msg(pos, head, len, rem) \
-	for (pos = head, rem = len; \
-	     nlmsg_ok(pos, rem); \
-	     pos = nlmsg_next(pos, &(rem)))
+		nlmsg_for_each(pos, head, len)
 
 /** @} */
 
diff --git a/include/netlink/netfilter/ct.h b/include/netlink/netfilter/ct.h
index c4402b3..ef5d035 100644
--- a/include/netlink/netfilter/ct.h
+++ b/include/netlink/netfilter/ct.h
@@ -25,6 +25,11 @@
 
 struct nfnl_ct;
 
+struct nfnl_ct_timestamp {
+	uint64_t		start;
+	uint64_t		stop;
+};
+
 extern struct nl_object_ops ct_obj_ops;
 
 extern struct nfnl_ct *	nfnl_ct_alloc(void);
@@ -44,7 +49,7 @@
 
 extern int	nfnl_ct_build_delete_request(const struct nfnl_ct *, int,
 					     struct nl_msg **);
-extern int	nfnl_ct_delete(struct nl_sock *, const struct nfnl_ct *, int);
+extern int	nfnl_ct_del(struct nl_sock *, const struct nfnl_ct *, int);
 
 extern int	nfnl_ct_build_query_request(const struct nfnl_ct *, int,
 					    struct nl_msg **);
@@ -65,6 +70,7 @@
 
 extern void	nfnl_ct_set_status(struct nfnl_ct *, uint32_t);
 extern void	nfnl_ct_unset_status(struct nfnl_ct *, uint32_t);
+extern int	nfnl_ct_test_status(const struct nfnl_ct *ct);
 extern uint32_t	nfnl_ct_get_status(const struct nfnl_ct *);
 extern char *	nfnl_ct_status2str(int, char *, size_t);
 extern int	nfnl_ct_str2status(const char *);
@@ -85,6 +91,10 @@
 extern int	nfnl_ct_test_id(const struct nfnl_ct *);
 extern uint32_t	nfnl_ct_get_id(const struct nfnl_ct *);
 
+extern void	nfnl_ct_set_zone(struct nfnl_ct *, uint16_t);
+extern int	nfnl_ct_test_zone(const struct nfnl_ct *);
+extern uint16_t	nfnl_ct_get_zone(const struct nfnl_ct *);
+
 extern int	nfnl_ct_set_src(struct nfnl_ct *, int, struct nl_addr *);
 extern struct nl_addr *	nfnl_ct_get_src(const struct nfnl_ct *, int);
 
@@ -119,6 +129,10 @@
 extern int	nfnl_ct_test_bytes(const struct nfnl_ct *, int);
 extern uint64_t	nfnl_ct_get_bytes(const struct nfnl_ct *, int);
 
+extern void nfnl_ct_set_timestamp(struct nfnl_ct *, uint64_t, uint64_t);
+extern int nfnl_ct_test_timestamp(const struct nfnl_ct *);
+extern const struct nfnl_ct_timestamp *nfnl_ct_get_timestamp(const struct nfnl_ct *);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/netlink/netfilter/exp.h b/include/netlink/netfilter/exp.h
new file mode 100644
index 0000000..4e95014
--- /dev/null
+++ b/include/netlink/netfilter/exp.h
@@ -0,0 +1,129 @@
+/*
+ * netlink/netfilter/exp.h   Conntrack Expectation
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation version 2.1
+ *  of the License.
+ *
+ * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2007 Philip Craig <philipc@snapgear.com>
+ * Copyright (c) 2007 Secure Computing Corporation
+ * Copyright (c) 2012 Rich Fought <rich.fought@watchguard.com>
+ */
+
+#ifndef NETLINK_EXP_H_
+#define NETLINK_EXP_H_
+
+#include <netlink/netlink.h>
+#include <netlink/addr.h>
+#include <netlink/cache.h>
+#include <netlink/msg.h>
+
+#include <linux/version.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct nfnl_exp;
+
+enum nfnl_exp_tuples {
+	NFNL_EXP_TUPLE_EXPECT,
+	NFNL_EXP_TUPLE_MASTER,
+	NFNL_EXP_TUPLE_MASK,
+	NFNL_EXP_TUPLE_NAT,
+	NFNL_EXP_TUPLE_MAX
+};
+
+extern struct nl_object_ops exp_obj_ops;
+
+extern struct nfnl_exp * nfnl_exp_alloc(void);
+extern int  nfnl_exp_alloc_cache(struct nl_sock *, struct nl_cache **);
+
+extern int  nfnlmsg_exp_group(struct nlmsghdr *);
+extern int  nfnlmsg_exp_parse(struct nlmsghdr *, struct nfnl_exp **);
+
+extern void nfnl_exp_get(struct nfnl_exp *);
+extern void nfnl_exp_put(struct nfnl_exp *);
+
+extern int  nfnl_exp_dump_request(struct nl_sock *);
+
+extern int  nfnl_exp_build_add_request(const struct nfnl_exp *, int,
+						struct nl_msg **);
+extern int  nfnl_exp_add(struct nl_sock *, const struct nfnl_exp *, int);
+
+extern int  nfnl_exp_build_delete_request(const struct nfnl_exp *, int,
+						struct nl_msg **);
+extern int  nfnl_exp_del(struct nl_sock *, const struct nfnl_exp *, int);
+
+extern int  nfnl_exp_build_query_request(const struct nfnl_exp *, int,
+						struct nl_msg **);
+extern int  nfnl_exp_query(struct nl_sock *, const struct nfnl_exp *, int);
+
+extern void nfnl_exp_set_family(struct nfnl_exp *, uint8_t);
+extern uint8_t  nfnl_exp_get_family(const struct nfnl_exp *);
+
+extern void nfnl_exp_set_timeout(struct nfnl_exp *, uint32_t);
+extern int  nfnl_exp_test_timeout(const struct nfnl_exp *);
+extern uint32_t nfnl_exp_get_timeout(const struct nfnl_exp *);
+
+extern void nfnl_exp_set_id(struct nfnl_exp *, uint32_t);
+extern int  nfnl_exp_test_id(const struct nfnl_exp *);
+extern uint32_t nfnl_exp_get_id(const struct nfnl_exp *);
+
+extern int  nfnl_exp_set_helper_name(struct nfnl_exp *, void *);
+extern int  nfnl_exp_test_helper_name(const struct nfnl_exp *);
+extern const char * nfnl_exp_get_helper_name(const struct nfnl_exp *);
+
+extern void nfnl_exp_set_zone(struct nfnl_exp *, uint16_t);
+extern int  nfnl_exp_test_zone(const struct nfnl_exp *);
+extern uint16_t nfnl_exp_get_zone(const struct nfnl_exp *);
+
+extern void nfnl_exp_set_flags(struct nfnl_exp *, uint32_t);
+extern int  nfnl_exp_test_flags(const struct nfnl_exp *);
+extern uint32_t nfnl_exp_get_flags(const struct nfnl_exp *);
+
+extern void nfnl_exp_set_class(struct nfnl_exp *, uint32_t);
+extern int  nfnl_exp_test_class(const struct nfnl_exp *);
+extern uint32_t nfnl_exp_get_class(const struct nfnl_exp *);
+
+extern int  nfnl_exp_set_fn(struct nfnl_exp *, void *);
+extern int  nfnl_exp_test_fn(const struct nfnl_exp *);
+extern const char * nfnl_exp_get_fn(const struct nfnl_exp *);
+
+extern void nfnl_exp_set_nat_dir(struct nfnl_exp *, uint8_t);
+extern int  nfnl_exp_test_nat_dir(const struct nfnl_exp *);
+extern uint8_t nfnl_exp_get_nat_dir(const struct nfnl_exp *);
+
+// The int argument specifies which nfnl_exp_dir (expect, master, mask or nat)
+// Expectation objects only use orig, not reply
+
+extern int  nfnl_exp_set_src(struct nfnl_exp *, int, struct nl_addr *);
+extern int  nfnl_exp_test_src(const struct nfnl_exp *, int);
+extern struct nl_addr * nfnl_exp_get_src(const struct nfnl_exp *, int);
+
+extern int  nfnl_exp_set_dst(struct nfnl_exp *, int, struct nl_addr *);
+extern int  nfnl_exp_test_dst(const struct nfnl_exp *, int);
+extern struct nl_addr * nfnl_exp_get_dst(const struct nfnl_exp *, int);
+
+extern void  nfnl_exp_set_l4protonum(struct nfnl_exp *, int, uint8_t);
+extern int  nfnl_exp_test_l4protonum(const struct nfnl_exp *, int);
+extern uint8_t nfnl_exp_get_l4protonum(const struct nfnl_exp *, int);
+
+extern void nfnl_exp_set_ports(struct nfnl_exp *, int, uint16_t, uint16_t);
+extern int nfnl_exp_test_ports(const struct nfnl_exp *, int);
+extern uint16_t nfnl_exp_get_src_port(const struct nfnl_exp *, int);
+extern uint16_t nfnl_exp_get_dst_port(const struct nfnl_exp *, int);
+
+extern void nfnl_exp_set_icmp(struct nfnl_exp *, int, uint16_t, uint8_t, uint8_t);
+extern int nfnl_exp_test_icmp(const struct nfnl_exp *, int);
+extern uint16_t nfnl_exp_get_icmp_id(const struct nfnl_exp *, int);
+extern uint8_t  nfnl_exp_get_icmp_type(const struct nfnl_exp *, int);
+extern uint8_t  nfnl_exp_get_icmp_code(const struct nfnl_exp *, int);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/netfilter/queue_msg.h b/include/netlink/netfilter/queue_msg.h
index 24ed081..9befee7 100644
--- a/include/netlink/netfilter/queue_msg.h
+++ b/include/netlink/netfilter/queue_msg.h
@@ -93,6 +93,8 @@
 extern struct nl_msg *		nfnl_queue_msg_build_verdict(const struct nfnl_queue_msg *);
 extern int			nfnl_queue_msg_send_verdict(struct nl_sock *,
 							    const struct nfnl_queue_msg *);
+extern int			nfnl_queue_msg_send_verdict_batch(struct nl_sock *,
+							    const struct nfnl_queue_msg *);
 extern int			nfnl_queue_msg_send_verdict_payload(struct nl_sock *,
 						const struct nfnl_queue_msg *,
 						const void *, unsigned );
diff --git a/include/netlink/netlink-kernel.h b/include/netlink/netlink-kernel.h
index a0f5535..f09051d 100644
--- a/include/netlink/netlink-kernel.h
+++ b/include/netlink/netlink-kernel.h
@@ -1,5 +1,13 @@
-#ifndef __LINUX_NETLINK_H
-#define __LINUX_NETLINK_H
+#ifndef __NETLINK_KERNEL_H_
+#define __NETLINK_KERNEL_H_
+
+#if 0
+
+/*
+ * FIXME: Goal is to preseve the documentation but make it simple
+ * to keep linux/netlink.h in sync. Maybe use named documentation
+ * sections.
+ */
 
 /**
  * Netlink socket address
@@ -21,34 +29,29 @@
 };
 
 /**
+ * @addtogroup msg
+ * @{
+ */
+
+
+/**
  * Netlink message header
- * @ingroup msg
  */
 struct nlmsghdr
 {
-	/**
-	 * Length of message including header.
-	 */
+	/** Length of message including header and padding. */
 	uint32_t	nlmsg_len;
 
-	/**
-	 * Message type (content type)
-	 */
+	/** Message type (content type) */
 	uint16_t	nlmsg_type;
 
-	/**
-	 * Message flags
-	 */
+	/** Message flags */
 	uint16_t	nlmsg_flags;
 
-	/**
-	 * Sequence number
-	 */
+	/** Sequence number of message \see core_sk_seq_num. */
 	uint32_t	nlmsg_seq;
 
-	/**
-	 * Netlink PID of the proccess sending the message.
-	 */
+	/** Netlink port */
 	uint32_t	nlmsg_pid;
 };
 
@@ -60,7 +63,6 @@
 /**
  * Must be set on all request messages (typically from user space to
  * kernel space).
- * @ingroup msg
  */
 #define NLM_F_REQUEST		1
 
@@ -89,7 +91,6 @@
 
 /**
  * Return the complete table instead of a single entry.
- * @ingroup msg
  */
 #define NLM_F_ROOT	0x100
 
@@ -119,7 +120,6 @@
 
 /**
  * Replace existing matching config object with this request.
- * @ingroup msg
  */
 #define NLM_F_REPLACE	0x100
 
@@ -147,7 +147,6 @@
 
 /**
  * No operation, message must be ignored
- * @ingroup msg
  */
 #define NLMSG_NOOP		0x1
 
@@ -176,8 +175,7 @@
 /** @} */
 
 /**
- * Netlink error message
- * @ingroup msg
+ * Netlink error message header
  */
 struct nlmsgerr
 {
@@ -193,4 +191,103 @@
 	__u32	group;
 };
 
+/**
+ * Netlink alignment constant, all boundries within messages must be align to this.
+ *
+ * See \ref core_msg_fmt_align for more information on message alignment.
+ */
+#define NLMSG_ALIGNTO	4
+
+/**
+ * Returns \p len properly aligned to NLMSG_ALIGNTO.
+ *
+ * See \ref core_msg_fmt_align for more information on message alignment.
+ */
+#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
+
+/**
+ * Length of a netlink message header including padding.
+ *
+ * See \ref core_msg_fmt_align for more information on message alignment.
+ */
+#define NLMSG_HDRLEN	 ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
+
+/** @} */
+
+/**
+ * @addtogroup attr
+ * @{
+ */
+
+/*
+ */
+
+/**
+ * Netlink attribute structure
+ *
+ * @code
+ *  <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ * |        Header       | Pad |     Payload       | Pad |
+ * |   (struct nlattr)   | ing |                   | ing |
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ *  <-------------- nlattr->nla_len -------------->
+ * @endcode
+ */
+struct nlattr {
+	/**
+	 * Attribute length in bytes including header
+	 */
+	__u16           nla_len;
+
+	/**
+	 * Netlink attribute type
+	 */
+	__u16           nla_type;
+};
+
+/**
+ * @name Attribute Type Flags
+ *
+ * @code
+ * nla_type (16 bits)
+ * +---+---+-------------------------------+
+ * | N | O | Attribute Type                |
+ * +---+---+-------------------------------+
+ * N := Carries nested attributes
+ * O := Payload stored in network byte order
+ * @endcode
+ *
+ * @note The N and O flag are mutually exclusive.
+ *
+ * @{
+ */
+
+/*
+ */
+#define NLA_F_NESTED		(1 << 15)
+#define NLA_F_NET_BYTEORDER	(1 << 14)
+#define NLA_TYPE_MASK		~(NLA_F_NESTED | NLA_F_NET_BYTEORDER)
+
+/** @} */
+
+#define NLA_ALIGNTO		4
+
+/**
+ * Returns \p len properly aligned to NLA_ALIGNTO.
+ *
+ * See \ref core_msg_fmt_align for more information on message alignment.
+ */
+#define NLA_ALIGN(len)		(((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
+
+/**
+ * Length of a netlink attribute header including padding.
+ *
+ * See \ref core_msg_fmt_align for more information on message alignment.
+ */
+#define NLA_HDRLEN		((int) NLA_ALIGN(sizeof(struct nlattr)))
+
+/** @} */
+
+#endif
 #endif	/* __LINUX_NETLINK_H */
diff --git a/include/netlink/netlink.h b/include/netlink/netlink.h
index 1cfe220..28dba06 100644
--- a/include/netlink/netlink.h
+++ b/include/netlink/netlink.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_NETLINK_H_
@@ -26,17 +26,23 @@
 #include <linux/rtnetlink.h>
 #include <linux/genetlink.h>
 #include <linux/netfilter/nfnetlink.h>
+#include <netinet/tcp.h>
 #include <netlink/version.h>
 #include <netlink/errno.h>
 #include <netlink/types.h>
 #include <netlink/handlers.h>
 #include <netlink/socket.h>
+#include <netlink/object.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 struct ucred;
+struct nl_cache_ops;
+struct nl_parser_param;
+struct nl_object;
+struct nl_sock;
 
 extern int nl_debug;
 extern struct nl_dump_params nl_debug_dp;
@@ -52,10 +58,14 @@
 extern int			nl_send(struct nl_sock *, struct nl_msg *);
 extern int			nl_send_iovec(struct nl_sock *, struct nl_msg *,
 					      struct iovec *, unsigned);
+extern void			nl_complete_msg(struct nl_sock *,
+						struct nl_msg *);
 extern void			nl_auto_complete(struct nl_sock *,
-						      struct nl_msg *);
+						 struct nl_msg *);
+extern int			nl_send_auto(struct nl_sock *, struct nl_msg *);
 extern int			nl_send_auto_complete(struct nl_sock *,
 						      struct nl_msg *);
+extern int			nl_send_sync(struct nl_sock *, struct nl_msg *);
 extern int			nl_send_simple(struct nl_sock *, int, int,
 					       void *, size_t);
 
@@ -65,11 +75,18 @@
 					struct ucred **);
 
 extern int			nl_recvmsgs(struct nl_sock *, struct nl_cb *);
+extern int			nl_recvmsgs_report(struct nl_sock *, struct nl_cb *);
 
 extern int			nl_recvmsgs_default(struct nl_sock *);
 
 extern int			nl_wait_for_ack(struct nl_sock *);
 
+extern int			nl_pickup(struct nl_sock *,
+					  int (*parser)(struct nl_cache_ops *,
+						struct sockaddr_nl *,
+						struct nlmsghdr *,
+						struct nl_parser_param *),
+					  struct nl_object **);
 /* Netlink Family Translations */
 extern char *			nl_nlfamily2str(int, char *, size_t);
 extern int			nl_str2nlfamily(const char *);
diff --git a/include/netlink/object-api.h b/include/netlink/object-api.h
index b3337f0..75f29cb 100644
--- a/include/netlink/object-api.h
+++ b/include/netlink/object-api.h
@@ -1,342 +1,19 @@
 /*
- * netlink/object-api.c		Object API
+ * netlink/object-api.h		Object API
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2007 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2013 Thomas Graf <tgraf@suug.ch>
  */
 
-#ifndef NETLINK_OBJECT_API_H_
-#define NETLINK_OBJECT_API_H_
+#ifndef NETLINK_DUMMY_OBJECT_API_H_
+#define NETLINK_DUMMY_OBJECT_API_H_
 
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * @ingroup object
- * @defgroup object_api Object API
- * @brief
- *
- * @par 1) Object Definition
- * @code
- * // Define your object starting with the common object header
- * struct my_obj {
- * 	NLHDR_COMMON
- * 	int		my_data;
- * };
- *
- * // Fill out the object operations structure
- * struct nl_object_ops my_ops = {
- * 	.oo_name	= "my_obj",
- * 	.oo_size	= sizeof(struct my_obj),
- * };
- *
- * // At this point the object can be allocated, you may want to provide a
- * // separate _alloc() function to ease allocting objects of this kind.
- * struct nl_object *obj = nl_object_alloc(&my_ops);
- *
- * // And release it again...
- * nl_object_put(obj);
- * @endcode
- *
- * @par 2) Allocating additional data
- * @code
- * // You may require to allocate additional data and store it inside
- * // object, f.e. assuming there is a field `ptr'.
- * struct my_obj {
- * 	NLHDR_COMMON
- * 	void *		ptr;
- * };
- *
- * // And at some point you may assign allocated data to this field:
- * my_obj->ptr = calloc(1, ...);
- *
- * // In order to not introduce any memory leaks you have to release
- * // this data again when the last reference is given back.
- * static void my_obj_free_data(struct nl_object *obj)
- * {
- * 	struct my_obj *my_obj = nl_object_priv(obj);
- *
- * 	free(my_obj->ptr);
- * }
- *
- * // Also when the object is cloned, you must ensure for your pointer
- * // stay valid even if one of the clones is freed by either making
- * // a clone as well or increase the reference count.
- * static int my_obj_clone(struct nl_object *src, struct nl_object *dst)
- * {
- * 	struct my_obj *my_src = nl_object_priv(src);
- * 	struct my_obj *my_dst = nl_object_priv(dst);
- *
- * 	if (src->ptr) {
- * 		dst->ptr = calloc(1, ...);
- * 		memcpy(dst->ptr, src->ptr, ...);
- * 	}
- * }
- *
- * struct nl_object_ops my_ops = {
- * 	...
- * 	.oo_free_data	= my_obj_free_data,
- * 	.oo_clone	= my_obj_clone,
- * };
- * @endcode
- *
- * @par 3) Object Dumping
- * @code
- * static int my_obj_dump_detailed(struct nl_object *obj,
- * 				   struct nl_dump_params *params)
- * {
- * 	struct my_obj *my_obj = nl_object_priv(obj);
- *
- * 	// It is absolutely essential to use nl_dump() when printing
- *	// any text to make sure the dumping parameters are respected.
- * 	nl_dump(params, "Obj Integer: %d\n", my_obj->my_int);
- *
- * 	// Before we can dump the next line, make sure to prefix
- *	// this line correctly.
- * 	nl_new_line(params);
- *
- * 	// You may also split a line into multiple nl_dump() calls.
- * 	nl_dump(params, "String: %s ", my_obj->my_string);
- * 	nl_dump(params, "String-2: %s\n", my_obj->another_string);
- * }
- *
- * struct nl_object_ops my_ops = {
- * 	...
- * 	.oo_dump[NL_DUMP_FULL]	= my_obj_dump_detailed,
- * };
- * @endcode
- *
- * @par 4) Object Attributes
- * @code
- * // The concept of object attributes is optional but can ease the typical
- * // case of objects that have optional attributes, e.g. a route may have a
- * // nexthop assigned but it is not required to.
- *
- * // The first step to define your object specific bitmask listing all
- * // attributes
- * #define MY_ATTR_FOO		(1<<0)
- * #define MY_ATTR_BAR		(1<<1)
- *
- * // When assigning an optional attribute to the object, make sure
- * // to mark its availability.
- * my_obj->foo = 123123;
- * my_obj->ce_mask |= MY_ATTR_FOO;
- *
- * // At any time you may use this mask to check for the availability
- * // of the attribute, e.g. while dumping
- * if (my_obj->ce_mask & MY_ATTR_FOO)
- * 	nl_dump(params, "foo %d ", my_obj->foo);
- *
- * // One of the big advantages of this concept is that it allows for
- * // standardized comparisons which make it trivial for caches to
- * // identify unique objects by use of unified comparison functions.
- * // In order for it to work, your object implementation must provide
- * // a comparison function and define a list of attributes which
- * // combined together make an object unique.
- *
- * static int my_obj_compare(struct nl_object *_a, struct nl_object *_b,
- * 			     uint32_t attrs, int flags)
- * {
- * 	struct my_obj *a = nl_object_priv(_a):
- * 	struct my_obj *b = nl_object_priv(_b):
- * 	int diff = 0;
- *
- * 	// We help ourselves in defining our own DIFF macro which will
- *	// call ATTR_DIFF() on both objects which will make sure to only
- *	// compare the attributes if required.
- * 	#define MY_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, MY_ATTR_##ATTR, a, b, EXPR)
- *
- * 	// Call our own diff macro for each attribute to build a bitmask
- *	// representing the attributes which mismatch.
- * 	diff |= MY_DIFF(FOO, a->foo != b->foo)
- * 	diff |= MY_DIFF(BAR, strcmp(a->bar, b->bar))
- *
- * 	return diff;
- * }
- *
- * // In order to identify identical objects with differing attributes
- * // you must specify the attributes required to uniquely identify
- * // your object. Make sure to not include too many attributes, this
- * // list is used when caches look for an old version of an object.
- * struct nl_object_ops my_ops = {
- * 	...
- * 	.oo_id_attrs		= MY_ATTR_FOO,
- * 	.oo_compare		= my_obj_compare,
- * };
- * @endcode
- * @{
- */
-
-/**
- * Common Object Header
- *
- * This macro must be included as first member in every object
- * definition to allow objects to be cached.
- */
-#define NLHDR_COMMON				\
-	int			ce_refcnt;	\
-	struct nl_object_ops *	ce_ops;		\
-	struct nl_cache *	ce_cache;	\
-	struct nl_list_head	ce_list;	\
-	int			ce_msgtype;	\
-	int			ce_flags;	\
-	uint32_t		ce_mask;
-
-/**
- * Return true if attribute is available in both objects
- * @arg A		an object
- * @arg B		another object
- * @arg ATTR		attribute bit
- *
- * @return True if the attribute is available, otherwise false is returned.
- */
-#define AVAILABLE(A, B, ATTR)		(((A)->ce_mask & (B)->ce_mask) & (ATTR))
-
-/**
- * Return true if attribute is available in only one of both objects
- * @arg A		an object
- * @arg B		another object
- * @arg ATTR		attribute bit
- *
- * @return True if the attribute is available in only one of both objects,
- * otherwise false is returned.
- */
-#define AVAILABLE_MISMATCH(A, B, ATTR)	(((A)->ce_mask ^ (B)->ce_mask) & (ATTR))
-
-/**
- * Return true if attributes mismatch
- * @arg A		an object
- * @arg B		another object
- * @arg ATTR		attribute bit
- * @arg EXPR		Comparison expression
- *
- * This function will check if the attribute in question is available
- * in both objects, if not this will count as a mismatch.
- *
- * If available the function will execute the expression which must
- * return true if the attributes mismatch.
- *
- * @return True if the attribute mismatch, or false if they match.
- */
-#define ATTR_MISMATCH(A, B, ATTR, EXPR)	(AVAILABLE_MISMATCH(A, B, ATTR) || \
-					 (AVAILABLE(A, B, ATTR) && (EXPR)))
-
-/**
- * Return attribute bit if attribute does not match
- * @arg LIST		list of attributes to be compared
- * @arg ATTR		attribute bit
- * @arg A		an object
- * @arg B		another object
- * @arg EXPR		Comparison expression
- *
- * This function will check if the attribute in question is available
- * in both objects, if not this will count as a mismatch.
- *
- * If available the function will execute the expression which must
- * return true if the attributes mismatch.
- *
- * In case the attributes mismatch, the attribute is returned, otherwise
- * 0 is returned.
- *
- * @code
- * diff |= ATTR_DIFF(attrs, MY_ATTR_FOO, a, b, a->foo != b->foo);
- * @endcode
- */
-#define ATTR_DIFF(LIST, ATTR, A, B, EXPR) \
-({	int diff = 0; \
-	if (((LIST) & (ATTR)) && ATTR_MISMATCH(A, B, ATTR, EXPR)) \
-		diff = ATTR; \
-	diff; })
-
-/**
- * Object Operations
- */
-struct nl_object_ops
-{
-	/**
-	 * Unique name of object type
-	 *
-	 * Must be in the form family/name, e.g. "route/addr"
-	 */
-	char *		oo_name;
-
-	/** Size of object including its header */
-	size_t		oo_size;
-
-	/* List of attributes needed to uniquely identify the object */
-	uint32_t	oo_id_attrs;
-
-	/**
-	 * Constructor function
-	 *
-	 * Will be called when a new object of this type is allocated.
-	 * Can be used to initialize members such as lists etc.
-	 */
-	void  (*oo_constructor)(struct nl_object *);
-
-	/**
-	 * Destructor function
-	 *
-	 * Will be called when an object is freed. Must free all
-	 * resources which may have been allocated as part of this
-	 * object.
-	 */
-	void  (*oo_free_data)(struct nl_object *);
-
-	/**
-	 * Cloning function
-	 *
-	 * Will be called when an object needs to be cloned. Please
-	 * note that the generic object code will make an exact
-	 * copy of the object first, therefore you only need to take
-	 * care of members which require reference counting etc.
-	 *
-	 * May return a negative error code to abort cloning.
-	 */
-	int  (*oo_clone)(struct nl_object *, struct nl_object *);
-
-	/**
-	 * Dumping functions
-	 *
-	 * Will be called when an object is dumped. The implementations
-	 * have to use nl_dump(), nl_dump_line(), and nl_new_line() to
-	 * dump objects.
-	 *
-	 * The functions must return the number of lines printed.
-	 */
-	void (*oo_dump[NL_DUMP_MAX+1])(struct nl_object *,
-				       struct nl_dump_params *);
-
-	/**
-	 * Comparison function
-	 *
-	 * Will be called when two objects of the same type are
-	 * compared. It takes the two objects in question, an object
-	 * specific bitmask defining which attributes should be
-	 * compared and flags to control the behaviour.
-	 *
-	 * The function must return a bitmask with the relevant bit
-	 * set for each attribute that mismatches.
-	 */
-	int   (*oo_compare)(struct nl_object *, struct nl_object *,
-			    uint32_t, int);
-
-
-	char *(*oo_attrs2str)(int, char *, size_t);
-};
-
-/** @} */
-
-#ifdef __cplusplus
-}
-#endif
+#include <netlink/object.h>
 
 #endif
diff --git a/include/netlink/object.h b/include/netlink/object.h
index ef1ed9f..a95feda 100644
--- a/include/netlink/object.h
+++ b/include/netlink/object.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_OBJECT_H_
@@ -31,11 +31,14 @@
 						     struct nl_object **);
 extern void			nl_object_free(struct nl_object *);
 extern struct nl_object *	nl_object_clone(struct nl_object *obj);
+extern int			nl_object_update(struct nl_object *dst,
+						 struct nl_object *src);
 extern void			nl_object_get(struct nl_object *);
 extern void			nl_object_put(struct nl_object *);
 extern int			nl_object_shared(struct nl_object *);
 extern void			nl_object_dump(struct nl_object *,
 					       struct nl_dump_params *);
+extern void			nl_object_dump_buf(struct nl_object *, char *, size_t);
 extern int			nl_object_identical(struct nl_object *,
 						    struct nl_object *);
 extern uint32_t			nl_object_diff(struct nl_object *,
@@ -47,6 +50,8 @@
 						    size_t);
 extern char *			nl_object_attr_list(struct nl_object *,
 						    char *, size_t);
+extern void			nl_object_keygen(struct nl_object *,
+						 uint32_t *, uint32_t);
 
 /* Marks */
 extern void			nl_object_mark(struct nl_object *);
@@ -56,6 +61,12 @@
 /* Access Functions */
 extern int			nl_object_get_refcnt(struct nl_object *);
 extern struct nl_cache *	nl_object_get_cache(struct nl_object *);
+extern const char *		nl_object_get_type(const struct nl_object *);
+extern int			nl_object_get_msgtype(const struct nl_object *);
+struct nl_object_ops *		nl_object_get_ops(const struct nl_object *);
+uint32_t			nl_object_get_id_attrs(struct nl_object *obj);
+
+
 static inline void *		nl_object_priv(struct nl_object *obj)
 {
 	return obj;
diff --git a/include/netlink/route/act/mirred.h b/include/netlink/route/act/mirred.h
new file mode 100644
index 0000000..d65ed37
--- /dev/null
+++ b/include/netlink/route/act/mirred.h
@@ -0,0 +1,35 @@
+/*
+ * netlink/route/cls/mirred.h	mirred action
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+#ifndef NETLINK_MIRRED_H_
+#define NETLINK_MIRRED_H_
+
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+#include <netlink/route/action.h>
+#include <linux/tc_act/tc_mirred.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int rtnl_mirred_set_action(struct rtnl_act *, int);
+extern int rtnl_mirred_get_action(struct rtnl_act *);
+extern int rtnl_mirred_set_ifindex(struct rtnl_act *, uint32_t);
+extern uint32_t rtnl_mirred_get_ifindex(struct rtnl_act *);
+extern int rtnl_mirred_set_policy(struct rtnl_act *, int);
+extern int rtnl_mirred_get_policy(struct rtnl_act *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/action.h b/include/netlink/route/action.h
new file mode 100644
index 0000000..054bdd8
--- /dev/null
+++ b/include/netlink/route/action.h
@@ -0,0 +1,46 @@
+/*
+ * netlink/route/action.h       Actions
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+#ifndef NETLINK_ACTION_H_
+#define NETLINK_ACTION_H_
+
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+#include <netlink/route/tc.h>
+#include <netlink/utils.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern struct rtnl_act *rtnl_act_alloc(void);
+extern void		rtnl_act_get(struct rtnl_act *);
+extern void		rtnl_act_put(struct rtnl_act *);
+extern int		rtnl_act_build_add_request(struct rtnl_act *, int,
+						   struct nl_msg **);
+extern int		rtnl_act_add(struct nl_sock *, struct rtnl_act *, int);
+
+extern int		rtnl_act_build_change_request(struct rtnl_act *, int,
+						      struct nl_msg **);
+extern int		rtnl_act_build_delete_request(struct rtnl_act *, int,
+						      struct nl_msg **);
+extern int		rtnl_act_delete(struct nl_sock *, struct rtnl_act *,
+					int);
+extern int		rtnl_act_append(struct rtnl_act **, struct rtnl_act *);
+extern int		rtnl_act_remove(struct rtnl_act **, struct rtnl_act *);
+extern int		rtnl_act_fill(struct nl_msg *, int, struct rtnl_act *);
+extern void		rtnl_act_put_all(struct rtnl_act **);
+extern int		rtnl_act_parse(struct rtnl_act **, struct nlattr *);
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/addr.h b/include/netlink/route/addr.h
index 1381486..56c12e7 100644
--- a/include/netlink/route/addr.h
+++ b/include/netlink/route/addr.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  * Copyright (c) 2003-2006 Baruch Even <baruch@ev-en.org>,
  *                         Mediatrix Telecom, inc. <ericb@mediatrix.com>
  */
@@ -17,6 +17,7 @@
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/addr.h>
+#include <netlink/route/link.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -29,6 +30,8 @@
 extern void	rtnl_addr_put(struct rtnl_addr *);
 
 extern int	rtnl_addr_alloc_cache(struct nl_sock *, struct nl_cache **);
+extern struct rtnl_addr *
+		rtnl_addr_get(struct nl_cache *, int, struct nl_addr *);
 
 extern int	rtnl_addr_build_add_request(struct rtnl_addr *, int,
 					    struct nl_msg **);
@@ -48,6 +51,10 @@
 extern void	rtnl_addr_set_ifindex(struct rtnl_addr *, int);
 extern int	rtnl_addr_get_ifindex(struct rtnl_addr *);
 
+extern void	rtnl_addr_set_link(struct rtnl_addr *, struct rtnl_link *);
+extern struct rtnl_link *
+		rtnl_addr_get_link(struct rtnl_addr *);
+
 extern void	rtnl_addr_set_family(struct rtnl_addr *, int);
 extern int	rtnl_addr_get_family(struct rtnl_addr *);
 
diff --git a/include/netlink/route/class-modules.h b/include/netlink/route/class-modules.h
deleted file mode 100644
index 74a25c9..0000000
--- a/include/netlink/route/class-modules.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * netlink/route/class-modules.h       Class Module API
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
- */
-
-#ifndef NETLINK_CLASS_MODULES_H_
-#define NETLINK_CLASS_MODULES_H_
-
-#include <netlink/netlink.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Class operations
- * @ingroup class_api
- */
-struct rtnl_class_ops
-{
-	/**
-	 * Kind/Name of class
-	 */
-	char co_kind[32];
-
-	/**
-	 * Dump callbacks
-	 */
-	void (*co_dump[NL_DUMP_MAX+1])(struct rtnl_class *,
-				       struct nl_dump_params *);
-
-	/**
-	 * Must return the contents supposed to be in TCA_OPTIONS
-	 */
-	struct nl_msg *(*co_get_opts)(struct rtnl_class *);
-
-	/**
-	 * TCA_OPTIONS message parser
-	 */
-	int  (*co_msg_parser)(struct rtnl_class *);
-
-	/**
-	 * Called before a class object gets destroyed
-	 */
-	void (*co_free_data)(struct rtnl_class *);
-
-	/**
-	 * Called whenever a class object needs to be cloned
-	 */
-	int (*co_clone)(struct rtnl_class *, struct rtnl_class *);
-
-	/**
-	 * INTERNAL (Do not use)
-	 */
-	struct rtnl_class_ops *co_next;
-};
-
-extern int			rtnl_class_register(struct rtnl_class_ops *);
-extern int			rtnl_class_unregister(struct rtnl_class_ops *);
-extern struct rtnl_class_ops *	rtnl_class_lookup_ops(struct rtnl_class *);
-extern struct rtnl_class_ops *	__rtnl_class_lookup_ops(const char *);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/include/netlink/route/class.h b/include/netlink/route/class.h
index 480095e..e73b60a 100644
--- a/include/netlink/route/class.h
+++ b/include/netlink/route/class.h
@@ -1,12 +1,12 @@
 /*
- * netlink/route/class.h       Classes
+ * netlink/route/class.h       Traffic Classes
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_CLASS_H_
@@ -22,16 +22,17 @@
 
 struct rtnl_class;
 
-extern struct nl_object_ops class_obj_ops;
-
-extern struct rtnl_class *	rtnl_class_alloc(void);
+extern struct rtnl_class *
+			rtnl_class_alloc(void);
 extern void		rtnl_class_put(struct rtnl_class *);
+
 extern int		rtnl_class_alloc_cache(struct nl_sock *, int,
 					       struct nl_cache **);
-extern struct rtnl_class *rtnl_class_get(struct nl_cache *, int, uint32_t);
+extern struct rtnl_class *
+			rtnl_class_get(struct nl_cache *, int, uint32_t);
 
-/* leaf qdisc access */
-extern struct rtnl_qdisc *	rtnl_class_leaf_qdisc(struct rtnl_class *,
+extern struct rtnl_qdisc *
+			rtnl_class_leaf_qdisc(struct rtnl_class *,
 						      struct nl_cache *);
 
 extern int		rtnl_class_build_add_request(struct rtnl_class *, int,
@@ -39,32 +40,24 @@
 extern int		rtnl_class_add(struct nl_sock *, struct rtnl_class *,
 				       int);
 
-extern int	rtnl_class_build_delete_request(struct rtnl_class *,
-											struct nl_msg **);
-extern int	rtnl_class_delete(struct nl_sock *, struct rtnl_class *);
+extern int		rtnl_class_build_delete_request(struct rtnl_class *,
+							struct nl_msg **);
+extern int		rtnl_class_delete(struct nl_sock *,
+					  struct rtnl_class *);
 
-extern void		rtnl_class_set_ifindex(struct rtnl_class *, int);
-extern int		rtnl_class_get_ifindex(struct rtnl_class *);
-extern void		rtnl_class_set_handle(struct rtnl_class *, uint32_t);
-extern uint32_t		rtnl_class_get_handle(struct rtnl_class *);
-extern void		rtnl_class_set_parent(struct rtnl_class *, uint32_t);
-extern uint32_t		rtnl_class_get_parent(struct rtnl_class *);
-extern void		rtnl_class_set_kind(struct rtnl_class *, const char *);
-extern char *		rtnl_class_get_kind(struct rtnl_class *);
-extern uint64_t		rtnl_class_get_stat(struct rtnl_class *,
-					    enum rtnl_tc_stats_id);
-
-/* iterators */
+/* deprecated functions */
 extern void		rtnl_class_foreach_child(struct rtnl_class *,
 						 struct nl_cache *,
 						 void (*cb)(struct nl_object *,
 						 	    void *),
-						 void *);
+						 void *)
+						 __attribute__((deprecated));
 extern void		rtnl_class_foreach_cls(struct rtnl_class *,
 					       struct nl_cache *,
 					       void (*cb)(struct nl_object *,
 							  void *),
-					       void *);
+					       void *)
+					       __attribute__((deprecated));
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/classifier-modules.h b/include/netlink/route/classifier-modules.h
deleted file mode 100644
index 35cb06e..0000000
--- a/include/netlink/route/classifier-modules.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * netlink/route/classifier-modules.h   Classifier Module API
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
- */
-
-#ifndef NETLINK_CLASS_MODULES_H_
-#define NETLINK_CLASS_MODULES_H_
-
-#include <netlink/netlink.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Classifier operations
- * @ingroup cls_api
- */
-struct rtnl_cls_ops
-{
-	/**
-	 * Name of classifier module
-	 */
-	char co_kind[32];
-
-
-	/**
-	 * Size of private classifier data
-	 */
-	size_t co_size;
-
-	/**
-	 * Dump callbacks
-	 */
-	void (*co_dump[NL_DUMP_MAX+1])(struct rtnl_cls *,
-				       struct nl_dump_params *);
-	/**
-	 * Must return the contents supposed to be in TCA_OPTIONS
-	 */
-	int (*co_get_opts)(struct rtnl_cls *, struct nl_msg *);
-
-	/**
-	 * TCA_OPTIONS message parser
-	 */
-	int (*co_msg_parser)(struct rtnl_cls *);
-
-	/**
-	 * Called before a class object gets destroyed
-	 */
-	void (*co_free_data)(struct rtnl_cls *);
-
-	/**
-	 * Called whenever a classifier object needs to be cloned
-	 */
-	int (*co_clone)(struct rtnl_cls *, struct rtnl_cls *);
-
-	/**
-	 * INTERNAL (Do not use)
-	 */
-	struct rtnl_cls_ops *co_next;
-};
-
-extern int 			rtnl_cls_register(struct rtnl_cls_ops *);
-extern int 			rtnl_cls_unregister(struct rtnl_cls_ops *);
-extern struct rtnl_cls_ops *	rtnl_cls_lookup_ops(struct rtnl_cls *);
-extern struct rtnl_cls_ops *	__rtnl_cls_lookup_ops(const char *kind);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/include/netlink/route/classifier.h b/include/netlink/route/classifier.h
index d9c3d21..a8c1179 100644
--- a/include/netlink/route/classifier.h
+++ b/include/netlink/route/classifier.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_CLASSIFIER_H_
@@ -21,39 +21,29 @@
 extern "C" {
 #endif
 
-extern struct nl_object_ops cls_obj_ops;
-
 extern struct rtnl_cls *rtnl_cls_alloc(void);
-extern void	rtnl_cls_put(struct rtnl_cls *);
+extern void		rtnl_cls_put(struct rtnl_cls *);
 
-extern int	rtnl_cls_alloc_cache(struct nl_sock *, int, uint32_t,
-				     struct nl_cache **);
+extern int		rtnl_cls_alloc_cache(struct nl_sock *, int, uint32_t,
+					     struct nl_cache **);
 
-extern int	rtnl_cls_build_add_request(struct rtnl_cls *, int,
-					   struct nl_msg **);
-extern int	rtnl_cls_add(struct nl_sock *, struct rtnl_cls *, int);
+extern int		rtnl_cls_build_add_request(struct rtnl_cls *, int,
+						   struct nl_msg **);
+extern int		rtnl_cls_add(struct nl_sock *, struct rtnl_cls *, int);
+extern int		rtnl_cls_change(struct nl_sock *, struct rtnl_cls *, int);
 
-extern int	rtnl_cls_build_change_request(struct rtnl_cls *, int,
-					      struct nl_msg **);
-extern int	rtnl_cls_build_delete_request(struct rtnl_cls *, int,
-					      struct nl_msg **);
-extern int	rtnl_cls_delete(struct nl_sock *, struct rtnl_cls *, int);
+extern int		rtnl_cls_build_change_request(struct rtnl_cls *, int,
+						      struct nl_msg **);
+extern int		rtnl_cls_build_delete_request(struct rtnl_cls *, int,
+						      struct nl_msg **);
+extern int		rtnl_cls_delete(struct nl_sock *, struct rtnl_cls *,
+					int);
 
-extern void rtnl_cls_set_ifindex(struct rtnl_cls *, int);
-extern int rtnl_cls_get_ifindex(struct rtnl_cls *);
-extern void rtnl_cls_set_handle(struct rtnl_cls *, uint32_t);
-extern void rtnl_cls_set_parent(struct rtnl_cls *, uint32_t);
-extern uint32_t rtnl_cls_get_parent(struct rtnl_cls *);
-extern int rtnl_cls_set_kind(struct rtnl_cls *, const char *);
-extern struct rtnl_cls_ops *rtnl_cls_get_ops(struct rtnl_cls *);
+extern void		rtnl_cls_set_prio(struct rtnl_cls *, uint16_t);
+extern uint16_t		rtnl_cls_get_prio(struct rtnl_cls *);
 
-extern void rtnl_cls_set_prio(struct rtnl_cls *, uint16_t);
-extern uint16_t rtnl_cls_get_prio(struct rtnl_cls *);
-
-extern void rtnl_cls_set_protocol(struct rtnl_cls *, uint16_t);
-extern uint16_t rtnl_cls_get_protocol(struct rtnl_cls *);
-
-extern void *rtnl_cls_data(struct rtnl_cls *);
+extern void		rtnl_cls_set_protocol(struct rtnl_cls *, uint16_t);
+extern uint16_t		rtnl_cls_get_protocol(struct rtnl_cls *);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/cls/basic.h b/include/netlink/route/cls/basic.h
index 7003124..f00793c 100644
--- a/include/netlink/route/cls/basic.h
+++ b/include/netlink/route/cls/basic.h
@@ -6,25 +6,28 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_BASIC_H_
 #define NETLINK_BASIC_H_
 
 #include <netlink/netlink.h>
+#include <netlink/route/classifier.h>
+#include <netlink/route/cls/ematch.h>
+#include <netlink/route/action.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-extern struct rtnl_cls_ops *rtnl_basic_get_ops(void);
-extern int	rtnl_basic_set_classid(struct rtnl_cls *, uint32_t);
-extern uint32_t	rtnl_basic_get_classid(struct rtnl_cls *);
-extern int	rtnl_basic_set_ematch(struct rtnl_cls *,
-				      struct rtnl_ematch_tree *);
-extern struct rtnl_ematch_tree *
-		rtnl_basic_get_ematch(struct rtnl_cls *);
+extern void			rtnl_basic_set_target(struct rtnl_cls *, uint32_t);
+extern uint32_t			rtnl_basic_get_target(struct rtnl_cls *);
+extern void			rtnl_basic_set_ematch(struct rtnl_cls *,
+						      struct rtnl_ematch_tree *);
+extern struct rtnl_ematch_tree *rtnl_basic_get_ematch(struct rtnl_cls *);
+extern int rtnl_basic_add_action(struct rtnl_cls *, struct rtnl_act *);
+extern int rtnl_basic_del_action(struct rtnl_cls *, struct rtnl_act *);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/cls/cgroup.h b/include/netlink/route/cls/cgroup.h
index 7b0e3d3..9cd4845 100644
--- a/include/netlink/route/cls/cgroup.h
+++ b/include/netlink/route/cls/cgroup.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2009-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_CLS_CGROUP_H_
@@ -14,15 +14,16 @@
 
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
+#include <netlink/route/classifier.h>
+#include <netlink/route/cls/ematch.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-extern int	rtnl_cgroup_set_ematch(struct rtnl_cls *,
-				       struct rtnl_ematch_tree *);
-extern struct rtnl_ematch_tree *
-		rtnl_cgroup_get_ematch(struct rtnl_cls *);
+extern void			rtnl_cgroup_set_ematch(struct rtnl_cls *,
+						struct rtnl_ematch_tree *);
+struct rtnl_ematch_tree *	rtnl_cgroup_get_ematch(struct rtnl_cls *);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/cls/ematch.h b/include/netlink/route/cls/ematch.h
index c4292bf..13f9c32 100644
--- a/include/netlink/route/cls/ematch.h
+++ b/include/netlink/route/cls/ematch.h
@@ -6,13 +6,14 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_CLS_EMATCH_H_
 #define NETLINK_CLS_EMATCH_H_
 
 #include <netlink/netlink.h>
+#include <netlink/msg.h>
 #include <netlink/route/classifier.h>
 #include <linux/pkt_cls.h>
 
@@ -20,51 +21,72 @@
 extern "C" {
 #endif
 
+/* FIXME: Should be moved to the kernel header at some point */
+#define RTNL_EMATCH_PROGID	2
+
 struct rtnl_ematch;
 struct rtnl_ematch_tree;
 
+/**
+ * Extended Match Operations
+ */
 struct rtnl_ematch_ops
 {
-	int				eo_kind;
-	const char *			eo_name;
-	size_t				eo_datalen;
+	int			eo_kind;
+	const char *		eo_name;
+	size_t			eo_minlen;
+	size_t			eo_datalen;
 
-	int			      (*eo_parse)(struct rtnl_ematch *,
-						  void *, size_t);
-	void			      (*eo_dump)(struct rtnl_ematch *,
-						 struct nl_dump_params *);
-	struct nl_list_head		eo_list;
+	int		      (*eo_parse)(struct rtnl_ematch *, void *, size_t);
+	void		      (*eo_dump)(struct rtnl_ematch *,
+					 struct nl_dump_params *);
+	int		      (*eo_fill)(struct rtnl_ematch *, struct nl_msg *);
+	void		      (*eo_free)(struct rtnl_ematch *);
+	struct nl_list_head	eo_list;
 };
 
-extern int	rtnl_ematch_register(struct rtnl_ematch_ops *);
-extern int	rtnl_ematch_unregister(struct rtnl_ematch_ops *);
+extern int			rtnl_ematch_register(struct rtnl_ematch_ops *);
+extern struct rtnl_ematch_ops *	rtnl_ematch_lookup_ops(int);
+extern struct rtnl_ematch_ops *	rtnl_ematch_lookup_ops_by_name(const char *);
 
-extern struct rtnl_ematch_ops *
-		rtnl_ematch_lookup_ops(int);
-extern struct rtnl_ematch_ops *
-		rtnl_ematch_lookup_ops_name(const char *);
+extern struct rtnl_ematch *	rtnl_ematch_alloc(void);
+extern int			rtnl_ematch_add_child(struct rtnl_ematch *,
+						      struct rtnl_ematch *);
+extern void			rtnl_ematch_unlink(struct rtnl_ematch *);
+extern void			rtnl_ematch_free(struct rtnl_ematch *);
 
-extern struct rtnl_ematch *
-		rtnl_ematch_alloc(struct rtnl_ematch_ops *);
-extern void	rtnl_ematch_add_child(struct rtnl_ematch *,
-				      struct rtnl_ematch *);
-extern void	rtnl_ematch_unlink(struct rtnl_ematch *);
-extern void	rtnl_ematch_free(struct rtnl_ematch *);
+extern void *			rtnl_ematch_data(struct rtnl_ematch *);
+extern void			rtnl_ematch_set_flags(struct rtnl_ematch *,
+						      uint16_t);
+extern void			rtnl_ematch_unset_flags(struct rtnl_ematch *,
+							uint16_t);
+extern uint16_t			rtnl_ematch_get_flags(struct rtnl_ematch *);
+extern int			rtnl_ematch_set_ops(struct rtnl_ematch *,
+						    struct rtnl_ematch_ops *);
+extern int			rtnl_ematch_set_kind(struct rtnl_ematch *,
+						     uint16_t);
+extern int			rtnl_ematch_set_name(struct rtnl_ematch *,
+						     const char *);
 
-extern void *	rtnl_ematch_data(struct rtnl_ematch *);
-extern void	rtnl_ematch_set_flags(struct rtnl_ematch *, uint16_t);
-extern void	rtnl_ematch_unset_flags(struct rtnl_ematch *, uint16_t);
-extern uint16_t	rtnl_ematch_get_flags(struct rtnl_ematch *);
+extern struct rtnl_ematch_tree *rtnl_ematch_tree_alloc(uint16_t);
+extern void			rtnl_ematch_tree_free(struct rtnl_ematch_tree *);
+extern void			rtnl_ematch_tree_add(struct rtnl_ematch_tree *,
+						     struct rtnl_ematch *);
 
-extern struct rtnl_ematch_tree *
-		rtnl_ematch_tree_alloc(uint16_t);
-extern void	rtnl_ematch_tree_free(struct rtnl_ematch_tree *);
+extern int			rtnl_ematch_parse_attr(struct nlattr *,
+						       struct rtnl_ematch_tree **);
+extern int			rtnl_ematch_fill_attr(struct nl_msg *, int,
+						      struct rtnl_ematch_tree *);
+extern void			rtnl_ematch_tree_dump(struct rtnl_ematch_tree *,
+						      struct nl_dump_params *);
 
-extern int	rtnl_ematch_parse(struct nlattr *, struct rtnl_ematch_tree **);
-extern void	rtnl_ematch_tree_add_tail(struct rtnl_ematch_tree *,
-					  struct rtnl_ematch *);
-extern void	rtnl_ematch_tree_dump(struct rtnl_ematch_tree *,
-				      struct nl_dump_params *);
+
+extern int			rtnl_ematch_parse_expr(const char *, char **,
+						       struct rtnl_ematch_tree **);
+
+extern char *			rtnl_ematch_offset2txt(uint8_t, uint16_t,
+						       char *, size_t);
+extern char *			rtnl_ematch_opnd2txt(uint8_t, char *, size_t);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/cls/ematch/cmp.h b/include/netlink/route/cls/ematch/cmp.h
index b4ad03a..308113e 100644
--- a/include/netlink/route/cls/ematch/cmp.h
+++ b/include/netlink/route/cls/ematch/cmp.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_CLS_EMATCH_CMP_H_
@@ -14,6 +14,7 @@
 
 #include <netlink/netlink.h>
 #include <netlink/route/cls/ematch.h>
+#include <linux/tc_ematch/tc_em_cmp.h>
 
 #ifdef __cplusplus
 extern "C" {
diff --git a/include/netlink/route/cls/ematch/meta.h b/include/netlink/route/cls/ematch/meta.h
new file mode 100644
index 0000000..2fe5899
--- /dev/null
+++ b/include/netlink/route/cls/ematch/meta.h
@@ -0,0 +1,41 @@
+/*
+ * netlink/route/cls/ematch/meta.h	Metadata Match
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_CLS_EMATCH_META_H_
+#define NETLINK_CLS_EMATCH_META_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/cls/ematch.h>
+#include <linux/tc_ematch/tc_em_meta.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct rtnl_meta_value;
+
+extern struct rtnl_meta_value *	rtnl_meta_value_alloc_int(uint64_t);
+extern struct rtnl_meta_value *	rtnl_meta_value_alloc_var(void *, size_t);
+extern struct rtnl_meta_value *	rtnl_meta_value_alloc_id(uint8_t, uint16_t,
+							  uint8_t, uint64_t);
+extern void	rtnl_meta_value_put(struct rtnl_meta_value *);
+
+extern void	rtnl_ematch_meta_set_lvalue(struct rtnl_ematch *,
+					    struct rtnl_meta_value *);
+void		rtnl_ematch_meta_set_rvalue(struct rtnl_ematch *,
+					    struct rtnl_meta_value *);
+extern void	rtnl_ematch_meta_set_operand(struct rtnl_ematch *, uint8_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/cls/ematch/nbyte.h b/include/netlink/route/cls/ematch/nbyte.h
new file mode 100644
index 0000000..014c719
--- /dev/null
+++ b/include/netlink/route/cls/ematch/nbyte.h
@@ -0,0 +1,36 @@
+/*
+ * netlink/route/cls/ematch/nbyte.h	N-Byte Comparison
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_CLS_EMATCH_NBYTE_H_
+#define NETLINK_CLS_EMATCH_NBYTE_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/cls/ematch.h>
+#include <linux/tc_ematch/tc_em_nbyte.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void		rtnl_ematch_nbyte_set_offset(struct rtnl_ematch *,
+						     uint8_t, uint16_t);
+extern uint16_t		rtnl_ematch_nbyte_get_offset(struct rtnl_ematch *);
+extern uint8_t		rtnl_ematch_nbyte_get_layer(struct rtnl_ematch *);
+extern void		rtnl_ematch_nbyte_set_pattern(struct rtnl_ematch *,
+						      uint8_t *, size_t);
+extern uint8_t *	rtnl_ematch_nbyte_get_pattern(struct rtnl_ematch *);
+extern size_t		rtnl_ematch_nbyte_get_len(struct rtnl_ematch *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/cls/ematch/text.h b/include/netlink/route/cls/ematch/text.h
new file mode 100644
index 0000000..e599abf
--- /dev/null
+++ b/include/netlink/route/cls/ematch/text.h
@@ -0,0 +1,42 @@
+/*
+ * netlink/route/cls/ematch/text.h	Text Search
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_CLS_EMATCH_TEXT_H_
+#define NETLINK_CLS_EMATCH_TEXT_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/cls/ematch.h>
+#include <linux/tc_ematch/tc_em_text.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void	rtnl_ematch_text_set_from(struct rtnl_ematch *,
+					  uint8_t, uint16_t);
+extern uint16_t	rtnl_ematch_text_get_from_offset(struct rtnl_ematch *);
+extern uint8_t	rtnl_ematch_text_get_from_layer(struct rtnl_ematch *);
+extern void	rtnl_ematch_text_set_to(struct rtnl_ematch *,
+					uint8_t, uint16_t);
+extern uint16_t	rtnl_ematch_text_get_to_offset(struct rtnl_ematch *);
+extern uint8_t	rtnl_ematch_text_get_to_layer(struct rtnl_ematch *);
+extern void	rtnl_ematch_text_set_pattern(struct rtnl_ematch *,
+					     char *, size_t);
+extern char *	rtnl_ematch_text_get_pattern(struct rtnl_ematch *);
+extern size_t	rtnl_ematch_text_get_len(struct rtnl_ematch *);
+extern void	rtnl_ematch_text_set_algo(struct rtnl_ematch *, const char *);
+extern char *	rtnl_ematch_text_get_algo(struct rtnl_ematch *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/cls/fw.h b/include/netlink/route/cls/fw.h
index 39878de..2e1bade 100644
--- a/include/netlink/route/cls/fw.h
+++ b/include/netlink/route/cls/fw.h
@@ -15,12 +15,14 @@
 #define NETLINK_FW_H_
 
 #include <netlink/netlink.h>
+#include <netlink/route/classifier.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 extern int	rtnl_fw_set_classid(struct rtnl_cls *, uint32_t);
+extern int	rtnl_fw_set_mask(struct rtnl_cls *, uint32_t);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/cls/u32.h b/include/netlink/route/cls/u32.h
index cf35e26..f35d37a 100644
--- a/include/netlink/route/cls/u32.h
+++ b/include/netlink/route/cls/u32.h
@@ -14,6 +14,8 @@
 
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
+#include <netlink/route/classifier.h>
+#include <netlink/route/action.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -21,20 +23,29 @@
 
 extern void	rtnl_u32_set_handle(struct rtnl_cls *, int, int, int);
 extern int	rtnl_u32_set_classid(struct rtnl_cls *, uint32_t);
+extern int	rtnl_u32_set_divisor(struct rtnl_cls *, uint32_t);
+extern int	rtnl_u32_set_link(struct rtnl_cls *, uint32_t);
+extern int	rtnl_u32_set_hashtable(struct rtnl_cls *, uint32_t);
+extern int	rtnl_u32_set_hashmask(struct rtnl_cls *, uint32_t, uint32_t);
+extern int	rtnl_u32_set_cls_terminal(struct rtnl_cls *);
 
 extern int	rtnl_u32_set_flags(struct rtnl_cls *, int);
 extern int	rtnl_u32_add_key(struct rtnl_cls *, uint32_t, uint32_t,
 				 int, int);
+extern int	rtnl_u32_get_key(struct rtnl_cls *, uint8_t, uint32_t *, uint32_t *,
+				 int *, int *);
 extern int	rtnl_u32_add_key_uint8(struct rtnl_cls *, uint8_t, uint8_t,
 				       int, int);
 extern int	rtnl_u32_add_key_uint16(struct rtnl_cls *, uint16_t, uint16_t,
 					int, int);
 extern int	rtnl_u32_add_key_uint32(struct rtnl_cls *, uint32_t, uint32_t,
 					int, int);
-extern int	rtnl_u32_add_key_in_addr(struct rtnl_cls *, struct in_addr *,
+extern int	rtnl_u32_add_key_in_addr(struct rtnl_cls *, const struct in_addr *,
 					 uint8_t, int, int);
-extern int	rtnl_u32_add_key_in6_addr(struct rtnl_cls *, struct in6_addr *,
+extern int	rtnl_u32_add_key_in6_addr(struct rtnl_cls *, const struct in6_addr *,
 					  uint8_t, int, int);
+extern int	rtnl_u32_add_action(struct rtnl_cls *, struct rtnl_act *);
+extern int	rtnl_u32_del_action(struct rtnl_cls *, struct rtnl_act *);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/link.h b/include/netlink/route/link.h
index 4b630f7..a7aa88b 100644
--- a/include/netlink/route/link.h
+++ b/include/netlink/route/link.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_LINK_H_
@@ -15,59 +15,122 @@
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/addr.h>
+#include <linux/if.h>
+#include <sys/types.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+/**
+ * @struct rtnl_link link.h "netlink/route/link.h"
+ * @brief Link object
+ * @implements nl_object
+ * @ingroup link
+ *
+ * @copydoc private_struct
+ */
 struct rtnl_link;
 
-enum rtnl_link_st {
-	RTNL_LINK_RX_PACKETS,
-	RTNL_LINK_TX_PACKETS,
-	RTNL_LINK_RX_BYTES,
-	RTNL_LINK_TX_BYTES,
-	RTNL_LINK_RX_ERRORS,
-	RTNL_LINK_TX_ERRORS,
-	RTNL_LINK_RX_DROPPED,
-	RTNL_LINK_TX_DROPPED,
-	RTNL_LINK_RX_COMPRESSED,
-	RTNL_LINK_TX_COMPRESSED,
-	RTNL_LINK_RX_FIFO_ERR,
-	RTNL_LINK_TX_FIFO_ERR,
-	RTNL_LINK_RX_LEN_ERR,
-	RTNL_LINK_RX_OVER_ERR,
-	RTNL_LINK_RX_CRC_ERR,
-	RTNL_LINK_RX_FRAME_ERR,
-	RTNL_LINK_RX_MISSED_ERR,
-	RTNL_LINK_TX_ABORT_ERR,
-	RTNL_LINK_TX_CARRIER_ERR,
-	RTNL_LINK_TX_HBEAT_ERR,
-	RTNL_LINK_TX_WIN_ERR,
-	RTNL_LINK_TX_COLLISIONS,
-	RTNL_LINK_MULTICAST,
+/**
+ * @ingroup link
+ */
+typedef enum {
+	RTNL_LINK_RX_PACKETS,		/*!< Packets received */
+	RTNL_LINK_TX_PACKETS,		/*!< Packets sent */
+	RTNL_LINK_RX_BYTES,		/*!< Bytes received */
+	RTNL_LINK_TX_BYTES,		/*!< Bytes sent */
+	RTNL_LINK_RX_ERRORS,		/*!< Receive errors */
+	RTNL_LINK_TX_ERRORS,		/*!< Send errors */
+	RTNL_LINK_RX_DROPPED,		/*!< Received packets dropped */
+	RTNL_LINK_TX_DROPPED,		/*!< Packets dropped during transmit */
+	RTNL_LINK_RX_COMPRESSED,	/*!< Compressed packets received */
+	RTNL_LINK_TX_COMPRESSED,	/*!< Compressed packets sent */
+	RTNL_LINK_RX_FIFO_ERR,		/*!< Receive FIFO errors */
+	RTNL_LINK_TX_FIFO_ERR,		/*!< Send FIFO errors */
+	RTNL_LINK_RX_LEN_ERR,		/*!< Length errors */
+	RTNL_LINK_RX_OVER_ERR,		/*!< Over errors */
+	RTNL_LINK_RX_CRC_ERR,		/*!< CRC errors */
+	RTNL_LINK_RX_FRAME_ERR,		/*!< Frame errors */
+	RTNL_LINK_RX_MISSED_ERR,	/*!< Missed errors */
+	RTNL_LINK_TX_ABORT_ERR,		/*!< Aborted errors */
+	RTNL_LINK_TX_CARRIER_ERR,	/*!< Carrier errors */
+	RTNL_LINK_TX_HBEAT_ERR,		/*!< Heartbeat errors */
+	RTNL_LINK_TX_WIN_ERR,		/*!< Window errors */
+	RTNL_LINK_COLLISIONS,		/*!< Send collisions */
+	RTNL_LINK_MULTICAST,		/*!< Multicast */
+	RTNL_LINK_IP6_INPKTS,		/*!< IPv6 SNMP InReceives */
+	RTNL_LINK_IP6_INHDRERRORS,	/*!< IPv6 SNMP InHdrErrors */
+	RTNL_LINK_IP6_INTOOBIGERRORS,	/*!< IPv6 SNMP InTooBigErrors */
+	RTNL_LINK_IP6_INNOROUTES,	/*!< IPv6 SNMP InNoRoutes */
+	RTNL_LINK_IP6_INADDRERRORS,	/*!< IPv6 SNMP InAddrErrors */
+	RTNL_LINK_IP6_INUNKNOWNPROTOS,	/*!< IPv6 SNMP InUnknownProtos */
+	RTNL_LINK_IP6_INTRUNCATEDPKTS,	/*!< IPv6 SNMP InTruncatedPkts */
+	RTNL_LINK_IP6_INDISCARDS,	/*!< IPv6 SNMP InDiscards */
+	RTNL_LINK_IP6_INDELIVERS,	/*!< IPv6 SNMP InDelivers */
+	RTNL_LINK_IP6_OUTFORWDATAGRAMS,	/*!< IPv6 SNMP OutForwDatagrams */
+	RTNL_LINK_IP6_OUTPKTS,		/*!< IPv6 SNMP OutRequests */
+	RTNL_LINK_IP6_OUTDISCARDS,	/*!< IPv6 SNMP OutDiscards */
+	RTNL_LINK_IP6_OUTNOROUTES,	/*!< IPv6 SNMP OutNoRoutes */
+	RTNL_LINK_IP6_REASMTIMEOUT,	/*!< IPv6 SNMP ReasmTimeout */
+	RTNL_LINK_IP6_REASMREQDS,	/*!< IPv6 SNMP ReasmReqds */
+	RTNL_LINK_IP6_REASMOKS,		/*!< IPv6 SNMP ReasmOKs */
+	RTNL_LINK_IP6_REASMFAILS,	/*!< IPv6 SNMP ReasmFails */
+	RTNL_LINK_IP6_FRAGOKS,		/*!< IPv6 SNMP FragOKs */
+	RTNL_LINK_IP6_FRAGFAILS,	/*!< IPv6 SNMP FragFails */
+	RTNL_LINK_IP6_FRAGCREATES,	/*!< IPv6 SNMP FragCreates */
+	RTNL_LINK_IP6_INMCASTPKTS,	/*!< IPv6 SNMP InMcastPkts */
+	RTNL_LINK_IP6_OUTMCASTPKTS,	/*!< IPv6 SNMP OutMcastPkts */
+	RTNL_LINK_IP6_INBCASTPKTS,	/*!< IPv6 SNMP InBcastPkts */
+	RTNL_LINK_IP6_OUTBCASTPKTS,	/*!< IPv6 SNMP OutBcastPkts */
+	RTNL_LINK_IP6_INOCTETS,		/*!< IPv6 SNMP InOctets */
+	RTNL_LINK_IP6_OUTOCTETS,	/*!< IPv6 SNMP OutOctets */
+	RTNL_LINK_IP6_INMCASTOCTETS,	/*!< IPv6 SNMP InMcastOctets */
+	RTNL_LINK_IP6_OUTMCASTOCTETS,	/*!< IPv6 SNMP OutMcastOctets */
+	RTNL_LINK_IP6_INBCASTOCTETS,	/*!< IPv6 SNMP InBcastOctets */
+	RTNL_LINK_IP6_OUTBCASTOCTETS,	/*!< IPv6 SNMP OutBcastOctets */
+	RTNL_LINK_ICMP6_INMSGS,		/*!< ICMPv6 SNMP InMsgs */
+	RTNL_LINK_ICMP6_INERRORS,	/*!< ICMPv6 SNMP InErrors */
+	RTNL_LINK_ICMP6_OUTMSGS,	/*!< ICMPv6 SNMP OutMsgs */
+	RTNL_LINK_ICMP6_OUTERRORS,	/*!< ICMPv6 SNMP OutErrors */
+	RTNL_LINK_ICMP6_CSUMERRORS,	/*!< ICMPv6 SNMP InCsumErrors */
+	RTNL_LINK_IP6_CSUMERRORS,	/*!< IPv6 SNMP InCsumErrors */
+	RTNL_LINK_IP6_NOECTPKTS,	/*!< IPv6 SNMP InNoECTPkts */
+	RTNL_LINK_IP6_ECT1PKTS,		/*!< IPv6 SNMP InECT1Pkts */
+	RTNL_LINK_IP6_ECT0PKTS,		/*!< IPv6 SNMP InECT0Pkts */
+	RTNL_LINK_IP6_CEPKTS,		/*!< IPv6 SNMP InCEPkts */
 	__RTNL_LINK_STATS_MAX,
-};
+} rtnl_link_stat_id_t;
 
 #define RTNL_LINK_STATS_MAX (__RTNL_LINK_STATS_MAX - 1)
 
-/* link object allocation/freeage */
+extern struct nla_policy rtln_link_policy[];
+
 extern struct rtnl_link *rtnl_link_alloc(void);
 extern void	rtnl_link_put(struct rtnl_link *);
-extern void	rtnl_link_free(struct rtnl_link *);
 
-/* link cache management */
-extern int	rtnl_link_alloc_cache(struct nl_sock *, struct nl_cache **);
+extern int	rtnl_link_alloc_cache(struct nl_sock *, int, struct nl_cache **);
 extern struct rtnl_link *rtnl_link_get(struct nl_cache *, int);
 extern struct rtnl_link *rtnl_link_get_by_name(struct nl_cache *, const char *);
 
 
+extern int	rtnl_link_build_add_request(struct rtnl_link *, int,
+					    struct nl_msg **);
+extern int	rtnl_link_add(struct nl_sock *, struct rtnl_link *, int);
 extern int	rtnl_link_build_change_request(struct rtnl_link *,
 					       struct rtnl_link *, int,
 					       struct nl_msg **);
 extern int	rtnl_link_change(struct nl_sock *, struct rtnl_link *,
 				 struct rtnl_link *, int);
 
+extern int	rtnl_link_build_delete_request(const struct rtnl_link *,
+					       struct nl_msg **);
+extern int	rtnl_link_delete(struct nl_sock *, const struct rtnl_link *);
+extern int	rtnl_link_build_get_request(int, const char *,
+					    struct nl_msg **);
+extern int	rtnl_link_get_kernel(struct nl_sock *, int, const char *,
+				     struct rtnl_link **);
+
 /* Name <-> Index Translations */
 extern char * 	rtnl_link_i2name(struct nl_cache *, int, char *, size_t);
 extern int	rtnl_link_name2i(struct nl_cache *, const char *);
@@ -80,12 +143,16 @@
 extern char *	rtnl_link_flags2str(int, char *, size_t);
 extern int	rtnl_link_str2flags(const char *);
 
-extern char *	rtnl_link_operstate2str(int, char *, size_t);
+extern char *	rtnl_link_operstate2str(uint8_t, char *, size_t);
 extern int	rtnl_link_str2operstate(const char *);
 
-extern char *	rtnl_link_mode2str(int, char *, size_t);
+extern char *	rtnl_link_mode2str(uint8_t, char *, size_t);
 extern int	rtnl_link_str2mode(const char *);
 
+/* Carrier State Translations */
+extern char *	rtnl_link_carrier2str(uint8_t, char *, size_t);
+extern int	rtnl_link_str2carrier(const char *);
+
 /* Access Functions */
 extern void	rtnl_link_set_qdisc(struct rtnl_link *, const char *);
 extern char *	rtnl_link_get_qdisc(struct rtnl_link *);
@@ -93,6 +160,9 @@
 extern void	rtnl_link_set_name(struct rtnl_link *, const char *);
 extern char *	rtnl_link_get_name(struct rtnl_link *);
 
+extern void	rtnl_link_set_group(struct rtnl_link *, uint32_t);
+extern uint32_t	rtnl_link_get_group(struct rtnl_link *);
+
 extern void	rtnl_link_set_flags(struct rtnl_link *, unsigned int);
 extern void	rtnl_link_unset_flags(struct rtnl_link *, unsigned int);
 extern unsigned int rtnl_link_get_flags(struct rtnl_link *);
@@ -103,9 +173,6 @@
 extern void	rtnl_link_set_txqlen(struct rtnl_link *, unsigned int);
 extern unsigned int rtnl_link_get_txqlen(struct rtnl_link *);
 
-extern void	rtnl_link_set_weight(struct rtnl_link *, unsigned int);
-extern unsigned int rtnl_link_get_weight(struct rtnl_link *);
-
 extern void	rtnl_link_set_ifindex(struct rtnl_link *, int);
 extern int	rtnl_link_get_ifindex(struct rtnl_link *);
 
@@ -127,16 +194,58 @@
 extern void	rtnl_link_set_master(struct rtnl_link *, int);
 extern int	rtnl_link_get_master(struct rtnl_link *);
 
+extern void	rtnl_link_set_carrier(struct rtnl_link *, uint8_t);
+extern uint8_t	rtnl_link_get_carrier(struct rtnl_link *);
+
 extern void	rtnl_link_set_operstate(struct rtnl_link *, uint8_t);
 extern uint8_t	rtnl_link_get_operstate(struct rtnl_link *);
 
 extern void	rtnl_link_set_linkmode(struct rtnl_link *, uint8_t);
 extern uint8_t	rtnl_link_get_linkmode(struct rtnl_link *);
 
-extern uint64_t rtnl_link_get_stat(struct rtnl_link *, int);
+extern const char *	rtnl_link_get_ifalias(struct rtnl_link *);
+extern void		rtnl_link_set_ifalias(struct rtnl_link *, const char *);
 
-extern int	rtnl_link_set_info_type(struct rtnl_link *, const char *);
-extern char *	rtnl_link_get_info_type(struct rtnl_link *);
+extern int		rtnl_link_get_num_vf(struct rtnl_link *, uint32_t *);
+
+extern uint64_t rtnl_link_get_stat(struct rtnl_link *, rtnl_link_stat_id_t);
+extern int	rtnl_link_set_stat(struct rtnl_link *, rtnl_link_stat_id_t,
+				   const uint64_t);
+
+extern int	rtnl_link_set_type(struct rtnl_link *, const char *);
+extern char *	rtnl_link_get_type(struct rtnl_link *);
+
+extern void	rtnl_link_set_promiscuity(struct rtnl_link *, uint32_t);
+extern uint32_t	rtnl_link_get_promiscuity(struct rtnl_link *);
+
+extern void	rtnl_link_set_num_tx_queues(struct rtnl_link *, uint32_t);
+extern uint32_t	rtnl_link_get_num_tx_queues(struct rtnl_link *);
+
+extern void	rtnl_link_set_num_rx_queues(struct rtnl_link *, uint32_t);
+extern uint32_t	rtnl_link_get_num_rx_queues(struct rtnl_link *);
+
+extern struct nl_data *	rtnl_link_get_phys_port_id(struct rtnl_link *);
+
+extern void	rtnl_link_set_ns_fd(struct rtnl_link *, int);
+extern int	rtnl_link_get_ns_fd(struct rtnl_link *);
+extern void	rtnl_link_set_ns_pid(struct rtnl_link *, pid_t);
+extern pid_t	rtnl_link_get_ns_pid(struct rtnl_link *);
+
+extern int	rtnl_link_enslave_ifindex(struct nl_sock *, int, int);
+extern int	rtnl_link_enslave(struct nl_sock *, struct rtnl_link *,
+				  struct rtnl_link *);
+extern int	rtnl_link_release_ifindex(struct nl_sock *, int);
+extern int	rtnl_link_release(struct nl_sock *, struct rtnl_link *);
+extern int	rtnl_link_fill_info(struct nl_msg *, struct rtnl_link *);
+extern int	rtnl_link_info_parse(struct rtnl_link *, struct nlattr **);
+
+
+/* deprecated */
+extern int	rtnl_link_set_info_type(struct rtnl_link *, const char *) __attribute__((deprecated));
+extern char *	rtnl_link_get_info_type(struct rtnl_link *) __attribute__((deprecated));
+extern void	rtnl_link_set_weight(struct rtnl_link *, unsigned int) __attribute__((deprecated));
+extern unsigned int rtnl_link_get_weight(struct rtnl_link *) __attribute__((deprecated));
+
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/link/api.h b/include/netlink/route/link/api.h
new file mode 100644
index 0000000..03b1e5e
--- /dev/null
+++ b/include/netlink/route/link/api.h
@@ -0,0 +1,20 @@
+/*
+ * netlink/route/link/api.h	Link Modules API
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_DUMMY_LINK_API_H_
+#define NETLINK_DUMMY_LINK_API_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#warning "You are including a deprecated header file, include <netlink/route/link.h>."
+
+#endif
diff --git a/include/netlink/route/link/bonding.h b/include/netlink/route/link/bonding.h
new file mode 100644
index 0000000..5c34662
--- /dev/null
+++ b/include/netlink/route/link/bonding.h
@@ -0,0 +1,39 @@
+/*
+ * netlink/route/link/bonding.h		Bonding Interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2011-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_LINK_BONDING_H_
+#define NETLINK_LINK_BONDING_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern struct rtnl_link *rtnl_link_bond_alloc(void);
+
+extern int	rtnl_link_bond_add(struct nl_sock *, const char *,
+				   struct rtnl_link *);
+
+extern int	rtnl_link_bond_enslave_ifindex(struct nl_sock *, int, int);
+extern int	rtnl_link_bond_enslave(struct nl_sock *, struct rtnl_link *,
+				       struct rtnl_link *);
+
+extern int	rtnl_link_bond_release_ifindex(struct nl_sock *, int);
+extern int	rtnl_link_bond_release(struct nl_sock *, struct rtnl_link *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/include/netlink/route/link/bridge.h b/include/netlink/route/link/bridge.h
new file mode 100644
index 0000000..16a4505
--- /dev/null
+++ b/include/netlink/route/link/bridge.h
@@ -0,0 +1,60 @@
+/*
+ * netlink/route/link/bridge.h		Bridge
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_LINK_BRIDGE_H_
+#define NETLINK_LINK_BRIDGE_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Bridge flags
+ * @ingroup bridge
+ */
+enum rtnl_link_bridge_flags {
+	RTNL_BRIDGE_HAIRPIN_MODE	= 0x0001,
+	RTNL_BRIDGE_BPDU_GUARD		= 0x0002,
+	RTNL_BRIDGE_ROOT_BLOCK		= 0x0004,
+	RTNL_BRIDGE_FAST_LEAVE		= 0x0008,
+};
+
+extern struct rtnl_link *rtnl_link_bridge_alloc(void);
+
+extern int	rtnl_link_is_bridge(struct rtnl_link *);
+extern int	rtnl_link_bridge_has_ext_info(struct rtnl_link *);
+
+extern int	rtnl_link_bridge_set_port_state(struct rtnl_link *, uint8_t );
+extern int	rtnl_link_bridge_get_port_state(struct rtnl_link *);
+
+extern int	rtnl_link_bridge_set_priority(struct rtnl_link *, uint16_t);
+extern int	rtnl_link_bridge_get_priority(struct rtnl_link *);
+
+extern int	rtnl_link_bridge_set_cost(struct rtnl_link *, uint32_t);
+extern int	rtnl_link_bridge_get_cost(struct rtnl_link *, uint32_t *);
+
+extern int	rtnl_link_bridge_unset_flags(struct rtnl_link *, unsigned int);
+extern int	rtnl_link_bridge_set_flags(struct rtnl_link *, unsigned int);
+extern int	rtnl_link_bridge_get_flags(struct rtnl_link *);
+
+extern char * rtnl_link_bridge_flags2str(int, char *, size_t);
+extern int	rtnl_link_bridge_str2flags(const char *);
+
+extern int	rtnl_link_bridge_add(struct nl_sock *sk, const char *name);
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/include/netlink/route/link/can.h b/include/netlink/route/link/can.h
new file mode 100644
index 0000000..61c9f47
--- /dev/null
+++ b/include/netlink/route/link/can.h
@@ -0,0 +1,60 @@
+/*
+ * netlink/route/link/can.h		CAN interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2012 Benedikt Spranger <b.spranger@linutronix.de>
+ */
+
+#ifndef NETLINK_LINK_CAN_H_
+#define NETLINK_LINK_CAN_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+#include <linux/can/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int rtnl_link_is_can(struct rtnl_link *link);
+
+extern char *rtnl_link_can_ctrlmode2str(int, char *, size_t);
+extern int rtnl_link_can_str2ctrlmode(const char *);
+
+extern int rtnl_link_can_restart(struct rtnl_link *);
+extern int rtnl_link_can_freq(struct rtnl_link *, uint32_t *);
+extern int rtnl_link_can_state(struct rtnl_link *, uint32_t *);
+
+extern int rtnl_link_can_berr_rx(struct rtnl_link *);
+extern int rtnl_link_can_berr_tx(struct rtnl_link *);
+extern int rtnl_link_can_berr(struct rtnl_link *, struct can_berr_counter *);
+
+extern int rtnl_link_can_get_bt_const(struct rtnl_link *,
+				      struct can_bittiming_const *);
+extern int rtnl_link_can_get_bittiming(struct rtnl_link *,
+				       struct can_bittiming *);
+extern int rtnl_link_can_set_bittiming(struct rtnl_link *,
+				       struct can_bittiming *);
+
+extern int rtnl_link_can_get_bitrate(struct rtnl_link *, uint32_t *);
+extern int rtnl_link_can_set_bitrate(struct rtnl_link *, uint32_t);
+
+extern int rtnl_link_can_get_sample_point(struct rtnl_link *, uint32_t *);
+extern int rtnl_link_can_set_sample_point(struct rtnl_link *, uint32_t);
+
+extern int rtnl_link_can_get_restart_ms(struct rtnl_link *, uint32_t *);
+extern int rtnl_link_can_set_restart_ms(struct rtnl_link *, uint32_t);
+
+extern int rtnl_link_can_get_ctrlmode(struct rtnl_link *, uint32_t *);
+extern int rtnl_link_can_set_ctrlmode(struct rtnl_link *, uint32_t);
+extern int rtnl_link_can_unset_ctrlmode(struct rtnl_link *, uint32_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/link/inet.h b/include/netlink/route/link/inet.h
new file mode 100644
index 0000000..506542f
--- /dev/null
+++ b/include/netlink/route/link/inet.h
@@ -0,0 +1,33 @@
+/*
+ * netlink/route/link/inet.h	INET Link Module
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_LINK_INET_H_
+#define NETLINK_LINK_INET_H_
+
+#include <netlink/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const char *	rtnl_link_inet_devconf2str(int, char *, size_t);
+extern int		rtnl_link_inet_str2devconf(const char *);
+
+extern int		rtnl_link_inet_get_conf(struct rtnl_link *,
+						const unsigned int, uint32_t *);
+extern int		rtnl_link_inet_set_conf(struct rtnl_link *,
+						const unsigned int, uint32_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/link/info-api.h b/include/netlink/route/link/info-api.h
index 7e18e31..1087ad4 100644
--- a/include/netlink/route/link/info-api.h
+++ b/include/netlink/route/link/info-api.h
@@ -1,71 +1,20 @@
 /*
- * netlink/route/link/info-api.h	Link Info API
+ * netlink/route/link/info-api.h	Link Modules API
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2013 Thomas Graf <tgraf@suug.ch>
  */
 
-#ifndef NETLINK_LINK_INFO_API_H_
-#define NETLINK_LINK_INFO_API_H_
+#ifndef NETLINK_DUMMY_LINK_INFO_API_H_
+#define NETLINK_DUMMY_LINK_INFO_API_H_
 
 #include <netlink/netlink.h>
+#include <netlink/route/link.h>
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * @ingroup link_info
- *
- * Link info operations
- */
-struct rtnl_link_info_ops
-{
-	/** Name of operations, must match name on kernel side */
-	char *		io_name;
-
-	/** Reference count (internal, do not use) */
-	int		io_refcnt;
-
-	/** Called to assign an info type to a link.
-	 * Has to allocate enough resources to hold attributes. Can
-	 * use link->l_info to store a pointer. */
-	int	      (*io_alloc)(struct rtnl_link *);
-
-	/** Called to parse the link info attribute.
-	 * Must parse the attribute and assign all values to the link.
-	 */
-	int	      (*io_parse)(struct rtnl_link *,
-				  struct nlattr *,
-				  struct nlattr *);
-
-	/** Called when the link object is dumped.
-	 * Must dump the info type specific attributes. */
-	void	      (*io_dump[NL_DUMP_MAX+1])(struct rtnl_link *,
-						struct nl_dump_params *);
-
-	/** Called when a link object is cloned.
-	 * Must clone all info type specific attributes. */
-	int	      (*io_clone)(struct rtnl_link *, struct rtnl_link *);
-
-	/** Called when construction a link netlink message.
-	 * Must append all info type specific attributes to the message. */
-	int	      (*io_put_attrs)(struct nl_msg *, struct rtnl_link *);
-
-	/** Called to release all resources previously allocated
-	 * in either io_alloc() or io_parse(). */
-	void	      (*io_free)(struct rtnl_link *);
-
-	struct rtnl_link_info_ops *	io_next;
-};
-
-extern struct rtnl_link_info_ops *rtnl_link_info_ops_lookup(const char *);
-
-extern int			rtnl_link_register_info(struct rtnl_link_info_ops *);
-extern int			rtnl_link_unregister_info(struct rtnl_link_info_ops *);
+#warning "You are including a deprecated header file, include <netlink/route/link.h>."
 
 #endif
diff --git a/include/netlink/route/link/ip6tnl.h b/include/netlink/route/link/ip6tnl.h
new file mode 100644
index 0000000..7e0c295
--- /dev/null
+++ b/include/netlink/route/link/ip6tnl.h
@@ -0,0 +1,56 @@
+/*
+ * netlink/route/link/ip6tnl.h		IP6TNL interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2014 Susant Sahani <susant@redhat.com>
+ */
+
+#ifndef NETLINK_LINK_IP6TNL_H_
+#define NETLINK_LINK_IP6TNL_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+	extern struct rtnl_link *rtnl_link_ip6_tnl_alloc(void);
+	extern int rtnl_link_ip6_tnl_add(struct nl_sock *sk, const char *name);
+
+	extern int rtnl_link_ip6_tnl_set_link(struct rtnl_link *link,  uint32_t index);
+	extern uint32_t rtnl_link_ip6_tnl_get_link(struct rtnl_link *link);
+
+	extern int rtnl_link_ip6_tnl_set_local(struct rtnl_link *link, struct in6_addr *);
+	extern int rtnl_link_ip6_tnl_get_local(struct rtnl_link *link, struct in6_addr *addr);
+
+	extern int rtnl_link_ip6_tnl_set_remote(struct rtnl_link *link, struct in6_addr *);
+	extern int rtnl_link_ip6_tnl_get_remote(struct rtnl_link *link, struct in6_addr *);
+
+	extern int rtnl_link_ip6_tnl_set_ttl(struct rtnl_link *link, uint8_t ttl);
+	extern uint8_t rtnl_link_ip6_tnl_get_ttl(struct rtnl_link *link);
+
+	extern int rtnl_link_ip6_tnl_set_tos(struct rtnl_link *link, uint8_t tos);
+	extern uint8_t rtnl_link_ip6_tnl_get_tos(struct rtnl_link *link);
+
+	extern int rtnl_link_ip6_tnl_set_encaplimit(struct rtnl_link *link, uint8_t encap_limit);
+	extern uint8_t rtnl_link_ip6_tnl_get_encaplimit(struct rtnl_link *link);
+
+	extern int rtnl_link_ip6_tnl_set_flags(struct rtnl_link *link, uint32_t flags);
+	extern uint32_t rtnl_link_ip6_tnl_get_flags(struct rtnl_link *link);
+
+	extern uint32_t rtnl_link_ip6_tnl_get_flowinfo(struct rtnl_link *link);
+	extern int rtnl_link_ip6_tnl_set_flowinfo(struct rtnl_link *link, uint32_t flowinfo);
+
+	extern int rtnl_link_ip6_tnl_set_proto(struct rtnl_link *link, uint8_t proto);
+	extern uint8_t rtnl_link_ip6_tnl_get_proto(struct rtnl_link *link);
+
+#ifdef _cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/link/ipgre.h b/include/netlink/route/link/ipgre.h
new file mode 100644
index 0000000..5a0a295
--- /dev/null
+++ b/include/netlink/route/link/ipgre.h
@@ -0,0 +1,59 @@
+/*
+ * netlink/route/link/ip_gre.h		IPGRE interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2014 Susant Sahani <susant@redhat.com>
+ */
+
+#ifndef NETLINK_LINK_IPGRE_H_
+#define NETLINK_LINK_IPGRE_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+	extern struct rtnl_link *rtnl_link_ipgre_alloc(void);
+	extern int rtnl_link_ipgre_add(struct nl_sock *sk, const char *name);
+
+	extern int rtnl_link_ipgre_set_link(struct rtnl_link *link,  uint32_t index);
+	extern uint32_t rtnl_link_ipgre_get_link(struct rtnl_link *link);
+
+	extern int rtnl_link_ipgre_set_iflags(struct rtnl_link *link, uint16_t iflags);
+	extern uint16_t rtnl_link_ipgre_get_iflags(struct rtnl_link *link);
+
+	extern int rtnl_link_ipgre_set_oflags(struct rtnl_link *link, uint16_t oflags);
+	extern uint16_t rtnl_link_ipgre_get_oflags(struct rtnl_link *link);
+
+	extern int rtnl_link_ipgre_set_ikey(struct rtnl_link *link, uint32_t ikey);
+	extern uint32_t rtnl_link_ipgre_get_ikey(struct rtnl_link *link);
+
+	extern int rtnl_link_ipgre_set_okey(struct rtnl_link *link, uint32_t okey);
+	extern uint32_t rtnl_link_ipgre_get_okey(struct rtnl_link *link);
+
+	extern int rtnl_link_ipgre_set_local(struct rtnl_link *link, uint32_t addr);
+	extern uint32_t rtnl_link_ipgre_get_local(struct rtnl_link *link);
+
+	extern int rtnl_link_ipgre_set_remote(struct rtnl_link *link, uint32_t addr);
+	extern uint32_t rtnl_link_ipgre_get_remote(struct rtnl_link *link);
+
+	extern int rtnl_link_ipgre_set_ttl(struct rtnl_link *link, uint8_t ttl);
+	extern uint8_t rtnl_link_ipgre_get_ttl(struct rtnl_link *link);
+
+	extern int rtnl_link_ipgre_set_tos(struct rtnl_link *link, uint8_t tos);
+	extern uint8_t rtnl_link_ipgre_get_tos(struct rtnl_link *link);
+
+	extern int rtnl_link_ipgre_set_pmtudisc(struct rtnl_link *link, uint8_t pmtudisc);
+	extern uint8_t rtnl_link_ipgre_get_pmtudisc(struct rtnl_link *link);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/link/ipip.h b/include/netlink/route/link/ipip.h
new file mode 100644
index 0000000..ccadb87
--- /dev/null
+++ b/include/netlink/route/link/ipip.h
@@ -0,0 +1,47 @@
+/*
+ * netlink/route/link/ipip.h            IPIP interface
+ *
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation version 2.1
+ *      of the License.
+ *
+ * Copyright (c) 2014 Susant Sahani <susant@redhat.com>
+ */
+
+#ifndef NETLINK_LINK_IPIP_H_
+#define NETLINK_LINK_IPIP_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+	extern struct rtnl_link *rtnl_link_ipip_alloc(void);
+	extern int rtnl_link_ipip_add(struct nl_sock *sk, const char *name);
+
+	extern uint32_t rtnl_link_ipip_get_link(struct rtnl_link *link);
+	extern int rtnl_link_ipip_set_link(struct rtnl_link *link,  uint32_t index);
+
+	extern int rtnl_link_ipip_set_local(struct rtnl_link *link, uint32_t addr);
+	extern uint32_t rtnl_link_ipip_get_local(struct rtnl_link *link);
+
+	extern int rtnl_link_ipip_set_remote(struct rtnl_link *link, uint32_t addr);
+	extern uint32_t rtnl_link_ipip_get_remote(struct rtnl_link *link);
+
+	extern int rtnl_link_ipip_set_ttl(struct rtnl_link *link, uint8_t ttl);
+	extern uint8_t rtnl_link_ipip_get_ttl(struct rtnl_link *link);
+
+	extern int rtnl_link_ipip_set_tos(struct rtnl_link *link, uint8_t tos);
+	extern uint8_t rtnl_link_ipip_get_tos(struct rtnl_link *link);
+
+	extern int rtnl_link_ipip_set_pmtudisc(struct rtnl_link *link, uint8_t pmtudisc);
+	extern uint8_t rtnl_link_ipip_get_pmtudisc(struct rtnl_link *link);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/link/ipvti.h b/include/netlink/route/link/ipvti.h
new file mode 100644
index 0000000..a3e7bba
--- /dev/null
+++ b/include/netlink/route/link/ipvti.h
@@ -0,0 +1,43 @@
+/*
+ * netlink/route/link/ipvti.h		IPVTI interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2014 Susant Sahani <susant@redhat.com>
+ */
+
+#ifndef NETLINK_LINK_IPVTI_H_
+#define NETLINK_LINK_IPVTI_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+	extern struct rtnl_link *rtnl_link_ipvti_alloc(void);
+	extern int rtnl_link_ipvti_add(struct nl_sock *sk, const char *name);
+
+	extern int rtnl_link_ipvti_set_link(struct rtnl_link *link,  uint32_t index);
+	extern uint32_t rtnl_link_ipvti_get_link(struct rtnl_link *link);
+
+	extern int rtnl_link_ipvti_set_ikey(struct rtnl_link *link, uint32_t ikey);
+	extern uint32_t rtnl_link_get_ikey(struct rtnl_link *link);
+
+	extern int rtnl_link_ipvti_set_okey(struct rtnl_link *link, uint32_t okey);
+	extern uint32_t rtnl_link_get_okey(struct rtnl_link *link);
+
+	extern int rtnl_link_ipvti_set_local(struct rtnl_link *link, uint32_t addr);
+	extern uint32_t rtnl_link_get_local(struct rtnl_link *link);
+
+	extern int rtnl_link_ipvti_set_remote(struct rtnl_link *link, uint32_t addr);
+	extern uint32_t rtnl_link_get_remote(struct rtnl_link *link);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/link/macvlan.h b/include/netlink/route/link/macvlan.h
new file mode 100644
index 0000000..2207c53
--- /dev/null
+++ b/include/netlink/route/link/macvlan.h
@@ -0,0 +1,46 @@
+/*
+ * netlink/route/link/macvlan.h		MACVLAN interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Michael Braun <michael-dev@fami-braun.de>
+ */
+
+#ifndef NETLINK_LINK_MACVLAN_H_
+#define NETLINK_LINK_MACVLAN_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern struct rtnl_link *rtnl_link_macvlan_alloc(void);
+
+extern int		rtnl_link_is_macvlan(struct rtnl_link *);
+
+extern char *		rtnl_link_macvlan_mode2str(int, char *, size_t);
+extern int		rtnl_link_macvlan_str2mode(const char *);
+
+extern char *		rtnl_link_macvlan_flags2str(int, char *, size_t);
+extern int		rtnl_link_macvlan_str2flags(const char *);
+
+extern int		rtnl_link_macvlan_set_mode(struct rtnl_link *,
+			                           uint32_t);
+extern uint32_t		rtnl_link_macvlan_get_mode(struct rtnl_link *);
+
+extern int		rtnl_link_macvlan_set_flags(struct rtnl_link *,
+						 uint16_t);
+extern int		rtnl_link_macvlan_unset_flags(struct rtnl_link *,
+						   uint16_t);
+extern uint16_t		rtnl_link_macvlan_get_flags(struct rtnl_link *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/link/sit.h b/include/netlink/route/link/sit.h
new file mode 100644
index 0000000..84dc44a
--- /dev/null
+++ b/include/netlink/route/link/sit.h
@@ -0,0 +1,53 @@
+/*
+ * netlink/route/link/sit.h		SIT interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2014 Susant Sahani <susant@redhat.com>
+ */
+
+#ifndef NETLINK_LINK_SIT_H_
+#define NETLINK_LINK_SIT_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+	extern struct rtnl_link *rtnl_link_sit_alloc(void);
+	extern int rtnl_link_sit_add(struct nl_sock *sk, const char *name);
+
+	extern int rtnl_link_sit_set_link(struct rtnl_link *link,  uint32_t index);
+	extern uint32_t rtnl_link_sit_get_link(struct rtnl_link *link);
+
+	extern int rtnl_link_sit_set_local(struct rtnl_link *link, uint32_t addr);
+	extern uint32_t rtnl_link_get_sit_local(struct rtnl_link *link);
+
+	extern int rtnl_link_sit_set_remote(struct rtnl_link *link, uint32_t addr);
+	extern uint32_t rtnl_link_sit_get_remote(struct rtnl_link *link);
+
+	extern int rtnl_link_sit_set_ttl(struct rtnl_link *link, uint8_t ttl);
+	extern uint8_t rtnl_link_sit_get_ttl(struct rtnl_link *link);
+
+	extern int rtnl_link_sit_set_tos(struct rtnl_link *link, uint8_t tos);
+	extern uint8_t rtnl_link_sit_get_tos(struct rtnl_link *link);
+
+	extern int rtnl_link_sit_set_pmtudisc(struct rtnl_link *link, uint8_t pmtudisc);
+	extern uint8_t rtnl_link_sit_get_pmtudisc(struct rtnl_link *link);
+
+	extern int rtnl_link_sit_set_flags(struct rtnl_link *link, uint16_t flags);
+	extern uint16_t rtnl_link_sit_get_flags(struct rtnl_link *link);
+
+	int rtnl_link_sit_set_proto(struct rtnl_link *link, uint8_t proto);
+	uint8_t rtnl_link_get_proto(struct rtnl_link *link);
+
+#ifdef _cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/link/veth.h b/include/netlink/route/link/veth.h
new file mode 100644
index 0000000..35c2345c
--- /dev/null
+++ b/include/netlink/route/link/veth.h
@@ -0,0 +1,36 @@
+/*
+ * netlink/route/link/veth.h		VETH interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+#ifndef NETLINK_LINK_VETH_H_
+#define NETLINK_LINK_VETH_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern struct rtnl_link *rtnl_link_veth_alloc(void);
+extern void rtnl_link_veth_release(struct rtnl_link *);
+
+extern int rtnl_link_is_veth(struct rtnl_link *);
+
+extern struct rtnl_link *rtnl_link_veth_get_peer(struct rtnl_link *);
+extern int rtnl_link_veth_add(struct nl_sock *sock, const char *name,
+			      const char *peer, pid_t pid);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/link/vlan.h b/include/netlink/route/link/vlan.h
index a3ad76d..4ec751e 100644
--- a/include/netlink/route/link/vlan.h
+++ b/include/netlink/route/link/vlan.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_LINK_VLAN_H_
@@ -27,17 +27,24 @@
 
 #define VLAN_PRIO_MAX 7
 
+extern struct rtnl_link *rtnl_link_vlan_alloc(void);
+
+extern int		rtnl_link_is_vlan(struct rtnl_link *);
+
 extern char *		rtnl_link_vlan_flags2str(int, char *, size_t);
 extern int		rtnl_link_vlan_str2flags(const char *);
 
-extern int		rtnl_link_vlan_set_id(struct rtnl_link *, int);
+extern int		rtnl_link_vlan_set_protocol(struct rtnl_link *link, uint16_t);
+extern int		rtnl_link_vlan_get_protocol(struct rtnl_link *link);
+
+extern int		rtnl_link_vlan_set_id(struct rtnl_link *, uint16_t);
 extern int		rtnl_link_vlan_get_id(struct rtnl_link *);
 
 extern int		rtnl_link_vlan_set_flags(struct rtnl_link *,
 						 unsigned int);
 extern int		rtnl_link_vlan_unset_flags(struct rtnl_link *,
 						   unsigned int);
-extern unsigned int	rtnl_link_vlan_get_flags(struct rtnl_link *);
+extern int		rtnl_link_vlan_get_flags(struct rtnl_link *);
 
 extern int		rtnl_link_vlan_set_ingress_map(struct rtnl_link *,
 						       int, uint32_t);
diff --git a/include/netlink/route/link/vxlan.h b/include/netlink/route/link/vxlan.h
new file mode 100644
index 0000000..f7f7b60
--- /dev/null
+++ b/include/netlink/route/link/vxlan.h
@@ -0,0 +1,86 @@
+/*
+ * netlink/route/link/vxlan.h		VXLAN interface
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Yasunobu Chiba <yasu@dsl.gr.jp>
+ */
+
+#ifndef NETLINK_LINK_VXLAN_H_
+#define NETLINK_LINK_VXLAN_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define VXLAN_ID_MAX 16777215
+
+extern struct rtnl_link *rtnl_link_vxlan_alloc(void);
+
+extern int		rtnl_link_is_vxlan(struct rtnl_link *);
+
+extern int		rtnl_link_vxlan_set_id(struct rtnl_link *, uint32_t);
+extern int		rtnl_link_vxlan_get_id(struct rtnl_link *, uint32_t *);
+
+extern int	rtnl_link_vxlan_set_group(struct rtnl_link *, struct nl_addr *);
+extern int	rtnl_link_vxlan_get_group(struct rtnl_link *, struct nl_addr **);
+
+extern int		rtnl_link_vxlan_set_link(struct rtnl_link *, uint32_t);
+extern int		rtnl_link_vxlan_get_link(struct rtnl_link *, uint32_t *);
+
+extern int	rtnl_link_vxlan_set_local(struct rtnl_link *, struct nl_addr *);
+extern int	rtnl_link_vxlan_get_local(struct rtnl_link *, struct nl_addr **);
+
+extern int	rtnl_link_vxlan_set_ttl(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_ttl(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_tos(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_tos(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_learning(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_learning(struct rtnl_link *);
+extern int	rtnl_link_vxlan_enable_learning(struct rtnl_link *);
+extern int	rtnl_link_vxlan_disable_learning(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_ageing(struct rtnl_link *, uint32_t);
+extern int	rtnl_link_vxlan_get_ageing(struct rtnl_link *, uint32_t *);
+
+extern int	rtnl_link_vxlan_set_limit(struct rtnl_link *, uint32_t);
+extern int	rtnl_link_vxlan_get_limit(struct rtnl_link *, uint32_t *);
+
+extern int	rtnl_link_vxlan_set_port_range(struct rtnl_link *,
+										   struct ifla_vxlan_port_range *);
+extern int	rtnl_link_vxlan_get_port_range(struct rtnl_link *,
+										   struct ifla_vxlan_port_range *);
+
+extern int	rtnl_link_vxlan_set_proxy(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_proxy(struct rtnl_link *);
+extern int	rtnl_link_vxlan_enable_proxy(struct rtnl_link *);
+extern int	rtnl_link_vxlan_disable_proxy(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_rsc(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_rsc(struct rtnl_link *);
+extern int	rtnl_link_vxlan_enable_rsc(struct rtnl_link *);
+extern int	rtnl_link_vxlan_disable_rsc(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_l2miss(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_l2miss(struct rtnl_link *);
+extern int	rtnl_link_vxlan_enable_l2miss(struct rtnl_link *);
+extern int	rtnl_link_vxlan_disable_l2miss(struct rtnl_link *);
+
+extern int	rtnl_link_vxlan_set_l3miss(struct rtnl_link *, uint8_t);
+extern int	rtnl_link_vxlan_get_l3miss(struct rtnl_link *);
+extern int	rtnl_link_vxlan_enable_l3miss(struct rtnl_link *);
+extern int	rtnl_link_vxlan_disable_l3miss(struct rtnl_link *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/neighbour.h b/include/netlink/route/neighbour.h
index 698539a..1d1179b 100644
--- a/include/netlink/route/neighbour.h
+++ b/include/netlink/route/neighbour.h
@@ -29,6 +29,8 @@
 extern struct rtnl_neigh *rtnl_neigh_get(struct nl_cache *, int,
 					       struct nl_addr *);
 
+extern int      rtnl_neigh_parse(struct nlmsghdr *, struct rtnl_neigh **);
+
 extern char *	rtnl_neigh_state2str(int, char *, size_t);
 extern int	rtnl_neigh_str2state(const char *);
 
diff --git a/include/netlink/route/pktloc.h b/include/netlink/route/pktloc.h
index 28e1dc2..c3768ce 100644
--- a/include/netlink/route/pktloc.h
+++ b/include/netlink/route/pktloc.h
@@ -25,17 +25,22 @@
 struct rtnl_pktloc
 {
 	char *			name;
-	uint8_t			align:4;
-	uint8_t			layer:4;
-	uint8_t			flags;
+	uint8_t			layer;
+	uint8_t			shift;
 	uint16_t		offset;
+	uint16_t		align;
 	uint32_t		mask;
+	uint32_t		refcnt;
 
 	struct nl_list_head	list;
 };
 
-extern int rtnl_pktloc_lookup(const char *, struct rtnl_pktloc **);
-
+extern int	rtnl_pktloc_lookup(const char *, struct rtnl_pktloc **);
+extern struct rtnl_pktloc *rtnl_pktloc_alloc(void);
+extern void	rtnl_pktloc_put(struct rtnl_pktloc *);
+extern int	rtnl_pktloc_add(struct rtnl_pktloc *);
+extern void	rtnl_pktloc_foreach(void (*cb)(struct rtnl_pktloc *, void *),
+				    void *);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/qdisc-modules.h b/include/netlink/route/qdisc-modules.h
deleted file mode 100644
index 769625e..0000000
--- a/include/netlink/route/qdisc-modules.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * netlink/route/qdisc-modules.h       Qdisc Module API
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
- */
-
-#ifndef NETLINK_QDISC_MODULES_H_
-#define NETLINK_QDISC_MODULES_H_
-
-#include <netlink/netlink.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Qdisc Operations
- * @ingroup qdisc
- */
-struct rtnl_qdisc_ops
-{
-	/**
-	 * Kind/Name of Qdisc
-	 */
-	char qo_kind[32];
-
-	/**
-	 * Dump callbacks
-	 */
-	void  (*qo_dump[NL_DUMP_MAX+1])(struct rtnl_qdisc *,
-					struct nl_dump_params *);
-
-	/**
-	 * Must return the contents supposed to be in TCA_OPTIONS
-	 */
-	struct nl_msg *(*qo_get_opts)(struct rtnl_qdisc *);
-
-	int (*qo_build_msg)(struct rtnl_qdisc *, struct nl_msg *);
-
-	/**
-	 * TCA_OPTIONS message parser
-	 */
-	int  (*qo_msg_parser)(struct rtnl_qdisc *);
-
-	/**
-	 * Called before a Qdisc object gets destroyed
-	 */
-	void (*qo_free_data)(struct rtnl_qdisc *);
-
-	/**
-	 * Called whenever a qdisc object needs to be cloned
-	 */
-	int  (*qo_clone)(struct rtnl_qdisc *, struct rtnl_qdisc *);
-
-	/**
-	 * INTERNAL (Do not use)
-	 */
-	struct rtnl_qdisc_ops *qo_next;
-};
-
-extern int			rtnl_qdisc_register(struct rtnl_qdisc_ops *);
-extern int			rtnl_qdisc_unregister(struct rtnl_qdisc_ops *);
-extern struct rtnl_qdisc_ops *	rtnl_qdisc_lookup_ops(struct rtnl_qdisc *);
-extern struct rtnl_qdisc_ops *	__rtnl_qdisc_lookup_ops(const char *);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/include/netlink/route/qdisc.h b/include/netlink/route/qdisc.h
index 5acd6e1..10b85c5 100644
--- a/include/netlink/route/qdisc.h
+++ b/include/netlink/route/qdisc.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_QDISC_H_
@@ -22,49 +22,49 @@
 
 struct rtnl_qdisc;
 
-extern struct nl_object_ops qdisc_obj_ops;
-
-extern struct rtnl_qdisc *rtnl_qdisc_alloc(void);
+extern struct rtnl_qdisc *
+		rtnl_qdisc_alloc(void);
 extern void	rtnl_qdisc_put(struct rtnl_qdisc *);
 
 extern int	rtnl_qdisc_alloc_cache(struct nl_sock *, struct nl_cache **);
-extern struct rtnl_qdisc *rtnl_qdisc_get(struct nl_cache *, int, uint32_t);
-extern struct rtnl_qdisc *rtnl_qdisc_get_by_parent(struct nl_cache *,
-						   int, uint32_t);
+
+extern struct rtnl_qdisc *
+		rtnl_qdisc_get(struct nl_cache *, int, uint32_t);
+
+extern struct rtnl_qdisc *
+		rtnl_qdisc_get_by_parent(struct nl_cache *, int, uint32_t);
 
 extern int	rtnl_qdisc_build_add_request(struct rtnl_qdisc *, int,
 					     struct nl_msg **);
 extern int	rtnl_qdisc_add(struct nl_sock *, struct rtnl_qdisc *, int);
 
-extern int	rtnl_qdisc_build_change_request(struct rtnl_qdisc *,
+extern int	rtnl_qdisc_build_update_request(struct rtnl_qdisc *,
 						struct rtnl_qdisc *,
-						struct nl_msg **);
-extern int	rtnl_qdisc_change(struct nl_sock *, struct rtnl_qdisc *,
-				  struct rtnl_qdisc *);
+						int, struct nl_msg **);
+
+extern int	rtnl_qdisc_update(struct nl_sock *, struct rtnl_qdisc *,
+				  struct rtnl_qdisc *, int);
 
 extern int	rtnl_qdisc_build_delete_request(struct rtnl_qdisc *,
 						struct nl_msg **);
 extern int	rtnl_qdisc_delete(struct nl_sock *, struct rtnl_qdisc *);
 
-extern void	rtnl_qdisc_set_ifindex(struct rtnl_qdisc *, int);
-extern int	rtnl_qdisc_get_ifindex(struct rtnl_qdisc *);
-extern void	rtnl_qdisc_set_handle(struct rtnl_qdisc *, uint32_t);
-extern uint32_t	rtnl_qdisc_get_handle(struct rtnl_qdisc *);
-extern void	rtnl_qdisc_set_parent(struct rtnl_qdisc *, uint32_t);
-extern uint32_t	rtnl_qdisc_get_parent(struct rtnl_qdisc *);
-extern void	rtnl_qdisc_set_kind(struct rtnl_qdisc *, const char *);
-extern char *	rtnl_qdisc_get_kind(struct rtnl_qdisc *);
-extern uint64_t	rtnl_qdisc_get_stat(struct rtnl_qdisc *, enum rtnl_tc_stats_id);
+/* Deprecated functions */
+extern void rtnl_qdisc_foreach_child(struct rtnl_qdisc *, struct nl_cache *,
+				     void (*cb)(struct nl_object *, void *),
+				     void *) __attribute__ ((deprecated));
 
-extern void	rtnl_qdisc_foreach_child(struct rtnl_qdisc *, struct nl_cache *,
-					 void (*cb)(struct nl_object *, void *),
-					 void *);
+extern void rtnl_qdisc_foreach_cls(struct rtnl_qdisc *, struct nl_cache *,
+				   void (*cb)(struct nl_object *, void *),
+				   void *) __attribute__ ((deprecated));
 
-extern void	rtnl_qdisc_foreach_cls(struct rtnl_qdisc *, struct nl_cache *,
-				       void (*cb)(struct nl_object *, void *),
-				       void *);
+extern int rtnl_qdisc_build_change_request(struct rtnl_qdisc *,
+					   struct rtnl_qdisc *,
+					   struct nl_msg **)
+					   __attribute__ ((deprecated));
 
-extern struct nl_msg *	rtnl_qdisc_get_opts(struct rtnl_qdisc *);
+extern int rtnl_qdisc_change(struct nl_sock *, struct rtnl_qdisc *,
+			     struct rtnl_qdisc *) __attribute__ ((deprecated));
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/sch/cbq.h b/include/netlink/route/qdisc/cbq.h
similarity index 100%
rename from include/netlink/route/sch/cbq.h
rename to include/netlink/route/qdisc/cbq.h
diff --git a/include/netlink/route/sch/dsmark.h b/include/netlink/route/qdisc/dsmark.h
similarity index 79%
rename from include/netlink/route/sch/dsmark.h
rename to include/netlink/route/qdisc/dsmark.h
index de65496..06bd9d3 100644
--- a/include/netlink/route/sch/dsmark.h
+++ b/include/netlink/route/qdisc/dsmark.h
@@ -6,20 +6,22 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_DSMARK_H_
 #define NETLINK_DSMARK_H_
 
 #include <netlink/netlink.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/class.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-extern int	rtnl_class_dsmark_set_bmask(struct rtnl_class *, uint8_t);
-extern int	rtnl_class_dsmark_get_bmask(struct rtnl_class *);
+extern int	rtnl_class_dsmark_set_bitmask(struct rtnl_class *, uint8_t);
+extern int	rtnl_class_dsmark_get_bitmask(struct rtnl_class *);
 
 extern int	rtnl_class_dsmark_set_value(struct rtnl_class *, uint8_t);
 extern int	rtnl_class_dsmark_get_value(struct rtnl_class *);
diff --git a/include/netlink/route/sch/fifo.h b/include/netlink/route/qdisc/fifo.h
similarity index 94%
rename from include/netlink/route/sch/fifo.h
rename to include/netlink/route/qdisc/fifo.h
index c18dd79..c033427 100644
--- a/include/netlink/route/sch/fifo.h
+++ b/include/netlink/route/qdisc/fifo.h
@@ -13,6 +13,7 @@
 #define NETLINK_FIFO_H_
 
 #include <netlink/netlink.h>
+#include <netlink/route/qdisc.h>
 
 #ifdef __cplusplus
 extern "C" {
diff --git a/include/netlink/route/qdisc/fq_codel.h b/include/netlink/route/qdisc/fq_codel.h
new file mode 100644
index 0000000..d2c3d25
--- /dev/null
+++ b/include/netlink/route/qdisc/fq_codel.h
@@ -0,0 +1,44 @@
+/*
+ * netlink/route/sch/fq_codel.h	fq_codel
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+#ifndef NETLINK_FQ_CODEL_H_
+#define NETLINK_FQ_CODEL_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/qdisc.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int	rtnl_qdisc_fq_codel_set_limit(struct rtnl_qdisc *, int);
+extern int	rtnl_qdisc_fq_codel_get_limit(struct rtnl_qdisc *);
+
+extern int	rtnl_qdisc_fq_codel_set_target(struct rtnl_qdisc *, uint32_t);
+extern uint32_t rtnl_qdisc_fq_codel_get_target(struct rtnl_qdisc *);
+
+extern int	rtnl_qdisc_fq_codel_set_interval(struct rtnl_qdisc *, uint32_t);
+extern uint32_t rtnl_qdisc_fq_codel_get_interval(struct rtnl_qdisc *);
+
+extern int	rtnl_qdisc_fq_codel_set_quantum(struct rtnl_qdisc *, uint32_t);
+extern uint32_t rtnl_qdisc_fq_codel_get_quantum(struct rtnl_qdisc *);
+
+extern int	rtnl_qdisc_fq_codel_set_flows(struct rtnl_qdisc *, int);
+extern int	rtnl_qdisc_fq_codel_get_flows(struct rtnl_qdisc *);
+
+extern int	rtnl_qdisc_fq_codel_set_ecn(struct rtnl_qdisc *, int);
+extern int	rtnl_qdisc_fq_codel_get_ecn(struct rtnl_qdisc *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/qdisc/htb.h b/include/netlink/route/qdisc/htb.h
new file mode 100644
index 0000000..c909f84
--- /dev/null
+++ b/include/netlink/route/qdisc/htb.h
@@ -0,0 +1,49 @@
+/*
+ * netlink/route/sch/htb.h	HTB Qdisc
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2005 Petr Gotthard <petr.gotthard@siemens.com>
+ * Copyright (c) 2005 Siemens AG Oesterreich
+ */
+
+#ifndef NETLINK_HTB_H_
+#define NETLINK_HTB_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/tc.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/class.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern uint32_t	rtnl_htb_get_rate2quantum(struct rtnl_qdisc *);
+extern int	rtnl_htb_set_rate2quantum(struct rtnl_qdisc *, uint32_t);
+extern uint32_t	rtnl_htb_get_defcls(struct rtnl_qdisc *);
+extern int	rtnl_htb_set_defcls(struct rtnl_qdisc *, uint32_t);
+
+extern uint32_t	rtnl_htb_get_prio(struct rtnl_class *);
+extern int	rtnl_htb_set_prio(struct rtnl_class *, uint32_t);
+extern uint32_t	rtnl_htb_get_rate(struct rtnl_class *);
+extern int	rtnl_htb_set_rate(struct rtnl_class *, uint32_t);
+extern uint32_t	rtnl_htb_get_ceil(struct rtnl_class *);
+extern int	rtnl_htb_set_ceil(struct rtnl_class *, uint32_t);
+extern uint32_t	rtnl_htb_get_rbuffer(struct rtnl_class *);
+extern int	rtnl_htb_set_rbuffer(struct rtnl_class *, uint32_t);
+extern uint32_t	rtnl_htb_get_cbuffer(struct rtnl_class *);
+extern int	rtnl_htb_set_cbuffer(struct rtnl_class *, uint32_t);
+extern uint32_t	rtnl_htb_get_quantum(struct rtnl_class *);
+extern int	rtnl_htb_set_quantum(struct rtnl_class *, uint32_t);
+extern int	rtnl_htb_get_level(struct rtnl_class *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/sch/netem.h b/include/netlink/route/qdisc/netem.h
similarity index 65%
rename from include/netlink/route/sch/netem.h
rename to include/netlink/route/qdisc/netem.h
index c293777..4b071bf 100644
--- a/include/netlink/route/sch/netem.h
+++ b/include/netlink/route/qdisc/netem.h
@@ -13,53 +13,54 @@
 #define NETLINK_NETEM_H_
 
 #include <netlink/netlink.h>
+#include <netlink/route/qdisc.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-extern int rtnl_netem_set_limit(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_limit(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_limit(struct rtnl_qdisc *);
 
 /* Packet Re-ordering */
-extern int rtnl_netem_set_gap(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_gap(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_gap(struct rtnl_qdisc *);
 
-extern int rtnl_netem_set_reorder_probability(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_reorder_probability(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_reorder_probability(struct rtnl_qdisc *);
 
-extern int rtnl_netem_set_reorder_correlation(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_reorder_correlation(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_reorder_correlation(struct rtnl_qdisc *);
 
 /* Corruption */
-extern int rtnl_netem_set_corruption_probability(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_corruption_probability(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_corruption_probability(struct rtnl_qdisc *);
 
-extern int rtnl_netem_set_corruption_correlation(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_corruption_correlation(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_corruption_correlation(struct rtnl_qdisc *);
 
 /* Packet Loss */
-extern int rtnl_netem_set_loss(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_loss(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_loss(struct rtnl_qdisc *);
 
-extern int rtnl_netem_set_loss_correlation(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_loss_correlation(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_loss_correlation(struct rtnl_qdisc *);
 
 /* Packet Duplication */
-extern int rtnl_netem_set_duplicate(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_duplicate(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_duplicate(struct rtnl_qdisc *);
 
-extern int rtnl_netem_set_duplicate_correlation(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_duplicate_correlation(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_duplicate_correlation(struct rtnl_qdisc *);
 
 /* Packet Delay */
-extern int rtnl_netem_set_delay(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_delay(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_delay(struct rtnl_qdisc *);
 
-extern int rtnl_netem_set_jitter(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_jitter(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_jitter(struct rtnl_qdisc *);
 
-extern int rtnl_netem_set_delay_correlation(struct rtnl_qdisc *, int);
+extern void rtnl_netem_set_delay_correlation(struct rtnl_qdisc *, int);
 extern int rtnl_netem_get_delay_correlation(struct rtnl_qdisc *);
 
 /* Delay Distribution */
diff --git a/include/netlink/route/qdisc/plug.h b/include/netlink/route/qdisc/plug.h
new file mode 100644
index 0000000..40f7e53
--- /dev/null
+++ b/include/netlink/route/qdisc/plug.h
@@ -0,0 +1,31 @@
+/*
+ * netlink/route/qdisc/plug.c	PLUG Qdisc
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2012 Shriram Rajagopalan <rshriram@cs.ubc.ca>
+ */
+
+#ifndef NETLINK_PLUG_H_
+#define NETLINK_PLUG_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/qdisc.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int	rtnl_qdisc_plug_set_limit(struct rtnl_qdisc *, int);
+extern int	rtnl_qdisc_plug_buffer(struct rtnl_qdisc *);
+extern int	rtnl_qdisc_plug_release_one(struct rtnl_qdisc *);
+extern int	rtnl_qdisc_plug_release_indefinite(struct rtnl_qdisc *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/netlink/route/sch/prio.h b/include/netlink/route/qdisc/prio.h
similarity index 86%
rename from include/netlink/route/sch/prio.h
rename to include/netlink/route/qdisc/prio.h
index ff35b07..636a8f9 100644
--- a/include/netlink/route/sch/prio.h
+++ b/include/netlink/route/qdisc/prio.h
@@ -6,13 +6,14 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_PRIO_H_
 #define NETLINK_PRIO_H_
 
 #include <netlink/netlink.h>
+#include <netlink/route/qdisc.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -38,7 +39,7 @@
 
 /** @} */
 
-extern int  rtnl_qdisc_prio_set_bands(struct rtnl_qdisc *, int);
+extern void rtnl_qdisc_prio_set_bands(struct rtnl_qdisc *, int);
 extern int  rtnl_qdisc_prio_get_bands(struct rtnl_qdisc *);
 extern int  rtnl_qdisc_prio_set_priomap(struct rtnl_qdisc *, uint8_t[], int);
 extern uint8_t *rtnl_qdisc_prio_get_priomap(struct rtnl_qdisc *);
diff --git a/include/netlink/route/sch/red.h b/include/netlink/route/qdisc/red.h
similarity index 100%
rename from include/netlink/route/sch/red.h
rename to include/netlink/route/qdisc/red.h
diff --git a/include/netlink/route/sch/sfq.h b/include/netlink/route/qdisc/sfq.h
similarity index 70%
rename from include/netlink/route/sch/sfq.h
rename to include/netlink/route/qdisc/sfq.h
index 19b3817..77d2e29 100644
--- a/include/netlink/route/sch/sfq.h
+++ b/include/netlink/route/qdisc/sfq.h
@@ -6,25 +6,26 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_SFQ_H_
 #define NETLINK_SFQ_H_
 
 #include <netlink/netlink.h>
+#include <netlink/route/qdisc.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-extern int	rtnl_sfq_set_quantum(struct rtnl_qdisc *, int);
+extern void	rtnl_sfq_set_quantum(struct rtnl_qdisc *, int);
 extern int	rtnl_sfq_get_quantum(struct rtnl_qdisc *);
 
-extern int	rtnl_sfq_set_limit(struct rtnl_qdisc *, int);
+extern void	rtnl_sfq_set_limit(struct rtnl_qdisc *, int);
 extern int	rtnl_sfq_get_limit(struct rtnl_qdisc *);
 
-extern int	rtnl_sfq_set_perturb(struct rtnl_qdisc *, int);
+extern void	rtnl_sfq_set_perturb(struct rtnl_qdisc *, int);
 extern int	rtnl_sfq_get_perturb(struct rtnl_qdisc *);
 
 extern int	rtnl_sfq_get_divisor(struct rtnl_qdisc *);
diff --git a/include/netlink/route/sch/tbf.h b/include/netlink/route/qdisc/tbf.h
similarity index 77%
rename from include/netlink/route/sch/tbf.h
rename to include/netlink/route/qdisc/tbf.h
index 8e0ea1e..ce31c54 100644
--- a/include/netlink/route/sch/tbf.h
+++ b/include/netlink/route/qdisc/tbf.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_TBF_H_
@@ -14,19 +14,17 @@
 
 #include <netlink/netlink.h>
 #include <netlink/route/tc.h>
+#include <netlink/route/qdisc.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-extern int rtnl_qdisc_tbf_set_limit(struct rtnl_qdisc *, int);
+extern void rtnl_qdisc_tbf_set_limit(struct rtnl_qdisc *, int);
 extern int rtnl_qdisc_tbf_set_limit_by_latency(struct rtnl_qdisc *, int);
 extern int rtnl_qdisc_tbf_get_limit(struct rtnl_qdisc *);
 
-extern int rtnl_qdisc_tbf_set_mpu(struct rtnl_qdisc *, int);
-extern int rtnl_qdisc_tbf_get_mpu(struct rtnl_qdisc *);
-
-extern int rtnl_qdisc_tbf_set_rate(struct rtnl_qdisc *, int, int, int);
+extern void rtnl_qdisc_tbf_set_rate(struct rtnl_qdisc *, int, int, int);
 extern int rtnl_qdisc_tbf_get_rate(struct rtnl_qdisc *);
 extern int rtnl_qdisc_tbf_get_rate_bucket(struct rtnl_qdisc *);
 extern int rtnl_qdisc_tbf_get_rate_cell(struct rtnl_qdisc *);
diff --git a/include/netlink/route/route.h b/include/netlink/route/route.h
index 5729cd7..477250d 100644
--- a/include/netlink/route/route.h
+++ b/include/netlink/route/route.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_ROUTE_H_
@@ -24,7 +24,12 @@
 extern "C" {
 #endif
 
-/* flags */
+/**
+ * @ingroup route
+ * When passed to rtnl_route_alloc_cache() the cache will
+ * correspond to the contents of the routing cache instead
+ * of the actual routes.
+ */
 #define ROUTE_CACHE_CONTENT	1
 
 struct rtnl_route;
@@ -49,7 +54,6 @@
 				       struct nl_cache **);
 
 extern void	rtnl_route_get(struct rtnl_route *);
-extern void	rtnl_route_put(struct rtnl_route *);
 
 extern int	rtnl_route_parse(struct nlmsghdr *, struct rtnl_route **);
 extern int	rtnl_route_build_msg(struct nl_msg *, struct rtnl_route *);
diff --git a/include/netlink/route/rule.h b/include/netlink/route/rule.h
index 928dc0f..760b782 100644
--- a/include/netlink/route/rule.h
+++ b/include/netlink/route/rule.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_RULE_H_
@@ -16,6 +16,7 @@
 #include <netlink/cache.h>
 #include <netlink/addr.h>
 #include <netlink/route/route.h>
+#include <linux/fib_rules.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -42,34 +43,30 @@
 /* attribute modification */
 extern void		rtnl_rule_set_family(struct rtnl_rule *, int);
 extern int		rtnl_rule_get_family(struct rtnl_rule *);
-extern void		rtnl_rule_set_prio(struct rtnl_rule *, int);
-extern int		rtnl_rule_get_prio(struct rtnl_rule *);
-extern void		rtnl_rule_set_mark(struct rtnl_rule *, uint64_t);
-extern uint64_t		rtnl_rule_get_mark(struct rtnl_rule *);
-extern void		rtnl_rule_set_table(struct rtnl_rule *, int);
-extern int		rtnl_rule_get_table(struct rtnl_rule *);
-extern void		rtnl_rule_set_dsfield(struct rtnl_rule *, int);
-extern int		rtnl_rule_get_dsfield(struct rtnl_rule *);
+extern void		rtnl_rule_set_prio(struct rtnl_rule *, uint32_t);
+extern uint32_t		rtnl_rule_get_prio(struct rtnl_rule *);
+extern void		rtnl_rule_set_mark(struct rtnl_rule *, uint32_t);
+extern uint32_t		rtnl_rule_get_mark(struct rtnl_rule *);
+extern void		rtnl_rule_set_mask(struct rtnl_rule *, uint32_t);
+extern uint32_t		rtnl_rule_get_mask(struct rtnl_rule *);
+extern void		rtnl_rule_set_table(struct rtnl_rule *, uint32_t);
+extern uint32_t		rtnl_rule_get_table(struct rtnl_rule *);
+extern void		rtnl_rule_set_dsfield(struct rtnl_rule *, uint8_t);
+extern uint8_t		rtnl_rule_get_dsfield(struct rtnl_rule *);
 extern int		rtnl_rule_set_src(struct rtnl_rule *, struct nl_addr *);
 extern struct nl_addr *	rtnl_rule_get_src(struct rtnl_rule *);
 extern int		rtnl_rule_set_dst(struct rtnl_rule *, struct nl_addr *);
 extern struct nl_addr *	rtnl_rule_get_dst(struct rtnl_rule *);
-extern void		rtnl_rule_set_src_len(struct rtnl_rule *, int);
-extern int		rtnl_rule_get_src_len(struct rtnl_rule *);
-extern void		rtnl_rule_set_dst_len(struct rtnl_rule *, int);
-extern int		rtnl_rule_get_dst_len(struct rtnl_rule *);
-
-extern void		rtnl_rule_set_action(struct rtnl_rule *, int);
-extern int		rtnl_rule_get_action(struct rtnl_rule *);
-
+extern void		rtnl_rule_set_action(struct rtnl_rule *, uint8_t);
+extern uint8_t		rtnl_rule_get_action(struct rtnl_rule *);
 extern int		rtnl_rule_set_iif(struct rtnl_rule *, const char *);
 extern char *		rtnl_rule_get_iif(struct rtnl_rule *);
-
-extern void		rtnl_rule_set_classid(struct rtnl_rule *, uint32_t);
-extern uint32_t		rtnl_rule_get_classid(struct rtnl_rule *);
-
+extern int		rtnl_rule_set_oif(struct rtnl_rule *, const char *);
+extern char *		rtnl_rule_get_oif(struct rtnl_rule *);
 extern void		rtnl_rule_set_realms(struct rtnl_rule *, uint32_t);
 extern uint32_t		rtnl_rule_get_realms(struct rtnl_rule *);
+extern void		rtnl_rule_set_goto(struct rtnl_rule *, uint32_t);
+extern uint32_t		rtnl_rule_get_goto(struct rtnl_rule *);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/route/sch/htb.h b/include/netlink/route/sch/htb.h
deleted file mode 100644
index d44f039..0000000
--- a/include/netlink/route/sch/htb.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * netlink/route/sch/htb.h	HTB Qdisc
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
- * Copyright (c) 2005 Petr Gotthard <petr.gotthard@siemens.com>
- * Copyright (c) 2005 Siemens AG Oesterreich
- */
-
-#ifndef NETLINK_HTB_H_
-#define NETLINK_HTB_H_
-
-#include <netlink/netlink.h>
-#include <netlink/route/tc.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-extern void rtnl_htb_set_rate2quantum(struct rtnl_qdisc *, uint32_t);
-extern void rtnl_htb_set_defcls(struct rtnl_qdisc *, uint32_t);
-
-extern void rtnl_htb_set_prio(struct rtnl_class *, uint32_t);
-extern void rtnl_htb_set_mtu(struct rtnl_class *, uint32_t);
-extern void rtnl_htb_set_rate(struct rtnl_class *, uint32_t);
-extern void rtnl_htb_set_ceil(struct rtnl_class *, uint32_t);
-extern void rtnl_htb_set_rbuffer(struct rtnl_class *, uint32_t);
-extern void rtnl_htb_set_cbuffer(struct rtnl_class *, uint32_t);
-extern void rtnl_htb_set_quantum(struct rtnl_class *, uint32_t quantum);
-extern void rtnl_htb_set_overhead(struct rtnl_class *, uint8_t overhead);
-extern void rtnl_htb_set_mpu(struct rtnl_class *, uint8_t mpu);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/include/netlink/route/tc-api.h b/include/netlink/route/tc-api.h
new file mode 100644
index 0000000..b7771b5
--- /dev/null
+++ b/include/netlink/route/tc-api.h
@@ -0,0 +1,21 @@
+/*
+ * netlink/route/tc-api.h	Traffic Control API
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_DUMMY_TC_API_H_
+#define NETLINK_DUMMY_TC_API_H_
+
+#include <netlink/netlink.h>
+#include <netlink/msg.h>
+#include <netlink/route/tc.h>
+
+#warning "You are including a deprecated header file, include <netlink/route/tc.h>."
+
+#endif
diff --git a/include/netlink/route/tc.h b/include/netlink/route/tc.h
index 3cb876f..870c1f2 100644
--- a/include/netlink/route/tc.h
+++ b/include/netlink/route/tc.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_TC_H_
@@ -15,46 +15,98 @@
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/data.h>
+#include <netlink/route/link.h>
+#include <linux/pkt_sched.h>
+#include <linux/pkt_cls.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+enum rtnl_tc_type {
+	RTNL_TC_TYPE_QDISC,
+	RTNL_TC_TYPE_CLASS,
+	RTNL_TC_TYPE_CLS,
+	RTNL_TC_TYPE_ACT,
+	__RTNL_TC_TYPE_MAX,
+};
+
+#define RTNL_TC_TYPE_MAX (__RTNL_TC_TYPE_MAX - 1)
+
 /**
- * TC statistics identifiers
+ * Compute tc handle based on major and minor parts
  * @ingroup tc
  */
-enum rtnl_tc_stats_id {
-	RTNL_TC_PACKETS,	/**< Packets seen */
-	RTNL_TC_BYTES,		/**< Bytes seen */
+#define TC_HANDLE(maj, min)	(TC_H_MAJ((maj) << 16) | TC_H_MIN(min))
+
+/**
+ * Traffic control object
+ * @ingroup tc
+ */
+struct rtnl_tc;
+
+/**
+ * Macro to cast qdisc/class/classifier to tc object
+ * @ingroup tc
+ *
+ * @code
+ * rtnl_tc_set_mpu(TC_CAST(qdisc), 40);
+ * @endcode
+ */
+#define TC_CAST(ptr)		((struct rtnl_tc *) (ptr))
+
+/**
+ * Traffic control statistical identifier
+ * @ingroup tc
+ *
+ * @code
+ * uint64_t n = rtnl_tc_get_stat(TC_CAST(class), RTNL_TC_PACKETS);
+ * @endcode
+ */
+enum rtnl_tc_stat {
+	RTNL_TC_PACKETS,	/**< Number of packets seen */
+	RTNL_TC_BYTES,		/**< Total bytes seen */
 	RTNL_TC_RATE_BPS,	/**< Current bits/s (rate estimator) */
 	RTNL_TC_RATE_PPS,	/**< Current packet/s (rate estimator) */
-	RTNL_TC_QLEN,		/**< Queue length */
-	RTNL_TC_BACKLOG,	/**< Backlog length */
-	RTNL_TC_DROPS,		/**< Packets dropped */
-	RTNL_TC_REQUEUES,	/**< Number of requeues */
-	RTNL_TC_OVERLIMITS,	/**< Number of overlimits */
+	RTNL_TC_QLEN,		/**< Current queue length */
+	RTNL_TC_BACKLOG,	/**< Current backlog length */
+	RTNL_TC_DROPS,		/**< Total number of packets dropped */
+	RTNL_TC_REQUEUES,	/**< Total number of requeues */
+	RTNL_TC_OVERLIMITS,	/**< Total number of overlimits */
 	__RTNL_TC_STATS_MAX,
 };
 
 #define RTNL_TC_STATS_MAX (__RTNL_TC_STATS_MAX - 1)
 
-extern int rtnl_tc_calc_txtime(int, int);
-extern int rtnl_tc_calc_bufsize(int, int);
-extern int rtnl_tc_calc_cell_log(int);
+extern void		rtnl_tc_set_ifindex(struct rtnl_tc *, int);
+extern int		rtnl_tc_get_ifindex(struct rtnl_tc *);
+extern void		rtnl_tc_set_link(struct rtnl_tc *, struct rtnl_link *);
+extern struct rtnl_link *rtnl_tc_get_link(struct rtnl_tc *);
+extern void		rtnl_tc_set_mtu(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_mtu(struct rtnl_tc *);
+extern void		rtnl_tc_set_mpu(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_mpu(struct rtnl_tc *);
+extern void		rtnl_tc_set_overhead(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_overhead(struct rtnl_tc *);
+extern void		rtnl_tc_set_linktype(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_linktype(struct rtnl_tc *);
+extern void		rtnl_tc_set_handle(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_handle(struct rtnl_tc *);
+extern void		rtnl_tc_set_parent(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_parent(struct rtnl_tc *);
+extern int		rtnl_tc_set_kind(struct rtnl_tc *, const char *);
+extern char *		rtnl_tc_get_kind(struct rtnl_tc *);
+extern uint64_t		rtnl_tc_get_stat(struct rtnl_tc *, enum rtnl_tc_stat);
 
-/**
- * Number of entries in a transmission time lookup table
- * @ingroup tc
- */
-#define RTNL_TC_RTABLE_SIZE	256
+extern int		rtnl_tc_calc_txtime(int, int);
+extern int		rtnl_tc_calc_bufsize(int, int);
+extern int		rtnl_tc_calc_cell_log(int);
 
-extern int rtnl_tc_build_rate_table(uint32_t *, uint8_t, uint8_t, int, int);
-
-
-/* TC Handle Translations */
+extern int		rtnl_tc_read_classid_file(void);
 extern char *		rtnl_tc_handle2str(uint32_t, char *, size_t);
 extern int		rtnl_tc_str2handle(const char *, uint32_t *);
+extern int		rtnl_classid_generate(const char *, uint32_t *,
+					      uint32_t);
 
 #ifdef __cplusplus
 }
diff --git a/include/netlink/socket.h b/include/netlink/socket.h
index 7e71aed..1007eba 100644
--- a/include/netlink/socket.h
+++ b/include/netlink/socket.h
@@ -23,7 +23,7 @@
 extern struct nl_sock *	nl_socket_alloc_cb(struct nl_cb *);
 extern void		nl_socket_free(struct nl_sock *);
 
-extern uint32_t		nl_socket_get_local_port(struct nl_sock *);
+extern uint32_t		nl_socket_get_local_port(const struct nl_sock *);
 extern void		nl_socket_set_local_port(struct nl_sock *, uint32_t);
 
 extern int		nl_socket_add_memberships(struct nl_sock *, int, ...);
@@ -34,18 +34,23 @@
 extern void		nl_join_groups(struct nl_sock *, int);
 
 
-extern uint32_t		nl_socket_get_peer_port(struct nl_sock *);
+extern uint32_t		nl_socket_get_peer_port(const struct nl_sock *);
 extern void		nl_socket_set_peer_port(struct nl_sock *,
 							uint32_t);
-
-extern struct nl_cb *	nl_socket_get_cb(struct nl_sock *);
+extern uint32_t 	nl_socket_get_peer_groups(const struct nl_sock *sk);
+extern void 		nl_socket_set_peer_groups(struct nl_sock *sk, uint32_t groups);
+extern struct nl_cb *	nl_socket_get_cb(const struct nl_sock *);
 extern void		nl_socket_set_cb(struct nl_sock *,
 						 struct nl_cb *);
 extern int		nl_socket_modify_cb(struct nl_sock *, enum nl_cb_type,
 					    enum nl_cb_kind,
 					    nl_recvmsg_msg_cb_t, void *);
+extern int nl_socket_modify_err_cb(struct nl_sock *, enum nl_cb_kind,
+				   nl_recvmsg_err_cb_t, void *);
 
 extern int		nl_socket_set_buffer_size(struct nl_sock *, int, int);
+extern int		nl_socket_set_msg_buf_size(struct nl_sock *, size_t);
+extern size_t		nl_socket_get_msg_buf_size(struct nl_sock *);
 extern int		nl_socket_set_passcred(struct nl_sock *, int);
 extern int		nl_socket_recv_pktinfo(struct nl_sock *, int);
 
@@ -54,8 +59,8 @@
 extern void		nl_socket_disable_auto_ack(struct nl_sock *);
 extern void		nl_socket_enable_auto_ack(struct nl_sock *);
 
-extern int		nl_socket_get_fd(struct nl_sock *);
-extern int		nl_socket_set_nonblocking(struct nl_sock *);
+extern int		nl_socket_get_fd(const struct nl_sock *);
+extern int		nl_socket_set_nonblocking(const struct nl_sock *);
 extern void		nl_socket_enable_msg_peek(struct nl_sock *);
 extern void		nl_socket_disable_msg_peek(struct nl_sock *);
 
diff --git a/include/netlink/types.h b/include/netlink/types.h
index 2e0b9c3..09cc5bd 100644
--- a/include/netlink/types.h
+++ b/include/netlink/types.h
@@ -1,12 +1,12 @@
 /*
- * netlink/netlink-types.h	Netlink Types
+ * netlink/types.h		Definition of public types
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef __NETLINK_TYPES_H_
@@ -15,21 +15,20 @@
 #include <stdio.h>
 
 /**
- * Dumping types (dp_type)
  * @ingroup utils
+ * Enumeration of dumping variations (dp_type)
  */
 enum nl_dump_type {
 	NL_DUMP_LINE,		/**< Dump object briefly on one line */
 	NL_DUMP_DETAILS,	/**< Dump all attributes but no statistics */
 	NL_DUMP_STATS,		/**< Dump all attributes including statistics */
-	NL_DUMP_ENV,		/**< Dump all attribtues as env variables */
 	__NL_DUMP_MAX,
 };
 #define NL_DUMP_MAX (__NL_DUMP_MAX - 1)
 
 /**
- * Dumping parameters
  * @ingroup utils
+ * Dumping parameters
  */
 struct nl_dump_params
 {
diff --git a/include/netlink/utils.h b/include/netlink/utils.h
index 480bab6..6b4b787 100644
--- a/include/netlink/utils.h
+++ b/include/netlink/utils.h
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_UTILS_H_
@@ -31,24 +31,32 @@
 #define NL_PROB_MIN 0x0
 
 /**
- * Upper probability limit
+ * Upper probability limit nl_dump_type
  * @ingroup utils
  */
 #define NL_PROB_MAX 0xffffffff
 
 /** @} */
 
+enum {
+	NL_BYTE_RATE,
+	NL_BIT_RATE,
+};
+
 /* unit pretty-printing */
 extern double	nl_cancel_down_bytes(unsigned long long, char **);
 extern double	nl_cancel_down_bits(unsigned long long, char **);
+extern int	nl_rate2str(unsigned long long, int, char *, size_t);
 extern double	nl_cancel_down_us(uint32_t, char **);
 
 /* generic unit translations */
 extern long	nl_size2int(const char *);
+extern char *	nl_size2str(const size_t, char *, const size_t);
 extern long	nl_prob2int(const char *);
 
 /* time translations */
-extern int	nl_get_hz(void);
+extern int	nl_get_user_hz(void);
+extern int	nl_get_psched_hz(void);
 extern uint32_t	nl_us2ticks(uint32_t);
 extern uint32_t	nl_ticks2us(uint32_t);
 extern int	nl_str2msec(const char *, uint64_t *);
@@ -71,6 +79,45 @@
 extern void	nl_dump(struct nl_dump_params *, const char *, ...);
 extern void	nl_dump_line(struct nl_dump_params *, const char *, ...);
 
+enum {
+	NL_CAPABILITY_NONE,
+
+	/**
+	 * rtnl_route_build_msg() no longer guesses the route scope
+	 * if explicitly set to RT_SCOPE_NOWHERE.
+	 * @ingroup utils
+	 */
+	NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE         = 1,
+#define NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE
+
+	/**
+	 * rtnl_link_veth_get_peer() now returns a reference that is owned by the
+	 * caller and must be released by the caller with rtnl_link_put().
+	 */
+	NL_CAPABILITY_ROUTE_LINK_VETH_GET_PEER_OWN_REFERENCE = 2,
+#define NL_CAPABILITY_ROUTE_LINK_VETH_GET_PEER_OWN_REFERENCE NL_CAPABILITY_ROUTE_LINK_VETH_GET_PEER_OWN_REFERENCE
+
+	/**
+	 * rtnl_u32_add_action() and rtnl_basic_add_action() now grab a reference to act
+	 * caller are free to release its own
+	 */
+	NL_CAPABILITY_ROUTE_LINK_CLS_ADD_ACT_OWN_REFERENCE = 3,
+#define NL_CAPABILITY_ROUTE_LINK_CLS_ADD_ACT_OWN_REFERENCE NL_CAPABILITY_ROUTE_LINK_CLS_ADD_ACT_OWN_REFERENCE
+
+	/**
+	 * Indicate that the local port is unspecified until the user accesses
+	 * it (via nl_socket_get_local_port()) or until nl_connect(). More importantly,
+	 * if the port is left unspecified, nl_connect() will retry generating another
+	 * port when bind() fails with ADDRINUSE.
+	 */
+	NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE = 4,
+#define NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE
+
+	__NL_CAPABILITY_MAX
+#define NL_CAPABILITY_MAX                               (__NL_CAPABILITY_MAX - 1)
+};
+int nl_has_capability (int capability);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/netlink/version.h b/include/netlink/version.h
index 84af8f3..a809442 100644
--- a/include/netlink/version.h
+++ b/include/netlink/version.h
@@ -1,18 +1,37 @@
 /*
- * netlink/version.h	Compile Time Versioning Information
+ * netlink/version.h	Versioning Information
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_VERSION_H_
 #define NETLINK_VERSION_H_
 
-#define LIBNL_STRING "libnl 2.0"
-#define LIBNL_VERSION "2.0"
+/* Compile Time Versioning Information */
+
+#define LIBNL_STRING "libnl 3.2.25"
+#define LIBNL_VERSION "3.2.25"
+
+#define LIBNL_VER_MAJ		3
+#define LIBNL_VER_MIN		2
+#define LIBNL_VER_MIC		25
+#define LIBNL_VER(maj,min)	((maj) << 8 | (min))
+#define LIBNL_VER_NUM		LIBNL_VER(LIBNL_VER_MAJ, LIBNL_VER_MIN)
+
+#define LIBNL_CURRENT		220
+#define LIBNL_REVISION		0
+#define LIBNL_AGE		20
+
+/* Run-time version information */
+
+extern const int        nl_ver_num;
+extern const int        nl_ver_maj;
+extern const int        nl_ver_min;
+extern const int        nl_ver_mic;
 
 #endif
diff --git a/include/netlink/version.h.in b/include/netlink/version.h.in
index 7bd38cc..35bf2aa 100644
--- a/include/netlink/version.h.in
+++ b/include/netlink/version.h.in
@@ -1,18 +1,37 @@
 /*
- * netlink/version.h	Compile Time Versioning Information
+ * netlink/version.h	Versioning Information
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 #ifndef NETLINK_VERSION_H_
 #define NETLINK_VERSION_H_
 
+/* Compile Time Versioning Information */
+
 #define LIBNL_STRING "@PACKAGE_STRING@"
 #define LIBNL_VERSION "@PACKAGE_VERSION@"
 
+#define LIBNL_VER_MAJ		@MAJ_VERSION@
+#define LIBNL_VER_MIN		@MIN_VERSION@
+#define LIBNL_VER_MIC		@MIC_VERSION@
+#define LIBNL_VER(maj,min)	((maj) << 8 | (min))
+#define LIBNL_VER_NUM		LIBNL_VER(LIBNL_VER_MAJ, LIBNL_VER_MIN)
+
+#define LIBNL_CURRENT		@LT_CURRENT@
+#define LIBNL_REVISION		@LT_REVISION@
+#define LIBNL_AGE		@LT_AGE@
+
+/* Run-time version information */
+
+extern const int        nl_ver_num;
+extern const int        nl_ver_maj;
+extern const int        nl_ver_min;
+extern const int        nl_ver_mic;
+
 #endif
diff --git a/lib/.gitignore b/lib/.gitignore
index 2a450e8..e1abf18 100644
--- a/lib/.gitignore
+++ b/lib/.gitignore
@@ -1,2 +1,3 @@
 libnl.so*
 libnl-*.so*
+lex.yy.c
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 92a916e..bc8944d 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,57 +1,135 @@
 # -*- Makefile -*-
 
-AM_CPPFLAGS  = -Wall -I${top_srcdir}/include -I${top_builddir}/include -D_GNU_SOURCE -DSYSCONFDIR=\"$(sysconfdir)/libnl\"
+AM_CPPFLAGS  =			 	\
+	-Wall \
+	-I${top_srcdir}/include/linux-private \
+	-I${top_srcdir}/include \
+	-I${top_builddir}/include \
+	-I${builddir}/route \
+	-I${builddir}/route/cls \
+	-D_GNU_SOURCE \
+	-DSYSCONFDIR=\"$(sysconfdir)/libnl\"
+
+AM_LDFLAGS = \
+	-version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \
+	-Wl,--version-script=$(top_builddir)/libnl.sym
 
 lib_LTLIBRARIES = \
-	libnl.la libnl-genl.la libnl-route.la libnl-nf.la
+	libnl-3.la libnl-genl-3.la libnl-route-3.la libnl-nf-3.la libnl-idiag-3.la
 
-libnl_la_LDFLAGS = -version-info 2:0:0
-libnl_la_SOURCES = \
-	addr.c attr.c cache.c cache_mngr.c cache_mngt.c data.c doc.c \
-	error.c handlers.c msg.c nl.c object.c socket.c utils.c
+libnl_3_la_SOURCES = \
+	addr.c attr.c cache.c cache_mngr.c cache_mngt.c data.c \
+	error.c handlers.c msg.c nl.c object.c socket.c utils.c \
+	version.c hash.c hashtable.c
 
-libnl_genl_la_LDFLAGS = -version-info 2:0:0
-libnl_genl_la_LIBADD  = libnl.la
-libnl_genl_la_SOURCES = \
+libnl_idiag_3_la_LIBADD = libnl-3.la
+libnl_idiag_3_la_SOURCES = \
+	idiag/idiag_meminfo_obj.c idiag/idiag_vegasinfo_obj.c \
+	idiag/idiag_msg_obj.c idiag/idiag_req_obj.c idiag/idiag.c
+
+libnl_genl_3_la_LIBADD  = libnl-3.la
+libnl_genl_3_la_SOURCES = \
 	genl/ctrl.c genl/family.c genl/genl.c genl/mngt.c
 
-libnl_nf_la_LDFLAGS = -version-info 2:0:0
-libnl_nf_la_LIBADD  = libnl-route.la
-libnl_nf_la_SOURCES = \
+libnl_nf_3_la_LIBADD  = libnl-route-3.la
+libnl_nf_3_la_SOURCES = \
 	netfilter/ct.c netfilter/ct_obj.c netfilter/log.c \
 	netfilter/log_msg.c netfilter/log_msg_obj.c netfilter/log_obj.c \
 	netfilter/netfilter.c netfilter/nfnl.c netfilter/queue.c \
-	netfilter/queue_msg.c netfilter/queue_msg_obj.c netfilter/queue_obj.c
+	netfilter/queue_msg.c netfilter/queue_msg_obj.c netfilter/queue_obj.c \
+	netfilter/exp.c netfilter/exp_obj.c
 
 CLEANFILES = \
 	route/pktloc_grammar.c route/pktloc_grammar.h \
-	route/pktloc_syntax.c route/pktloc_syntax.h
+	route/pktloc_syntax.c route/pktloc_syntax.h \
+	route/cls/ematch_grammar.c route/cls/ematch_grammar.h \
+	route/cls/ematch_syntax.c route/cls/ematch_syntax.h
 
 # Hack to avoid using ylwrap. It does not function correctly in combination
 # with --header-file=
 route/pktloc_grammar.c: route/pktloc_grammar.l
-	$(LEX) --header-file=route/pktloc_grammar.h $(LFLAGS) -o $@ $^
+	$(AM_V_GEN) $(MKDIR_P) route; $(FLEX) --header-file=route/pktloc_grammar.h $(LFLAGS) -o $@ $^
 
 route/pktloc_syntax.c: route/pktloc_syntax.y
-	$(YACC) -d $(YFLAGS) -o $@ $^
+	$(AM_V_GEN) $(MKDIR_P) route; $(YACC) -d $(YFLAGS) -o $@ $^
 
-libnl_route_la_LDFLAGS = -version-info 2:0:0
-libnl_route_la_LIBADD  = libnl.la
-libnl_route_la_SOURCES = \
-	route/addr.c route/class.c route/class_api.c route/class_obj.c \
-	route/cls.c route/cls_api.c route/cls_obj.c route/link.c \
+route/cls/ematch_grammar.c: route/cls/ematch_grammar.l
+	$(AM_V_GEN) $(MKDIR_P) route/cls; $(FLEX) --header-file=route/cls/ematch_grammar.h $(LFLAGS) -o $@ $^
+
+route/cls/ematch_syntax.c: route/cls/ematch_syntax.y
+	$(AM_V_GEN) $(MKDIR_P) route/cls; $(YACC) -d $(YFLAGS) -o $@ $^
+
+libnl_route_3_la_LIBADD  = libnl-3.la
+libnl_route_3_la_SOURCES = \
+	route/addr.c route/class.c route/cls.c route/act.c route/link.c \
 	route/neigh.c route/neightbl.c route/nexthop.c route/qdisc.c \
-	route/qdisc_api.c route/qdisc_obj.c route/route.c route/route_obj.c \
-	route/route_utils.c route/rtnl.c route/rule.c route/tc.c \
+	route/route.c route/route_obj.c route/route_utils.c route/rtnl.c \
+	route/rule.c route/tc.c route/classid.c \
 	\
-	route/cls/fw.c route/cls/police.c route/cls/u32.c \
+	route/cls/fw.c route/cls/police.c route/cls/u32.c route/cls/basic.c \
+	route/cls/cgroup.c \
 	\
-	route/link/api.c route/link/vlan.c \
+	route/act/mirred.c \
 	\
-	route/sch/blackhole.c route/sch/cbq.c route/sch/dsmark.c \
-	route/sch/fifo.c route/sch/htb.c route/sch/netem.c route/sch/prio.c \
-	route/sch/red.c route/sch/sfq.c route/sch/tbf.c \
+	route/cls/ematch.c \
+	route/cls/ematch/container.c route/cls/ematch/cmp.c \
+	route/cls/ematch/nbyte.c route/cls/ematch/text.c \
+	route/cls/ematch/meta.c \
+	\
+	route/link/api.c route/link/vlan.c route/link/dummy.c \
+	route/link/bridge.c route/link/inet6.c route/link/inet.c \
+	route/link/bonding.c route/link/can.c route/link/macvlan.c \
+	route/link/vxlan.c route/link/veth.c route/link/ipip.c \
+	route/link/ipgre.c route/link/sit.c route/link/ipvti.c \
+	route/link/ip6tnl.c \
+	\
+	route/qdisc/blackhole.c route/qdisc/cbq.c route/qdisc/dsmark.c \
+	route/qdisc/fifo.c route/qdisc/htb.c route/qdisc/netem.c \
+	route/qdisc/prio.c route/qdisc/red.c route/qdisc/sfq.c \
+	route/qdisc/tbf.c route/qdisc/plug.c route/qdisc/ingress.c \
+	route/qdisc/fq_codel.c \
 	\
 	fib_lookup/lookup.c fib_lookup/request.c \
 	\
-	route/pktloc_syntax.c route/pktloc_grammar.c route/pktloc.c
+	route/pktloc.c
+
+nodist_libnl_route_3_la_SOURCES = \
+	route/pktloc_syntax.c route/pktloc_syntax.h \
+	route/pktloc_grammar.c route/pktloc_grammar.h \
+	route/cls/ematch_syntax.c route/cls/ematch_syntax.h \
+	route/cls/ematch_grammar.c route/cls/ematch_grammar.h
+
+BUILT_SOURCES = \
+	route/cls/ematch_grammar.c \
+	route/cls/ematch_syntax.c \
+	route/pktloc_grammar.c \
+	route/pktloc_syntax.c
+
+EXTRA_DIST = \
+	route/pktloc_grammar.l \
+	route/pktloc_syntax.y \
+	route/cls/ematch_grammar.l \
+	route/cls/ematch_syntax.y
+
+if ENABLE_CLI
+nobase_pkglib_LTLIBRARIES = \
+	cli/qdisc/htb.la \
+	cli/qdisc/blackhole.la \
+	cli/qdisc/pfifo.la \
+	cli/qdisc/plug.la \
+	cli/qdisc/bfifo.la \
+	cli/qdisc/ingress.la \
+	cli/qdisc/fq_codel.la \
+	cli/cls/basic.la \
+	cli/cls/cgroup.la
+
+cli_qdisc_htb_la_LDFLAGS = -module -avoid-version
+cli_qdisc_blackhole_la_LDFLAGS = -module -avoid-version
+cli_qdisc_pfifo_la_LDFLAGS = -module -avoid-version
+cli_qdisc_plug_la_LDFLAGS = -module -avoid-version
+cli_qdisc_bfifo_la_LDFLAGS = -module -avoid-version
+cli_qdisc_ingress_la_LDFLAGS = -module -avoid-version
+cli_qdisc_fq_codel_la_LDFLAGS = -module -avoid-version
+cli_cls_basic_la_LDFLAGS = -module -avoid-version
+cli_cls_cgroup_la_LDFLAGS = -module -avoid-version
+endif
diff --git a/lib/addr.c b/lib/addr.c
index 1f000e7..54d2b1d 100644
--- a/lib/addr.c
+++ b/lib/addr.c
@@ -1,31 +1,33 @@
 /*
- * lib/addr.c		Abstract Address
+ * lib/addr.c		Network Address
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup core
- * @defgroup addr Abstract Address
+ * @ingroup core_types
+ * @defgroup addr Network Address
  *
- * @par 1) Transform character string to abstract address
- * @code
- * struct nl_addr *a = nl_addr_parse("::1", AF_UNSPEC);
- * printf("Address family: %s\n", nl_af2str(nl_addr_get_family(a)));
- * nl_addr_put(a);
- * a = nl_addr_parse("11:22:33:44:55:66", AF_UNSPEC);
- * printf("Address family: %s\n", nl_af2str(nl_addr_get_family(a)));
- * nl_addr_put(a);
- * @endcode
+ * Abstract data type representing any kind of network address
+ *
+ * Related sections in the development guide:
+ * - @core_doc{_abstract_address, Network Addresses}
+ *
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/addr.h>
+ * ~~~~
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/addr.h>
@@ -151,15 +153,34 @@
 	return 1;
 }
 
+static void addr_destroy(struct nl_addr *addr)
+{
+	if (!addr)
+		return;
+
+	if (addr->a_refcnt != 1)
+		BUG();
+
+	free(addr);
+}
+
 /**
- * @name Creating Abstract Addresses
+ * @name Creating Abstract Network Addresses
  * @{
  */
 
 /**
- * Allocate new abstract address object.
- * @arg maxsize		Maximum size of the binary address.
- * @return Newly allocated address object or NULL
+ * Allocate empty abstract address
+ * @arg maxsize		Upper limit of the binary address to be stored
+ *
+ * The new address object will be empty with a prefix length of 0 and will
+ * be capable of holding binary addresses up to the specified limit.
+ *
+ * @see nl_addr_build()
+ * @see nl_addr_parse()
+ * @see nl_addr_put()
+ *
+ * @return Allocated address object or NULL upon failure.
  */
 struct nl_addr *nl_addr_alloc(size_t maxsize)
 {
@@ -176,11 +197,21 @@
 }
 
 /**
- * Allocate new abstract address object based on a binary address.
- * @arg family		Address family.
- * @arg buf		Buffer containing the binary address.
- * @arg size		Length of binary address buffer.
- * @return Newly allocated address handle or NULL
+ * Allocate abstract address based on a binary address.
+ * @arg family		Address family
+ * @arg buf		Binary address
+ * @arg size		Length of binary address
+ *
+ * This function will allocate an abstract address capable of holding the
+ * binary address specified. The prefix length will be set to the full
+ * length of the binary address provided.
+ *
+ * @see nl_addr_alloc()
+ * @see nl_addr_alloc_attr()
+ * @see nl_addr_parse()
+ * @see nl_addr_put()
+ *
+ * @return Allocated address object or NULL upon failure.
  */
 struct nl_addr *nl_addr_build(int family, void *buf, size_t size)
 {
@@ -201,14 +232,25 @@
 }
 
 /**
- * Allocate abstract address based on netlink attribute.
- * @arg nla		Netlink attribute of unspecific type.
+ * Allocate abstract address based on Netlink attribute.
+ * @arg nla		Netlink attribute
  * @arg family		Address family.
  *
- * Considers the netlink attribute payload a address of the specified
- * family and allocates a new abstract address based on it.
+ * Allocates an abstract address based on the specified Netlink attribute
+ * by interpreting the payload of the Netlink attribute as the binary
+ * address.
  *
- * @return Newly allocated address handle or NULL.
+ * This function is identical to:
+ * @code
+ * nl_addr_build(family, nla_data(nla), nla_len(nla));
+ * @endcode
+ *
+ * @see nl_addr_alloc()
+ * @see nl_addr_build()
+ * @see nl_addr_parse()
+ * @see nl_addr_put()
+ *
+ * @return Allocated address object or NULL upon failure.
  */
 struct nl_addr *nl_addr_alloc_attr(struct nlattr *nla, int family)
 {
@@ -216,13 +258,13 @@
 }
 
 /**
- * Allocate abstract address object based on a character string
+ * Allocate abstract address based on character string
  * @arg addrstr		Address represented as character string.
  * @arg hint		Address family hint or AF_UNSPEC.
  * @arg result		Pointer to store resulting address.
  *
  * Regognizes the following address formats:
- *@code
+ * @code
  *  Format                      Len                Family
  *  ----------------------------------------------------------------
  *  IPv6 address format         16                 AF_INET6
@@ -240,6 +282,10 @@
  * The prefix length may be appened at the end prefixed with a
  * slash, e.g. 10.0.0.0/8.
  *
+ * @see nl_addr_alloc()
+ * @see nl_addr_build()
+ * @see nl_addr_put()
+ *
  * @return 0 on success or a negative error code.
  */
 int nl_addr_parse(const char *addrstr, int hint, struct nl_addr **result)
@@ -266,7 +312,9 @@
 	if (!strcasecmp(str, "default") ||
 	    !strcasecmp(str, "all") ||
 	    !strcasecmp(str, "any")) {
-			
+
+		len = 0;
+
 		switch (hint) {
 			case AF_INET:
 			case AF_UNSPEC:
@@ -274,17 +322,14 @@
 				 * no hint given the user wants to have a IPv4
 				 * address given back. */
 				family = AF_INET;
-				len = 4;
 				goto prefix;
 
 			case AF_INET6:
 				family = AF_INET6;
-				len = 16;
 				goto prefix;
 
 			case AF_LLC:
 				family = AF_LLC;
-				len = 6;
 				goto prefix;
 
 			default:
@@ -355,7 +400,7 @@
 	}
 
 	if (hint == AF_UNSPEC && strchr(str, ':')) {
-		int i = 0;
+		size_t i = 0;
 		char *s = str, *p;
 		for (;;) {
 			long l = strtol(s, &p, 16);
@@ -395,7 +440,7 @@
 		char *p;
 		long pl = strtol(++prefix, &p, 0);
 		if (p == prefix) {
-			nl_addr_destroy(addr);
+			addr_destroy(addr);
 			err = -NLE_INVAL;
 			goto errout;
 		}
@@ -412,10 +457,16 @@
 }
 
 /**
- * Clone existing abstract address object.
- * @arg addr		Abstract address object.
- * @return Newly allocated abstract address object being a duplicate of the
- *         specified address object or NULL if a failure occured.
+ * Clone existing abstract address object
+ * @arg addr		Abstract address object
+ *
+ * Allocates new abstract address representing an identical clone of an
+ * existing address.
+ *
+ * @see nl_addr_alloc()
+ * @see nl_addr_put()
+ *
+ * @return Allocated abstract address or NULL upon failure.
  */
 struct nl_addr *nl_addr_clone(struct nl_addr *addr)
 {
@@ -431,32 +482,22 @@
 /** @} */
 
 /**
- * @name Destroying Abstract Addresses
- * @{
- */
-
-/**
- * Destroy abstract address object.
- * @arg addr		Abstract address object.
- */
-void nl_addr_destroy(struct nl_addr *addr)
-{
-	if (!addr)
-		return;
-
-	if (addr->a_refcnt != 1)
-		BUG();
-
-	free(addr);
-}
-
-/** @} */
-
-/**
  * @name Managing Usage References
  * @{
  */
 
+/**
+ * Increase the reference counter of an abstract address
+ * @arg addr		Abstract address
+ *
+ * Increases the reference counter of the address and thus prevents the
+ * release of the memory resources until the reference is given back
+ * using the function nl_addr_put().
+ *
+ * @see nl_addr_put()
+ *
+ * @return Pointer to the existing abstract address
+ */
 struct nl_addr *nl_addr_get(struct nl_addr *addr)
 {
 	addr->a_refcnt++;
@@ -464,21 +505,31 @@
 	return addr;
 }
 
+/**
+ * Decrease the reference counter of an abstract address
+ * @arg addr		Abstract addr
+ *
+ * @note The resources of the abstract address will be freed after the
+ *       last reference to the address has been returned.
+ *
+ * @see nl_addr_get()
+ */
 void nl_addr_put(struct nl_addr *addr)
 {
 	if (!addr)
 		return;
 
 	if (addr->a_refcnt == 1)
-		nl_addr_destroy(addr);
+		addr_destroy(addr);
 	else
 		addr->a_refcnt--;
 }
 
 /**
- * Check whether an abstract address object is shared.
+ * Check whether an abstract address is shared.
  * @arg addr		Abstract address object.
- * @return Non-zero if the abstract address object is shared, otherwise 0.
+ *
+ * @return Non-zero if the abstract address is shared, otherwise 0.
  */
 int nl_addr_shared(struct nl_addr *addr)
 {
@@ -493,12 +544,21 @@
  */
 
 /**
- * Compares two abstract address objects.
- * @arg a		A abstract address object.
- * @arg b		Another abstract address object.
+ * Compare abstract addresses
+ * @arg a		An abstract address
+ * @arg b		Another abstract address
  *
- * @return Integer less than, equal to or greather than zero if \c is found,
- *         respectively to be less than, to, or be greater than \c b.
+ * Verifies whether the address family, address length, prefix length, and
+ * binary addresses of two abstract addresses matches.
+ *
+ * @note This function will *not* respect the prefix length in the sense
+ *       that only the actual prefix will be compared. Please refer to the
+ *       nl_addr_cmp_prefix() function if you require this functionality.
+ *
+ * @see nl_addr_cmp_prefix()
+ *
+ * @return Integer less than, equal to or greather than zero if the two
+ *         addresses match.
  */
 int nl_addr_cmp(struct nl_addr *a, struct nl_addr *b)
 {
@@ -507,20 +567,29 @@
 	if (d == 0) {
 		d = a->a_len - b->a_len;
 
-		if (a->a_len && d == 0)
-			return memcmp(a->a_addr, b->a_addr, a->a_len);
+		if (a->a_len && d == 0) {
+			d = memcmp(a->a_addr, b->a_addr, a->a_len);
+
+			if (d == 0)
+				return (a->a_prefixlen - b->a_prefixlen);
+		}
 	}
 
 	return d;
 }
 
 /**
- * Compares the prefix of two abstract address objects.
- * @arg a		A abstract address object.
- * @arg b		Another abstract address object.
+ * Compare the prefix of two abstract addresses
+ * @arg a		An abstract address
+ * @arg b		Another abstract address
  *
- * @return Integer less than, equal to or greather than zero if \c is found,
- *         respectively to be less than, to, or be greater than \c b.
+ * Verifies whether the address family and the binary address covered by
+ * the smaller prefix length of the two abstract addresses matches.
+ *
+ * @see nl_addr_cmp()
+ *
+ * @return Integer less than, equal to or greather than zero if the two
+ *         addresses match.
  */
 int nl_addr_cmp_prefix(struct nl_addr *a, struct nl_addr *b)
 {
@@ -531,8 +600,8 @@
 		int bytes = len / 8;
 
 		d = memcmp(a->a_addr, b->a_addr, bytes);
-		if (d == 0) {
-			int mask = (1UL << (len % 8)) - 1UL;
+		if (d == 0 && (len % 8) != 0) {
+			int mask = (0xFF00 >> (len % 8)) & 0xFF;
 
 			d = (a->a_addr[bytes] & mask) -
 			    (b->a_addr[bytes] & mask);
@@ -544,11 +613,13 @@
 
 /**
  * Returns true if the address consists of all zeros
- * @arg addr		Address to look at.
+ * @arg addr		Abstract address
+ *
+ * @return 1 if the binary address consists of all zeros, 0 otherwise.
  */
 int nl_addr_iszero(struct nl_addr *addr)
 {
-	int i;
+	unsigned int i;
 
 	for (i = 0; i < addr->a_len; i++)
 		if (addr->a_addr[i])
@@ -558,11 +629,11 @@
 }
 
 /**
- * Check if an address matches a certain family.
+ * Check if address string is parseable for a specific address family
  * @arg addr		Address represented as character string.
  * @arg family		Desired address family.
  *
- * @return 1 if the address is of the desired address family,
+ * @return 1 if the address is parseable assuming the specified address family,
  *         otherwise 0 is returned.
  */
 int nl_addr_valid(char *addr, int family)
@@ -594,9 +665,10 @@
 }
 
 /**
- * Guess address family of an abstract address object based on address size.
+ * Guess address family of abstract address based on address size
  * @arg addr		Abstract address object.
- * @return Address family or AF_UNSPEC if guessing wasn't successful.
+ *
+ * @return Numeric address family or AF_UNSPEC
  */
 int nl_addr_guess_family(struct nl_addr *addr)
 {
@@ -672,7 +744,7 @@
  * Call getaddrinfo() for an abstract address object.
  * @arg addr		Abstract address object.
  * @arg result		Pointer to store resulting address list.
- * 
+ *
  * Calls getaddrinfo() for the specified abstract address in AI_NUMERICHOST
  * mode.
  *
@@ -750,11 +822,26 @@
  * @{
  */
 
+/**
+ * Set address family
+ * @arg addr		Abstract address object
+ * @arg family		Address family
+ *
+ * @see nl_addr_get_family()
+ */
 void nl_addr_set_family(struct nl_addr *addr, int family)
 {
 	addr->a_family = family;
 }
 
+/**
+ * Return address family
+ * @arg addr		Abstract address object
+ *
+ * @see nl_addr_set_family()
+ *
+ * @return The numeric address family or `AF_UNSPEC`
+ */
 int nl_addr_get_family(struct nl_addr *addr)
 {
 	return addr->a_family;
@@ -765,6 +852,20 @@
  * @arg addr		Abstract address object.
  * @arg buf		Buffer containing binary address.
  * @arg len		Length of buffer containing binary address.
+ *
+ * Modifies the binary address portion of the abstract address. The
+ * abstract address must be capable of holding the required amount
+ * or this function will fail.
+ *
+ * @note This function will *not* modify the prefix length. It is within
+ *       the responsibility of the caller to set the prefix length to the
+ *       desirable length.
+ *
+ * @see nl_addr_alloc()
+ * @see nl_addr_get_binary_addr()
+ * @see nl_addr_get_len()
+ *
+ * @return 0 on success or a negative error code.
  */
 int nl_addr_set_binary_addr(struct nl_addr *addr, void *buf, size_t len)
 {
@@ -772,7 +873,10 @@
 		return -NLE_RANGE;
 
 	addr->a_len = len;
-	memcpy(addr->a_addr, buf, len);
+	memset(addr->a_addr, 0, addr->a_maxsize);
+
+	if (len)
+		memcpy(addr->a_addr, buf, len);
 
 	return 0;
 }
@@ -780,6 +884,11 @@
 /**
  * Get binary address of abstract address object.
  * @arg addr		Abstract address object.
+ *
+ * @see nl_addr_set_binary_addr()
+ * @see nl_addr_get_len()
+ *
+ * @return Pointer to binary address of length nl_addr_get_len()
  */
 void *nl_addr_get_binary_addr(struct nl_addr *addr)
 {
@@ -789,20 +898,32 @@
 /**
  * Get length of binary address of abstract address object.
  * @arg addr		Abstract address object.
+ *
+ * @see nl_addr_get_binary_addr()
+ * @see nl_addr_set_binary_addr()
  */
 unsigned int nl_addr_get_len(struct nl_addr *addr)
 {
 	return addr->a_len;
 }
 
+/**
+ * Set the prefix length of an abstract address
+ * @arg addr		Abstract address object
+ * @arg prefixlen	New prefix length
+ *
+ * @see nl_addr_get_prefixlen()
+ */
 void nl_addr_set_prefixlen(struct nl_addr *addr, int prefixlen)
 {
 	addr->a_prefixlen = prefixlen;
 }
 
 /**
- * Get prefix length of abstract address object.
- * @arg addr		Abstract address object.
+ * Return prefix length of abstract address object.
+ * @arg addr		Abstract address object
+ *
+ * @see nl_addr_set_prefixlen()
  */
 unsigned int nl_addr_get_prefixlen(struct nl_addr *addr)
 {
@@ -829,7 +950,7 @@
  */
 char *nl_addr2str(struct nl_addr *addr, char *buf, size_t size)
 {
-	int i;
+	unsigned int i;
 	char tmp[16];
 
 	if (!addr || !addr->a_len) {
@@ -881,10 +1002,9 @@
  * @{
  */
 
-static struct trans_tbl afs[] = {
+static const struct trans_tbl afs[] = {
 	__ADD(AF_UNSPEC,unspec)
 	__ADD(AF_UNIX,unix)
-	__ADD(AF_LOCAL,local)
 	__ADD(AF_INET,inet)
 	__ADD(AF_AX25,ax25)
 	__ADD(AF_IPX,ipx)
@@ -900,17 +1020,49 @@
 	__ADD(AF_SECURITY,security)
 	__ADD(AF_KEY,key)
 	__ADD(AF_NETLINK,netlink)
-	__ADD(AF_ROUTE,route)
 	__ADD(AF_PACKET,packet)
 	__ADD(AF_ASH,ash)
 	__ADD(AF_ECONET,econet)
 	__ADD(AF_ATMSVC,atmsvc)
+#ifdef AF_RDS
+	__ADD(AF_RDS,rds)
+#endif
 	__ADD(AF_SNA,sna)
 	__ADD(AF_IRDA,irda)
 	__ADD(AF_PPPOX,pppox)
 	__ADD(AF_WANPIPE,wanpipe)
 	__ADD(AF_LLC,llc)
+#ifdef AF_CAN
+	__ADD(AF_CAN,can)
+#endif
+#ifdef AF_TIPC
+	__ADD(AF_TIPC,tipc)
+#endif
 	__ADD(AF_BLUETOOTH,bluetooth)
+#ifdef AF_IUCV
+	__ADD(AF_IUCV,iucv)
+#endif
+#ifdef AF_RXRPC
+	__ADD(AF_RXRPC,rxrpc)
+#endif
+#ifdef AF_ISDN
+	__ADD(AF_ISDN,isdn)
+#endif
+#ifdef AF_PHONET
+	__ADD(AF_PHONET,phonet)
+#endif
+#ifdef AF_IEEE802154
+	__ADD(AF_IEEE802154,ieee802154)
+#endif
+#ifdef AF_CAIF
+	__ADD(AF_CAIF,caif)
+#endif
+#ifdef AF_ALG
+	__ADD(AF_ALG,alg)
+#endif
+#ifdef AF_NFC
+	__ADD(AF_NFC,nfc)
+#endif
 };
 
 char *nl_af2str(int family, char *buf, size_t size)
@@ -921,7 +1073,7 @@
 int nl_str2af(const char *name)
 {
 	int fam = __str2type(name, afs, ARRAY_SIZE(afs));
-	return fam >= 0 ? fam : AF_UNSPEC;
+	return fam >= 0 ? fam : -EINVAL;
 }
 
 /** @} */
diff --git a/lib/attr.c b/lib/attr.c
index 8394330..bdf3c0f 100644
--- a/lib/attr.c
+++ b/lib/attr.c
@@ -6,10 +6,10 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/addr.h>
@@ -22,356 +22,16 @@
  * @defgroup attr Attributes
  * Netlink Attributes Construction/Parsing Interface
  *
- * \section attr_sec Netlink Attributes
- * Netlink attributes allow for data chunks of arbitary length to be
- * attached to a netlink message. Each attribute is encoded with a
- * type and length field, both 16 bits, stored in the attribute header
- * preceding the attribute data. The main advantage of using attributes
- * over packing everything into the family header is that the interface
- * stays extendable as new attributes can supersede old attributes while
- * remaining backwards compatible. Also attributes can be defined optional
- * thus avoiding the transmission of unnecessary empty data blocks.
- * Special nested attributes allow for more complex data structures to
- * be transmitted, e.g. trees, lists, etc.
- *
- * While not required, netlink attributes typically follow the family
- * header of a netlink message and must be properly aligned to NLA_ALIGNTO:
- * @code
- *   +----------------+- - -+---------------+- - -+------------+- - -+
- *   | Netlink Header | Pad | Family Header | Pad | Attributes | Pad |
- *   +----------------+- - -+---------------+- - -+------------+- - -+
- * @endcode
- *
- * The actual attributes are chained together each separately aligned to
- * NLA_ALIGNTO. The position of an attribute is defined based on the
- * length field of the preceding attributes:
- * @code
- *   +-------------+- - -+-------------+- - -+------
- *   | Attribute 1 | Pad | Attribute 2 | Pad | ...
- *   +-------------+- - -+-------------+- - -+------
- *   nla_next(attr1)------^
- * @endcode
- *
- * The attribute itself consists of the attribute header followed by
- * the actual payload also aligned to NLA_ALIGNTO. The function nla_data()
- * returns a pointer to the start of the payload while nla_len() returns
- * the length of the payload in bytes.
- *
- * \b Note: Be aware, NLA_ALIGNTO equals to 4 bytes, therefore it is not
- * safe to dereference any 64 bit data types directly.
- *
- * @code
- *    <----------- nla_total_size(payload) ----------->
- *    <-------- nla_attr_size(payload) --------->
- *   +------------------+- - -+- - - - - - - - - +- - -+
- *   | Attribute Header | Pad |     Payload      | Pad |
- *   +------------------+- - -+- - - - - - - - - +- - -+
- *   nla_data(nla)-------------^
- *                             <- nla_len(nla) ->
- * @endcode
- *
- * @subsection attr_datatypes Attribute Data Types
- * A number of basic data types are supported to simplify access and
- * validation of netlink attributes. This data type information is
- * not encoded in the attribute, both the kernel and userspace part
- * are required to share this information on their own.
- *
- * One of the major advantages of these basic types is the automatic
- * validation of each attribute based on an attribute policy. The
- * validation covers most of the checks required to safely use
- * attributes and thus keeps the individual sanity check to a minimum.
- *
- * Never access attribute payload without ensuring basic validation
- * first, attributes may:
- * - not be present even though required
- * - contain less actual payload than expected
- * - fake a attribute length which exceeds the end of the message
- * - contain unterminated character strings
- *
- * Policies are defined as array of the struct nla_policy. The array is
- * indexed with the attribute type, therefore the array must be sized
- * accordingly.
- * @code
- * static struct nla_policy my_policy[ATTR_MAX+1] = {
- * 	[ATTR_FOO] = { .type = ..., .minlen = ..., .maxlen = ... },
- * };
- *
- * err = nla_validate(attrs, attrlen, ATTR_MAX, &my_policy);
- * @endcode
- *
- * Some basic validations are performed on every attribute, regardless of type.
- * - If the attribute type exceeds the maximum attribute type specified or
- *   the attribute type is lesser-or-equal than zero, the attribute will
- *   be silently ignored.
- * - If the payload length falls below the \a minlen value the attribute
- *   will be rejected.
- * - If \a maxlen is non-zero and the payload length exceeds the \a maxlen
- *   value the attribute will be rejected.
- *
- *
- * @par Unspecific Attribute (NLA_UNSPEC)
- * This is the standard type if no type is specified. It is used for
- * binary data of arbitary length. Typically this attribute carries
- * a binary structure or a stream of bytes.
- * @par
- * @code
- * // In this example, we will assume a binary structure requires to
- * // be transmitted. The definition of the structure will typically
- * // go into a header file available to both the kernel and userspace
- * // side.
- * //
- * // Note: Be careful when putting 64 bit data types into a structure.
- * // The attribute payload is only aligned to 4 bytes, dereferencing
- * // the member may fail.
- * struct my_struct {
- *     int a;
- *     int b;
- * };
- *
- * // The validation function will not enforce an exact length match to
- * // allow structures to grow as required. Note: While it is allowed
- * // to add members to the end of the structure, changing the order or
- * // inserting members in the middle of the structure will break your
- * // binary interface.
- * static struct nla_policy my_policy[ATTR_MAX+1] = {
- *     [ATTR_MY_STRICT] = { .type = NLA_UNSPEC,
- *                          .minlen = sizeof(struct my_struct) },
- *
- * // The binary structure is appened to the message using nla_put()
- * struct my_struct foo = { .a = 1, .b = 2 };
- * nla_put(msg, ATTR_MY_STRUCT, sizeof(foo), &foo);
- *
- * // On the receiving side, a pointer to the structure pointing inside
- * // the message payload is returned by nla_get().
- * if (attrs[ATTR_MY_STRUCT])
- *     struct my_struct *foo = nla_get(attrs[ATTR_MY_STRUCT]);
- * @endcode
- *
- * @par Integers (NLA_U8, NLA_U16, NLA_U32, NLA_U64)
- * Integers come in different sizes from 8 bit to 64 bit. However, since the
- * payload length is aligned to 4 bytes, integers smaller than 32 bit are
- * only useful to enforce the maximum range of values.
- * @par
- * \b Note: There is no difference made between signed and unsigned integers.
- * The validation only enforces the minimal payload length required to store
- * an integer of specified type.
- * @par
- * @code
- * // Even though possible, it does not make sense to specify .minlen or
- * // .maxlen for integer types. The data types implies the corresponding
- * // minimal payload length.
- * static struct nla_policy my_policy[ATTR_MAX+1] = {
- *     [ATTR_FOO] = { .type = NLA_U32 },
- *
- * // Numeric values can be appended directly using the respective
- * // nla_put_uxxx() function
- * nla_put_u32(msg, ATTR_FOO, 123);
- *
- * // Same for the receiving side.
- * if (attrs[ATTR_FOO])
- *     uint32_t foo = nla_get_u32(attrs[ATTR_FOO]);
- * @endcode
- *
- * @par Character string (NLA_STRING)
- * This data type represents a NUL terminated character string of variable
- * length. For binary data streams the type NLA_UNSPEC is recommended.
- * @par
- * @code
- * // Enforce a NUL terminated character string of at most 4 characters
- * // including the NUL termination.
- * static struct nla_policy my_policy[ATTR_MAX+1] = {
- *     [ATTR_BAR] = { .type = NLA_STRING, maxlen = 4 },
- *
- * // nla_put_string() creates a string attribute of the necessary length
- * // and appends it to the message including the NUL termination.
- * nla_put_string(msg, ATTR_BAR, "some text");
- *
- * // It is safe to use the returned character string directly if the
- * // attribute has been validated as the validation enforces the proper
- * // termination of the string.
- * if (attrs[ATTR_BAR])
- *     char *text = nla_get_string(attrs[ATTR_BAR]);
- * @endcode
- *
- * @par Flag (NLA_FLAG)
- * This attribute type may be used to indicate the presence of a flag. The
- * attribute is only valid if the payload length is zero. The presence of
- * the attribute header indicates the presence of the flag.
- * @par
- * @code
- * // This attribute type is special as .minlen and .maxlen have no effect.
- * static struct nla_policy my_policy[ATTR_MAX+1] = {
- *     [ATTR_FLAG] = { .type = NLA_FLAG },
- *
- * // nla_put_flag() appends a zero sized attribute to the message.
- * nla_put_flag(msg, ATTR_FLAG);
- *
- * // There is no need for a receival function, the presence is the value.
- * if (attrs[ATTR_FLAG])
- *     // flag is present
- * @endcode
- *
- * @par Micro Seconds (NLA_MSECS)
- *
- * @par Nested Attribute (NLA_NESTED)
- * Attributes can be nested and put into a container to create groups, lists
- * or to construct trees of attributes. Nested attributes are often used to
- * pass attributes to a subsystem where the top layer has no knowledge of the
- * configuration possibilities of each subsystem.
- * @par
- * \b Note: When validating the attributes using nlmsg_validate() or
- * nlmsg_parse() it will only affect the top level attributes. Each
- * level of nested attributes must be validated seperately using
- * nla_parse_nested() or nla_validate().
- * @par
- * @code
- * // The minimal length policy may be used to enforce the presence of at
- * // least one attribute.
- * static struct nla_policy my_policy[ATTR_MAX+1] = {
- *     [ATTR_OPTS] = { .type = NLA_NESTED, minlen = NLA_HDRLEN },
- *
- * // Nested attributes are constructed by enclosing the attributes
- * // to be nested with calls to nla_nest_start() respetively nla_nest_end().
- * struct nlattr *opts = nla_nest_start(msg, ATTR_OPTS);
- * nla_put_u32(msg, ATTR_FOO, 123);
- * nla_put_string(msg, ATTR_BAR, "some text");
- * nla_nest_end(msg, opts);
- *
- * // Various methods exist to parse nested attributes, the easiest being
- * // nla_parse_nested() which also allows validation in the same step.
- * if (attrs[ATTR_OPTS]) {
- *     struct nlattr *nested[ATTR_MAX+1];
- *
- *     nla_parse_nested(nested, ATTR_MAX, attrs[ATTR_OPTS], &policy);
- *
- *     if (nested[ATTR_FOO])
- *         uint32_t foo = nla_get_u32(nested[ATTR_FOO]);
- * }
- * @endcode
- *
- * @subsection attr_exceptions Exception Based Attribute Construction
- * Often a large number of attributes are added to a message in a single
- * function. In order to simplify error handling, a second set of
- * construction functions exist which jump to a error label when they
- * fail instead of returning an error code. This second set consists
- * of macros which are named after their error code based counterpart
- * except that the name is written all uppercase.
- *
- * All of the macros jump to the target \c nla_put_failure if they fail.
- * @code
- * void my_func(struct nl_msg *msg)
- * {
- *     NLA_PUT_U32(msg, ATTR_FOO, 10);
- *     NLA_PUT_STRING(msg, ATTR_BAR, "bar");
- *
- *     return 0;
- *
- * nla_put_failure:
- *     return -NLE_NOMEM;
- * }
- * @endcode
- *
- * @subsection attr_examples Examples
- * @par Example 1.1 Constructing a netlink message with attributes.
- * @code
- * struct nl_msg *build_msg(int ifindex, struct nl_addr *lladdr, int mtu)
- * {
- *     struct nl_msg *msg;
- *     struct nlattr *info, *vlan;
- *     struct ifinfomsg ifi = {
- *         .ifi_family = AF_INET,
- *         .ifi_index = ifindex,
- *     };
- *
- *     // Allocate a new netlink message, type=RTM_SETLINK, flags=NLM_F_ECHO
- *     if (!(msg = nlmsg_alloc_simple(RTM_SETLINK, NLM_F_ECHO)))
- *         return NULL;
- *
- *     // Append the family specific header (struct ifinfomsg)
- *     if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
- *         goto nla_put_failure
- *
- *     // Append a 32 bit integer attribute to carry the MTU
- *     NLA_PUT_U32(msg, IFLA_MTU, mtu);
- *
- *     // Append a unspecific attribute to carry the link layer address
- *     NLA_PUT_ADDR(msg, IFLA_ADDRESS, lladdr);
- *
- *     // Append a container for nested attributes to carry link information
- *     if (!(info = nla_nest_start(msg, IFLA_LINKINFO)))
- *         goto nla_put_failure;
- *
- *     // Put a string attribute into the container
- *     NLA_PUT_STRING(msg, IFLA_INFO_KIND, "vlan");
- *
- *     // Append another container inside the open container to carry
- *     // vlan specific attributes
- *     if (!(vlan = nla_nest_start(msg, IFLA_INFO_DATA)))
- *         goto nla_put_failure;
- *
- *     // add vlan specific info attributes here...
- *
- *     // Finish nesting the vlan attributes and close the second container.
- *     nla_nest_end(msg, vlan);
- *
- *     // Finish nesting the link info attribute and close the first container.
- *     nla_nest_end(msg, info);
- *
- *     return msg;
- *
- * // If any of the construction macros fails, we end up here.
- * nla_put_failure:
- *     nlmsg_free(msg);
- *     return NULL;
- * }
- * @endcode
- *
- * @par Example 2.1 Parsing a netlink message with attributes.
- * @code
- * int parse_message(struct nl_msg *msg)
- * {
- *     // The policy defines two attributes: a 32 bit integer and a container
- *     // for nested attributes.
- *     struct nla_policy attr_policy[ATTR_MAX+1] = {
- *         [ATTR_FOO] = { .type = NLA_U32 },
- *         [ATTR_BAR] = { .type = NLA_NESTED },
- *     };
- *     struct nlattr *attrs[ATTR_MAX+1];
- *     int err;
- *
- *     // The nlmsg_parse() function will make sure that the message contains
- *     // enough payload to hold the header (struct my_hdr), validates any
- *     // attributes attached to the messages and stores a pointer to each
- *     // attribute in the attrs[] array accessable by attribute type.
- *     if ((err = nlmsg_parse(nlmsg_hdr(msg), sizeof(struct my_hdr), attrs,
- *                            ATTR_MAX, attr_policy)) < 0)
- *         goto errout;
- *
- *     if (attrs[ATTR_FOO]) {
- *         // It is safe to directly access the attribute payload without
- *         // any further checks since nlmsg_parse() enforced the policy.
- *         uint32_t foo = nla_get_u32(attrs[ATTR_FOO]);
- *     }
- *
- *     if (attrs[ATTR_BAR]) {
- *         struct nlattr *nested[NESTED_MAX+1];
- *
- *         // Attributes nested in a container can be parsed the same way
- *         // as top level attributes.
- *         if ((err = nla_parse_nested(nested, NESTED_MAX, attrs[ATTR_BAR],
- *                                     nested_policy)) < 0)
- *             goto errout;
- *
- *         // Process nested attributes here.
- *     }
- *
- *     err = 0;
- * errout:
- *     return err;
- * }
- * @endcode
+ * Related sections in the development guide:
+ * - @core_doc{core_attr,Netlink Attributes}
  *
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/attr.h>
+ * ~~~~
  */
 
 /**
@@ -522,15 +182,17 @@
 	[NLA_U32]	= sizeof(uint32_t),
 	[NLA_U64]	= sizeof(uint64_t),
 	[NLA_STRING]	= 1,
+	[NLA_FLAG]	= 0,
 };
 
 static int validate_nla(struct nlattr *nla, int maxtype,
 			struct nla_policy *policy)
 {
 	struct nla_policy *pt;
-	int minlen = 0, type = nla_type(nla);
+	unsigned int minlen = 0;
+	int type = nla_type(nla);
 
-	if (type <= 0 || type > maxtype)
+	if (type < 0 || type > maxtype)
 		return 0;
 
 	pt = &policy[type];
@@ -543,9 +205,6 @@
 	else if (pt->type != NLA_UNSPEC)
 		minlen = nla_attr_minlen[pt->type];
 
-	if (pt->type == NLA_FLAG && nla_len(nla) > 0)
-		return -NLE_RANGE;
-
 	if (nla_len(nla) < minlen)
 		return -NLE_RANGE;
 
@@ -591,24 +250,24 @@
 	nla_for_each_attr(nla, head, len, rem) {
 		int type = nla_type(nla);
 
-		if (type == 0) {
-			fprintf(stderr, "Illegal nla->nla_type == 0\n");
+		if (type > maxtype)
 			continue;
+
+		if (policy) {
+			err = validate_nla(nla, maxtype, policy);
+			if (err < 0)
+				goto errout;
 		}
 
-		if (type <= maxtype) {
-			if (policy) {
-				err = validate_nla(nla, maxtype, policy);
-				if (err < 0)
-					goto errout;
-			}
+		if (tb[type])
+			NL_DBG(1, "Attribute of type %#x found multiple times in message, "
+				  "previous attribute is being ignored.\n", type);
 
-			tb[type] = nla;
-		}
+		tb[type] = nla;
 	}
 
 	if (rem > 0)
-		fprintf(stderr, "netlink: %d bytes leftover after parsing "
+		NL_DBG(1, "netlink: %d bytes leftover after parsing "
 		       "attributes.\n", rem);
 
 	err = 0;
@@ -628,8 +287,7 @@
  * than the maximum type specified will be silently ignored in order to
  * maintain backwards compatibility.
  *
- * See \ref attr_datatypes for more details on what kind of validation
- * checks are performed on each attribute data type.
+ * See section @core_doc{core_attr_parse,Attribute Parsing} for more details.
  *
  * @return 0 on success or a negative error code.
  */
@@ -805,20 +463,22 @@
 
 	tlen = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) + nla_total_size(attrlen);
 
-	if ((tlen + msg->nm_nlh->nlmsg_len) > msg->nm_size)
+	if (tlen > msg->nm_size)
 		return NULL;
 
 	nla = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
 	nla->nla_type = attrtype;
 	nla->nla_len = nla_attr_size(attrlen);
 
-	memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen));
+	if (attrlen)
+		memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen));
 	msg->nm_nlh->nlmsg_len = tlen;
 
-	NL_DBG(2, "msg %p: Reserved %d bytes at offset +%td for attr %d "
-		  "nlmsg_len=%d\n", msg, attrlen,
+	NL_DBG(2, "msg %p: attr <%p> %d: Reserved %d (%d) bytes at offset +%td "
+		  "nlmsg_len=%d\n", msg, nla, nla->nla_type,
+		  nla_total_size(attrlen), attrlen,
 		  (void *) nla - nlmsg_data(msg->nm_nlh),
-		  attrtype, msg->nm_nlh->nlmsg_len);
+		  msg->nm_nlh->nlmsg_len);
 
 	return nla;
 }
@@ -848,9 +508,12 @@
 	if (!nla)
 		return -NLE_NOMEM;
 
-	memcpy(nla_data(nla), data, datalen);
-	NL_DBG(2, "msg %p: Wrote %d bytes at offset +%td for attr %d\n",
-	       msg, datalen, (void *) nla - nlmsg_data(msg->nm_nlh), attrtype);
+	if (datalen > 0) {
+		memcpy(nla_data(nla), data, datalen);
+		NL_DBG(2, "msg %p: attr <%p> %d: Wrote %d bytes at offset +%td\n",
+		       msg, nla, nla->nla_type, datalen,
+		       (void *) nla - nlmsg_data(msg->nm_nlh));
+	}
 
 	return 0;
 }
@@ -991,9 +654,10 @@
  */
 uint64_t nla_get_u64(struct nlattr *nla)
 {
-	uint64_t tmp;
+	uint64_t tmp = 0;
 
-	nla_memcpy(&tmp, nla, sizeof(tmp));
+	if (nla && nla_len(nla) >= sizeof(tmp))
+		memcpy(&tmp, nla_data(nla), sizeof(tmp));
 
 	return tmp;
 }
@@ -1113,7 +777,10 @@
  */
 int nla_put_nested(struct nl_msg *msg, int attrtype, struct nl_msg *nested)
 {
-	return nla_put(msg, attrtype, nlmsg_len(nested->nm_nlh),
+	NL_DBG(2, "msg %p: attr <> %d: adding msg %p as nested attribute\n",
+		msg, attrtype, nested);
+
+	return nla_put(msg, attrtype, nlmsg_datalen(nested->nm_nlh),
 		       nlmsg_data(nested->nm_nlh));
 }
 
@@ -1132,6 +799,9 @@
 	if (nla_put(msg, attrtype, 0, NULL) < 0)
 		return NULL;
 
+	NL_DBG(2, "msg %p: attr <%p> %d: starting nesting\n",
+		msg, start, start->nla_type);
+
 	return start;
 }
 
@@ -1146,12 +816,66 @@
  */
 int nla_nest_end(struct nl_msg *msg, struct nlattr *start)
 {
-	start->nla_len = (unsigned char *) nlmsg_tail(msg->nm_nlh) -
-				(unsigned char *) start;
+	size_t pad, len;
+
+	len = (void *) nlmsg_tail(msg->nm_nlh) - (void *) start;
+
+	if (len == NLA_HDRLEN) {
+		/*
+		 * Kernel can't handle empty nested attributes, trim the
+		 * attribute header again
+		 */
+		nla_nest_cancel(msg, start);
+
+		return 0;
+	}
+
+	start->nla_len = len;
+
+	pad = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len;
+	if (pad > 0) {
+		/*
+		 * Data inside attribute does not end at a alignment boundry.
+		 * Pad accordingly and accoun for the additional space in
+		 * the message. nlmsg_reserve() may never fail in this situation,
+		 * the allocate message buffer must be a multiple of NLMSG_ALIGNTO.
+		 */
+		if (!nlmsg_reserve(msg, pad, 0))
+			BUG();
+
+		NL_DBG(2, "msg %p: attr <%p> %d: added %zu bytes of padding\n",
+			msg, start, start->nla_type, pad);
+	}
+
+	NL_DBG(2, "msg %p: attr <%p> %d: closing nesting, len=%u\n",
+		msg, start, start->nla_type, start->nla_len);
+
 	return 0;
 }
 
 /**
+ * Cancel the addition of a nested attribute
+ * @arg msg		Netlink message
+ * @arg attr		Nested netlink attribute
+ *
+ * Removes any partially added nested Netlink attribute from the message
+ * by resetting the message to the size before the call to nla_nest_start()
+ * and by overwriting any potentially touched message segments with 0.
+ */
+void nla_nest_cancel(struct nl_msg *msg, struct nlattr *attr)
+{
+	ssize_t len;
+
+	len = (void *) nlmsg_tail(msg->nm_nlh) - (void *) attr;
+	if (len < 0)
+		BUG();
+	else if (len > 0) {
+		msg->nm_nlh->nlmsg_len -= len;
+		memset(nlmsg_tail(msg->nm_nlh), 0, len);
+	}
+}
+
+/**
  * Create attribute index based on nested attribute
  * @arg tb		Index array to be filled (maxtype+1 elements).
  * @arg maxtype		Maximum attribute type expected and accepted.
@@ -1170,6 +894,17 @@
 	return nla_parse(tb, maxtype, nla_data(nla), nla_len(nla), policy);
 }
 
+/**
+ * Return true if attribute has NLA_F_NESTED flag set
+ * @arg attr		Netlink attribute
+ *
+ * @return True if attribute has NLA_F_NESTED flag set, oterhwise False.
+ */
+int nla_is_nested(struct nlattr *attr)
+{
+	return !!(attr->nla_type & NLA_F_NESTED);
+}
+
 /** @} */
 
 /** @} */
diff --git a/lib/cache.c b/lib/cache.c
index 2b24946..b4f9649 100644
--- a/lib/cache.c
+++ b/lib/cache.c
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
@@ -37,13 +37,23 @@
  *                                |      |                 Core Netlink
  * @endcode
  * 
+ * Related sections in the development guide:
+ * - @core_doc{core_cache, Caching System}
+ *
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/cache.h>
+ * ~~~~
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/object.h>
+#include <netlink/hashtable.h>
 #include <netlink/utils.h>
 
 /**
@@ -67,15 +77,12 @@
  */
 int nl_cache_nitems_filter(struct nl_cache *cache, struct nl_object *filter)
 {
-	struct nl_object_ops *ops;
 	struct nl_object *obj;
 	int nitems = 0;
 
 	if (cache->c_ops == NULL)
 		BUG();
 
-	ops = cache->c_ops->co_obj_ops;
-	
 	nl_list_for_each_entry(obj, &cache->c_items, ce_list) {
 		if (filter && !nl_object_match_filter(obj, filter))
 			continue;
@@ -160,15 +167,18 @@
 /** @} */
 
 /**
- * @name Cache Creation/Deletion
+ * @name Cache Allocation/Deletion
  * @{
  */
 
 /**
- * Allocate an empty cache
- * @arg ops		cache operations to base the cache on
- * 
- * @return A newly allocated and initialized cache.
+ * Allocate new cache
+ * @arg ops		Cache operations
+ *
+ * Allocate and initialize a new cache based on the cache operations
+ * provided.
+ *
+ * @return Allocated cache or NULL if allocation failed.
  */
 struct nl_cache *nl_cache_alloc(struct nl_cache_ops *ops)
 {
@@ -180,12 +190,46 @@
 
 	nl_init_list_head(&cache->c_items);
 	cache->c_ops = ops;
+	cache->c_flags |= ops->co_flags;
+	cache->c_refcnt = 1;
+
+	/*
+	 * If object type provides a hash keygen
+	 * functions, allocate a hash table for the
+	 * cache objects for faster lookups
+	 */
+	if (ops->co_obj_ops->oo_keygen) {
+		int hashtable_size;
+
+		if (ops->co_hash_size)
+			hashtable_size = ops->co_hash_size;
+		else
+			hashtable_size = NL_MAX_HASH_ENTRIES;
+
+		cache->hashtable = nl_hash_table_alloc(hashtable_size);
+	}
 
 	NL_DBG(2, "Allocated cache %p <%s>.\n", cache, nl_cache_name(cache));
 
 	return cache;
 }
 
+/**
+ * Allocate new cache and fill it
+ * @arg ops		Cache operations
+ * @arg sock		Netlink socket
+ * @arg result		Result pointer
+ *
+ * Allocate new cache and fill it. Equivalent to calling:
+ * @code
+ * cache = nl_cache_alloc(ops);
+ * nl_cache_refill(sock, cache);
+ * @endcode
+ *
+ * @see nl_cache_alloc
+ *
+ * @return 0 on success or a negative error code.
+ */
 int nl_cache_alloc_and_fill(struct nl_cache_ops *ops, struct nl_sock *sock,
 			    struct nl_cache **result)
 {
@@ -205,20 +249,30 @@
 }
 
 /**
- * Allocate an empty cache based on type name
+ * Allocate new cache based on type name
  * @arg kind		Name of cache type
- * @return A newly allocated and initialized cache.
+ * @arg result		Result pointer
+ *
+ * Lookup cache ops via nl_cache_ops_lookup() and allocate the cache
+ * by calling nl_cache_alloc(). Stores the allocated cache in the
+ * result pointer provided.
+ *
+ * @see nl_cache_alloc
+ *
+ * @return 0 on success or a negative error code.
  */
 int nl_cache_alloc_name(const char *kind, struct nl_cache **result)
 {
 	struct nl_cache_ops *ops;
 	struct nl_cache *cache;
 
-	ops = nl_cache_ops_lookup(kind);
+	ops = nl_cache_ops_lookup_safe(kind);
 	if (!ops)
 		return -NLE_NOCACHE;
 
-	if (!(cache = nl_cache_alloc(ops)))
+	cache = nl_cache_alloc(ops);
+	nl_cache_ops_put(ops);
+	if (!cache)
 		return -NLE_NOMEM;
 
 	*result = cache;
@@ -226,16 +280,24 @@
 }
 
 /**
- * Allocate a new cache containing a subset of a cache
- * @arg orig		Original cache to be based on
- * @arg filter		Filter defining the subset to be filled into new cache
+ * Allocate new cache containing a subset of an existing cache
+ * @arg orig		Original cache to base new cache on
+ * @arg filter		Filter defining the subset to be filled into the new cache
+ *
+ * Allocates a new cache matching the type of the cache specified by
+ * \p orig. Iterates over the \p orig cache applying the specified
+ * \p filter and copies all objects that match to the new cache.
+ *
+ * The copied objects are clones but do not contain a reference to each
+ * other. Later modifications to objects in the original cache will
+ * not affect objects in the new cache.
+ *
  * @return A newly allocated cache or NULL.
  */
 struct nl_cache *nl_cache_subset(struct nl_cache *orig,
 				 struct nl_object *filter)
 {
 	struct nl_cache *cache;
-	struct nl_object_ops *ops;
 	struct nl_object *obj;
 
 	if (!filter)
@@ -245,7 +307,8 @@
 	if (!cache)
 		return NULL;
 
-	ops = orig->c_ops->co_obj_ops;
+	NL_DBG(2, "Filling subset of cache %p <%s> with filter %p into %p\n",
+	       orig, nl_cache_name(orig), filter, cache);
 
 	nl_list_for_each_entry(obj, &orig->c_items, ce_list) {
 		if (!nl_object_match_filter(obj, filter))
@@ -258,37 +321,107 @@
 }
 
 /**
- * Clear a cache.
- * @arg cache		cache to clear
+ * Allocate new cache and copy the contents of an existing cache
+ * @arg cache		Original cache to base new cache on
  *
- * Removes all elements of a cache.
+ * Allocates a new cache matching the type of the cache specified by
+ * \p cache. Iterates over the \p cache cache and copies all objects
+ * to the new cache.
+ *
+ * The copied objects are clones but do not contain a reference to each
+ * other. Later modifications to objects in the original cache will
+ * not affect objects in the new cache.
+ *
+ * @return A newly allocated cache or NULL.
+ */
+struct nl_cache *nl_cache_clone(struct nl_cache *cache)
+{
+	struct nl_cache_ops *ops = nl_cache_get_ops(cache);
+	struct nl_cache *clone;
+	struct nl_object *obj;
+
+	clone = nl_cache_alloc(ops);
+	if (!clone)
+		return NULL;
+
+	NL_DBG(2, "Cloning %p into %p\n", cache, clone);
+
+	nl_list_for_each_entry(obj, &cache->c_items, ce_list)
+		nl_cache_add(clone, obj);
+
+	return clone;
+}
+
+/**
+ * Remove all objects of a cache.
+ * @arg cache		Cache to clear
+ *
+ * The objects are unliked/removed from the cache by calling
+ * nl_cache_remove() on each object in the cache. If any of the objects
+ * to not contain any further references to them, those objects will
+ * be freed.
+ *
+ * Unlike with nl_cache_free(), the cache is not freed just emptied.
  */
 void nl_cache_clear(struct nl_cache *cache)
 {
 	struct nl_object *obj, *tmp;
 
-	NL_DBG(1, "Clearing cache %p <%s>...\n", cache, nl_cache_name(cache));
+	NL_DBG(2, "Clearing cache %p <%s>...\n", cache, nl_cache_name(cache));
 
 	nl_list_for_each_entry_safe(obj, tmp, &cache->c_items, ce_list)
 		nl_cache_remove(obj);
 }
 
+static void __nl_cache_free(struct nl_cache *cache)
+{
+	nl_cache_clear(cache);
+
+	if (cache->hashtable)
+		nl_hash_table_free(cache->hashtable);
+
+	NL_DBG(2, "Freeing cache %p <%s>...\n", cache, nl_cache_name(cache));
+	free(cache);
+}
+
+/**
+ * Increase reference counter of cache
+ * @arg cache		Cache
+ */
+void nl_cache_get(struct nl_cache *cache)
+{
+	cache->c_refcnt++;
+
+	NL_DBG(3, "Incremented cache %p <%s> reference count to %d\n",
+	       cache, nl_cache_name(cache), cache->c_refcnt);
+}
+
 /**
  * Free a cache.
  * @arg cache		Cache to free.
  *
- * Removes all elements of a cache and frees all memory.
+ * Calls nl_cache_clear() to remove all objects associated with the
+ * cache and frees the cache afterwards.
  *
- * @note Use this function if you are working with allocated caches.
+ * @see nl_cache_clear()
  */
 void nl_cache_free(struct nl_cache *cache)
 {
 	if (!cache)
 		return;
 
-	nl_cache_clear(cache);
-	NL_DBG(1, "Freeing cache %p <%s>...\n", cache, nl_cache_name(cache));
-	free(cache);
+	cache->c_refcnt--;
+
+	NL_DBG(3, "Decremented cache %p <%s> reference count, %d remaining\n",
+	       cache, nl_cache_name(cache), cache->c_refcnt);
+
+	if (cache->c_refcnt <= 0)
+		__nl_cache_free(cache);
+}
+
+void nl_cache_put(struct nl_cache *cache)
+{
+	return nl_cache_free(cache);
 }
 
 /** @} */
@@ -300,35 +433,60 @@
 
 static int __cache_add(struct nl_cache *cache, struct nl_object *obj)
 {
+	int ret;
+
 	obj->ce_cache = cache;
 
+	if (cache->hashtable) {
+		ret = nl_hash_table_add(cache->hashtable, obj);
+		if (ret < 0) {
+			obj->ce_cache = NULL;
+			return ret;
+		}
+	}
+
 	nl_list_add_tail(&obj->ce_list, &cache->c_items);
 	cache->c_nitems++;
 
-	NL_DBG(1, "Added %p to cache %p <%s>.\n",
-	       obj, cache, nl_cache_name(cache));
+	NL_DBG(3, "Added object %p to cache %p <%s>, nitems %d\n",
+	       obj, cache, nl_cache_name(cache), cache->c_nitems);
 
 	return 0;
 }
 
 /**
- * Add object to a cache.
- * @arg cache		Cache to add object to
+ * Add object to cache.
+ * @arg cache		Cache
  * @arg obj		Object to be added to the cache
  *
- * Adds the given object to the specified cache. The object is cloned
- * if it has been added to another cache already.
+ * Adds the object \p obj to the specified \p cache. In case the object
+ * is already associated with another cache, the object is cloned before
+ * adding it to the cache. In this case, the sole reference to the object
+ * will be the one of the cache. Therefore clearing/freeing the cache
+ * will result in the object being freed again.
+ *
+ * If the object has not been associated with a cache yet, the reference
+ * counter of the object is incremented to account for the additional
+ * reference.
+ *
+ * The type of the object and cache must match, otherwise an error is
+ * returned (-NLE_OBJ_MISMATCH).
+ *
+ * @see nl_cache_move()
  *
  * @return 0 or a negative error code.
  */
 int nl_cache_add(struct nl_cache *cache, struct nl_object *obj)
 {
 	struct nl_object *new;
+	int ret = 0;
 
 	if (cache->c_ops->co_obj_ops != obj->ce_ops)
 		return -NLE_OBJ_MISMATCH;
 
 	if (!nl_list_empty(&obj->ce_list)) {
+		NL_DBG(3, "Object %p already in cache, cloning new object\n", obj);
+
 		new = nl_object_clone(obj);
 		if (!new)
 			return -NLE_NOMEM;
@@ -337,7 +495,11 @@
 		new = obj;
 	}
 
-	return __cache_add(cache, new);
+	ret = __cache_add(cache, new);
+	if (ret < 0)
+		nl_object_put(new);
+
+	return ret;
 }
 
 /**
@@ -345,8 +507,16 @@
  * @arg cache		Cache to move object to.
  * @arg obj		Object subject to be moved
  *
- * Removes the given object from its associated cache if needed
- * and adds it to the new cache.
+ * Removes the the specified object \p obj from its associated cache
+ * and moves it to another cache.
+ *
+ * If the object is not associated with a cache, the function behaves
+ * just like nl_cache_add().
+ *
+ * The type of the object and cache must match, otherwise an error is
+ * returned (-NLE_OBJ_MISMATCH).
+ *
+ * @see nl_cache_add()
  *
  * @return 0 on success or a negative error code.
  */
@@ -355,7 +525,8 @@
 	if (cache->c_ops->co_obj_ops != obj->ce_ops)
 		return -NLE_OBJ_MISMATCH;
 
-	NL_DBG(3, "Moving object %p to cache %p\n", obj, cache);
+	NL_DBG(3, "Moving object %p from cache %p to cache %p\n",
+	       obj, obj->ce_cache, cache);
 	
 	/* Acquire reference, if already in a cache this will be
 	 * reverted during removal */
@@ -368,56 +539,39 @@
 }
 
 /**
- * Removes an object from a cache.
- * @arg obj		Object to remove from its cache
+ * Remove object from cache.
+ * @arg obj		Object to remove from cache
  *
- * Removes the object \c obj from the cache it is assigned to, since
- * an object can only be assigned to one cache at a time, the cache
- * must ne be passed along with it.
+ * Removes the object \c obj from the cache it is associated with. The
+ * reference counter of the object will be decremented. If the reference
+ * to the object was the only one remaining, the object will be freed.
+ *
+ * If no cache is associated with the object, this function is a NOP.
  */
 void nl_cache_remove(struct nl_object *obj)
 {
+	int ret;
 	struct nl_cache *cache = obj->ce_cache;
 
 	if (cache == NULL)
 		return;
 
+	if (cache->hashtable) {
+		ret = nl_hash_table_del(cache->hashtable, obj);
+		if (ret < 0)
+			NL_DBG(2, "Failed to delete %p from cache %p <%s>.\n",
+			       obj, cache, nl_cache_name(cache));
+	}
+
 	nl_list_del(&obj->ce_list);
 	obj->ce_cache = NULL;
 	nl_object_put(obj);
 	cache->c_nitems--;
 
-	NL_DBG(1, "Deleted %p from cache %p <%s>.\n",
+	NL_DBG(2, "Deleted object %p from cache %p <%s>.\n",
 	       obj, cache, nl_cache_name(cache));
 }
 
-/**
- * Search for an object in a cache
- * @arg cache		Cache to search in.
- * @arg needle		Object to look for.
- *
- * Iterates over the cache and looks for an object with identical
- * identifiers as the needle.
- *
- * @return Reference to object or NULL if not found.
- * @note The returned object must be returned via nl_object_put().
- */
-struct nl_object *nl_cache_search(struct nl_cache *cache,
-				  struct nl_object *needle)
-{
-	struct nl_object *obj;
-
-	nl_list_for_each_entry(obj, &cache->c_items, ce_list) {
-		if (nl_object_identical(obj, needle)) {
-			nl_object_get(obj);
-			return obj;
-		}
-	}
-
-	return NULL;
-}
-
-
 /** @} */
 
 /**
@@ -426,24 +580,77 @@
  */
 
 /**
- * Request a full dump from the kernel to fill a cache
- * @arg sk		Netlink socket.
- * @arg cache		Cache subjected to be filled.
+ * Set synchronization arg1 of cache
+ * @arg cache		Cache
+ * @arg arg		argument
  *
- * Send a dumping request to the kernel causing it to dump all objects
- * related to the specified cache to the netlink socket.
- *
- * Use nl_cache_pickup() to read the objects from the socket and fill them
- * into a cache.
+ * Synchronization arguments are used to specify filters when
+ * requesting dumps from the kernel.
  */
-int nl_cache_request_full_dump(struct nl_sock *sk, struct nl_cache *cache)
+void nl_cache_set_arg1(struct nl_cache *cache, int arg)
 {
-	NL_DBG(2, "Requesting dump from kernel for cache %p <%s>...\n",
-	          cache, nl_cache_name(cache));
+        cache->c_iarg1 = arg;
+}
+
+/**
+ * Set synchronization arg2 of cache
+ * @arg cache		Cache
+ * @arg arg		argument
+ *
+ * Synchronization arguments are used to specify filters when
+ * requesting dumps from the kernel.
+ */
+void nl_cache_set_arg2(struct nl_cache *cache, int arg)
+{
+        cache->c_iarg2 = arg;
+}
+
+/**
+ * Set cache flags
+ * @arg cache		Cache
+ * @arg flags		Flags
+ */
+void nl_cache_set_flags(struct nl_cache *cache, unsigned int flags)
+{
+	cache->c_flags |= flags;
+}
+
+/**
+ * Invoke the request-update operation
+ * @arg sk		Netlink socket.
+ * @arg cache		Cache
+ *
+ * This function causes the \e request-update function of the cache
+ * operations to be invoked. This usually causes a dump request to
+ * be sent over the netlink socket which triggers the kernel to dump
+ * all objects of a specific type to be dumped onto the netlink
+ * socket for pickup.
+ *
+ * The behaviour of this function depends on the implemenation of
+ * the \e request_update function of each individual type of cache.
+ *
+ * This function will not have any effects on the cache (unless the
+ * request_update implementation of the cache operations does so).
+ *
+ * Use nl_cache_pickup() to pick-up (read) the objects from the socket
+ * and fill them into the cache.
+ *
+ * @see nl_cache_pickup(), nl_cache_resync()
+ *
+ * @return 0 on success or a negative error code.
+ */
+static int nl_cache_request_full_dump(struct nl_sock *sk,
+				      struct nl_cache *cache)
+{
+	if (sk->s_proto != cache->c_ops->co_protocol)
+		return -NLE_PROTO_MISMATCH;
 
 	if (cache->c_ops->co_request_update == NULL)
 		return -NLE_OPNOTSUPP;
 
+	NL_DBG(2, "Requesting update from kernel for cache %p <%s>\n",
+	          cache, nl_cache_name(cache));
+
 	return cache->c_ops->co_request_update(cache, sk);
 }
 
@@ -456,13 +663,24 @@
 static int update_msg_parser(struct nl_msg *msg, void *arg)
 {
 	struct update_xdata *x = arg;
-	
-	return nl_cache_parse(x->ops, &msg->nm_src, msg->nm_nlh, x->params);
+	int ret = 0;
+
+	ret = nl_cache_parse(x->ops, &msg->nm_src, msg->nm_nlh, x->params);
+	if (ret == -NLE_EXIST)
+		return NL_SKIP;
+	else
+		return ret;
 }
 /** @endcond */
 
-int __cache_pickup(struct nl_sock *sk, struct nl_cache *cache,
-		   struct nl_parser_param *param)
+/**
+ * Pick-up a netlink request-update with your own parser
+ * @arg sk		Netlink socket
+ * @arg cache		Cache
+ * @arg param		Parser parameters
+ */
+static int __cache_pickup(struct nl_sock *sk, struct nl_cache *cache,
+			  struct nl_parser_param *param)
 {
 	int err;
 	struct nl_cb *cb;
@@ -471,8 +689,8 @@
 		.params = param,
 	};
 
-	NL_DBG(1, "Picking up answer for cache %p <%s>...\n",
-		  cache, nl_cache_name(cache));
+	NL_DBG(2, "Picking up answer for cache %p <%s>\n",
+	       cache, nl_cache_name(cache));
 
 	cb = nl_cb_clone(sk->s_cb);
 	if (cb == NULL)
@@ -482,9 +700,8 @@
 
 	err = nl_recvmsgs(sk, cb);
 	if (err < 0)
-		NL_DBG(2, "While picking up for %p <%s>, recvmsgs() returned " \
-		       "%d: %s", cache, nl_cache_name(cache),
-		       err, nl_geterror(err));
+		NL_DBG(2, "While picking up for %p <%s>, recvmsgs() returned %d: %s\n",
+		       cache, nl_cache_name(cache), err, nl_geterror(err));
 
 	nl_cb_put(cb);
 
@@ -493,7 +710,21 @@
 
 static int pickup_cb(struct nl_object *c, struct nl_parser_param *p)
 {
-	return nl_cache_add((struct nl_cache *) p->pp_arg, c);
+	struct nl_cache *cache = (struct nl_cache *)p->pp_arg;
+	struct nl_object *old;
+
+	old = nl_cache_search(cache, c);
+	if (old) {
+		if (nl_object_update(old, c) == 0) {
+			nl_object_put(old);
+			return 0;
+		}
+
+		nl_cache_remove(old);
+		nl_object_put(old);
+	}
+
+	return nl_cache_add(cache, c);
 }
 
 /**
@@ -502,7 +733,10 @@
  * @arg cache		Cache to put items into.
  *
  * Waits for netlink messages to arrive, parses them and puts them into
- * the specified cache.
+ * the specified cache. If an old object with same key attributes is
+ * present in the cache, it is replaced with the new object.
+ * If the old object type supports an update operation, an update is
+ * attempted before a replace.
  *
  * @return 0 on success or a negative error code.
  */
@@ -513,6 +747,9 @@
 		.pp_arg = cache,
 	};
 
+	if (sk->s_proto != cache->c_ops->co_protocol)
+		return -NLE_PROTO_MISMATCH;
+
 	return __cache_pickup(sk, cache, &p);
 }
 
@@ -526,6 +763,18 @@
 	case NL_ACT_DEL:
 		old = nl_cache_search(cache, obj);
 		if (old) {
+			/*
+			 * Some objects types might support merging the new
+			 * object with the old existing cache object.
+			 * Handle them first.
+			 */
+			if (nl_object_update(old, obj) == 0) {
+				if (cb)
+					cb(cache, old, NL_ACT_CHANGE, data);
+				nl_object_put(old);
+				return 0;
+			}
+
 			nl_cache_remove(old);
 			if (type->mt_act == NL_ACT_DEL) {
 				if (cb)
@@ -568,6 +817,9 @@
 			return cache_include(cache, obj, &ops->co_msgtypes[i],
 					     change_cb, data);
 
+	NL_DBG(3, "Object %p does not seem to belong to cache %p <%s>\n",
+	       obj, cache, nl_cache_name(cache));
+
 	return -NLE_MSGTYPE_NOSUPPORT;
 }
 
@@ -582,6 +834,7 @@
 		    change_func_t change_cb, void *data)
 {
 	struct nl_object *obj, *next;
+	struct nl_af_group *grp;
 	struct nl_cache_assoc ca = {
 		.ca_cache = cache,
 		.ca_change = change_cb,
@@ -593,18 +846,35 @@
 	};
 	int err;
 
+	if (sk->s_proto != cache->c_ops->co_protocol)
+		return -NLE_PROTO_MISMATCH;
+
 	NL_DBG(1, "Resyncing cache %p <%s>...\n", cache, nl_cache_name(cache));
 
 	/* Mark all objects so we can see if some of them are obsolete */
 	nl_cache_mark_all(cache);
 
-	err = nl_cache_request_full_dump(sk, cache);
-	if (err < 0)
-		goto errout;
+	grp = cache->c_ops->co_groups;
+	do {
+		if (grp && grp->ag_group &&
+			(cache->c_flags & NL_CACHE_AF_ITER))
+			nl_cache_set_arg1(cache, grp->ag_family);
 
-	err = __cache_pickup(sk, cache, &p);
-	if (err < 0)
-		goto errout;
+restart:
+		err = nl_cache_request_full_dump(sk, cache);
+		if (err < 0)
+			goto errout;
+
+		err = __cache_pickup(sk, cache, &p);
+		if (err == -NLE_DUMP_INTR)
+			goto restart;
+		else if (err < 0)
+			goto errout;
+
+		if (grp)
+			grp++;
+	} while (grp && grp->ag_group &&
+		(cache->c_flags & NL_CACHE_AF_ITER));
 
 	nl_list_for_each_entry_safe(obj, next, &cache->c_items, ce_list) {
 		if (nl_object_is_marked(obj)) {
@@ -660,7 +930,10 @@
  * @arg msg		netlink message
  *
  * Parses a netlink message by calling the cache specific message parser
- * and adds the new element to the cache.
+ * and adds the new element to the cache. If an old object with same key
+ * attributes is present in the cache, it is replaced with the new object.
+ * If the old object type supports an update operation, an update is
+ * attempted before a replace.
  *
  * @return 0 or a negative error code.
  */
@@ -686,17 +959,40 @@
  */
 int nl_cache_refill(struct nl_sock *sk, struct nl_cache *cache)
 {
+	struct nl_af_group *grp;
 	int err;
 
-	err = nl_cache_request_full_dump(sk, cache);
-	if (err < 0)
-		return err;
+	if (sk->s_proto != cache->c_ops->co_protocol)
+		return -NLE_PROTO_MISMATCH;
 
-	NL_DBG(2, "Upading cache %p <%s>, request sent, waiting for dump...\n",
-	       cache, nl_cache_name(cache));
 	nl_cache_clear(cache);
+	grp = cache->c_ops->co_groups;
+	do {
+		if (grp && grp->ag_group &&
+			(cache->c_flags & NL_CACHE_AF_ITER))
+			nl_cache_set_arg1(cache, grp->ag_family);
 
-	return nl_cache_pickup(sk, cache);
+restart:
+		err = nl_cache_request_full_dump(sk, cache);
+		if (err < 0)
+			return err;
+
+		NL_DBG(2, "Updating cache %p <%s> for family %u, request sent, waiting for reply\n",
+		       cache, nl_cache_name(cache), grp ? grp->ag_family : AF_UNSPEC);
+
+		err = nl_cache_pickup(sk, cache);
+		if (err == -NLE_DUMP_INTR) {
+			NL_DBG(2, "Dump interrupted, restarting!\n");
+			goto restart;
+		} else if (err < 0)
+			break;
+
+		if (grp)
+			grp++;
+	} while (grp && grp->ag_group &&
+			(cache->c_flags & NL_CACHE_AF_ITER));
+
+	return err;
 }
 
 /** @} */
@@ -705,17 +1001,105 @@
  * @name Utillities
  * @{
  */
+static struct nl_object *__cache_fast_lookup(struct nl_cache *cache,
+					     struct nl_object *needle)
+{
+	struct nl_object *obj;
+
+	obj = nl_hash_table_lookup(cache->hashtable, needle);
+	if (obj) {
+	    nl_object_get(obj);
+	    return obj;
+	}
+
+	return NULL;
+}
 
 /**
- * Mark all objects in a cache
- * @arg cache		Cache to mark all objects in
+ * Search object in cache
+ * @arg cache		Cache
+ * @arg needle		Object to look for.
+ *
+ * Searches the cache for an object which matches the object \p needle.
+ * The function nl_object_identical() is used to determine if the
+ * objects match. If a matching object is found, the reference counter
+ * is incremented and the object is returned.
+ * 
+ * Therefore, if an object is returned, the reference to the object
+ * must be returned by calling nl_object_put() after usage.
+ *
+ * @return Reference to object or NULL if not found.
+ */
+struct nl_object *nl_cache_search(struct nl_cache *cache,
+				  struct nl_object *needle)
+{
+	struct nl_object *obj;
+
+	if (cache->hashtable)
+		return __cache_fast_lookup(cache, needle);
+
+	nl_list_for_each_entry(obj, &cache->c_items, ce_list) {
+		if (nl_object_identical(obj, needle)) {
+			nl_object_get(obj);
+			return obj;
+		}
+	}
+
+	return NULL;
+}
+
+/**
+ * Find object in cache
+ * @arg cache		Cache
+ * @arg filter		object acting as a filter
+ *
+ * Searches the cache for an object which matches the object filter.
+ * If the filter attributes matches the object type id attributes,
+ * and the cache supports hash lookups, a faster hashtable lookup
+ * is used to return the object. Else, function nl_object_match_filter() is
+ * used to determine if the objects match. If a matching object is
+ * found, the reference counter is incremented and the object is returned.
+ *
+ * Therefore, if an object is returned, the reference to the object
+ * must be returned by calling nl_object_put() after usage.
+ *
+ * @return Reference to object or NULL if not found.
+ */
+struct nl_object *nl_cache_find(struct nl_cache *cache,
+				struct nl_object *filter)
+{
+	struct nl_object *obj;
+
+	if (cache->c_ops == NULL)
+		BUG();
+
+	if ((nl_object_get_id_attrs(filter) == filter->ce_mask)
+		&& cache->hashtable)
+		return __cache_fast_lookup(cache, filter);
+
+	nl_list_for_each_entry(obj, &cache->c_items, ce_list) {
+		if (nl_object_match_filter(obj, filter)) {
+			nl_object_get(obj);
+			return obj;
+		}
+	}
+
+	return NULL;
+}
+
+/**
+ * Mark all objects of a cache
+ * @arg cache		Cache
+ *
+ * Marks all objects of a cache by calling nl_object_mark() on each
+ * object associated with the cache.
  */
 void nl_cache_mark_all(struct nl_cache *cache)
 {
 	struct nl_object *obj;
 
-	NL_DBG(2, "Marking all objects in cache %p <%s>...\n",
-	          cache, nl_cache_name(cache));
+	NL_DBG(2, "Marking all objects in cache %p <%s>\n",
+	       cache, nl_cache_name(cache));
 
 	nl_list_for_each_entry(obj, &cache->c_items, ce_list)
 		nl_object_mark(obj);
@@ -757,7 +1141,7 @@
 	struct nl_object_ops *ops;
 	struct nl_object *obj;
 
-	NL_DBG(2, "Dumping cache %p <%s> filter %p\n",
+	NL_DBG(2, "Dumping cache %p <%s> with filter %p\n",
 	       cache, nl_cache_name(cache), filter);
 
 	if (type > NL_DUMP_MAX || type < 0)
@@ -770,6 +1154,9 @@
 	if (!ops->oo_dump[type])
 		return;
 
+	if (params && params->dp_buf)
+		memset(params->dp_buf, 0, params->dp_buflen);
+
 	nl_list_for_each_entry(obj, &cache->c_items, ce_list) {
 		if (filter && !nl_object_match_filter(obj, filter))
 			continue;
@@ -816,18 +1203,27 @@
 			     void (*cb)(struct nl_object *, void *), void *arg)
 {
 	struct nl_object *obj, *tmp;
-	struct nl_object_ops *ops;
 
 	if (cache->c_ops == NULL)
 		BUG();
 
-	ops = cache->c_ops->co_obj_ops;
-
 	nl_list_for_each_entry_safe(obj, tmp, &cache->c_items, ce_list) {
-		if (filter && !nl_object_match_filter(obj, filter))
-			continue;
+		if (filter) {
+			int diff = nl_object_match_filter(obj, filter);
+
+			NL_DBG(3, "%p<->%p object difference: %x\n",
+				obj, filter, diff);
+
+			if (!diff)
+				continue;
+		}
+
+		/* Caller may hold obj for a long time */
+		nl_object_get(obj);
 
 		cb(obj, arg);
+
+		nl_object_put(obj);
 	}
 }
 
diff --git a/lib/cache_mngr.c b/lib/cache_mngr.c
index 81052aa..9b25e9b 100644
--- a/lib/cache_mngr.c
+++ b/lib/cache_mngr.c
@@ -6,96 +6,62 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup cache_mngt
  * @defgroup cache_mngr Manager
- * @brief Helps keeping caches up to date.
+ * @brief Manager keeping caches up to date automatically.
  *
- * The purpose of a cache manager is to keep track of caches and
- * automatically receive event notifications to keep the caches
- * up to date with the kernel state. Each manager has exactly one
- * netlink socket assigned which limits the scope of each manager
- * to exactly one netlink family. Therefore all caches committed
- * to a manager must be part of the same netlink family. Due to the
- * nature of a manager, it is not possible to have a cache maintain
- * two instances of the same cache type. The socket is subscribed
- * to the event notification group of each cache and also put into
- * non-blocking mode. Functions exist to poll() on the socket to
- * wait for new events to be received.
+ * The cache manager keeps caches up to date automatically by listening to
+ * netlink notifications and integrating the received information into the
+ * existing cache.
  *
- * @code
- * App       libnl                        Kernel
- *        |                            |
- *            +-----------------+        [ notification, link change ]
- *        |   |  Cache Manager  |      | [   (IFF_UP | IFF_RUNNING)  ]
- *            |                 |                |
- *        |   |   +------------+|      |         |  [ notification, new addr ]
- *    <-------|---| route/link |<-------(async)--+  [  10.0.1.1/32 dev eth1  ]
- *        |   |   +------------+|      |                      |
- *            |   +------------+|                             |
- *    <---|---|---| route/addr |<------|-(async)--------------+
- *            |   +------------+|
- *        |   |   +------------+|      |
- *    <-------|---| ...        ||
- *        |   |   +------------+|      |
- *            +-----------------+
- *        |                            |
- * @endcode
+ * @note This functionality is still considered experimental.
  *
- * @par 1) Creating a new cache manager
- * @code
- * struct nl_cache_mngr *mngr;
+ * Related sections in the development guide:
+ * - @core_doc{_cache_manager,Cache Manager}
  *
- * // Allocate a new cache manager for RTNETLINK and automatically
- * // provide the caches added to the manager.
- * mngr = nl_cache_mngr_alloc(NETLINK_ROUTE, NL_AUTO_PROVIDE);
- * @endcode
- *
- * @par 2) Keep track of a cache
- * @code
- * struct nl_cache *cache;
- *
- * // Create a new cache for links/interfaces and ask the manager to
- * // keep it up to date for us. This will trigger a full dump request
- * // to initially fill the cache.
- * cache = nl_cache_mngr_add(mngr, "route/link");
- * @endcode
- *
- * @par 3) Make the manager receive updates
- * @code
- * // Give the manager the ability to receive updates, will call poll()
- * // with a timeout of 5 seconds.
- * if (nl_cache_mngr_poll(mngr, 5000) > 0) {
- *         // Manager received at least one update, dump cache?
- *         nl_cache_dump(cache, ...);
- * }
- * @endcode
- *
- * @par 4) Release cache manager
- * @code
- * nl_cache_mngr_free(mngr);
- * @endcode
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/cache.h>
+ * ~~~~
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/utils.h>
 
+/** @cond SKIP */
+#define NASSOC_INIT		16
+#define NASSOC_EXPAND		8
+/** @endcond */
+
 static int include_cb(struct nl_object *obj, struct nl_parser_param *p)
 {
 	struct nl_cache_assoc *ca = p->pp_arg;
+	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)
 		nl_object_dump(obj, &nl_debug_dp);
 #endif
-	return nl_cache_include(ca->ca_cache, obj, ca->ca_change, ca->ca_change_data);
+
+	if (ops->co_event_filter)
+		if (ops->co_event_filter(ca->ca_cache, obj) != NL_OK)
+			return 0;
+
+	if (ops->co_include_event)
+		return ops->co_include_event(ca->ca_cache, obj, ca->ca_change,
+					     ca->ca_change_data);
+	else
+		return nl_cache_include(ca->ca_cache, obj, ca->ca_change, ca->ca_change_data);
 }
 
 static int event_input(struct nl_msg *msg, void *arg)
@@ -140,11 +106,31 @@
 
 /**
  * Allocate new cache manager
- * @arg sk		Netlink socket.
- * @arg protocol	Netlink Protocol this manager is used for
- * @arg flags		Flags
+ * @arg sk		Netlink socket or NULL to auto allocate
+ * @arg protocol	Netlink protocol this manager is used for
+ * @arg flags		Flags (\c NL_AUTO_PROVIDE)
+ * @arg result		Result pointer
  *
- * @return Newly allocated cache manager or NULL on failure.
+ * Allocates a new cache manager for the specified netlink protocol.
+ *
+ * 1. If sk is not specified (\c NULL) a netlink socket matching the
+ *    specified protocol will be automatically allocated.
+ *
+ * 2. The socket will be put in non-blocking mode and sequence checking
+ *    will be disabled regardless of whether the socket was provided by
+ *    the caller or automatically allocated.
+ *
+ * 3. The socket will be connected.
+ *
+ * If the flag \c NL_AUTO_PROVIDE is specified, any cache added to the
+ * manager will automatically be made available to other users using
+ * nl_cache_mngt_provide().
+ *
+ * @note If the socket is provided by the caller, it is NOT recommended
+ *       to use the socket for anything else besides receiving netlink
+ *       notifications.
+ *
+ * @return 0 on success or a negative error code.
  */
 int nl_cache_mngr_alloc(struct nl_sock *sk, int protocol, int flags,
 			struct nl_cache_mngr **result)
@@ -152,15 +138,23 @@
 	struct nl_cache_mngr *mngr;
 	int err = -NLE_NOMEM;
 
-	if (sk == NULL)
+	/* Catch abuse of flags */
+	if (flags & NL_ALLOCATED_SOCK)
 		BUG();
 
 	mngr = calloc(1, sizeof(*mngr));
 	if (!mngr)
-		goto errout;
+		return -NLE_NOMEM;
 
-	mngr->cm_handle = sk;
-	mngr->cm_nassocs = 32;
+	if (!sk) {
+		if (!(sk = nl_socket_alloc()))
+			goto errout;
+
+		flags |= NL_ALLOCATED_SOCK;
+	}
+
+	mngr->cm_sock = sk;
+	mngr->cm_nassocs = NASSOC_INIT;
 	mngr->cm_protocol = protocol;
 	mngr->cm_flags = flags;
 	mngr->cm_assocs = calloc(mngr->cm_nassocs,
@@ -168,55 +162,72 @@
 	if (!mngr->cm_assocs)
 		goto errout;
 
-	nl_socket_modify_cb(mngr->cm_handle, NL_CB_VALID, NL_CB_CUSTOM,
-			    event_input, mngr);
-
 	/* Required to receive async event notifications */
-	nl_socket_disable_seq_check(mngr->cm_handle);
+	nl_socket_disable_seq_check(mngr->cm_sock);
 
-	if ((err = nl_connect(mngr->cm_handle, protocol) < 0))
+	if ((err = nl_connect(mngr->cm_sock, protocol)) < 0)
 		goto errout;
 
-	if ((err = nl_socket_set_nonblocking(mngr->cm_handle) < 0))
+	if ((err = nl_socket_set_nonblocking(mngr->cm_sock)) < 0)
 		goto errout;
 
+	/* 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;
+
 	NL_DBG(1, "Allocated cache manager %p, protocol %d, %d caches\n",
 	       mngr, protocol, mngr->cm_nassocs);
 
 	*result = mngr;
 	return 0;
 
+errout_free_sync_sock:
+	nl_socket_free(mngr->cm_sync_sock);
 errout:
 	nl_cache_mngr_free(mngr);
 	return err;
 }
 
 /**
- * Add cache responsibility to cache manager
+ * Add cache to cache manager
  * @arg mngr		Cache manager.
- * @arg name		Name of cache to keep track of
+ * @arg cache		Cache to be added to cache manager
  * @arg cb		Function to be called upon changes.
- * @arg result		Pointer to store added cache.
+ * @arg data		Argument passed on to change callback
  *
- * Allocates a new cache of the specified type and adds it to the manager.
- * The operation will trigger a full dump request from the kernel to
- * initially fill the contents of the cache. The manager will subscribe
- * to the notification group of the cache to keep track of any further
- * changes.
+ * Adds cache to the manager. The operation will trigger a full
+ * dump request from the kernel to initially fill the contents
+ * of the cache. The manager will subscribe to the notification group
+ * of the cache and keep track of any further changes.
+ *
+ * The user is responsible for calling nl_cache_mngr_poll() or monitor
+ * the socket and call nl_cache_mngr_data_ready() to allow the library
+ * to process netlink notification events.
+ *
+ * @see nl_cache_mngr_poll()
+ * @see nl_cache_mngr_data_ready()
  *
  * @return 0 on success or a negative error code.
+ * @return -NLE_PROTO_MISMATCH Protocol mismatch between cache manager and
+ * 			       cache type
+ * @return -NLE_OPNOTSUPP Cache type does not support updates
+ * @return -NLE_EXIST Cache of this type already being managed
  */
-int nl_cache_mngr_add(struct nl_cache_mngr *mngr, const char *name,
-		      change_func_t cb, void *data, struct nl_cache **result)
+int nl_cache_mngr_add_cache(struct nl_cache_mngr *mngr, struct nl_cache *cache,
+		      change_func_t cb, void *data)
 {
 	struct nl_cache_ops *ops;
-	struct nl_cache *cache;
 	struct nl_af_group *grp;
 	int err, i;
 
-	ops = nl_cache_ops_lookup(name);
+	ops = cache->c_ops;
 	if (!ops)
-		return -NLE_NOCACHE;
+		return -NLE_INVAL;
 
 	if (ops->co_protocol != mngr->cm_protocol)
 		return -NLE_PROTO_MISMATCH;
@@ -235,30 +246,28 @@
 			break;
 
 	if (i >= mngr->cm_nassocs) {
-		mngr->cm_nassocs += 16;
+		mngr->cm_nassocs += NASSOC_EXPAND;
 		mngr->cm_assocs = realloc(mngr->cm_assocs,
 					  mngr->cm_nassocs *
 					  sizeof(struct nl_cache_assoc));
 		if (mngr->cm_assocs == NULL)
 			return -NLE_NOMEM;
-		else {
-			NL_DBG(1, "Increased capacity of cache manager %p " \
-				  "to %d\n", mngr, mngr->cm_nassocs);
-			goto retry;
-		}
-	}
 
-	cache = nl_cache_alloc(ops);
-	if (!cache)
-		return -NLE_NOMEM;
+		memset(mngr->cm_assocs + (mngr->cm_nassocs - NASSOC_EXPAND), 0,
+		       NASSOC_EXPAND * sizeof(struct nl_cache_assoc));
+
+		NL_DBG(1, "Increased capacity of cache manager %p " \
+			  "to %d\n", mngr, mngr->cm_nassocs);
+		goto retry;
+	}
 
 	for (grp = ops->co_groups; grp->ag_group; grp++) {
-		err = nl_socket_add_membership(mngr->cm_handle, grp->ag_group);
+		err = nl_socket_add_membership(mngr->cm_sock, grp->ag_group);
 		if (err < 0)
-			goto errout_free_cache;
+			return err;
 	}
 
-	err = nl_cache_refill(mngr->cm_handle, cache);
+	err = nl_cache_refill(mngr->cm_sync_sock, cache);
 	if (err < 0)
 		goto errout_drop_membership;
 
@@ -272,12 +281,66 @@
 	NL_DBG(1, "Added cache %p <%s> to cache manager %p\n",
 	       cache, nl_cache_name(cache), mngr);
 
-	*result = cache;
 	return 0;
 
 errout_drop_membership:
 	for (grp = ops->co_groups; grp->ag_group; grp++)
-		nl_socket_drop_membership(mngr->cm_handle, grp->ag_group);
+		nl_socket_drop_membership(mngr->cm_sock, grp->ag_group);
+
+	return err;
+}
+
+/**
+ * Add cache to cache manager
+ * @arg mngr		Cache manager.
+ * @arg name		Name of cache to keep track of
+ * @arg cb		Function to be called upon changes.
+ * @arg data		Argument passed on to change callback
+ * @arg result		Pointer to store added cache (optional)
+ *
+ * Allocates a new cache of the specified type and adds it to the manager.
+ * The operation will trigger a full dump request from the kernel to
+ * initially fill the contents of the cache. The manager will subscribe
+ * to the notification group of the cache and keep track of any further
+ * changes.
+ *
+ * The user is responsible for calling nl_cache_mngr_poll() or monitor
+ * the socket and call nl_cache_mngr_data_ready() to allow the library
+ * to process netlink notification events.
+ *
+ * @see nl_cache_mngr_poll()
+ * @see nl_cache_mngr_data_ready()
+ *
+ * @return 0 on success or a negative error code.
+ * @return -NLE_NOCACHE Unknown cache type
+ * @return -NLE_PROTO_MISMATCH Protocol mismatch between cache manager and
+ * 			       cache type
+ * @return -NLE_OPNOTSUPP Cache type does not support updates
+ * @return -NLE_EXIST Cache of this type already being managed
+ */
+int nl_cache_mngr_add(struct nl_cache_mngr *mngr, const char *name,
+		      change_func_t cb, void *data, struct nl_cache **result)
+{
+	struct nl_cache_ops *ops;
+	struct nl_cache *cache;
+	int err;
+
+	ops = nl_cache_ops_lookup_safe(name);
+	if (!ops)
+		return -NLE_NOCACHE;
+
+	cache = nl_cache_alloc(ops);
+	nl_cache_ops_put(ops);
+	if (!cache)
+		return -NLE_NOMEM;
+
+	err = nl_cache_mngr_add_cache(mngr, cache, cb, data);
+	if (err < 0)
+		goto errout_free_cache;
+
+	*result = cache;
+	return 0;
+
 errout_free_cache:
 	nl_cache_free(cache);
 
@@ -285,16 +348,17 @@
 }
 
 /**
- * Get file descriptor
+ * Get socket file descriptor
  * @arg mngr		Cache Manager
  *
- * Get the file descriptor of the socket associated to the manager.
- * This can be used to change socket options or monitor activity
- * using poll()/select().
+ * Get the file descriptor of the socket associated with the manager.
+ *
+ * @note Do not use the socket for anything besides receiving
+ *       notifications.
  */
 int nl_cache_mngr_get_fd(struct nl_cache_mngr *mngr)
 {
-	return nl_socket_get_fd(mngr->cm_handle);
+	return nl_socket_get_fd(mngr->cm_sock);
 }
 
 /**
@@ -303,20 +367,24 @@
  * @arg timeout		Upper limit poll() will block, in milliseconds.
  *
  * Causes poll() to be called to check for new event notifications
- * being available. Automatically receives and handles available
- * notifications.
+ * being available. Calls nl_cache_mngr_data_ready() to process
+ * available data.
  *
  * This functionally is ideally called regularly during an idle
  * period.
  *
- * @return A positive value if at least one update was handled, 0
- *         for none, or a  negative error code.
+ * A timeout can be specified in milliseconds to limit the time the
+ * function will wait for updates.
+ *
+ * @see nl_cache_mngr_data_ready()
+ *
+ * @return The number of messages processed or a negative error code.
  */
 int nl_cache_mngr_poll(struct nl_cache_mngr *mngr, int timeout)
 {
 	int ret;
 	struct pollfd fds = {
-		.fd = nl_socket_get_fd(mngr->cm_handle),
+		.fd = nl_socket_get_fd(mngr->cm_sock),
 		.events = POLLIN,
 	};
 
@@ -326,6 +394,7 @@
 	if (ret < 0)
 		return -nl_syserr2nlerr(errno);
 
+	/* No events, return */
 	if (ret == 0)
 		return 0;
 
@@ -337,28 +406,90 @@
  * @arg mngr		Cache manager
  *
  * This function can be called if the socket associated to the manager
- * contains updates to be received. This function should not be used
- * if nl_cache_mngr_poll() is used.
+ * contains updates to be received. This function should only be used
+ * if nl_cache_mngr_poll() is not used.
  *
- * @return A positive value if at least one update was handled, 0
- *         for none, or a  negative error code.
+ * The function will process messages until there is no more data to
+ * be read from the socket.
+ *
+ * @see nl_cache_mngr_poll()
+ *
+ * @return The number of messages processed or a negative error code.
  */
 int nl_cache_mngr_data_ready(struct nl_cache_mngr *mngr)
 {
-	int err;
+	int err, nread = 0;
+	struct nl_cb *cb;
 
-	err = nl_recvmsgs_default(mngr->cm_handle);
-	if (err < 0)
+	NL_DBG(2, "Cache manager %p, reading new data from fd %d\n",
+	       mngr, nl_socket_get_fd(mngr->cm_sock));
+
+	cb = nl_cb_clone(mngr->cm_sock->s_cb);
+	if (cb == NULL)
+		return -NLE_NOMEM;
+
+	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, event_input, mngr);
+
+	while ((err = nl_recvmsgs_report(mngr->cm_sock, cb)) > 0) {
+		NL_DBG(2, "Cache manager %p, recvmsgs read %d messages\n",
+		       mngr, err);
+		nread += err;
+	}
+
+	nl_cb_put(cb);
+	if (err < 0 && err != -NLE_AGAIN)
 		return err;
 
-	return 1;
+	return nread;
+}
+
+/**
+ * Print information about cache manager
+ * @arg mngr		Cache manager
+ * @arg p		Dumping parameters
+ *
+ * Prints information about the cache manager including all managed caches.
+ *
+ * @note This is a debugging function.
+ */
+void nl_cache_mngr_info(struct nl_cache_mngr *mngr, struct nl_dump_params *p)
+{
+	char buf[128];
+	int i;
+
+	nl_dump_line(p, "cache-manager <%p>\n", mngr);
+	nl_dump_line(p, "  .protocol = %s\n",
+		     nl_nlfamily2str(mngr->cm_protocol, buf, sizeof(buf)));
+	nl_dump_line(p, "  .flags    = %#x\n", mngr->cm_flags);
+	nl_dump_line(p, "  .nassocs  = %u\n", mngr->cm_nassocs);
+	nl_dump_line(p, "  .sock     = <%p>\n", mngr->cm_sock);
+
+	for (i = 0; i < mngr->cm_nassocs; i++) {
+		struct nl_cache_assoc *assoc = &mngr->cm_assocs[i];
+
+		if (assoc->ca_cache) {
+			nl_dump_line(p, "  .cache[%d] = <%p> {\n", i, assoc->ca_cache);
+			nl_dump_line(p, "    .name = %s\n", assoc->ca_cache->c_ops->co_name);
+			nl_dump_line(p, "    .change_func = <%p>\n", assoc->ca_change);
+			nl_dump_line(p, "    .change_data = <%p>\n", assoc->ca_change_data);
+			nl_dump_line(p, "    .nitems = %u\n", nl_cache_nitems(assoc->ca_cache));
+			nl_dump_line(p, "    .objects = {\n");
+
+			p->dp_prefix += 6;
+			nl_cache_dump(assoc->ca_cache, p);
+			p->dp_prefix -= 6;
+
+			nl_dump_line(p, "    }\n");
+			nl_dump_line(p, "  }\n");
+		}
+	}
 }
 
 /**
  * Free cache manager and all caches.
  * @arg mngr		Cache manager.
  *
- * Release all resources after usage of a cache manager.
+ * Release all resources held by a cache manager.
  */
 void nl_cache_mngr_free(struct nl_cache_mngr *mngr)
 {
@@ -367,17 +498,29 @@
 	if (!mngr)
 		return;
 
-	if (mngr->cm_handle)
-		nl_close(mngr->cm_handle);
+	if (mngr->cm_sock)
+		nl_close(mngr->cm_sock);
 
-	for (i = 0; i < mngr->cm_nassocs; i++)
-		if (mngr->cm_assocs[i].ca_cache)
+	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);
+
+	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);
 			nl_cache_free(mngr->cm_assocs[i].ca_cache);
+		}
+	}
 
 	free(mngr->cm_assocs);
-	free(mngr);
 
 	NL_DBG(1, "Cache manager %p freed\n", mngr);
+
+	free(mngr);
 }
 
 /** @} */
diff --git a/lib/cache_mngt.c b/lib/cache_mngt.c
index d57d836..4d3d6ff 100644
--- a/lib/cache_mngt.c
+++ b/lib/cache_mngt.c
@@ -6,35 +6,39 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup core
- * @defgroup cache_mngt Caching
+ * @defgroup cache_mngt Caching System
+ *
+ * Related sections in the development guide:
+ * - @core_doc{core_cache, Caching System}
+ *
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/cache.h>
+ * ~~~~
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/utils.h>
 
 static struct nl_cache_ops *cache_ops;
+static NL_RW_LOCK(cache_ops_lock);
 
 /**
  * @name Cache Operations Sets
  * @{
  */
 
-/**
- * Lookup the set cache operations of a certain cache type
- * @arg name		name of the cache type
- *
- * @return The cache operations or NULL if no operations
- *         have been registered under the specified name.
- */
-struct nl_cache_ops *nl_cache_ops_lookup(const char *name)
+struct nl_cache_ops *__nl_cache_ops_lookup(const char *name)
 {
 	struct nl_cache_ops *ops;
 
@@ -46,17 +50,65 @@
 }
 
 /**
- * Associate a message type to a set of cache operations
- * @arg protocol		netlink protocol
- * @arg msgtype			netlink message type
- *
- * Associates the specified netlink message type with
- * a registered set of cache operations.
- *
- * @return The cache operations or NULL if no association
- *         could be made.
+ * Increment reference counter
+ * @arg ops		Cache operations
  */
-struct nl_cache_ops *nl_cache_ops_associate(int protocol, int msgtype)
+void nl_cache_ops_get(struct nl_cache_ops *ops)
+{
+	ops->co_refcnt++;
+}
+
+/**
+ * Decrement reference counter
+ * @arg ops		Cache operations
+ */
+void nl_cache_ops_put(struct nl_cache_ops *ops)
+{
+	ops->co_refcnt--;
+}
+
+/**
+ * Lookup cache operations by name
+ * @arg name		name of the cache type
+ *
+ * @attention This function is not safe, it does not increment the reference
+ *            counter. Please use nl_cache_ops_lookup_safe().
+ *
+ * @return The cache operations or NULL if not found.
+ */
+struct nl_cache_ops *nl_cache_ops_lookup(const char *name)
+{
+	struct nl_cache_ops *ops;
+
+	nl_read_lock(&cache_ops_lock);
+	ops = __nl_cache_ops_lookup(name);
+	nl_read_unlock(&cache_ops_lock);
+
+	return ops;
+}
+
+/**
+ * Lookup cache operations by name
+ * @arg name		name of the cache type
+ *
+ * @note The reference counter of the returned cache operation is incremented
+ *       and must be decremented after use with nl_cache_ops_put().
+ *
+ * @return The cache operations or NULL if not found.
+ */
+struct nl_cache_ops *nl_cache_ops_lookup_safe(const char *name)
+{
+	struct nl_cache_ops *ops;
+
+	nl_write_lock(&cache_ops_lock);
+	if ((ops = __nl_cache_ops_lookup(name)))
+		nl_cache_ops_get(ops);
+	nl_write_unlock(&cache_ops_lock);
+
+	return ops;
+}
+
+static struct nl_cache_ops *__cache_ops_associate(int protocol, int msgtype)
 {
 	int i;
 	struct nl_cache_ops *ops;
@@ -74,6 +126,54 @@
 }
 
 /**
+ * Associate protocol and message type to cache operations
+ * @arg protocol		netlink protocol
+ * @arg msgtype			netlink message type
+ *
+ * @attention This function is not safe, it does not increment the reference
+ *            counter. Please use nl_cache_ops_associate_safe().
+ *
+ * @see nl_cache_ops_associate_safe()
+ *
+ * @return The cache operations or NULL if no match found.
+ */
+struct nl_cache_ops *nl_cache_ops_associate(int protocol, int msgtype)
+{
+	struct nl_cache_ops *ops;
+
+	nl_read_lock(&cache_ops_lock);
+	ops = __cache_ops_associate(protocol, msgtype);
+	nl_read_unlock(&cache_ops_lock);
+
+	return ops;
+}
+
+/**
+ * Associate protocol and message type to cache operations
+ * @arg protocol		netlink protocol
+ * @arg msgtype			netlink message type
+ *
+ * Searches the registered cache operations for a matching protocol
+ * and message type.
+ *
+ * @note The reference counter of the returned cache operation is incremented
+ *       and must be decremented after use with nl_cache_ops_put().
+ *
+ * @return The cache operations or NULL if no no match was found.
+ */
+struct nl_cache_ops *nl_cache_ops_associate_safe(int protocol, int msgtype)
+{
+	struct nl_cache_ops *ops;
+
+	nl_write_lock(&cache_ops_lock);
+	if ((ops = __cache_ops_associate(protocol, msgtype)))
+		nl_cache_ops_get(ops);
+	nl_write_unlock(&cache_ops_lock);
+
+	return ops;
+}
+
+/**
  * Lookup message type cache association
  * @arg ops			cache operations
  * @arg msgtype			netlink message type
@@ -81,6 +181,9 @@
  * Searches for a matching message type association ing the specified
  * cache operations.
  *
+ * @attention The guranteed lifetime of the returned message type is bound
+ *            to the lifetime of the underlying cache operations.
+ *
  * @return A message type association or NULL.
  */
 struct nl_msgtype *nl_msgtype_lookup(struct nl_cache_ops *ops, int msgtype)
@@ -94,6 +197,7 @@
 	return NULL;
 }
 
+/* Must hold cache_ops_lock */
 static struct nl_cache_ops *cache_ops_lookup_for_obj(struct nl_object_ops *obj_ops)
 {
 	struct nl_cache_ops *ops;
@@ -115,8 +219,25 @@
 {
 	struct nl_cache_ops *ops;
 
+	nl_read_lock(&cache_ops_lock);
 	for (ops = cache_ops; ops; ops = ops->co_next)
 		cb(ops, arg);
+	nl_read_unlock(&cache_ops_lock);
+}
+
+/**
+ * Set default flags for caches of this type
+ * @arg ops		Cache ops
+ * @arg flags		Flags to set
+ *
+ * The cache operation flags will be derived to all caches allocates
+ * based on this set of cache operations.
+ */
+void nl_cache_ops_set_flags(struct nl_cache_ops *ops, unsigned int flags)
+{
+	nl_write_lock(&cache_ops_lock);
+	ops->co_flags |= flags;
+	nl_write_unlock(&cache_ops_lock);
 }
 
 /**
@@ -133,11 +254,16 @@
 	if (!ops->co_name || !ops->co_obj_ops)
 		return -NLE_INVAL;
 
-	if (nl_cache_ops_lookup(ops->co_name))
+	nl_write_lock(&cache_ops_lock);
+	if (__nl_cache_ops_lookup(ops->co_name)) {
+		nl_write_unlock(&cache_ops_lock);
 		return -NLE_EXIST;
+	}
 
+	ops->co_refcnt = 0;
 	ops->co_next = cache_ops;
 	cache_ops = ops;
+	nl_write_unlock(&cache_ops_lock);
 
 	NL_DBG(1, "Registered cache operations %s\n", ops->co_name);
 
@@ -158,18 +284,31 @@
 int nl_cache_mngt_unregister(struct nl_cache_ops *ops)
 {
 	struct nl_cache_ops *t, **tp;
+	int err = 0;
+
+	nl_write_lock(&cache_ops_lock);
+
+	if (ops->co_refcnt > 0) {
+		err = -NLE_BUSY;
+		goto errout;
+	}
 
 	for (tp = &cache_ops; (t=*tp) != NULL; tp = &t->co_next)
 		if (t == ops)
 			break;
 
-	if (!t)
-		return -NLE_NOCACHE;
+	if (!t) {
+		err = -NLE_NOCACHE;
+		goto errout;
+	}
 
 	NL_DBG(1, "Unregistered cache operations %s\n", ops->co_name);
 
 	*tp = t->co_next;
-	return 0;
+errout:
+	nl_write_unlock(&cache_ops_lock);
+
+	return err;
 }
 
 /** @} */
@@ -191,11 +330,25 @@
 {
 	struct nl_cache_ops *ops;
 
+	nl_write_lock(&cache_ops_lock);
+
 	ops = cache_ops_lookup_for_obj(cache->c_ops->co_obj_ops);
 	if (!ops)
 		BUG();
-	else
+	else {
+		nl_cache_get(cache);
+
+		/*
+		 * Hold a reference to the cache operations to ensure the
+		 * ops don't go away while we use it to store the cache pointer.
+		 */
+		if (!ops->co_major_cache)
+			nl_cache_ops_get(ops);
+
 		ops->co_major_cache = cache;
+	}
+
+	nl_write_unlock(&cache_ops_lock);
 }
 
 /**
@@ -210,38 +363,75 @@
 {
 	struct nl_cache_ops *ops;
 
+	nl_write_lock(&cache_ops_lock);
+
 	ops = cache_ops_lookup_for_obj(cache->c_ops->co_obj_ops);
 	if (!ops)
 		BUG();
-	else if (ops->co_major_cache == cache)
+	else if (ops->co_major_cache == cache) {
+		nl_cache_free(ops->co_major_cache);
+		nl_cache_ops_put(ops);
 		ops->co_major_cache = NULL;
+	}
+
+	nl_write_unlock(&cache_ops_lock);
+}
+
+struct nl_cache *__nl_cache_mngt_require(const char *name)
+{
+	struct nl_cache_ops *ops;
+	struct nl_cache *cache = NULL;
+
+	ops = nl_cache_ops_lookup_safe(name);
+	if (ops) {
+		cache = ops->co_major_cache;
+		nl_cache_ops_put(ops);
+	}
+	
+	return cache;
 }
 
 /**
- * Demand the use of a global cache
- * @arg name		name of the required object type
+ * Return cache previously provided via nl_cache_mngt_provide()
+ * @arg name		Name of cache to lookup
  *
- * Trys to find a cache of the specified type for global
- * use.
+ * @attention This function is not safe, it does not increment the reference
+ *            counter. Please use nl_cache_mngt_require_safe().
  *
- * @return A cache provided by another subsystem of the
- *         specified type marked to be available.
+ * @see nl_cache_mngt_require_safe()
+ *
+ * @return Pointer to cache or NULL if none registered
  */
 struct nl_cache *nl_cache_mngt_require(const char *name)
 {
-	struct nl_cache_ops *ops;
+	struct nl_cache *cache;
 
-	ops = nl_cache_ops_lookup(name);
-	if (!ops || !ops->co_major_cache) {
-		fprintf(stderr, "Application BUG: Your application must "
-			"call nl_cache_mngt_provide() and\nprovide a valid "
-			"%s cache to be used for internal lookups.\nSee the "
-			" API documentation for more details.\n", name);
-
-		return NULL;
-	}
+	if (!(cache = __nl_cache_mngt_require(name)))
+		NL_DBG(1, "Application BUG: Your application must "
+		       "call nl_cache_mngt_provide() and\nprovide a valid "
+		       "%s cache to be used for internal lookups.\nSee the "
+		       " API documentation for more details.\n", name);
 	
-	return ops->co_major_cache;
+	return cache;
+}
+
+/**
+ * Return cache previously provided via nl_cache_mngt_provide()
+ * @arg name		Name of cache to lookup
+ *
+ * @note The reference counter of the returned cache is incremented
+ *       and must be decremented after use with nl_cache_put().
+ *
+ * @return Pointer to cache or NULL if none registered
+ */
+struct nl_cache *nl_cache_mngt_require_safe(const char *name)
+{
+	struct nl_cache *cache;
+
+	if ((cache = nl_cache_mngt_require(name)))
+		nl_cache_get(cache);
+	
+	return cache;
 }
 
 /** @} */
diff --git a/lib/cli/cls/basic.c b/lib/cli/cls/basic.c
new file mode 100644
index 0000000..1939988
--- /dev/null
+++ b/lib/cli/cls/basic.c
@@ -0,0 +1,93 @@
+/*
+ * lib/cli/cls/basic.c    	basic classifier module for CLI lib
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/cli/cls.h>
+#include <netlink/route/cls/basic.h>
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-cls-add [...] basic [OPTIONS]...\n"
+"\n"
+"OPTIONS\n"
+" -h, --help                Show this help text.\n"
+" -t, --target=ID           Target class to send matching packets to\n"
+" -e, --ematch=EXPR         Ematch expression\n"
+"\n"
+"EXAMPLE"
+"    # Create a \"catch-all\" classifier, attached to \"q_root\", classyfing\n"
+"    # all not yet classified packets to class \"c_default\"\n"
+"    nl-cls-add --dev=eth0 --parent=q_root basic --target=c_default\n");
+}
+
+static void parse_argv(struct rtnl_tc *tc, int argc, char **argv)
+{
+	struct rtnl_cls *cls = (struct rtnl_cls *) tc;
+	struct rtnl_ematch_tree *tree;
+	uint32_t target;
+	int err;
+
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_TARGET = 257,
+			ARG_DEFAULT = 258,
+		};
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ "target", 1, 0, 't' },
+			{ "ematch", 1, 0, 'e' },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "ht:e:", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			exit(0);
+
+		case 't':
+			if ((err = rtnl_tc_str2handle(optarg, &target)) < 0)
+				nl_cli_fatal(err, "Unable to parse target \"%s\":",
+					optarg, nl_geterror(err));
+
+			rtnl_basic_set_target(cls, target);
+			break;
+
+		case 'e':
+			tree = nl_cli_cls_parse_ematch(cls, optarg);
+			rtnl_basic_set_ematch(cls, tree);
+			break;
+		}
+ 	}
+}
+
+static struct nl_cli_tc_module basic_module =
+{
+	.tm_name		= "basic",
+	.tm_type		= RTNL_TC_TYPE_CLS,
+	.tm_parse_argv		= parse_argv,
+};
+
+static void __init basic_init(void)
+{
+	nl_cli_tc_register(&basic_module);
+}
+
+static void __exit basic_exit(void)
+{
+	nl_cli_tc_unregister(&basic_module);
+}
diff --git a/lib/cli/cls/cgroup.c b/lib/cli/cls/cgroup.c
new file mode 100644
index 0000000..fae6208
--- /dev/null
+++ b/lib/cli/cls/cgroup.c
@@ -0,0 +1,75 @@
+/*
+ * lib/cli/cls/cgroup.c    	cgroup classifier module for CLI lib
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/cli/cls.h>
+#include <netlink/route/cls/cgroup.h>
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-cls-add [...] cgroup [OPTIONS]...\n"
+"\n"
+"OPTIONS\n"
+" -h, --help                Show this help text.\n"
+" -e, --ematch=EXPR         Ematch expression\n"
+"\n"
+"EXAMPLE"
+"    nl-cls-add --dev=eth0 --parent=q_root cgroup\n");
+}
+
+static void parse_argv(struct rtnl_tc *tc, int argc, char **argv)
+{
+	struct rtnl_cls *cls = (struct rtnl_cls *) tc;
+	struct rtnl_ematch_tree *tree;
+
+	for (;;) {
+		int c, optidx = 0;
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ "ematch", 1, 0, 'e' },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "he:", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			exit(0);
+
+		case 'e':
+			tree = nl_cli_cls_parse_ematch(cls, optarg);
+			rtnl_cgroup_set_ematch(cls, tree);
+			break;
+		}
+ 	}
+}
+
+static struct nl_cli_tc_module cgroup_module =
+{
+	.tm_name		= "cgroup",
+	.tm_type		= RTNL_TC_TYPE_CLS,
+	.tm_parse_argv		= parse_argv,
+};
+
+static void __init cgroup_init(void)
+{
+	nl_cli_tc_register(&cgroup_module);
+}
+
+static void __exit cgroup_exit(void)
+{
+	nl_cli_tc_unregister(&cgroup_module);
+}
diff --git a/lib/cli/qdisc/bfifo.c b/lib/cli/qdisc/bfifo.c
new file mode 100644
index 0000000..1ee4777
--- /dev/null
+++ b/lib/cli/qdisc/bfifo.c
@@ -0,0 +1,83 @@
+/*
+ * src/lib/bfifo.c     	bfifo module for CLI lib
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/route/qdisc/fifo.h>
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-qdisc-add [...] bfifo [OPTIONS]...\n"
+"\n"
+"OPTIONS\n"
+"     --help                Show this help text.\n"
+"     --limit=LIMIT         Maximum queue length in number of bytes.\n"
+"\n"
+"EXAMPLE"
+"    # Attach bfifo with a 4KB bytes limit to eth1\n"
+"    nl-qdisc-add --dev=eth1 --parent=root bfifo --limit=4096\n");
+}
+
+static void bfifo_parse_argv(struct rtnl_tc *tc, int argc, char **argv)
+{
+	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc;
+	int limit;
+
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_LIMIT = 257,
+		};
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ "limit", 1, 0, ARG_LIMIT },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "h", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			return;
+
+		case ARG_LIMIT:
+			limit = nl_size2int(optarg);
+			if (limit < 0) {
+				nl_cli_fatal(limit, "Unable to parse bfifo limit "
+					"\"%s\": Invalid format.", optarg);
+			}
+
+			rtnl_qdisc_fifo_set_limit(qdisc, limit);
+			break;
+		}
+ 	}
+}
+
+static struct nl_cli_tc_module bfifo_module =
+{
+	.tm_name		= "bfifo",
+	.tm_type		= RTNL_TC_TYPE_QDISC,
+	.tm_parse_argv		= bfifo_parse_argv,
+};
+
+static void __init bfifo_init(void)
+{
+	nl_cli_tc_register(&bfifo_module);
+}
+
+static void __exit bfifo_exit(void)
+{
+	nl_cli_tc_unregister(&bfifo_module);
+}
diff --git a/lib/cli/qdisc/blackhole.c b/lib/cli/qdisc/blackhole.c
new file mode 100644
index 0000000..af9dc6d
--- /dev/null
+++ b/lib/cli/qdisc/blackhole.c
@@ -0,0 +1,64 @@
+/*
+ * src/lib/blackhole.c    Blackhole module for CLI lib
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-qdisc-add [...] blackhole [OPTIONS]...\n"
+"\n"
+"OPTIONS\n"
+"     --help                Show this help text.\n"
+"\n"
+"EXAMPLE"
+"    # Drop all outgoing packets on eth1\n"
+"    nl-qdisc-add --dev=eth1 --parent=root blackhole\n");
+}
+
+static void blackhole_parse_argv(struct rtnl_tc *tc, int argc, char **argv)
+{
+	for (;;) {
+		int c, optidx = 0;
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "h", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			return;
+		}
+ 	}
+}
+
+static struct nl_cli_tc_module blackhole_module =
+{
+	.tm_name		= "blackhole",
+	.tm_type		= RTNL_TC_TYPE_QDISC,
+	.tm_parse_argv		= blackhole_parse_argv,
+};
+
+static void __init blackhole_init(void)
+{
+	nl_cli_tc_register(&blackhole_module);
+}
+
+static void __exit blackhole_exit(void)
+{
+	nl_cli_tc_unregister(&blackhole_module);
+}
diff --git a/lib/cli/qdisc/fq_codel.c b/lib/cli/qdisc/fq_codel.c
new file mode 100644
index 0000000..1602bcb
--- /dev/null
+++ b/lib/cli/qdisc/fq_codel.c
@@ -0,0 +1,112 @@
+/*
+ * lib/cli/qdisc/fq_codel.c     	fq_codel module for CLI lib
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/route/qdisc/fq_codel.h>
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-qdisc-add [...] fq_codel [OPTIONS]...\n"
+"\n"
+"OPTIONS\n"
+"     --help                Show this help text.\n"
+"     --limit=LIMIT         Maximum queue length in number of bytes.\n"
+"     --quantum=SIZE        Amount of bytes to serve at once.\n"
+"     --flows=N             Number of flows.\n"
+"     --interval=N          The interval in usec.\n"
+"     --target=N            The minimum delay in usec.\n"
+"\n"
+"EXAMPLE"
+"    # Attach fq_codel with a 4096 packets limit to eth1\n"
+"    nl-qdisc-add --dev=eth1 --parent=root fq_codel --limit=4096\n");
+}
+
+static void fq_codel_parse_argv(struct rtnl_tc *tc, int argc, char **argv)
+{
+	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc;
+	int limit, flows;
+	uint32_t quantum, target, interval;
+
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_LIMIT = 257,
+			ARG_QUANTUM = 258,
+			ARG_FLOWS,
+			ARG_INTERVAL,
+			ARG_TARGET,
+		};
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ "limit", 1, 0, ARG_LIMIT },
+			{ "quantum", 1, 0, ARG_QUANTUM },
+			{ "flows", 1, 0, ARG_FLOWS},
+			{ "interval", 1, 0, ARG_INTERVAL},
+			{ "target", 1, 0, ARG_TARGET},
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "h", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			return;
+
+		case ARG_LIMIT:
+			limit = nl_cli_parse_u32(optarg);
+			rtnl_qdisc_fq_codel_set_limit(qdisc, limit);
+			break;
+
+		case ARG_QUANTUM:
+			quantum = nl_cli_parse_u32(optarg);
+			rtnl_qdisc_fq_codel_set_quantum(qdisc, quantum);
+			break;
+
+		case ARG_FLOWS:
+			flows = nl_cli_parse_u32(optarg);
+			rtnl_qdisc_fq_codel_set_flows(qdisc, flows);
+			break;
+
+		case ARG_INTERVAL:
+			interval = nl_cli_parse_u32(optarg);
+			rtnl_qdisc_fq_codel_set_interval(qdisc, interval);
+			break;
+
+		case ARG_TARGET:
+			target = nl_cli_parse_u32(optarg);
+			rtnl_qdisc_fq_codel_set_target(qdisc, target);
+			break;
+
+		}
+ 	}
+}
+
+static struct nl_cli_tc_module fq_codel_module =
+{
+	.tm_name		= "fq_codel",
+	.tm_type		= RTNL_TC_TYPE_QDISC,
+	.tm_parse_argv		= fq_codel_parse_argv,
+};
+
+static void __init fq_codel_init(void)
+{
+	nl_cli_tc_register(&fq_codel_module);
+}
+
+static void __exit fq_codel_exit(void)
+{
+	nl_cli_tc_unregister(&fq_codel_module);
+}
diff --git a/lib/cli/qdisc/htb.c b/lib/cli/qdisc/htb.c
new file mode 100644
index 0000000..1751595
--- /dev/null
+++ b/lib/cli/qdisc/htb.c
@@ -0,0 +1,203 @@
+/*
+ * src/lib/htb.c     	HTB module for CLI lib
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/route/qdisc/htb.h>
+
+static void print_qdisc_usage(void)
+{
+	printf(
+"Usage: nl-qdisc-add [...] htb [OPTIONS]...\n"
+"\n"
+"OPTIONS\n"
+"     --help                Show this help text.\n"
+"     --r2q=DIV             Rate to quantum divisor (default: 10)\n"
+"     --default=ID          Default class for unclassified traffic.\n"
+"\n"
+"EXAMPLE"
+"    # Create htb root qdisc 1: and direct unclassified traffic to class 1:10\n"
+"    nl-qdisc-add --dev=eth1 --parent=root --handle=1: htb --default=10\n");
+}
+
+static void htb_parse_qdisc_argv(struct rtnl_tc *tc, int argc, char **argv)
+{
+	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc;
+
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_R2Q = 257,
+			ARG_DEFAULT = 258,
+		};
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ "r2q", 1, 0, ARG_R2Q },
+			{ "default", 1, 0, ARG_DEFAULT },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "hv", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_qdisc_usage();
+			return;
+
+		case ARG_R2Q:
+			rtnl_htb_set_rate2quantum(qdisc, nl_cli_parse_u32(optarg));
+			break;
+
+		case ARG_DEFAULT:
+			rtnl_htb_set_defcls(qdisc, nl_cli_parse_u32(optarg));
+			break;
+		}
+ 	}
+}
+
+static void print_class_usage(void)
+{
+	printf(
+"Usage: nl-class-add [...] htb [OPTIONS]...\n"
+"\n"
+"OPTIONS\n"
+"     --help                Show this help text.\n"
+"     --rate=RATE           Rate limit.\n"
+"     --ceil=RATE           Rate limit while borrowing (default: equal to --rate).\n"
+"     --prio=PRIO           Priority, lower is served first (default: 0).\n"
+"     --quantum=SIZE        Amount of bytes to serve at once (default: rate/r2q).\n"
+"     --burst=SIZE          Max charge size of rate burst buffer (default: auto).\n"
+"     --cburst=SIZE         Max charge size of ceil rate burst buffer (default: auto)\n"
+"\n"
+"EXAMPLE"
+"    # Attach class 1:1 to htb qdisc 1: and rate limit it to 20mbit\n"
+"    nl-class-add --dev=eth1 --parent=1: --classid=1:1 htb --rate=20mbit\n");
+}
+
+static void htb_parse_class_argv(struct rtnl_tc *tc, int argc, char **argv)
+{
+	struct rtnl_class *class = (struct rtnl_class *) tc;
+	long rate;
+
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_RATE = 257,
+			ARG_QUANTUM = 258,
+			ARG_CEIL,
+			ARG_PRIO,
+			ARG_BURST,
+			ARG_CBURST,
+		};
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ "rate", 1, 0, ARG_RATE },
+			{ "quantum", 1, 0, ARG_QUANTUM },
+			{ "ceil", 1, 0, ARG_CEIL },
+			{ "prio", 1, 0, ARG_PRIO },
+			{ "burst", 1, 0, ARG_BURST },
+			{ "cburst", 1, 0, ARG_CBURST },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "h", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_class_usage();
+			return;
+
+		case ARG_RATE:
+			rate = nl_size2int(optarg);
+			if (rate < 0) {
+				nl_cli_fatal(rate, "Unable to parse htb rate "
+					"\"%s\": Invalid format.", optarg);
+			}
+
+			rtnl_htb_set_rate(class, rate);
+			break;
+
+		case ARG_CEIL:
+			rate = nl_size2int(optarg);
+			if (rate < 0) {
+				nl_cli_fatal(rate, "Unable to parse htb ceil rate "
+					"\"%s\": Invalid format.", optarg);
+			}
+
+			rtnl_htb_set_ceil(class, rate);
+			break;
+
+		case ARG_PRIO:
+			rtnl_htb_set_prio(class, nl_cli_parse_u32(optarg));
+			break;
+
+		case ARG_QUANTUM:
+			rate = nl_size2int(optarg);
+			if (rate < 0) {
+				nl_cli_fatal(rate, "Unable to parse quantum "
+					"\"%s\": Invalid format.", optarg);
+			}
+
+			rtnl_htb_set_quantum(class, rate);
+			break;
+
+		case ARG_BURST:
+			rate = nl_size2int(optarg);
+			if (rate < 0) {
+				nl_cli_fatal(rate, "Unable to parse burst "
+					"\"%s\": Invalid format.", optarg);
+			}
+
+			rtnl_htb_set_rbuffer(class, rate);
+			break;
+
+		case ARG_CBURST:
+			rate = nl_size2int(optarg);
+			if (rate < 0) {
+				nl_cli_fatal(rate, "Unable to parse cburst "
+					"\"%s\": Invalid format.", optarg);
+			}
+
+			rtnl_htb_set_cbuffer(class, rate);
+			break;
+		}
+ 	}
+}
+
+static struct nl_cli_tc_module htb_qdisc_module =
+{
+	.tm_name		= "htb",
+	.tm_type		= RTNL_TC_TYPE_QDISC,
+	.tm_parse_argv		= htb_parse_qdisc_argv,
+};
+
+static struct nl_cli_tc_module htb_class_module =
+{
+	.tm_name		= "htb",
+	.tm_type		= RTNL_TC_TYPE_CLASS,
+	.tm_parse_argv		= htb_parse_class_argv,
+};
+
+static void __init htb_init(void)
+{
+	nl_cli_tc_register(&htb_qdisc_module);
+	nl_cli_tc_register(&htb_class_module);
+}
+
+static void __exit htb_exit(void)
+{
+	nl_cli_tc_unregister(&htb_class_module);
+	nl_cli_tc_unregister(&htb_qdisc_module);
+}
diff --git a/lib/cli/qdisc/ingress.c b/lib/cli/qdisc/ingress.c
new file mode 100644
index 0000000..8892a64
--- /dev/null
+++ b/lib/cli/qdisc/ingress.c
@@ -0,0 +1,65 @@
+
+/*
+ * src/lib/ingress.c     	ingress module for CLI lib
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-qdisc-add [...] ingress\n"
+"\n"
+"OPTIONS\n"
+"     --help                Show this help text.\n"
+"\n"
+"EXAMPLE"
+"    # Attach ingress to eth1\n"
+"    nl-qdisc-add --dev=eth1 --parent=root ingress\n");
+}
+
+static void ingress_parse_argv(struct rtnl_tc *tc, int argc, char **argv)
+{
+	for (;;) {
+		int c, optidx = 0;
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ 0, 0, 0, 0 }
+		};
+
+		c = getopt_long(argc, argv, "h", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			return;
+		}
+	}
+}
+
+static struct nl_cli_tc_module ingress_module =
+{
+	.tm_name		= "ingress",
+	.tm_type		= RTNL_TC_TYPE_QDISC,
+	.tm_parse_argv		= ingress_parse_argv,
+};
+
+static void __init ingress_init(void)
+{
+	nl_cli_tc_register(&ingress_module);
+}
+
+static void __exit ingress_exit(void)
+{
+	nl_cli_tc_unregister(&ingress_module);
+}
diff --git a/lib/cli/qdisc/pfifo.c b/lib/cli/qdisc/pfifo.c
new file mode 100644
index 0000000..02c4d22
--- /dev/null
+++ b/lib/cli/qdisc/pfifo.c
@@ -0,0 +1,77 @@
+
+/*
+ * src/lib/pfifo.c     	pfifo module for CLI lib
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/route/qdisc/fifo.h>
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-qdisc-add [...] pfifo [OPTIONS]...\n"
+"\n"
+"OPTIONS\n"
+"     --help                Show this help text.\n"
+"     --limit=LIMIT         Maximum queue length in number of packets.\n"
+"\n"
+"EXAMPLE"
+"    # Attach pfifo with a 32 packet limit to eth1\n"
+"    nl-qdisc-add --dev=eth1 --parent=root pfifo --limit=32\n");
+}
+
+static void pfifo_parse_argv(struct rtnl_tc *tc, int argc, char **argv)
+{
+	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc;
+
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_LIMIT = 257,
+		};
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ "limit", 1, 0, ARG_LIMIT },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "h", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			return;
+
+		case ARG_LIMIT:
+			rtnl_qdisc_fifo_set_limit(qdisc, nl_cli_parse_u32(optarg));
+			break;
+		}
+ 	}
+}
+
+static struct nl_cli_tc_module pfifo_module =
+{
+	.tm_name		= "pfifo",
+	.tm_type		= RTNL_TC_TYPE_QDISC,
+	.tm_parse_argv		= pfifo_parse_argv,
+};
+
+static void __init pfifo_init(void)
+{
+	nl_cli_tc_register(&pfifo_module);
+}
+
+static void __exit pfifo_exit(void)
+{
+	nl_cli_tc_unregister(&pfifo_module);
+}
diff --git a/lib/cli/qdisc/plug.c b/lib/cli/qdisc/plug.c
new file mode 100644
index 0000000..2b8d5d6
--- /dev/null
+++ b/lib/cli/qdisc/plug.c
@@ -0,0 +1,113 @@
+
+/*
+ * src/lib/cli/qdisc/plug.c     	plug module for CLI lib
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2012 Shriram Rajagopalan <rshriram@cs.ubc.ca>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/route/qdisc/plug.h>
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-qdisc-add [...] plug [OPTIONS]...\n"
+"\n"
+"OPTIONS\n"
+"     --help                Show this help text.\n"
+"     --limit               Maximum queue length in bytes.\n"
+"     --buffer              create a new buffer(plug) and queue incoming traffic into it.\n"
+"     --release-one         release traffic from previous buffer.\n"
+"     --release-indefinite  stop buffering and release all (buffered and new) packets.\n"
+"\n"
+"EXAMPLE"
+"    # Attach plug qdisc with 32KB queue size to ifb0\n"
+"    nl-qdisc-add --dev=ifb0 --parent=root plug --limit=32768\n"
+"    # Plug network traffic arriving at ifb0\n"
+"    nl-qdisc-add --dev=ifb0 --parent=root --update plug --buffer\n"
+"    # Unplug traffic arriving at ifb0 indefinitely\n"
+"    nl-qdisc-add --dev=ifb0 --parent=root --update plug --release-indefinite\n\n"
+"    # If operating in output buffering mode:\n"
+"    # at time t=t0, create a new output buffer b0 to hold network output\n"
+"    nl-qdisc-add --dev=ifb0 --parent=root --update plug --buffer\n\n"
+"    # at time t=t1, take a checkpoint c0, create a new output buffer b1\n"
+"    nl-qdisc-add --dev=ifb0 --parent=root --update plug --buffer\n"
+"    # at time t=t1+r, after c0 is committed, release b0\n"
+"    nl-qdisc-add --dev=ifb0 --parent=root --update plug --release-one\n\n"
+"    # at time t=t2, take a checkpoint c1, create a new output buffer b2\n"
+"    nl-qdisc-add --dev=ifb0 --parent=root --update plug --buffer\n"
+"    # at time t=t2+r, after c1 is committed, release b1\n"
+"    nl-qdisc-add --dev=ifb0 --parent=root --update plug --release-one\n");
+}
+
+static void plug_parse_argv(struct rtnl_tc *tc, int argc, char **argv)
+{
+	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc;
+
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_LIMIT              = 257,
+			ARG_BUFFER             = 258,
+			ARG_RELEASE_ONE        = 259,
+			ARG_RELEASE_INDEFINITE = 260,
+		};
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ "limit", 1, 0, ARG_LIMIT },
+			{ "buffer", 0, 0, ARG_BUFFER },
+			{ "release-one", 0, 0, ARG_RELEASE_ONE },
+			{ "release-indefinite", 0, 0, ARG_RELEASE_INDEFINITE },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "h", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			return;
+
+		case ARG_LIMIT:
+			rtnl_qdisc_plug_set_limit(qdisc, nl_cli_parse_u32(optarg));
+			break;
+
+		case ARG_BUFFER:
+			rtnl_qdisc_plug_buffer(qdisc);
+			break;
+
+		case ARG_RELEASE_ONE:
+		        rtnl_qdisc_plug_release_one(qdisc);
+			break;
+
+		case ARG_RELEASE_INDEFINITE:
+			rtnl_qdisc_plug_release_indefinite(qdisc);
+			break;
+		}
+ 	}
+}
+
+static struct nl_cli_tc_module plug_module =
+{
+	.tm_name		= "plug",
+	.tm_type		= RTNL_TC_TYPE_QDISC,
+	.tm_parse_argv		= plug_parse_argv,
+};
+
+static void __init plug_init(void)
+{
+	nl_cli_tc_register(&plug_module);
+}
+
+static void __exit plug_exit(void)
+{
+	nl_cli_tc_unregister(&plug_module);
+}
diff --git a/lib/data.c b/lib/data.c
index 03cd9fe..1a3a3fb 100644
--- a/lib/data.c
+++ b/lib/data.c
@@ -6,16 +6,28 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup core
+ * @ingroup core_types
  * @defgroup data Abstract Data
+ *
+ * Abstract data type representing a binary data blob.
+ *
+ * Related sections in the development guide:
+ * - @core_doc{_abstract_data, Abstract Data}
+ *
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/data.h>
+ * ~~~~
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <linux/socket.h>
@@ -98,9 +110,6 @@
  */
 int nl_data_append(struct nl_data *data, void *buf, size_t size)
 {
-	if (size < 0)
-		BUG();
-
 	if (size > 0) {
 		data->d_data = realloc(data->d_data, data->d_size + size);
 		if (!data->d_data)
diff --git a/lib/doc.c b/lib/doc.c
deleted file mode 100644
index 7e68bb3..0000000
--- a/lib/doc.c
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * lib/doc.c		Documentation Purpose
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @mainpage
- *
- * @section intro Introduction
- *
- * libnl is a set of libraries to deal with the netlink protocol and some
- * of the high level protocols implemented on top of it. Its goal is to
- * simplify netlink protocol usage and to create an abstraction layer using
- * object based interfaces for various netlink based subsystems.The library
- * was developed and tested on the 2.6.x kernel releases but it may work with
- * older kernel series.
- *
- * @section toc Table of Contents
- *
- * - \subpage core_doc
- * - \subpage route_doc
- * - \subpage genl_doc
- * - \subpage nf_doc
- *
- * @section remarks Remarks
- *
- * @subsection cache_alloc Allocation of Caches
- *
- * Almost all subsystem provide a function to allocate a new cache
- * of some form. The function usually looks like this:
- * @code
- * struct nl_cache *<object name>_alloc_cache(struct nl_sock *sk);
- * @endcode
- *
- * These functions allocate a new cache for the own object type,
- * initializes it properly and updates it to represent the current
- * state of their master, e.g. a link cache would include all
- * links currently configured in the kernel.
- *
- * Some of the allocation functions may take additional arguments
- * to further specify what will be part of the cache.
- *
- * All such functions return a newly allocated cache or NULL
- * in case of an error.
- *
- * @subsection addr Setting of Addresses
- * @code
- * int <object name>_set_addr(struct nl_object *, struct nl_addr *)
- * @endcode
- *
- * All attribute functions avaiable for assigning addresses to objects
- * take a struct nl_addr argument. The provided address object is
- * validated against the address family of the object if known already.
- * The assignment fails if the address families mismatch. In case the
- * address family has not been specified yet, the address family of
- * the new address is elected to be the new requirement.
- *
- * The function will acquire a new reference on the address object
- * before assignment, the caller is NOT responsible for this.
- *
- * All functions return 0 on success or a negative error code.
- *
- * @subsection flags Flags to Character StringTranslations
- * All functions converting a set of flags to a character string follow
- * the same principles, therefore, the following information applies
- * to all functions convertings flags to a character string and vice versa.
- *
- * @subsubsection flags2str Flags to Character String
- * @code
- * char *<object name>_flags2str(int flags, char *buf, size_t len)
- * @endcode
- * @arg flags		Flags.
- * @arg buf		Destination buffer.
- * @arg len		Buffer length.
- *
- * Converts the specified flags to a character string separated by
- * commas and stores it in the specified destination buffer.
- *
- * @return The destination buffer
- *
- * @subsubsection str2flags Character String to Flags
- * @code
- * int <object name>_str2flags(const char *name)
- * @endcode
- * @arg name		Name of flag.
- *
- * Converts the provided character string specifying a flag
- * to the corresponding numeric value.
- *
- * @return Link flag or a negative value if none was found.
- *
- * @subsubsection type2str Type to Character String
- * @code
- * char *<object name>_<type>2str(int type, char *buf, size_t len)
- * @endcode
- * @arg type		Type as numeric value
- * @arg buf		Destination buffer.
- * @arg len		Buffer length.
- *
- * Converts an identifier (type) to a character string and stores
- * it in the specified destination buffer.
- *
- * @return The destination buffer or the type encoded in hexidecimal
- *         form if the identifier is unknown.
- *
- * @subsubsection str2type Character String to Type
- * @code
- * int <object name>_str2<type>(const char *name)
- * @endcode
- * @arg name		Name of identifier (type).
- *
- * Converts the provided character string specifying a identifier
- * to the corresponding numeric value.
- *
- * @return Identifier as numeric value or a negative value if none was found.
- *
- * @page core_doc Core Library (-lnl)
- * 
- * @section core_intro Introduction
- *
- * The core library contains the fundamentals required to communicate over
- * netlink sockets. It deals with connecting and unconnecting of sockets,
- * sending and receiving of data, provides a customizeable receiving state
- * machine, and provides a abstract data type framework which eases the
- * implementation of object based netlink protocols where objects are added,
- * removed, or modified with the help of netlink messages.
- *
- * @section core_toc Table of Contents
- * 
- * - \ref proto_fund
- * - \ref sk_doc
- * - \ref rxtx_doc
- * - \ref cb_doc
- *
- * @section proto_fund Netlink Protocol Fundamentals
- *
- * The netlink protocol is a socket based IPC mechanism used for communication
- * between userspace processes and the kernel. The netlink protocol uses the
- * \c AF_NETLINK address family and defines a protocol type for each subsystem
- * protocol (e.g. NETLINK_ROUTE, NETLINK_NETFILTER, etc). Its addressing
- * schema is based on a 32 bit port number, formerly referred to as PID, which
- * uniquely identifies each peer.
- *
- * The netlink protocol is based on messages each limited to the size of a
- * memory page and consists of the netlink message header (struct nlmsghdr)
- * plus the payload attached to it. The payload can consist of arbitary data
- * but often contains a fixed sized family specifc header followed by a
- * stream of \ref attr_doc. The use of attributes dramatically increases
- * the flexibility of the protocol and allows for the protocol to be
- * extended while maintaining backwards compatibility.
- *
- * The netlink message header (struct nlmsghdr):
- * @code   
- * 0                   1                   2                   3
- * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- * +-------------------------------------------------------------+
- * |                          Length                             |
- * +------------------------------+------------------------------+
- * |            Type              |           Flags              |
- * +------------------------------+------------------------------+
- * |                      Sequence Number                        |
- * +-------------------------------------------------------------+
- * |                       Port (Address)                        |
- * +-------------------------------------------------------------+
- * @endcode
- *
- * Netlink differs between requests, notifications, and replies. Requests
- * are messages which have the \c NLM_F_REQUEST flag set and are meant to
- * request an action from the receiver. A request is typically sent from
- * a userspace process to the kernel. Every request should be assigned a
- * sequence number which should be incremented for each request sent on the
- * sending side. Depending on the nature of the request, the receiver may
- * reply to the request with regular netlink messages which should contain
- * the same sequence number as the request it relates to. Notifications are
- * of informal nature and don't expect a reply, therefore the sequence number
- * is typically set to 0. It should be noted that unlike in protocols such as
- * TCP there is no strict enforcment of the sequence number. The sole purpose
- * of sequence numbers is to assist a sender in relating replies to the
- * corresponding requests.
- *
- * @msc
- * A,B;
- * A=>B [label="GET (seq=1, NLM_F_REQUEST)"];
- * A<=B [label="PUT (seq=1)"];
- * ...;
- * A<=B [label="NOTIFY (seq=0)"];
- * @endmsc
- *
- * If the size of a reply exceeds the size of a memory page and thus exceeds
- * the maximum message size, the reply can be split into a series of multipart
- * messages. A multipart message has the \c flag NLM_F_MULTI set and the
- * receiver is expected to continue parsing the reply until the special
- * message type \c NLMSG_DONE is received.
- *
- * @msc
- * A,B;
- * A=>B [label="GET (seq=1, NLM_F_REQUEST)"];
- * A<=B [label="PUT (seq=1, NLM_F_MULTI)"];
- * ...;
- * A<=B [label="PUT (seq=1, NLM_F_MULTI)"];
- * A<=B [label="NLMSG_DONE (seq=1)"];
- * @endmsc
- *
- * Errors can be reported using the standard message type \c NLMSG_ERROR which
- * can carry an error code and the netlink mesage header of the request.
- * Error messages should set their sequence number to the sequence number
- * of the message which caused the error.
- *
- * @msc
- * A,B;
- * A=>B [label="GET (seq=1, NLM_F_REQUEST)"];
- * A<=B [label="NLMSG_ERROR code=EINVAL (seq=1)"];
- * @endmsc
- *
- * The \c NLMSG_ERROR message type is also used to send acknowledge messages.
- * An acknowledge message can be requested by setting the \c NLM_F_ACK flag
- * message except that the error code is set to 0.
- *
- * @msc
- * A,B;
- * A=>B [label="GET (seq=1, NLM_F_REQUEST | NLM_F_ACK)"];
- * A<=B [label="ACK (seq=1)"];
- * @endmsc
- *
- * @section sk_doc Dealing with Netlink Sockets
- *
- * In order to use the netlink protocol, a netlink socket is required. Each
- * socket defines a completely independent context for sending and receiving
- * of messages. The netlink socket and all its related attributes are
- * represented by struct nl_sock.
- *
- * @code
- * nl_socket_alloc()                      Allocate new socket structure.
- * nl_socket_free(s)                      Free socket structure.
- * @endcode
- *
- * @subsection local_port Local Port
- * The local port number uniquely identifies the socket and is used to
- * address it. A unique local port is generated automatically when the socket
- * is allocated. It will consist of the Process ID (22 bits) and a random
- * number (10 bits) to allow up to 1024 sockets per process.
- *
- * @code
- * nl_socket_get_local_port(sk)           Return the peer's port number.
- * nl_socket_set_local_port(sk, port)     Set the peer's port number.
- * @endcode
- *
- * @subsection peer_port Peer Port
- * A peer port can be assigned to the socket which will result in all unicast
- * messages sent over the socket to be addresses to the corresponding peer. If
- * no peer is specified, the kernel will try to automatically bind the socket
- * to a kernel side socket of the same netlink protocol family. It is common
- * practice not to bind the socket to a peer port as typically only one kernel
- * side socket exists per netlink protocol family.
- *
- * @code
- * nl_socket_get_peer_port(sk)            Return the local port number.
- * nl_socket_set_peer_port(sk, port)      Set the local port number.
- * @endcode
- *
- * @subsection sock_fd File Descriptor
- * The file descriptor of the socket(2).
- *
- * @code
- * nl_socket_get_fd(sk)                   Return file descriptor.
- * nl_socket_set_buffer_size(sk, rx, tx)  Set buffer size of socket.
- * nl_socket_set_nonblocking(sk)          Set socket to non-blocking state.
- * @endcode
- *
- * @subsection group_sub Group Subscriptions
- * Each socket can subscribe to multicast groups of the netlink protocol
- * family it is bound to. The socket will then receive a copy of each
- * message sent to any of the groups. Multicast groups are commonly used
- * to implement event notifications. Prior to kernel 2.6.14 the group
- * subscription was performed using a bitmask which limited the number of
- * groups per protocol family to 32. This outdated interface can still be
- * accessed via the function nl_join_groups even though it is not recommended
- * for new code. Starting with 2.6.14 a new method was introduced which
- * supports subscribing to an almost unlimited number of multicast groups.
- *
- * @code
- * nl_socket_add_membership(sk, group)    Become a member of a multicast group.
- * nl_socket_drop_membership(sk, group)   Drop multicast group membership.
- * nl_join_groups(sk, groupmask)          Join a multicast group (obsolete).
- * @endcode
- *
- * @subsection seq_num Sequence Numbers
- * The socket keeps track of the sequence numbers used. The library will
- * automatically verify the sequence number of messages received unless
- * the check was disabled using the function nl_socket_disable_seq_check().
- * When a message is sent using nl_send_auto_complete(), the sequence number
- * is automatically filled in, and replies will be verified correctly.
- *
- * @code
- * nl_socket_disable_seq_check(sk)        Disable checking of sequece numbers.
- * nl_socket_use_seq(sk)                  Use sequence number and bump to next.
- * @endcode
- *
- * @subsection sock_cb Callback Configuration
- * Every socket is associated a callback configuration which enables the
- * applications to hook into various internal functions and control the
- * receiving and sendings semantics. For more information, see section
- * \ref cb_doc.
- *
- * @code
- * nl_socket_alloc_cb(cb)                 Allocate socket based on callback set.
- * nl_socket_get_cb(sk)                   Return callback configuration.
- * nl_socket_set_cb(sk, cb)               Replace callback configuration.
- * nl_socket_modify_cb(sk, ...)           Modify a specific callback function.
- * @endcode
- *
- * @subsection sk_other Other Functions
- * @code
- * nl_socket_enable_auto_ack(sock)        Enable automatic request of ACK.
- * nl_socket_disable_auto_ack(sock)       Disable automatic request of ACK.
- * nl_socket_enable_msg_peek(sock)        Enable message peeking.
- * nl_socket_disable_msg_peek(sock)       Disable message peeking.
- * nl_socket_set_passcred(sk, state)      Enable/disable credential passing.
- * nl_socket_recv_pktinfo(sk, state)      Enable/disable packet information.
- * @endcode
- *
- * @section rxtx_doc Sending and Receiving of Data
- *
- * @subsection recv_semantisc Receiving Semantics
- * @code
- *          nl_recvmsgs_default(set)
- *                 | cb = nl_socket_get_cb(sk)
- *                 v
- *          nl_recvmsgs(sk, cb)
- *                 |           [Application provides nl_recvmsgs() replacement]
- *                 |- - - - - - - - - - - - - - - v
- *                 |                     cb->cb_recvmsgs_ow()
- *                 |
- *                 |               [Application provides nl_recv() replacement]
- * +-------------->|- - - - - - - - - - - - - - - v
- * |           nl_recv()                   cb->cb_recv_ow()
- * |  +----------->|<- - - - - - - - - - - - - - -+
- * |  |            v
- * |  |      Parse Message
- * |  |            |- - - - - - - - - - - - - - - v
- * |  |            |                         NL_CB_MSG_IN()
- * |  |            |<- - - - - - - - - - - - - - -+
- * |  |            |
- * |  |            |- - - - - - - - - - - - - - - v
- * |  |      Sequence Check                NL_CB_SEQ_CHECK()
- * |  |            |<- - - - - - - - - - - - - - -+
- * |  |            |
- * |  |            |- - - - - - - - - - - - - - - v  [ NLM_F_ACK is set ]
- * |  |            |                      NL_CB_SEND_ACK()
- * |  |            |<- - - - - - - - - - - - - - -+
- * |  |            |
- * |  |      +-----+------+--------------+----------------+--------------+
- * |  |      v            v              v                v              v
- * |  | Valid Message    ACK       NO-OP Message  End of Multipart     Error
- * |  |      |            |              |                |              |
- * |  |      v            v              v                v              v
- * |  |NL_CB_VALID()  NL_CB_ACK()  NL_CB_SKIPPED()  NL_CB_FINISH()  cb->cb_err()
- * |  |      |            |              |                |              |
- * |  |      +------------+--------------+----------------+              v
- * |  |                                  |                           (FAILURE)
- * |  |                                  |  [Callback returned NL_SKIP]
- * |  |  [More messages to be parsed]    |<-----------
- * |  +----------------------------------|
- * |                                     |
- * |         [is Multipart message]      |
- * +-------------------------------------|  [Callback returned NL_STOP]
- *                                       |<-----------
- *                                       v
- *                                   (SUCCESS)
- *
- *                          At any time:
- *                                Message Format Error
- *                                         |- - - - - - - - - - - - v
- *                                         v                  NL_CB_INVALID()
- *                                     (FAILURE)
- *
- *                                Message Overrun (Kernel Lost Data)
- *                                         |- - - - - - - - - - - - v
- *                                         v                  NL_CB_OVERRUN()
- *                                     (FAILURE)
- *
- *                                Callback returned negative error code
- *                                     (FAILURE)
- * @endcode
- *
- * @subsection send_semantics Sending Semantisc
- *
- * @code
- *     nl_send_auto_complete(sk, msg)
- *             | [Automatically completes netlink message header]
- *             | [(local port, sequence number)                 ]
- *             |
- *             |                   [Application provies nl_send() replacement]
- *             |- - - - - - - - - - - - - - - - - - - - v
- *             v                                 cb->cb_send_ow()
- *         nl_send(sk, msg)
- *             | [If available, add peer port and credentials]
- *             v
- *        nl_sendmsg(sk, msg, msghdr)
- *             |- - - - - - - - - - - - - - - - - - - - v
- *             |                                 NL_CB_MSG_OUT()
- *             |<- - - - - - - - - - - - - - - - - - - -+
- *             v
- *         sendmsg()
- * @endcode
- *
- * @section cb_doc Callback Configurations
- * Callbacks and overwriting capabilities are provided to control various
- * semantics of the library. All callback functions are packed together in
- * struct nl_cb which is attached to a netlink socket or passed on to 
- * the respective functions directly.
- *
- * @subsection cb_ret_doc Callback Return Values
- * Callback functions can control the flow of the calling layer by returning
- * appropriate error codes:
- * @code
- * Action ID        | Description
- * -----------------+-------------------------------------------------------
- * NL_OK            | Proceed with whatever comes next.
- * NL_SKIP          | Skip message currently being processed and continue
- *                  | with next message.
- * NL_STOP          | Stop parsing and discard all remaining messages in
- *                  | this set of messages.
- * @endcode
- *
- * All callbacks are optional and a default action is performed if no 
- * application specific implementation is provided:
- *
- * @code
- * Callback ID       | Default Return Value
- * ------------------+----------------------
- * NL_CB_VALID       | NL_OK
- * NL_CB_FINISH      | NL_STOP
- * NL_CB_OVERRUN     | NL_STOP
- * NL_CB_SKIPPED     | NL_SKIP
- * NL_CB_ACK         | NL_STOP
- * NL_CB_MSG_IN      | NL_OK
- * NL_CB_MSG_OUT     | NL_OK
- * NL_CB_INVALID     | NL_STOP
- * NL_CB_SEQ_CHECK   | NL_OK
- * NL_CB_SEND_ACK    | NL_OK
- *                   |
- * Error Callback    | NL_STOP
- * @endcode
- *
- * In order to simplify typical usages of the library, different sets of
- * default callback implementations exist:
- * @code
- * NL_CB_DEFAULT: No additional actions
- * NL_CB_VERBOSE: Automatically print warning and error messages to a file
- *                descriptor as appropriate. This is useful for CLI based
- *                applications.
- * NL_CB_DEBUG:   Print informal debugging information for each message
- *                received. This will result in every message beint sent or
- *                received to be printed to the screen in a decoded,
- *                human-readable format.
- * @endcode
- *
- * @par 1) Setting up a callback set
- * @code
- * // Allocate a callback set and initialize it to the verbose default set
- * struct nl_cb *cb = nl_cb_alloc(NL_CB_VERBOSE);
- *
- * // Modify the set to call my_func() for all valid messages
- * nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, my_func, NULL);
- *
- * // Set the error message handler to the verbose default implementation
- * // and direct it to print all errors to the given file descriptor.
- * FILE *file = fopen(...);
- * nl_cb_err(cb, NL_CB_VERBOSE, NULL, file);
- * @endcode
- *
- * @page route_doc Routing Family
- *
- * @page genl_doc Generic Netlink Family
- *
- * @page nf_doc Netfilter Subsystem
- */
diff --git a/lib/error.c b/lib/error.c
index 9a9fac7..f30b9a5 100644
--- a/lib/error.c
+++ b/lib/error.c
@@ -9,7 +9,7 @@
  * Copyright (c) 2008 Thomas Graf <tgraf@suug.ch>
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 
 static const char *errmsg[NLE_MAX+1] = {
@@ -43,6 +43,10 @@
 [NLE_NOACCESS]		= "No Access",
 [NLE_PERM]		= "Operation not permitted",
 [NLE_PKTLOC_FILE]	= "Unable to open packet location file",
+[NLE_PARSE_ERR]		= "Unable to parse object",
+[NLE_NODEV]		= "No such device",
+[NLE_IMMUTABLE]		= "Immutable attribute",
+[NLE_DUMP_INTR]		= "Dump inconsistency detected, interrupted",
 };
 
 /**
@@ -86,6 +90,7 @@
 	case EADDRINUSE:	return NLE_EXIST;
 	case EEXIST:		return NLE_EXIST;
 	case EADDRNOTAVAIL:	return NLE_NOADDR;
+	case ESRCH:		/* fall through */
 	case ENOENT:		return NLE_OBJ_NOTFOUND;
 	case EINTR:		return NLE_INTR;
 	case EAGAIN:		return NLE_AGAIN;
@@ -102,6 +107,7 @@
 	case EPERM:		return NLE_PERM;
 	case EBUSY:		return NLE_BUSY;
 	case ERANGE:		return NLE_RANGE;
+	case ENODEV:		return NLE_NODEV;
 	default:		return NLE_FAILURE;
 	}
 }
diff --git a/lib/fib_lookup/lookup.c b/lib/fib_lookup/lookup.c
index ce9c027..3d07317 100644
--- a/lib/fib_lookup/lookup.c
+++ b/lib/fib_lookup/lookup.c
@@ -6,16 +6,17 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
+ * @ingroup rtnl
  * @defgroup fib_lookup FIB Lookup
  * @brief
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/utils.h>
@@ -123,7 +124,7 @@
 static void result_dump_line(struct nl_object *obj, struct nl_dump_params *p)
 {
 	struct flnl_result *res = (struct flnl_result *) obj;
-	char buf[128];
+	char buf[256];
 
 	nl_dump_line(p, "table %s prefixlen %u next-hop-selector %u\n",
 		rtnl_route_table2str(res->fr_table_id, buf, sizeof(buf)),
@@ -132,7 +133,7 @@
 		     nl_rtntype2str(res->fr_type, buf, sizeof(buf)));
 	nl_dump(p, "scope %s error %s (%d)\n",
 		rtnl_scope2str(res->fr_scope, buf, sizeof(buf)),
-		strerror(-res->fr_error), res->fr_error);
+		strerror_r(-res->fr_error, buf, sizeof(buf)), res->fr_error);
 }
 
 static void result_dump_details(struct nl_object *obj, struct nl_dump_params *p)
@@ -192,6 +193,7 @@
  * Builds a netlink request message to do a lookup
  * @arg req		Requested match.
  * @arg flags		additional netlink message flags
+ * @arg result		Result pointer
  *
  * Builds a new netlink message requesting a change of link attributes.
  * The netlink message header isn't fully equipped with all relevant
@@ -201,9 +203,7 @@
  * and \a tmpl must contain the attributes to be changed set via
  * \c rtnl_link_set_* functions.
  *
- * @return New netlink message
- * @note Not all attributes can be changed, see
- *       \ref link_changeable "Changeable Attributes" for more details.
+ * @return 0 on success or a negative error code.
  */
 int flnl_lookup_build_request(struct flnl_request *req, int flags,
 			      struct nl_msg **result)
diff --git a/lib/fib_lookup/request.c b/lib/fib_lookup/request.c
index ffcf8f5..1b021b6 100644
--- a/lib/fib_lookup/request.c
+++ b/lib/fib_lookup/request.c
@@ -16,7 +16,7 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/utils.h>
diff --git a/lib/genl/ctrl.c b/lib/genl/ctrl.c
index 1301642..ce07f1d 100644
--- a/lib/genl/ctrl.c
+++ b/lib/genl/ctrl.c
@@ -6,18 +6,22 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup genl_mngt
- * @defgroup ctrl Controller
- * @brief
+ * @ingroup genl
+ * @defgroup genl_ctrl Controller (Resolver)
  *
+ * Resolves Generic Netlink family names to numeric identifiers.
+ *
+ * The controller is a component in the kernel that resolves Generic Netlink
+ * family names to their numeric identifiers. This module provides functions
+ * to query the controller to access the resolving functionality.
  * @{
  */
 
-#include <netlink-generic.h>
+#include <netlink-private/genl.h>
 #include <netlink/netlink.h>
 #include <netlink/genl/genl.h>
 #include <netlink/genl/family.h>
@@ -29,7 +33,6 @@
 #define CTRL_VERSION		0x0001
 
 static struct nl_cache_ops genl_ctrl_ops;
-/** @endcond */
 
 static int ctrl_request_update(struct nl_cache *c, struct nl_sock *h)
 {
@@ -45,6 +48,7 @@
 	[CTRL_ATTR_HDRSIZE]	= { .type = NLA_U32 },
 	[CTRL_ATTR_MAXATTR]	= { .type = NLA_U32 },
 	[CTRL_ATTR_OPS]		= { .type = NLA_NESTED },
+	[CTRL_ATTR_MCAST_GROUPS] = { .type = NLA_NESTED },
 };
 
 static struct nla_policy family_op_policy[CTRL_ATTR_OP_MAX+1] = {
@@ -52,6 +56,52 @@
 	[CTRL_ATTR_OP_FLAGS]	= { .type = NLA_U32 },
 };
 
+static struct nla_policy family_grp_policy[CTRL_ATTR_MCAST_GRP_MAX+1] = {
+	[CTRL_ATTR_MCAST_GRP_NAME] = { .type = NLA_STRING },
+	[CTRL_ATTR_MCAST_GRP_ID]   = { .type = NLA_U32 },
+};
+
+static int parse_mcast_grps(struct genl_family *family, struct nlattr *grp_attr)
+{
+	struct nlattr *nla;
+	int remaining, err;
+
+	if (!grp_attr)
+		BUG();
+
+	nla_for_each_nested(nla, grp_attr, remaining) {
+		struct nlattr *tb[CTRL_ATTR_MCAST_GRP_MAX+1];
+		int id;
+		const char * name;
+
+		err = nla_parse_nested(tb, CTRL_ATTR_MCAST_GRP_MAX, nla,
+				       family_grp_policy);
+		if (err < 0)
+			goto errout;
+
+		if (tb[CTRL_ATTR_MCAST_GRP_ID] == NULL) {
+			err = -NLE_MISSING_ATTR;
+			goto errout;
+		}
+		id = nla_get_u32(tb[CTRL_ATTR_MCAST_GRP_ID]);
+
+		if (tb[CTRL_ATTR_MCAST_GRP_NAME] == NULL) {
+			err = -NLE_MISSING_ATTR;
+			goto errout;
+		}
+		name = nla_get_string(tb[CTRL_ATTR_MCAST_GRP_NAME]);
+
+		err = genl_family_add_grp(family, id, name);
+		if (err < 0)
+			goto errout;
+	}
+
+	err = 0;
+
+errout:
+	return err;
+}
+
 static int ctrl_msg_parser(struct nl_cache_ops *ops, struct genl_cmd *cmd,
 			   struct genl_info *info, void *arg)
 {
@@ -126,6 +176,12 @@
 
 		}
 	}
+	
+	if (info->attrs[CTRL_ATTR_MCAST_GROUPS]) {
+		err = parse_mcast_grps(family, info->attrs[CTRL_ATTR_MCAST_GROUPS]);
+		if (err < 0)
+			goto errout;
+	}
 
 	err = pp->pp_cb((struct nl_object *) family, pp);
 errout:
@@ -134,27 +190,167 @@
 }
 
 /**
- * @name Cache Management
- * @{
+ * process responses from from the query sent by genl_ctrl_probe_by_name 
+ * @arg nl_msg		Returned message.
+ * @arg name		genl_family structure to fill out.
+ *
+ * Process returned messages, filling out the missing informatino in the
+ * genl_family structure
+ *
+ * @return Indicator to keep processing frames or not
+ *
  */
-
-int genl_ctrl_alloc_cache(struct nl_sock *sock, struct nl_cache **result)
+static int probe_response(struct nl_msg *msg, void *arg)
 {
-	return nl_cache_alloc_and_fill(&genl_ctrl_ops, sock, result);
+	struct nlattr *tb[CTRL_ATTR_MAX+1];
+	struct nlmsghdr *nlh = nlmsg_hdr(msg);
+	struct genl_family *ret = (struct genl_family *)arg;
+
+	if (genlmsg_parse(nlh, 0, tb, CTRL_ATTR_MAX, ctrl_policy))
+		return NL_SKIP;
+
+	if (tb[CTRL_ATTR_FAMILY_ID])
+		genl_family_set_id(ret, nla_get_u16(tb[CTRL_ATTR_FAMILY_ID]));
+
+	if (tb[CTRL_ATTR_MCAST_GROUPS])
+		if (parse_mcast_grps(ret, tb[CTRL_ATTR_MCAST_GROUPS]) < 0)
+			return NL_SKIP;
+
+	return NL_STOP;
 }
 
 /**
- * Look up generic netlink family by id in the provided cache.
- * @arg cache		Generic netlink family cache.
- * @arg id		Family identifier.
+ * Look up generic netlink family by family name querying the kernel directly
+ * @arg sk		Socket.
+ * @arg name		Family name.
  *
- * Searches through the cache looking for a registered family
- * matching the specified identifier. The caller will own a
- * reference on the returned object which needs to be given
- * back after usage using genl_family_put().
+ * Directly query's the kernel for a given family name.  The caller will own a
+ * reference on the returned object which needsd to be given back after usage
+ * using genl_family_put.
+ *
+ * Note: This API call differs from genl_ctrl_search_by_name in that it querys
+ * the kernel directly, alowing for module autoload to take place to resolve the
+ * family request. Using an nl_cache prevents that operation
  *
  * @return Generic netlink family object or NULL if no match was found.
  */
+static struct genl_family *genl_ctrl_probe_by_name(struct nl_sock *sk,
+						   const char *name)
+{
+	struct nl_msg *msg;
+	struct genl_family *ret;
+	struct nl_cb *cb, *orig;
+	int rc;
+
+	ret = genl_family_alloc();
+	if (!ret)
+		goto out;
+
+	genl_family_set_name(ret, name);
+
+	msg = nlmsg_alloc();
+	if (!msg)
+		goto out_fam_free;
+
+	if (!(orig = nl_socket_get_cb(sk)))
+		goto out_msg_free;
+
+	cb = nl_cb_clone(orig);
+	nl_cb_put(orig);
+	if (!cb)
+		goto out_msg_free;
+
+	if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL,
+			0, 0, CTRL_CMD_GETFAMILY, 1)) {
+		BUG();
+		goto out_cb_free;
+	}
+
+	if (nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, name) < 0)
+		goto out_cb_free;
+
+	rc = nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, probe_response,
+		       (void *) ret);
+	if (rc < 0)
+		goto out_cb_free;
+
+	rc = nl_send_auto_complete(sk, msg);
+	if (rc < 0)
+		goto out_cb_free;
+
+	rc = nl_recvmsgs(sk, cb);
+	if (rc < 0)
+		goto out_cb_free;
+
+	/* If search was successful, request may be ACKed after data */
+	rc = wait_for_ack(sk);
+	if (rc < 0)
+		goto out_cb_free;
+
+	if (genl_family_get_id(ret) != 0) {
+		nlmsg_free(msg);
+		nl_cb_put(cb);
+		return ret;
+	}
+
+out_cb_free:
+	nl_cb_put(cb);
+out_msg_free:
+	nlmsg_free(msg);
+out_fam_free:
+	genl_family_put(ret);
+	ret = NULL;
+out:
+	return ret;
+}
+
+
+/** @endcond */
+
+/**
+ * @name Controller Cache
+ *
+ * The controller cache allows to keep a local copy of the list of all
+ * kernel side registered Generic Netlink families to quickly resolve
+ * multiple Generic Netlink family names without requiring to communicate
+ * with the kernel for each resolving iteration. 
+ *
+ * @{
+ */
+
+/**
+ * Allocate a new controller cache
+ * @arg sk		Generic Netlink socket
+ * @arg result		Pointer to store resulting cache
+ *
+ * Allocates a new cache mirroring the state of the controller and stores it
+ * in \c *result. The allocated cache will contain a list of all currently
+ * registered kernel side Generic Netlink families. The cache is meant to be
+ * used to resolve family names locally.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int genl_ctrl_alloc_cache(struct nl_sock *sk, struct nl_cache **result)
+{
+	return nl_cache_alloc_and_fill(&genl_ctrl_ops, sk, result);
+}
+
+/**
+ * Search controller cache for a numeric address match
+ * @arg cache		Controller cache
+ * @arg id		Numeric family identifier.
+ *
+ * Searches a previously allocated controller cache and looks for an entry
+ * that matches the specified numeric family identifier \c id.  If a match
+ * is found successfully, the reference count of the matching object is
+ * increased by one before the objet is returned.
+ *
+ * @see genl_ctrl_alloc_cache()
+ * @see genl_ctrl_search_by_name()
+ * @see genl_family_put()
+ *
+ * @return Generic Netlink family object or NULL if no match was found.
+ */
 struct genl_family *genl_ctrl_search(struct nl_cache *cache, int id)
 {
 	struct genl_family *fam;
@@ -173,24 +369,23 @@
 }
 
 /**
- * @name Resolver
- * @{
- */
-
-/**
- * Look up generic netlink family by family name in the provided cache.
- * @arg cache		Generic netlink family cache.
- * @arg name		Family name.
+ * Search controller cache for a family name match
+ * @arg cache		Controller cache
+ * @arg name		Name of Generic Netlink family
  *
- * Searches through the cache looking for a registered family
- * matching the specified name. The caller will own a reference
- * on the returned object which needs to be given back after
- * usage using genl_family_put().
+ * Searches a previously allocated controller cache and looks for an entry
+ * that matches the specified family \c name. If a match is found successfully,
+ * the reference count of the matching object is increased by one before the
+ * objet is returned.
  *
- * @return Generic netlink family object or NULL if no match was found.
+ * @see genl_ctrl_alloc_cache()
+ * @see genl_ctrl_search()
+ * @see genl_family_put()
+ *
+ * @return Generic Netlink family object or NULL if no match was found.
  */
 struct genl_family *genl_ctrl_search_by_name(struct nl_cache *cache,
-					    const char *name)
+					     const char *name)
 {
 	struct genl_family *fam;
 
@@ -210,25 +405,33 @@
 /** @} */
 
 /**
- * Resolve generic netlink family name to its identifier
- * @arg sk		Netlink socket.
- * @arg name		Name of generic netlink family
+ * @name Direct Resolvers
  *
- * Resolves the generic netlink family name to its identifer and returns
- * it.
+ * These functions communicate directly with the kernel and do not require
+ * a cache to be kept up to date.
  *
- * @return A positive identifier or a negative error code.
+ * @{
+ */
+
+/**
+ * Resolve Generic Netlink family name to numeric identifier
+ * @arg sk		Generic Netlink socket.
+ * @arg name		Name of Generic Netlink family
+ *
+ * Resolves the Generic Netlink family name to the corresponding numeric
+ * family identifier. This function queries the kernel directly, use
+ * genl_ctrl_search_by_name() if you need to resolve multiple names.
+ *
+ * @see genl_ctrl_search_by_name()
+ *
+ * @return The numeric family identifier or a negative error code.
  */
 int genl_ctrl_resolve(struct nl_sock *sk, const char *name)
 {
-	struct nl_cache *cache;
 	struct genl_family *family;
 	int err;
 
-	if ((err = genl_ctrl_alloc_cache(sk, &cache)) < 0)
-		return err;
-
-	family = genl_ctrl_search_by_name(cache, name);
+	family = genl_ctrl_probe_by_name(sk, name);
 	if (family == NULL) {
 		err = -NLE_OBJ_NOTFOUND;
 		goto errout;
@@ -237,13 +440,56 @@
 	err = genl_family_get_id(family);
 	genl_family_put(family);
 errout:
-	nl_cache_free(cache);
+	return err;
+}
 
+static int genl_ctrl_grp_by_name(const struct genl_family *family,
+				 const char *grp_name)
+{
+	struct genl_family_grp *grp;
+
+	nl_list_for_each_entry(grp, &family->gf_mc_grps, list) {
+		if (!strcmp(grp->name, grp_name)) {
+			return grp->id;
+		}
+	}
+
+	return -NLE_OBJ_NOTFOUND;
+}
+
+/**
+ * Resolve Generic Netlink family group name
+ * @arg sk		Generic Netlink socket
+ * @arg family_name	Name of Generic Netlink family
+ * @arg grp_name	Name of group to resolve
+ *
+ * Looks up the family object and resolves the group name to the numeric
+ * group identifier.
+ *
+ * @return Numeric group identifier or a negative error code.
+ */
+int genl_ctrl_resolve_grp(struct nl_sock *sk, const char *family_name,
+			  const char *grp_name)
+{
+
+	struct genl_family *family;
+	int err;
+
+	family = genl_ctrl_probe_by_name(sk, family_name);
+	if (family == NULL) {
+		err = -NLE_OBJ_NOTFOUND;
+		goto errout;
+	}
+
+	err = genl_ctrl_grp_by_name(family, grp_name);
+	genl_family_put(family);
+errout:
 	return err;
 }
 
 /** @} */
 
+/** @cond SKIP */
 static struct genl_cmd genl_cmds[] = {
 	{
 		.c_id		= CTRL_CMD_NEWFAMILY,
@@ -275,9 +521,7 @@
 	.o_ncmds		= ARRAY_SIZE(genl_cmds),
 };
 
-/** @cond SKIP */
 extern struct nl_object_ops genl_family_ops;
-/** @endcond */
 
 static struct nl_cache_ops genl_ctrl_ops = {
 	.co_name		= "genl/family",
@@ -298,5 +542,6 @@
 {
 	genl_unregister(&genl_ctrl_ops);
 }
+/** @endcond */
 
 /** @} */
diff --git a/lib/genl/family.c b/lib/genl/family.c
index 4c6c18d..897c809 100644
--- a/lib/genl/family.c
+++ b/lib/genl/family.c
@@ -6,18 +6,19 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup genl
- * @defgroup genl_family Generic Netlink Family
- * @brief
+ * @ingroup genl_ctrl
+ * @defgroup genl_family Generic Netlink Family Object
+ *
+ * Object representing a kernel side registered Generic Netlink family
  *
  * @{
  */
 
-#include <netlink-generic.h>
+#include <netlink-private/genl.h>
 #include <netlink/netlink.h>
 #include <netlink/genl/genl.h>
 #include <netlink/genl/family.h>
@@ -32,19 +33,20 @@
 #define FAMILY_ATTR_OPS		0x20
 
 struct nl_object_ops genl_family_ops;
-/** @endcond */
 
 static void family_constructor(struct nl_object *c)
 {
 	struct genl_family *family = (struct genl_family *) c;
 
 	nl_init_list_head(&family->gf_ops);
+	nl_init_list_head(&family->gf_mc_grps);
 }
 
 static void family_free_data(struct nl_object *c)
 {
 	struct genl_family *family = (struct genl_family *) c;
 	struct genl_family_op *ops, *tmp;
+	struct genl_family_grp *grp, *t_grp;
 
 	if (family == NULL)
 		return;
@@ -53,6 +55,12 @@
 		nl_list_del(&ops->o_list);
 		free(ops);
 	}
+
+	nl_list_for_each_entry_safe(grp, t_grp, &family->gf_mc_grps, list) {
+		nl_list_del(&grp->list);
+		free(grp);
+	}
+
 }
 
 static int family_clone(struct nl_object *_dst, struct nl_object *_src)
@@ -60,6 +68,7 @@
 	struct genl_family *dst = nl_object_priv(_dst);
 	struct genl_family *src = nl_object_priv(_src);
 	struct genl_family_op *ops;
+	struct genl_family_grp *grp;
 	int err;
 
 	nl_list_for_each_entry(ops, &src->gf_ops, o_list) {
@@ -67,6 +76,13 @@
 		if (err < 0)
 			return err;
 	}
+
+	nl_list_for_each_entry(grp, &src->gf_mc_grps, list) {
+		err = genl_family_add_grp(dst, grp->id, grp->name);
+		if (err < 0)
+			return err;
+	}
+
 	
 	return 0;
 }
@@ -79,11 +95,11 @@
 		family->gf_id, family->gf_name, family->gf_version);
 }
 
-static struct trans_tbl ops_flags[] = {
-	__ADD(GENL_ADMIN_PERM, admin-perm)
-	__ADD(GENL_CMD_CAP_DO, has-doit)
-	__ADD(GENL_CMD_CAP_DUMP, has-dump)
-	__ADD(GENL_CMD_CAP_HASPOL, has-policy)
+static const struct trans_tbl ops_flags[] = {
+	__ADD(GENL_ADMIN_PERM, admin_perm)
+	__ADD(GENL_CMD_CAP_DO, has_doit)
+	__ADD(GENL_CMD_CAP_DUMP, has_dump)
+	__ADD(GENL_CMD_CAP_HASPOL, has_policy)
 };
 
 static char *ops_flags2str(int flags, char *buf, size_t len)
@@ -93,6 +109,7 @@
 
 static void family_dump_details(struct nl_object *obj, struct nl_dump_params *p)
 {
+	struct genl_family_grp *grp;
 	struct genl_family *family = (struct genl_family *) obj;
 
 	family_dump_line(obj, p);
@@ -118,6 +135,11 @@
 			nl_dump(p, "\n");
 		}
 	}
+
+	nl_list_for_each_entry(grp, &family->gf_mc_grps, list) {
+		nl_dump_line(p, "      grp %s (0x%02x)\n", grp->name, grp->id);
+	}
+
 }
 
 static void family_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
@@ -144,18 +166,32 @@
 
 	return diff;
 }
-
+/** @endcond */
 
 /**
- * @name Family Object
+ * @name Object Allocation
  * @{
  */
 
+/**
+ * Allocate new Generic Netlink family object
+ * 
+ * @return Newly allocated Generic Netlink family object or NULL.
+ */
 struct genl_family *genl_family_alloc(void)
 {
 	return (struct genl_family *) nl_object_alloc(&genl_family_ops);
 }
 
+/**
+ * Release reference on Generic Netlink family object
+ * @arg family		Generic Netlink family object
+ *
+ * Reduces the reference counter of a Generic Netlink family object by one.
+ * The object is freed after the last user has returned its reference.
+ *
+ * @see nl_object_put()
+ */
 void genl_family_put(struct genl_family *family)
 {
 	nl_object_put((struct nl_object *) family);
@@ -164,10 +200,16 @@
 /** @} */
 
 /**
- * @name Attributes
+ * @name Numeric Identifier
  * @{
  */
 
+/**
+ * Return numeric identifier
+ * @arg family		Generic Netlink family object
+ *
+ * @return Numeric identifier or 0 if not available.
+ */
 unsigned int genl_family_get_id(struct genl_family *family)
 {
 	if (family->ce_mask & FAMILY_ATTR_ID)
@@ -176,12 +218,30 @@
 		return GENL_ID_GENERATE;
 }
 
+/**
+ * Set the numeric identifier
+ * @arg family		Generic Netlink family object
+ * @arg id		New numeric identifier
+ */
 void genl_family_set_id(struct genl_family *family, unsigned int id)
 {
 	family->gf_id = id;
 	family->ce_mask |= FAMILY_ATTR_ID;
 }
 
+/** @} */
+
+/**
+ * @name Human Readable Name
+ * @{
+ */
+
+/**
+ * Return human readable name
+ * @arg family		Generic Netlink family object
+ *
+ * @return Name of family or NULL if not available
+ */
 char *genl_family_get_name(struct genl_family *family)
 {
 	if (family->ce_mask & FAMILY_ATTR_NAME)
@@ -190,12 +250,28 @@
 		return NULL;
 }
 
+/**
+ * Set human readable name
+ * @arg family		Generic Netlink family object
+ * @arg name		New human readable name
+ */
 void genl_family_set_name(struct genl_family *family, const char *name)
 {
 	strncpy(family->gf_name, name, GENL_NAMSIZ-1);
 	family->ce_mask |= FAMILY_ATTR_NAME;
 }
 
+/**
+ * @name Interface Version
+ * @{
+ */
+
+/**
+ * Return interface version
+ * @arg family		Generic Netlink family object
+ *
+ * @return Interface version or 0 if not available.
+ */
 uint8_t genl_family_get_version(struct genl_family *family)
 {
 	if (family->ce_mask & FAMILY_ATTR_VERSION)
@@ -204,12 +280,30 @@
 		return 0;
 }
 
+/**
+ * Set interface version
+ * @arg family		Generic Netlink family object
+ * @arg version		New interface version
+ */
 void genl_family_set_version(struct genl_family *family, uint8_t version)
 {
 	family->gf_version = version;
 	family->ce_mask |= FAMILY_ATTR_VERSION;
 }
 
+/** @} */
+
+/**
+ * @name Header Size
+ * @{
+ */
+
+/**
+ * Return user header size expected by kernel component
+ * @arg family		Generic Netlink family object
+ *
+ * @return Expected header length or 0 if not available.
+ */
 uint32_t genl_family_get_hdrsize(struct genl_family *family)
 {
 	if (family->ce_mask & FAMILY_ATTR_HDRSIZE)
@@ -224,6 +318,13 @@
 	family->ce_mask |= FAMILY_ATTR_HDRSIZE;
 }
 
+/** @} */
+
+/**
+ * @name Maximum Expected Attribute
+ * @{
+ */
+
 uint32_t genl_family_get_maxattr(struct genl_family *family)
 {
 	if (family->ce_mask & FAMILY_ATTR_MAXATTR)
@@ -238,6 +339,13 @@
 	family->ce_mask |= FAMILY_ATTR_MAXATTR;
 }
 
+/** @} */
+
+/**
+ * @name Operations
+ * @{
+ */
+
 int genl_family_add_op(struct genl_family *family, int id, int flags)
 {
 	struct genl_family_op *op;
@@ -255,6 +363,23 @@
 	return 0;
 }
 
+int genl_family_add_grp(struct genl_family *family, uint32_t id,
+	       		const char *name)
+{
+	struct genl_family_grp *grp;  
+
+	grp = calloc(1, sizeof(*grp));
+	if (grp == NULL)
+		return -NLE_NOMEM;
+
+	grp->id = id;
+	strncpy(grp->name, name, GENL_NAMSIZ - 1);
+
+	nl_list_add_tail(&grp->list, &family->gf_mc_grps);
+
+	return 0;
+}
+
 /** @} */
 
 /** @cond SKIP */
diff --git a/lib/genl/genl.c b/lib/genl/genl.c
index 055be91..0c9b11e 100644
--- a/lib/genl/genl.c
+++ b/lib/genl/genl.c
@@ -6,99 +6,42 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @defgroup genl Generic Netlink
+ * @defgroup genl Generic Netlink Library (libnl-genl)
  *
- * @par Message Format
- * @code
- *  <------- NLMSG_ALIGN(hlen) ------> <---- NLMSG_ALIGN(len) --->
- * +----------------------------+- - -+- - - - - - - - - - -+- - -+
- * |           Header           | Pad |       Payload       | Pad |
- * |      struct nlmsghdr       |     |                     |     |
- * +----------------------------+- - -+- - - - - - - - - - -+- - -+
- * @endcode
- * @code
- *  <-------- GENL_HDRLEN -------> <--- hdrlen -->
- *                                 <------- genlmsg_len(ghdr) ------>
- * +------------------------+- - -+---------------+- - -+------------+
- * | Generic Netlink Header | Pad | Family Header | Pad | Attributes |
- * |    struct genlmsghdr   |     |               |     |            |
- * +------------------------+- - -+---------------+- - -+------------+
- * genlmsg_data(ghdr)--------------^                     ^
- * genlmsg_attrdata(ghdr, hdrlen)-------------------------
- * @endcode
- *
- * @par Example
- * @code
- * #include <netlink/netlink.h>
- * #include <netlink/genl/genl.h>
- * #include <netlink/genl/ctrl.h>
- *
- * struct nl_sock *sock;
- * struct nl_msg *msg;
- * int family;
- *
- * // Allocate a new netlink socket
- * sock = nl_socket_alloc();
- *
- * // Connect to generic netlink socket on kernel side
- * genl_connect(sock);
- *
- * // Ask kernel to resolve family name to family id
- * family = genl_ctrl_resolve(sock, "generic_netlink_family_name");
- *
- * // Construct a generic netlink by allocating a new message, fill in
- * // the header and append a simple integer attribute.
- * msg = nlmsg_alloc();
- * genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_ECHO,
- *             CMD_FOO_GET, FOO_VERSION);
- * nla_put_u32(msg, ATTR_FOO, 123);
- *
- * // Send message over netlink socket
- * nl_send_auto_complete(sock, msg);
- *
- * // Free message
- * nlmsg_free(msg);
- *
- * // Prepare socket to receive the answer by specifying the callback
- * // function to be called for valid messages.
- * nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, parse_cb, NULL);
- *
- * // Wait for the answer and receive it
- * nl_recvmsgs_default(sock);
- *
- * static int parse_cb(struct nl_msg *msg, void *arg)
- * {
- *     struct nlmsghdr *nlh = nlmsg_hdr(msg);
- *     struct nlattr *attrs[ATTR_MAX+1];
- *
- *     // Validate message and parse attributes
- *     genlmsg_parse(nlh, 0, attrs, ATTR_MAX, policy);
- *
- *     if (attrs[ATTR_FOO]) {
- *         uint32_t value = nla_get_u32(attrs[ATTR_FOO]);
- *         ...
- *     }
- *
- *     return 0;
- * }
- * @endcode
  * @{
  */
 
-#include <netlink-generic.h>
+#include <netlink-private/genl.h>
 #include <netlink/netlink.h>
 #include <netlink/genl/genl.h>
 #include <netlink/utils.h>
 
 /**
- * @name Socket Creating
+ * @name Generic Netlink Socket
  * @{
  */
 
+/**
+ * Connect a Generic Netlink socket
+ * @arg sk		Unconnected Netlink socket
+ *
+ * This function expects a struct nl_socket object previously allocated via
+ * nl_socket_alloc(). It calls nl_connect() to create the local socket file
+ * descriptor and binds the socket to the \c NETLINK_GENERIC Netlink protocol.
+ *
+ * Using this function is equivalent to:
+ * @code
+ * nl_connect(sk, NETLINK_GENERIC);
+ * @endcode
+ *
+ * @see nl_connect()
+ *
+ * @return 0 on success or a negative error code.
+ */
 int genl_connect(struct nl_sock *sk)
 {
 	return nl_connect(sk, NETLINK_GENERIC);
@@ -107,20 +50,34 @@
 /** @} */
 
 /**
- * @name Sending
+ * @name Sending Data
  * @{
  */
 
 /**
- * Send trivial generic netlink message
- * @arg sk		Netlink socket.
- * @arg family		Generic netlink family
- * @arg cmd		Command
- * @arg version		Version
- * @arg flags		Additional netlink message flags.
+ * Send a Generic Netlink message consisting only of a header
+ * @arg sk		Generic Netlink socket
+ * @arg family		Numeric family identifier
+ * @arg cmd		Numeric command identifier
+ * @arg version		Interface version
+ * @arg flags		Additional Netlink message flags (optional)
  *
- * Fills out a routing netlink request message and sends it out
- * using nl_send_simple().
+ * This function is a shortcut for sending a Generic Netlink message without
+ * any message payload. The message will only consist of the Netlink and
+ * Generic Netlink headers. The header is constructed based on the specified
+ * parameters and passed on to nl_send_simple() to send it on the specified
+ * socket.
+ *
+ * @par Example:
+ * @code
+ * #include <netlink/genl/genl.h>
+ * #include <linux/genetlink.h>
+ *
+ * err = genl_send_simple(sk, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, CTRL_VERSION,
+ *                        NLM_F_DUMP);
+ * @endcode
+ *
+ * @see nl_send_simple()
  *
  * @return 0 on success or a negative error code.
  */
@@ -137,12 +94,26 @@
 
 /** @} */
 
-
 /**
  * @name Message Parsing
  * @{
  */
 
+/**
+ * Validate Generic Netlink message headers
+ * @arg nlh		Pointer to Netlink message header
+ * @arg hdrlen		Length of user header
+ *
+ * Verifies the integrity of the Netlink and Generic Netlink headers by
+ * enforcing the following requirements:
+ *  - Valid Netlink message header (nlmsg_valid_hdr())
+ *  - Presence of a complete Generic Netlink header
+ *  - At least \c hdrlen bytes of payload included after the generic
+ *    netlink header.
+ *
+ * @return A positive integer (true) if the headers are valid or
+ *         0 (false) if not.
+ */
 int genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen)
 {
 	struct genlmsghdr *ghdr;
@@ -157,8 +128,28 @@
 	return 1;
 }
 
+/**
+ * Validate Generic Netlink message including attributes
+ * @arg nlh		Pointer to Netlink message header
+ * @arg hdrlen		Length of user header
+ * @arg maxtype		Maximum attribtue id expected
+ * @arg policy		Attribute validation policy
+ *
+ * Verifies the validity of the Netlink and Generic Netlink headers using
+ * genlmsg_valid_hdr() and calls nla_validate() on the message payload to
+ * verify the integrity of eventual attributes.
+ *
+ * @note You may call genlmsg_parse() directly to perform validation and
+ *       parsing in a single step. 
+ *
+ * @see genlmsg_valid_hdr()
+ * @see nla_validate()
+ * @see genlmsg_parse()
+ *
+ * @return 0 on success or a negative error code.
+ */
 int genlmsg_validate(struct nlmsghdr *nlh, int hdrlen, int maxtype,
-		   struct nla_policy *policy)
+		     struct nla_policy *policy)
 {
 	struct genlmsghdr *ghdr;
 
@@ -170,6 +161,33 @@
 			    genlmsg_attrlen(ghdr, hdrlen), maxtype, policy);
 }
 
+/**
+ * Parse Generic Netlink message including attributes
+ * @arg nlh		Pointer to Netlink message header
+ * @arg hdrlen		Length of user header
+ * @arg tb		Array to store parsed attributes
+ * @arg maxtype		Maximum attribute id expected
+ * @arg policy		Attribute validation policy
+ *
+ * Verifies the validity of the Netlink and Generic Netlink headers using
+ * genlmsg_valid_hdr() and calls nla_parse() on the message payload to
+ * parse eventual attributes.
+ *
+ * @par Example:
+ * @code
+ * struct nlattr *attrs[MY_TYPE_MAX+1];
+ *
+ * if ((err = genlsmg_parse(nlmsg_nlh(msg), sizeof(struct my_hdr), attrs,
+ *                          MY_TYPE_MAX, attr_policy)) < 0)
+ * 	// ERROR
+ * @endcode
+ *
+ * @see genlmsg_valid_hdr()
+ * @see genlmsg_validate()
+ * @see nla_parse()
+ *
+ * @return 0 on success or a negative error code.
+ */
 int genlmsg_parse(struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
 		  int maxtype, struct nla_policy *policy)
 {
@@ -184,39 +202,102 @@
 }
 
 /**
- * Get head of message payload
- * @arg gnlh	genetlink messsage header
+ * Return pointer to Generic Netlink header
+ * @arg nlh		Netlink message header
+ *
+ * @return Pointer to Generic Netlink message header
  */
-void *genlmsg_data(const struct genlmsghdr *gnlh)
+struct genlmsghdr *genlmsg_hdr(struct nlmsghdr *nlh)
 {
-	return ((unsigned char *) gnlh + GENL_HDRLEN);
+	return nlmsg_data(nlh);
 }
 
 /**
- * Get lenght of message payload
- * @arg gnlh	genetlink message header
+ * Return length of message payload including user header
+ * @arg gnlh		Generic Netlink message header
+ *
+ * @see genlmsg_data()
+ *
+ * @return Length of user payload including an eventual user header in
+ *         number of bytes.
  */
 int genlmsg_len(const struct genlmsghdr *gnlh)
 {
-	struct nlmsghdr *nlh = (struct nlmsghdr *)((unsigned char *)gnlh -
-							NLMSG_HDRLEN);
+	const struct nlmsghdr *nlh;
+
+	nlh = (const struct nlmsghdr *)((const unsigned char *) gnlh - NLMSG_HDRLEN);
 	return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN);
 }
 
+
 /**
- * Get head of attribute data
- * @arg gnlh	generic netlink message header
- * @arg hdrlen	length of family specific header
+ * Return pointer to user header
+ * @arg gnlh		Generic Netlink message header
+ *
+ * Calculates the pointer to the user header based on the pointer to
+ * the Generic Netlink message header.
+ *
+ * @return Pointer to the user header
  */
-struct nlattr *genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen)
+void *genlmsg_user_hdr(const struct genlmsghdr *gnlh)
 {
-	return genlmsg_data(gnlh) + NLMSG_ALIGN(hdrlen);
+	return genlmsg_data(gnlh);
 }
 
 /**
- * Get length of attribute data
- * @arg gnlh	generic netlink message header
- * @arg hdrlen	length of family specific header
+ * Return pointer to user data
+ * @arg gnlh		Generic netlink message header
+ * @arg hdrlen		Length of user header
+ *
+ * Calculates the pointer to the user data based on the pointer to
+ * the Generic Netlink message header.
+ *
+ * @see genlmsg_user_datalen()
+ *
+ * @return Pointer to the user data
+ */
+void *genlmsg_user_data(const struct genlmsghdr *gnlh, const int hdrlen)
+{
+	return genlmsg_user_hdr(gnlh) + NLMSG_ALIGN(hdrlen);
+}
+
+/**
+ * Return length of user data
+ * @arg gnlh		Generic Netlink message header
+ * @arg hdrlen		Length of user header
+ *
+ * @see genlmsg_user_data()
+ *
+ * @return Length of user data in bytes
+ */
+int genlmsg_user_datalen(const struct genlmsghdr *gnlh, const int hdrlen)
+{
+	return genlmsg_len(gnlh) - NLMSG_ALIGN(hdrlen);
+}
+
+/**
+ * Return pointer to message attributes
+ * @arg gnlh		Generic Netlink message header
+ * @arg hdrlen		Length of user header
+ *
+ * @see genlmsg_attrlen()
+ *
+ * @return Pointer to the start of the message's attributes section.
+ */
+struct nlattr *genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen)
+{
+	return genlmsg_user_data(gnlh, hdrlen);
+}
+
+/**
+ * Return length of message attributes
+ * @arg gnlh		Generic Netlink message header
+ * @arg hdrlen		Length of user header
+ *
+ * @see genlmsg_attrdata()
+ *
+ * @return Length of the message section containing attributes in number
+ *         of bytes.
  */
 int genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen)
 {
@@ -226,24 +307,45 @@
 /** @} */
 
 /**
- * @name Message Building
+ * @name Message Construction
  * @{
  */
 
 /**
- * Add generic netlink header to netlink message
- * @arg msg		netlink message
- * @arg pid		netlink process id or NL_AUTO_PID
- * @arg seq		sequence number of message or NL_AUTO_SEQ
- * @arg family		generic netlink family
- * @arg hdrlen		length of user specific header
- * @arg flags		message flags
- * @arg cmd		generic netlink command
- * @arg version		protocol version
+ * Add Generic Netlink headers to Netlink message
+ * @arg msg		Netlink message object
+ * @arg port		Netlink port or NL_AUTO_PORT
+ * @arg seq		Sequence number of message or NL_AUTO_SEQ
+ * @arg family		Numeric family identifier
+ * @arg hdrlen		Length of user header
+ * @arg flags		Additional Netlink message flags (optional)
+ * @arg cmd		Numeric command identifier
+ * @arg version		Interface version
  *
- * Returns pointer to user specific header.
+ * Calls nlmsg_put() on the specified message object to reserve space for
+ * the Netlink header, the Generic Netlink header, and a user header of
+ * specified length. Fills out the header fields with the specified
+ * parameters.
+ *
+ * @par Example:
+ * @code
+ * struct nl_msg *msg;
+ * struct my_hdr *user_hdr;
+ *
+ * if (!(msg = nlmsg_alloc()))
+ * 	// ERROR
+ *
+ * user_hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family_id,
+ *                        sizeof(struct my_hdr), 0, MY_CMD_FOO, 0);
+ * if (!user_hdr)
+ * 	// ERROR
+ * @endcode
+ *
+ * @see nlmsg_put()
+ *
+ * Returns Pointer to user header or NULL if an error occurred.
  */
-void *genlmsg_put(struct nl_msg *msg, uint32_t pid, uint32_t seq, int family,
+void *genlmsg_put(struct nl_msg *msg, uint32_t port, uint32_t seq, int family,
 		  int hdrlen, int flags, uint8_t cmd, uint8_t version)
 {
 	struct nlmsghdr *nlh;
@@ -252,7 +354,7 @@
 		.version = version,
 	};
 
-	nlh = nlmsg_put(msg, pid, seq, family, GENL_HDRLEN + hdrlen, flags);
+	nlh = nlmsg_put(msg, port, seq, family, GENL_HDRLEN + hdrlen, flags);
 	if (nlh == NULL)
 		return NULL;
 
@@ -265,4 +367,25 @@
 
 /** @} */
 
+/**
+ * @name Deprecated
+ * @{
+ */
+
+/**
+ * Return pointer to message payload
+ * @arg gnlh		Generic Netlink message header
+ *
+ * @deprecated This function has been deprecated due to inability to specify
+ *             the length of the user header. Use genlmsg_user_hdr()
+ *             respectively genlmsg_user_data().
+ *
+ * @return Pointer to payload section
+ */
+void *genlmsg_data(const struct genlmsghdr *gnlh)
+{
+	return ((unsigned char *) gnlh + GENL_HDRLEN);
+}
+
+/** @} */
 /** @} */
diff --git a/lib/genl/mngt.c b/lib/genl/mngt.c
index 0ebe74d..3648663 100644
--- a/lib/genl/mngt.c
+++ b/lib/genl/mngt.c
@@ -6,81 +6,19 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup genl
- * @defgroup genl_mngt Management
+ * @defgroup genl_mngt Family and Command Registration
  *
- * @par 1) Registering a generic netlink module
- * @code
- * #include <netlink/genl/mngt.h>
+ * Registering Generic Netlink Families and Commands
  *
- * // First step is to define all the commands being used in
- * // particular generic netlink family. The ID and name are
- * // mandatory to be filled out. A callback function and
- * // most the attribute policy that comes with it must be
- * // defined for commands expected to be issued towards
- * // userspace.
- * static struct genl_cmd foo_cmds[] = {
- * 	{
- * 		.c_id		= FOO_CMD_NEW,
- * 		.c_name		= "NEWFOO" ,
- * 		.c_maxattr	= FOO_ATTR_MAX,
- * 		.c_attr_policy	= foo_policy,
- * 		.c_msg_parser	= foo_msg_parser,
- * 	},
- * 	{
- * 		.c_id		= FOO_CMD_DEL,
- * 		.c_name		= "DELFOO" ,
- * 	},
- * };
- *
- * // The list of commands must then be integrated into a
- * // struct genl_ops serving as handle for this particular
- * // family.
- * static struct genl_ops my_genl_ops = {
- * 	.o_cmds			= foo_cmds,
- * 	.o_ncmds		= ARRAY_SIZE(foo_cmds),
- * };
- *
- * // Using the above struct genl_ops an arbitary number of
- * // cache handles can be associated to it.
- * //
- * // The macro GENL_HDRSIZE() must be used to specify the
- * // length of the header to automatically take headers on
- * // generic layers into account.
- * //
- * // The macro GENL_FAMILY() is used to represent the generic
- * // netlink family id.
- * static struct nl_cache_ops genl_foo_ops = {
- * 	.co_name		= "genl/foo",
- * 	.co_hdrsize		= GENL_HDRSIZE(sizeof(struct my_hdr)),
- * 	.co_msgtypes		= GENL_FAMILY(GENL_ID_GENERATE, "foo"),
- * 	.co_genl		= &my_genl_ops,
- * 	.co_protocol		= NETLINK_GENERIC,
- * 	.co_request_update      = foo_request_update,
- * 	.co_obj_ops		= &genl_foo_ops,
- * };
- *
- * // Finally each cache handle for a generic netlink family
- * // must be registered using genl_register().
- * static void __init foo_init(void)
- * {
- * 	genl_register(&genl_foo_ops);
- * }
- *
- * // ... respectively unregsted again.
- * static void __exit foo_exit(void)
- * {
- * 	genl_unregister(&genl_foo_ops);
- * }
- * @endcode
  * @{
  */
 
-#include <netlink-generic.h>
+#include <netlink-private/genl.h>
 #include <netlink/netlink.h>
 #include <netlink/genl/genl.h>
 #include <netlink/genl/mngt.h>
@@ -88,30 +26,38 @@
 #include <netlink/genl/ctrl.h>
 #include <netlink/utils.h>
 
+/** @cond SKIP */
+
 static NL_LIST_HEAD(genl_ops_list);
 
-static int genl_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
-			   struct nlmsghdr *nlh, struct nl_parser_param *pp)
+static struct genl_cmd *lookup_cmd(struct genl_ops *ops, int cmd_id)
 {
-	int i, err;
+	struct genl_cmd *cmd;
+	int i;
+
+	for (i = 0; i < ops->o_ncmds; i++) {
+		cmd = &ops->o_cmds[i];
+		if (cmd->c_id == cmd_id)
+			return cmd;
+	}
+
+	return NULL;
+}
+
+static int cmd_msg_parser(struct sockaddr_nl *who, struct nlmsghdr *nlh,
+			  struct genl_ops *ops, struct nl_cache_ops *cache_ops, void *arg)
+{
+	int err;
 	struct genlmsghdr *ghdr;
 	struct genl_cmd *cmd;
 
-	ghdr = nlmsg_data(nlh);
+	ghdr = genlmsg_hdr(nlh);
 
-	if (ops->co_genl == NULL)
-		BUG();
-
-	for (i = 0; i < ops->co_genl->o_ncmds; i++) {
-		cmd = &ops->co_genl->o_cmds[i];
-		if (cmd->c_id == ghdr->cmd)
-			goto found;
+	if (!(cmd = lookup_cmd(ops, ghdr->cmd))) {
+		err = -NLE_MSGTYPE_NOSUPPORT;
+		goto errout;
 	}
 
-	err = -NLE_MSGTYPE_NOSUPPORT;
-	goto errout;
-
-found:
 	if (cmd->c_msg_parser == NULL)
 		err = -NLE_OPNOTSUPP;
 	else {
@@ -120,37 +66,68 @@
 			.who = who,
 			.nlh = nlh,
 			.genlhdr = ghdr,
-			.userhdr = genlmsg_data(ghdr),
+			.userhdr = genlmsg_user_hdr(ghdr),
 			.attrs = tb,
 		};
 
-		err = nlmsg_parse(nlh, ops->co_hdrsize, tb, cmd->c_maxattr,
+		err = nlmsg_parse(nlh, GENL_HDRSIZE(ops->o_hdrsize), tb, cmd->c_maxattr,
 				  cmd->c_attr_policy);
 		if (err < 0)
 			goto errout;
 
-		err = cmd->c_msg_parser(ops, cmd, &info, pp);
+		err = cmd->c_msg_parser(cache_ops, cmd, &info, arg);
 	}
 errout:
 	return err;
 
 }
 
+static int genl_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
+			   struct nlmsghdr *nlh, struct nl_parser_param *pp)
+{
+	if (ops->co_genl == NULL)
+		BUG();
+
+	return cmd_msg_parser(who, nlh, ops->co_genl, ops, pp);
+}
+
+static struct genl_ops *lookup_family(int family)
+{
+	struct genl_ops *ops;
+
+	nl_list_for_each_entry(ops, &genl_ops_list, o_list) {
+		if (ops->o_id == family)
+			return ops;
+	}
+
+	return NULL;
+}
+
+static struct genl_ops *lookup_family_by_name(const char *name)
+{
+	struct genl_ops *ops;
+
+	nl_list_for_each_entry(ops, &genl_ops_list, o_list) {
+		if (!strcmp(ops->o_name, name))
+			return ops;
+	}
+
+	return NULL;
+}
+
 char *genl_op2name(int family, int op, char *buf, size_t len)
 {
 	struct genl_ops *ops;
 	int i;
 
-	nl_list_for_each_entry(ops, &genl_ops_list, o_list) {
-		if (ops->o_family == family) {
-			for (i = 0; i < ops->o_ncmds; i++) {
-				struct genl_cmd *cmd;
-				cmd = &ops->o_cmds[i];
+	if ((ops = lookup_family(family))) {
+		for (i = 0; i < ops->o_ncmds; i++) {
+			struct genl_cmd *cmd;
+			cmd = &ops->o_cmds[i];
 
-				if (cmd->c_id == op) {
-					strncpy(buf, cmd->c_name, len - 1);
-					return buf;
-				}
+			if (cmd->c_id == op) {
+				strncpy(buf, cmd->c_name, len - 1);
+				return buf;
 			}
 		}
 	}
@@ -159,15 +136,107 @@
 	return NULL;
 }
 
+/** @endcond */
 
 /**
- * @name Register/Unregister
+ * @name Registration
  * @{
  */
 
 /**
- * Register generic netlink operations
- * @arg ops		cache operations
+ * Register Generic Netlink family and associated commands
+ * @arg ops		Generic Netlink family definition
+ *
+ * Registers the specified Generic Netlink family definition together with
+ * all associated commands. After registration, received Generic Netlink
+ * messages can be passed to genl_handle_msg() which will validate the
+ * messages, look for a matching command and call the respective callback
+ * function automatically.
+ *
+ * @note Consider using genl_register() if the family is used to implement a
+ *       cacheable type.
+ *
+ * @see genl_unregister_family();
+ * @see genl_register();
+ *
+ * @return 0 on success or a negative error code.
+ */
+int genl_register_family(struct genl_ops *ops)
+{
+	if (!ops->o_name)
+		return -NLE_INVAL;
+
+	if (ops->o_cmds && ops->o_ncmds <= 0)
+		return -NLE_INVAL;
+
+	if (ops->o_id && lookup_family(ops->o_id))
+		return -NLE_EXIST;
+
+	if (lookup_family_by_name(ops->o_name))
+		return -NLE_EXIST;
+
+	nl_list_add_tail(&ops->o_list, &genl_ops_list);
+
+	return 0;
+}
+
+/**
+ * Unregister Generic Netlink family
+ * @arg ops		Generic Netlink family definition
+ *
+ * Unregisters a family and all associated commands that were previously
+ * registered using genl_register_family().
+ *
+ * @see genl_register_family()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int genl_unregister_family(struct genl_ops *ops)
+{
+	nl_list_del(&ops->o_list);
+
+	return 0;
+}
+
+/**
+ * Run a received message through the demultiplexer
+ * @arg msg		Generic Netlink message
+ * @arg arg		Argument passed on to the message handler callback
+ *
+ * @return 0 on success or a negative error code.
+ */
+int genl_handle_msg(struct nl_msg *msg, void *arg)
+{
+	struct nlmsghdr *nlh = nlmsg_hdr(msg);
+	struct genl_ops *ops;
+
+	if (!genlmsg_valid_hdr(nlh, 0))
+		return -NLE_INVAL;
+
+	if (!(ops = lookup_family(nlh->nlmsg_type)))
+		return -NLE_MSGTYPE_NOSUPPORT;
+
+	return cmd_msg_parser(nlmsg_get_src(msg), nlh, ops, NULL, arg);
+}
+
+/** @} */
+
+/**
+ * @name Registration of Cache Operations
+ * @{
+ */
+
+/**
+ * Register Generic Netlink family backed cache
+ * @arg ops		Cache operations definition
+ *
+ * Same as genl_register_family() but additionally registers the specified
+ * cache operations using nl_cache_mngt_register() and associates it with
+ * the Generic Netlink family.
+ *
+ * @see genl_register_family()
+ *
+ * @return 0 on success or a negative error code.
  */
 int genl_register(struct nl_cache_ops *ops)
 {
@@ -189,13 +258,13 @@
 	}
 
 	ops->co_genl->o_cache_ops = ops;
+	ops->co_genl->o_hdrsize = ops->co_hdrsize - GENL_HDRLEN;
 	ops->co_genl->o_name = ops->co_msgtypes[0].mt_name;
-	ops->co_genl->o_family = ops->co_msgtypes[0].mt_id;
+	ops->co_genl->o_id = ops->co_msgtypes[0].mt_id;
 	ops->co_msg_parser = genl_msg_parser;
 
-	/* FIXME: check for dup */
-
-	nl_list_add_tail(&ops->co_genl->o_list, &genl_ops_list);
+	if ((err = genl_register_family(ops->co_genl)) < 0)
+		goto errout;
 
 	err = nl_cache_mngt_register(ops);
 errout:
@@ -203,22 +272,22 @@
 }
 
 /**
- * Unregister generic netlink operations
- * @arg ops		cache operations
+ * Unregister cache based Generic Netlink family
+ * @arg ops		Cache operations definition
  */
 void genl_unregister(struct nl_cache_ops *ops)
 {
+	if (!ops)
+		return;
+
 	nl_cache_mngt_unregister(ops);
-	nl_list_del(&ops->co_genl->o_list);
+
+	genl_unregister_family(ops->co_genl);
 }
 
 /** @} */
 
-/**
- * @name Resolving ID/Name
- * @{
- */
-
+/** @cond SKIP */
 static int __genl_ops_resolve(struct nl_cache *ctrl, struct genl_ops *ops)
 {
 	struct genl_family *family;
@@ -226,6 +295,10 @@
 	family = genl_ctrl_search_by_name(ctrl, ops->o_name);
 	if (family != NULL) {
 		ops->o_id = genl_family_get_id(family);
+
+		if (ops->o_cache_ops)
+			ops->o_cache_ops->co_msgtypes[0].mt_id = ops->o_id;
+
 		genl_family_put(family);
 
 		return 0;
@@ -234,6 +307,47 @@
 	return -NLE_OBJ_NOTFOUND;
 }
 
+int genl_resolve_id(struct genl_ops *ops)
+{
+	struct nl_sock *sk;
+	int err = 0;
+
+	/* Check if resolved already */
+	if (ops->o_id != GENL_ID_GENERATE)
+		return 0;
+
+	if (!ops->o_name)
+		return -NLE_INVAL;
+
+	if (!(sk = nl_socket_alloc()))
+		return -NLE_NOMEM;
+
+	if ((err = genl_connect(sk)) < 0)
+		goto errout_free;
+
+	err = genl_ops_resolve(sk, ops);
+
+errout_free:
+	nl_socket_free(sk);
+
+	return err;
+}
+/** @endcond */
+
+/**
+ * @name Resolving the name of registered families
+ * @{
+ */
+
+/**
+ * Resolve a single Generic Netlink family
+ * @arg sk		Generic Netlink socket
+ * @arg ops		Generic Netlink family definition
+ *
+ * Resolves the family name to its numeric identifier.
+ *
+ * @return 0 on success or a negative error code.
+ */
 int genl_ops_resolve(struct nl_sock *sk, struct genl_ops *ops)
 {
 	struct nl_cache *ctrl;
@@ -249,6 +363,19 @@
 	return err;
 }
 
+/**
+ * Resolve all registered Generic Netlink families
+ * @arg sk		Generic Netlink socket
+ *
+ * Walks through all local Generic Netlink families that have been registered
+ * using genl_register() and resolves the name of each family to the
+ * corresponding numeric identifier.
+ *
+ * @see genl_register()
+ * @see genl_ops_resolve()
+ *
+ * @return 0 on success or a negative error code.
+ */
 int genl_mngt_resolve(struct nl_sock *sk)
 {
 	struct nl_cache *ctrl;
@@ -269,5 +396,4 @@
 
 /** @} */
 
-
 /** @} */
diff --git a/lib/handlers.c b/lib/handlers.c
index f13b89e..a6a97bb 100644
--- a/lib/handlers.c
+++ b/lib/handlers.c
@@ -13,24 +13,19 @@
  * @ingroup core
  * @defgroup cb Callbacks/Customization
  *
- * @details
- * @par 1) Setting up a callback set
- * @code
- * // Allocate a callback set and initialize it to the verbose default set
- * struct nl_cb *cb = nl_cb_alloc(NL_CB_VERBOSE);
+ * Related sections in the development guide:
+ * - @core_doc{core_cb, Callback Configuration}
  *
- * // Modify the set to call my_func() for all valid messages
- * nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, my_func, NULL);
- *
- * // Set the error message handler to the verbose default implementation
- * // and direct it to print all errors to the given file descriptor.
- * FILE *file = fopen(...);
- * nl_cb_err(cb, NL_CB_VERBOSE, NULL, file);
- * @endcode
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/handlers.h>
+ * ~~~~
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/msg.h>
@@ -40,7 +35,7 @@
 {
 	char flags[128];
 	char type[32];
-	
+
 	fprintf(ofd, "type=%s length=%u flags=<%s> sequence-nr=%u pid=%u",
 		nl_nlmsgtype2str(n->nlmsg_type, type, sizeof(type)),
 		n->nlmsg_len, nl_nlmsg_flags2str(n->nlmsg_flags, flags,
@@ -76,7 +71,7 @@
 	fprintf(ofd, "-- Error: Netlink Overrun: ");
 	print_header_content(ofd, nlmsg_hdr(msg));
 	fprintf(ofd, "\n");
-	
+
 	return NL_STOP;
 }
 
@@ -84,9 +79,10 @@
 				    struct nlmsgerr *e, void *arg)
 {
 	FILE *ofd = arg ? arg : stderr;
+	char buf[256];
 
 	fprintf(ofd, "-- Error received: %s\n-- Original message: ",
-		strerror(-e->error));
+		strerror_r(-e->error, buf, sizeof(buf)));
 	print_header_content(ofd, &e->msg);
 	fprintf(ofd, "\n");
 
@@ -111,7 +107,7 @@
 	fprintf(ofd, "-- Debug: End of multipart message block: ");
 	print_header_content(ofd, nlmsg_hdr(msg));
 	fprintf(ofd, "\n");
-	
+
 	return NL_STOP;
 }
 
@@ -121,7 +117,7 @@
 
 	fprintf(ofd, "-- Debug: Received Message:\n");
 	nl_msg_dump(msg, ofd);
-	
+
 	return NL_OK;
 }
 
@@ -215,6 +211,7 @@
 		return NULL;
 
 	cb->cb_refcnt = 1;
+	cb->cb_active = NL_CB_TYPE_MAX + 1;
 
 	for (i = 0; i <= NL_CB_TYPE_MAX; i++)
 		nl_cb_set(cb, i, kind, NULL, NULL);
@@ -233,7 +230,7 @@
 struct nl_cb *nl_cb_clone(struct nl_cb *orig)
 {
 	struct nl_cb *cb;
-	
+
 	cb = nl_cb_alloc(NL_CB_DEFAULT);
 	if (!cb)
 		return NULL;
@@ -265,6 +262,17 @@
 		free(cb);
 }
 
+/**
+ * Obtain type of current active callback
+ * @arg cb		callback to query
+ *
+ * @return type or __NL_CB_TYPE_MAX if none active
+ */
+enum nl_cb_type nl_cb_active_type(struct nl_cb *cb)
+{
+	return cb->cb_active;
+}
+
 /** @} */
 
 /**
@@ -273,7 +281,7 @@
  */
 
 /**
- * Set up a callback 
+ * Set up a callback
  * @arg cb		callback set
  * @arg type		callback to modify
  * @arg kind		kind of implementation
diff --git a/lib/hash.c b/lib/hash.c
new file mode 100644
index 0000000..47c938b
--- /dev/null
+++ b/lib/hash.c
@@ -0,0 +1,482 @@
+/*
+ * This code was taken from http://ccodearchive.net/info/hash.html
+ * The original file was modified to remove unwanted code
+ * and some changes to fit the current build environment
+ */
+/*
+-------------------------------------------------------------------------------
+lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+
+These are functions for producing 32-bit hashes for hash table lookup.
+hash_word(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
+are externally useful functions.  Routines to test the hash are included
+if SELF_TEST is defined.  You can use this free for any purpose.  It's in
+the public domain.  It has no warranty.
+
+You probably want to use hashlittle().  hashlittle() and hashbig()
+hash byte arrays.  hashlittle() is is faster than hashbig() on
+little-endian machines.  Intel and AMD are little-endian machines.
+On second thought, you probably want hashlittle2(), which is identical to
+hashlittle() except it returns two 32-bit hashes for the price of one.
+You could implement hashbig2() if you wanted but I haven't bothered here.
+
+If you want to find a hash of, say, exactly 7 integers, do
+  a = i1;  b = i2;  c = i3;
+  mix(a,b,c);
+  a += i4; b += i5; c += i6;
+  mix(a,b,c);
+  a += i7;
+  final(a,b,c);
+then use c as the hash value.  If you have a variable length array of
+4-byte integers to hash, use hash_word().  If you have a byte array (like
+a character string), use hashlittle().  If you have several byte arrays, or
+a mix of things, see the comments above hashlittle().
+
+Why is this so big?  I read 12 bytes at a time into 3 4-byte integers,
+then mix those integers.  This is fast (you can do a lot more thorough
+mixing with 12*3 instructions on 3 integers than you can with 3 instructions
+on 1 byte), but shoehorning those bytes into integers efficiently is messy.
+-------------------------------------------------------------------------------
+*/
+#include <netlink/hash.h>
+
+#if HAVE_LITTLE_ENDIAN
+#define HASH_LITTLE_ENDIAN 1
+#define HASH_BIG_ENDIAN 0
+#elif HAVE_BIG_ENDIAN
+#define HASH_LITTLE_ENDIAN 0
+#define HASH_BIG_ENDIAN 1
+#else
+#error Unknown endian
+#endif
+
+#define hashsize(n) ((uint32_t)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+/*
+-------------------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+
+This is reversible, so any information in (a,b,c) before mix() is
+still in (a,b,c) after mix().
+
+If four pairs of (a,b,c) inputs are run through mix(), or through
+mix() in reverse, there are at least 32 bits of the output that
+are sometimes the same for one pair and different for another pair.
+This was tested for:
+* pairs that differed by one bit, by two bits, in any combination
+  of top bits of (a,b,c), or in any combination of bottom bits of
+  (a,b,c).
+* "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
+  the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+  is commonly produced by subtraction) look like a single 1-bit
+  difference.
+* the base values were pseudorandom, all zero but one bit set, or
+  all zero plus a counter that starts at zero.
+
+Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
+satisfy this are
+    4  6  8 16 19  4
+    9 15  3 18 27 15
+   14  9  3  7 17  3
+Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+for "differ" defined as + with a one-bit base and a two-bit delta.  I
+used http://burtleburtle.net/bob/hash/avalanche.html to choose
+the operations, constants, and arrangements of the variables.
+
+This does not achieve avalanche.  There are input bits of (a,b,c)
+that fail to affect some output bits of (a,b,c), especially of a.  The
+most thoroughly mixed value is c, but it doesn't really even achieve
+avalanche in c.
+
+This allows some parallelism.  Read-after-writes are good at doubling
+the number of bits affected, so the goal of mixing pulls in the opposite
+direction as the goal of parallelism.  I did what I could.  Rotates
+seem to cost as much as shifts on every machine I could lay my hands
+on, and rotates are much kinder to the top and bottom bits, so I used
+rotates.
+-------------------------------------------------------------------------------
+*/
+#define mix(a,b,c) \
+{ \
+  a -= c;  a ^= rot(c, 4);  c += b; \
+  b -= a;  b ^= rot(a, 6);  a += c; \
+  c -= b;  c ^= rot(b, 8);  b += a; \
+  a -= c;  a ^= rot(c,16);  c += b; \
+  b -= a;  b ^= rot(a,19);  a += c; \
+  c -= b;  c ^= rot(b, 4);  b += a; \
+}
+
+/*
+-------------------------------------------------------------------------------
+final -- final mixing of 3 32-bit values (a,b,c) into c
+
+Pairs of (a,b,c) values differing in only a few bits will usually
+produce values of c that look totally different.  This was tested for
+* pairs that differed by one bit, by two bits, in any combination
+  of top bits of (a,b,c), or in any combination of bottom bits of
+  (a,b,c).
+* "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
+  the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+  is commonly produced by subtraction) look like a single 1-bit
+  difference.
+* the base values were pseudorandom, all zero but one bit set, or
+  all zero plus a counter that starts at zero.
+
+These constants passed:
+ 14 11 25 16 4 14 24
+ 12 14 25 16 4 14 24
+and these came close:
+  4  8 15 26 3 22 24
+ 10  8 15 26 3 22 24
+ 11  8 15 26 3 22 24
+-------------------------------------------------------------------------------
+*/
+#define final(a,b,c) \
+{ \
+  c ^= b; c -= rot(b,14); \
+  a ^= c; a -= rot(c,11); \
+  b ^= a; b -= rot(a,25); \
+  c ^= b; c -= rot(b,16); \
+  a ^= c; a -= rot(c,4);  \
+  b ^= a; b -= rot(a,14); \
+  c ^= b; c -= rot(b,24); \
+}
+
+/*
+-------------------------------------------------------------------------------
+hashlittle() -- hash a variable-length key into a 32-bit value
+  k       : the key (the unaligned variable-length array of bytes)
+  length  : the length of the key, counting by bytes
+  val2    : IN: can be any 4-byte value OUT: second 32 bit hash.
+Returns a 32-bit value.  Every bit of the key affects every bit of
+the return value.  Two keys differing by one or two bits will have
+totally different hash values.  Note that the return value is better
+mixed than val2, so use that first.
+
+The best hash table sizes are powers of 2.  There is no need to do
+mod a prime (mod is sooo slow!).  If you need less than 32 bits,
+use a bitmask.  For example, if you need only 10 bits, do
+  h = (h & hashmask(10));
+In which case, the hash table should have hashsize(10) elements.
+
+If you are hashing n strings (uint8_t **)k, do it like this:
+  for (i=0, h=0; i<n; ++i) h = hashlittle( k[i], len[i], h);
+
+By Bob Jenkins, 2006.  bob_jenkins@burtleburtle.net.  You may use this
+code any way you wish, private, educational, or commercial.  It's free.
+
+Use for hash table lookup, or anything where one collision in 2^^32 is
+acceptable.  Do NOT use for cryptographic purposes.
+-------------------------------------------------------------------------------
+*/
+
+static uint32_t hashlittle( const void *key, size_t length, uint32_t *val2 )
+{
+  uint32_t a,b,c;                                          /* internal state */
+  union { const void *ptr; size_t i; } u;     /* needed for Mac Powerbook G4 */
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + ((uint32_t)length) + *val2;
+
+  u.ptr = key;
+  if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+    const uint32_t *k = (const uint32_t *)key;         /* read 32-bit chunks */
+    const uint8_t  *k8;
+
+    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      b += k[1];
+      c += k[2];
+      mix(a,b,c);
+      length -= 12;
+      k += 3;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    /*
+     * "k[2]&0xffffff" actually reads beyond the end of the string, but
+     * then masks off the part it's not allowed to read.  Because the
+     * string is aligned, the masked-off tail is in the same word as the
+     * rest of the string.  Every machine with memory protection I've seen
+     * does it on word boundaries, so is OK with this.  But VALGRIND will
+     * still catch it and complain.  The masking trick does make the hash
+     * noticably faster for short strings (like English words).
+     *
+     * Not on my testing with gcc 4.5 on an intel i5 CPU, at least --RR.
+     */
+#if 0
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+    case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+    case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+    case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+    case 5 : b+=k[1]&0xff; a+=k[0]; break;
+    case 4 : a+=k[0]; break;
+    case 3 : a+=k[0]&0xffffff; break;
+    case 2 : a+=k[0]&0xffff; break;
+    case 1 : a+=k[0]&0xff; break;
+    case 0 : return c;              /* zero length strings require no mixing */
+    }
+
+#else /* make valgrind happy */
+
+    k8 = (const uint8_t *)k;
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=((uint32_t)k8[10])<<16;  /* fall through */
+    case 10: c+=((uint32_t)k8[9])<<8;    /* fall through */
+    case 9 : c+=k8[8];                   /* fall through */
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=((uint32_t)k8[6])<<16;   /* fall through */
+    case 6 : b+=((uint32_t)k8[5])<<8;    /* fall through */
+    case 5 : b+=k8[4];                   /* fall through */
+    case 4 : a+=k[0]; break;
+    case 3 : a+=((uint32_t)k8[2])<<16;   /* fall through */
+    case 2 : a+=((uint32_t)k8[1])<<8;    /* fall through */
+    case 1 : a+=k8[0]; break;
+    case 0 : return c;
+    }
+
+#endif /* !valgrind */
+
+  } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+    const uint16_t *k = (const uint16_t *)key;         /* read 16-bit chunks */
+    const uint8_t  *k8;
+
+    /*--------------- all but last block: aligned reads and different mixing */
+    while (length > 12)
+    {
+      a += k[0] + (((uint32_t)k[1])<<16);
+      b += k[2] + (((uint32_t)k[3])<<16);
+      c += k[4] + (((uint32_t)k[5])<<16);
+      mix(a,b,c);
+      length -= 12;
+      k += 6;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    k8 = (const uint8_t *)k;
+    switch(length)
+    {
+    case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+             b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 11: c+=((uint32_t)k8[10])<<16;     /* fall through */
+    case 10: c+=k[4];
+             b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 9 : c+=k8[8];                      /* fall through */
+    case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 7 : b+=((uint32_t)k8[6])<<16;      /* fall through */
+    case 6 : b+=k[2];
+             a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 5 : b+=k8[4];                      /* fall through */
+    case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+             break;
+    case 3 : a+=((uint32_t)k8[2])<<16;      /* fall through */
+    case 2 : a+=k[0];
+             break;
+    case 1 : a+=k8[0];
+             break;
+    case 0 : return c;                     /* zero length requires no mixing */
+    }
+
+  } else {                        /* need to read the key one byte at a time */
+    const uint8_t *k = (const uint8_t *)key;
+
+    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      a += ((uint32_t)k[1])<<8;
+      a += ((uint32_t)k[2])<<16;
+      a += ((uint32_t)k[3])<<24;
+      b += k[4];
+      b += ((uint32_t)k[5])<<8;
+      b += ((uint32_t)k[6])<<16;
+      b += ((uint32_t)k[7])<<24;
+      c += k[8];
+      c += ((uint32_t)k[9])<<8;
+      c += ((uint32_t)k[10])<<16;
+      c += ((uint32_t)k[11])<<24;
+      mix(a,b,c);
+      length -= 12;
+      k += 12;
+    }
+
+    /*-------------------------------- last block: affect all 32 bits of (c) */
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=((uint32_t)k[11])<<24;
+    case 11: c+=((uint32_t)k[10])<<16;
+    case 10: c+=((uint32_t)k[9])<<8;
+    case 9 : c+=k[8];
+    case 8 : b+=((uint32_t)k[7])<<24;
+    case 7 : b+=((uint32_t)k[6])<<16;
+    case 6 : b+=((uint32_t)k[5])<<8;
+    case 5 : b+=k[4];
+    case 4 : a+=((uint32_t)k[3])<<24;
+    case 3 : a+=((uint32_t)k[2])<<16;
+    case 2 : a+=((uint32_t)k[1])<<8;
+    case 1 : a+=k[0];
+             break;
+    case 0 : return c;
+    }
+  }
+
+  final(a,b,c);
+  *val2 = b;
+  return c;
+}
+
+/*
+ * hashbig():
+ * This is the same as hash_word() on big-endian machines.  It is different
+ * from hashlittle() on all machines.  hashbig() takes advantage of
+ * big-endian byte ordering.
+ */
+static uint32_t hashbig( const void *key, size_t length, uint32_t *val2)
+{
+  uint32_t a,b,c;
+  union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */
+
+  /* Set up the internal state */
+  a = b = c = 0xdeadbeef + ((uint32_t)length) + *val2;
+
+  u.ptr = key;
+  if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
+    const uint32_t *k = (const uint32_t *)key;         /* read 32-bit chunks */
+    const uint8_t  *k8;
+
+    /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += k[0];
+      b += k[1];
+      c += k[2];
+      mix(a,b,c);
+      length -= 12;
+      k += 3;
+    }
+
+    /*----------------------------- handle the last (probably partial) block */
+    /*
+     * "k[2]<<8" actually reads beyond the end of the string, but
+     * then shifts out the part it's not allowed to read.  Because the
+     * string is aligned, the illegal read is in the same word as the
+     * rest of the string.  Every machine with memory protection I've seen
+     * does it on word boundaries, so is OK with this.  But VALGRIND will
+     * still catch it and complain.  The masking trick does make the hash
+     * noticably faster for short strings (like English words).
+     *
+     * Not on my testing with gcc 4.5 on an intel i5 CPU, at least --RR.
+     */
+#if 0
+    switch(length)
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
+    case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
+    case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=k[1]&0xffffff00; a+=k[0]; break;
+    case 6 : b+=k[1]&0xffff0000; a+=k[0]; break;
+    case 5 : b+=k[1]&0xff000000; a+=k[0]; break;
+    case 4 : a+=k[0]; break;
+    case 3 : a+=k[0]&0xffffff00; break;
+    case 2 : a+=k[0]&0xffff0000; break;
+    case 1 : a+=k[0]&0xff000000; break;
+    case 0 : return c;              /* zero length strings require no mixing */
+    }
+
+#else  /* make valgrind happy */
+
+    k8 = (const uint8_t *)k;
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+    case 11: c+=((uint32_t)k8[10])<<8;  /* fall through */
+    case 10: c+=((uint32_t)k8[9])<<16;  /* fall through */
+    case 9 : c+=((uint32_t)k8[8])<<24;  /* fall through */
+    case 8 : b+=k[1]; a+=k[0]; break;
+    case 7 : b+=((uint32_t)k8[6])<<8;   /* fall through */
+    case 6 : b+=((uint32_t)k8[5])<<16;  /* fall through */
+    case 5 : b+=((uint32_t)k8[4])<<24;  /* fall through */
+    case 4 : a+=k[0]; break;
+    case 3 : a+=((uint32_t)k8[2])<<8;   /* fall through */
+    case 2 : a+=((uint32_t)k8[1])<<16;  /* fall through */
+    case 1 : a+=((uint32_t)k8[0])<<24; break;
+    case 0 : return c;
+    }
+
+#endif /* !VALGRIND */
+
+  } else {                        /* need to read the key one byte at a time */
+    const uint8_t *k = (const uint8_t *)key;
+
+    /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+    while (length > 12)
+    {
+      a += ((uint32_t)k[0])<<24;
+      a += ((uint32_t)k[1])<<16;
+      a += ((uint32_t)k[2])<<8;
+      a += ((uint32_t)k[3]);
+      b += ((uint32_t)k[4])<<24;
+      b += ((uint32_t)k[5])<<16;
+      b += ((uint32_t)k[6])<<8;
+      b += ((uint32_t)k[7]);
+      c += ((uint32_t)k[8])<<24;
+      c += ((uint32_t)k[9])<<16;
+      c += ((uint32_t)k[10])<<8;
+      c += ((uint32_t)k[11]);
+      mix(a,b,c);
+      length -= 12;
+      k += 12;
+    }
+
+    /*-------------------------------- last block: affect all 32 bits of (c) */
+    switch(length)                   /* all the case statements fall through */
+    {
+    case 12: c+=k[11];
+    case 11: c+=((uint32_t)k[10])<<8;
+    case 10: c+=((uint32_t)k[9])<<16;
+    case 9 : c+=((uint32_t)k[8])<<24;
+    case 8 : b+=k[7];
+    case 7 : b+=((uint32_t)k[6])<<8;
+    case 6 : b+=((uint32_t)k[5])<<16;
+    case 5 : b+=((uint32_t)k[4])<<24;
+    case 4 : a+=k[3];
+    case 3 : a+=((uint32_t)k[2])<<8;
+    case 2 : a+=((uint32_t)k[1])<<16;
+    case 1 : a+=((uint32_t)k[0])<<24;
+             break;
+    case 0 : return c;
+    }
+  }
+
+  final(a,b,c);
+  *val2 = b;
+  return c;
+}
+
+uint32_t nl_hash_any(const void *key, size_t length, uint32_t base)
+{
+	if (HASH_BIG_ENDIAN)
+		return hashbig(key, length, &base);
+	else
+		return hashlittle(key, length, &base);
+}
diff --git a/lib/hashtable.c b/lib/hashtable.c
new file mode 100644
index 0000000..8b15925
--- /dev/null
+++ b/lib/hashtable.c
@@ -0,0 +1,197 @@
+/*
+ * netlink/hashtable.c      Netlink hashtable Utilities
+ *
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation version 2.1
+ *      of the License.
+ *
+ * Copyright (c) 2012 Cumulus Networks, Inc
+ */
+#include <string.h>
+#include <netlink-private/netlink.h>
+#include <netlink/object.h>
+#include <netlink/hash.h>
+#include <netlink/hashtable.h>
+
+/**
+ * @ingroup core_types
+ * @defgroup hashtable Hashtable
+ * @{
+ */
+
+/**
+ * Allocate hashtable
+ * @arg size		Size of hashtable in number of elements
+ *
+ * @return Allocated hashtable or NULL.
+ */
+nl_hash_table_t *nl_hash_table_alloc(int size)
+{
+	nl_hash_table_t *ht;
+
+	ht = calloc(1, sizeof (*ht));
+	if (!ht)
+		goto errout;
+
+	ht->nodes = calloc(size, sizeof (*ht->nodes));
+	if (!ht->nodes) {
+		free(ht);
+		goto errout;
+	}
+
+	ht->size = size;
+
+	return ht;
+errout:
+	return NULL;
+}
+
+/**
+ * Free hashtable including all nodes
+ * @arg ht		Hashtable
+ *
+ * @note Reference counter of all objects in the hashtable will be decremented.
+ */
+void nl_hash_table_free(nl_hash_table_t *ht)
+{
+	int i;
+
+	for(i = 0; i < ht->size; i++) {
+	    nl_hash_node_t *node = ht->nodes[i];
+	    nl_hash_node_t *saved_node;
+
+	    while (node) {
+		   saved_node = node;
+		   node = node->next;
+		   nl_object_put(saved_node->obj);
+		   free(saved_node);
+	    }
+	}
+
+	free(ht->nodes);
+	free(ht);
+}
+
+/**
+ * Lookup identical object in hashtable
+ * @arg ht		Hashtable
+ * @arg	obj		Object to lookup
+ *
+ * Generates hashkey for `obj` and traverses the corresponding chain calling
+ * `nl_object_identical()` on each trying to find a match.
+ *
+ * @return Pointer to object if match was found or NULL.
+ */
+struct nl_object* nl_hash_table_lookup(nl_hash_table_t *ht,
+				       struct nl_object *obj)
+{
+	nl_hash_node_t *node;
+	uint32_t key_hash;
+
+	nl_object_keygen(obj, &key_hash, ht->size);
+	node = ht->nodes[key_hash];
+
+	while (node) {
+	       if (nl_object_identical(node->obj, obj))
+		   return node->obj;
+	       node = node->next;
+	}
+
+	return NULL;
+}
+
+/**
+ * Add object to hashtable
+ * @arg ht		Hashtable
+ * @arg obj		Object to add
+ *
+ * Adds `obj` to the hashtable. Object type must support hashing, otherwise all
+ * objects will be added to the chain `0`.
+ *
+ * @note The reference counter of the object is incremented.
+ *
+ * @return 0 on success or a negative error code
+ * @retval -NLE_EXIST Identical object already present in hashtable
+ */
+int nl_hash_table_add(nl_hash_table_t *ht, struct nl_object *obj)
+{
+	nl_hash_node_t *node;
+	uint32_t key_hash;
+
+	nl_object_keygen(obj, &key_hash, ht->size);
+	node = ht->nodes[key_hash];
+
+	while (node) {
+	       if (nl_object_identical(node->obj, obj)) {
+		   NL_DBG(2, "Warning: Add to hashtable found duplicate...\n");
+		   return -NLE_EXIST;
+	       }
+	       node = node->next;
+	}
+
+	NL_DBG (5, "adding cache entry of obj %p in table %p, with hash 0x%x\n",
+		obj, ht, key_hash);
+
+	node = malloc(sizeof(nl_hash_node_t));
+	if (!node)
+		return -NLE_NOMEM;
+	nl_object_get(obj);
+	node->obj = obj;
+	node->key = key_hash;
+	node->key_size = sizeof(uint32_t);
+	node->next = ht->nodes[key_hash];
+	ht->nodes[key_hash] = node;
+
+	return 0;
+}
+
+/**
+ * Remove object from hashtable
+ * @arg ht		Hashtable
+ * @arg obj		Object to remove
+ *
+ * Remove `obj` from hashtable if it exists.
+ *
+ * @note Reference counter of object will be decremented.
+ *
+ * @return 0 on success or a negative error code.
+ * @retval -NLE_OBJ_NOTFOUND Object not present in hashtable.
+ */
+int nl_hash_table_del(nl_hash_table_t *ht, struct nl_object *obj)
+{
+	nl_hash_node_t *node, *prev;
+	uint32_t key_hash;
+
+	nl_object_keygen(obj, &key_hash, ht->size);
+	prev = node = ht->nodes[key_hash];
+
+	while (node) {
+	       if (nl_object_identical(node->obj, obj)) {
+		   nl_object_put(obj);
+
+		   NL_DBG (5, "deleting cache entry of obj %p in table %p, with"
+			   " hash 0x%x\n", obj, ht, key_hash);
+
+	           if (node == ht->nodes[key_hash])
+		       ht->nodes[key_hash] = node->next;
+	           else
+		       prev->next = node->next;
+
+	           free(node);
+
+	           return 0;
+		}
+		prev = node;
+		node = node->next;
+	}
+
+	return -NLE_OBJ_NOTFOUND;
+}
+
+uint32_t nl_hash(void *k, size_t length, uint32_t initval)
+{
+	return(__nl_hash(k, length, initval));
+}
+
+/** @} */
diff --git a/lib/idiag/idiag.c b/lib/idiag/idiag.c
new file mode 100644
index 0000000..81d73db
--- /dev/null
+++ b/lib/idiag/idiag.c
@@ -0,0 +1,274 @@
+/*
+ *  lib/idiag/idiag.c    Inet Diag Netlink
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+/**
+ * @defgroup  idiag Inet Diag library (libnl-idiag)
+ * @brief
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+#include <netlink/idiag/idiagnl.h>
+#include <linux/inet_diag.h>
+
+/**
+ * @name Socket Creation
+ * @{
+ */
+
+/**
+ * Create and connect idiag netlink socket.
+ * @arg sk    Netlink socket.
+ *
+ * Creates a NETLINK_INET_DIAG socket, binds the socket, and issues a connection
+ * attemp.
+ *
+ * @see nl_connect()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int idiagnl_connect(struct nl_sock *sk)
+{
+	return nl_connect(sk, NETLINK_INET_DIAG);
+}
+
+/** @} */
+
+/**
+ * @name Sending
+ * @{
+ */
+
+/**
+ * Send trivial idiag netlink message
+ * @arg sk	Netlink socket.
+ * @arg flags	Message flags
+ * @arg family	Address family
+ * @arg states	Socket states to query
+ * @arg ext	Inet Diag attribute extensions to query
+ *
+ * @return Newly allocated netlink message or NULL.
+ */
+int idiagnl_send_simple(struct nl_sock *sk, int flags, uint8_t family,
+		uint16_t states, uint16_t ext)
+{
+	struct inet_diag_req req;
+	memset(&req, 0, sizeof(req));
+
+	flags |= NLM_F_ROOT;
+
+	req.idiag_family = family;
+	req.idiag_states = states;
+	req.idiag_ext = ext;
+
+	return nl_send_simple(sk, TCPDIAG_GETSOCK, flags, &req, sizeof(req));
+}
+
+/** @} */
+
+/**
+ * @name Inet Diag flag and attribute conversions
+ * @{
+ */
+
+static const struct trans_tbl idiag_states[] = {
+	__ADD(IDIAG_SS_UNKNOWN, unknown)
+	__ADD(IDIAG_SS_ESTABLISHED, established)
+	__ADD(IDIAG_SS_SYN_SENT, syn_sent)
+	__ADD(IDIAG_SS_SYN_RECV, syn_recv)
+	__ADD(IDIAG_SS_FIN_WAIT1, fin_wait)
+	__ADD(IDIAG_SS_FIN_WAIT2, fin_wait2)
+	__ADD(IDIAG_SS_TIME_WAIT, time_wait)
+	__ADD(IDIAG_SS_CLOSE, close)
+	__ADD(IDIAG_SS_CLOSE_WAIT, close_wait)
+	__ADD(IDIAG_SS_LAST_ACK, last_ack)
+	__ADD(IDIAG_SS_LISTEN, listen)
+	__ADD(IDIAG_SS_CLOSING, closing)
+	__ADD(IDIAG_SS_MAX, max)
+	{ ((1<<IDIAG_SS_MAX)-1), "all" }
+};
+
+/**
+ * Convert inet diag socket states to strings.
+ * @arg state	  inetdiag socket state (e.g., IDIAG_SS_ESTABLISHED)
+ * @arg buf	  output buffer which will hold string result
+ * @arg len	  length in bytes of the output buffer
+ *
+ * @return string representation of the inetdiag socket state or an empty
+ * string.
+ */
+char * idiagnl_state2str(int state, char *buf, size_t len)
+{
+	return __type2str(state, buf, len, idiag_states,
+			ARRAY_SIZE(idiag_states));
+}
+
+/**
+ * Convert inet diag socket state string to int.
+ * @arg name	inetdiag socket state string
+ *
+ * @return the int representation of the socket state strign or a negative error
+ * code.
+ */
+int idiagnl_str2state(const char *name)
+{
+	return __str2type(name, idiag_states, ARRAY_SIZE(idiag_states));
+}
+
+static const struct trans_tbl idiag_timers[] = {
+	__ADD(IDIAG_TIMER_OFF, off)
+	__ADD(IDIAG_TIMER_ON, on)
+	__ADD(IDIAG_TIMER_KEEPALIVE, keepalive)
+	__ADD(IDIAG_TIMER_TIMEWAIT, timewait)
+	__ADD(IDIAG_TIMER_PERSIST, persist)
+	__ADD(IDIAG_TIMER_UNKNOWN, unknown)
+};
+
+/**
+ * Convert inet diag timer types to strings.
+ * @arg timer	  inetdiag timer (e.g., IDIAG_TIMER_ON)
+ * @arg buf	  output buffer which will hold string result
+ * @arg len	  length in bytes of the output buffer
+ *
+ * @return string representation of the inetdiag timer type or an empty string.
+ */
+char * idiagnl_timer2str(int timer, char *buf, size_t len)
+{
+	return __type2str(timer, buf, len, idiag_timers,
+	    ARRAY_SIZE(idiag_timers));
+}
+
+/**
+ * Convert inet diag timer string to int.
+ * @arg name	inetdiag timer string
+ *
+ * @return the int representation of the timer string or a negative error code.
+ */
+int idiagnl_str2timer(const char *name)
+{
+	return __str2type(name, idiag_timers, ARRAY_SIZE(idiag_timers));
+}
+
+static const struct trans_tbl idiag_attrs[] = {
+	__ADD(IDIAG_ATTR_NONE, none)
+	__ADD(IDIAG_ATTR_MEMINFO, meminfo)
+	__ADD(IDIAG_ATTR_INFO, info)
+	__ADD(IDIAG_ATTR_VEGASINFO, vegasinfo)
+	__ADD(IDIAG_ATTR_CONG, congestion)
+	__ADD(IDIAG_ATTR_TOS, tos)
+	__ADD(IDIAG_ATTR_TCLASS, tclass)
+};
+
+/**
+ * Convert inetdiag extended attributes to strings.
+ * @arg attrs	  inetdiag attribute (e.g., IDIAG_ATTR_MEMINFO)
+ * @arg buf	  output buffer which will hold string result
+ * @arg len	  length in bytes of the output buffer
+ *
+ * @return string representation of attrs or an empty string.
+ */
+char *idiagnl_attrs2str(int attrs, char *buf, size_t len)
+{
+	return __type2str(attrs, buf, len, idiag_attrs, ARRAY_SIZE(idiag_attrs));
+}
+
+static const struct trans_tbl idiagnl_tcpstates[] = {
+	__ADD(TCP_CA_Open, open)
+	__ADD(TCP_CA_Disorder, disorder)
+	__ADD(TCP_CA_CWR, cwr)
+	__ADD(TCP_CA_Recovery, recovery)
+	__ADD(TCP_CA_Loss, loss)
+};
+
+/**
+ * Convert inetdiag tcp states to strings.
+ * @arg state	TCP state (e.g., TCP_CA_Open)
+ * @arg buf	output buffer which will hold string result
+ * @arg len	length in bytes of the output buffer
+ */
+char *idiagnl_tcpstate2str(uint8_t state, char *buf, size_t len)
+{
+	return __type2str(state, buf, len, idiagnl_tcpstates,
+			ARRAY_SIZE(idiagnl_tcpstates));
+}
+
+static const struct trans_tbl idiagnl_tcpopt_attrs[] = {
+	__ADD(TCPI_OPT_TIMESTAMPS, timestamps)
+	__ADD(TCPI_OPT_SACK, sACK)
+	__ADD(TCPI_OPT_WSCALE, wscale)
+	__ADD(TCPI_OPT_ECN, ecn)
+};
+
+/**
+ * Convert TCP option attributes to string
+ * @arg attrs	  TCP option attributes to convert (e.g., TCPI_OPT_SACK |
+ *  TCPI_OPT_WSCALE)
+ * @arg	buf	  Output buffer for string
+ * @arg len	  Length in bytes of output buffer
+ *
+ * @return buffer with string representation or empty string
+ */
+char *idiagnl_tcpopts2str(uint8_t attrs, char *buf, size_t len)
+{
+	return __flags2str(attrs, buf, len, idiagnl_tcpopt_attrs,
+			ARRAY_SIZE(idiagnl_tcpopt_attrs));
+}
+
+/**
+ * Convert shutdown state to string.
+ * @arg shutdown    Shutdown state (e.g., idiag_msg->shutdown)
+ * @arg buf	    Ouput buffer to hold string representation
+ * @arg len	    Length in bytes of output buffer
+ *
+ * @return string representation of shutdown state or NULL
+ */
+char * idiagnl_shutdown2str(uint8_t shutdown, char *buf, size_t len)
+{
+  if (shutdown == 0) {
+	  snprintf(buf, len, " ");
+	  return buf;
+  } else if (shutdown == 1) {
+	  snprintf(buf, len, "receive shutdown");
+	  return buf;
+  } else if (shutdown == 2) {
+	  snprintf(buf, len, "send shutdown");
+	  return buf;
+  }
+
+  return NULL;
+}
+
+static const struct trans_tbl idiag_exts[] = {
+	__ADD(IDIAG_ATTR_NONE, none)
+	__ADD(IDIAG_ATTR_MEMINFO, meminfo)
+	__ADD(IDIAG_ATTR_INFO, info)
+	__ADD(IDIAG_ATTR_VEGASINFO, vegasinfo)
+	__ADD(IDIAG_ATTR_CONG, congestion)
+	__ADD(IDIAG_ATTR_TOS, tos)
+	__ADD(IDIAG_ATTR_TCLASS, tclass)
+};
+
+/**
+ * Convert inet diag extension flags to a string.
+ * @arg attrs	inet diag extension flags (e.g., (IDIAG_ATTR_MEMINFO |
+ *   IDIAG_ATTR_CONG | IDIAG_ATTR_TOS))
+ * @arg buf	Output buffer to hold string representation
+ * @arg len	length in bytes of the output buffer
+ */
+char *idiagnl_exts2str(uint8_t attrs, char *buf, size_t len)
+{
+	return __flags2str(attrs, buf, len, idiag_exts, ARRAY_SIZE(idiag_exts));
+}
+
+/** @} */
+/** @} */
diff --git a/lib/idiag/idiag_meminfo_obj.c b/lib/idiag/idiag_meminfo_obj.c
new file mode 100644
index 0000000..a60f497
--- /dev/null
+++ b/lib/idiag/idiag_meminfo_obj.c
@@ -0,0 +1,100 @@
+/*
+ * lib/idiag/idiagnl_meminfo_obj.c Inet Diag Meminfo Object
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/idiag/meminfo.h>
+
+/**
+ * @ingroup idiag
+ * @defgroup idiagnl_meminfo Inet Diag Memory Info
+ *
+ * @details
+ * @idiagnl_doc{idiagnl_meminfo, Inet Diag Memory Info Documentation}
+ * @{
+ */
+struct idiagnl_meminfo *idiagnl_meminfo_alloc(void)
+{
+	return (struct idiagnl_meminfo *) nl_object_alloc(&idiagnl_meminfo_obj_ops);
+}
+
+void idiagnl_meminfo_get(struct idiagnl_meminfo *minfo)
+{
+	nl_object_get((struct nl_object *) minfo);
+}
+
+void idiagnl_meminfo_put(struct idiagnl_meminfo *minfo)
+{
+	nl_object_put((struct nl_object *) minfo);
+}
+
+/**
+ * @name Attributes
+ * @{
+ */
+uint32_t idiagnl_meminfo_get_rmem(const struct idiagnl_meminfo *minfo)
+{
+	return minfo->idiag_rmem;
+}
+
+void idiagnl_meminfo_set_rmem(struct idiagnl_meminfo *minfo, uint32_t rmem)
+{
+	minfo->idiag_rmem = rmem;
+}
+
+uint32_t idiagnl_meminfo_get_wmem(const struct idiagnl_meminfo *minfo)
+{
+	return minfo->idiag_wmem;
+}
+
+void idiagnl_meminfo_set_wmem(struct idiagnl_meminfo *minfo, uint32_t wmem)
+{
+	minfo->idiag_wmem = wmem;
+}
+
+uint32_t idiagnl_meminfo_get_fmem(const struct idiagnl_meminfo *minfo)
+{
+	return minfo->idiag_fmem;
+}
+
+void idiagnl_meminfo_set_fmem(struct idiagnl_meminfo *minfo, uint32_t fmem)
+{
+	minfo->idiag_fmem = fmem;
+}
+
+uint32_t idiagnl_meminfo_get_tmem(const struct idiagnl_meminfo *minfo)
+{
+	return minfo->idiag_tmem;
+}
+
+void idiagnl_meminfo_set_tmem(struct idiagnl_meminfo *minfo, uint32_t tmem)
+{
+	minfo->idiag_tmem = tmem;
+}
+/** @} */
+
+static int idiagnl_meminfo_clone(struct nl_object *_dst, struct nl_object *_src)
+{
+	struct idiagnl_meminfo *dst = (struct idiagnl_meminfo *) _dst;
+	struct idiagnl_meminfo *src = (struct idiagnl_meminfo *) _src;
+
+	memcpy(dst, src, sizeof(struct idiagnl_meminfo));
+
+	return 0;
+}
+
+/** @cond SKIP */
+struct nl_object_ops idiagnl_meminfo_obj_ops = {
+	.oo_name	= "idiag/idiag_meminfo",
+	.oo_size	= sizeof(struct idiagnl_meminfo),
+	.oo_clone	= idiagnl_meminfo_clone,
+};
+/** @endcond */
+/** @} */
diff --git a/lib/idiag/idiag_msg_obj.c b/lib/idiag/idiag_msg_obj.c
new file mode 100644
index 0000000..19e6c5b
--- /dev/null
+++ b/lib/idiag/idiag_msg_obj.c
@@ -0,0 +1,729 @@
+/*
+ * lib/idiag/idiagnl_msg_obj.c Inet Diag Message Object
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/idiag/msg.h>
+#include <netlink/idiag/meminfo.h>
+#include <netlink/idiag/vegasinfo.h>
+#include <linux/inet_diag.h>
+
+/**
+ * @ingroup idiag
+ * @defgroup idiagnl_msg Inet Diag Messages
+ *
+ * @details
+ * @idiagnl_doc{idiagnl_msg, Inet Diag Message Documentation}
+ * @{
+ */
+struct idiagnl_msg *idiagnl_msg_alloc(void)
+{
+	return (struct idiagnl_msg *) nl_object_alloc(&idiagnl_msg_obj_ops);
+}
+
+void idiagnl_msg_get(struct idiagnl_msg *msg)
+{
+	nl_object_get((struct nl_object *) msg);
+}
+
+void idiagnl_msg_put(struct idiagnl_msg *msg)
+{
+	nl_object_put((struct nl_object *) msg);
+}
+
+static struct nl_cache_ops idiagnl_msg_ops;
+
+static int idiagnl_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
+		struct nlmsghdr *nlh, struct nl_parser_param *pp)
+{
+	struct idiagnl_msg *msg = NULL;
+	int err = 0;
+
+	if ((err = idiagnl_msg_parse(nlh, &msg)) < 0)
+		return err;
+
+	err = pp->pp_cb((struct nl_object *) msg, pp);
+	idiagnl_msg_put(msg);
+
+	return err;
+}
+
+static int idiagnl_request_update(struct nl_cache *cache, struct nl_sock *sk)
+{
+	int family = cache->c_iarg1;
+	int states = cache->c_iarg2;
+
+	return idiagnl_send_simple(sk, 0, family, states, IDIAG_ATTR_ALL);
+}
+
+static struct nl_cache_ops idiagnl_msg_ops = {
+	.co_name		= "idiag/idiag",
+	.co_hdrsize		= sizeof(struct inet_diag_msg),
+	.co_msgtypes		= {
+		{ IDIAG_TCPDIAG_GETSOCK, NL_ACT_NEW, "new" },
+		{ IDIAG_DCCPDIAG_GETSOCK, NL_ACT_NEW, "new" },
+		END_OF_MSGTYPES_LIST,
+	},
+	.co_protocol		= NETLINK_INET_DIAG,
+	.co_request_update	= idiagnl_request_update,
+	.co_msg_parser		= idiagnl_msg_parser,
+	.co_obj_ops		= &idiagnl_msg_obj_ops,
+};
+
+static void __init idiagnl_init(void)
+{
+	nl_cache_mngt_register(&idiagnl_msg_ops);
+}
+
+static void __exit idiagnl_exit(void)
+{
+	nl_cache_mngt_unregister(&idiagnl_msg_ops);
+}
+
+/**
+ * @name Cache Management
+ * @{
+ */
+
+/**
+ * Build an inetdiag cache to hold socket state information.
+ * @arg	sk      Netlink socket
+ * @arg family  The address family to query
+ * @arg states  Socket states to query
+ * @arg result  Result pointer
+ *
+ * @note The caller is responsible for destroying and free the cache after using
+ *  it.
+ * @return 0 on success of a negative error code.
+ */
+int idiagnl_msg_alloc_cache(struct nl_sock *sk, int family, int states,
+		struct nl_cache **result)
+{
+	struct nl_cache *cache = NULL;
+	int err;
+
+	if (!(cache = nl_cache_alloc(&idiagnl_msg_ops)))
+		return -NLE_NOMEM;
+
+	cache->c_iarg1 = family;
+	cache->c_iarg2 = states;
+
+	if (sk && (err = nl_cache_refill(sk, cache)) < 0) {
+		free(cache);
+		return err;
+	}
+
+	*result = cache;
+	return 0;
+}
+
+/** @} */
+
+/**
+ * @name Attributes
+ * @{
+ */
+
+uint8_t idiagnl_msg_get_family(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_family;
+}
+
+void idiagnl_msg_set_family(struct idiagnl_msg *msg, uint8_t family)
+{
+	msg->idiag_family = family;
+}
+
+uint8_t idiagnl_msg_get_state(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_state;
+}
+
+void idiagnl_msg_set_state(struct idiagnl_msg *msg, uint8_t state)
+{
+	msg->idiag_state = state;
+}
+
+uint8_t idiagnl_msg_get_timer(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_timer;
+}
+
+void idiagnl_msg_set_timer(struct idiagnl_msg *msg, uint8_t timer)
+{
+	msg->idiag_timer = timer;
+}
+
+uint8_t idiagnl_msg_get_retrans(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_retrans;
+}
+
+void idiagnl_msg_set_retrans(struct idiagnl_msg *msg, uint8_t retrans)
+{
+	msg->idiag_retrans = retrans;
+}
+
+uint16_t idiagnl_msg_get_sport(struct idiagnl_msg *msg)
+{
+	return msg->idiag_sport;
+}
+
+void idiagnl_msg_set_sport(struct idiagnl_msg *msg, uint16_t port)
+{
+	msg->idiag_sport = port;
+}
+
+uint16_t idiagnl_msg_get_dport(struct idiagnl_msg *msg)
+{
+	return msg->idiag_dport;
+}
+
+void idiagnl_msg_set_dport(struct idiagnl_msg *msg, uint16_t port)
+{
+	msg->idiag_dport = port;
+}
+
+struct nl_addr *idiagnl_msg_get_src(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_src;
+}
+
+int idiagnl_msg_set_src(struct idiagnl_msg *msg, struct nl_addr *addr)
+{
+	if (msg->idiag_src)
+		nl_addr_put(msg->idiag_src);
+
+	nl_addr_get(addr);
+	msg->idiag_src = addr;
+
+	return 0;
+}
+
+struct nl_addr *idiagnl_msg_get_dst(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_dst;
+}
+
+int idiagnl_msg_set_dst(struct idiagnl_msg *msg, struct nl_addr *addr)
+{
+	if (msg->idiag_dst)
+		nl_addr_put(msg->idiag_dst);
+
+	nl_addr_get(addr);
+	msg->idiag_dst = addr;
+
+	return 0;
+}
+
+uint32_t idiagnl_msg_get_ifindex(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_ifindex;
+}
+
+void idiagnl_msg_set_ifindex(struct idiagnl_msg *msg, uint32_t ifindex)
+{
+	msg->idiag_ifindex = ifindex;
+}
+
+uint32_t idiagnl_msg_get_expires(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_expires;
+}
+
+void idiagnl_msg_set_expires(struct idiagnl_msg *msg, uint32_t expires)
+{
+	msg->idiag_expires = expires;
+}
+
+uint32_t idiagnl_msg_get_rqueue(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_rqueue;
+}
+
+void idiagnl_msg_set_rqueue(struct idiagnl_msg *msg, uint32_t rqueue)
+{
+	msg->idiag_rqueue = rqueue;
+}
+
+uint32_t idiagnl_msg_get_wqueue(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_wqueue;
+}
+
+void idiagnl_msg_set_wqueue(struct idiagnl_msg *msg, uint32_t wqueue)
+{
+	msg->idiag_wqueue = wqueue;
+}
+
+uint32_t idiagnl_msg_get_uid(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_uid;
+}
+
+void idiagnl_msg_set_uid(struct idiagnl_msg *msg, uint32_t uid)
+{
+	msg->idiag_uid = uid;
+}
+
+uint32_t idiagnl_msg_get_inode(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_inode;
+}
+
+void idiagnl_msg_set_inode(struct idiagnl_msg *msg, uint32_t inode)
+{
+	msg->idiag_inode = inode;
+}
+
+uint8_t idiagnl_msg_get_tos(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_tos;
+}
+
+void idiagnl_msg_set_tos(struct idiagnl_msg *msg, uint8_t tos)
+{
+	msg->idiag_tos = tos;
+}
+
+uint8_t idiagnl_msg_get_tclass(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_tclass;
+}
+
+void idiagnl_msg_set_tclass(struct idiagnl_msg *msg, uint8_t tclass)
+{
+	msg->idiag_tclass = tclass;
+}
+
+uint8_t	idiagnl_msg_get_shutdown(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_shutdown;
+}
+
+void  idiagnl_msg_set_shutdown(struct idiagnl_msg *msg, uint8_t shutdown)
+{
+	msg->idiag_shutdown = shutdown;
+}
+
+char *idiagnl_msg_get_cong(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_cong;
+}
+
+void idiagnl_msg_set_cong(struct idiagnl_msg *msg, char *cong)
+{
+	msg->idiag_cong = strdup(cong);
+}
+
+struct idiagnl_meminfo *idiagnl_msg_get_meminfo(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_meminfo;
+}
+
+void idiagnl_msg_set_meminfo(struct idiagnl_msg *msg, struct idiagnl_meminfo
+		*minfo)
+{
+	if (msg->idiag_meminfo)
+		idiagnl_meminfo_put(msg->idiag_meminfo);
+
+	idiagnl_meminfo_get(minfo);
+	msg->idiag_meminfo = minfo;
+}
+
+struct idiagnl_vegasinfo *idiagnl_msg_get_vegasinfo(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_vegasinfo;
+}
+
+void idiagnl_msg_set_vegasinfo(struct idiagnl_msg *msg, struct idiagnl_vegasinfo
+		*vinfo)
+{
+	if (msg->idiag_vegasinfo)
+		idiagnl_vegasinfo_put(msg->idiag_vegasinfo);
+
+	idiagnl_vegasinfo_get(vinfo);
+	msg->idiag_vegasinfo = vinfo;
+}
+
+struct tcp_info idiagnl_msg_get_tcpinfo(const struct idiagnl_msg *msg)
+{
+	return msg->idiag_tcpinfo;
+}
+
+void idiagnl_msg_set_tcpinfo(struct idiagnl_msg *msg, struct tcp_info *tinfo)
+{
+	memcpy(&msg->idiag_tcpinfo, tinfo, sizeof(struct tcp_info));
+}
+
+/** @} */
+
+static void idiag_msg_dump_line(struct nl_object *a, struct nl_dump_params *p)
+{
+	struct idiagnl_msg *msg = (struct idiagnl_msg *) a;
+	char buf[64] = { 0 };
+
+	nl_dump_line(p, "family: %s ", nl_af2str(msg->idiag_family, buf, sizeof(buf)));
+	nl_dump(p, "src: %s:%d ", nl_addr2str(msg->idiag_src, buf, sizeof(buf)),
+			ntohs(msg->idiag_sport));
+	nl_dump(p, "dst: %s:%d ", nl_addr2str(msg->idiag_dst, buf, sizeof(buf)),
+			ntohs(msg->idiag_dport));
+	nl_dump(p, "iif: %d ", msg->idiag_ifindex);
+	nl_dump(p, "\n");
+}
+
+static void idiag_msg_dump_details(struct nl_object *a, struct nl_dump_params *p)
+{
+	struct idiagnl_msg *msg = (struct idiagnl_msg *) a;
+	char buf[64], buf2[64];
+
+	nl_dump(p, "\nfamily: %s\n", nl_af2str(msg->idiag_family, buf, sizeof(buf)));
+	nl_dump(p, "state: %s\n",
+			idiagnl_state2str(msg->idiag_state, buf, sizeof(buf)));
+	nl_dump(p, "timer (%s, %s, retransmits: %d)\n",
+			idiagnl_timer2str(msg->idiag_timer, buf, sizeof(buf)),
+			nl_msec2str(msg->idiag_expires, buf2, sizeof(buf2)),
+			msg->idiag_retrans);
+
+	nl_dump(p, "source: %s:%d\n", nl_addr2str(msg->idiag_src, buf, sizeof(buf)),
+			ntohs(msg->idiag_sport));
+	nl_dump(p, "destination: %s:%d\n", nl_addr2str(msg->idiag_dst, buf, sizeof(buf)),
+			ntohs(msg->idiag_dport));
+
+	nl_dump(p, "ifindex: %d\n", msg->idiag_ifindex);
+	nl_dump(p, "rqueue: %-6d wqueue: %-6d\n", msg->idiag_rqueue, msg->idiag_wqueue);
+	nl_dump(p, "uid %d\n", msg->idiag_uid);
+	nl_dump(p, "inode %d\n", msg->idiag_inode);
+	if (msg->idiag_shutdown) {
+		nl_dump(p, "socket shutdown: %s\n",
+				idiagnl_shutdown2str(msg->idiag_shutdown,
+					buf, sizeof(buf)));
+	}
+
+	nl_dump(p, "tos: 0x%x\n", msg->idiag_tos);
+	nl_dump(p, "traffic class: %d\n", msg->idiag_tclass);
+	nl_dump(p, "congestion algorithm: %s\n", msg->idiag_cong);
+}
+
+static void idiag_msg_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
+{
+	struct idiagnl_msg *msg = (struct idiagnl_msg *) obj;
+	char buf[64];
+
+	idiag_msg_dump_details(obj, p);
+
+	nl_dump(p, "tcp info:  [\n");
+	nl_dump(p, "\tsocket state: %s\n",
+			idiagnl_state2str(msg->idiag_tcpinfo.tcpi_state,
+				buf, sizeof(buf)));
+	nl_dump(p, "\ttcp state: %s\n",
+			idiagnl_tcpstate2str(msg->idiag_tcpinfo.tcpi_ca_state,
+				buf, sizeof(buf)));
+	nl_dump(p, "\tretransmits: %d\n",
+			msg->idiag_tcpinfo.tcpi_retransmits);
+	nl_dump(p, "\tprobes: %d\n",
+			msg->idiag_tcpinfo.tcpi_probes);
+	nl_dump(p, "\tbackoff: %d\n",
+			msg->idiag_tcpinfo.tcpi_backoff);
+	nl_dump(p, "\toptions: %s\n",
+			idiagnl_tcpopts2str(msg->idiag_tcpinfo.tcpi_options,
+				buf, sizeof(buf)));
+	nl_dump(p, "\tsnd_wscale: %d\n", msg->idiag_tcpinfo.tcpi_snd_wscale);
+	nl_dump(p, "\trcv_wscale: %d\n", msg->idiag_tcpinfo.tcpi_rcv_wscale);
+	nl_dump(p, "\trto: %d\n", msg->idiag_tcpinfo.tcpi_rto);
+	nl_dump(p, "\tato: %d\n", msg->idiag_tcpinfo.tcpi_ato);
+	nl_dump(p, "\tsnd_mss: %s\n", nl_size2str(msg->idiag_tcpinfo.tcpi_snd_mss,
+				buf, sizeof(buf)));
+	nl_dump(p, "\trcv_mss: %s\n", nl_size2str(msg->idiag_tcpinfo.tcpi_rcv_mss,
+				buf, sizeof(buf)));
+	nl_dump(p, "\tunacked: %d\n", msg->idiag_tcpinfo.tcpi_unacked);
+	nl_dump(p, "\tsacked: %d\n", msg->idiag_tcpinfo.tcpi_sacked);
+
+	nl_dump(p, "\tlost: %d\n", msg->idiag_tcpinfo.tcpi_lost);
+	nl_dump(p, "\tretransmit segments: %d\n",
+			msg->idiag_tcpinfo.tcpi_retrans);
+	nl_dump(p, "\tfackets: %d\n",
+			msg->idiag_tcpinfo.tcpi_fackets);
+	nl_dump(p, "\tlast data sent: %s\n",
+			nl_msec2str(msg->idiag_tcpinfo.tcpi_last_data_sent, buf,
+				sizeof(buf)));
+	nl_dump(p, "\tlast ack sent: %s\n",
+			nl_msec2str(msg->idiag_tcpinfo.tcpi_last_ack_sent, buf, sizeof(buf)));
+	nl_dump(p, "\tlast data recv: %s\n",
+			nl_msec2str(msg->idiag_tcpinfo.tcpi_last_data_recv, buf,
+				sizeof(buf)));
+	nl_dump(p, "\tlast ack recv: %s\n",
+			nl_msec2str(msg->idiag_tcpinfo.tcpi_last_ack_recv, buf,
+				sizeof(buf)));
+	nl_dump(p, "\tpath mtu: %s\n",
+			nl_size2str(msg->idiag_tcpinfo.tcpi_pmtu, buf,
+				sizeof(buf)));
+	nl_dump(p, "\trcv ss threshold: %d\n",
+			msg->idiag_tcpinfo.tcpi_rcv_ssthresh);
+	nl_dump(p, "\tsmoothed round trip time: %d\n",
+			msg->idiag_tcpinfo.tcpi_rtt);
+	nl_dump(p, "\tround trip time variation: %d\n",
+			msg->idiag_tcpinfo.tcpi_rttvar);
+	nl_dump(p, "\tsnd ss threshold: %s\n",
+			nl_size2str(msg->idiag_tcpinfo.tcpi_snd_ssthresh, buf,
+				sizeof(buf)));
+	nl_dump(p, "\tsend congestion window: %d\n",
+			msg->idiag_tcpinfo.tcpi_snd_cwnd);
+	nl_dump(p, "\tadvertised mss: %s\n",
+			nl_size2str(msg->idiag_tcpinfo.tcpi_advmss, buf,
+				sizeof(buf)));
+	nl_dump(p, "\treordering: %d\n",
+			msg->idiag_tcpinfo.tcpi_reordering);
+	nl_dump(p, "\trcv rround trip time: %d\n",
+			msg->idiag_tcpinfo.tcpi_rcv_rtt);
+	nl_dump(p, "\treceive queue space: %s\n",
+			nl_size2str(msg->idiag_tcpinfo.tcpi_rcv_space, buf,
+				sizeof(buf)));
+	nl_dump(p, "\ttotal retransmits: %d\n",
+			msg->idiag_tcpinfo.tcpi_total_retrans);
+	nl_dump(p, "]\n");
+
+	if (msg->idiag_meminfo) {
+		nl_dump(p, "meminfo:  [\n");
+		nl_dump(p, "\trmem: %s\n",
+				nl_size2str(msg->idiag_meminfo->idiag_rmem,
+					    buf,
+					    sizeof(buf)));
+		nl_dump(p, "\twmem: %s\n",
+				nl_size2str(msg->idiag_meminfo->idiag_wmem,
+					    buf,
+					    sizeof(buf)));
+		nl_dump(p, "\tfmem: %s\n",
+				nl_size2str(msg->idiag_meminfo->idiag_fmem,
+					    buf,
+					    sizeof(buf)));
+		nl_dump(p, "\ttmem: %s\n",
+				nl_size2str(msg->idiag_meminfo->idiag_tmem,
+					    buf,
+					    sizeof(buf)));
+		nl_dump(p, "]\n");
+	}
+
+	if (msg->idiag_vegasinfo) {
+		nl_dump(p, "vegasinfo:  [\n");
+		nl_dump(p, "\tvegas enabled: %d\n",
+				msg->idiag_vegasinfo->tcpv_enabled);
+		if (msg->idiag_vegasinfo->tcpv_enabled) {
+			nl_dump(p, "\trtt cnt: %d",
+					msg->idiag_vegasinfo->tcpv_rttcnt);
+			nl_dump(p, "\trtt (propagation delay): %d",
+					msg->idiag_vegasinfo->tcpv_rtt);
+			nl_dump(p, "\tmin rtt: %d",
+					msg->idiag_vegasinfo->tcpv_minrtt);
+		}
+		nl_dump(p, "]\n");
+	}
+
+	nl_dump(p, "skmeminfo:  [\n");
+	nl_dump(p, "\trmem alloc: %d\n",
+			msg->idiag_skmeminfo[IDIAG_SK_MEMINFO_RMEM_ALLOC]);
+	nl_dump(p, "\trcv buf: %s\n",
+			nl_size2str(msg->idiag_skmeminfo[IDIAG_SK_MEMINFO_RCVBUF],
+				buf, sizeof(buf)));
+	nl_dump(p, "\twmem alloc: %d\n",
+			msg->idiag_skmeminfo[IDIAG_SK_MEMINFO_WMEM_ALLOC]);
+	nl_dump(p, "\tsnd buf: %s\n",
+			nl_size2str(msg->idiag_skmeminfo[IDIAG_SK_MEMINFO_SNDBUF],
+				buf, sizeof(buf)));
+	nl_dump(p, "\tfwd alloc: %d\n",
+			msg->idiag_skmeminfo[IDIAG_SK_MEMINFO_FWD_ALLOC]);
+	nl_dump(p, "\twmem queued: %s\n",
+			nl_size2str(msg->idiag_skmeminfo[IDIAG_SK_MEMINFO_WMEM_QUEUED],
+				buf, sizeof(buf)));
+	nl_dump(p, "\topt mem: %d\n",
+			msg->idiag_skmeminfo[IDIAG_SK_MEMINFO_OPTMEM]);
+	nl_dump(p, "\tbacklog: %d\n",
+			msg->idiag_skmeminfo[IDIAG_SK_MEMINFO_BACKLOG]);
+	nl_dump(p, "]\n\n");
+}
+
+static void idiagnl_msg_free(struct nl_object *a)
+{
+	struct idiagnl_msg *msg = (struct idiagnl_msg *) a;
+	if (a == NULL)
+		return;
+
+	free(msg->idiag_cong);
+	nl_addr_put(msg->idiag_src);
+	nl_addr_put(msg->idiag_dst);
+	idiagnl_meminfo_put(msg->idiag_meminfo);
+	idiagnl_vegasinfo_put(msg->idiag_vegasinfo);
+}
+
+static int idiagnl_msg_clone(struct nl_object *_dst, struct nl_object *_src)
+{
+	struct idiagnl_msg *dst = (struct idiagnl_msg *) _dst;
+	struct idiagnl_msg *src = (struct idiagnl_msg *) _src;
+
+	if (src->idiag_src)
+		if (!(dst->idiag_src = nl_addr_clone(src->idiag_src)))
+			return -NLE_NOMEM;
+
+	if (src->idiag_dst)
+		if (!(dst->idiag_dst = nl_addr_clone(src->idiag_dst)))
+			return -NLE_NOMEM;
+
+	return 0;
+}
+
+static struct nla_policy ext_policy[IDIAG_ATTR_MAX] = {
+	[IDIAG_ATTR_MEMINFO]    = { .minlen = sizeof(struct inet_diag_meminfo) },
+	[IDIAG_ATTR_INFO]       = { .minlen = sizeof(struct tcp_info)	},
+	[IDIAG_ATTR_VEGASINFO]  = { .minlen = sizeof(struct tcpvegas_info) },
+	[IDIAG_ATTR_CONG]       = { .type = NLA_STRING },
+	[IDIAG_ATTR_TOS]        = { .type = NLA_U8 },
+	[IDIAG_ATTR_TCLASS]     = { .type = NLA_U8 },
+	[IDIAG_ATTR_SKMEMINFO]  = { .minlen = (sizeof(uint32_t) * IDIAG_SK_MEMINFO_VARS)  },
+	[IDIAG_ATTR_SHUTDOWN]   = { .type = NLA_U8 },
+};
+
+int idiagnl_msg_parse(struct nlmsghdr *nlh, struct idiagnl_msg **result)
+{
+	struct idiagnl_msg *msg = NULL;
+	struct inet_diag_msg *raw_msg = NULL;
+	struct nl_addr *src = NULL, *dst = NULL;
+	struct nlattr *tb[IDIAG_ATTR_MAX];
+	int err = 0;
+
+	msg = idiagnl_msg_alloc();
+	if (!msg)
+		goto errout_nomem;
+
+	err = nlmsg_parse(nlh, sizeof(struct inet_diag_msg), tb, IDIAG_ATTR_MAX,
+			ext_policy);
+	if (err < 0)
+		goto errout;
+
+	raw_msg = nlmsg_data(nlh);
+	msg->idiag_family = raw_msg->idiag_family;
+	msg->idiag_state = raw_msg->idiag_state;
+	msg->idiag_timer = raw_msg->idiag_timer;
+	msg->idiag_retrans = raw_msg->idiag_retrans;
+	msg->idiag_expires = raw_msg->idiag_expires;
+	msg->idiag_rqueue = raw_msg->idiag_rqueue;
+	msg->idiag_wqueue = raw_msg->idiag_wqueue;
+	msg->idiag_uid = raw_msg->idiag_uid;
+	msg->idiag_inode = raw_msg->idiag_inode;
+	msg->idiag_sport = raw_msg->id.idiag_sport;
+	msg->idiag_dport = raw_msg->id.idiag_dport;
+	msg->idiag_ifindex = raw_msg->id.idiag_if;
+
+	dst = nl_addr_build(raw_msg->idiag_family, raw_msg->id.idiag_dst,
+			sizeof(raw_msg->id.idiag_dst));
+	if (!dst)
+		goto errout_nomem;
+
+	err = idiagnl_msg_set_dst(msg, dst);
+	if (err < 0)
+		goto errout;
+
+	nl_addr_put(dst);
+
+	src = nl_addr_build(raw_msg->idiag_family, raw_msg->id.idiag_src,
+			sizeof(raw_msg->id.idiag_src));
+	if (!src)
+		goto errout_nomem;
+
+	err = idiagnl_msg_set_src(msg, src);
+	if (err < 0)
+		goto errout;
+
+	nl_addr_put(src);
+
+	if (tb[IDIAG_ATTR_TOS])
+		msg->idiag_tos = nla_get_u8(tb[IDIAG_ATTR_TOS]);
+
+	if (tb[IDIAG_ATTR_TCLASS])
+		msg->idiag_tclass = nla_get_u8(tb[IDIAG_ATTR_TCLASS]);
+
+	if (tb[IDIAG_ATTR_SHUTDOWN])
+		msg->idiag_shutdown = nla_get_u8(tb[IDIAG_ATTR_SHUTDOWN]);
+
+	if (tb[IDIAG_ATTR_CONG])
+		msg->idiag_cong = nla_strdup(tb[IDIAG_ATTR_CONG]);
+
+	if (tb[IDIAG_ATTR_INFO])
+		nla_memcpy(&msg->idiag_tcpinfo, tb[IDIAG_ATTR_INFO],
+				sizeof(msg->idiag_tcpinfo));
+
+	if (tb[IDIAG_ATTR_MEMINFO]) {
+		struct idiagnl_meminfo *minfo = idiagnl_meminfo_alloc();
+		struct inet_diag_meminfo *raw_minfo = NULL;
+
+		if (!minfo)
+			goto errout_nomem;
+
+		raw_minfo = (struct inet_diag_meminfo *)
+			nla_data(tb[IDIAG_ATTR_MEMINFO]);
+
+		idiagnl_meminfo_set_rmem(minfo, raw_minfo->idiag_rmem);
+		idiagnl_meminfo_set_wmem(minfo, raw_minfo->idiag_wmem);
+		idiagnl_meminfo_set_fmem(minfo, raw_minfo->idiag_fmem);
+		idiagnl_meminfo_set_tmem(minfo, raw_minfo->idiag_tmem);
+
+		msg->idiag_meminfo = minfo;
+	}
+
+	if (tb[IDIAG_ATTR_VEGASINFO]) {
+		struct idiagnl_vegasinfo *vinfo = idiagnl_vegasinfo_alloc();
+		struct tcpvegas_info *raw_vinfo = NULL;
+
+		if (!vinfo)
+			goto errout_nomem;
+
+		raw_vinfo = (struct tcpvegas_info *)
+			nla_data(tb[IDIAG_ATTR_VEGASINFO]);
+
+		idiagnl_vegasinfo_set_enabled(vinfo, raw_vinfo->tcpv_enabled);
+		idiagnl_vegasinfo_set_rttcnt(vinfo, raw_vinfo->tcpv_rttcnt);
+		idiagnl_vegasinfo_set_rtt(vinfo, raw_vinfo->tcpv_rtt);
+		idiagnl_vegasinfo_set_minrtt(vinfo, raw_vinfo->tcpv_minrtt);
+
+		msg->idiag_vegasinfo = vinfo;
+	}
+
+	if (tb[IDIAG_ATTR_SKMEMINFO])
+		nla_memcpy(&msg->idiag_skmeminfo, tb[IDIAG_ATTR_SKMEMINFO],
+				sizeof(msg->idiag_skmeminfo));
+
+	*result = msg;
+	return 0;
+
+errout:
+	idiagnl_msg_put(msg);
+	return err;
+
+errout_nomem:
+	err = -NLE_NOMEM;
+	goto errout;
+}
+
+/** @cond SKIP */
+struct nl_object_ops idiagnl_msg_obj_ops = {
+	.oo_name			 = "idiag/idiag_msg",
+	.oo_size			 = sizeof(struct idiagnl_msg),
+	.oo_free_data			 = idiagnl_msg_free,
+	.oo_clone			 = idiagnl_msg_clone,
+	.oo_dump			 = {
+		[NL_DUMP_LINE]		 = idiag_msg_dump_line,
+		[NL_DUMP_DETAILS]	 = idiag_msg_dump_details,
+		[NL_DUMP_STATS]		 = idiag_msg_dump_stats,
+	},
+	.oo_attrs2str			= idiagnl_attrs2str,
+	.oo_id_attrs			= (IDIAG_ATTR_INFO)
+};
+/** @endcond */
+
+/** @} */
diff --git a/lib/idiag/idiag_req_obj.c b/lib/idiag/idiag_req_obj.c
new file mode 100644
index 0000000..d9dab8e
--- /dev/null
+++ b/lib/idiag/idiag_req_obj.c
@@ -0,0 +1,255 @@
+/*
+ * lib/idiag/idiagnl_req_obj.c Inet Diag Request Object
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/idiag/req.h>
+#include <linux/inet_diag.h>
+
+/**
+ * @ingroup idiag
+ * @defgroup idiagnl_req Inet Diag Requests
+ *
+ * @details
+ * @idiagnl_doc{idiagnl_req, Inet Diag Request Documentation}
+ * @{
+ */
+struct idiagnl_req *idiagnl_req_alloc(void)
+{
+	return (struct idiagnl_req *) nl_object_alloc(&idiagnl_req_obj_ops);
+}
+
+void idiagnl_req_get(struct idiagnl_req *req)
+{
+	nl_object_get((struct nl_object *) req);
+}
+
+void idiagnl_req_put(struct idiagnl_req *req)
+{
+	nl_object_put((struct nl_object *) req);
+}
+
+/**
+ * @name Attributes
+ * @{
+ */
+
+uint8_t idiagnl_req_get_family(const struct idiagnl_req *req)
+{
+	return req->idiag_family;
+}
+
+void idiagnl_req_set_family(struct idiagnl_req *req, uint8_t family)
+{
+	req->idiag_family = family;
+}
+
+uint8_t idiagnl_req_get_ext(const struct idiagnl_req *req)
+{
+	return req->idiag_ext;
+}
+
+void idiagnl_req_set_ext(struct idiagnl_req *req, uint8_t ext)
+{
+	req->idiag_ext = ext;
+}
+
+uint32_t idiagnl_req_get_ifindex(const struct idiagnl_req *req)
+{
+	return req->idiag_ifindex;
+}
+
+void idiagnl_req_set_ifindex(struct idiagnl_req *req, uint32_t ifindex)
+{
+	req->idiag_states = ifindex;
+}
+
+uint32_t idiagnl_req_get_states(const struct idiagnl_req *req)
+{
+	return req->idiag_states;
+}
+
+void idiagnl_req_set_states(struct idiagnl_req *req, uint32_t states)
+{
+	req->idiag_states = states;
+}
+
+uint32_t idiagnl_req_get_dbs(const struct idiagnl_req *req)
+{
+	return req->idiag_dbs;
+}
+
+void idiagnl_req_set_dbs(struct idiagnl_req *req, uint32_t dbs)
+{
+	req->idiag_dbs = dbs;
+}
+
+struct nl_addr *idiagnl_req_get_src(const struct idiagnl_req *req)
+{
+	return req->idiag_src;
+}
+
+int idiagnl_req_set_src(struct idiagnl_req *req, struct nl_addr *addr)
+{
+	if (req->idiag_src)
+		nl_addr_put(req->idiag_src);
+
+	nl_addr_get(addr);
+	req->idiag_src = addr;
+
+	return 0;
+}
+
+struct nl_addr *idiagnl_req_get_dst(const struct idiagnl_req *req)
+{
+	return req->idiag_dst;
+}
+
+int idiagnl_req_set_dst(struct idiagnl_req *req, struct nl_addr *addr)
+{
+	if (req->idiag_dst)
+		nl_addr_put(req->idiag_dst);
+
+	nl_addr_get(addr);
+	req->idiag_dst = addr;
+
+	return 0;
+}
+
+/** @} */
+
+static void idiag_req_dump_line(struct nl_object *a, struct nl_dump_params *p)
+{
+	struct idiagnl_req *req = (struct idiagnl_req *) a;
+	char buf[64] = { 0 };
+
+	nl_dump_line(p, "%s ", nl_af2str(req->idiag_family, buf, sizeof(buf)));
+	nl_dump(p, "src %s ", nl_addr2str(req->idiag_src, buf, sizeof(buf)));
+	nl_dump(p, "dst %s ", nl_addr2str(req->idiag_dst, buf, sizeof(buf)));
+	nl_dump(p, "iif %d ", req->idiag_ifindex);
+	nl_dump(p, "\n");
+}
+
+static void idiag_req_dump_details(struct nl_object *a, struct nl_dump_params *p)
+{
+	struct idiagnl_req *req = (struct idiagnl_req *) a;
+	char buf[64];
+
+	nl_dump_line(p, "    ");
+	nl_dump(p, "%s ", nl_af2str(req->idiag_family, buf, sizeof(buf)));
+	nl_dump(p, "exts %s ",
+			idiagnl_exts2str(req->idiag_ext, buf, sizeof(buf)));
+	nl_dump(p, "src %s ", nl_addr2str(req->idiag_src, buf, sizeof(buf)));
+	nl_dump(p, "dst %s ", nl_addr2str(req->idiag_dst, buf, sizeof(buf)));
+	nl_dump(p, "iif %d ", req->idiag_ifindex);
+	nl_dump(p, "states %s ", idiagnl_state2str(req->idiag_states, buf,
+				sizeof(buf)));
+	nl_dump(p, "dbs %d", req->idiag_dbs);
+	nl_dump(p, "\n");
+}
+
+static void idiag_req_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
+{
+	idiag_req_dump_details(obj, p);
+}
+
+static void idiagnl_req_free(struct nl_object *a)
+{
+	struct idiagnl_req *req = (struct idiagnl_req *) a;
+	if (a == NULL)
+		return;
+
+	nl_addr_put(req->idiag_src);
+	nl_addr_put(req->idiag_dst);
+}
+
+static int idiagnl_req_clone(struct nl_object *_dst, struct nl_object *_src)
+{
+	struct idiagnl_req *dst = (struct idiagnl_req *) _dst;
+	struct idiagnl_req *src = (struct idiagnl_req *) _src;
+
+	if (src->idiag_src)
+		if (!(dst->idiag_src = nl_addr_clone(src->idiag_src)))
+			return -NLE_NOMEM;
+
+	if (src->idiag_dst)
+		if (!(dst->idiag_dst = nl_addr_clone(src->idiag_dst)))
+			return -NLE_NOMEM;
+
+	return 0;
+}
+
+int idiagnl_req_parse(struct nlmsghdr *nlh, struct idiagnl_req **result)
+{
+	struct idiagnl_req *req = NULL;
+	struct inet_diag_req *raw_req = NULL;
+	struct nl_addr *src = NULL, *dst = NULL;
+	int err = 0;
+
+	req = idiagnl_req_alloc();
+	if (!req)
+		goto errout_nomem;
+
+	raw_req = nlmsg_data(nlh);
+	req->idiag_family = raw_req->idiag_family;
+	req->idiag_ext = raw_req->idiag_ext;
+	req->idiag_states = raw_req->idiag_states;
+	req->idiag_dbs = raw_req->idiag_dbs;
+	req->idiag_ifindex = raw_req->id.idiag_if;
+
+	dst = nl_addr_build(raw_req->idiag_family, raw_req->id.idiag_dst,
+			sizeof(raw_req->id.idiag_dst));
+	if (!dst)
+		goto errout_nomem;
+
+	err = idiagnl_req_set_dst(req, dst);
+	if (err < 0)
+		goto errout;
+
+	nl_addr_put(dst);
+
+	src = nl_addr_build(raw_req->idiag_family, raw_req->id.idiag_src,
+			sizeof(raw_req->id.idiag_src));
+	if (!src)
+		goto errout_nomem;
+
+	err = idiagnl_req_set_src(req, src);
+	if (err < 0)
+		goto errout;
+
+	nl_addr_put(src);
+
+	*result = req;
+	return 0;
+
+errout:
+	idiagnl_req_put(req);
+	return err;
+
+errout_nomem:
+	err = -NLE_NOMEM;
+	goto errout;
+}
+
+/** @cond SKIP */
+struct nl_object_ops idiagnl_req_obj_ops = {
+	.oo_name		  = "idiag/idiag_req",
+	.oo_size		  = sizeof(struct idiagnl_req),
+	.oo_free_data		  = idiagnl_req_free,
+	.oo_clone		  = idiagnl_req_clone,
+	.oo_dump		  = {
+		[NL_DUMP_LINE]	  = idiag_req_dump_line,
+		[NL_DUMP_DETAILS] = idiag_req_dump_details,
+		[NL_DUMP_STATS]	  = idiag_req_dump_stats,
+	},
+};
+/** @endcond */
+
+/** @} */
diff --git a/lib/idiag/idiag_vegasinfo_obj.c b/lib/idiag/idiag_vegasinfo_obj.c
new file mode 100644
index 0000000..5279e83
--- /dev/null
+++ b/lib/idiag/idiag_vegasinfo_obj.c
@@ -0,0 +1,104 @@
+/*
+ * lib/idiag/idiagnl_vegasinfo_obj.c Inet Diag TCP Vegas Info Object
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/idiag/vegasinfo.h>
+
+/**
+ * @ingroup idiag
+ * @defgroup idiagnl_vegasinfo Inet Diag TCP Vegas Info
+ *
+ * @details
+ * @idiagnl_doc{idiagnl_vegasinfo, Inet Diag TCP Vegas Info Documentation}
+ * @{
+ */
+struct idiagnl_vegasinfo *idiagnl_vegasinfo_alloc(void)
+{
+	return (struct idiagnl_vegasinfo *) nl_object_alloc(&idiagnl_vegasinfo_obj_ops);
+}
+
+void idiagnl_vegasinfo_get(struct idiagnl_vegasinfo *vinfo)
+{
+	nl_object_get((struct nl_object *) vinfo);
+}
+
+void idiagnl_vegasinfo_put(struct idiagnl_vegasinfo *vinfo)
+{
+	nl_object_put((struct nl_object *) vinfo);
+}
+
+/**
+ * @name Attributes
+ * @{
+ */
+uint32_t idiagnl_vegasinfo_get_enabled(const struct idiagnl_vegasinfo *vinfo)
+{
+	return vinfo->tcpv_enabled;
+}
+
+void idiagnl_vegasinfo_set_enabled(struct idiagnl_vegasinfo *vinfo, uint32_t
+		enabled)
+{
+	vinfo->tcpv_enabled = enabled;
+}
+
+uint32_t idiagnl_vegasinfo_get_rttcnt(const struct idiagnl_vegasinfo *vinfo)
+{
+	return vinfo->tcpv_rttcnt;
+}
+
+void idiagnl_vegasinfo_set_rttcnt(struct idiagnl_vegasinfo *vinfo, uint32_t
+		rttcnt)
+{
+	vinfo->tcpv_rttcnt = rttcnt;
+}
+
+uint32_t idiagnl_vegasinfo_get_rtt(const struct idiagnl_vegasinfo *vinfo)
+{
+	return vinfo->tcpv_rtt;
+}
+
+void idiagnl_vegasinfo_set_rtt(struct idiagnl_vegasinfo *vinfo, uint32_t rtt)
+{
+	vinfo->tcpv_rtt = rtt;
+}
+
+uint32_t idiagnl_vegasinfo_get_minrtt(const struct idiagnl_vegasinfo *vinfo)
+{
+	return vinfo->tcpv_minrtt;
+}
+
+void idiagnl_vegasinfo_set_minrtt(struct idiagnl_vegasinfo *vinfo, uint32_t
+		minrtt)
+{
+	vinfo->tcpv_minrtt = minrtt;
+}
+/** @} */
+
+static int idiagnl_vegasinfo_clone(struct nl_object *_dst,
+                                   struct nl_object *_src)
+{
+	struct idiagnl_vegasinfo *dst = (struct idiagnl_vegasinfo *) _dst;
+	struct idiagnl_vegasinfo *src = (struct idiagnl_vegasinfo *) _src;
+
+	memcpy(dst, src, sizeof(struct idiagnl_vegasinfo));
+
+	return 0;
+}
+
+/** @cond SKIP */
+struct nl_object_ops idiagnl_vegasinfo_obj_ops = {
+	.oo_name	= "idiag/idiag_vegasinfo",
+	.oo_size	= sizeof(struct idiagnl_vegasinfo),
+	.oo_clone	= idiagnl_vegasinfo_clone,
+};
+/** @endcond */
+/** @} */
diff --git a/lib/msg.c b/lib/msg.c
index 91b86cb..01e533d 100644
--- a/lib/msg.c
+++ b/lib/msg.c
@@ -6,159 +6,27 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup core
- * @defgroup msg Messages
+ * @defgroup msg Message Construction & Parsing
  * Netlink Message Construction/Parsing Interface
  * 
- * The following information is partly extracted from RFC3549
- * (ftp://ftp.rfc-editor.org/in-notes/rfc3549.txt)
+ * Related sections in the development guide:
+ * - @core_doc{_message_parsing_amp_construction,Message Parsing & Construction}
  *
- * @par Message Format
- * Netlink messages consist of a byte stream with one or multiple
- * Netlink headers and an associated payload.  If the payload is too big
- * to fit into a single message it, can be split over multiple Netlink
- * messages, collectively called a multipart message.  For multipart
- * messages, the first and all following headers have the \c NLM_F_MULTI
- * Netlink header flag set, except for the last header which has the
- * Netlink header type \c NLMSG_DONE.
- *
- * @par
- * The Netlink message header (\link nlmsghdr struct nlmsghdr\endlink) is shown below.
- * @code   
- * 0                   1                   2                   3
- * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |                          Length                             |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |            Type              |           Flags              |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |                      Sequence Number                        |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |                      Process ID (PID)                       |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * @endcode
- *
- * @par
- * The netlink message header and payload must be aligned properly:
- * @code
- *  <------- NLMSG_ALIGN(hlen) ------> <---- NLMSG_ALIGN(len) --->
- * +----------------------------+- - -+- - - - - - - - - - -+- - -+
- * |           Header           | Pad |       Payload       | Pad |
- * |      struct nlmsghdr       |     |                     |     |
- * +----------------------------+- - -+- - - - - - - - - - -+- - -+
- * @endcode
- * @par
- * Message Format:
- * @code
- *    <--- nlmsg_total_size(payload)  --->
- *    <-- nlmsg_msg_size(payload) ->
- *   +----------+- - -+-------------+- - -+-------- - -
- *   | nlmsghdr | Pad |   Payload   | Pad | nlmsghdr
- *   +----------+- - -+-------------+- - -+-------- - -
- *   nlmsg_data(nlh)---^                   ^
- *   nlmsg_next(nlh)-----------------------+
- * @endcode
- * @par
- * The payload may consist of arbitary data but may have strict
- * alignment and formatting rules depening on the specific netlink
- * families.
- * @par
- * @code
- *    <---------------------- nlmsg_len(nlh) --------------------->
- *    <------ hdrlen ------>       <- nlmsg_attrlen(nlh, hdrlen) ->
- *   +----------------------+- - -+--------------------------------+
- *   |     Family Header    | Pad |           Attributes           |
- *   +----------------------+- - -+--------------------------------+
- *   nlmsg_attrdata(nlh, hdrlen)---^
- * @endcode
- * @par The ACK Netlink Message
- * This message is actually used to denote both an ACK and a NACK.
- * Typically, the direction is from FEC to CPC (in response to an ACK
- * request message).  However, the CPC should be able to send ACKs back
- * to FEC when requested.
- * @code
- *  0                   1                   2                   3
- *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |                       Netlink message header                  |
- * |                       type = NLMSG_ERROR                      |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |                          Error code                           |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |                       OLD Netlink message header              |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * @endcode
- *
- * @par Example
- * @code
- * // Various methods exist to create/allocate a new netlink
- * // message. 
- * //
- * // nlmsg_alloc() will allocate an empty netlink message with
- * // a maximum payload size which defaults to the page size of
- * // the system. This default size can be modified using the
- * // function nlmsg_set_default_size().
- * struct nl_msg *msg = nlmsg_alloc();
- *
- * // Very often, the message type and message flags are known
- * // at allocation time while the other fields are auto generated:
- * struct nl_msg *msg = nlmsg_alloc_simple(MY_TYPE, MY_FLAGS);
- *
- * // Alternatively an existing netlink message header can be used
- * // to inherit the header values:
- * struct nlmsghdr hdr = {
- * 	.nlmsg_type = MY_TYPE,
- * 	.nlmsg_flags = MY_FLAGS,
- * };
- * struct nl_msg *msg = nlmsg_inherit(&hdr);
- *
- * // Last but not least, netlink messages received from netlink sockets
- * // can be converted into nl_msg objects using nlmsg_convert(). This
- * // will create a message with a maximum payload size which equals the
- * // length of the existing netlink message, therefore no more data can
- * // be appened without calling nlmsg_expand() first.
- * struct nl_msg *msg = nlmsg_convert(nlh_from_nl_sock);
- *
- * // Payload may be added to the message via nlmsg_append(). The fourth
- * // parameter specifies the number of alignment bytes the data should
- * // be padding with at the end. Common values are 0 to disable it or
- * // NLMSG_ALIGNTO to ensure proper netlink message padding.
- * nlmsg_append(msg, &mydata, sizeof(mydata), 0);
- *
- * // Sometimes it may be necessary to reserve room for data but defer
- * // the actual copying to a later point, nlmsg_reserve() can be used
- * // for this purpose:
- * void *data = nlmsg_reserve(msg, sizeof(mydata), NLMSG_ALIGNTO);
- *
- * // Attributes may be added using the attributes interface.
- *
- * // After successful use of the message, the memory must be freed
- * // using nlmsg_free()
- * nlmsg_free(msg);
- * @endcode
- * 
- * @par 4) Parsing messages
- * @code
- * int n;
- * unsigned char *buf;
- * struct nlmsghdr *hdr;
- *
- * n = nl_recv(handle, NULL, &buf);
- * 
- * hdr = (struct nlmsghdr *) buf;
- * while (nlmsg_ok(hdr, n)) {
- * 	// Process message here...
- * 	hdr = nlmsg_next(hdr, &n);
- * }
- * @endcode
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/msg.h>
+ * ~~~~
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/cache.h>
@@ -178,17 +46,28 @@
  */
 
 /**
- * length of netlink message not including padding
- * @arg payload		length of message payload
+ * Calculates size of netlink message based on payload length.
+ * @arg payload		Length of payload
+ *
+ * @return size of netlink message without padding.
  */
-int nlmsg_msg_size(int payload)
+int nlmsg_size(int payload)
 {
 	return NLMSG_HDRLEN + payload;
 }
 
+static int nlmsg_msg_size(int payload)
+{
+	return nlmsg_size(payload);
+}
+
 /**
- * length of netlink message including padding
- * @arg payload		length of message payload
+ * Calculates size of netlink message including padding based on payload length
+ * @arg payload		Length of payload
+ *
+ * This function is idential to nlmsg_size() + nlmsg_padlen().
+ *
+ * @return Size of netlink message including padding.
  */
 int nlmsg_total_size(int payload)
 {
@@ -196,8 +75,14 @@
 }
 
 /**
- * length of padding at the message's tail
- * @arg payload		length of message payload
+ * Size of padding that needs to be added at end of message
+ * @arg payload		Length of payload
+ *
+ * Calculates the number of bytes of padding which is required to be added to
+ * the end of the message to ensure that the next netlink message header begins
+ * properly aligned to NLMSG_ALIGNTO.
+ *
+ * @return Number of bytes of padding needed.
  */
 int nlmsg_padlen(int payload)
 {
@@ -207,13 +92,15 @@
 /** @} */
 
 /**
- * @name Payload Access
+ * @name Access to Message Payload
  * @{
  */
 
 /**
- * head of message payload
- * @arg nlh		netlink messsage header
+ * Return pointer to message payload
+ * @arg nlh		Netlink message header
+ *
+ * @return Pointer to start of message payload.
  */
 void *nlmsg_data(const struct nlmsghdr *nlh)
 {
@@ -226,14 +113,21 @@
 }
 
 /**
- * length of message payload
- * @arg nlh		netlink message header
+ * Return length of message payload
+ * @arg nlh		Netlink message header
+ *
+ * @return Length of message payload in bytes.
  */
-int nlmsg_len(const struct nlmsghdr *nlh)
+int nlmsg_datalen(const struct nlmsghdr *nlh)
 {
 	return nlh->nlmsg_len - NLMSG_HDRLEN;
 }
 
+static int nlmsg_len(const struct nlmsghdr *nlh)
+{
+	return nlmsg_datalen(nlh);
+}
+
 /** @} */
 
 /**
@@ -259,7 +153,7 @@
  */
 int nlmsg_attrlen(const struct nlmsghdr *nlh, int hdrlen)
 {
-	return nlmsg_len(nlh) - NLMSG_ALIGN(hdrlen);
+	return max_t(int, nlmsg_len(nlh) - NLMSG_ALIGN(hdrlen), 0);
 }
 
 /** @} */
@@ -368,18 +262,19 @@
 {
 	struct nl_msg *nm;
 
+	if (len < sizeof(struct nlmsghdr))
+		len = sizeof(struct nlmsghdr);
+
 	nm = calloc(1, sizeof(*nm));
 	if (!nm)
 		goto errout;
 
 	nm->nm_refcnt = 1;
 
-	nm->nm_nlh = malloc(len);
+	nm->nm_nlh = calloc(1, len);
 	if (!nm->nm_nlh)
 		goto errout;
 
-	memset(nm->nm_nlh, 0, sizeof(struct nlmsghdr));
-
 	nm->nm_protocol = -1;
 	nm->nm_size = len;
 	nm->nm_nlh->nlmsg_len = nlmsg_total_size(0);
@@ -490,14 +385,11 @@
 
 	nm = __nlmsg_alloc(NLMSG_ALIGN(hdr->nlmsg_len));
 	if (!nm)
-		goto errout;
+		return NULL;
 
 	memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len);
 
 	return nm;
-errout:
-	nlmsg_free(nm);
-	return NULL;
 }
 
 /**
@@ -532,8 +424,8 @@
 	if (tlen > len)
 		memset(buf + len, 0, tlen - len);
 
-	NL_DBG(2, "msg %p: Reserved %zu bytes, pad=%d, nlmsg_len=%d\n",
-		  n, len, pad, n->nm_nlh->nlmsg_len);
+	NL_DBG(2, "msg %p: Reserved %zu (%zu) bytes, pad=%d, nlmsg_len=%d\n",
+		  n, tlen, len, pad, n->nm_nlh->nlmsg_len);
 
 	return buf;
 }
@@ -680,8 +572,8 @@
 
 	if (msg->nm_refcnt <= 0) {
 		free(msg->nm_nlh);
-		free(msg);
 		NL_DBG(2, "msg %p: Freed\n", msg);
+		free(msg);
 	}
 }
 
@@ -747,7 +639,7 @@
  * @{
  */
 
-static struct trans_tbl nl_msgtypes[] = {
+static const struct trans_tbl nl_msgtypes[] = {
 	__ADD(NLMSG_NOOP,NOOP)
 	__ADD(NLMSG_ERROR,ERROR)
 	__ADD(NLMSG_DONE,DONE)
@@ -839,14 +731,18 @@
 		.cb = cb,
 		.arg = arg,
 	};
+	int err;
 
-	ops = nl_cache_ops_associate(nlmsg_get_proto(msg),
-				     nlmsg_hdr(msg)->nlmsg_type);
+	ops = nl_cache_ops_associate_safe(nlmsg_get_proto(msg),
+					  nlmsg_hdr(msg)->nlmsg_type);
 	if (ops == NULL)
 		return -NLE_MSGTYPE_NOSUPPORT;
 	p.pp_arg = &x;
 
-	return nl_cache_parse(ops, NULL, nlmsg_hdr(msg), &p);
+	err = nl_cache_parse(ops, NULL, nlmsg_hdr(msg), &p);
+	nl_cache_ops_put(ops);
+
+	return err;
 }
 
 /** @} */
@@ -869,7 +765,7 @@
 	int i, a, c, limit;
 	char ascii[21] = {0};
 
-	limit = 18 - (prefix * 2);
+	limit = 16 - (prefix * 2);
 	prefix_line(ofd, prefix);
 	fprintf(ofd, "    ");
 
@@ -879,7 +775,7 @@
 		fprintf(ofd, "%02x ", v);
 		ascii[a++] = isprint(v) ? v : '.';
 
-		if (c == limit-1) {
+		if (++c >= limit) {
 			fprintf(ofd, "%s\n", ascii);
 			if (i < (len - 1)) {
 				prefix_line(ofd, prefix);
@@ -887,8 +783,7 @@
 			}
 			a = c = 0;
 			memset(ascii, 0, sizeof(ascii));
-		} else
-			c++;
+		}
 	}
 
 	if (c != 0) {
@@ -907,24 +802,73 @@
 
 	fprintf(ofd, "    .nlmsg_len = %d\n", nlh->nlmsg_len);
 
-	ops = nl_cache_ops_associate(nlmsg_get_proto(msg), nlh->nlmsg_type);
+	ops = nl_cache_ops_associate_safe(nlmsg_get_proto(msg), nlh->nlmsg_type);
 	if (ops) {
 		mt = nl_msgtype_lookup(ops, nlh->nlmsg_type);
 		if (!mt)
 			BUG();
 
 		snprintf(buf, sizeof(buf), "%s::%s", ops->co_name, mt->mt_name);
+		nl_cache_ops_put(ops);
 	} else
 		nl_nlmsgtype2str(nlh->nlmsg_type, buf, sizeof(buf));
 
-	fprintf(ofd, "    .nlmsg_type = %d <%s>\n", nlh->nlmsg_type, buf);
-	fprintf(ofd, "    .nlmsg_flags = %d <%s>\n", nlh->nlmsg_flags,
+	fprintf(ofd, "    .type = %d <%s>\n", nlh->nlmsg_type, buf);
+	fprintf(ofd, "    .flags = %d <%s>\n", nlh->nlmsg_flags,
 		nl_nlmsg_flags2str(nlh->nlmsg_flags, buf, sizeof(buf)));
-	fprintf(ofd, "    .nlmsg_seq = %d\n", nlh->nlmsg_seq);
-	fprintf(ofd, "    .nlmsg_pid = %d\n", nlh->nlmsg_pid);
+	fprintf(ofd, "    .seq = %d\n", nlh->nlmsg_seq);
+	fprintf(ofd, "    .port = %d\n", nlh->nlmsg_pid);
 
 }
 
+static void print_genl_hdr(FILE *ofd, void *start)
+{
+	struct genlmsghdr *ghdr = start;
+
+	fprintf(ofd, "  [GENERIC NETLINK HEADER] %zu octets\n", GENL_HDRLEN);
+	fprintf(ofd, "    .cmd = %u\n", ghdr->cmd);
+	fprintf(ofd, "    .version = %u\n", ghdr->version);
+	fprintf(ofd, "    .unused = %#x\n", ghdr->reserved);
+}
+
+static void *print_genl_msg(struct nl_msg *msg, FILE *ofd, struct nlmsghdr *hdr,
+			    struct nl_cache_ops *ops, int *payloadlen)
+{
+	void *data = nlmsg_data(hdr);
+
+	if (*payloadlen < GENL_HDRLEN)
+		return data;
+
+	print_genl_hdr(ofd, data);
+
+	*payloadlen -= GENL_HDRLEN;
+	data += GENL_HDRLEN;
+
+	if (ops) {
+		int hdrsize = ops->co_hdrsize - GENL_HDRLEN;
+
+		if (hdrsize > 0) {
+			if (*payloadlen < hdrsize)
+				return data;
+
+			fprintf(ofd, "  [HEADER] %d octets\n", hdrsize);
+			dump_hex(ofd, data, hdrsize, 0);
+
+			*payloadlen -= hdrsize;
+			data += hdrsize;
+		}
+	}
+
+	return data;
+}
+
+static void dump_attr(FILE *ofd, struct nlattr *attr, int prefix)
+{
+	int len = nla_len(attr);
+
+	dump_hex(ofd, nla_data(attr), len, prefix);
+}
+
 static void dump_attrs(FILE *ofd, struct nlattr *attrs, int attrlen,
 		       int prefix)
 {
@@ -935,14 +879,18 @@
 		int padlen, alen = nla_len(nla);
 
 		prefix_line(ofd, prefix);
-		fprintf(ofd, "  [ATTR %02d%s] %d octets\n", nla_type(nla),
-			nla->nla_type & NLA_F_NESTED ? " NESTED" : "",
-			alen);
 
-		if (nla->nla_type & NLA_F_NESTED)
+		if (nla->nla_type == 0)
+			fprintf(ofd, "  [ATTR PADDING] %d octets\n", alen);
+		else
+			fprintf(ofd, "  [ATTR %02d%s] %d octets\n", nla_type(nla),
+				nla_is_nested(nla) ? " NESTED" : "",
+				alen);
+
+		if (nla_is_nested(nla))
 			dump_attrs(ofd, nla_data(nla), alen, prefix+1);
 		else
-			dump_hex(ofd, nla_data(nla), alen, prefix);
+			dump_attr(ofd, nla, prefix);
 
 		padlen = nla_padlen(alen);
 		if (padlen > 0) {
@@ -960,6 +908,63 @@
 	}
 }
 
+static void dump_error_msg(struct nl_msg *msg, FILE *ofd)
+{
+	struct nlmsghdr *hdr = nlmsg_hdr(msg);
+	struct nlmsgerr *err = nlmsg_data(hdr);
+
+	fprintf(ofd, "  [ERRORMSG] %zu octets\n", sizeof(*err));
+
+	if (nlmsg_len(hdr) >= sizeof(*err)) {
+		char buf[256];
+		struct nl_msg *errmsg;
+
+		fprintf(ofd, "    .error = %d \"%s\"\n", err->error,
+			strerror_r(-err->error, buf, sizeof(buf)));
+		fprintf(ofd, "  [ORIGINAL MESSAGE] %zu octets\n", sizeof(*hdr));
+
+		errmsg = nlmsg_inherit(&err->msg);
+		print_hdr(ofd, errmsg);
+		nlmsg_free(errmsg);
+	}
+}
+
+static void print_msg(struct nl_msg *msg, FILE *ofd, struct nlmsghdr *hdr)
+{
+	struct nl_cache_ops *ops;
+	int payloadlen = nlmsg_len(hdr);
+	int attrlen = 0;
+	void *data;
+
+	data = nlmsg_data(hdr);
+	ops = nl_cache_ops_associate_safe(nlmsg_get_proto(msg),
+					  hdr->nlmsg_type);
+	if (ops) {
+		attrlen = nlmsg_attrlen(hdr, ops->co_hdrsize);
+		payloadlen -= attrlen;
+	}
+
+	if (msg->nm_protocol == NETLINK_GENERIC)
+		data = print_genl_msg(msg, ofd, hdr, ops, &payloadlen);
+
+	if (payloadlen) {
+		fprintf(ofd, "  [PAYLOAD] %d octets\n", payloadlen);
+		dump_hex(ofd, data, payloadlen, 0);
+	}
+
+	if (attrlen) {
+		struct nlattr *attrs;
+		int attrlen;
+		
+		attrs = nlmsg_attrdata(hdr, ops->co_hdrsize);
+		attrlen = nlmsg_attrlen(hdr, ops->co_hdrsize);
+		dump_attrs(ofd, attrs, attrlen, 0);
+	}
+
+	if (ops)
+		nl_cache_ops_put(ops);
+}
+
 /**
  * Dump message in human readable format to file descriptor
  * @arg msg		Message to print
@@ -970,53 +975,18 @@
 	struct nlmsghdr *hdr = nlmsg_hdr(msg);
 	
 	fprintf(ofd, 
-	"--------------------------   BEGIN NETLINK MESSAGE "
-	"---------------------------\n");
+	"--------------------------   BEGIN NETLINK MESSAGE ---------------------------\n");
 
-	fprintf(ofd, "  [HEADER] %Zu octets\n", sizeof(struct nlmsghdr));
+	fprintf(ofd, "  [NETLINK HEADER] %zu octets\n", sizeof(struct nlmsghdr));
 	print_hdr(ofd, msg);
 
-	if (hdr->nlmsg_type == NLMSG_ERROR &&
-	    hdr->nlmsg_len >= nlmsg_msg_size(sizeof(struct nlmsgerr))) {
-		struct nl_msg *errmsg;
-		struct nlmsgerr *err = nlmsg_data(hdr);
-
-		fprintf(ofd, "  [ERRORMSG] %Zu octets\n", sizeof(*err));
-		fprintf(ofd, "    .error = %d \"%s\"\n", err->error,
-			strerror(-err->error));
-		fprintf(ofd, "  [ORIGINAL MESSAGE] %Zu octets\n", sizeof(*hdr));
-
-		errmsg = nlmsg_inherit(&err->msg);
-		print_hdr(ofd, errmsg);
-		nlmsg_free(errmsg);
-	} else if (nlmsg_len(hdr) > 0) {
-		struct nl_cache_ops *ops;
-		int payloadlen = nlmsg_len(hdr);
-		int attrlen = 0;
-
-		ops = nl_cache_ops_associate(nlmsg_get_proto(msg),
-					     hdr->nlmsg_type);
-		if (ops) {
-			attrlen = nlmsg_attrlen(hdr, ops->co_hdrsize);
-			payloadlen -= attrlen;
-		}
-
-		fprintf(ofd, "  [PAYLOAD] %d octets\n", payloadlen);
-		dump_hex(ofd, nlmsg_data(hdr), payloadlen, 0);
-
-		if (attrlen) {
-			struct nlattr *attrs;
-			int attrlen;
-			
-			attrs = nlmsg_attrdata(hdr, ops->co_hdrsize);
-			attrlen = nlmsg_attrlen(hdr, ops->co_hdrsize);
-			dump_attrs(ofd, attrs, attrlen, 0);
-		}
-	}
+	if (hdr->nlmsg_type == NLMSG_ERROR)
+		dump_error_msg(msg, ofd);
+	else if (nlmsg_len(hdr) > 0)
+		print_msg(msg, ofd, hdr);
 
 	fprintf(ofd, 
-	"---------------------------  END NETLINK MESSAGE   "
-	"---------------------------\n");
+	"---------------------------  END NETLINK MESSAGE   ---------------------------\n");
 }
 
 /** @} */
diff --git a/lib/netfilter/ct.c b/lib/netfilter/ct.c
index 9d61b6c..36a83db 100644
--- a/lib/netfilter/ct.c
+++ b/lib/netfilter/ct.c
@@ -23,7 +23,7 @@
 #include <sys/types.h>
 #include <linux/netfilter/nfnetlink_conntrack.h>
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/netfilter/nfnl.h>
 #include <netlink/netfilter/ct.h>
@@ -35,10 +35,18 @@
 {
 	return x;
 }
+static uint64_t htonll(uint64_t x)
+{
+	return x;
+}
 #elif __BYTE_ORDER == __LITTLE_ENDIAN
 static uint64_t ntohll(uint64_t x)
 {
-	return __bswap_64(x);
+	return bswap_64(x);
+}
+static uint64_t htonll(uint64_t x)
+{
+	return bswap_64(x);
 }
 #endif
 
@@ -55,6 +63,7 @@
 	[CTA_COUNTERS_REPLY]	= { .type = NLA_NESTED },
 	[CTA_USE]		= { .type = NLA_U32 },
 	[CTA_ID]		= { .type = NLA_U32 },
+	[CTA_ZONE]		= { .type = NLA_U16 },
 	//[CTA_NAT_DST]
 };
 
@@ -102,6 +111,11 @@
 	[CTA_COUNTERS32_BYTES]	= { .type = NLA_U32 },
 };
 
+static struct nla_policy ct_timestamp_policy[CTA_TIMESTAMP_MAX + 1] = {
+	[CTA_TIMESTAMP_START]	= { .type = NLA_U64 },
+	[CTA_TIMESTAMP_STOP]	= { .type = NLA_U64 },
+};
+
 static int ct_parse_ip(struct nfnl_ct *ct, int repl, struct nlattr *attr)
 {
 	struct nlattr *tb[CTA_IP_MAX+1];
@@ -174,15 +188,28 @@
 	if (tb[CTA_PROTO_DST_PORT])
 		nfnl_ct_set_dst_port(ct, repl,
 			ntohs(nla_get_u16(tb[CTA_PROTO_DST_PORT])));
-	if (tb[CTA_PROTO_ICMP_ID])
-		nfnl_ct_set_icmp_id(ct, repl,
-			ntohs(nla_get_u16(tb[CTA_PROTO_ICMP_ID])));
-	if (tb[CTA_PROTO_ICMP_TYPE])
-		nfnl_ct_set_icmp_type(ct, repl,
+
+	if (ct->ct_family == AF_INET) {
+		if (tb[CTA_PROTO_ICMP_ID])
+			nfnl_ct_set_icmp_id(ct, repl,
+				ntohs(nla_get_u16(tb[CTA_PROTO_ICMP_ID])));
+		if (tb[CTA_PROTO_ICMP_TYPE])
+			nfnl_ct_set_icmp_type(ct, repl,
 				nla_get_u8(tb[CTA_PROTO_ICMP_TYPE]));
-	if (tb[CTA_PROTO_ICMP_CODE])
-		nfnl_ct_set_icmp_code(ct, repl,
+		if (tb[CTA_PROTO_ICMP_CODE])
+			nfnl_ct_set_icmp_code(ct, repl,
 				nla_get_u8(tb[CTA_PROTO_ICMP_CODE]));
+	} else if (ct->ct_family == AF_INET6) {
+		if (tb[CTA_PROTO_ICMPV6_ID])
+			nfnl_ct_set_icmp_id(ct, repl,
+			    ntohs(nla_get_u16(tb[CTA_PROTO_ICMPV6_ID])));
+		if (tb[CTA_PROTO_ICMPV6_TYPE])
+			nfnl_ct_set_icmp_type(ct, repl,
+				nla_get_u8(tb[CTA_PROTO_ICMPV6_TYPE]));
+		if (tb[CTA_PROTO_ICMPV6_CODE])
+			nfnl_ct_set_icmp_code(ct, repl,
+				nla_get_u8(tb[CTA_PROTO_ICMPV6_CODE]));
+	}
 
 	return 0;
 }
@@ -287,6 +314,24 @@
 	}
 }
 
+static int ct_parse_timestamp(struct nfnl_ct *ct, struct nlattr *attr)
+{
+	struct nlattr *tb[CTA_TIMESTAMP_MAX + 1];
+	int err;
+
+	err = nla_parse_nested(tb, CTA_TIMESTAMP_MAX, attr,
+			       ct_timestamp_policy);
+	if (err < 0)
+		return err;
+
+	if (tb[CTA_TIMESTAMP_START] && tb[CTA_TIMESTAMP_STOP])
+		nfnl_ct_set_timestamp(ct,
+			      ntohll(nla_get_u64(tb[CTA_TIMESTAMP_START])),
+			      ntohll(nla_get_u64(tb[CTA_TIMESTAMP_STOP])));
+
+	return 0;
+}
+
 int nfnlmsg_ct_parse(struct nlmsghdr *nlh, struct nfnl_ct **result)
 {
 	struct nfnl_ct *ct;
@@ -333,6 +378,8 @@
 		nfnl_ct_set_use(ct, ntohl(nla_get_u32(tb[CTA_USE])));
 	if (tb[CTA_ID])
 		nfnl_ct_set_id(ct, ntohl(nla_get_u32(tb[CTA_ID])));
+	if (tb[CTA_ZONE])
+		nfnl_ct_set_zone(ct, ntohs(nla_get_u16(tb[CTA_ZONE])));
 
 	if (tb[CTA_COUNTERS_ORIG]) {
 		err = ct_parse_counters(ct, 0, tb[CTA_COUNTERS_ORIG]);
@@ -346,6 +393,12 @@
 			goto errout;
 	}
 
+	if (tb[CTA_TIMESTAMP]) {
+		err = ct_parse_timestamp(ct, tb[CTA_TIMESTAMP]);
+		if (err < 0)
+			goto errout;
+	}
+
 	*result = ct;
 	return 0;
 
@@ -361,10 +414,9 @@
 	int err;
 
 	if ((err = nfnlmsg_ct_parse(nlh, &ct)) < 0)
-		goto errout;
+		return err;
 
 	err = pp->pp_cb((struct nl_object *) ct, pp);
-errout:
 	nfnl_ct_put(ct);
 	return err;
 }
@@ -426,17 +478,31 @@
 		NLA_PUT_U16(msg, CTA_PROTO_DST_PORT,
 			htons(nfnl_ct_get_dst_port(ct, repl)));
 
-	if (nfnl_ct_test_icmp_id(ct, repl))
-		NLA_PUT_U16(msg, CTA_PROTO_ICMP_ID,
-			htons(nfnl_ct_get_icmp_id(ct, repl)));
+	if (family == AF_INET) {
+		if (nfnl_ct_test_icmp_id(ct, repl))
+			NLA_PUT_U16(msg, CTA_PROTO_ICMP_ID,
+						htons(nfnl_ct_get_icmp_id(ct, repl)));
 
-	if (nfnl_ct_test_icmp_type(ct, repl))
-		NLA_PUT_U8(msg, CTA_PROTO_ICMP_TYPE,
-			    nfnl_ct_get_icmp_type(ct, repl));
+		if (nfnl_ct_test_icmp_type(ct, repl))
+			NLA_PUT_U8(msg, CTA_PROTO_ICMP_TYPE,
+					   nfnl_ct_get_icmp_type(ct, repl));
 
-	if (nfnl_ct_test_icmp_code(ct, repl))
-		NLA_PUT_U8(msg, CTA_PROTO_ICMP_CODE,
-			    nfnl_ct_get_icmp_code(ct, repl));
+		if (nfnl_ct_test_icmp_code(ct, repl))
+			NLA_PUT_U8(msg, CTA_PROTO_ICMP_CODE,
+					   nfnl_ct_get_icmp_code(ct, repl));
+	} else if (family == AF_INET6) {
+		if (nfnl_ct_test_icmp_id(ct, repl))
+			NLA_PUT_U16(msg, CTA_PROTO_ICMPV6_ID,
+						htons(nfnl_ct_get_icmp_id(ct, repl)));
+
+		if (nfnl_ct_test_icmp_type(ct, repl))
+			NLA_PUT_U8(msg, CTA_PROTO_ICMPV6_TYPE,
+					   nfnl_ct_get_icmp_type(ct, repl));
+
+		if (nfnl_ct_test_icmp_code(ct, repl))
+			NLA_PUT_U8(msg, CTA_PROTO_ICMPV6_CODE,
+					   nfnl_ct_get_icmp_code(ct, repl));
+	}
 
 	nla_nest_end(msg, proto);
 
@@ -461,9 +527,31 @@
 	if ((err = nfnl_ct_build_tuple(msg, ct, 0)) < 0)
 		goto err_out;
 
+	/* REPLY tuple is optional, dont add unless at least src/dst specified */
+
+	if ( nfnl_ct_get_src(ct, 1) && nfnl_ct_get_dst(ct, 1) )
+		if ((err = nfnl_ct_build_tuple(msg, ct, 1)) < 0)
+			goto err_out;
+
+	if (nfnl_ct_test_status(ct))
+		NLA_PUT_U32(msg, CTA_STATUS, htonl(nfnl_ct_get_status(ct)));
+
+	if (nfnl_ct_test_timeout(ct))
+		NLA_PUT_U32(msg, CTA_TIMEOUT, htonl(nfnl_ct_get_timeout(ct)));
+
+	if (nfnl_ct_test_mark(ct))
+		NLA_PUT_U32(msg, CTA_MARK, htonl(nfnl_ct_get_mark(ct)));
+
+	if (nfnl_ct_test_id(ct))
+		NLA_PUT_U32(msg, CTA_ID, htonl(nfnl_ct_get_id(ct)));
+
+	if (nfnl_ct_test_zone(ct))
+		NLA_PUT_U16(msg, CTA_ZONE, htons(nfnl_ct_get_zone(ct)));
+
 	*result = msg;
 	return 0;
 
+nla_put_failure:
 err_out:
 	nlmsg_free(msg);
 	return err;
diff --git a/lib/netfilter/ct_obj.c b/lib/netfilter/ct_obj.c
index ae14c0d..61b6a31 100644
--- a/lib/netfilter/ct_obj.c
+++ b/lib/netfilter/ct_obj.c
@@ -16,7 +16,7 @@
 #include <linux/netfilter/nf_conntrack_common.h>
 #include <linux/netfilter/nf_conntrack_tcp.h>
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netfilter/nfnl.h>
 #include <netlink/netfilter/ct.h>
 
@@ -51,6 +51,8 @@
 #define CT_ATTR_REPL_ICMP_CODE	(1UL << 23)
 #define CT_ATTR_REPL_PACKETS	(1UL << 24)
 #define CT_ATTR_REPL_BYTES	(1UL << 25)
+#define CT_ATTR_TIMESTAMP	(1UL << 26)
+#define CT_ATTR_ZONE	(1UL << 27)
 /** @endcond */
 
 static void ct_free_data(struct nl_object *c)
@@ -121,10 +123,10 @@
 	if (nfnl_ct_test_icmp_type(ct, reply))
 		nl_dump(p, "icmp type %d ", nfnl_ct_get_icmp_type(ct, reply));
 
-	if (nfnl_ct_test_icmp_type(ct, reply))
+	if (nfnl_ct_test_icmp_code(ct, reply))
 		nl_dump(p, "code %d ", nfnl_ct_get_icmp_code(ct, reply));
 
-	if (nfnl_ct_test_icmp_type(ct, reply))
+	if (nfnl_ct_test_icmp_id(ct, reply))
 		nl_dump(p, "id %d ", nfnl_ct_get_icmp_id(ct, reply));
 }
 
@@ -192,6 +194,20 @@
 	if (nfnl_ct_test_mark(ct) && nfnl_ct_get_mark(ct))
 		nl_dump(p, "mark %u ", nfnl_ct_get_mark(ct));
 
+	if (nfnl_ct_test_zone(ct))
+		nl_dump(p, "zone %hu ", nfnl_ct_get_zone(ct));
+
+	if (nfnl_ct_test_timestamp(ct)) {
+		const struct nfnl_ct_timestamp *tstamp = nfnl_ct_get_timestamp(ct);
+		int64_t delta_time = tstamp->stop - tstamp->start;
+
+		if (delta_time > 0)
+			delta_time /= NSEC_PER_SEC;
+		else
+			delta_time = 0;
+		nl_dump(p, "delta-time %llu ", delta_time);
+	}
+
 	nl_dump(p, "\n");
 }
 
@@ -256,18 +272,29 @@
 	struct nfnl_ct *ct = (struct nfnl_ct *) a;
 	double res;
 	char *unit;
+	uint64_t packets;
+	const char * const names[] = {"rx", "tx"};
+	int i;
 
 	ct_dump_details(a, p);
 
+	if (!nfnl_ct_test_bytes(ct, 0) ||
+	    !nfnl_ct_test_packets(ct, 0) ||
+	    !nfnl_ct_test_bytes(ct, 1) ||
+	    !nfnl_ct_test_packets(ct, 1))
+	    {
+		nl_dump_line(p, "    Statistics are not available.\n");
+		nl_dump_line(p, "    Please set sysctl net.netfilter.nf_conntrack_acct=1\n");
+		nl_dump_line(p, "    (Require kernel 2.6.27)\n");
+		return;
+	    }
+
 	nl_dump_line(p, "        # packets      volume\n");
-
-	res = nl_cancel_down_bytes(nfnl_ct_get_bytes(ct, 1), &unit);
-	nl_dump_line(p, "    rx %10llu %7.2f %s\n",
-		nfnl_ct_get_packets(ct, 1), res, unit);
-
-	res = nl_cancel_down_bytes(nfnl_ct_get_bytes(ct, 0), &unit);
-	nl_dump_line(p, "    tx %10llu %7.2f %s\n",
-		nfnl_ct_get_packets(ct, 0), res, unit);
+	for (i=0; i<=1; i++) {
+		res = nl_cancel_down_bytes(nfnl_ct_get_bytes(ct, i), &unit);
+		packets = nfnl_ct_get_packets(ct, i);
+		nl_dump_line(p, "    %s %10" PRIu64  " %7.2f %s\n", names[i], packets, res, unit);
+	}
 }
 
 static int ct_compare(struct nl_object *_a, struct nl_object *_b,
@@ -323,7 +350,7 @@
 	return diff;
 }
 
-static struct trans_tbl ct_attrs[] = {
+static const struct trans_tbl ct_attrs[] = {
 	__ADD(CT_ATTR_FAMILY,		family)
 	__ADD(CT_ATTR_PROTO,		proto)
 	__ADD(CT_ATTR_TCP_STATE,	tcpstate)
@@ -430,7 +457,7 @@
 	return ct->ct_protoinfo.tcp.state;
 }
 
-static struct trans_tbl tcp_states[] = {
+static const struct trans_tbl tcp_states[] = {
 	__ADD(TCP_CONNTRACK_NONE,NONE)
 	__ADD(TCP_CONNTRACK_SYN_SENT,SYN_SENT)
 	__ADD(TCP_CONNTRACK_SYN_RECV,SYN_RECV)
@@ -467,12 +494,17 @@
 	ct->ce_mask |= CT_ATTR_STATUS;
 }
 
+int nfnl_ct_test_status(const struct nfnl_ct *ct)
+{
+	return !!(ct->ce_mask & CT_ATTR_STATUS);
+}
+
 uint32_t nfnl_ct_get_status(const struct nfnl_ct *ct)
 {
 	return ct->ct_status;
 }
 
-static struct trans_tbl status_flags[] = {
+static const struct trans_tbl status_flags[] = {
 	__ADD(IPS_EXPECTED, expected)
 	__ADD(IPS_SEEN_REPLY, seen_reply)
 	__ADD(IPS_ASSURED, assured)
@@ -561,6 +593,22 @@
 	return ct->ct_id;
 }
 
+void nfnl_ct_set_zone(struct nfnl_ct *ct, uint16_t zone)
+{
+	ct->ct_zone = zone;
+	ct->ce_mask |= CT_ATTR_ZONE;
+}
+
+int nfnl_ct_test_zone(const struct nfnl_ct *ct)
+{
+	return !!(ct->ce_mask & CT_ATTR_ZONE);
+}
+
+uint16_t nfnl_ct_get_zone(const struct nfnl_ct *ct)
+{
+	return ct->ct_zone;
+}
+
 static int ct_set_addr(struct nfnl_ct *ct, struct nl_addr *addr,
 		int attr, struct nl_addr ** ct_addr)
 {
@@ -766,6 +814,23 @@
 	return dir->bytes;
 }
 
+void nfnl_ct_set_timestamp(struct nfnl_ct *ct, uint64_t start, uint64_t stop)
+{
+	ct->ct_tstamp.start = start;
+	ct->ct_tstamp.stop = stop;
+	ct->ce_mask |= CT_ATTR_TIMESTAMP;
+}
+
+int nfnl_ct_test_timestamp(const struct nfnl_ct *ct)
+{
+	return !!(ct->ce_mask & CT_ATTR_TIMESTAMP);
+}
+
+const struct nfnl_ct_timestamp *nfnl_ct_get_timestamp(const struct nfnl_ct *ct)
+{
+	return &ct->ct_tstamp;
+}
+
 /** @} */
 
 struct nl_object_ops ct_obj_ops = {
diff --git a/lib/netfilter/exp.c b/lib/netfilter/exp.c
new file mode 100644
index 0000000..9cfdd2b
--- /dev/null
+++ b/lib/netfilter/exp.c
@@ -0,0 +1,620 @@
+/*
+ * lib/netfilter/exp.c	Conntrack Expectation
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2007 Philip Craig <philipc@snapgear.com>
+ * Copyright (c) 2007 Secure Computing Corporation
+ * Copyright (c= 2008 Patrick McHardy <kaber@trash.net>
+ * Copyright (c) 2012 Rich Fought <rich.fought@watchguard.com>
+ */
+
+/**
+ * @ingroup nfnl
+ * @defgroup exp Expectation
+ * @brief
+ * @{
+ */
+
+#include <byteswap.h>
+#include <sys/types.h>
+#include <linux/netfilter/nfnetlink_conntrack.h>
+
+#include <netlink-private/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/netfilter/nfnl.h>
+#include <netlink/netfilter/exp.h>
+
+static struct nl_cache_ops nfnl_exp_ops;
+
+static struct nla_policy exp_policy[CTA_EXPECT_MAX+1] = {
+	[CTA_EXPECT_MASTER]	= { .type = NLA_NESTED },
+	[CTA_EXPECT_TUPLE]	= { .type = NLA_NESTED },
+	[CTA_EXPECT_MASK]	= { .type = NLA_NESTED },
+	[CTA_EXPECT_TIMEOUT]	= { .type = NLA_U32 },
+	[CTA_EXPECT_ID]		= { .type = NLA_U32 },
+	[CTA_EXPECT_HELP_NAME]	= { .type = NLA_STRING },
+	[CTA_EXPECT_ZONE]	= { .type = NLA_U16 },
+	[CTA_EXPECT_FLAGS]	= { .type = NLA_U32 },    // Added in kernel 2.6.37
+	[CTA_EXPECT_CLASS]	= { .type = NLA_U32 },    // Added in kernel 3.5
+	[CTA_EXPECT_NAT]	= { .type = NLA_NESTED }, // Added in kernel 3.5
+	[CTA_EXPECT_FN]		= { .type = NLA_STRING }, // Added in kernel 3.5
+};
+
+static struct nla_policy exp_tuple_policy[CTA_TUPLE_MAX+1] = {
+	[CTA_TUPLE_IP]		= { .type = NLA_NESTED },
+	[CTA_TUPLE_PROTO]	= { .type = NLA_NESTED },
+};
+
+static struct nla_policy exp_ip_policy[CTA_IP_MAX+1] = {
+	[CTA_IP_V4_SRC]		= { .type = NLA_U32 },
+	[CTA_IP_V4_DST]		= { .type = NLA_U32 },
+	[CTA_IP_V6_SRC]		= { .minlen = 16 },
+	[CTA_IP_V6_DST]		= { .minlen = 16 },
+};
+
+static struct nla_policy exp_proto_policy[CTA_PROTO_MAX+1] = {
+	[CTA_PROTO_NUM]		= { .type = NLA_U8 },
+	[CTA_PROTO_SRC_PORT]	= { .type = NLA_U16 },
+	[CTA_PROTO_DST_PORT]	= { .type = NLA_U16 },
+	[CTA_PROTO_ICMP_ID]	= { .type = NLA_U16 },
+	[CTA_PROTO_ICMP_TYPE]	= { .type = NLA_U8 },
+	[CTA_PROTO_ICMP_CODE]	= { .type = NLA_U8 },
+	[CTA_PROTO_ICMPV6_ID]	= { .type = NLA_U16 },
+	[CTA_PROTO_ICMPV6_TYPE]	= { .type = NLA_U8 },
+	[CTA_PROTO_ICMPV6_CODE]	= { .type = NLA_U8 },
+};
+
+static struct nla_policy exp_nat_policy[CTA_EXPECT_NAT_MAX+1] = {
+	[CTA_EXPECT_NAT_DIR]	= { .type = NLA_U32 },
+	[CTA_EXPECT_NAT_TUPLE]	= { .type = NLA_NESTED },
+};
+
+static int exp_parse_ip(struct nfnl_exp *exp, int tuple, struct nlattr *attr)
+{
+	struct nlattr *tb[CTA_IP_MAX+1];
+	struct nl_addr *addr;
+	int err;
+
+	err = nla_parse_nested(tb, CTA_IP_MAX, attr, exp_ip_policy);
+	if (err < 0)
+		goto errout;
+
+	if (tb[CTA_IP_V4_SRC]) {
+		addr = nl_addr_alloc_attr(tb[CTA_IP_V4_SRC], AF_INET);
+		if (addr == NULL)
+			goto errout_enomem;
+		err = nfnl_exp_set_src(exp, tuple, addr);
+		nl_addr_put(addr);
+		if (err < 0)
+			goto errout;
+	}
+	if (tb[CTA_IP_V4_DST]) {
+		addr = nl_addr_alloc_attr(tb[CTA_IP_V4_DST], AF_INET);
+		if (addr == NULL)
+			goto errout_enomem;
+		err = nfnl_exp_set_dst(exp, tuple, addr);
+		nl_addr_put(addr);
+		if (err < 0)
+			goto errout;
+	}
+	if (tb[CTA_IP_V6_SRC]) {
+		addr = nl_addr_alloc_attr(tb[CTA_IP_V6_SRC], AF_INET6);
+		if (addr == NULL)
+			goto errout_enomem;
+		err = nfnl_exp_set_src(exp, tuple, addr);
+		nl_addr_put(addr);
+		if (err < 0)
+			goto errout;
+	}
+	if (tb[CTA_IP_V6_DST]) {
+		addr = nl_addr_alloc_attr(tb[CTA_IP_V6_DST], AF_INET6);
+		if (addr == NULL)
+			goto errout_enomem;
+		err = nfnl_exp_set_dst(exp, tuple, addr);
+		nl_addr_put(addr);
+		if (err < 0)
+			goto errout;
+	}
+
+	return 0;
+
+errout_enomem:
+	err = -NLE_NOMEM;
+errout:
+	return err;
+}
+
+static int exp_parse_proto(struct nfnl_exp *exp, int tuple, struct nlattr *attr)
+{
+	struct nlattr *tb[CTA_PROTO_MAX+1];
+	int err;
+	uint16_t srcport = 0, dstport = 0, icmpid = 0;
+	uint8_t icmptype = 0, icmpcode = 0;
+
+	err = nla_parse_nested(tb, CTA_PROTO_MAX, attr, exp_proto_policy);
+	if (err < 0)
+		return err;
+
+	if (tb[CTA_PROTO_NUM])
+		nfnl_exp_set_l4protonum(exp, tuple, nla_get_u8(tb[CTA_PROTO_NUM]));
+
+	if (tb[CTA_PROTO_SRC_PORT])
+		srcport = ntohs(nla_get_u16(tb[CTA_PROTO_SRC_PORT]));
+	if (tb[CTA_PROTO_DST_PORT])
+		dstport = ntohs(nla_get_u16(tb[CTA_PROTO_DST_PORT]));
+	if (tb[CTA_PROTO_SRC_PORT] || tb[CTA_PROTO_DST_PORT])
+		nfnl_exp_set_ports(exp, tuple, srcport, dstport);
+
+	if (tb[CTA_PROTO_ICMP_ID])
+		icmpid = ntohs(nla_get_u16(tb[CTA_PROTO_ICMP_ID]));
+	if (tb[CTA_PROTO_ICMP_TYPE])
+		icmptype = nla_get_u8(tb[CTA_PROTO_ICMP_TYPE]);
+	if (tb[CTA_PROTO_ICMP_CODE])
+		icmpcode = nla_get_u8(tb[CTA_PROTO_ICMP_CODE]);
+	if (tb[CTA_PROTO_ICMP_ID] || tb[CTA_PROTO_ICMP_TYPE] || tb[CTA_PROTO_ICMP_CODE])
+		nfnl_exp_set_icmp(exp, tuple, icmpid, icmptype, icmpcode);
+	return 0;
+}
+
+static int exp_parse_tuple(struct nfnl_exp *exp, int tuple, struct nlattr *attr)
+{
+	struct nlattr *tb[CTA_TUPLE_MAX+1];
+	int err;
+
+	err = nla_parse_nested(tb, CTA_TUPLE_MAX, attr, exp_tuple_policy);
+	if (err < 0)
+		return err;
+
+	if (tb[CTA_TUPLE_IP]) {
+		err = exp_parse_ip(exp, tuple, tb[CTA_TUPLE_IP]);
+		if (err < 0)
+			return err;
+	}
+
+	if (tb[CTA_TUPLE_PROTO]) {
+		err = exp_parse_proto(exp, tuple, tb[CTA_TUPLE_PROTO]);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int exp_parse_nat(struct nfnl_exp *exp, struct nlattr *attr)
+{
+	struct nlattr *tb[CTA_EXPECT_NAT_MAX+1];
+	int err;
+
+	err = nla_parse_nested(tb, CTA_EXPECT_NAT_MAX, attr, exp_nat_policy);
+	if (err < 0)
+		return err;
+
+	if (tb[CTA_EXPECT_NAT_DIR])
+		nfnl_exp_set_nat_dir(exp, nla_get_u32(tb[CTA_EXPECT_NAT_DIR]));
+
+	if (tb[CTA_EXPECT_NAT_TUPLE]) {
+		err = exp_parse_tuple(exp, NFNL_EXP_TUPLE_NAT, tb[CTA_EXPECT_NAT_TUPLE]);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+int nfnlmsg_exp_group(struct nlmsghdr *nlh)
+{
+	switch (nfnlmsg_subtype(nlh)) {
+	case IPCTNL_MSG_EXP_NEW:
+		if (nlh->nlmsg_flags & (NLM_F_CREATE|NLM_F_EXCL))
+			return NFNLGRP_CONNTRACK_EXP_NEW;
+		else
+			return NFNLGRP_CONNTRACK_EXP_UPDATE;
+	case IPCTNL_MSG_EXP_DELETE:
+		return NFNLGRP_CONNTRACK_EXP_DESTROY;
+	default:
+		return NFNLGRP_NONE;
+	}
+}
+
+int nfnlmsg_exp_parse(struct nlmsghdr *nlh, struct nfnl_exp **result)
+{
+	struct nfnl_exp *exp;
+	struct nlattr *tb[CTA_MAX+1];
+	int err;
+
+	exp = nfnl_exp_alloc();
+	if (!exp)
+		return -NLE_NOMEM;
+
+	exp->ce_msgtype = nlh->nlmsg_type;
+
+	err = nlmsg_parse(nlh, sizeof(struct nfgenmsg), tb, CTA_EXPECT_MAX,
+			  exp_policy);
+	if (err < 0)
+		goto errout;
+
+	nfnl_exp_set_family(exp, nfnlmsg_family(nlh));
+
+	if (tb[CTA_EXPECT_TUPLE]) {
+		err = exp_parse_tuple(exp, NFNL_EXP_TUPLE_EXPECT, tb[CTA_EXPECT_TUPLE]);
+		if (err < 0)
+			goto errout;
+	}
+	if (tb[CTA_EXPECT_MASTER]) {
+		err = exp_parse_tuple(exp, NFNL_EXP_TUPLE_MASTER, tb[CTA_EXPECT_MASTER]);
+		if (err < 0)
+			goto errout;
+	}
+	if (tb[CTA_EXPECT_MASK]) {
+		err = exp_parse_tuple(exp, NFNL_EXP_TUPLE_MASK, tb[CTA_EXPECT_MASK]);
+		if (err < 0)
+			goto errout;
+	}
+
+	if (tb[CTA_EXPECT_NAT]) {
+		err = exp_parse_nat(exp, tb[CTA_EXPECT_MASK]);
+		if (err < 0)
+			goto errout;
+	}
+
+	if (tb[CTA_EXPECT_CLASS])
+		nfnl_exp_set_class(exp, ntohl(nla_get_u32(tb[CTA_EXPECT_CLASS])));
+
+	if (tb[CTA_EXPECT_FN])
+		nfnl_exp_set_fn(exp, nla_data(tb[CTA_EXPECT_FN]));
+
+	if (tb[CTA_EXPECT_TIMEOUT])
+		nfnl_exp_set_timeout(exp, ntohl(nla_get_u32(tb[CTA_EXPECT_TIMEOUT])));
+
+	if (tb[CTA_EXPECT_ID])
+		nfnl_exp_set_id(exp, ntohl(nla_get_u32(tb[CTA_EXPECT_ID])));
+
+	if (tb[CTA_EXPECT_HELP_NAME])
+		nfnl_exp_set_helper_name(exp, nla_data(tb[CTA_EXPECT_HELP_NAME]));
+
+	if (tb[CTA_EXPECT_ZONE])
+		nfnl_exp_set_zone(exp, ntohs(nla_get_u16(tb[CTA_EXPECT_ZONE])));
+
+	if (tb[CTA_EXPECT_FLAGS])
+		nfnl_exp_set_flags(exp, ntohl(nla_get_u32(tb[CTA_EXPECT_FLAGS])));
+
+	*result = exp;
+	return 0;
+
+errout:
+	nfnl_exp_put(exp);
+	return err;
+}
+
+static int exp_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
+			 struct nlmsghdr *nlh, struct nl_parser_param *pp)
+{
+	struct nfnl_exp *exp;
+	int err;
+
+	if ((err = nfnlmsg_exp_parse(nlh, &exp)) < 0)
+		return err;
+
+	err = pp->pp_cb((struct nl_object *) exp, pp);
+	nfnl_exp_put(exp);
+	return err;
+}
+
+int nfnl_exp_dump_request(struct nl_sock *sk)
+{
+	return nfnl_send_simple(sk, NFNL_SUBSYS_CTNETLINK_EXP, IPCTNL_MSG_EXP_GET,
+				NLM_F_DUMP, AF_UNSPEC, 0);
+}
+
+static int exp_request_update(struct nl_cache *cache, struct nl_sock *sk)
+{
+	return nfnl_exp_dump_request(sk);
+}
+
+static int exp_get_tuple_attr(int tuple)
+{
+	int attr = 0;
+
+	switch (tuple) {
+		case CTA_EXPECT_MASTER:
+			attr = NFNL_EXP_TUPLE_MASTER;
+			break;
+		case CTA_EXPECT_MASK:
+			attr = NFNL_EXP_TUPLE_MASK;
+			break;
+		case CTA_EXPECT_NAT:
+			attr = NFNL_EXP_TUPLE_NAT;
+			break;
+		case CTA_EXPECT_TUPLE:
+		default :
+			attr = NFNL_EXP_TUPLE_EXPECT;
+			break;
+	}
+
+	return attr;
+}
+
+static int nfnl_exp_build_tuple(struct nl_msg *msg, const struct nfnl_exp *exp,
+			       int cta)
+{
+	struct nlattr *tuple, *ip, *proto;
+	struct nl_addr *addr;
+	int family;
+
+	family = nfnl_exp_get_family(exp);
+
+	int type = exp_get_tuple_attr(cta);
+
+    if (cta == CTA_EXPECT_NAT)
+        tuple = nla_nest_start(msg, CTA_EXPECT_NAT_TUPLE);
+    else
+        tuple = nla_nest_start(msg, cta);
+
+	if (!tuple)
+		goto nla_put_failure;
+
+	ip = nla_nest_start(msg, CTA_TUPLE_IP);
+	if (!ip)
+		goto nla_put_failure;
+
+	addr = nfnl_exp_get_src(exp, type);
+	if (addr)
+		NLA_PUT_ADDR(msg,
+			     family == AF_INET ? CTA_IP_V4_SRC : CTA_IP_V6_SRC,
+			     addr);
+
+	addr = nfnl_exp_get_dst(exp, type);
+	if (addr)
+		NLA_PUT_ADDR(msg,
+			     family == AF_INET ? CTA_IP_V4_DST : CTA_IP_V6_DST,
+			     addr);
+
+	nla_nest_end(msg, ip);
+
+	proto = nla_nest_start(msg, CTA_TUPLE_PROTO);
+	if (!proto)
+		goto nla_put_failure;
+
+	if (nfnl_exp_test_l4protonum(exp, type))
+		NLA_PUT_U8(msg, CTA_PROTO_NUM, nfnl_exp_get_l4protonum(exp, type));
+
+	if (nfnl_exp_test_ports(exp, type)) {
+		NLA_PUT_U16(msg, CTA_PROTO_SRC_PORT,
+			htons(nfnl_exp_get_src_port(exp, type)));
+
+		NLA_PUT_U16(msg, CTA_PROTO_DST_PORT,
+			htons(nfnl_exp_get_dst_port(exp, type)));
+	}
+
+	if (nfnl_exp_test_icmp(exp, type)) {
+		NLA_PUT_U16(msg, CTA_PROTO_ICMP_ID,
+			htons(nfnl_exp_get_icmp_id(exp, type)));
+
+		NLA_PUT_U8(msg, CTA_PROTO_ICMP_TYPE,
+			    nfnl_exp_get_icmp_type(exp, type));
+
+		NLA_PUT_U8(msg, CTA_PROTO_ICMP_CODE,
+			    nfnl_exp_get_icmp_code(exp, type));
+	}
+
+	nla_nest_end(msg, proto);
+
+	nla_nest_end(msg, tuple);
+	return 0;
+
+nla_put_failure:
+	return -NLE_MSGSIZE;
+}
+
+static int nfnl_exp_build_nat(struct nl_msg *msg, const struct nfnl_exp *exp)
+{
+	struct nlattr *nat;
+	int err;
+
+	nat = nla_nest_start(msg, CTA_EXPECT_NAT);
+
+	if (nfnl_exp_test_nat_dir(exp)) {
+		NLA_PUT_U32(msg, CTA_EXPECT_NAT_DIR,
+				nfnl_exp_get_nat_dir(exp));
+	}
+
+	if ((err = nfnl_exp_build_tuple(msg, exp, CTA_EXPECT_NAT)) < 0)
+		goto nla_put_failure;
+
+	nla_nest_end(msg, nat);
+	return 0;
+
+nla_put_failure:
+	return -NLE_MSGSIZE;
+}
+
+static int nfnl_exp_build_message(const struct nfnl_exp *exp, int cmd, int flags,
+				 struct nl_msg **result)
+{
+	struct nl_msg *msg;
+	int err;
+
+	msg = nfnlmsg_alloc_simple(NFNL_SUBSYS_CTNETLINK_EXP, cmd, flags,
+				   nfnl_exp_get_family(exp), 0);
+	if (msg == NULL)
+		return -NLE_NOMEM;
+
+	if ((err = nfnl_exp_build_tuple(msg, exp, CTA_EXPECT_TUPLE)) < 0)
+		goto err_out;
+
+	if ((err = nfnl_exp_build_tuple(msg, exp, CTA_EXPECT_MASTER)) < 0)
+		goto err_out;
+
+	if ((err = nfnl_exp_build_tuple(msg, exp, CTA_EXPECT_MASK)) < 0)
+		goto err_out;
+
+	if (nfnl_exp_test_src(exp, NFNL_EXP_TUPLE_NAT)) {
+		if ((err = nfnl_exp_build_nat(msg, exp)) < 0)
+			goto err_out;
+	}
+
+	if (nfnl_exp_test_class(exp))
+		NLA_PUT_U32(msg, CTA_EXPECT_CLASS, htonl(nfnl_exp_get_class(exp)));
+
+	if (nfnl_exp_test_fn(exp))
+		NLA_PUT_STRING(msg, CTA_EXPECT_FN, nfnl_exp_get_fn(exp));
+
+	if (nfnl_exp_test_id(exp))
+		NLA_PUT_U32(msg, CTA_EXPECT_ID, htonl(nfnl_exp_get_id(exp)));
+
+	if (nfnl_exp_test_timeout(exp))
+		NLA_PUT_U32(msg, CTA_EXPECT_TIMEOUT, htonl(nfnl_exp_get_timeout(exp)));
+
+	if (nfnl_exp_test_helper_name(exp))
+		NLA_PUT_STRING(msg, CTA_EXPECT_HELP_NAME, nfnl_exp_get_helper_name(exp));
+
+	if (nfnl_exp_test_zone(exp))
+		NLA_PUT_U16(msg, CTA_EXPECT_ZONE, htons(nfnl_exp_get_zone(exp)));
+
+	if (nfnl_exp_test_flags(exp))
+		NLA_PUT_U32(msg, CTA_EXPECT_FLAGS, htonl(nfnl_exp_get_flags(exp)));
+
+	*result = msg;
+	return 0;
+
+nla_put_failure:
+err_out:
+	nlmsg_free(msg);
+	return err;
+}
+
+int nfnl_exp_build_add_request(const struct nfnl_exp *exp, int flags,
+			      struct nl_msg **result)
+{
+	return nfnl_exp_build_message(exp, IPCTNL_MSG_EXP_NEW, flags, result);
+}
+
+int nfnl_exp_add(struct nl_sock *sk, const struct nfnl_exp *exp, int flags)
+{
+	struct nl_msg *msg;
+	int err;
+
+	if ((err = nfnl_exp_build_add_request(exp, flags, &msg)) < 0)
+		return err;
+
+	err = nl_send_auto_complete(sk, msg);
+	nlmsg_free(msg);
+	if (err < 0)
+		return err;
+
+	return wait_for_ack(sk);
+}
+
+int nfnl_exp_build_delete_request(const struct nfnl_exp *exp, int flags,
+				 struct nl_msg **result)
+{
+	return nfnl_exp_build_message(exp, IPCTNL_MSG_EXP_DELETE, flags, result);
+}
+
+int nfnl_exp_del(struct nl_sock *sk, const struct nfnl_exp *exp, int flags)
+{
+	struct nl_msg *msg;
+	int err;
+
+	if ((err = nfnl_exp_build_delete_request(exp, flags, &msg)) < 0)
+		return err;
+
+	err = nl_send_auto_complete(sk, msg);
+	nlmsg_free(msg);
+	if (err < 0)
+		return err;
+
+	return wait_for_ack(sk);
+}
+
+int nfnl_exp_build_query_request(const struct nfnl_exp *exp, int flags,
+				struct nl_msg **result)
+{
+	return nfnl_exp_build_message(exp, IPCTNL_MSG_EXP_GET, flags, result);
+}
+
+int nfnl_exp_query(struct nl_sock *sk, const struct nfnl_exp *exp, int flags)
+{
+	struct nl_msg *msg;
+	int err;
+
+	if ((err = nfnl_exp_build_query_request(exp, flags, &msg)) < 0)
+		return err;
+
+	err = nl_send_auto_complete(sk, msg);
+	nlmsg_free(msg);
+	if (err < 0)
+		return err;
+
+	return wait_for_ack(sk);
+}
+
+/**
+ * @name Cache Management
+ * @{
+ */
+
+/**
+ * Build a expectation cache holding all expectations currently in the kernel
+ * @arg sk		Netlink socket.
+ * @arg result		Pointer to store resulting cache.
+ *
+ * Allocates a new cache, initializes it properly and updates it to
+ * contain all expectations currently in the kernel.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int nfnl_exp_alloc_cache(struct nl_sock *sk, struct nl_cache **result)
+{
+	return nl_cache_alloc_and_fill(&nfnl_exp_ops, sk, result);
+}
+
+/** @} */
+
+/**
+ * @name Expectation Addition
+ * @{
+ */
+
+/** @} */
+
+static struct nl_af_group exp_groups[] = {
+	{ AF_UNSPEC, NFNLGRP_CONNTRACK_EXP_NEW },
+	{ AF_UNSPEC, NFNLGRP_CONNTRACK_EXP_UPDATE },
+	{ AF_UNSPEC, NFNLGRP_CONNTRACK_EXP_DESTROY },
+	{ END_OF_GROUP_LIST },
+};
+
+#define NFNLMSG_EXP_TYPE(type) NFNLMSG_TYPE(NFNL_SUBSYS_CTNETLINK_EXP, (type))
+static struct nl_cache_ops nfnl_exp_ops = {
+	.co_name		    = "netfilter/exp",
+	.co_hdrsize		    = NFNL_HDRLEN,
+	.co_msgtypes		= {
+		{ NFNLMSG_EXP_TYPE(IPCTNL_MSG_EXP_NEW), NL_ACT_NEW, "new" },
+		{ NFNLMSG_EXP_TYPE(IPCTNL_MSG_EXP_GET), NL_ACT_GET, "get" },
+		{ NFNLMSG_EXP_TYPE(IPCTNL_MSG_EXP_DELETE), NL_ACT_DEL, "del" },
+		END_OF_MSGTYPES_LIST,
+	},
+	.co_protocol		= NETLINK_NETFILTER,
+	.co_groups		= exp_groups,
+	.co_request_update	= exp_request_update,
+	.co_msg_parser		= exp_msg_parser,
+	.co_obj_ops		= &exp_obj_ops,
+};
+
+static void __init exp_init(void)
+{
+	nl_cache_mngt_register(&nfnl_exp_ops);
+}
+
+static void __exit exp_exit(void)
+{
+	nl_cache_mngt_unregister(&nfnl_exp_ops);
+}
+
+/** @} */
diff --git a/lib/netfilter/exp_obj.c b/lib/netfilter/exp_obj.c
new file mode 100644
index 0000000..69b4dd3
--- /dev/null
+++ b/lib/netfilter/exp_obj.c
@@ -0,0 +1,888 @@
+/*
+ * lib/netfilter/exp_obj.c	Conntrack Expectation Object
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2007 Philip Craig <philipc@snapgear.com>
+ * Copyright (c) 2007 Secure Computing Corporation
+ * Copyright (c) 2012 Rich Fought <rich.fought@watchguard.com>
+ */
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <linux/netfilter/nfnetlink_conntrack.h>
+#include <linux/netfilter/nf_conntrack_common.h>
+#include <linux/netfilter/nf_conntrack_tcp.h>
+
+#include <netlink-private/netlink.h>
+#include <netlink/netfilter/nfnl.h>
+#include <netlink/netfilter/exp.h>
+
+// The 32-bit attribute mask in the common object header isn't
+// big enough to handle all attributes of an expectation.  So
+// we'll for sure specify optional attributes + parent attributes
+// that are required for valid object comparison.  Comparison of
+// these parent attributes will include nested attributes.
+
+/** @cond SKIP */
+#define EXP_ATTR_FAMILY			(1UL << 0) // 8-bit
+#define EXP_ATTR_TIMEOUT		(1UL << 1) // 32-bit
+#define EXP_ATTR_ID			(1UL << 2) // 32-bit
+#define EXP_ATTR_HELPER_NAME		(1UL << 3) // string
+#define EXP_ATTR_ZONE			(1UL << 4) // 16-bit
+#define EXP_ATTR_FLAGS			(1UL << 5) // 32-bit
+#define EXP_ATTR_CLASS			(1UL << 6) // 32-bit
+#define EXP_ATTR_FN			(1UL << 7) // String
+// Tuples
+#define EXP_ATTR_EXPECT_IP_SRC		(1UL << 8)
+#define EXP_ATTR_EXPECT_IP_DST		(1UL << 9)
+#define EXP_ATTR_EXPECT_L4PROTO_NUM	(1UL << 10)
+#define EXP_ATTR_EXPECT_L4PROTO_PORTS	(1UL << 11)
+#define EXP_ATTR_EXPECT_L4PROTO_ICMP	(1UL << 12)
+#define EXP_ATTR_MASTER_IP_SRC		(1UL << 13)
+#define EXP_ATTR_MASTER_IP_DST		(1UL << 14)
+#define EXP_ATTR_MASTER_L4PROTO_NUM	(1UL << 15)
+#define EXP_ATTR_MASTER_L4PROTO_PORTS	(1UL << 16)
+#define EXP_ATTR_MASTER_L4PROTO_ICMP	(1UL << 17)
+#define EXP_ATTR_MASK_IP_SRC		(1UL << 18)
+#define EXP_ATTR_MASK_IP_DST		(1UL << 19)
+#define EXP_ATTR_MASK_L4PROTO_NUM	(1UL << 20)
+#define EXP_ATTR_MASK_L4PROTO_PORTS	(1UL << 21)
+#define EXP_ATTR_MASK_L4PROTO_ICMP	(1UL << 22)
+#define EXP_ATTR_NAT_IP_SRC		(1UL << 23)
+#define EXP_ATTR_NAT_IP_DST		(1UL << 24)
+#define EXP_ATTR_NAT_L4PROTO_NUM	(1UL << 25)
+#define EXP_ATTR_NAT_L4PROTO_PORTS	(1UL << 26)
+#define EXP_ATTR_NAT_L4PROTO_ICMP	(1UL << 27)
+#define EXP_ATTR_NAT_DIR		(1UL << 28)
+/** @endcond */
+
+static void exp_free_data(struct nl_object *c)
+{
+	struct nfnl_exp *exp = (struct nfnl_exp *) c;
+
+	if (exp == NULL)
+		return;
+
+	nl_addr_put(exp->exp_expect.src);
+	nl_addr_put(exp->exp_expect.dst);
+	nl_addr_put(exp->exp_master.src);
+	nl_addr_put(exp->exp_master.dst);
+	nl_addr_put(exp->exp_mask.src);
+	nl_addr_put(exp->exp_mask.dst);
+	nl_addr_put(exp->exp_nat.src);
+	nl_addr_put(exp->exp_nat.dst);
+
+	free(exp->exp_fn);
+	free(exp->exp_helper_name);
+}
+
+static int exp_clone(struct nl_object *_dst, struct nl_object *_src)
+{
+	struct nfnl_exp *dst = (struct nfnl_exp *) _dst;
+	struct nfnl_exp *src = (struct nfnl_exp *) _src;
+	struct nl_addr *addr;
+
+	// Expectation
+	if (src->exp_expect.src) {
+		addr = nl_addr_clone(src->exp_expect.src);
+		if (!addr)
+			return -NLE_NOMEM;
+		dst->exp_expect.src = addr;
+	}
+
+	if (src->exp_expect.dst) {
+		addr = nl_addr_clone(src->exp_expect.dst);
+		if (!addr)
+			return -NLE_NOMEM;
+		dst->exp_expect.dst = addr;
+	}
+
+	// Master CT
+	if (src->exp_master.src) {
+		addr = nl_addr_clone(src->exp_master.src);
+		if (!addr)
+			return -NLE_NOMEM;
+		dst->exp_master.src = addr;
+	}
+
+	if (src->exp_master.dst) {
+		addr = nl_addr_clone(src->exp_master.dst);
+		if (!addr)
+			return -NLE_NOMEM;
+		dst->exp_master.dst = addr;
+	}
+
+	// Mask
+	if (src->exp_mask.src) {
+		addr = nl_addr_clone(src->exp_mask.src);
+		if (!addr)
+			return -NLE_NOMEM;
+		dst->exp_mask.src = addr;
+	}
+
+	if (src->exp_mask.dst) {
+		addr = nl_addr_clone(src->exp_mask.dst);
+		if (!addr)
+			return -NLE_NOMEM;
+		dst->exp_mask.dst = addr;
+	}
+
+    // NAT
+	if (src->exp_nat.src) {
+		addr = nl_addr_clone(src->exp_nat.src);
+		if (!addr)
+			return -NLE_NOMEM;
+		dst->exp_nat.src = addr;
+	}
+
+	if (src->exp_nat.dst) {
+		addr = nl_addr_clone(src->exp_nat.dst);
+		if (!addr)
+			return -NLE_NOMEM;
+		dst->exp_nat.dst = addr;
+	}
+
+	if (src->exp_fn)
+		dst->exp_fn = strdup(src->exp_fn);
+
+	if (src->exp_helper_name)
+		dst->exp_helper_name = strdup(src->exp_helper_name);
+
+	return 0;
+}
+
+static void dump_addr(struct nl_dump_params *p, struct nl_addr *addr, int port)
+{
+	char buf[64];
+
+	if (addr)
+		nl_dump(p, "%s", nl_addr2str(addr, buf, sizeof(buf)));
+
+	if (port)
+		nl_dump(p, ":%u ", port);
+	else if (addr)
+		nl_dump(p, " ");
+}
+
+static void dump_icmp(struct nl_dump_params *p, struct nfnl_exp *exp, int tuple)
+{
+	if (nfnl_exp_test_icmp(exp, tuple)) {
+
+		nl_dump(p, "icmp type %d ", nfnl_exp_get_icmp_type(exp, tuple));
+
+		nl_dump(p, "code %d ", nfnl_exp_get_icmp_code(exp, tuple));
+
+		nl_dump(p, "id %d ", nfnl_exp_get_icmp_id(exp, tuple));
+	}
+}
+
+static void exp_dump_tuples(struct nfnl_exp *exp, struct nl_dump_params *p)
+{
+	struct nl_addr *tuple_src, *tuple_dst;
+	int tuple_sport, tuple_dport;
+	int i = 0;
+	char buf[64];
+
+	for (i = NFNL_EXP_TUPLE_EXPECT; i < NFNL_EXP_TUPLE_MAX; i++) {
+		tuple_src = NULL;
+		tuple_dst = NULL;
+		tuple_sport = 0;
+		tuple_dport = 0;
+
+		// Test needed for NAT case
+		if (nfnl_exp_test_src(exp, i))
+			tuple_src = nfnl_exp_get_src(exp, i);
+		if (nfnl_exp_test_dst(exp, i))
+			tuple_dst = nfnl_exp_get_dst(exp, i);
+
+        // Don't have tests for individual ports/types/codes/ids,
+		if (nfnl_exp_test_l4protonum(exp, i)) {
+			nl_dump(p, "%s ",
+				nl_ip_proto2str(nfnl_exp_get_l4protonum(exp, i), buf, sizeof(buf)));
+		}
+
+		if (nfnl_exp_test_ports(exp, i)) {
+			tuple_sport = nfnl_exp_get_src_port(exp, i);
+			tuple_dport = nfnl_exp_get_dst_port(exp, i);
+		}
+
+		dump_addr(p, tuple_src, tuple_sport);
+		dump_addr(p, tuple_dst, tuple_dport);
+		dump_icmp(p, exp, 0);
+	}
+
+	if (nfnl_exp_test_nat_dir(exp))
+		nl_dump(p, "nat dir %s ", exp->exp_nat_dir);
+
+}
+
+/* FIXME Compatible with /proc/net/nf_conntrack */
+static void exp_dump_line(struct nl_object *a, struct nl_dump_params *p)
+{
+	struct nfnl_exp *exp = (struct nfnl_exp *) a;
+
+	nl_new_line(p);
+
+	exp_dump_tuples(exp, p);
+
+	nl_dump(p, "\n");
+}
+
+static void exp_dump_details(struct nl_object *a, struct nl_dump_params *p)
+{
+	struct nfnl_exp *exp = (struct nfnl_exp *) a;
+	char buf[64];
+	int fp = 0;
+
+	exp_dump_line(a, p);
+
+	nl_dump(p, "    id 0x%x ", exp->exp_id);
+	nl_dump_line(p, "family %s ",
+		nl_af2str(exp->exp_family, buf, sizeof(buf)));
+
+	if (nfnl_exp_test_timeout(exp)) {
+		uint64_t timeout_ms = nfnl_exp_get_timeout(exp) * 1000UL;
+		nl_dump(p, "timeout %s ",
+			nl_msec2str(timeout_ms, buf, sizeof(buf)));
+	}
+
+	if (nfnl_exp_test_helper_name(exp))
+		nl_dump(p, "helper %s ", exp->exp_helper_name);
+
+	if (nfnl_exp_test_fn(exp))
+		nl_dump(p, "fn %s ", exp->exp_fn);
+
+	if (nfnl_exp_test_class(exp))
+		nl_dump(p, "class %u ", nfnl_exp_get_class(exp));
+
+	if (nfnl_exp_test_zone(exp))
+		nl_dump(p, "zone %u ", nfnl_exp_get_zone(exp));
+
+	if (nfnl_exp_test_flags(exp))
+		nl_dump(p, "<");
+#define PRINT_FLAG(str) \
+	{ nl_dump(p, "%s%s", fp++ ? "," : "", (str)); }
+
+	if (exp->exp_flags & NF_CT_EXPECT_PERMANENT)
+		PRINT_FLAG("PERMANENT");
+	if (exp->exp_flags & NF_CT_EXPECT_INACTIVE)
+		PRINT_FLAG("INACTIVE");
+	if (exp->exp_flags & NF_CT_EXPECT_USERSPACE)
+		PRINT_FLAG("USERSPACE");
+#undef PRINT_FLAG
+
+	if (nfnl_exp_test_flags(exp))
+		nl_dump(p, ">");
+
+	nl_dump(p, "\n");
+}
+
+static int exp_cmp_l4proto_ports (union nfnl_exp_protodata *a, union nfnl_exp_protodata *b) {
+	// Must return 0 for match, 1 for mismatch
+	int d = 0;
+	d = ( (a->port.src != b->port.src) ||
+		  (a->port.dst != b->port.dst) );
+
+	return d;
+}
+
+static int exp_cmp_l4proto_icmp (union nfnl_exp_protodata *a, union nfnl_exp_protodata *b) {
+    // Must return 0 for match, 1 for mismatch
+	int d = 0;
+	d = ( (a->icmp.code != b->icmp.code) ||
+		  (a->icmp.type != b->icmp.type) ||
+		  (a->icmp.id != b->icmp.id) );
+
+	return d;
+}
+
+static int exp_compare(struct nl_object *_a, struct nl_object *_b,
+							uint32_t attrs, int flags)
+{
+	struct nfnl_exp *a = (struct nfnl_exp *) _a;
+	struct nfnl_exp *b = (struct nfnl_exp *) _b;
+	int diff = 0;
+
+#define EXP_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, EXP_ATTR_##ATTR, a, b, EXPR)
+#define EXP_DIFF_VAL(ATTR, FIELD) EXP_DIFF(ATTR, a->FIELD != b->FIELD)
+#define EXP_DIFF_STRING(ATTR, FIELD) EXP_DIFF(ATTR, (strcmp(a->FIELD, b->FIELD) != 0))
+#define EXP_DIFF_ADDR(ATTR, FIELD) \
+		((flags & LOOSE_COMPARISON) \
+		? EXP_DIFF(ATTR, nl_addr_cmp_prefix(a->FIELD, b->FIELD)) \
+		: EXP_DIFF(ATTR, nl_addr_cmp(a->FIELD, b->FIELD)))
+#define EXP_DIFF_L4PROTO_PORTS(ATTR, FIELD) \
+		EXP_DIFF(ATTR, exp_cmp_l4proto_ports(&(a->FIELD), &(b->FIELD)))
+#define EXP_DIFF_L4PROTO_ICMP(ATTR, FIELD) \
+		EXP_DIFF(ATTR, exp_cmp_l4proto_icmp(&(a->FIELD), &(b->FIELD)))
+
+		diff |= EXP_DIFF_VAL(FAMILY,			exp_family);
+		diff |= EXP_DIFF_VAL(TIMEOUT,			exp_timeout);
+		diff |= EXP_DIFF_VAL(ID,			exp_id);
+		diff |= EXP_DIFF_VAL(ZONE,			exp_zone);
+		diff |= EXP_DIFF_VAL(CLASS,			exp_class);
+		diff |= EXP_DIFF_VAL(FLAGS,			exp_flags);
+		diff |= EXP_DIFF_VAL(NAT_DIR,			exp_nat_dir);
+
+		diff |= EXP_DIFF_STRING(FN,			exp_fn);
+		diff |= EXP_DIFF_STRING(HELPER_NAME,		exp_helper_name);
+
+		diff |= EXP_DIFF_ADDR(EXPECT_IP_SRC,			exp_expect.src);
+		diff |= EXP_DIFF_ADDR(EXPECT_IP_DST,			exp_expect.dst);
+		diff |= EXP_DIFF_VAL(EXPECT_L4PROTO_NUM,		exp_expect.proto.l4protonum);
+		diff |= EXP_DIFF_L4PROTO_PORTS(EXPECT_L4PROTO_PORTS,	exp_expect.proto.l4protodata);
+		diff |= EXP_DIFF_L4PROTO_ICMP(EXPECT_L4PROTO_ICMP,	exp_expect.proto.l4protodata);
+
+		diff |= EXP_DIFF_ADDR(MASTER_IP_SRC,			exp_master.src);
+		diff |= EXP_DIFF_ADDR(MASTER_IP_DST,			exp_master.dst);
+		diff |= EXP_DIFF_VAL(MASTER_L4PROTO_NUM,		exp_master.proto.l4protonum);
+		diff |= EXP_DIFF_L4PROTO_PORTS(MASTER_L4PROTO_PORTS,	exp_master.proto.l4protodata);
+		diff |= EXP_DIFF_L4PROTO_ICMP(MASTER_L4PROTO_ICMP,	exp_master.proto.l4protodata);
+
+		diff |= EXP_DIFF_ADDR(MASK_IP_SRC,			exp_mask.src);
+		diff |= EXP_DIFF_ADDR(MASK_IP_DST,			exp_mask.dst);
+		diff |= EXP_DIFF_VAL(MASK_L4PROTO_NUM,			exp_mask.proto.l4protonum);
+		diff |= EXP_DIFF_L4PROTO_PORTS(MASK_L4PROTO_PORTS,	exp_mask.proto.l4protodata);
+		diff |= EXP_DIFF_L4PROTO_ICMP(MASK_L4PROTO_ICMP,	exp_mask.proto.l4protodata);
+
+		diff |= EXP_DIFF_ADDR(NAT_IP_SRC,			exp_nat.src);
+		diff |= EXP_DIFF_ADDR(NAT_IP_DST,			exp_nat.dst);
+		diff |= EXP_DIFF_VAL(NAT_L4PROTO_NUM,			exp_nat.proto.l4protonum);
+		diff |= EXP_DIFF_L4PROTO_PORTS(NAT_L4PROTO_PORTS,	exp_nat.proto.l4protodata);
+		diff |= EXP_DIFF_L4PROTO_ICMP(NAT_L4PROTO_ICMP,		exp_nat.proto.l4protodata);
+
+#undef EXP_DIFF
+#undef EXP_DIFF_VAL
+#undef EXP_DIFF_STRING
+#undef EXP_DIFF_ADDR
+#undef EXP_DIFF_L4PROTO_PORTS
+#undef EXP_DIFF_L4PROTO_ICMP
+
+	return diff;
+}
+
+// CLI arguments?
+static const struct trans_tbl exp_attrs[] = {
+	__ADD(EXP_ATTR_FAMILY,				family)
+	__ADD(EXP_ATTR_TIMEOUT,				timeout)
+	__ADD(EXP_ATTR_ID,				id)
+	__ADD(EXP_ATTR_HELPER_NAME,			helpername)
+	__ADD(EXP_ATTR_ZONE,				zone)
+	__ADD(EXP_ATTR_CLASS,				class)
+	__ADD(EXP_ATTR_FLAGS,				flags)
+	__ADD(EXP_ATTR_FN,				function)
+	__ADD(EXP_ATTR_EXPECT_IP_SRC,			expectipsrc)
+	__ADD(EXP_ATTR_EXPECT_IP_DST,			expectipdst)
+	__ADD(EXP_ATTR_EXPECT_L4PROTO_NUM,		expectprotonum)
+	__ADD(EXP_ATTR_EXPECT_L4PROTO_PORTS,		expectports)
+	__ADD(EXP_ATTR_EXPECT_L4PROTO_ICMP,		expecticmp)
+	__ADD(EXP_ATTR_MASTER_IP_SRC,			masteripsrc)
+	__ADD(EXP_ATTR_MASTER_IP_DST,			masteripdst)
+	__ADD(EXP_ATTR_MASTER_L4PROTO_NUM,		masterprotonum)
+	__ADD(EXP_ATTR_MASTER_L4PROTO_PORTS,		masterports)
+	__ADD(EXP_ATTR_MASTER_L4PROTO_ICMP,		mastericmp)
+	__ADD(EXP_ATTR_MASK_IP_SRC,			maskipsrc)
+	__ADD(EXP_ATTR_MASK_IP_DST,			maskipdst)
+	__ADD(EXP_ATTR_MASK_L4PROTO_NUM,		maskprotonum)
+	__ADD(EXP_ATTR_MASK_L4PROTO_PORTS,		maskports)
+	__ADD(EXP_ATTR_MASK_L4PROTO_ICMP,		maskicmp)
+	__ADD(EXP_ATTR_NAT_IP_SRC,			natipsrc)
+	__ADD(EXP_ATTR_NAT_IP_DST,			natipdst)
+	__ADD(EXP_ATTR_NAT_L4PROTO_NUM,			natprotonum)
+	__ADD(EXP_ATTR_NAT_L4PROTO_PORTS,		natports)
+	__ADD(EXP_ATTR_NAT_L4PROTO_ICMP,		naticmp)
+	__ADD(EXP_ATTR_NAT_DIR,				natdir)
+};
+
+static char *exp_attrs2str(int attrs, char *buf, size_t len)
+{
+	return __flags2str(attrs, buf, len, exp_attrs, ARRAY_SIZE(exp_attrs));
+}
+
+/**
+ * @name Allocation/Freeing
+ * @{
+ */
+
+struct nfnl_exp *nfnl_exp_alloc(void)
+{
+	return (struct nfnl_exp *) nl_object_alloc(&exp_obj_ops);
+}
+
+void nfnl_exp_get(struct nfnl_exp *exp)
+{
+	nl_object_get((struct nl_object *) exp);
+}
+
+void nfnl_exp_put(struct nfnl_exp *exp)
+{
+	nl_object_put((struct nl_object *) exp);
+}
+
+/** @} */
+
+/**
+ * @name Attributes
+ * @{
+ */
+
+void nfnl_exp_set_family(struct nfnl_exp *exp, uint8_t family)
+{
+	exp->exp_family = family;
+	exp->ce_mask |= EXP_ATTR_FAMILY;
+}
+
+uint8_t nfnl_exp_get_family(const struct nfnl_exp *exp)
+{
+	if (exp->ce_mask & EXP_ATTR_FAMILY)
+		return exp->exp_family;
+	else
+		return AF_UNSPEC;
+}
+
+void nfnl_exp_set_flags(struct nfnl_exp *exp, uint32_t flags)
+{
+	exp->exp_flags |= flags;
+	exp->ce_mask |= EXP_ATTR_FLAGS;
+}
+
+int nfnl_exp_test_flags(const struct nfnl_exp *exp)
+{
+	return !!(exp->ce_mask & EXP_ATTR_FLAGS);
+}
+
+void nfnl_exp_unset_flags(struct nfnl_exp *exp, uint32_t flags)
+{
+	exp->exp_flags &= ~flags;
+	exp->ce_mask |= EXP_ATTR_FLAGS;
+}
+
+uint32_t nfnl_exp_get_flags(const struct nfnl_exp *exp)
+{
+	return exp->exp_flags;
+}
+
+static const struct trans_tbl flag_table[] = {
+	__ADD(IPS_EXPECTED, expected)
+	__ADD(IPS_SEEN_REPLY, seen_reply)
+	__ADD(IPS_ASSURED, assured)
+};
+
+char * nfnl_exp_flags2str(int flags, char *buf, size_t len)
+{
+	return __flags2str(flags, buf, len, flag_table,
+			   ARRAY_SIZE(flag_table));
+}
+
+int nfnl_exp_str2flags(const char *name)
+{
+	return __str2flags(name, flag_table, ARRAY_SIZE(flag_table));
+}
+
+void nfnl_exp_set_timeout(struct nfnl_exp *exp, uint32_t timeout)
+{
+	exp->exp_timeout = timeout;
+	exp->ce_mask |= EXP_ATTR_TIMEOUT;
+}
+
+int nfnl_exp_test_timeout(const struct nfnl_exp *exp)
+{
+	return !!(exp->ce_mask & EXP_ATTR_TIMEOUT);
+}
+
+uint32_t nfnl_exp_get_timeout(const struct nfnl_exp *exp)
+{
+	return exp->exp_timeout;
+}
+
+void nfnl_exp_set_id(struct nfnl_exp *exp, uint32_t id)
+{
+	exp->exp_id = id;
+	exp->ce_mask |= EXP_ATTR_ID;
+}
+
+int nfnl_exp_test_id(const struct nfnl_exp *exp)
+{
+	return !!(exp->ce_mask & EXP_ATTR_ID);
+}
+
+uint32_t nfnl_exp_get_id(const struct nfnl_exp *exp)
+{
+	return exp->exp_id;
+}
+
+int nfnl_exp_set_helper_name(struct nfnl_exp *exp, void *name)
+{
+	free(exp->exp_helper_name);
+	exp->exp_helper_name = strdup(name);
+	if (!exp->exp_helper_name)
+		return -NLE_NOMEM;
+
+	exp->ce_mask |= EXP_ATTR_HELPER_NAME;
+	return 0;
+}
+
+int  nfnl_exp_test_helper_name(const struct nfnl_exp *exp)
+{
+	return !!(exp->ce_mask & EXP_ATTR_HELPER_NAME);
+}
+
+const char * nfnl_exp_get_helper_name(const struct nfnl_exp *exp)
+{
+	return exp->exp_helper_name;
+}
+
+void nfnl_exp_set_zone(struct nfnl_exp *exp, uint16_t zone)
+{
+	exp->exp_zone = zone;
+	exp->ce_mask |= EXP_ATTR_ZONE;
+}
+
+int nfnl_exp_test_zone(const struct nfnl_exp *exp)
+{
+	return !!(exp->ce_mask & EXP_ATTR_ZONE);
+}
+
+uint16_t nfnl_exp_get_zone(const struct nfnl_exp *exp)
+{
+	return exp->exp_zone;
+}
+
+void nfnl_exp_set_class(struct nfnl_exp *exp, uint32_t class)
+{
+	exp->exp_class = class;
+	exp->ce_mask |= EXP_ATTR_CLASS;
+}
+
+int nfnl_exp_test_class(const struct nfnl_exp *exp)
+{
+	return !!(exp->ce_mask & EXP_ATTR_CLASS);
+}
+
+uint32_t nfnl_exp_get_class(const struct nfnl_exp *exp)
+{
+	return exp->exp_class;
+}
+
+int nfnl_exp_set_fn(struct nfnl_exp *exp, void *fn)
+{
+	free(exp->exp_fn);
+	exp->exp_fn = strdup(fn);
+	if (!exp->exp_fn)
+		return -NLE_NOMEM;
+
+	exp->ce_mask |= EXP_ATTR_FN;
+	return 0;
+}
+
+int nfnl_exp_test_fn(const struct nfnl_exp *exp)
+{
+	return !!(exp->ce_mask & EXP_ATTR_FN);
+}
+
+const char * nfnl_exp_get_fn(const struct nfnl_exp *exp)
+{
+	return exp->exp_fn;
+}
+
+void nfnl_exp_set_nat_dir(struct nfnl_exp *exp, uint8_t nat_dir)
+{
+	exp->exp_nat_dir = nat_dir;
+	exp->ce_mask |= EXP_ATTR_NAT_DIR;
+}
+
+int nfnl_exp_test_nat_dir(const struct nfnl_exp *exp)
+{
+	return !!(exp->ce_mask & EXP_ATTR_NAT_DIR);
+}
+
+uint8_t nfnl_exp_get_nat_dir(const struct nfnl_exp *exp)
+{
+	return exp->exp_nat_dir;
+}
+
+#define EXP_GET_TUPLE(e, t) \
+	(t == NFNL_EXP_TUPLE_MASTER) ? \
+		&(e->exp_master) : \
+	(t == NFNL_EXP_TUPLE_MASK) ? \
+		&(e->exp_mask) : \
+	(t == NFNL_EXP_TUPLE_NAT) ? \
+		&(e->exp_nat) : &(exp->exp_expect)
+
+static int exp_get_src_attr(int tuple)
+{
+	int attr = 0;
+
+	switch (tuple) {
+		case NFNL_EXP_TUPLE_MASTER:
+			attr = EXP_ATTR_MASTER_IP_SRC;
+			break;
+		case NFNL_EXP_TUPLE_MASK:
+			attr = EXP_ATTR_MASK_IP_SRC;
+			break;
+		case NFNL_EXP_TUPLE_NAT:
+			attr = EXP_ATTR_NAT_IP_SRC;
+			break;
+		case NFNL_EXP_TUPLE_EXPECT:
+		default :
+			attr = EXP_ATTR_EXPECT_IP_SRC;
+	}
+
+	return attr;
+}
+
+static int exp_get_dst_attr(int tuple)
+{
+	int attr = 0;
+
+	switch (tuple) {
+		case NFNL_EXP_TUPLE_MASTER:
+			attr = EXP_ATTR_MASTER_IP_DST;
+			break;
+		case NFNL_EXP_TUPLE_MASK:
+			attr = EXP_ATTR_MASK_IP_DST;
+			break;
+		case NFNL_EXP_TUPLE_NAT:
+			attr = EXP_ATTR_NAT_IP_DST;
+			break;
+		case NFNL_EXP_TUPLE_EXPECT:
+		default :
+			attr = EXP_ATTR_EXPECT_IP_DST;
+	}
+
+	return attr;
+}
+
+
+static int exp_set_addr(struct nfnl_exp *exp, struct nl_addr *addr,
+                          int attr, struct nl_addr ** exp_addr)
+{
+	if (exp->ce_mask & EXP_ATTR_FAMILY) {
+		if (addr->a_family != exp->exp_family)
+			return -NLE_AF_MISMATCH;
+	} else
+		nfnl_exp_set_family(exp, addr->a_family);
+
+	if (*exp_addr)
+		nl_addr_put(*exp_addr);
+
+	nl_addr_get(addr);
+	*exp_addr = addr;
+	exp->ce_mask |= attr;
+
+	return 0;
+}
+
+int nfnl_exp_set_src(struct nfnl_exp *exp, int tuple, struct nl_addr *addr)
+{
+	struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	return exp_set_addr(exp, addr, exp_get_src_attr(tuple), &dir->src);
+}
+
+int nfnl_exp_set_dst(struct nfnl_exp *exp, int tuple, struct nl_addr *addr)
+{
+	struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	return exp_set_addr(exp, addr, exp_get_dst_attr(tuple), &dir->dst);
+}
+
+int nfnl_exp_test_src(const struct nfnl_exp *exp, int tuple)
+{
+	return !!(exp->ce_mask & exp_get_src_attr(tuple));
+}
+
+int nfnl_exp_test_dst(const struct nfnl_exp *exp, int tuple)
+{
+	return !!(exp->ce_mask & exp_get_dst_attr(tuple));
+}
+
+struct nl_addr *nfnl_exp_get_src(const struct nfnl_exp *exp, int tuple)
+{
+	const struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	if (!(exp->ce_mask & exp_get_src_attr(tuple)))
+		return NULL;
+	return dir->src;
+}
+
+struct nl_addr *nfnl_exp_get_dst(const struct nfnl_exp *exp, int tuple)
+{
+	const struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	if (!(exp->ce_mask & exp_get_dst_attr(tuple)))
+		return NULL;
+	return dir->dst;
+}
+
+static int exp_get_l4protonum_attr(int tuple)
+{
+	int attr = 0;
+
+	switch (tuple) {
+		case NFNL_EXP_TUPLE_MASTER:
+			attr = EXP_ATTR_MASTER_L4PROTO_NUM;
+			break;
+		case NFNL_EXP_TUPLE_MASK:
+			attr = EXP_ATTR_MASK_L4PROTO_NUM;
+			break;
+		case NFNL_EXP_TUPLE_NAT:
+			attr = EXP_ATTR_NAT_L4PROTO_NUM;
+			break;
+		case NFNL_EXP_TUPLE_EXPECT:
+		default :
+			attr = EXP_ATTR_EXPECT_L4PROTO_NUM;
+	}
+
+	return attr;
+}
+
+void nfnl_exp_set_l4protonum(struct nfnl_exp *exp, int tuple, uint8_t l4protonum)
+{
+	struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	dir->proto.l4protonum = l4protonum;
+	exp->ce_mask |= exp_get_l4protonum_attr(tuple);
+}
+
+int nfnl_exp_test_l4protonum(const struct nfnl_exp *exp, int tuple)
+{
+	return !!(exp->ce_mask & exp_get_l4protonum_attr(tuple));
+}
+
+uint8_t nfnl_exp_get_l4protonum(const struct nfnl_exp *exp, int tuple)
+{
+	const struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+	return dir->proto.l4protonum;
+}
+
+static int exp_get_l4ports_attr(int tuple)
+{
+	int attr = 0;
+
+	switch (tuple) {
+		case NFNL_EXP_TUPLE_MASTER:
+			attr = EXP_ATTR_MASTER_L4PROTO_PORTS;
+			break;
+		case NFNL_EXP_TUPLE_MASK:
+			attr = EXP_ATTR_MASK_L4PROTO_PORTS;
+			break;
+		case NFNL_EXP_TUPLE_NAT:
+			attr = EXP_ATTR_NAT_L4PROTO_PORTS;
+			break;
+		case NFNL_EXP_TUPLE_EXPECT:
+		default :
+			attr = EXP_ATTR_EXPECT_L4PROTO_PORTS;
+	}
+
+	return attr;
+}
+
+void nfnl_exp_set_ports(struct nfnl_exp *exp, int tuple, uint16_t srcport, uint16_t dstport)
+{
+	struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	dir->proto.l4protodata.port.src = srcport;
+	dir->proto.l4protodata.port.dst = dstport;
+
+	exp->ce_mask |= exp_get_l4ports_attr(tuple);
+}
+
+int nfnl_exp_test_ports(const struct nfnl_exp *exp, int tuple)
+{
+	return !!(exp->ce_mask & exp_get_l4ports_attr(tuple));
+}
+
+uint16_t nfnl_exp_get_src_port(const struct nfnl_exp *exp, int tuple)
+{
+	const struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+	return dir->proto.l4protodata.port.src;
+}
+
+uint16_t nfnl_exp_get_dst_port(const struct nfnl_exp *exp, int tuple)
+{
+	const struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	return dir->proto.l4protodata.port.dst;
+}
+
+static int exp_get_l4icmp_attr(int tuple)
+{
+	int attr = 0;
+
+	switch (tuple) {
+		case NFNL_EXP_TUPLE_MASTER:
+			attr = EXP_ATTR_MASTER_L4PROTO_ICMP;
+			break;
+		case NFNL_EXP_TUPLE_MASK:
+			attr = EXP_ATTR_MASK_L4PROTO_ICMP;
+			break;
+		case NFNL_EXP_TUPLE_NAT:
+			attr = EXP_ATTR_NAT_L4PROTO_ICMP;
+			break;
+		case NFNL_EXP_TUPLE_EXPECT:
+		default :
+			attr = EXP_ATTR_EXPECT_L4PROTO_ICMP;
+	}
+
+	return attr;
+}
+
+void nfnl_exp_set_icmp(struct nfnl_exp *exp, int tuple, uint16_t id, uint8_t type, uint8_t code)
+{
+	struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	dir->proto.l4protodata.icmp.id = id;
+	dir->proto.l4protodata.icmp.type = type;
+	dir->proto.l4protodata.icmp.code = code;
+
+	exp->ce_mask |= exp_get_l4icmp_attr(tuple);
+}
+
+int nfnl_exp_test_icmp(const struct nfnl_exp *exp, int tuple)
+{
+	int attr = exp_get_l4icmp_attr(tuple);
+	return !!(exp->ce_mask & attr);
+}
+
+uint16_t nfnl_exp_get_icmp_id(const struct nfnl_exp *exp, int tuple)
+{
+	const struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	return dir->proto.l4protodata.icmp.id;
+}
+
+uint8_t nfnl_exp_get_icmp_type(const struct nfnl_exp *exp, int tuple)
+{
+	const struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	return dir->proto.l4protodata.icmp.type;
+}
+
+uint8_t nfnl_exp_get_icmp_code(const struct nfnl_exp *exp, int tuple)
+{
+	const struct nfnl_exp_dir *dir = EXP_GET_TUPLE(exp, tuple);
+
+	return dir->proto.l4protodata.icmp.code;
+}
+
+/** @} */
+
+struct nl_object_ops exp_obj_ops = {
+	.oo_name	= "netfilter/exp",
+	.oo_size	= sizeof(struct nfnl_exp),
+	.oo_free_data   = exp_free_data,
+	.oo_clone	= exp_clone,
+	.oo_dump = {
+		[NL_DUMP_LINE]		= exp_dump_line,
+		[NL_DUMP_DETAILS]	= exp_dump_details,
+	},
+	.oo_compare	= exp_compare,
+	.oo_attrs2str	= exp_attrs2str,
+};
+
+/** @} */
diff --git a/lib/netfilter/log.c b/lib/netfilter/log.c
index 96ae6c5..1bab9b6 100644
--- a/lib/netfilter/log.c
+++ b/lib/netfilter/log.c
@@ -21,7 +21,7 @@
 #include <sys/types.h>
 #include <linux/netfilter/nfnetlink_log.h>
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/netfilter/nfnl.h>
 #include <netlink/netfilter/log.h>
diff --git a/lib/netfilter/log_msg.c b/lib/netfilter/log_msg.c
index cad6ddd..5ffdaf8 100644
--- a/lib/netfilter/log_msg.c
+++ b/lib/netfilter/log_msg.c
@@ -22,10 +22,11 @@
 #include <sys/types.h>
 #include <linux/netfilter/nfnetlink_log.h>
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/netfilter/nfnl.h>
 #include <netlink/netfilter/log_msg.h>
+#include <byteswap.h>
 
 #if __BYTE_ORDER == __BIG_ENDIAN
 static uint64_t ntohll(uint64_t x)
@@ -35,7 +36,7 @@
 #elif __BYTE_ORDER == __LITTLE_ENDIAN
 static uint64_t ntohll(uint64_t x)
 {
-	return __bswap_64(x);
+	return bswap_64(x);
 }
 #endif
 
@@ -173,10 +174,9 @@
 	int err;
 
 	if ((err = nfnlmsg_log_msg_parse(nlh, &msg)) < 0)
-		goto errout;
+		return err;
 
 	err = pp->pp_cb((struct nl_object *) msg, pp);
-errout:
 	nfnl_log_msg_put(msg);
 	return err;
 }
diff --git a/lib/netfilter/log_msg_obj.c b/lib/netfilter/log_msg_obj.c
index d2cde4e..57db9d4 100644
--- a/lib/netfilter/log_msg_obj.c
+++ b/lib/netfilter/log_msg_obj.c
@@ -11,7 +11,7 @@
  * Copyright (c) 2007 Secure Computing Corporation
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netfilter/nfnl.h>
 #include <netlink/netfilter/netfilter.h>
 #include <netlink/netfilter/log_msg.h>
@@ -76,7 +76,7 @@
 	struct nl_cache *link_cache;
 	char buf[64];
 
-	link_cache = nl_cache_mngt_require("route/link");
+	link_cache = nl_cache_mngt_require_safe("route/link");
 
 	nl_new_line(p);
 
@@ -167,6 +167,9 @@
 		nl_dump(p, "SEQGLOBAL=%d ", msg->log_msg_seq_global);
 
 	nl_dump(p, "\n");
+
+	if (link_cache)
+		nl_cache_put(link_cache);
 }
 
 /**
diff --git a/lib/netfilter/log_obj.c b/lib/netfilter/log_obj.c
index ff2b63a..2b11414 100644
--- a/lib/netfilter/log_obj.c
+++ b/lib/netfilter/log_obj.c
@@ -12,7 +12,7 @@
  * Copyright (c) 2008 Patrick McHardy <kaber@trash.net>
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netfilter/nfnl.h>
 #include <netlink/netfilter/log.h>
 
@@ -56,7 +56,7 @@
 	nl_dump(p, "\n");
 }
 
-static struct trans_tbl copy_modes[] = {
+static const struct trans_tbl copy_modes[] = {
 	__ADD(NFNL_LOG_COPY_NONE,	none)
 	__ADD(NFNL_LOG_COPY_META,	meta)
 	__ADD(NFNL_LOG_COPY_PACKET,	packet)
@@ -214,7 +214,7 @@
 	log->log_flag_mask |= flags;
 }
 
-static struct trans_tbl log_flags[] = {
+static const struct trans_tbl log_flags[] = {
 	__ADD(NFNL_LOG_FLAG_SEQ,	seq)
 	__ADD(NFNL_LOG_FLAG_SEQ_GLOBAL,	seq_global)
 };
@@ -254,7 +254,7 @@
 	return diff;
 }
 
-static struct trans_tbl nfnl_log_attrs[] = {
+static const struct trans_tbl nfnl_log_attrs[] = {
 	__ADD(LOG_ATTR_GROUP,		group)
 	__ADD(LOG_ATTR_COPY_MODE,	copy_mode)
 	__ADD(LOG_ATTR_COPY_RANGE,	copy_range)
diff --git a/lib/netfilter/netfilter.c b/lib/netfilter/netfilter.c
index f88b355..0062994 100644
--- a/lib/netfilter/netfilter.c
+++ b/lib/netfilter/netfilter.c
@@ -9,11 +9,11 @@
  * Copyright (c) 2008 Patrick McHardy <kaber@trash.net>
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netfilter/netfilter.h>
 #include <linux/netfilter.h>
 
-static struct trans_tbl nfnl_verdicts[] = {
+static const struct trans_tbl nfnl_verdicts[] = {
 	__ADD(NF_DROP,		NF_DROP)
 	__ADD(NF_ACCEPT,	NF_ACCEPT)
 	__ADD(NF_STOLEN,	NF_STOLEN)
@@ -33,7 +33,7 @@
 	return __str2type(name, nfnl_verdicts, ARRAY_SIZE(nfnl_verdicts));
 }
 
-static struct trans_tbl nfnl_inet_hooks[] = {
+static const struct trans_tbl nfnl_inet_hooks[] = {
 	__ADD(NF_INET_PRE_ROUTING,	NF_INET_PREROUTING)
 	__ADD(NF_INET_LOCAL_IN,		NF_INET_LOCAL_IN)
 	__ADD(NF_INET_FORWARD,		NF_INET_FORWARD)
diff --git a/lib/netfilter/nfnl.c b/lib/netfilter/nfnl.c
index ddce4b9..f028a85 100644
--- a/lib/netfilter/nfnl.c
+++ b/lib/netfilter/nfnl.c
@@ -6,13 +6,13 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  * Copyright (c) 2007 Philip Craig <philipc@snapgear.com>
  * Copyright (c) 2007 Secure Computing Corporation
  */
 
 /**
- * @defgroup nfnl Netfilter Netlink
+ * @defgroup nfnl Netfilter Library (libnl-nf)
  *
  * @par Message Format
  * @code
@@ -61,7 +61,7 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/netfilter/nfnl.h>
 
diff --git a/lib/netfilter/queue.c b/lib/netfilter/queue.c
index ff1de0e..5655647 100644
--- a/lib/netfilter/queue.c
+++ b/lib/netfilter/queue.c
@@ -19,7 +19,7 @@
 #include <sys/types.h>
 #include <linux/netfilter/nfnetlink_queue.h>
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/netfilter/nfnl.h>
 #include <netlink/netfilter/queue.h>
diff --git a/lib/netfilter/queue_msg.c b/lib/netfilter/queue_msg.c
index ab0a58b..3388923 100644
--- a/lib/netfilter/queue_msg.c
+++ b/lib/netfilter/queue_msg.c
@@ -20,10 +20,11 @@
 #include <sys/types.h>
 #include <linux/netfilter/nfnetlink_queue.h>
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/netfilter/nfnl.h>
 #include <netlink/netfilter/queue_msg.h>
+#include <byteswap.h>
 
 static struct nl_cache_ops nfnl_queue_msg_ops;
 
@@ -35,7 +36,7 @@
 #elif __BYTE_ORDER == __LITTLE_ENDIAN
 static uint64_t ntohll(uint64_t x)
 {
-	return __bswap_64(x);
+	return bswap_64(x);
 }
 #endif
 
@@ -152,22 +153,23 @@
 	int err;
 
 	if ((err = nfnlmsg_queue_msg_parse(nlh, &msg)) < 0)
-		goto errout;
+		return err;
 
 	err = pp->pp_cb((struct nl_object *) msg, pp);
-errout:
 	nfnl_queue_msg_put(msg);
 	return err;
 }
 
 /** @} */
 
-struct nl_msg *nfnl_queue_msg_build_verdict(const struct nfnl_queue_msg *msg)
+static struct nl_msg *
+__nfnl_queue_msg_build_verdict(const struct nfnl_queue_msg *msg,
+							   uint8_t type)
 {
 	struct nl_msg *nlmsg;
 	struct nfqnl_msg_verdict_hdr verdict;
 
-	nlmsg = nfnlmsg_alloc_simple(NFNL_SUBSYS_QUEUE, NFQNL_MSG_VERDICT, 0,
+	nlmsg = nfnlmsg_alloc_simple(NFNL_SUBSYS_QUEUE, type, 0,
 				     nfnl_queue_msg_get_family(msg),
 				     nfnl_queue_msg_get_group(msg));
 	if (nlmsg == NULL)
@@ -190,6 +192,18 @@
 	return NULL;
 }
 
+struct nl_msg *
+nfnl_queue_msg_build_verdict(const struct nfnl_queue_msg *msg)
+{
+	return __nfnl_queue_msg_build_verdict(msg, NFQNL_MSG_VERDICT);
+}
+
+struct nl_msg *
+nfnl_queue_msg_build_verdict_batch(const struct nfnl_queue_msg *msg)
+{
+	return __nfnl_queue_msg_build_verdict(msg, NFQNL_MSG_VERDICT_BATCH);
+}
+
 /**
 * Send a message verdict/mark
 * @arg nlh            netlink messsage header
@@ -214,6 +228,29 @@
 }
 
 /**
+* Send a message batched verdict/mark
+* @arg nlh            netlink messsage header
+* @arg msg            queue msg
+* @return 0 on OK or error code
+*/
+int nfnl_queue_msg_send_verdict_batch(struct nl_sock *nlh,
+									  const struct nfnl_queue_msg *msg)
+{
+	struct nl_msg *nlmsg;
+	int err;
+
+	nlmsg = nfnl_queue_msg_build_verdict_batch(msg);
+	if (nlmsg == NULL)
+		return -NLE_NOMEM;
+
+	err = nl_send_auto_complete(nlh, nlmsg);
+	nlmsg_free(nlmsg);
+	if (err < 0)
+		return err;
+	return wait_for_ack(nlh);
+}
+
+/**
 * Send a message verdict including the payload
 * @arg nlh            netlink messsage header
 * @arg msg            queue msg
@@ -249,7 +286,7 @@
 	iov[2].iov_base = (void *) payload_data;
 	iov[2].iov_len = NLA_ALIGN(payload_len);
 
-	nl_auto_complete(nlh, nlmsg);
+	nl_complete_msg(nlh, nlmsg);
 	err = nl_send_iovec(nlh, nlmsg, iov, 3);
 
 	nlmsg_free(nlmsg);
diff --git a/lib/netfilter/queue_msg_obj.c b/lib/netfilter/queue_msg_obj.c
index 97813e8..8085f1b 100644
--- a/lib/netfilter/queue_msg_obj.c
+++ b/lib/netfilter/queue_msg_obj.c
@@ -9,7 +9,7 @@
  * Copyright (c) 2007, 2008 Patrick McHardy <kaber@trash.net>
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netfilter/nfnl.h>
 #include <netlink/netfilter/netfilter.h>
 #include <netlink/netfilter/queue_msg.h>
@@ -66,7 +66,7 @@
 	struct nl_cache *link_cache;
 	char buf[64];
 
-	link_cache = nl_cache_mngt_require("route/link");
+	link_cache = nl_cache_mngt_require_safe("route/link");
 
 	nl_new_line(p);
 
@@ -152,6 +152,9 @@
 					 buf, sizeof(buf)));
 
 	nl_dump(p, "\n");
+
+	if (link_cache)
+		nl_cache_put(link_cache);
 }
 
 /**
@@ -451,7 +454,7 @@
 	return msg->queue_msg_verdict;
 }
 
-static struct trans_tbl nfnl_queue_msg_attrs[] = {
+static const struct trans_tbl nfnl_queue_msg_attrs[] = {
 	__ADD(QUEUE_MSG_ATTR_GROUP,		group)
 	__ADD(QUEUE_MSG_ATTR_FAMILY,		family)
 	__ADD(QUEUE_MSG_ATTR_PACKETID,		packetid)
diff --git a/lib/netfilter/queue_obj.c b/lib/netfilter/queue_obj.c
index ee03836..5edcc68 100644
--- a/lib/netfilter/queue_obj.c
+++ b/lib/netfilter/queue_obj.c
@@ -16,7 +16,7 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netfilter/nfnl.h>
 #include <netlink/netfilter/queue.h>
 
@@ -52,7 +52,7 @@
 	nl_dump(p, "\n");
 }
 
-static struct trans_tbl copy_modes[] = {
+static const struct trans_tbl copy_modes[] = {
 	__ADD(NFNL_QUEUE_COPY_NONE,	none)
 	__ADD(NFNL_QUEUE_COPY_META,	meta)
 	__ADD(NFNL_QUEUE_COPY_PACKET,	packet)
@@ -184,7 +184,7 @@
 	return diff;
 }
 
-static struct trans_tbl nfnl_queue_attrs[] = {
+static const struct trans_tbl nfnl_queue_attrs[] = {
 	__ADD(QUEUE_ATTR_GROUP,		group)
 	__ADD(QUEUE_ATTR_MAXLEN,	maxlen)
 	__ADD(QUEUE_ATTR_COPY_MODE,	copy_mode)
diff --git a/lib/nl.c b/lib/nl.c
index c453b60..25fd59c 100644
--- a/lib/nl.c
+++ b/lib/nl.c
@@ -6,82 +6,27 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @defgroup core Core
+ * @defgroup core Core Library (libnl)
  *
- * @details
- * @par 1) Connecting the socket
- * @code
- * // Bind and connect the socket to a protocol, NETLINK_ROUTE in this example.
- * nl_connect(sk, NETLINK_ROUTE);
- * @endcode
+ * Socket handling, connection management, sending and receiving of data,
+ * message construction and parsing, object caching system, ...
  *
- * @par 2) Sending data
- * @code
- * // The most rudimentary method is to use nl_sendto() simply pushing
- * // a piece of data to the other netlink peer. This method is not
- * // recommended.
- * const char buf[] = { 0x01, 0x02, 0x03, 0x04 };
- * nl_sendto(sk, buf, sizeof(buf));
+ * This is the API reference of the core library. It is not meant as a guide
+ * but as a reference. Please refer to the core library guide for detailed
+ * documentation on the library architecture and examples:
  *
- * // A more comfortable interface is nl_send() taking a pointer to
- * // a netlink message.
- * struct nl_msg *msg = my_msg_builder();
- * nl_send(sk, nlmsg_hdr(msg));
+ * * @ref_asciidoc{core,_,Netlink Core Library Development Guide}
  *
- * // nl_sendmsg() provides additional control over the sendmsg() message
- * // header in order to allow more specific addressing of multiple peers etc.
- * struct msghdr hdr = { ... };
- * nl_sendmsg(sk, nlmsg_hdr(msg), &hdr);
  *
- * // You're probably too lazy to fill out the netlink pid, sequence number
- * // and message flags all the time. nl_send_auto_complete() automatically
- * // extends your message header as needed with an appropriate sequence
- * // number, the netlink pid stored in the netlink socket and the message
- * // flags NLM_F_REQUEST and NLM_F_ACK (if not disabled in the socket)
- * nl_send_auto_complete(sk, nlmsg_hdr(msg));
- *
- * // Simple protocols don't require the complex message construction interface
- * // and may favour nl_send_simple() to easly send a bunch of payload
- * // encapsulated in a netlink message header.
- * nl_send_simple(sk, MY_MSG_TYPE, 0, buf, sizeof(buf));
- * @endcode
- *
- * @par 3) Receiving data
- * @code
- * // nl_recv() receives a single message allocating a buffer for the message
- * // content and gives back the pointer to you.
- * struct sockaddr_nl peer;
- * unsigned char *msg;
- * nl_recv(sk, &peer, &msg);
- *
- * // nl_recvmsgs() receives a bunch of messages until the callback system
- * // orders it to state, usually after receving a compolete multi part
- * // message series.
- * nl_recvmsgs(sk, my_callback_configuration);
- *
- * // nl_recvmsgs_default() acts just like nl_recvmsg() but uses the callback
- * // configuration stored in the socket.
- * nl_recvmsgs_default(sk);
- *
- * // In case you want to wait for the ACK to be recieved that you requested
- * // with your latest message, you can call nl_wait_for_ack()
- * nl_wait_for_ack(sk);
- * @endcode
- *
- * @par 4) Closing
- * @code
- * // Close the socket first to release kernel memory
- * nl_close(sk);
- * @endcode
- * 
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/socket.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/handlers.h>
@@ -89,28 +34,83 @@
 #include <netlink/attr.h>
 
 /**
+ * @defgroup core_types Data Types
+ *
+ * Core library data types
+ * @{
+ * @}
+ *
+ * @defgroup send_recv Send & Receive Data
+ *
+ * Connection management, sending & receiving of data
+ *
+ * Related sections in the development guide:
+ * - @core_doc{core_send_recv, Sending & Receiving}
+ * - @core_doc{core_sockets, Sockets}
+ *
+ * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/netlink.h>
+ * ~~~~
+ */
+
+/**
  * @name Connection Management
  * @{
  */
 
 /**
- * Create and connect netlink socket.
- * @arg sk		Netlink socket.
- * @arg protocol	Netlink protocol to use.
+ * Create file descriptor and bind socket.
+ * @arg sk		Netlink socket (required)
+ * @arg protocol	Netlink protocol to use (required)
  *
- * Creates a netlink socket using the specified protocol, binds the socket
- * and issues a connection attempt.
+ * Creates a new Netlink socket using `socket()` and binds the socket to the
+ * protocol and local port specified in the `sk` socket object. Fails if
+ * the socket is already connected.
+ *
+ * @note If available, the `close-on-exec` (`SOCK_CLOEXEC`) feature is enabled
+ *       automatically on the new file descriptor. This causes the socket to
+ *       be closed automatically if any of the `exec` family functions succeed.
+ *       This is essential for multi threaded programs.
+ *
+ * @note The local port (`nl_socket_get_local_port()`) is unspecified after
+ *       creating a new socket. It only gets determined when accessing the
+ *       port the first time or during `nl_connect()`. When nl_connect()
+ *       fails during `bind()` due to `ADDRINUSE`, it will retry with
+ *       different ports if the port is unspecified. Unless you want to enforce
+ *       the use of a specific local port, don't access the local port (or
+ *       reset it to `unspecified` by calling `nl_socket_set_local_port(sk, 0)`).
+ *       This capability is indicated by
+ *       `%NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE`.
+ *
+ * @see nl_socket_alloc()
+ * @see nl_close()
  *
  * @return 0 on success or a negative error code.
+ *
+ * @retval -NLE_BAD_SOCK Socket is already connected
  */
 int nl_connect(struct nl_sock *sk, int protocol)
 {
-	int err;
+	int err, flags = 0;
+	int errsv;
 	socklen_t addrlen;
 
-	sk->s_fd = socket(AF_NETLINK, SOCK_RAW, protocol);
+#ifdef SOCK_CLOEXEC
+	flags |= SOCK_CLOEXEC;
+#endif
+
+        if (sk->s_fd != -1)
+                return -NLE_BAD_SOCK;
+
+	sk->s_fd = socket(AF_NETLINK, SOCK_RAW | flags, protocol);
 	if (sk->s_fd < 0) {
-		err = -nl_syserr2nlerr(errno);
+		errsv = errno;
+		NL_DBG(4, "nl_connect(%p): socket() failed with %d\n", sk, errsv);
+		err = -nl_syserr2nlerr(errsv);
 		goto errout;
 	}
 
@@ -120,11 +120,45 @@
 			goto errout;
 	}
 
-	err = bind(sk->s_fd, (struct sockaddr*) &sk->s_local,
-		   sizeof(sk->s_local));
-	if (err < 0) {
-		err = -nl_syserr2nlerr(errno);
-		goto errout;
+	if (_nl_socket_is_local_port_unspecified (sk)) {
+		uint32_t port;
+		uint32_t used_ports[32] = { 0 };
+
+		while (1) {
+			port = _nl_socket_generate_local_port_no_release(sk);
+
+			if (port == UINT32_MAX) {
+				NL_DBG(4, "nl_connect(%p): no more unused local ports.\n", sk);
+				_nl_socket_used_ports_release_all(used_ports);
+				err = -NLE_EXIST;
+				goto errout;
+			}
+			err = bind(sk->s_fd, (struct sockaddr*) &sk->s_local,
+				   sizeof(sk->s_local));
+			if (err == 0)
+				break;
+
+			errsv = errno;
+			if (errsv == EADDRINUSE) {
+				NL_DBG(4, "nl_connect(%p): local port %u already in use. Retry.\n", sk, (unsigned) port);
+				_nl_socket_used_ports_set(used_ports, port);
+			} else {
+				NL_DBG(4, "nl_connect(%p): bind() for port %u failed with %d\n", sk, (unsigned) port, errsv);
+				_nl_socket_used_ports_release_all(used_ports);
+				err = -nl_syserr2nlerr(errsv);
+				goto errout;
+			}
+		}
+		_nl_socket_used_ports_release_all(used_ports);
+	} else {
+		err = bind(sk->s_fd, (struct sockaddr*) &sk->s_local,
+			   sizeof(sk->s_local));
+		if (err != 0) {
+			errsv = errno;
+			NL_DBG(4, "nl_connect(%p): bind() failed with %d\n", sk, errsv);
+			err = -nl_syserr2nlerr(errsv);
+			goto errout;
+		}
 	}
 
 	addrlen = sizeof(sk->s_local);
@@ -149,15 +183,24 @@
 
 	return 0;
 errout:
-	close(sk->s_fd);
-	sk->s_fd = -1;
+        if (sk->s_fd != -1) {
+    		close(sk->s_fd);
+    		sk->s_fd = -1;
+        }
 
 	return err;
 }
 
 /**
- * Close/Disconnect netlink socket.
- * @arg sk		Netlink socket.
+ * Close Netlink socket
+ * @arg sk		Netlink socket (required)
+ *
+ * Closes the Netlink socket using `close()`.
+ *
+ * @note The socket is closed automatically if a `struct nl_sock` object is
+ *       freed using `nl_socket_free()`.
+ *
+ * @see nl_connect()
  */
 void nl_close(struct nl_sock *sk)
 {
@@ -177,16 +220,38 @@
  */
 
 /**
- * Send raw data over netlink socket.
- * @arg sk		Netlink socket.
- * @arg buf		Data buffer.
- * @arg size		Size of data buffer.
- * @return Number of characters written on success or a negative error code.
+ * Transmit raw data over Netlink socket.
+ * @arg sk		Netlink socket (required)
+ * @arg buf		Buffer carrying data to send (required)
+ * @arg size		Size of buffer (required)
+ *
+ * Transmits "raw" data over the specified Netlink socket. Unlike the other
+ * transmit functions it does not modify the data in any way. It directly
+ * passes the buffer \c buf of \c size to sendto().
+ *
+ * The message is addressed to the peer as specified in the socket by either
+ * the nl_socket_set_peer_port() or nl_socket_set_peer_groups() function.
+ *
+ * @note Because there is no indication on the message boundaries of the data
+ *       being sent, the \c NL_CB_MSG_OUT callback handler will not be invoked
+ *       for data that is being sent using this function.
+ *
+ * @see nl_socket_set_peer_port()
+ * @see nl_socket_set_peer_groups()
+ * @see nl_sendmsg()
+ *
+ * @return Number of bytes sent or a negative error code.
  */
 int nl_sendto(struct nl_sock *sk, void *buf, size_t size)
 {
 	int ret;
 
+	if (!buf)
+		return -NLE_INVAL;
+
+	if (sk->s_fd < 0)
+		return -NLE_BAD_SOCK;
+
 	ret = sendto(sk->s_fd, buf, size, 0, (struct sockaddr *)
 		     &sk->s_peer, sizeof(sk->s_peer));
 	if (ret < 0)
@@ -196,23 +261,55 @@
 }
 
 /**
- * Send netlink message with control over sendmsg() message header.
- * @arg sk		Netlink socket.
- * @arg msg		Netlink message to be sent.
- * @arg hdr		Sendmsg() message header.
- * @return Number of characters sent on sucess or a negative error code.
+ * Transmit Netlink message using sendmsg()
+ * @arg sk		Netlink socket (required)
+ * @arg msg		Netlink message to be sent (required)
+ * @arg hdr		sendmsg() message header (required)
+ *
+ * Transmits the message specified in \c hdr over the Netlink socket using the
+ * sendmsg() system call.
+ *
+ * @attention
+ * The `msg` argument will *not* be used to derive the message payload that
+ * is being sent out. The `msg` argument is *only* passed on to the
+ * `NL_CB_MSG_OUT` callback. The caller is responsible to initialize the
+ * `hdr` struct properly and have it point to the message payload and
+ * socket address.
+ *
+ * @note
+ * This function uses `nlmsg_set_src()` to modify the `msg` argument prior to
+ * invoking the `NL_CB_MSG_OUT` callback to provide the local port number.
+ *
+ * @callback This function triggers the `NL_CB_MSG_OUT` callback.
+ *
+ * @attention
+ * Think twice before using this function. It provides a low level access to
+ * the Netlink socket. Among other limitations, it does not add credentials
+ * even if enabled or respect the destination address specified in the `msg`
+ * object.
+ *
+ * @see nl_socket_set_local_port()
+ * @see nl_send_auto()
+ * @see nl_send_iovec()
+ *
+ * @return Number of bytes sent on success or a negative error code.
+ *
+ * @lowlevel
  */
 int nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
 {
 	struct nl_cb *cb;
 	int ret;
 
+	if (sk->s_fd < 0)
+		return -NLE_BAD_SOCK;
+
 	nlmsg_set_src(msg, &sk->s_local);
 
 	cb = sk->s_cb;
 	if (cb->cb_set[NL_CB_MSG_OUT])
-		if (nl_cb_call(cb, NL_CB_MSG_OUT, msg) != NL_OK)
-			return 0;
+		if ((ret = nl_cb_call(cb, NL_CB_MSG_OUT, msg)) != NL_OK)
+			return ret;
 
 	ret = sendmsg(sk->s_fd, hdr, 0);
 	if (ret < 0)
@@ -224,13 +321,23 @@
 
 
 /**
- * Send netlink message.
- * @arg sk		Netlink socket.
- * @arg msg		Netlink message to be sent.
- * @arg iov		iovec to be sent.
- * @arg iovlen		number of struct iovec to be sent.
- * @see nl_sendmsg()
- * @return Number of characters sent on success or a negative error code.
+ * Transmit Netlink message (taking IO vector)
+ * @arg sk		Netlink socket (required)
+ * @arg msg		Netlink message to be sent (required)
+ * @arg iov		IO vector to be sent (required)
+ * @arg iovlen		Number of struct iovec to be sent (required)
+ *
+ * This function is identical to nl_send() except that instead of taking a
+ * `struct nl_msg` object it takes an IO vector. Please see the description
+ * of `nl_send()`.
+ *
+ * @callback This function triggers the `NL_CB_MSG_OUT` callback.
+ *
+ * @see nl_send()
+ *
+ * @return Number of bytes sent on success or a negative error code.
+ *
+ * @lowlevel
  */
 int nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen)
 {
@@ -269,34 +376,86 @@
 	return nl_sendmsg(sk, msg, &hdr);
 }
 
-
-
 /**
-* Send netlink message.
-* @arg sk		Netlink socket.
-* @arg msg		Netlink message to be sent.
-* @see nl_sendmsg()
-* @return Number of characters sent on success or a negative error code.
+ * Transmit Netlink message
+ * @arg sk		Netlink socket (required)
+ * @arg msg		Netlink message (required)
+ *
+ * Transmits the Netlink message `msg` over the Netlink socket using the
+ * `sendmsg()` system call. This function is based on `nl_send_iovec()` but
+ * takes care of initializing a `struct iovec` based on the `msg` object.
+ *
+ * The message is addressed to the peer as specified in the socket by either
+ * the nl_socket_set_peer_port() or nl_socket_set_peer_groups() function.
+ * The peer address can be overwritten by specifying an address in the `msg`
+ * object using nlmsg_set_dst().
+ *
+ * If present in the `msg`, credentials set by the nlmsg_set_creds() function
+ * are added to the control buffer of the message.
+ *
+ * @par Overwriting Capability:
+ * Calls to this function can be overwritten by providing an alternative using
+ * the nl_cb_overwrite_send() function.
+ *
+ * @callback This function triggers the `NL_CB_MSG_OUT` callback.
+ *
+ * @attention
+ * Unlike `nl_send_auto()`, this function does *not* finalize the message in
+ * terms of automatically adding needed flags or filling out port numbers.
+ *
+ * @see nl_send_auto()
+ * @see nl_send_iovec()
+ * @see nl_socket_set_peer_port()
+ * @see nl_socket_set_peer_groups()
+ * @see nlmsg_set_dst()
+ * @see nlmsg_set_creds()
+ * @see nl_cb_overwrite_send()
+ *
+ * @return Number of bytes sent on success or a negative error code.
 */
 int nl_send(struct nl_sock *sk, struct nl_msg *msg)
 {
-	struct iovec iov = {
-		.iov_base = (void *) nlmsg_hdr(msg),
-		.iov_len = nlmsg_hdr(msg)->nlmsg_len,
-	};
+	struct nl_cb *cb = sk->s_cb;
 
-	return nl_send_iovec(sk, msg, &iov, 1);
+	if (cb->cb_send_ow)
+		return cb->cb_send_ow(sk, msg);
+	else {
+		struct iovec iov = {
+			.iov_base = (void *) nlmsg_hdr(msg),
+			.iov_len = nlmsg_hdr(msg)->nlmsg_len,
+		};
+
+		return nl_send_iovec(sk, msg, &iov, 1);
+	}
 }
 
-void nl_auto_complete(struct nl_sock *sk, struct nl_msg *msg)
+/**
+ * Finalize Netlink message
+ * @arg sk		Netlink socket (required)
+ * @arg msg		Netlink message (required)
+ *
+ * This function finalizes a Netlink message by completing the message with
+ * desirable flags and values depending on the socket configuration.
+ *
+ *  - If not yet filled out, the source address of the message (`nlmsg_pid`)
+ *    will be set to the local port number of the socket.
+ *  - If not yet specified, the next available sequence number is assigned
+ *    to the message (`nlmsg_seq`).
+ *  - If not yet specified, the protocol field of the message will be set to
+ *    the protocol field of the socket.
+ *  - The `NLM_F_REQUEST` Netlink message flag will be set.
+ *  - The `NLM_F_ACK` flag will be set if Auto-ACK mode is enabled on the
+ *    socket.
+ */
+void nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg)
 {
 	struct nlmsghdr *nlh;
 
 	nlh = nlmsg_hdr(msg);
-	if (nlh->nlmsg_pid == 0)
-		nlh->nlmsg_pid = sk->s_local.nl_pid;
+	if (nlh->nlmsg_pid == NL_AUTO_PORT)
+		nlh->nlmsg_pid = nl_socket_get_local_port(sk);
 
-	if (nlh->nlmsg_seq == 0)
+	if (nlh->nlmsg_seq == NL_AUTO_SEQ)
 		nlh->nlmsg_seq = sk->s_seq_next++;
 
 	if (msg->nm_protocol == -1)
@@ -309,42 +468,83 @@
 }
 
 /**
- * Send netlink message and check & extend header values as needed.
- * @arg sk		Netlink socket.
- * @arg msg		Netlink message to be sent.
+ * Finalize and transmit Netlink message
+ * @arg sk		Netlink socket (required)
+ * @arg msg		Netlink message (required)
  *
- * Checks the netlink message \c nlh for completness and extends it
- * as required before sending it out. Checked fields include pid,
- * sequence nr, and flags.
+ * Finalizes the message by passing it to `nl_complete_msg()` and transmits it
+ * by passing it to `nl_send()`.
  *
+ * @callback This function triggers the `NL_CB_MSG_OUT` callback.
+ *
+ * @see nl_complete_msg()
  * @see nl_send()
- * @return Number of characters sent or a negative error code.
+ *
+ * @return Number of bytes sent or a negative error code.
  */
-int nl_send_auto_complete(struct nl_sock *sk, struct nl_msg *msg)
+int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg)
 {
-	struct nl_cb *cb = sk->s_cb;
+	nl_complete_msg(sk, msg);
 
-	nl_auto_complete(sk, msg);
-
-	if (cb->cb_send_ow)
-		return cb->cb_send_ow(sk, msg);
-	else
-		return nl_send(sk, msg);
+	return nl_send(sk, msg);
 }
 
 /**
- * Send simple netlink message using nl_send_auto_complete()
- * @arg sk		Netlink socket.
- * @arg type		Netlink message type.
- * @arg flags		Netlink message flags.
- * @arg buf		Data buffer.
- * @arg size		Size of data buffer.
+ * Finalize and transmit Netlink message and wait for ACK or error message
+ * @arg sk		Netlink socket (required)
+ * @arg msg		Netlink message (required)
  *
- * Builds a netlink message with the specified type and flags and
- * appends the specified data as payload to the message.
+ * Passes the `msg` to `nl_send_auto()` to finalize and transmit it. Frees the
+ * message and waits (sleeps) for the ACK or error message to be received.
  *
- * @see nl_send_auto_complete()
+ * @attention
+ * Disabling Auto-ACK (nl_socket_disable_auto_ack()) will cause this function
+ * to return immediately after transmitting the message. However, the peer may
+ * still be returning an error message in response to the request. It is the
+ * responsibility of the caller to handle such messages.
+ *
+ * @callback This function triggers the `NL_CB_MSG_OUT` callback.
+ *
+ * @attention
+ * This function frees the `msg` object after transmitting it by calling
+ * `nlmsg_free()`.
+ *
+ * @see nl_send_auto().
+ * @see nl_wait_for_ack()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int nl_send_sync(struct nl_sock *sk, struct nl_msg *msg)
+{
+	int err;
+
+	err = nl_send_auto(sk, msg);
+	nlmsg_free(msg);
+	if (err < 0)
+		return err;
+
+	return wait_for_ack(sk);
+}
+
+/**
+ * Construct and transmit a Netlink message
+ * @arg sk		Netlink socket (required)
+ * @arg type		Netlink message type (required)
+ * @arg flags		Netlink message flags (optional)
+ * @arg buf		Data buffer (optional)
+ * @arg size		Size of data buffer (optional)
+ *
+ * Allocates a new Netlink message based on `type` and `flags`. If `buf`
+ * points to payload of length `size` that payload will be appended to the
+ * message.
+ *
+ * Sends out the message using `nl_send_auto()` and frees the message
+ * afterwards.
+ *
+ * @see nl_send_auto()
+ *
  * @return Number of characters sent on success or a negative error code.
+ * @retval -NLE_NOMEM Unable to allocate Netlink message
  */
 int nl_send_simple(struct nl_sock *sk, int type, int flags, void *buf,
 		   size_t size)
@@ -361,9 +561,8 @@
 		if (err < 0)
 			goto errout;
 	}
-	
 
-	err = nl_send_auto_complete(sk, msg);
+	err = nl_send_auto(sk, msg);
 errout:
 	nlmsg_free(msg);
 
@@ -379,27 +578,54 @@
 
 /**
  * Receive data from netlink socket
- * @arg sk		Netlink socket.
- * @arg nla		Destination pointer for peer's netlink address.
- * @arg buf		Destination pointer for message content.
- * @arg creds		Destination pointer for credentials.
+ * @arg sk		Netlink socket (required)
+ * @arg nla		Netlink socket structure to hold address of peer (required)
+ * @arg buf		Destination pointer for message content (required)
+ * @arg creds		Destination pointer for credentials (optional)
  *
- * Receives a netlink message, allocates a buffer in \c *buf and
- * stores the message content. The peer's netlink address is stored
- * in \c *nla. The caller is responsible for freeing the buffer allocated
- * in \c *buf if a positive value is returned.  Interruped system calls
- * are handled by repeating the read. The input buffer size is determined
- * by peeking before the actual read is done.
+ * Receives data from a connected netlink socket using recvmsg() and returns
+ * the number of bytes read. The read data is stored in a newly allocated
+ * buffer that is assigned to \c *buf. The peer's netlink address will be
+ * stored in \c *nla.
  *
- * A non-blocking sockets causes the function to return immediately with
- * a return value of 0 if no data is available.
+ * This function blocks until data is available to be read unless the socket
+ * has been put into non-blocking mode using nl_socket_set_nonblocking() in
+ * which case this function will return immediately with a return value of 0.
  *
- * @return Number of octets read, 0 on EOF or a negative error code.
+ * The buffer size used when reading from the netlink socket and thus limiting
+ * the maximum size of a netlink message that can be read defaults to the size
+ * of a memory page (getpagesize()). The buffer size can be modified on a per
+ * socket level using the function nl_socket_set_msg_buf_size().
+ *
+ * If message peeking is enabled using nl_socket_enable_msg_peek() the size of
+ * the message to be read will be determined using the MSG_PEEK flag prior to
+ * performing the actual read. This leads to an additional recvmsg() call for
+ * every read operation which has performance implications and is not
+ * recommended for high throughput protocols.
+ *
+ * An eventual interruption of the recvmsg() system call is automatically
+ * handled by retrying the operation.
+ *
+ * If receiving of credentials has been enabled using the function
+ * nl_socket_set_passcred(), this function will allocate a new struct ucred
+ * filled with the received credentials and assign it to \c *creds. The caller
+ * is responsible for freeing the buffer.
+ *
+ * @note The caller is responsible to free the returned data buffer and if
+ *       enabled, the credentials buffer.
+ *
+ * @see nl_socket_set_nonblocking()
+ * @see nl_socket_set_msg_buf_size()
+ * @see nl_socket_enable_msg_peek()
+ * @see nl_socket_set_passcred()
+ *
+ * @return Number of bytes read, 0 on EOF, 0 on no data event (non-blocking
+ *         mode), or a negative error code.
  */
 int nl_recv(struct nl_sock *sk, struct sockaddr_nl *nla,
 	    unsigned char **buf, struct ucred **creds)
 {
-	int n;
+	ssize_t n;
 	int flags = 0;
 	static int page_size = 0;
 	struct iovec iov;
@@ -408,85 +634,127 @@
 		.msg_namelen = sizeof(struct sockaddr_nl),
 		.msg_iov = &iov,
 		.msg_iovlen = 1,
-		.msg_control = NULL,
-		.msg_controllen = 0,
-		.msg_flags = 0,
 	};
-	struct cmsghdr *cmsg;
+	struct ucred* tmpcreds = NULL;
+	int retval = 0;
+
+	if (!buf || !nla)
+		return -NLE_INVAL;
 
 	if (sk->s_flags & NL_MSG_PEEK)
-		flags |= MSG_PEEK;
+		flags |= MSG_PEEK | MSG_TRUNC;
 
 	if (page_size == 0)
-		page_size = getpagesize();
+		page_size = getpagesize() * 4;
 
-	iov.iov_len = page_size;
-	iov.iov_base = *buf = malloc(iov.iov_len);
+	iov.iov_len = sk->s_bufsize ? : page_size;
+	iov.iov_base = malloc(iov.iov_len);
 
-	if (sk->s_flags & NL_SOCK_PASSCRED) {
+	if (!iov.iov_base) {
+		retval = -NLE_NOMEM;
+		goto abort;
+	}
+
+	if (creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
 		msg.msg_controllen = CMSG_SPACE(sizeof(struct ucred));
-		msg.msg_control = calloc(1, msg.msg_controllen);
+		msg.msg_control = malloc(msg.msg_controllen);
+		if (!msg.msg_control) {
+			retval = -NLE_NOMEM;
+			goto abort;
+		}
 	}
 retry:
 
 	n = recvmsg(sk->s_fd, &msg, flags);
-	if (!n)
+	if (!n) {
+		retval = 0;
 		goto abort;
-	else if (n < 0) {
+	}
+	if (n < 0) {
 		if (errno == EINTR) {
 			NL_DBG(3, "recvmsg() returned EINTR, retrying\n");
 			goto retry;
-		} else if (errno == EAGAIN) {
-			NL_DBG(3, "recvmsg() returned EAGAIN, aborting\n");
-			goto abort;
-		} else {
-			free(msg.msg_control);
-			free(*buf);
-			return -nl_syserr2nlerr(errno);
 		}
+		retval = -nl_syserr2nlerr(errno);
+		goto abort;
 	}
 
-	if (iov.iov_len < n ||
-	    msg.msg_flags & MSG_TRUNC) {
-		/* Provided buffer is not long enough, enlarge it
-		 * and try again. */
-		iov.iov_len *= 2;
-		iov.iov_base = *buf = realloc(*buf, iov.iov_len);
-		goto retry;
-	} else if (msg.msg_flags & MSG_CTRUNC) {
+	if (msg.msg_flags & MSG_CTRUNC) {
+		void *tmp;
 		msg.msg_controllen *= 2;
-		msg.msg_control = realloc(msg.msg_control, msg.msg_controllen);
+		tmp = realloc(msg.msg_control, msg.msg_controllen);
+		if (!tmp) {
+			retval = -NLE_NOMEM;
+			goto abort;
+		}
+		msg.msg_control = tmp;
 		goto retry;
-	} else if (flags != 0) {
+	}
+
+	if (iov.iov_len < n || (msg.msg_flags & MSG_TRUNC)) {
+		void *tmp;
+		/* Provided buffer is not long enough, enlarge it
+		 * to size of n (which should be total length of the message)
+		 * and try again. */
+		iov.iov_len = n;
+		tmp = realloc(iov.iov_base, iov.iov_len);
+		if (!tmp) {
+			retval = -NLE_NOMEM;
+			goto abort;
+		}
+		iov.iov_base = tmp;
+		flags = 0;
+		goto retry;
+	}
+
+        if (flags != 0) {
 		/* Buffer is big enough, do the actual reading */
 		flags = 0;
 		goto retry;
 	}
 
 	if (msg.msg_namelen != sizeof(struct sockaddr_nl)) {
-		free(msg.msg_control);
-		free(*buf);
-		return -NLE_NOADDR;
+		retval =  -NLE_NOADDR;
+		goto abort;
 	}
 
-	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
-		if (cmsg->cmsg_level == SOL_SOCKET &&
-		    cmsg->cmsg_type == SCM_CREDENTIALS) {
-			*creds = calloc(1, sizeof(struct ucred));
-			memcpy(*creds, CMSG_DATA(cmsg), sizeof(struct ucred));
+	if (creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
+		struct cmsghdr *cmsg;
+
+		for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+			if (cmsg->cmsg_level != SOL_SOCKET)
+				continue;
+			if (cmsg->cmsg_type != SCM_CREDENTIALS)
+				continue;
+			tmpcreds = malloc(sizeof(*tmpcreds));
+			if (!tmpcreds) {
+				retval = -NLE_NOMEM;
+				goto abort;
+			}
+			memcpy(tmpcreds, CMSG_DATA(cmsg), sizeof(*tmpcreds));
 			break;
 		}
 	}
 
-	free(msg.msg_control);
-	return n;
-
+	retval = n;
 abort:
 	free(msg.msg_control);
-	free(*buf);
-	return 0;
+
+	if (retval <= 0) {
+		free(iov.iov_base);
+		iov.iov_base = NULL;
+		free(tmpcreds);
+		tmpcreds = NULL;
+	} else
+		*buf = iov.iov_base;
+
+	if (creds)
+		*creds = tmpcreds;
+
+	return retval;
 }
 
+/** @cond SKIP */
 #define NL_CB_CALL(cb, type, msg) \
 do { \
 	err = nl_cb_call(cb, type, msg); \
@@ -502,12 +770,19 @@
 		goto out; \
 	} \
 } while (0)
+/** @endcond */
 
 static int recvmsgs(struct nl_sock *sk, struct nl_cb *cb)
 {
-	int n, err = 0, multipart = 0;
+	int n, err = 0, multipart = 0, interrupted = 0, nrecv = 0;
 	unsigned char *buf = NULL;
 	struct nlmsghdr *hdr;
+
+	/*
+	nla is passed on to not only to nl_recv() but may also be passed
+	to a function pointer provided by the caller which may or may not
+	initialize the variable. Thomas Graf.
+	*/
 	struct sockaddr_nl nla = {0};
 	struct nl_msg *msg = NULL;
 	struct ucred *creds = NULL;
@@ -526,7 +801,7 @@
 
 	hdr = (struct nlmsghdr *) buf;
 	while (nlmsg_ok(hdr, n)) {
-		NL_DBG(3, "recgmsgs(%p): Processing valid message...\n", sk);
+		NL_DBG(3, "recvmsgs(%p): Processing valid message...\n", sk);
 
 		nlmsg_free(msg);
 		msg = nlmsg_convert(hdr);
@@ -540,6 +815,8 @@
 		if (creds)
 			nlmsg_set_creds(msg, creds);
 
+		nrecv++;
+
 		/* Raw callback is the first, it gives the most control
 		 * to the user and he can do his very own parsing. */
 		if (cb->cb_set[NL_CB_MSG_IN])
@@ -548,14 +825,18 @@
 		/* Sequence number checking. The check may be done by
 		 * the user, otherwise a very simple check is applied
 		 * enforcing strict ordering */
-		if (cb->cb_set[NL_CB_SEQ_CHECK])
+		if (cb->cb_set[NL_CB_SEQ_CHECK]) {
 			NL_CB_CALL(cb, NL_CB_SEQ_CHECK, msg);
-		else if (hdr->nlmsg_seq != sk->s_seq_expect) {
-			if (cb->cb_set[NL_CB_INVALID])
-				NL_CB_CALL(cb, NL_CB_INVALID, msg);
-			else {
-				err = -NLE_SEQ_MISMATCH;
-				goto out;
+
+		/* Only do sequence checking if auto-ack mode is enabled */
+		} else if (!(sk->s_flags & NL_NO_AUTO_ACK)) {
+			if (hdr->nlmsg_seq != sk->s_seq_expect) {
+				if (cb->cb_set[NL_CB_INVALID])
+					NL_CB_CALL(cb, NL_CB_INVALID, msg);
+				else {
+					err = -NLE_SEQ_MISMATCH;
+					goto out;
+				}
 			}
 		}
 
@@ -573,6 +854,19 @@
 
 		if (hdr->nlmsg_flags & NLM_F_MULTI)
 			multipart = 1;
+
+		if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
+			if (cb->cb_set[NL_CB_DUMP_INTR])
+				NL_CB_CALL(cb, NL_CB_DUMP_INTR, msg);
+			else {
+				/*
+				 * We have to continue reading to clear
+				 * all messages until a NLMSG_DONE is
+				 * received and report the inconsistency.
+				 */
+				interrupted = 1;
+			}
+		}
 	
 		/* Other side wishes to see an ack for this message */
 		if (hdr->nlmsg_flags & NLM_F_ACK) {
@@ -583,7 +877,7 @@
 			}
 		}
 
-		/* messages terminates a multpart message, this is
+		/* messages terminates a multipart message, this is
 		 * usually the end of a message and therefore we slip
 		 * out of the loop by default. the user may overrule
 		 * this action by skipping this packet. */
@@ -620,7 +914,7 @@
 		else if (hdr->nlmsg_type == NLMSG_ERROR) {
 			struct nlmsgerr *e = nlmsg_data(hdr);
 
-			if (hdr->nlmsg_len < nlmsg_msg_size(sizeof(*e))) {
+			if (hdr->nlmsg_len < 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
@@ -680,10 +974,36 @@
 	free(buf);
 	free(creds);
 
+	if (interrupted)
+		err = -NLE_DUMP_INTR;
+
+	if (!err)
+		err = nrecv;
+
 	return err;
 }
 
 /**
+ * Receive a set of messages from a netlink socket and report parsed messages
+ * @arg sk		Netlink socket.
+ * @arg cb		set of callbacks to control behaviour.
+ *
+ * This function is identical to nl_recvmsgs() to the point that it will
+ * return the number of parsed messages instead of 0 on success.
+ *
+ * @see nl_recvmsgs()
+ *
+ * @return Number of received messages or a negative error code from nl_recv().
+ */
+int nl_recvmsgs_report(struct nl_sock *sk, struct nl_cb *cb)
+{
+	if (cb->cb_recvmsgs_ow)
+		return cb->cb_recvmsgs_ow(sk, cb);
+	else
+		return recvmsgs(sk, cb);
+}
+
+/**
  * Receive a set of messages from a netlink socket.
  * @arg sk		Netlink socket.
  * @arg cb		set of callbacks to control behaviour.
@@ -696,14 +1016,18 @@
  * A non-blocking sockets causes the function to return immediately if
  * no data is available.
  *
+ * @see nl_recvmsgs_report()
+ *
  * @return 0 on success or a negative error code from nl_recv().
  */
 int nl_recvmsgs(struct nl_sock *sk, struct nl_cb *cb)
 {
-	if (cb->cb_recvmsgs_ow)
-		return cb->cb_recvmsgs_ow(sk, cb);
-	else
-		return recvmsgs(sk, cb);
+	int err;
+
+	if ((err = nl_recvmsgs_report(sk, cb)) > 0)
+		err = 0;
+
+	return err;
 }
 
 /**
@@ -747,6 +1071,102 @@
 	return err;
 }
 
+/** @cond SKIP */
+struct pickup_param
+{
+	int (*parser)(struct nl_cache_ops *, struct sockaddr_nl *,
+		      struct nlmsghdr *, struct nl_parser_param *);
+	struct nl_object *result;
+};
+
+static int __store_answer(struct nl_object *obj, struct nl_parser_param *p)
+{
+	struct pickup_param *pp = p->pp_arg;
+	/*
+	 * the parser will put() the object at the end, expecting the cache
+	 * to take the reference.
+	 */
+	nl_object_get(obj);
+	pp->result =  obj;
+
+	return 0;
+}
+
+static int __pickup_answer(struct nl_msg *msg, void *arg)
+{
+	struct pickup_param *pp = arg;
+	struct nl_parser_param parse_arg = {
+		.pp_cb = __store_answer,
+		.pp_arg = pp,
+	};
+
+	return pp->parser(NULL, &msg->nm_src, msg->nm_nlh, &parse_arg);
+}
+
+/** @endcond */
+
+/**
+ * Pickup netlink answer, parse is and return object
+ * @arg sk		Netlink socket
+ * @arg parser		Parser function to parse answer
+ * @arg result		Result pointer to return parsed object
+ *
+ * @return 0 on success or a negative error code.
+ */
+int nl_pickup(struct nl_sock *sk,
+	      int (*parser)(struct nl_cache_ops *, struct sockaddr_nl *,
+			    struct nlmsghdr *, struct nl_parser_param *),
+	      struct nl_object **result)
+{
+	struct nl_cb *cb;
+	int err;
+	struct pickup_param pp = {
+		.parser = parser,
+	};
+
+	cb = nl_cb_clone(sk->s_cb);
+	if (cb == NULL)
+		return -NLE_NOMEM;
+
+	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, __pickup_answer, &pp);
+
+	err = nl_recvmsgs(sk, cb);
+	if (err < 0)
+		goto errout;
+
+	*result = pp.result;
+errout:
+	nl_cb_put(cb);
+
+	return err;
+}
+
+/** @} */
+
+/**
+ * @name Deprecated
+ * @{
+ */
+
+/**
+ * @deprecated Please use nl_complete_msg()
+ */
+void nl_auto_complete(struct nl_sock *sk, struct nl_msg *msg)
+{
+	nl_complete_msg(sk, msg);
+}
+
+/**
+ * @deprecated Please use nl_send_auto()
+ */
+int nl_send_auto_complete(struct nl_sock *sk, struct nl_msg *msg)
+{
+	return nl_send_auto(sk, msg);
+}
+
+
+/** @} */
+
 /** @} */
 
 /** @} */
diff --git a/lib/object.c b/lib/object.c
index d881ac9..52bc873 100644
--- a/lib/object.c
+++ b/lib/object.c
@@ -6,16 +6,28 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup cache
- * @defgroup object Object
+ * @ingroup core_types
+ * @defgroup object Object (Cacheable)
+ *
+ * Generic object data type, for inheritance purposes to implement cacheable
+ * data types.
+ *
+ * Related sections in the development guide:
+ *
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/object.h>
+ * ~~~~
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/object.h>
@@ -63,19 +75,23 @@
 }
 
 /**
- * Allocate a new object of kind specified by the name
+ * Allocate new object of kind specified by the name
  * @arg kind		name of object type
- * @return The new object or nULL
+ * @arg result		Result pointer
+ *
+ * @return 0 on success or a negative error code.
  */
 int nl_object_alloc_name(const char *kind, struct nl_object **result)
 {
 	struct nl_cache_ops *ops;
 
-	ops = nl_cache_ops_lookup(kind);
+	ops = nl_cache_ops_lookup_safe(kind);
 	if (!ops)
 		return -NLE_OPNOTSUPP;
 
-	if (!(*result = nl_object_alloc(ops->co_obj_ops)))
+	*result = nl_object_alloc(ops->co_obj_ops);
+	nl_cache_ops_put(ops);
+	if (!*result)
 		return -NLE_NOMEM;
 
 	return 0;
@@ -94,10 +110,14 @@
 struct nl_object *nl_object_clone(struct nl_object *obj)
 {
 	struct nl_object *new;
-	struct nl_object_ops *ops = obj_ops(obj);
+	struct nl_object_ops *ops;
 	int doff = offsetof(struct nl_derived_object, data);
 	int size;
 
+	if (!obj)
+		return NULL;
+
+	ops = obj_ops(obj);
 	new = nl_object_alloc(ops);
 	if (!new)
 		return NULL;
@@ -125,6 +145,23 @@
 }
 
 /**
+ * Merge a cacheable object
+ * @arg dst		object to be merged into
+ * @arg src		new object to be merged into dst
+ *
+ * @return 0 or a negative error code.
+ */
+int nl_object_update(struct nl_object *dst, struct nl_object *src)
+{
+	struct nl_object_ops *ops = obj_ops(dst);
+
+	if (ops->oo_update)
+		return ops->oo_update(dst, src);
+
+	return -NLE_OPNOTSUPP;
+}
+
+/**
  * Free a cacheable object
  * @arg obj		object to free
  *
@@ -132,7 +169,12 @@
  */
 void nl_object_free(struct nl_object *obj)
 {
-	struct nl_object_ops *ops = obj_ops(obj);
+	struct nl_object_ops *ops;
+
+	if (!obj)
+		return;
+
+	ops = obj_ops(obj);
 
 	if (obj->ce_refcnt > 0)
 		NL_DBG(1, "Warning: Freeing object in use...\n");
@@ -143,9 +185,9 @@
 	if (ops->oo_free_data)
 		ops->oo_free_data(obj);
 
-	free(obj);
-
 	NL_DBG(4, "Freed object %p\n", obj);
+
+	free(obj);
 }
 
 /** @} */
@@ -245,9 +287,22 @@
  */
 void nl_object_dump(struct nl_object *obj, struct nl_dump_params *params)
 {
+	if (params->dp_buf)
+		memset(params->dp_buf, 0, params->dp_buflen);
+
 	dump_from_ops(obj, params);
 }
 
+void nl_object_dump_buf(struct nl_object *obj, char *buf, size_t len)
+{
+        struct nl_dump_params dp = {
+                .dp_buf = buf,
+                .dp_buflen = len,
+        };
+
+        return nl_object_dump(obj, &dp);
+}
+
 /**
  * Check if the identifiers of two objects are identical 
  * @arg a		an object
@@ -258,14 +313,24 @@
 int nl_object_identical(struct nl_object *a, struct nl_object *b)
 {
 	struct nl_object_ops *ops = obj_ops(a);
-	int req_attrs;
+	uint32_t req_attrs;
 
 	/* Both objects must be of same type */
 	if (ops != obj_ops(b))
 		return 0;
 
-	req_attrs = ops->oo_id_attrs;
-	if (req_attrs == ~0)
+	if (ops->oo_id_attrs_get) {
+		int req_attrs_a = ops->oo_id_attrs_get(a);
+		int req_attrs_b = ops->oo_id_attrs_get(b);
+		if (req_attrs_a != req_attrs_b)
+			return 0;
+		req_attrs = req_attrs_a;
+	} else if (ops->oo_id_attrs) {
+		req_attrs = ops->oo_id_attrs;
+	} else {
+		req_attrs = 0xFFFFFFFF;
+	}
+	if (req_attrs == 0xFFFFFFFF)
 		req_attrs = a->ce_mask & b->ce_mask;
 
 	/* Both objects must provide all required attributes to uniquely
@@ -361,6 +426,27 @@
 	return nl_object_attrs2str(obj, obj->ce_mask, buf, len);
 }
 
+/**
+ * Generate object hash key
+ * @arg obj		the object
+ * @arg hashkey		destination buffer to be used for key stream
+ * @arg hashtbl_sz	hash table size
+ *
+ * @return hash key in destination buffer
+ */
+void nl_object_keygen(struct nl_object *obj, uint32_t *hashkey,
+		      uint32_t hashtbl_sz)
+{
+	struct nl_object_ops *ops = obj_ops(obj);
+
+	if (ops->oo_keygen)
+		ops->oo_keygen(obj, hashkey, hashtbl_sz);
+	else
+		*hashkey = 0;
+
+	return;
+}
+
 /** @} */
 
 /**
@@ -368,16 +454,91 @@
  * @{
  */
 
+/**
+ * Return number of references held
+ * @arg obj		object
+ *
+ * @return The number of references held to this object
+ */
 int nl_object_get_refcnt(struct nl_object *obj)
 {
 	return obj->ce_refcnt;
 }
 
+/**
+ * Return cache the object is associated with
+ * @arg obj		object
+ *
+ * @note The returned pointer is not protected with a reference counter,
+ *       it is your responsibility.
+ *
+ * @return Pointer to cache or NULL if not associated with a cache.
+ */
 struct nl_cache *nl_object_get_cache(struct nl_object *obj)
 {
 	return obj->ce_cache;
 }
 
+/**
+ * Return the object's type
+ * @arg obj		object
+ *
+ * FIXME: link to list of object types
+ *
+ * @return Name of the object type
+ */
+const char *nl_object_get_type(const struct nl_object *obj)
+{
+	if (!obj->ce_ops)
+		BUG();
+
+	return obj->ce_ops->oo_name;
+}
+
+/**
+ * Return the netlink message type the object was derived from
+ * @arg obj		object
+ *
+ * @return Netlink message type or 0.
+ */
+int nl_object_get_msgtype(const struct nl_object *obj)
+{
+	return obj->ce_msgtype;
+}
+
+/**
+ * Return object operations structure
+ * @arg obj		object
+ *
+ * @return Pointer to the object operations structure
+ */
+struct nl_object_ops *nl_object_get_ops(const struct nl_object *obj)
+{
+	return obj->ce_ops;
+}
+
+/**
+ * Return object id attribute mask
+ * @arg obj		object
+ *
+ * @return object id attribute mask
+ */
+uint32_t nl_object_get_id_attrs(struct nl_object *obj)
+{
+	struct nl_object_ops *ops = obj_ops(obj);
+	uint32_t id_attrs;
+
+	if (!ops)
+		return 0;
+
+	if (ops->oo_id_attrs_get)
+		id_attrs = ops->oo_id_attrs_get(obj);
+	else
+		id_attrs = ops->oo_id_attrs;
+
+	return id_attrs;
+}
+
 /** @} */
 
 /** @} */
diff --git a/lib/route/act.c b/lib/route/act.c
new file mode 100644
index 0000000..85ce445
--- /dev/null
+++ b/lib/route/act.c
@@ -0,0 +1,580 @@
+/*
+ * lib/route/act.c       Action
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+/**
+ * @ingroup tc
+ * @defgroup act Action
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/link.h>
+
+
+static struct nl_object_ops act_obj_ops;
+static struct nl_cache_ops rtnl_act_ops;
+
+int rtnl_act_append(struct rtnl_act **head, struct rtnl_act *new)
+{
+	struct rtnl_act *p_act;
+	int count = 1;
+
+	if (*head == NULL) {
+		*head = new;
+		return 0;
+	}
+
+	p_act = *head;
+	while (p_act->a_next) {
+		++count;
+		p_act = p_act->a_next;
+	}
+
+	if (count > TCA_ACT_MAX_PRIO)
+		return -NLE_RANGE;
+
+	p_act->a_next = new;
+	return 0;
+}
+
+int rtnl_act_remove(struct rtnl_act **head, struct rtnl_act *act)
+{
+	struct rtnl_act *a, **ap;
+
+        for (ap = head; (a = *ap) != NULL; ap = &a->a_next)
+                if (a == act)
+                        break;
+        if (a) {
+                *ap = a->a_next;
+                a->a_next = NULL;
+                return 0;
+        }
+
+	return -NLE_OBJ_NOTFOUND;
+}
+
+static int rtnl_act_fill_one(struct nl_msg *msg, struct rtnl_act *act, int order)
+{
+	struct rtnl_tc *tc = TC_CAST(act);
+	struct rtnl_tc_ops *ops;
+	struct nlattr *nest;
+	int err = -NLE_NOMEM;
+
+	nest = nla_nest_start(msg, order);
+	if (!nest)
+		goto nla_put_failure;
+
+	if (tc->ce_mask & TCA_ATTR_KIND)
+	    NLA_PUT_STRING(msg, TCA_ACT_KIND, tc->tc_kind);
+
+	ops = rtnl_tc_get_ops(tc);
+	if (ops && (ops->to_msg_fill || ops->to_msg_fill_raw)) {
+		struct nlattr *opts;
+		void *data = rtnl_tc_data(tc);
+
+		if (ops->to_msg_fill) {
+			if (!(opts = nla_nest_start(msg, TCA_ACT_OPTIONS)))
+				goto nla_put_failure;
+
+			if ((err = ops->to_msg_fill(tc, data, msg)) < 0)
+				goto nla_put_failure;
+
+			nla_nest_end(msg, opts);
+		} else if ((err = ops->to_msg_fill_raw(tc, data, msg)) < 0)
+			goto nla_put_failure;
+	}
+	nla_nest_end(msg, nest);
+	return 0;
+
+nla_put_failure:
+	return err;
+}
+
+int rtnl_act_fill(struct nl_msg *msg, int attrtype, struct rtnl_act *act)
+{
+	struct rtnl_act *p_act = act;
+	struct nlattr *nest;
+	int err, order = 0;
+
+	nest = nla_nest_start(msg, attrtype);
+	if (!nest)
+		return -NLE_MSGSIZE;
+
+	while (p_act) {
+		err = rtnl_act_fill_one(msg, p_act, ++order);
+		if (err)
+			return err;
+		p_act = p_act->a_next;
+	}
+
+	nla_nest_end(msg, nest);
+	return 0;
+}
+
+static int rtnl_act_msg_build(struct rtnl_act *act, int type, int flags,
+		      struct nl_msg **result)
+{
+	struct nl_msg *msg;
+	struct tcamsg tcahdr = {
+		.tca_family = AF_UNSPEC,
+	};
+	int err = -NLE_MSGSIZE;
+
+	msg = nlmsg_alloc_simple(type, flags);
+	if (!msg)
+		return -NLE_NOMEM;
+
+	if (nlmsg_append(msg, &tcahdr, sizeof(tcahdr), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	err = rtnl_act_fill(msg, TCA_ACT_TAB, act);
+	if (err < 0)
+		goto nla_put_failure;
+
+	*result = msg;
+	return 0;
+
+nla_put_failure:
+	nlmsg_free(msg);
+	return err;
+}
+
+static int act_build(struct rtnl_act *act, int type, int flags,
+		     struct nl_msg **result)
+{
+	int err;
+
+	err = rtnl_act_msg_build(act, type, flags, result);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/**
+ * @name Allocation/Freeing
+ * @{
+ */
+
+struct rtnl_act *rtnl_act_alloc(void)
+{
+	struct rtnl_tc *tc;
+
+	tc = TC_CAST(nl_object_alloc(&act_obj_ops));
+	if (tc)
+		tc->tc_type = RTNL_TC_TYPE_ACT;
+
+	return (struct rtnl_act *) tc;
+}
+
+void rtnl_act_get(struct rtnl_act *act)
+{
+	nl_object_get(OBJ_CAST(act));
+}
+
+void rtnl_act_put(struct rtnl_act *act)
+{
+	nl_object_put((struct nl_object *) act);
+}
+
+/** @} */
+
+/**
+ * @name Addition/Modification/Deletion
+ * @{
+ */
+
+/**
+ * Build a netlink message requesting the addition of an action
+ * @arg act		Action to add
+ * @arg flags		Additional netlink message flags
+ * @arg result		Pointer to store resulting netlink message
+ *
+ * The behaviour of this function is identical to rtnl_act_add() with
+ * the exception that it will not send the message but return it int the
+ * provided return pointer instead.
+ *
+ * @see rtnl_act_add()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_act_build_add_request(struct rtnl_act *act, int flags,
+			       struct nl_msg **result)
+{
+	return act_build(act, RTM_NEWACTION, flags, result);
+}
+
+/**
+ * Add/Update action
+ * @arg sk		Netlink socket
+ * @arg act		Action to add/update
+ * @arg flags		Additional netlink message flags
+ *
+ * Builds a \c RTM_NEWACTION netlink message requesting the addition
+ * of a new action and sends the message to the kernel. The
+ * configuration of the action is derived from the attributes of
+ * the specified traffic class.
+ *
+ * The following flags may be specified:
+ *  - \c NLM_F_CREATE:  Create action if it does not exist,
+ *                      otherwise -NLE_OBJ_NOTFOUND is returned.
+ *  - \c NLM_F_EXCL:    Return -NLE_EXISTS if an action with
+ *                      matching handle exists already.
+ *
+ * Existing actions with matching handles will be updated, unless
+ * the flag \c NLM_F_EXCL is specified. If no matching action
+ * exists, it will be created if the flag \c NLM_F_CREATE is set,
+ * otherwise the error -NLE_OBJ_NOTFOUND is returned.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
+ *       this function to return immediately after sending. In this case,
+ *       it is the responsibility of the caller to handle any error
+ *       messages returned.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_act_add(struct nl_sock *sk, struct rtnl_act *act, int flags)
+{
+	struct nl_msg *msg;
+	int err;
+
+	if ((err = rtnl_act_build_add_request(act, flags, &msg)) < 0)
+		return err;
+
+	return nl_send_sync(sk, msg);
+}
+
+/**
+ * Build a netlink message to change action attributes
+ * @arg act		Action to change
+ * @arg flags		additional netlink message flags
+ * @arg result		Pointer to store resulting message.
+ *
+ * Builds a new netlink message requesting a change of a neigh
+ * attributes. The netlink message header isn't fully equipped with
+ * all relevant fields and must thus be sent out via nl_send_auto_complete()
+ * or supplemented as needed.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_act_build_change_request(struct rtnl_act *act, int flags,
+				  struct nl_msg **result)
+{
+	return act_build(act, RTM_NEWACTION, NLM_F_REPLACE | flags, result);
+}
+
+/**
+ * Change an action
+ * @arg sk		Netlink socket.
+ * @arg act		action to change
+ * @arg flags		additional netlink message flags
+ *
+ * Builds a netlink message by calling rtnl_act_build_change_request(),
+ * sends the request to the kernel and waits for the next ACK to be
+ * received and thus blocks until the request has been processed.
+ *
+ * @return 0 on sucess or a negative error if an error occured.
+ */
+int rtnl_act_change(struct nl_sock *sk, struct rtnl_act *act, int flags)
+{
+	struct nl_msg *msg;
+	int err;
+
+	if ((err = rtnl_act_build_change_request(act, flags, &msg)) < 0)
+		return err;
+
+	return nl_send_sync(sk, msg);
+}
+
+/**
+ * Build netlink message requesting the deletion of an action
+ * @arg act		Action to delete
+ * @arg flags		Additional netlink message flags
+ * @arg result		Pointer to store resulting netlink message
+ *
+ * The behaviour of this function is identical to rtnl_act_delete() with
+ * the exception that it will not send the message but return it in the
+ * provided return pointer instead.
+ *
+ * @see rtnl_act_delete()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_act_build_delete_request(struct rtnl_act *act, int flags,
+				  struct nl_msg **result)
+{
+	return act_build(act, RTM_DELACTION, flags, result);
+}
+
+/**
+ * Delete action
+ * @arg sk		Netlink socket
+ * @arg act		Action to delete
+ * @arg flags		Additional netlink message flags
+ *
+ * Builds a \c RTM_DELACTION netlink message requesting the deletion
+ * of an action and sends the message to the kernel.
+ *
+ * The message is constructed out of the following attributes:
+ * - \c ifindex (required)
+ * - \c prio (required)
+ * - \c protocol (required)
+ * - \c handle (required)
+ * - \c parent (optional, if not specified parent equals root-qdisc)
+ * - \c kind (optional, must match if provided)
+ *
+ * All other action attributes including all class type specific
+ * attributes are ignored.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
+ *       this function to return immediately after sending. In this case,
+ *       it is the responsibility of the caller to handle any error
+ *       messages returned.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_act_delete(struct nl_sock *sk, struct rtnl_act *act, int flags)
+{
+	struct nl_msg *msg;
+	int err;
+
+	if ((err = rtnl_act_build_delete_request(act, flags, &msg)) < 0)
+		return err;
+
+	return nl_send_sync(sk, msg);
+}
+
+/** @} */
+
+static void act_dump_line(struct rtnl_tc *tc, struct nl_dump_params *p)
+{
+}
+
+void rtnl_act_put_all(struct rtnl_act **head)
+{
+	struct rtnl_act *curr, *next;
+
+	curr = *head;
+	while (curr) {
+		next = curr->a_next;
+		rtnl_act_put(curr);
+		curr = next;
+	}
+	*head = NULL;
+}
+
+int rtnl_act_parse(struct rtnl_act **head, struct nlattr *tb)
+{
+	struct rtnl_act *act;
+	struct rtnl_tc_ops *ops;
+	struct nlattr *tb2[TCA_ACT_MAX + 1];
+	struct nlattr *nla[TCA_ACT_MAX_PRIO + 1];
+	char kind[TCKINDSIZ];
+	int err, i;
+
+	err = nla_parse(nla, TCA_ACT_MAX_PRIO, nla_data(tb),
+			NLMSG_ALIGN(nla_len(tb)), NULL);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < TCA_ACT_MAX_PRIO; i++) {
+		struct rtnl_tc *tc;
+
+		if (nla[i] == NULL)
+			continue;
+
+		act = rtnl_act_alloc();
+		if (!act) {
+			err = -NLE_NOMEM;
+			goto err_free;
+		}
+		tc = TC_CAST(act);
+		err = nla_parse(tb2, TCA_ACT_MAX, nla_data(nla[i]),
+				nla_len(nla[i]), NULL);
+		if (err < 0)
+			goto err_free;
+
+		if (tb2[TCA_ACT_KIND] == NULL) {
+			err = -NLE_MISSING_ATTR;
+			goto err_free;
+		}
+
+		nla_strlcpy(kind, tb2[TCA_ACT_KIND], sizeof(kind));
+		rtnl_tc_set_kind(tc, kind);
+
+		if (tb2[TCA_ACT_OPTIONS]) {
+			tc->tc_opts = nl_data_alloc_attr(tb2[TCA_ACT_OPTIONS]);
+			if (!tc->tc_opts) {
+				err = -NLE_NOMEM;
+				goto err_free;
+			}
+			tc->ce_mask |= TCA_ATTR_OPTS;
+		}
+
+		ops = rtnl_tc_get_ops(tc);
+		if (ops && ops->to_msg_parser) {
+			void *data = rtnl_tc_data(tc);
+
+			if (!data) {
+				err = -NLE_NOMEM;
+				goto err_free;
+			}
+
+			err = ops->to_msg_parser(tc, data);
+			if (err < 0)
+				goto err_free;
+		}
+		err = rtnl_act_append(head, act);
+		if (err < 0)
+			goto err_free;
+	}
+	return 0;
+
+err_free:
+	rtnl_act_put (act);
+	rtnl_act_put_all(head);
+
+	return err;
+}
+
+static int rtnl_act_msg_parse(struct nlmsghdr *n, struct rtnl_act **act)
+{
+	struct rtnl_tc *tc = TC_CAST(*act);
+	struct nl_cache *link_cache;
+	struct nlattr *tb[TCAA_MAX + 1];
+	struct tcamsg *tm;
+	int err;
+
+	tc->ce_msgtype = n->nlmsg_type;
+
+	err = nlmsg_parse(n, sizeof(*tm), tb, TCAA_MAX, NULL);
+	if (err < 0)
+		return err;
+
+	tm = nlmsg_data(n);
+	tc->tc_family  = tm->tca_family;
+
+	if (tb[TCA_ACT_TAB] == NULL)
+		return -NLE_MISSING_ATTR;
+
+	err = rtnl_act_parse(act, tb[TCA_ACT_TAB]);
+	if (err < 0)
+		return err;
+
+	if ((link_cache = __nl_cache_mngt_require("route/link"))) {
+		struct rtnl_link *link;
+
+		if ((link = rtnl_link_get(link_cache, tc->tc_ifindex))) {
+			rtnl_tc_set_link(tc, link);
+
+			/* rtnl_tc_set_link incs refcnt */
+			rtnl_link_put(link);
+		}
+	}
+
+	return 0;
+}
+static int act_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
+			  struct nlmsghdr *nlh, struct nl_parser_param *pp)
+{
+	struct rtnl_act *act, *p_act;
+	int err;
+
+	if (!(act = rtnl_act_alloc()))
+		return -NLE_NOMEM;
+
+	if ((err = rtnl_act_msg_parse(nlh, &act)) < 0)
+		goto errout;
+
+	p_act = act;
+	while(p_act) {
+		err = pp->pp_cb(OBJ_CAST(act), pp);
+		if (err)
+			break;
+		p_act = p_act->a_next;
+	}
+errout:
+	rtnl_act_put(act);
+
+	return err;
+}
+
+static int act_request_update(struct nl_cache *cache, struct nl_sock *sk)
+{
+	struct tcamsg tcahdr = {
+		.tca_family = AF_UNSPEC,
+	};
+
+	return nl_send_simple(sk, RTM_GETACTION, NLM_F_DUMP, &tcahdr,
+			      sizeof(tcahdr));
+}
+
+static struct rtnl_tc_type_ops act_ops = {
+	.tt_type		= RTNL_TC_TYPE_ACT,
+	.tt_dump_prefix		= "act",
+	.tt_dump = {
+		[NL_DUMP_LINE]	= act_dump_line,
+	},
+};
+
+static struct nl_cache_ops rtnl_act_ops = {
+	.co_name		= "route/act",
+	.co_hdrsize		= sizeof(struct tcmsg),
+	.co_msgtypes		= {
+					{ RTM_NEWACTION, NL_ACT_NEW, "new" },
+					{ RTM_DELACTION, NL_ACT_DEL, "del" },
+					{ RTM_GETACTION, NL_ACT_GET, "get" },
+					END_OF_MSGTYPES_LIST,
+				  },
+	.co_protocol		= NETLINK_ROUTE,
+	.co_request_update	= act_request_update,
+	.co_msg_parser		= act_msg_parser,
+	.co_obj_ops		= &act_obj_ops,
+};
+
+static struct nl_object_ops act_obj_ops = {
+	.oo_name		= "route/act",
+	.oo_size		= sizeof(struct rtnl_act),
+	.oo_free_data		= rtnl_tc_free_data,
+	.oo_clone		= rtnl_tc_clone,
+	.oo_dump = {
+	    [NL_DUMP_LINE]	= rtnl_tc_dump_line,
+	    [NL_DUMP_DETAILS]	= rtnl_tc_dump_details,
+	    [NL_DUMP_STATS]	= rtnl_tc_dump_stats,
+	},
+	.oo_compare		= rtnl_tc_compare,
+	.oo_id_attrs		= (TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE),
+};
+
+static void __init act_init(void)
+{
+	rtnl_tc_type_register(&act_ops);
+	nl_cache_mngt_register(&rtnl_act_ops);
+}
+
+static void __exit act_exit(void)
+{
+	nl_cache_mngt_unregister(&rtnl_act_ops);
+	rtnl_tc_type_unregister(&act_ops);
+}
+
+/** @} */
diff --git a/lib/route/act/mirred.c b/lib/route/act/mirred.c
new file mode 100644
index 0000000..d047e24
--- /dev/null
+++ b/lib/route/act/mirred.c
@@ -0,0 +1,242 @@
+/*
+ * lib/route/cls/mirred.c		mirred action
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+/**
+ * @ingroup act
+ * @defgroup act_mirred Mirror and Redirect
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/act/mirred.h>
+
+static struct nla_policy mirred_policy[TCA_MIRRED_MAX + 1] = {
+	[TCA_MIRRED_PARMS]      = { .minlen = sizeof(struct tc_mirred) },
+};
+
+static int mirred_msg_parser(struct rtnl_tc *tc, void *data)
+{
+	struct rtnl_mirred *u = data;
+	struct nlattr *tb[TCA_MIRRED_MAX + 1];
+	int err;
+
+	err = tca_parse(tb, TCA_MIRRED_MAX, tc, mirred_policy);
+	if (err < 0)
+		return err;
+
+	if (!tb[TCA_MIRRED_PARMS])
+		return -NLE_MISSING_ATTR;
+
+	nla_memcpy(&u->m_parm, tb[TCA_MIRRED_PARMS], sizeof(u->m_parm));
+	return 0;
+}
+
+static void mirred_free_data(struct rtnl_tc *tc, void *data)
+{
+}
+
+static int mirred_clone(void *_dst, void *_src)
+{
+	struct rtnl_mirred *dst = _dst, *src = _src;
+
+	memcpy(&dst->m_parm, &src->m_parm, sizeof(src->m_parm));
+	return 0;
+}
+
+static void mirred_dump_line(struct rtnl_tc *tc, void *data,
+			  struct nl_dump_params *p)
+{
+	struct rtnl_mirred *u = data;
+	if (!u)
+		return;
+
+	nl_dump(p, " index %u", u->m_parm.ifindex);
+
+	if (u->m_parm.eaction == TCA_EGRESS_MIRROR)
+		nl_dump(p, " egress mirror");
+	else if (u->m_parm.eaction == TCA_EGRESS_REDIR)
+		nl_dump(p, " egress redirect");
+
+	switch(u->m_parm.action) {
+	case TC_ACT_UNSPEC:
+		nl_dump(p, " unspecified");
+		break;
+	case TC_ACT_PIPE:
+		nl_dump(p, " pipe");
+		break;
+	case TC_ACT_STOLEN:
+		nl_dump(p, " stolen");
+		break;
+	case TC_ACT_SHOT:
+		nl_dump(p, " shot");
+		break;
+	case TC_ACT_QUEUED:
+		nl_dump(p, " queued");
+		break;
+	case TC_ACT_REPEAT:
+		nl_dump(p, " repeat");
+		break;
+	}
+}
+
+static void mirred_dump_details(struct rtnl_tc *tc, void *data,
+			     struct nl_dump_params *p)
+{
+}
+
+static void mirred_dump_stats(struct rtnl_tc *tc, void *data,
+			   struct nl_dump_params *p)
+{
+	struct rtnl_mirred *u = data;
+
+	if (!u)
+		return;
+	/* TODO */
+}
+
+
+static int mirred_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+	struct rtnl_mirred *u = data;
+
+	if (!u)
+		return 0;
+
+	NLA_PUT(msg, TCA_MIRRED_PARMS, sizeof(u->m_parm), &u->m_parm);
+	return 0;
+
+nla_put_failure:
+	return -NLE_NOMEM;
+}
+
+/**
+ * @name Attribute Modifications
+ * @{
+ */
+
+int rtnl_mirred_set_action(struct rtnl_act *act, int action)
+{
+	struct rtnl_mirred *u;
+
+	if (!(u = (struct rtnl_mirred *) rtnl_tc_data(TC_CAST(act))))
+		return -NLE_NOMEM;
+
+	if (action > TCA_INGRESS_MIRROR || action < TCA_EGRESS_REDIR)
+		return -NLE_INVAL;
+
+	switch (action) {
+	case TCA_EGRESS_MIRROR:
+	case TCA_EGRESS_REDIR:
+		u->m_parm.eaction = action;
+		break;
+	case TCA_INGRESS_REDIR:
+	case TCA_INGRESS_MIRROR:
+	default:
+		return NLE_OPNOTSUPP;
+	}
+	return 0;
+}
+
+int rtnl_mirred_get_action(struct rtnl_act *act)
+{
+	struct rtnl_mirred *u;
+
+	if (!(u = (struct rtnl_mirred *) rtnl_tc_data(TC_CAST(act))))
+		return -NLE_NOMEM;
+	return u->m_parm.eaction;
+}
+
+int rtnl_mirred_set_ifindex(struct rtnl_act *act, uint32_t ifindex)
+{
+	struct rtnl_mirred *u;
+
+	if (!(u = (struct rtnl_mirred *) rtnl_tc_data(TC_CAST(act))))
+		return -NLE_NOMEM;
+
+	u->m_parm.ifindex = ifindex;
+	return 0;
+}
+
+uint32_t rtnl_mirred_get_ifindex(struct rtnl_act *act)
+{
+	struct rtnl_mirred *u;
+
+	if ((u = (struct rtnl_mirred *) rtnl_tc_data(TC_CAST(act))))
+		return u->m_parm.ifindex;
+	return 0;
+}
+
+int rtnl_mirred_set_policy(struct rtnl_act *act, int policy)
+{
+	struct rtnl_mirred *u;
+
+	if (!(u = (struct rtnl_mirred *) rtnl_tc_data(TC_CAST(act))))
+		return -NLE_NOMEM;
+
+	if (policy > TC_ACT_REPEAT || policy < TC_ACT_OK)
+		return -NLE_INVAL;
+
+	switch (u->m_parm.eaction) {
+	case TCA_EGRESS_MIRROR:
+	case TCA_EGRESS_REDIR:
+		u->m_parm.action = policy;
+		break;
+	case TCA_INGRESS_REDIR:
+	case TCA_INGRESS_MIRROR:
+	default:
+		return NLE_OPNOTSUPP;
+	}
+	return 0;
+}
+
+int rtnl_mirred_get_policy(struct rtnl_act *act)
+{
+	struct rtnl_mirred *u;
+
+	if (!(u = (struct rtnl_mirred *) rtnl_tc_data(TC_CAST(act))))
+		return -NLE_NOMEM;
+	return u->m_parm.action;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops mirred_ops = {
+	.to_kind		= "mirred",
+	.to_type		= RTNL_TC_TYPE_ACT,
+	.to_size		= sizeof(struct rtnl_mirred),
+	.to_msg_parser		= mirred_msg_parser,
+	.to_free_data		= mirred_free_data,
+	.to_clone		= mirred_clone,
+	.to_msg_fill		= mirred_msg_fill,
+	.to_dump = {
+	    [NL_DUMP_LINE]	= mirred_dump_line,
+	    [NL_DUMP_DETAILS]	= mirred_dump_details,
+	    [NL_DUMP_STATS]	= mirred_dump_stats,
+	},
+};
+
+static void __init mirred_init(void)
+{
+	rtnl_tc_register(&mirred_ops);
+}
+
+static void __exit mirred_exit(void)
+{
+	rtnl_tc_unregister(&mirred_ops);
+}
+
+/** @} */
diff --git a/lib/route/addr.c b/lib/route/addr.c
index 2e72f6e..e6e91d2 100644
--- a/lib/route/addr.c
+++ b/lib/route/addr.c
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  * Copyright (c) 2003-2006 Baruch Even <baruch@ev-en.org>,
  *                         Mediatrix Telecom, inc. <ericb@mediatrix.com>
  */
@@ -106,7 +106,7 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/route/rtnl.h>
 #include <netlink/route/addr.h>
@@ -151,6 +151,7 @@
 	nl_addr_put(addr->a_bcast);
 	nl_addr_put(addr->a_multicast);
 	nl_addr_put(addr->a_anycast);
+	rtnl_link_put(addr->a_link);
 }
 
 static int addr_clone(struct nl_object *_dst, struct nl_object *_src)
@@ -158,6 +159,11 @@
 	struct rtnl_addr *dst = nl_object_priv(_dst);
 	struct rtnl_addr *src = nl_object_priv(_src);
 
+	if (src->a_link) {
+		nl_object_get(OBJ_CAST(src->a_link));
+		dst->a_link = src->a_link;
+	}
+
 	if (src->a_peer)
 		if (!(dst->a_peer = nl_addr_clone(src->a_peer)))
 			return -NLE_NOMEM;
@@ -193,7 +199,9 @@
 	struct rtnl_addr *addr;
 	struct ifaddrmsg *ifa;
 	struct nlattr *tb[IFA_MAX+1];
-	int err, peer_prefix = 0, family;
+	int err, family;
+	struct nl_cache *link_cache;
+	struct nl_addr *plen_addr = NULL;
 
 	addr = rtnl_addr_alloc();
 	if (!addr)
@@ -208,8 +216,9 @@
 	ifa = nlmsg_data(nlh);
 	addr->a_family = family = ifa->ifa_family;
 	addr->a_prefixlen = ifa->ifa_prefixlen;
-	addr->a_flags = ifa->ifa_flags;
 	addr->a_scope = ifa->ifa_scope;
+	addr->a_flags = tb[IFA_FLAGS] ? nla_get_u32(tb[IFA_FLAGS]) :
+					ifa->ifa_flags;
 	addr->a_ifindex = ifa->ifa_index;
 
 	addr->ce_mask = (ADDR_ATTR_FAMILY | ADDR_ATTR_PREFIXLEN |
@@ -220,6 +229,7 @@
 		addr->ce_mask |= ADDR_ATTR_LABEL;
 	}
 
+	/* IPv6 only */
 	if (tb[IFA_CACHEINFO]) {
 		struct ifa_cacheinfo *ca;
 		
@@ -236,6 +246,7 @@
 		if (!addr->a_local)
 			goto errout_nomem;
 		addr->ce_mask |= ADDR_ATTR_LOCAL;
+		plen_addr = addr->a_local;
 	}
 
 	if (tb[IFA_ADDRESS]) {
@@ -255,13 +266,15 @@
 		} else {
 			addr->a_peer = a;
 			addr->ce_mask |= ADDR_ATTR_PEER;
-			peer_prefix = 1;
 		}
+
+		plen_addr = a;
 	}
 
-	nl_addr_set_prefixlen(peer_prefix ? addr->a_peer : addr->a_local,
-			      addr->a_prefixlen);
+	if (plen_addr)
+		nl_addr_set_prefixlen(plen_addr, addr->a_prefixlen);
 
+	/* IPv4 only */
 	if (tb[IFA_BROADCAST]) {
 		addr->a_bcast = nl_addr_alloc_attr(tb[IFA_BROADCAST], family);
 		if (!addr->a_bcast)
@@ -270,6 +283,7 @@
 		addr->ce_mask |= ADDR_ATTR_BROADCAST;
 	}
 
+	/* IPv6 only */
 	if (tb[IFA_MULTICAST]) {
 		addr->a_multicast = nl_addr_alloc_attr(tb[IFA_MULTICAST],
 						       family);
@@ -279,6 +293,7 @@
 		addr->ce_mask |= ADDR_ATTR_MULTICAST;
 	}
 
+	/* IPv6 only */
 	if (tb[IFA_ANYCAST]) {
 		addr->a_anycast = nl_addr_alloc_attr(tb[IFA_ANYCAST],
 						       family);
@@ -288,6 +303,17 @@
 		addr->ce_mask |= ADDR_ATTR_ANYCAST;
 	}
 
+	if ((link_cache = __nl_cache_mngt_require("route/link"))) {
+		struct rtnl_link *link;
+
+		if ((link = rtnl_link_get(link_cache, addr->a_ifindex))) {
+			rtnl_addr_set_link(addr, link);
+
+			/* rtnl_addr_set_link incs refcnt */
+			rtnl_link_put(link);
+		}
+	}
+
 	err = pp->pp_cb((struct nl_object *) addr, pp);
 errout:
 	rtnl_addr_put(addr);
@@ -310,7 +336,7 @@
 	struct nl_cache *link_cache;
 	char buf[128];
 
-	link_cache = nl_cache_mngt_require("route/link");
+	link_cache = nl_cache_mngt_require_safe("route/link");
 
 	if (addr->ce_mask & ADDR_ATTR_LOCAL)
 		nl_dump_line(p, "%s",
@@ -339,6 +365,9 @@
 		nl_dump(p, " <%s>", buf);
 
 	nl_dump(p, "\n");
+
+	if (link_cache)
+		nl_cache_put(link_cache);
 }
 
 static void addr_dump_details(struct nl_object *obj, struct nl_dump_params *p)
@@ -400,81 +429,6 @@
 	addr_dump_details(obj, p);
 }
 
-static void addr_dump_env(struct nl_object *obj, struct nl_dump_params *p)
-{
-	struct rtnl_addr *addr = (struct rtnl_addr *) obj;
-	struct nl_cache *link_cache;
-	char buf[128];
-
-	nl_dump_line(p, "ADDR_FAMILY=%s\n",
-		     nl_af2str(addr->a_family, buf, sizeof(buf)));
-
-	if (addr->ce_mask & ADDR_ATTR_LOCAL)
-		nl_dump_line(p, "ADDR_LOCAL=%s\n",
-			     nl_addr2str(addr->a_local, buf, sizeof(buf)));
-
-	if (addr->ce_mask & ADDR_ATTR_PEER)
-		nl_dump_line(p, "ADDR_PEER=%s\n",
-			     nl_addr2str(addr->a_peer, buf, sizeof(buf)));
-
-	if (addr->ce_mask & ADDR_ATTR_BROADCAST)
-		nl_dump_line(p, "ADDR_BROADCAST=%s\n",
-			     nl_addr2str(addr->a_bcast, buf, sizeof(buf)));
-
-	if (addr->ce_mask & ADDR_ATTR_ANYCAST)
-		nl_dump_line(p, "ADDR_ANYCAST=%s\n",
-			     nl_addr2str(addr->a_anycast, buf, sizeof(buf)));
-
-	if (addr->ce_mask & ADDR_ATTR_MULTICAST)
-		nl_dump_line(p, "ADDR_MULTICAST=%s\n",
-			     nl_addr2str(addr->a_multicast, buf,
-					   sizeof(buf)));
-
-	if (addr->ce_mask & ADDR_ATTR_PREFIXLEN)
-		nl_dump_line(p, "ADDR_PREFIXLEN=%u\n",
-			     addr->a_prefixlen);
-	link_cache = nl_cache_mngt_require("route/link");
-
-	nl_dump_line(p, "ADDR_IFINDEX=%u\n", addr->a_ifindex);
-	if (link_cache)
-		nl_dump_line(p, "ADDR_IFNAME=%s\n",
-			     rtnl_link_i2name(link_cache, addr->a_ifindex,
-					      buf, sizeof(buf)));
-
-	if (addr->ce_mask & ADDR_ATTR_SCOPE)
-		nl_dump_line(p, "ADDR_SCOPE=%s\n",
-			     rtnl_scope2str(addr->a_scope, buf, sizeof(buf)));
-
-	if (addr->ce_mask & ADDR_ATTR_LABEL)
-		nl_dump_line(p, "ADDR_LABEL=%s\n", addr->a_label);
-
-	rtnl_addr_flags2str(addr->a_flags, buf, sizeof(buf));
-	if (buf[0])
-		nl_dump_line(p, "ADDR_FLAGS=%s\n", buf);
-
-	if (addr->ce_mask & ADDR_ATTR_CACHEINFO) {
-		struct rtnl_addr_cacheinfo *ci = &addr->a_cacheinfo;
-
-		nl_dump_line(p, "ADDR_CACHEINFO_VALID=%s\n",
-			     ci->aci_valid == 0xFFFFFFFFU ? "forever" :
-			     nl_msec2str(ci->aci_valid * 1000,
-					   buf, sizeof(buf)));
-
-		nl_dump_line(p, "ADDR_CACHEINFO_PREFERED=%s\n",
-			     ci->aci_prefered == 0xFFFFFFFFU ? "forever" :
-			     nl_msec2str(ci->aci_prefered * 1000,
-					 buf, sizeof(buf)));
-
-		nl_dump_line(p, "ADDR_CACHEINFO_CREATED=%s\n",
-			     nl_msec2str(addr->a_cacheinfo.aci_cstamp * 10,
-					 buf, sizeof(buf)));
-
-		nl_dump_line(p, "ADDR_CACHEINFO_LASTUPDATE=%s\n",
-			     nl_msec2str(addr->a_cacheinfo.aci_tstamp * 10,
-					 buf, sizeof(buf)));
-	}
-}
-
 static int addr_compare(struct nl_object *_a, struct nl_object *_b,
 			uint32_t attrs, int flags)
 {
@@ -506,7 +460,7 @@
 	return diff;
 }
 
-static struct trans_tbl addr_attrs[] = {
+static const struct trans_tbl addr_attrs[] = {
 	__ADD(ADDR_ATTR_FAMILY, family)
 	__ADD(ADDR_ATTR_PREFIXLEN, prefixlen)
 	__ADD(ADDR_ATTR_FLAGS, flags)
@@ -553,6 +507,42 @@
 	return nl_cache_alloc_and_fill(&rtnl_addr_ops, sk, result);
 }
 
+/**
+ * Search address in cache
+ * @arg cache		Address cache
+ * @arg ifindex		Interface index of address
+ * @arg addr		Local address part
+ *
+ * Searches address cache previously allocated with rtnl_addr_alloc_cache()
+ * for an address with a matching local address.
+ *
+ * The reference counter is incremented before returning the address, therefore
+ * the reference must be given back with rtnl_addr_put() after usage.
+ *
+ * @return Address object or NULL if no match was found.
+ */
+struct rtnl_addr *rtnl_addr_get(struct nl_cache *cache, int ifindex,
+				struct nl_addr *addr)
+{
+	struct rtnl_addr *a;
+
+	if (cache->c_ops != &rtnl_addr_ops)
+		return NULL;
+
+	nl_list_for_each_entry(a, &cache->c_items, ce_list) {
+		if (ifindex && a->a_ifindex != ifindex)
+			continue;
+
+		if (a->ce_mask & ADDR_ATTR_LOCAL &&
+		    !nl_addr_cmp(a->a_local, addr)) {
+			nl_object_get((struct nl_object *) a);
+			return a;
+		}
+	}
+
+	return NULL;
+}
+
 /** @} */
 
 static int build_addr_msg(struct rtnl_addr *tmpl, int cmd, int flags,
@@ -563,6 +553,7 @@
 		.ifa_family = tmpl->a_family,
 		.ifa_index = tmpl->a_ifindex,
 		.ifa_prefixlen = tmpl->a_prefixlen,
+		.ifa_flags = tmpl->a_flags,
 	};
 
 	if (tmpl->ce_mask & ADDR_ATTR_SCOPE)
@@ -607,6 +598,19 @@
 		NLA_PUT(msg, IFA_CACHEINFO, sizeof(ca), &ca);
 	}
 
+	if (tmpl->a_flags & ~0xFF) {
+		/* only set the IFA_FLAGS attribute, if they actually contain additional
+		 * flags that are not already set to am.ifa_flags.
+		 *
+		 * Older kernels refuse RTM_NEWADDR and RTM_NEWROUTE messages with EINVAL
+		 * if they contain unknown netlink attributes. See net/core/rtnetlink.c, which
+		 * was fixed by kernel commit 661d2967b3f1b34eeaa7e212e7b9bbe8ee072b59.
+		 *
+		 * With this workaround, libnl will function correctly with older kernels,
+		 * unless there is a new libnl user that wants to set these flags. In this
+		 * case it's up to the user to workaround this issue. */
+		NLA_PUT_U32(msg, IFA_FLAGS, tmpl->a_flags);
+	}
 
 	*result = msg;
 	return 0;
@@ -646,7 +650,7 @@
 int rtnl_addr_build_add_request(struct rtnl_addr *addr, int flags,
 				struct nl_msg **result)
 {
-	int required = ADDR_ATTR_IFINDEX | ADDR_ATTR_FAMILY |
+	uint32_t required = ADDR_ATTR_IFINDEX | ADDR_ATTR_FAMILY |
 		       ADDR_ATTR_PREFIXLEN | ADDR_ATTR_LOCAL;
 
 	if ((addr->ce_mask & required) != required)
@@ -719,7 +723,7 @@
 int rtnl_addr_build_delete_request(struct rtnl_addr *addr, int flags,
 				   struct nl_msg **result)
 {
-	int required = ADDR_ATTR_IFINDEX | ADDR_ATTR_FAMILY;
+	uint32_t required = ADDR_ATTR_IFINDEX | ADDR_ATTR_FAMILY;
 
 	if ((addr->ce_mask & required) != required)
 		return -NLE_MISSING_ATTR;
@@ -794,6 +798,29 @@
 	return addr->a_ifindex;
 }
 
+void rtnl_addr_set_link(struct rtnl_addr *addr, struct rtnl_link *link)
+{
+	rtnl_link_put(addr->a_link);
+
+	if (!link)
+		return;
+
+	nl_object_get(OBJ_CAST(link));
+	addr->a_link = link;
+	addr->a_ifindex = link->l_index;
+	addr->ce_mask |= ADDR_ATTR_IFINDEX;
+}
+
+struct rtnl_link *rtnl_addr_get_link(struct rtnl_addr *addr)
+{
+	if (addr->a_link) {
+		nl_object_get(OBJ_CAST(addr->a_link));
+		return addr->a_link;
+	}
+
+	return NULL;
+}
+
 void rtnl_addr_set_family(struct rtnl_addr *addr, int family)
 {
 	addr->a_family = family;
@@ -805,10 +832,39 @@
 	return addr->a_family;
 }
 
-void rtnl_addr_set_prefixlen(struct rtnl_addr *addr, int prefix)
+/**
+ * Set the prefix length / netmask
+ * @arg addr		Address
+ * @arg prefixlen	Length of prefix (netmask)
+ *
+ * Modifies the length of the prefix. If the address object contains a peer
+ * address the prefix length will apply to it, otherwise the prefix length
+ * will apply to the local address of the address.
+ *
+ * If the address object contains a peer or local address the corresponding
+ * `struct nl_addr` will be updated with the new prefix length.
+ *
+ * @note Specifying a length of 0 will remove the prefix length alltogether.
+ *
+ * @see rtnl_addr_get_prefixlen()
+ */
+void rtnl_addr_set_prefixlen(struct rtnl_addr *addr, int prefixlen)
 {
-	addr->a_prefixlen = prefix;
-	addr->ce_mask |= ADDR_ATTR_PREFIXLEN;
+	addr->a_prefixlen = prefixlen;
+
+	if (prefixlen)
+		addr->ce_mask |= ADDR_ATTR_PREFIXLEN;
+	else
+		addr->ce_mask &= ~ADDR_ATTR_PREFIXLEN;
+
+	/*
+	 * The prefix length always applies to the peer address if
+	 * a peer address is present.
+	 */
+	if (addr->a_peer)
+		nl_addr_set_prefixlen(addr->a_peer, prefixlen);
+	else if (addr->a_local)
+		nl_addr_set_prefixlen(addr->a_local, prefixlen);
 }
 
 int rtnl_addr_get_prefixlen(struct rtnl_addr *addr)
@@ -849,17 +905,25 @@
 static inline int __assign_addr(struct rtnl_addr *addr, struct nl_addr **pos,
 			        struct nl_addr *new, int flag)
 {
-	if (addr->ce_mask & ADDR_ATTR_FAMILY) {
-		if (new->a_family != addr->a_family)
-			return -NLE_AF_MISMATCH;
-	} else
-		addr->a_family = new->a_family;
+	if (new) {
+		if (addr->ce_mask & ADDR_ATTR_FAMILY) {
+			if (new->a_family != addr->a_family)
+				return -NLE_AF_MISMATCH;
+		} else
+			addr->a_family = new->a_family;
 
-	if (*pos)
-		nl_addr_put(*pos);
+		if (*pos)
+			nl_addr_put(*pos);
 
-	*pos = nl_addr_get(new);
-	addr->ce_mask |= (flag | ADDR_ATTR_FAMILY);
+		*pos = nl_addr_get(new);
+		addr->ce_mask |= (flag | ADDR_ATTR_FAMILY);
+	} else {
+		if (*pos)
+			nl_addr_put(*pos);
+
+		*pos = NULL;
+		addr->ce_mask &= ~flag;
+	}
 
 	return 0;
 }
@@ -868,14 +932,18 @@
 {
 	int err;
 
+	/* Prohibit local address with prefix length if peer address is present */
+	if ((addr->ce_mask & ADDR_ATTR_PEER) && local &&
+	    nl_addr_get_prefixlen(local))
+		return -NLE_INVAL;
+
 	err = __assign_addr(addr, &addr->a_local, local, ADDR_ATTR_LOCAL);
 	if (err < 0)
 		return err;
 
-	if (!(addr->ce_mask & ADDR_ATTR_PEER)) {
-		addr->a_prefixlen = nl_addr_get_prefixlen(addr->a_local);
-		addr->ce_mask |= ADDR_ATTR_PREFIXLEN;
-	}
+	/* Never overwrite the prefix length if a peer address is present */
+	if (!(addr->ce_mask & ADDR_ATTR_PEER))
+		rtnl_addr_set_prefixlen(addr, local ? nl_addr_get_prefixlen(local) : 0);
 
 	return 0;
 }
@@ -887,10 +955,16 @@
 
 int rtnl_addr_set_peer(struct rtnl_addr *addr, struct nl_addr *peer)
 {
-	return __assign_addr(addr, &addr->a_peer, peer, ADDR_ATTR_PEER);
+	int err;
 
-	addr->a_prefixlen = nl_addr_get_prefixlen(addr->a_peer);
-	addr->ce_mask |= ADDR_ATTR_PREFIXLEN;
+	if (peer && peer->a_family != AF_INET)
+		return -NLE_AF_NOSUPPORT;
+
+	err = __assign_addr(addr, &addr->a_peer, peer, ADDR_ATTR_PEER);
+	if (err < 0)
+		return err;
+
+	rtnl_addr_set_prefixlen(addr, peer ? nl_addr_get_prefixlen(peer) : 0);
 
 	return 0;
 }
@@ -902,6 +976,9 @@
 
 int rtnl_addr_set_broadcast(struct rtnl_addr *addr, struct nl_addr *bcast)
 {
+	if (bcast && bcast->a_family != AF_INET)
+		return -NLE_AF_NOSUPPORT;
+
 	return __assign_addr(addr, &addr->a_bcast, bcast, ADDR_ATTR_BROADCAST);
 }
 
@@ -912,6 +989,9 @@
 
 int rtnl_addr_set_multicast(struct rtnl_addr *addr, struct nl_addr *multicast)
 {
+	if (multicast && multicast->a_family != AF_INET6)
+		return -NLE_AF_NOSUPPORT;
+
 	return __assign_addr(addr, &addr->a_multicast, multicast,
 			     ADDR_ATTR_MULTICAST);
 }
@@ -923,6 +1003,9 @@
 
 int rtnl_addr_set_anycast(struct rtnl_addr *addr, struct nl_addr *anycast)
 {
+	if (anycast && anycast->a_family != AF_INET6)
+		return -NLE_AF_NOSUPPORT;
+
 	return __assign_addr(addr, &addr->a_anycast, anycast,
 			     ADDR_ATTR_ANYCAST);
 }
@@ -977,7 +1060,7 @@
  * @{
  */
 
-static struct trans_tbl addr_flags[] = {
+static const struct trans_tbl addr_flags[] = {
 	__ADD(IFA_F_SECONDARY, secondary)
 	__ADD(IFA_F_NODAD, nodad)
 	__ADD(IFA_F_OPTIMISTIC, optimistic)
@@ -985,6 +1068,8 @@
 	__ADD(IFA_F_DEPRECATED, deprecated)
 	__ADD(IFA_F_TENTATIVE, tentative)
 	__ADD(IFA_F_PERMANENT, permanent)
+	__ADD(IFA_F_MANAGETEMPADDR, mngtmpaddr)
+	__ADD(IFA_F_NOPREFIXROUTE, noprefixroute)
 };
 
 char *rtnl_addr_flags2str(int flags, char *buf, size_t size)
@@ -1010,7 +1095,6 @@
 	    [NL_DUMP_LINE] 	= addr_dump_line,
 	    [NL_DUMP_DETAILS]	= addr_dump_details,
 	    [NL_DUMP_STATS]	= addr_dump_stats,
-	    [NL_DUMP_ENV]	= addr_dump_env,
 	},
 	.oo_compare		= addr_compare,
 	.oo_attrs2str		= addr_attrs2str,
diff --git a/lib/route/class.c b/lib/route/class.c
index ddf2d2e..56ad1d8 100644
--- a/lib/route/class.c
+++ b/lib/route/class.c
@@ -1,61 +1,59 @@
 /*
- * lib/route/class.c            Queueing Classes
+ * lib/route/class.c            Traffic Classes
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup tc
- * @defgroup class Queueing Classes
+ * @defgroup class Traffic Classes
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
-#include <netlink/route/tc.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/class.h>
-#include <netlink/route/class-modules.h>
 #include <netlink/route/qdisc.h>
 #include <netlink/route/classifier.h>
 #include <netlink/utils.h>
 
 static struct nl_cache_ops rtnl_class_ops;
+static struct nl_object_ops class_obj_ops;
+
+static void class_dump_details(struct rtnl_tc *tc, struct nl_dump_params *p)
+{
+	struct rtnl_class *class = (struct rtnl_class *) tc;
+	char buf[32];
+
+	if (class->c_info)
+		nl_dump(p, "child-qdisc %s ",
+			rtnl_tc_handle2str(class->c_info, buf, sizeof(buf)));
+}
+
 
 static int class_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
-			    struct nlmsghdr *n, struct nl_parser_param *pp)
+			    struct nlmsghdr *nlh, struct nl_parser_param *pp)
 {
-	int err;
 	struct rtnl_class *class;
-	struct rtnl_class_ops *cops;
+	int err;
 
-	class = rtnl_class_alloc();
-	if (!class) {
-		err = -NLE_NOMEM;
+	if (!(class = rtnl_class_alloc()))
+		return -NLE_NOMEM;
+
+	if ((err = rtnl_tc_msg_parse(nlh, TC_CAST(class))) < 0)
 		goto errout;
-	}
-	class->ce_msgtype = n->nlmsg_type;
 
-	err = tca_msg_parser(n, (struct rtnl_tca *) class);
-	if (err < 0)
-		goto errout_free;
-
-	cops = rtnl_class_lookup_ops(class);
-	if (cops && cops->co_msg_parser) {
-		err = cops->co_msg_parser(class);
-		if (err < 0)
-			goto errout_free;
-	}
-
-	err = pp->pp_cb((struct nl_object *) class, pp);
-errout_free:
-	rtnl_class_put(class);
+	err = pp->pp_cb(OBJ_CAST(class), pp);
 errout:
+	rtnl_class_put(class);
+
 	return err;
 }
 
@@ -71,75 +69,104 @@
 }
 
 /**
- * @name Addition/Modification
+ * @name Allocation/Freeing
+ * @{
+ */
+
+struct rtnl_class *rtnl_class_alloc(void)
+{
+	struct rtnl_tc *tc;
+
+	tc = TC_CAST(nl_object_alloc(&class_obj_ops));
+	if (tc)
+		tc->tc_type = RTNL_TC_TYPE_CLASS;
+
+	return (struct rtnl_class *) tc;
+}
+
+void rtnl_class_put(struct rtnl_class *class)
+{
+	nl_object_put((struct nl_object *) class);
+}
+
+/** @} */
+
+
+/**
+ * @name Addition/Modification/Deletion
  * @{
  */
 
 static int class_build(struct rtnl_class *class, int type, int flags,
 		       struct nl_msg **result)
 {
-	struct rtnl_class_ops *cops;
-	int err;
+	uint32_t needed = TCA_ATTR_PARENT | TCA_ATTR_HANDLE;
 
-	err = tca_build_msg((struct rtnl_tca *) class, type, flags, result);
-	if (err < 0)
-		return err;
-
-	cops = rtnl_class_lookup_ops(class);
-	if (cops && cops->co_get_opts) {
-		struct nl_msg *opts;
-		
-		opts = cops->co_get_opts(class);
-		if (opts) {
-			err = nla_put_nested(*result, TCA_OPTIONS, opts);
-			nlmsg_free(opts);
-			if (err < 0)
-				goto errout;
-		}
+	if ((class->ce_mask & needed) == needed &&
+	    TC_H_MAJ(class->c_parent) && TC_H_MAJ(class->c_handle) &&
+	    TC_H_MAJ(class->c_parent) != TC_H_MAJ(class->c_handle)) {
+		APPBUG("TC_H_MAJ(parent) must match TC_H_MAJ(handle)");
+		return -NLE_INVAL;
 	}
 
-	return 0;
-errout:
-	nlmsg_free(*result);
-	return err;
+	return rtnl_tc_msg_build(TC_CAST(class), type, flags, result);
 }
 
 /**
- * Build a netlink message to add a new class
- * @arg class		class to add 
- * @arg flags		additional netlink message flags
- * @arg result		Pointer to store resulting message.
+ * Build a netlink message requesting the addition of a traffic class
+ * @arg class		Traffic class to add 
+ * @arg flags		Additional netlink message flags
+ * @arg result		Pointer to store resulting netlink message
  *
- * Builds a new netlink message requesting an addition of a class.
- * The netlink message header isn't fully equipped with all relevant
- * fields and must be sent out via nl_send_auto_complete() or
- * supplemented as needed. 
+ * The behaviour of this function is identical to rtnl_class_add() with
+ * the exception that it will not send the message but return it int the
+ * provided return pointer instead.
  *
- * Common message flags
- *   - NLM_F_REPLACE - replace possibly existing classes
+ * @see rtnl_class_add()
  *
  * @return 0 on success or a negative error code.
  */
 int rtnl_class_build_add_request(struct rtnl_class *class, int flags,
 				 struct nl_msg **result)
 {
-	return class_build(class, RTM_NEWTCLASS, NLM_F_CREATE | flags, result);
+	return class_build(class, RTM_NEWTCLASS, flags, result);
 }
 
 /**
- * Add a new class
- * @arg sk		Netlink socket.
- * @arg class		class to delete
- * @arg flags		additional netlink message flags
+ * Add/Update traffic class
+ * @arg sk		Netlink socket
+ * @arg class		Traffic class to add 
+ * @arg flags		Additional netlink message flags
  *
- * Builds a netlink message by calling rtnl_qdisc_build_add_request(),
- * sends the request to the kernel and waits for the next ACK to be
- * received and thus blocks until the request has been processed.
+ * Builds a \c RTM_NEWTCLASS netlink message requesting the addition
+ * of a new traffic class and sends the message to the kernel. The
+ * configuration of the traffic class is derived from the attributes
+ * of the specified traffic class.
  *
- * Common message flags
- *   - NLM_F_REPLACE - replace possibly existing classes
+ * The following flags may be specified:
+ *  - \c NLM_F_CREATE:  Create traffic class if it does not exist,
+ *                      otherwise -NLE_OBJ_NOTFOUND is returned.
+ *  - \c NLM_F_EXCL:    Return -NLE_EXISTS if a traffic class with
+ *                      matching handle exists already.
  *
- * @return 0 on success or a negative error code
+ * Existing traffic classes with matching handles will be updated,
+ * unless the flag \c NLM_F_EXCL is specified. If no matching traffic
+ * class exists, it will be created if the flag \c NLM_F_CREATE is set,
+ * otherwise the error -NLE_OBJ_NOTFOUND is returned. 
+ *
+ * If the parent qdisc does not support classes, the error
+ * \c NLE_OPNOTSUPP is returned.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
+ *       this function to return immediately after sending. In this case,
+ *       it is the responsibility of the caller to handle any error
+ *       messages returned.
+ *
+ * @return 0 on success or a negative error code.
  */
 int rtnl_class_add(struct nl_sock *sk, struct rtnl_class *class, int flags)
 {
@@ -149,32 +176,44 @@
 	if ((err = rtnl_class_build_add_request(class, flags, &msg)) < 0)
 		return err;
 
-	err = nl_send_auto_complete(sk, msg);
-	nlmsg_free(msg);
-	if (err < 0)
-		return err;
-
-	return wait_for_ack(sk);
+	return nl_send_sync(sk, msg);
 }
 
-int rtnl_class_build_delete_request(struct rtnl_class *class,
-									struct nl_msg **result)
+/**
+ * Build netlink message requesting the deletion of a traffic class
+ * @arg class		Traffic class to delete
+ * @arg result		Pointer to store resulting netlink message
+ *
+ * The behaviour of this function is identical to rtnl_class_delete() with
+ * the exception that it will not send the message but return it in the
+ * provided return pointer instead.
+ *
+ * @see rtnl_class_delete()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_class_build_delete_request(struct rtnl_class *class, struct nl_msg **result)
 {
 	struct nl_msg *msg;
 	struct tcmsg tchdr;
-	int required = TCA_ATTR_IFINDEX | TCA_ATTR_PARENT;
+	uint32_t required = TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE;
 
-	if ((class->ce_mask & required) != required)
-		BUG();
+	if ((class->ce_mask & required) != required) {
+		APPBUG("ifindex and handle must be specified");
+		return -NLE_MISSING_ATTR;
+	}
 
-	msg = nlmsg_alloc_simple(RTM_DELTCLASS, 0);
-	if (!msg)
+	if (!(msg = nlmsg_alloc_simple(RTM_DELTCLASS, 0)))
 		return -NLE_NOMEM;
 
+	memset(&tchdr, 0, sizeof(tchdr));
 	tchdr.tcm_family = AF_UNSPEC;
-	tchdr.tcm_handle = class->c_handle;
-	tchdr.tcm_parent = class->c_parent;
 	tchdr.tcm_ifindex = class->c_ifindex;
+	tchdr.tcm_handle = class->c_handle;
+
+	if (class->ce_mask & TCA_ATTR_PARENT)
+		tchdr.tcm_parent = class->c_parent;
+
 	if (nlmsg_append(msg, &tchdr, sizeof(tchdr), NLMSG_ALIGNTO) < 0) {
 		nlmsg_free(msg);
 		return -NLE_MSGSIZE;
@@ -185,15 +224,30 @@
 }
 
 /**
- * Delete a class
- * @arg sk		Netlink socket.
- * @arg class		class to delete
+ * Delete traffic class
+ * @arg sk		Netlink socket
+ * @arg class		Traffic class to delete
  *
- * Builds a netlink message by calling rtnl_class_build_delete_request(),
- * sends the request to the kernel and waits for the ACK to be
- * received and thus blocks until the request has been processed.
+ * Builds a \c RTM_DELTCLASS netlink message requesting the deletion
+ * of a traffic class and sends the message to the kernel.
  *
- * @return 0 on success or a negative error code
+ * The message is constructed out of the following attributes:
+ * - \c ifindex and \c handle (required)
+ * - \c parent (optional, must match if provided)
+ *
+ * All other class attributes including all class type specific
+ * attributes are ignored.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
+ *       this function to return immediately after sending. In this case,
+ *       it is the responsibility of the caller to handle any error
+ *       messages returned.
+ *
+ * @return 0 on success or a negative error code.
  */
 int rtnl_class_delete(struct nl_sock *sk, struct rtnl_class *class)
 {
@@ -203,40 +257,70 @@
 	if ((err = rtnl_class_build_delete_request(class, &msg)) < 0)
 		return err;
 
-	err = nl_send_auto_complete(sk, msg);
-	nlmsg_free(msg);
-	if (err < 0)
-		return err;
-
-	return wait_for_ack(sk);
+	return nl_send_sync(sk, msg);
 }
 
 /** @} */
 
 /**
- * @name Cache Management
+ * @name Leaf Qdisc
  * @{
  */
 
 /**
- * Build a class cache including all classes attached to the specified interface
- * @arg sk		Netlink socket.
- * @arg ifindex		interface index of the link the classes are
- *                      attached to.
+ * Lookup the leaf qdisc of a traffic class
+ * @arg class		the parent traffic class
+ * @arg cache		a qdisc cache allocated using rtnl_qdisc_alloc_cache()
  *
- * Allocates a new cache, initializes it properly and updates it to
- * include all classes attached to the specified interface.
+ * @return Matching Qdisc or NULL if the traffic class has no leaf qdisc
+ */
+struct rtnl_qdisc *rtnl_class_leaf_qdisc(struct rtnl_class *class,
+					 struct nl_cache *cache)
+{
+	struct rtnl_qdisc *leaf;
+
+	if (!class->c_info)
+		return NULL;
+
+	leaf = rtnl_qdisc_get_by_parent(cache, class->c_ifindex,
+					class->c_handle);
+	if (!leaf || leaf->q_handle != class->c_info)
+		return NULL;
+
+	return leaf;
+}
+
+/** @} */
+
+/**
+ * @name Cache Related Functions
+ * @{
+ */
+
+/**
+ * Allocate a cache and fill it with all configured traffic classes
+ * @arg sk		Netlink socket
+ * @arg ifindex		Interface index of the network device
+ * @arg result		Pointer to store the created cache
  *
- * @return The cache or NULL if an error has occured.
+ * Allocates a new traffic class cache and fills it with a list of all
+ * configured traffic classes on a specific network device. Release the
+ * cache with nl_cache_free().
+ *
+ * @return 0 on success or a negative error code.
  */
 int rtnl_class_alloc_cache(struct nl_sock *sk, int ifindex,
 			   struct nl_cache **result)
 {
 	struct nl_cache * cache;
 	int err;
+
+	if (!ifindex) {
+		APPBUG("ifindex must be specified");
+		return -NLE_INVAL;
+	}
 	
-	cache = nl_cache_alloc(&rtnl_class_ops);
-	if (!cache)
+	if (!(cache = nl_cache_alloc(&rtnl_class_ops)))
 		return -NLE_NOMEM;
 
 	cache->c_iarg1 = ifindex;
@@ -251,14 +335,23 @@
 }
 
 /**
- * Look up class by its handle in the provided cache
- * @arg cache		class cache
- * @arg ifindex		interface the class is attached to
- * @arg handle		class handle
- * @return pointer to class inside the cache or NULL if no match was found.
+ * Search traffic class by interface index and handle
+ * @arg cache		Traffic class cache
+ * @arg ifindex		Interface index
+ * @arg handle		ID of traffic class
+ *
+ * Searches a traffic class cache previously allocated with
+ * rtnl_class_alloc_cache() and searches for a traffi class matching
+ * the interface index and handle.
+ *
+ * The reference counter is incremented before returning the traffic
+ * class, therefore the reference must be given back with rtnl_class_put()
+ * after usage.
+ *
+ * @return Traffic class or NULL if no match was found.
  */
 struct rtnl_class *rtnl_class_get(struct nl_cache *cache, int ifindex,
-								  uint32_t handle)
+				  uint32_t handle)
 {
 	struct rtnl_class *class;
 	
@@ -276,6 +369,80 @@
 
 /** @} */
 
+/**
+ * @name Deprecated Functions
+ * @{
+ */
+
+/**
+ * Call a callback for each child of a class
+ *
+ * @deprecated Use of this function is deprecated, it does not allow
+ *             to handle the out of memory situation that can occur.
+ */
+void rtnl_class_foreach_child(struct rtnl_class *class, struct nl_cache *cache,
+			      void (*cb)(struct nl_object *, void *), void *arg)
+{
+	struct rtnl_class *filter;
+	
+	filter = rtnl_class_alloc();
+	if (!filter)
+		return;
+
+	rtnl_tc_set_parent(TC_CAST(filter), class->c_handle);
+	rtnl_tc_set_ifindex(TC_CAST(filter), class->c_ifindex);
+	rtnl_tc_set_kind(TC_CAST(filter), class->c_kind);
+
+	nl_cache_foreach_filter(cache, OBJ_CAST(filter), cb, arg);
+	rtnl_class_put(filter);
+}
+
+/**
+ * Call a callback for each classifier attached to the class
+ *
+ * @deprecated Use of this function is deprecated, it does not allow
+ *             to handle the out of memory situation that can occur.
+ */
+void rtnl_class_foreach_cls(struct rtnl_class *class, struct nl_cache *cache,
+			    void (*cb)(struct nl_object *, void *), void *arg)
+{
+	struct rtnl_cls *filter;
+
+	filter = rtnl_cls_alloc();
+	if (!filter)
+		return;
+
+	rtnl_tc_set_ifindex((struct rtnl_tc *) filter, class->c_ifindex);
+	rtnl_tc_set_parent((struct rtnl_tc *) filter, class->c_parent);
+
+	nl_cache_foreach_filter(cache, (struct nl_object *) filter, cb, arg);
+	rtnl_cls_put(filter);
+}
+
+/** @} */
+
+static struct rtnl_tc_type_ops class_ops = {
+	.tt_type		= RTNL_TC_TYPE_CLASS,
+	.tt_dump_prefix		= "class",
+	.tt_dump = {
+	    [NL_DUMP_DETAILS]	= class_dump_details,
+	},
+};
+
+static struct nl_object_ops class_obj_ops = {
+	.oo_name		= "route/class",
+	.oo_size		= sizeof(struct rtnl_class),
+	.oo_free_data         	= rtnl_tc_free_data,
+	.oo_clone		= rtnl_tc_clone,
+	.oo_dump = {
+	    [NL_DUMP_LINE]	= rtnl_tc_dump_line,
+	    [NL_DUMP_DETAILS]	= rtnl_tc_dump_details,
+	    [NL_DUMP_STATS]	= rtnl_tc_dump_stats,
+	},
+	.oo_compare		= rtnl_tc_compare,
+	.oo_id_attrs		= (TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE),
+};
+
 static struct nl_cache_ops rtnl_class_ops = {
 	.co_name		= "route/class",
 	.co_hdrsize		= sizeof(struct tcmsg),
@@ -286,6 +453,7 @@
 					END_OF_MSGTYPES_LIST,
 				  },
 	.co_protocol		= NETLINK_ROUTE,
+	.co_groups		= tc_groups,
 	.co_request_update	= &class_request_update,
 	.co_msg_parser		= &class_msg_parser,
 	.co_obj_ops		= &class_obj_ops,
@@ -293,12 +461,14 @@
 
 static void __init class_init(void)
 {
+	rtnl_tc_type_register(&class_ops);
 	nl_cache_mngt_register(&rtnl_class_ops);
 }
 
 static void __exit class_exit(void)
 {
 	nl_cache_mngt_unregister(&rtnl_class_ops);
+	rtnl_tc_type_unregister(&class_ops);
 }
 
 /** @} */
diff --git a/lib/route/class_api.c b/lib/route/class_api.c
deleted file mode 100644
index 374cf0f..0000000
--- a/lib/route/class_api.c
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * lib/route/class_api.c            Queueing Classes Module API
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @ingroup class
- * @defgroup class_api Class Modules
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/route/tc.h>
-#include <netlink/route/class.h>
-#include <netlink/route/class-modules.h>
-#include <netlink/utils.h>
-
-static struct rtnl_class_ops *class_ops_list;
-
-/**
- * @name Module API
- * @{
- */
-
-/**
- * Register a class module
- * @arg cops		class module operations
- */
-int rtnl_class_register(struct rtnl_class_ops *cops)
-{
-	struct rtnl_class_ops *o, **op;
-
-	if (!cops->co_kind[0])
-		BUG();
-
-	for (op = &class_ops_list; (o = *op) != NULL; op = &o->co_next)
-		if (!strcasecmp(cops->co_kind, o->co_kind))
-			return -NLE_EXIST;
-
-	cops->co_next = NULL;
-	*op = cops;
-
-	return 0;
-}
-
-/**
- * Unregister a class module
- * @arg cops		class module operations
- */
-int rtnl_class_unregister(struct rtnl_class_ops *cops)
-{
-	struct rtnl_class_ops *o, **op;
-
-	for (op = &class_ops_list; (o = *op) != NULL; op = &o->co_next)
-		if (!strcasecmp(cops->co_kind, o->co_kind))
-			break;
-
-	if (!o)
-		return -NLE_OBJ_NOTFOUND;
-
-	*op = cops->co_next;
-
-	return 0;
-}
-
-struct rtnl_class_ops *__rtnl_class_lookup_ops(const char *kind)
-{
-	struct rtnl_class_ops *cops;
-
-	for (cops = class_ops_list; cops; cops = cops->co_next)
-		if (!strcmp(kind, cops->co_kind))
-			return cops;
-
-	return NULL;
-}
-
-/**
- * Lookup class operations for a class object
- * @arg class		Class object.
- *
- * @return Class operations or NULL if not found.
- */
-struct rtnl_class_ops *rtnl_class_lookup_ops(struct rtnl_class *class)
-{
-	if (!class->c_ops)
-		class->c_ops = __rtnl_class_lookup_ops(class->c_kind);
-
-	return class->c_ops;
-}
-
-
-/** @} */
-
-/** @} */
diff --git a/lib/route/class_obj.c b/lib/route/class_obj.c
deleted file mode 100644
index 5c2e5be..0000000
--- a/lib/route/class_obj.c
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * lib/route/class.c            Queueing Classes
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @ingroup class
- * @defgroup class_obj Class Object
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/route/tc.h>
-#include <netlink/route/class.h>
-#include <netlink/route/class-modules.h>
-#include <netlink/route/qdisc.h>
-#include <netlink/route/classifier.h>
-#include <netlink/utils.h>
-
-static void class_free_data(struct nl_object *obj)
-{
-	struct rtnl_class *class = (struct rtnl_class *) obj;
-	struct rtnl_class_ops *cops;
-	
-	tca_free_data((struct rtnl_tca *) class);
-
-	cops = rtnl_class_lookup_ops(class);
-	if (cops && cops->co_free_data)
-		cops->co_free_data(class);
-}
-
-static int class_clone(struct nl_object *_dst, struct nl_object *_src)
-{
-	struct rtnl_class *dst = nl_object_priv(_dst);
-	struct rtnl_class *src = nl_object_priv(_src);
-	struct rtnl_class_ops *cops;
-	int err;
-
-	err = tca_clone((struct rtnl_tca *) dst, (struct rtnl_tca *) src);
-	if (err < 0)
-		goto errout;
-
-	cops = rtnl_class_lookup_ops(src);
-	if (cops && cops->co_clone)
-		err = cops->co_clone(dst, src);
-errout:
-	return err;
-}
-
-static void class_dump_line(struct nl_object *obj, struct nl_dump_params *p)
-{
-	struct rtnl_class *class = (struct rtnl_class *) obj;
-	struct rtnl_class_ops *cops;
-
-	tca_dump_line((struct rtnl_tca *) class, "class", p);
-
-	cops = rtnl_class_lookup_ops(class);
-	if (cops && cops->co_dump[NL_DUMP_LINE])
-		cops->co_dump[NL_DUMP_LINE](class, p);
-	nl_dump(p, "\n");
-}
-
-static void class_dump_details(struct nl_object *obj, struct nl_dump_params *p)
-{
-	struct rtnl_class *class = (struct rtnl_class *) obj;
-	struct rtnl_class_ops *cops;
-
-	class_dump_line(obj, p);
-	tca_dump_details((struct rtnl_tca *) class, p);
-	
-	if (class->c_info) {
-		char buf[32];
-		nl_dump(p, "child-qdisc %s ",
-			rtnl_tc_handle2str(class->c_info, buf, sizeof(buf)));
-	}
-
-	cops = rtnl_class_lookup_ops(class);
-	if (cops && cops->co_dump[NL_DUMP_DETAILS])
-		cops->co_dump[NL_DUMP_DETAILS](class, p);
-	else if (!class->c_info)
-		nl_dump(p, "noop (no leaf qdisc)");
-
-	nl_dump(p, "\n");
-}
-
-static void class_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
-{
-	struct rtnl_class *class = (struct rtnl_class *) obj;
-	struct rtnl_class_ops *cops;
-
-	class_dump_details(obj, p);
-	tca_dump_stats((struct rtnl_tca *) class, p);
-	nl_dump(p, "\n");
-
-	cops = rtnl_class_lookup_ops(class);
-	if (cops && cops->co_dump[NL_DUMP_STATS])
-		cops->co_dump[NL_DUMP_STATS](class, p);
-}
-
-/**
- * @name Allocation/Freeing
- * @{
- */
-
-struct rtnl_class *rtnl_class_alloc(void)
-{
-	return (struct rtnl_class *) nl_object_alloc(&class_obj_ops);
-}
-
-void rtnl_class_put(struct rtnl_class *class)
-{
-	nl_object_put((struct nl_object *) class);
-}
-
-/** @} */
-
-/**
- * @name Leaf Qdisc
- * @{
- */
-
-/**
- * Lookup the leaf qdisc of a class
- * @arg class		the parent class
- * @arg cache		a qdisc cache including at laest all qdiscs of the
- *                      interface the specified class is attached to
- * @return The qdisc from the cache or NULL if the class has no leaf qdisc
- */
-struct rtnl_qdisc *rtnl_class_leaf_qdisc(struct rtnl_class *class,
-					 struct nl_cache *cache)
-{
-	struct rtnl_qdisc *leaf;
-
-	if (!class->c_info)
-		return NULL;
-
-	leaf = rtnl_qdisc_get_by_parent(cache, class->c_ifindex,
-					class->c_handle);
-	if (!leaf || leaf->q_handle != class->c_info)
-		return NULL;
-
-	return leaf;
-}
-
-/** @} */
-
-
-/**
- * @name Iterators
- * @{
- */
-
-/**
- * Call a callback for each child of a class
- * @arg class		the parent class
- * @arg cache		a class cache including all classes of the interface
- *                      the specified class is attached to
- * @arg cb              callback function
- * @arg arg             argument to be passed to callback function
- */
-void rtnl_class_foreach_child(struct rtnl_class *class, struct nl_cache *cache,
-			      void (*cb)(struct nl_object *, void *), void *arg)
-{
-	struct rtnl_class *filter;
-	
-	filter = rtnl_class_alloc();
-	if (!filter)
-		return;
-
-	rtnl_class_set_parent(filter, class->c_handle);
-	rtnl_class_set_ifindex(filter, class->c_ifindex);
-	rtnl_class_set_kind(filter, class->c_kind);
-
-	nl_cache_foreach_filter(cache, (struct nl_object *) filter, cb, arg);
-	rtnl_class_put(filter);
-}
-
-/**
- * Call a callback for each classifier attached to the class
- * @arg class		the parent class
- * @arg cache		a filter cache including at least all the filters
- *                      attached to the specified class
- * @arg cb              callback function
- * @arg arg             argument to be passed to callback function
- */
-void rtnl_class_foreach_cls(struct rtnl_class *class, struct nl_cache *cache,
-			    void (*cb)(struct nl_object *, void *), void *arg)
-{
-	struct rtnl_cls *filter;
-
-	filter = rtnl_cls_alloc();
-	if (!filter)
-		return;
-
-	rtnl_cls_set_ifindex(filter, class->c_ifindex);
-	rtnl_cls_set_parent(filter, class->c_parent);
-
-	nl_cache_foreach_filter(cache, (struct nl_object *) filter, cb, arg);
-	rtnl_cls_put(filter);
-}
-
-/** @} */
-
-
-/**
- * @name Attributes
- * @{
- */
-
-void rtnl_class_set_ifindex(struct rtnl_class *class, int ifindex)
-{
-	tca_set_ifindex((struct rtnl_tca *) class, ifindex);
-}
-
-int rtnl_class_get_ifindex(struct rtnl_class *class)
-{
-	return tca_get_ifindex((struct rtnl_tca *) class);
-}
-
-void rtnl_class_set_handle(struct rtnl_class *class, uint32_t handle)
-{
-	tca_set_handle((struct rtnl_tca *) class, handle);
-}
-
-uint32_t rtnl_class_get_handle(struct rtnl_class *class)
-{
-	return tca_get_handle((struct rtnl_tca *) class);
-}
-
-void rtnl_class_set_parent(struct rtnl_class *class, uint32_t parent)
-{
-	tca_set_parent((struct rtnl_tca *) class, parent);
-}
-
-uint32_t rtnl_class_get_parent(struct rtnl_class *class)
-{
-	return tca_get_parent((struct rtnl_tca *) class);
-}
-
-void rtnl_class_set_kind(struct rtnl_class *class, const char *name)
-{
-	tca_set_kind((struct rtnl_tca *) class, name);
-	class->c_ops = __rtnl_class_lookup_ops(name);
-}
-
-char *rtnl_class_get_kind(struct rtnl_class *class)
-{
-	return tca_get_kind((struct rtnl_tca *) class);
-}
-
-uint64_t rtnl_class_get_stat(struct rtnl_class *class,
-			     enum rtnl_tc_stats_id id)
-{
-	return tca_get_stat((struct rtnl_tca *) class, id);
-}
-
-/** @} */
-
-struct nl_object_ops class_obj_ops = {
-	.oo_name		= "route/class",
-	.oo_size		= sizeof(struct rtnl_class),
-	.oo_free_data         	= class_free_data,
-	.oo_clone		= class_clone,
-	.oo_dump = {
-	    [NL_DUMP_LINE]	= class_dump_line,
-	    [NL_DUMP_DETAILS]	= class_dump_details,
-	    [NL_DUMP_STATS]	= class_dump_stats,
-	},
-	.oo_compare		= tca_compare,
-	.oo_id_attrs		= (TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE),
-};
-
-/** @} */
diff --git a/lib/route/classid.c b/lib/route/classid.c
new file mode 100644
index 0000000..f2d3a01
--- /dev/null
+++ b/lib/route/classid.c
@@ -0,0 +1,456 @@
+/*
+ * lib/route/classid.c       ClassID Management
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup tc
+ * @defgroup classid ClassID Management
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/route/tc.h>
+
+struct classid_map
+{
+	uint32_t		classid;
+	char *			name;
+	struct nl_list_head	name_list;
+};
+
+#define CLASSID_NAME_HT_SIZ 256
+
+static struct nl_list_head tbl_name[CLASSID_NAME_HT_SIZ];
+
+static void *id_root = NULL;
+
+static int compare_id(const void *pa, const void *pb)
+{
+	const struct classid_map *ma = pa;
+	const struct classid_map *mb = pb;
+
+	if (ma->classid < mb->classid)
+		return -1;
+
+	if (ma->classid > mb->classid)
+		return 1;
+
+	return 0;
+}
+
+/* djb2 */
+static unsigned int classid_tbl_hash(const char *str)
+{
+	unsigned long hash = 5381;
+	int c;
+
+	while ((c = *str++))
+		hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
+
+	return hash % CLASSID_NAME_HT_SIZ;
+}
+
+static int classid_lookup(const char *name, uint32_t *result)
+{
+	struct classid_map *map;
+	int n = classid_tbl_hash(name);
+
+	nl_list_for_each_entry(map, &tbl_name[n], name_list) {
+		if (!strcasecmp(map->name, name)) {
+			*result = map->classid;
+			return 0;
+		}
+	}
+
+	return -NLE_OBJ_NOTFOUND;
+}
+
+static char *name_lookup(const uint32_t classid)
+{
+	void *res;
+	struct classid_map cm = {
+		.classid = classid,
+		.name = "search entry",
+	};
+
+	if ((res = tfind(&cm, &id_root, &compare_id)))
+		return (*(struct classid_map **) res)->name;
+
+	return NULL;
+}
+
+/**
+ * @name Traffic Control Handle Translations
+ * @{
+ */
+
+/**
+ * Convert a traffic control handle to a character string (Reentrant).
+ * @arg handle		traffic control handle
+ * @arg buf		destination buffer
+ * @arg len		buffer length
+ *
+ * Converts a tarffic control handle to a character string in the
+ * form of \c MAJ:MIN and stores it in the specified destination buffer.
+ *
+ * @return The destination buffer or the type encoded in hexidecimal
+ *         form if no match was found.
+ */
+char *rtnl_tc_handle2str(uint32_t handle, char *buf, size_t len)
+{
+	if (TC_H_ROOT == handle)
+		snprintf(buf, len, "root");
+	else if (TC_H_UNSPEC == handle)
+		snprintf(buf, len, "none");
+	else if (TC_H_INGRESS == handle)
+		snprintf(buf, len, "ingress");
+	else {
+		char *name;
+
+		if ((name = name_lookup(handle)))
+			snprintf(buf, len, "%s", name);
+		else if (0 == TC_H_MAJ(handle))
+			snprintf(buf, len, ":%x", TC_H_MIN(handle));
+		else if (0 == TC_H_MIN(handle))
+			snprintf(buf, len, "%x:", TC_H_MAJ(handle) >> 16);
+		else
+			snprintf(buf, len, "%x:%x",
+				TC_H_MAJ(handle) >> 16, TC_H_MIN(handle));
+	}
+
+	return buf;
+}
+
+/**
+ * Convert a charactering strint to a traffic control handle
+ * @arg str		traffic control handle as character string
+ * @arg res		destination buffer
+ *
+ * Converts the provided character string specifying a traffic
+ * control handle to the corresponding numeric value.
+ *
+ * The handle must be provided in one of the following formats:
+ *  - NAME
+ *  - root
+ *  - none
+ *  - MAJ:
+ *  - :MIN
+ *  - NAME:MIN
+ *  - MAJ:MIN
+ *  - MAJMIN
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_tc_str2handle(const char *str, uint32_t *res)
+{
+	char *colon, *end;
+	uint32_t h;
+	int err;
+
+	if (!strcasecmp(str, "root")) {
+		*res = TC_H_ROOT;
+		return 0;
+	}
+
+	if (!strcasecmp(str, "none")) {
+		*res = TC_H_UNSPEC;
+		return 0;
+	}
+
+	if (!strcasecmp(str, "ingress")) {
+		*res = TC_H_INGRESS;
+		return 0;
+	}
+
+	h = strtoul(str, &colon, 16);
+
+	/* MAJ is not a number */
+	if (colon == str) {
+not_a_number:
+		if (*colon == ':') {
+			/* :YYYY */
+			h = 0;
+		} else {
+			size_t len;
+			char name[64] = { 0 };
+
+			if (!(colon = strpbrk(str, ":"))) {
+				/* NAME */
+				return classid_lookup(str, res);
+			} else {
+				/* NAME:YYYY */
+				len = colon - str;
+				if (len >= sizeof(name))
+					return -NLE_INVAL;
+
+				memcpy(name, str, len);
+
+				if ((err = classid_lookup(name, &h)) < 0)
+					return err;
+
+				/* Name must point to a qdisc alias */
+				if (TC_H_MIN(h))
+					return -NLE_INVAL;
+
+				/* NAME: is not allowed */
+				if (colon[1] == '\0')
+					return -NLE_INVAL;
+
+				goto update;
+			}
+		}
+	}
+
+	if (':' == *colon) {
+		/* check if we would lose bits */
+		if (TC_H_MAJ(h))
+			return -NLE_RANGE;
+		h <<= 16;
+
+		if ('\0' == colon[1]) {
+			/* XXXX: */
+			*res = h;
+		} else {
+			/* XXXX:YYYY */
+			uint32_t l;
+
+update:
+			l = strtoul(colon+1, &end, 16);
+
+			/* check if we overlap with major part */
+			if (TC_H_MAJ(l))
+				return -NLE_RANGE;
+
+			if ('\0' != *end)
+				return -NLE_INVAL;
+
+			*res = (h | l);
+		}
+	} else if ('\0' == *colon) {
+		/* XXXXYYYY */
+		*res = h;
+	} else
+		goto not_a_number;
+
+	return 0;
+}
+
+static void free_nothing(void *arg)
+{
+}
+
+static void classid_map_free(struct classid_map *map)
+{
+	if (!map)
+		return;
+
+	free(map->name);
+	free(map);
+}
+
+static void clear_hashtable(void)
+{
+	int i;
+
+	for (i = 0; i < CLASSID_NAME_HT_SIZ; i++) {
+		struct classid_map *map, *n;
+
+		nl_list_for_each_entry_safe(map, n, &tbl_name[i], name_list)
+			classid_map_free(map);
+
+		nl_init_list_head(&tbl_name[i]);
+
+	}
+
+	if (id_root) {
+		tdestroy(&id_root, &free_nothing);
+		id_root = NULL;
+	}
+}
+
+static int classid_map_add(uint32_t classid, const char *name)
+{
+	struct classid_map *map;
+	int n;
+
+	if (!(map = calloc(1, sizeof(*map))))
+		return -NLE_NOMEM;
+
+	map->classid = classid;
+	map->name = strdup(name);
+
+	n = classid_tbl_hash(map->name);
+	nl_list_add_tail(&map->name_list, &tbl_name[n]);
+
+	if (!tsearch((void *) map, &id_root, &compare_id)) {
+		classid_map_free(map);
+		return -NLE_NOMEM;
+	}
+
+	return 0;
+}
+
+/**
+ * (Re-)read classid file
+ *
+ * Rereads the contents of the classid file (typically found at the location
+ * /etc/libnl/classid) and refreshes the classid maps.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_tc_read_classid_file(void)
+{
+	static time_t last_read;
+	struct stat st;
+	char buf[256], *path;
+	FILE *fd;
+	int err;
+
+	if (build_sysconf_path(&path, "classid") < 0)
+		return -NLE_NOMEM;
+
+	/* if stat fails, just (re-)read the file */
+	if (stat(path, &st) == 0) {
+		/* Don't re-read file if file is unchanged */
+		if (last_read == st.st_mtime) {
+			err = 0;
+			goto errout;
+		}
+	}
+
+	if (!(fd = fopen(path, "r"))) {
+		err = -nl_syserr2nlerr(errno);
+		goto errout;
+	}
+
+	clear_hashtable();
+
+	while (fgets(buf, sizeof(buf), fd)) {
+		uint32_t classid;
+		char *ptr, *tok;
+
+		/* ignore comments and empty lines */
+		if (*buf == '#' || *buf == '\n' || *buf == '\r')
+			continue;
+
+		/* token 1 */
+		if (!(tok = strtok_r(buf, " \t", &ptr))) {
+			err = -NLE_INVAL;
+			goto errout_close;
+		}
+
+		if ((err = rtnl_tc_str2handle(tok, &classid)) < 0)
+			goto errout_close;
+
+		if (!(tok = strtok_r(NULL, " \t\n\r#", &ptr))) {
+			err = -NLE_INVAL;
+			goto errout_close;
+		}
+
+		if ((err = classid_map_add(classid, tok)) < 0)
+			goto errout_close;
+	}
+
+	err = 0;
+	last_read = st.st_mtime;
+
+errout_close:
+	fclose(fd);
+errout:
+	free(path);
+
+	return err;
+
+}
+
+int rtnl_classid_generate(const char *name, uint32_t *result, uint32_t parent)
+{
+	static uint32_t base = 0x4000 << 16;
+	uint32_t classid;
+	char *path;
+	FILE *fd;
+	int err = 0;
+
+	if (parent == TC_H_ROOT || parent == TC_H_INGRESS) {
+		do {
+			base += (1 << 16);
+			if (base == TC_H_MAJ(TC_H_ROOT))
+				base = 0x4000 << 16;
+		} while (name_lookup(base));
+
+		classid = base;
+	} else {
+		classid = TC_H_MAJ(parent);
+		do {
+			if (TC_H_MIN(++classid) == TC_H_MIN(TC_H_ROOT))
+				return -NLE_RANGE;
+		} while (name_lookup(classid));
+	}
+
+	NL_DBG(2, "Generated new classid %#x\n", classid);
+
+	if (build_sysconf_path(&path, "classid") < 0)
+		return -NLE_NOMEM;
+
+	if (!(fd = fopen(path, "a"))) {
+		err = -nl_syserr2nlerr(errno);
+		goto errout;
+	}
+
+	fprintf(fd, "%x:", TC_H_MAJ(classid) >> 16);
+	if (TC_H_MIN(classid))
+		fprintf(fd, "%x", TC_H_MIN(classid));
+	fprintf(fd, "\t\t\t%s\n", name);
+
+	fclose(fd);
+
+	if ((err = classid_map_add(classid, name)) < 0) {
+		/*
+		 * Error adding classid map, re-read classid file is best
+		 * option here. It is likely to fail as well but better
+		 * than nothing, entry was added to the file already anyway.
+		 */
+		rtnl_tc_read_classid_file();
+	}
+
+	*result = classid;
+	err = 0;
+errout:
+	free(path);
+
+	return err;
+}
+
+/** @} */
+
+static void __init classid_init(void)
+{
+	int err, i;
+
+	for (i = 0; i < CLASSID_NAME_HT_SIZ; i++)
+		nl_init_list_head(&tbl_name[i]);
+
+	if ((err = rtnl_tc_read_classid_file()) < 0)
+		NL_DBG(1, "Failed to read classid file: %s\n", nl_geterror(err));
+}
+
+static void free_map(void *map) {
+	free(((struct classid_map *)map)->name);
+	free(map);
+};
+
+static void __exit classid_exit(void)
+{
+	tdestroy(id_root, free_map);
+}
+/** @} */
diff --git a/lib/route/cls.c b/lib/route/cls.c
index cbf0345..649a7d0 100644
--- a/lib/route/cls.c
+++ b/lib/route/cls.c
@@ -6,88 +6,45 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup tc
  * @defgroup cls Classifiers
- *
- * @par Classifier Identification
- * - protocol
- * - priority
- * - parent
- * - interface
- * - kind
- * - handle
- * 
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
-#include <netlink/route/tc.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/classifier.h>
-#include <netlink/route/classifier-modules.h>
 #include <netlink/route/link.h>
 
+/** @cond SKIP */
+#define CLS_ATTR_PRIO		(TCA_ATTR_MAX << 1)
+#define CLS_ATTR_PROTOCOL	(TCA_ATTR_MAX << 2)
+/** @endcond */
+
+static struct nl_object_ops cls_obj_ops;
 static struct nl_cache_ops rtnl_cls_ops;
 
-static int cls_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
-			  struct nlmsghdr *nlh, struct nl_parser_param *pp)
-{
-	struct rtnl_cls_ops *cops;
-	struct rtnl_cls *cls;
-	int err;
-
-	cls = rtnl_cls_alloc();
-	if (!cls) {
-		err = -NLE_NOMEM;
-		goto errout;
-	}
-	cls->ce_msgtype = nlh->nlmsg_type;
-
-	err = tca_msg_parser(nlh, (struct rtnl_tca *) cls);
-	if (err < 0)
-		goto errout_free;
-
-	cls->c_prio = TC_H_MAJ(cls->c_info) >> 16;
-	cls->c_protocol = ntohs(TC_H_MIN(cls->c_info));
-
-	cops = rtnl_cls_lookup_ops(cls);
-	if (cops && cops->co_msg_parser && (err = cops->co_msg_parser(cls)) < 0)
-		goto errout_free;
-
-	err = pp->pp_cb((struct nl_object *) cls, pp);
-errout_free:
-	rtnl_cls_put(cls);
-errout:
-	return err;
-}
-
-static int cls_request_update(struct nl_cache *cache, struct nl_sock *sk)
-{
-	struct tcmsg tchdr = {
-		.tcm_family = AF_UNSPEC,
-		.tcm_ifindex = cache->c_iarg1,
-		.tcm_parent = cache->c_iarg2,
-	};
-
-	return nl_send_simple(sk, RTM_GETTFILTER, NLM_F_DUMP, &tchdr,
-			      sizeof(tchdr));
-}
-
 
 static int cls_build(struct rtnl_cls *cls, int type, int flags,
 		     struct nl_msg **result)
 {
-	struct rtnl_cls_ops *cops;
 	int err, prio, proto;
 	struct tcmsg *tchdr;
+	uint32_t required = TCA_ATTR_IFINDEX;
 
-	err = tca_build_msg((struct rtnl_tca *) cls, type, flags, result);
+	if ((cls->ce_mask & required) != required) {
+		APPBUG("ifindex must be specified");
+		return -NLE_MISSING_ATTR;
+	}
+
+	err = rtnl_tc_msg_build(TC_CAST(cls), type, flags, result);
 	if (err < 0)
 		return err;
 
@@ -96,66 +53,133 @@
 	proto = rtnl_cls_get_protocol(cls);
 	tchdr->tcm_info = TC_H_MAKE(prio << 16, htons(proto));
 
-	cops = rtnl_cls_lookup_ops(cls);
-	if (cops && cops->co_get_opts) {
-		struct nl_msg *opts;
-
-		if (!(opts = nlmsg_alloc())) {
-			err = -NLE_NOMEM;
-			goto errout;
-		}
-
-		if (!(err = cops->co_get_opts(cls, opts)))
-			err = nla_put_nested(*result, TCA_OPTIONS, opts);
-
-		nlmsg_free(opts);
-		if (err < 0)
-			goto errout;
-	}
-
 	return 0;
-errout:
-	nlmsg_free(*result);
-	return err;
 }
 
 /**
- * @name Classifier Addition/Modification/Deletion
+ * @name Allocation/Freeing
+ * @{
+ */
+
+struct rtnl_cls *rtnl_cls_alloc(void)
+{
+	struct rtnl_tc *tc;
+
+	tc = TC_CAST(nl_object_alloc(&cls_obj_ops));
+	if (tc)
+		tc->tc_type = RTNL_TC_TYPE_CLS;
+
+	return (struct rtnl_cls *) tc;
+}
+
+void rtnl_cls_put(struct rtnl_cls *cls)
+{
+	nl_object_put((struct nl_object *) cls);
+}
+
+/** @} */
+
+/**
+ * @name Attributes
+ * @{
+ */
+
+void rtnl_cls_set_prio(struct rtnl_cls *cls, uint16_t prio)
+{
+	cls->c_prio = prio;
+	cls->ce_mask |= CLS_ATTR_PRIO;
+}
+
+uint16_t rtnl_cls_get_prio(struct rtnl_cls *cls)
+{
+	if (cls->ce_mask & CLS_ATTR_PRIO)
+		return cls->c_prio;
+	else
+		return 0;
+}
+
+void rtnl_cls_set_protocol(struct rtnl_cls *cls, uint16_t protocol)
+{
+	cls->c_protocol = protocol;
+	cls->ce_mask |= CLS_ATTR_PROTOCOL;
+}
+
+uint16_t rtnl_cls_get_protocol(struct rtnl_cls *cls)
+{
+	if (cls->ce_mask & CLS_ATTR_PROTOCOL)
+		return cls->c_protocol;
+	else
+		return ETH_P_ALL;
+}
+
+/** @} */
+
+
+/**
+ * @name Addition/Modification/Deletion
  * @{
  */
 
 /**
- * Build a netlink message to add a new classifier
- * @arg cls		classifier to add
- * @arg flags		additional netlink message flags
- * @arg result		Pointer to store resulting message.
+ * Build a netlink message requesting the addition of a classifier
+ * @arg cls		Classifier to add 
+ * @arg flags		Additional netlink message flags
+ * @arg result		Pointer to store resulting netlink message
  *
- * Builds a new netlink message requesting an addition of a classifier
- * The netlink message header isn't fully equipped with all relevant
- * fields and must be sent out via nl_send_auto_complete() or
- * supplemented as needed. \a classifier must contain the attributes of
- * the new classifier set via \c rtnl_cls_set_* functions. \a opts
- * may point to the clsasifier specific options.
+ * The behaviour of this function is identical to rtnl_cls_add() with
+ * the exception that it will not send the message but return it int the
+ * provided return pointer instead.
+ *
+ * @see rtnl_cls_add()
  *
  * @return 0 on success or a negative error code.
  */
 int rtnl_cls_build_add_request(struct rtnl_cls *cls, int flags,
 			       struct nl_msg **result)
 {
-	return cls_build(cls, RTM_NEWTFILTER, NLM_F_CREATE | flags, result);
+	if (!(flags & NLM_F_CREATE) && !(cls->ce_mask & CLS_ATTR_PRIO)) {
+		APPBUG("prio must be specified if not a new classifier");
+		return -NLE_MISSING_ATTR;
+	}
+
+	return cls_build(cls, RTM_NEWTFILTER, flags, result);
 }
 
 /**
- * Add a new classifier
- * @arg sk		Netlink socket.
- * @arg cls 		classifier to add
- * @arg flags		additional netlink message flags
+ * Add/Update classifier
+ * @arg sk		Netlink socket
+ * @arg cls		Classifier to add/update
+ * @arg flags		Additional netlink message flags
  *
- * Builds a netlink message by calling rtnl_cls_build_add_request(),
- * sends the request to the kernel and waits for the next ACK to be
- * received and thus blocks until the request has been processed.
+ * Builds a \c RTM_NEWTFILTER netlink message requesting the addition
+ * of a new classifier and sends the message to the kernel. The
+ * configuration of the classifier is derived from the attributes of
+ * the specified traffic class.
  *
- * @return 0 on sucess or a negative error if an error occured.
+ * The following flags may be specified:
+ *  - \c NLM_F_CREATE:  Create classifier if it does not exist,
+ *                      otherwise -NLE_OBJ_NOTFOUND is returned.
+ *  - \c NLM_F_EXCL:    Return -NLE_EXISTS if a classifier with
+ *                      matching handle exists already.
+ *
+ * Existing classifiers with matching handles will be updated, unless
+ * the flag \c NLM_F_EXCL is specified. If no matching classifier
+ * exists, it will be created if the flag \c NLM_F_CREATE is set,
+ * otherwise the error -NLE_OBJ_NOTFOUND is returned. 
+ *
+ * If the parent qdisc does not support classes, the error
+ * \c NLE_OPNOTSUPP is returned.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
+ *       this function to return immediately after sending. In this case,
+ *       it is the responsibility of the caller to handle any error
+ *       messages returned.
+ *
+ * @return 0 on success or a negative error code.
  */
 int rtnl_cls_add(struct nl_sock *sk, struct rtnl_cls *cls, int flags)
 {
@@ -164,13 +188,8 @@
 	
 	if ((err = rtnl_cls_build_add_request(cls, flags, &msg)) < 0)
 		return err;
-	
-	err = nl_send_auto_complete(sk, msg);
-	nlmsg_free(msg);
-	if (err < 0)
-		return err;
 
-	return nl_wait_for_ack(sk);
+	return nl_send_sync(sk, msg);
 }
 
 /**
@@ -212,45 +231,66 @@
 	if ((err = rtnl_cls_build_change_request(cls, flags, &msg)) < 0)
 		return err;
 	
-	err = nl_send_auto_complete(sk, msg);
-	nlmsg_free(msg);
-	if (err < 0)
-		return err;
-
-	return nl_wait_for_ack(sk);
+	return nl_send_sync(sk, msg);
 }
 
 /**
- * Build a netlink request message to delete a classifier
- * @arg cls		classifier to delete
- * @arg flags		additional netlink message flags
- * @arg result		Pointer to store resulting message.
+ * Build netlink message requesting the deletion of a classifier
+ * @arg cls		Classifier to delete
+ * @arg flags		Additional netlink message flags
+ * @arg result		Pointer to store resulting netlink message
  *
- * Builds a new netlink message requesting a deletion of a classifier.
- * The netlink message header isn't fully equipped with all relevant
- * fields and must thus be sent out via nl_send_auto_complete()
- * or supplemented as needed.
+ * The behaviour of this function is identical to rtnl_cls_delete() with
+ * the exception that it will not send the message but return it in the
+ * provided return pointer instead.
+ *
+ * @see rtnl_cls_delete()
  *
  * @return 0 on success or a negative error code.
  */
 int rtnl_cls_build_delete_request(struct rtnl_cls *cls, int flags,
 				  struct nl_msg **result)
 {
+	uint32_t required = CLS_ATTR_PRIO;
+
+	if ((cls->ce_mask & required) != required) {
+		APPBUG("prio must be specified");
+		return -NLE_MISSING_ATTR;
+	}
+
 	return cls_build(cls, RTM_DELTFILTER, flags, result);
 }
 
-
 /**
- * Delete a classifier
- * @arg sk		Netlink socket.
- * @arg cls		classifier to delete
- * @arg flags		additional netlink message flags
+ * Delete classifier
+ * @arg sk		Netlink socket
+ * @arg cls		Classifier to delete
+ * @arg flags		Additional netlink message flags
  *
- * Builds a netlink message by calling rtnl_cls_build_delete_request(),
- * sends the request to the kernel and waits for the next ACK to be
- * received and thus blocks until the request has been processed.
+ * Builds a \c RTM_DELTFILTER netlink message requesting the deletion
+ * of a classifier and sends the message to the kernel.
  *
- * @return 0 on sucess or a negative error if an error occured.
+ * The message is constructed out of the following attributes:
+ * - \c ifindex (required)
+ * - \c prio (required)
+ * - \c protocol (required)
+ * - \c handle (required)
+ * - \c parent (optional, if not specified parent equals root-qdisc)
+ * - \c kind (optional, must match if provided)
+ *
+ * All other classifier attributes including all class type specific
+ * attributes are ignored.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
+ *       this function to return immediately after sending. In this case,
+ *       it is the responsibility of the caller to handle any error
+ *       messages returned.
+ *
+ * @return 0 on success or a negative error code.
  */
 int rtnl_cls_delete(struct nl_sock *sk, struct rtnl_cls *cls, int flags)
 {
@@ -260,35 +300,28 @@
 	if ((err = rtnl_cls_build_delete_request(cls, flags, &msg)) < 0)
 		return err;
 	
-	err = nl_send_auto_complete(sk, msg);
-	nlmsg_free(msg);
-	if (err < 0)
-		return err;
-
-	return nl_wait_for_ack(sk);
+	return nl_send_sync(sk, msg);
 }
 
 /** @} */
 
 /**
- * @name Cache Management
+ * @name Cache Related Functions
  * @{
  */
 
 /**
- * Build a classifier cache including all classifiers attached to the
- * specified class/qdisc on eht specified interface.
- * @arg sk		Netlink socket.
- * @arg ifindex		interface index of the link the classes are
- *                      attached to.
- * @arg parent          parent qdisc/class
- * @arg result		Pointer to store resulting cache.
+ * Allocate a cache and fill it with all configured classifiers
+ * @arg sk		Netlink socket
+ * @arg ifindex		Interface index of the network device
+ * @arg parent		Parent qdisc/traffic class class
+ * @arg result		Pointer to store the created cache
  *
- * Allocates a new cache, initializes it properly and updates it to
- * include all classes attached to the specified interface.
+ * Allocates a new classifier cache and fills it with a list of all
+ * configured classifier attached to the specified parent qdisc/traffic
+ * class on the specified network device. Release the cache with
+ * nl_cache_free().
  *
- * @note The caller is responsible for destroying and freeing the
- *       cache after using it.
  * @return 0 on success or a negative error code.
  */
 int rtnl_cls_alloc_cache(struct nl_sock *sk, int ifindex, uint32_t parent,			 struct nl_cache **result)
@@ -313,6 +346,61 @@
 
 /** @} */
 
+static void cls_dump_line(struct rtnl_tc *tc, struct nl_dump_params *p)
+{
+	struct rtnl_cls *cls = (struct rtnl_cls *) tc;
+	char buf[32];
+
+	nl_dump(p, " prio %u protocol %s", cls->c_prio,
+		nl_ether_proto2str(cls->c_protocol, buf, sizeof(buf)));
+}
+
+static int cls_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
+			  struct nlmsghdr *nlh, struct nl_parser_param *pp)
+{
+	struct rtnl_cls *cls;
+	int err;
+
+	if (!(cls = rtnl_cls_alloc()))
+		return -NLE_NOMEM;
+
+	if ((err = rtnl_tc_msg_parse(nlh, TC_CAST(cls))) < 0)
+		goto errout;
+
+	cls->c_prio = TC_H_MAJ(cls->c_info) >> 16;
+	if (cls->c_prio)
+		cls->ce_mask |= CLS_ATTR_PRIO;
+	cls->c_protocol = ntohs(TC_H_MIN(cls->c_info));
+	if (cls->c_protocol)
+		cls->ce_mask |= CLS_ATTR_PROTOCOL;
+
+	err = pp->pp_cb(OBJ_CAST(cls), pp);
+errout:
+	rtnl_cls_put(cls);
+
+	return err;
+}
+
+static int cls_request_update(struct nl_cache *cache, struct nl_sock *sk)
+{
+	struct tcmsg tchdr = {
+		.tcm_family = AF_UNSPEC,
+		.tcm_ifindex = cache->c_iarg1,
+		.tcm_parent = cache->c_iarg2,
+	};
+
+	return nl_send_simple(sk, RTM_GETTFILTER, NLM_F_DUMP, &tchdr,
+			      sizeof(tchdr));
+}
+
+static struct rtnl_tc_type_ops cls_ops = {
+	.tt_type		= RTNL_TC_TYPE_CLS,
+	.tt_dump_prefix		= "cls",
+	.tt_dump = {
+		[NL_DUMP_LINE]	= cls_dump_line,
+	},
+};
+
 static struct nl_cache_ops rtnl_cls_ops = {
 	.co_name		= "route/cls",
 	.co_hdrsize		= sizeof(struct tcmsg),
@@ -323,19 +411,36 @@
 					END_OF_MSGTYPES_LIST,
 				  },
 	.co_protocol		= NETLINK_ROUTE,
+	.co_groups		= tc_groups,
 	.co_request_update	= cls_request_update,
 	.co_msg_parser		= cls_msg_parser,
 	.co_obj_ops		= &cls_obj_ops,
 };
 
+static struct nl_object_ops cls_obj_ops = {
+	.oo_name		= "route/cls",
+	.oo_size		= sizeof(struct rtnl_cls),
+	.oo_free_data		= rtnl_tc_free_data,
+	.oo_clone		= rtnl_tc_clone,
+	.oo_dump = {
+	    [NL_DUMP_LINE]	= rtnl_tc_dump_line,
+	    [NL_DUMP_DETAILS]	= rtnl_tc_dump_details,
+	    [NL_DUMP_STATS]	= rtnl_tc_dump_stats,
+	},
+	.oo_compare		= rtnl_tc_compare,
+	.oo_id_attrs		= (TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE),
+};
+
 static void __init cls_init(void)
 {
+	rtnl_tc_type_register(&cls_ops);
 	nl_cache_mngt_register(&rtnl_cls_ops);
 }
 
 static void __exit cls_exit(void)
 {
 	nl_cache_mngt_unregister(&rtnl_cls_ops);
+	rtnl_tc_type_unregister(&cls_ops);
 }
 
 /** @} */
diff --git a/lib/route/cls/.gitignore b/lib/route/cls/.gitignore
new file mode 100644
index 0000000..30f4521
--- /dev/null
+++ b/lib/route/cls/.gitignore
@@ -0,0 +1,2 @@
+ematch_syntax.[ch]
+ematch_grammar.[ch]
diff --git a/lib/route/cls/basic.c b/lib/route/cls/basic.c
index 1460b72..6af3844 100644
--- a/lib/route/cls/basic.c
+++ b/lib/route/cls/basic.c
@@ -6,12 +6,12 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup cls
- * @defgroup basic Basic Classifier
+ * @defgroup cls_basic Basic Classifier
  *
  * @par Introduction
  * The basic classifier is the simplest form of a classifier. It does
@@ -22,115 +22,140 @@
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/classifier.h>
-#include <netlink/route/classifier-modules.h>
+#include <netlink/route/action.h>
 #include <netlink/route/cls/basic.h>
 #include <netlink/route/cls/ematch.h>
 
 struct rtnl_basic
 {
-	uint32_t			b_classid;
+	uint32_t			b_target;
 	struct rtnl_ematch_tree *	b_ematch;
 	int				b_mask;
+	struct rtnl_act *		b_act;
 };
 
 /** @cond SKIP */
-#define BASIC_ATTR_CLASSID	0x001
+#define BASIC_ATTR_TARGET	0x001
 #define BASIC_ATTR_EMATCH	0x002
+#define BASIC_ATTR_ACTION	0x004
 /** @endcond */
 
-static struct nla_policy basic_policy[TCA_FW_MAX+1] = {
+static struct nla_policy basic_policy[TCA_BASIC_MAX+1] = {
 	[TCA_BASIC_CLASSID]	= { .type = NLA_U32 },
 	[TCA_BASIC_EMATCHES]	= { .type = NLA_NESTED },
-	[TCA_BASIC_ACT]		= { .type = NLA_NESTED },
-	[TCA_BASIC_POLICE]	= { .type = NLA_NESTED },
 };
 
-static int basic_clone(struct rtnl_cls *_dst, struct rtnl_cls *_src)
+static int basic_clone(void *_dst, void *_src)
 {
 	return -NLE_OPNOTSUPP;
 }
 
-static void basic_free_data(struct rtnl_cls *cls)
+static void basic_free_data(struct rtnl_tc *tc, void *data)
 {
-	struct rtnl_basic *basic = rtnl_cls_data(cls);
+	struct rtnl_basic *b = data;
 
-	rtnl_ematch_tree_free(basic->b_ematch);
+	if (!b)
+		return;
+
+	if (b->b_act)
+		rtnl_act_put_all(&b->b_act);
+	rtnl_ematch_tree_free(b->b_ematch);
 }
 
-static int basic_msg_parser(struct rtnl_cls *cls)
+static int basic_msg_parser(struct rtnl_tc *tc, void *data)
 {
 	struct nlattr *tb[TCA_BASIC_MAX + 1];
-	struct rtnl_basic *basic = rtnl_cls_data(cls);
+	struct rtnl_basic *b = data;
 	int err;
 
-	err = tca_parse(tb, TCA_BASIC_MAX, (struct rtnl_tca *) cls, basic_policy);
+	err = tca_parse(tb, TCA_BASIC_MAX, tc, basic_policy);
 	if (err < 0)
 		return err;
 
 	if (tb[TCA_BASIC_CLASSID]) {
-		basic->b_classid = nla_get_u32(tb[TCA_BASIC_CLASSID]);
-		basic->b_mask |= BASIC_ATTR_CLASSID;
+		b->b_target = nla_get_u32(tb[TCA_BASIC_CLASSID]);
+		b->b_mask |= BASIC_ATTR_TARGET;
 	}
 
 	if (tb[TCA_BASIC_EMATCHES]) {
-		if ((err = rtnl_ematch_parse(tb[TCA_BASIC_EMATCHES],
-					     &basic->b_ematch)) < 0)
+		if ((err = rtnl_ematch_parse_attr(tb[TCA_BASIC_EMATCHES],
+					     &b->b_ematch)) < 0)
 			return err;
 
-		if (basic->b_ematch)
-			basic->b_mask |= BASIC_ATTR_EMATCH;
+		if (b->b_ematch)
+			b->b_mask |= BASIC_ATTR_EMATCH;
 	}
-
 	if (tb[TCA_BASIC_ACT]) {
-		/* XXX */
-	}
-
-	if (tb[TCA_BASIC_POLICE]) {
-		/* XXX */
+		b->b_mask |= BASIC_ATTR_ACTION;
+		err = rtnl_act_parse(&b->b_act, tb[TCA_BASIC_ACT]);
+		if (err)
+			return err;
 	}
 
 	return 0;
 }
 
-static void basic_dump_line(struct rtnl_cls *cls, struct nl_dump_params *p)
+static void basic_dump_line(struct rtnl_tc *tc, void *data,
+			    struct nl_dump_params *p)
 {
-	struct rtnl_basic *b = rtnl_cls_data(cls);
+	struct rtnl_basic *b = data;
 	char buf[32];
 
+	if (!b)
+		return;
+
 	if (b->b_mask & BASIC_ATTR_EMATCH)
 		nl_dump(p, " ematch");
 	else
 		nl_dump(p, " match-all");
 
-	if (b->b_mask & BASIC_ATTR_CLASSID)
-		nl_dump(p, " classify-to %s",
-			rtnl_tc_handle2str(b->b_classid, buf, sizeof(buf)));
+	if (b->b_mask & BASIC_ATTR_TARGET)
+		nl_dump(p, " target %s",
+			rtnl_tc_handle2str(b->b_target, buf, sizeof(buf)));
 }
 
-static void basic_dump_details(struct rtnl_cls *cls, struct nl_dump_params *p)
+static void basic_dump_details(struct rtnl_tc *tc, void *data,
+			       struct nl_dump_params *p)
 {
-	struct rtnl_basic *b = rtnl_cls_data(cls);
+	struct rtnl_basic *b = data;
+
+	if (!b)
+		return;
 
 	if (b->b_mask & BASIC_ATTR_EMATCH) {
-		nl_dump(p, "\n");
 		nl_dump_line(p, "    ematch ");
 		rtnl_ematch_tree_dump(b->b_ematch, p);
 	} else
 		nl_dump(p, "no options.\n");
 }
 
-static int basic_get_opts(struct rtnl_cls *cls, struct nl_msg *msg)
+static int basic_msg_fill(struct rtnl_tc *tc, void *data,
+			  struct nl_msg *msg)
 {
-	struct rtnl_basic *b = rtnl_cls_data(cls);
+	struct rtnl_basic *b = data;
 
-	if (!(b->b_mask & BASIC_ATTR_CLASSID))
-		return -NLE_MISSING_ATTR;
+	if (!b)
+		return 0;
 
-	NLA_PUT_U32(msg, TCA_BASIC_CLASSID, b->b_classid);
+	if (b->b_mask & BASIC_ATTR_TARGET)
+		NLA_PUT_U32(msg, TCA_BASIC_CLASSID, b->b_target);
+
+	if (b->b_mask & BASIC_ATTR_EMATCH &&
+	    rtnl_ematch_fill_attr(msg, TCA_BASIC_EMATCHES, b->b_ematch) < 0)
+		goto nla_put_failure;
+
+	if (b->b_mask & BASIC_ATTR_ACTION) {
+		int err;
+
+		err = rtnl_act_fill(msg, TCA_BASIC_ACT, b->b_act);
+		if (err)
+			return err;
+	}
 
 	return 0;
 
@@ -143,26 +168,33 @@
  * @{
  */
 
-int rtnl_basic_set_classid(struct rtnl_cls *cls, uint32_t classid)
+void rtnl_basic_set_target(struct rtnl_cls *cls, uint32_t target)
 {
-	struct rtnl_basic *b = rtnl_cls_data(cls);
+	struct rtnl_basic *b;
 
-	b->b_classid = classid;
-	b->b_mask |= BASIC_ATTR_CLASSID;
+	if (!(b = rtnl_tc_data(TC_CAST(cls))))
+		return;
 
-	return 0;
+	b->b_target = target;
+	b->b_mask |= BASIC_ATTR_TARGET;
 }
 
-uint32_t rtnl_basic_get_classid(struct rtnl_cls *cls)
+uint32_t rtnl_basic_get_target(struct rtnl_cls *cls)
 {
-	struct rtnl_basic *b = rtnl_cls_data(cls);
+	struct rtnl_basic *b;
 
-	return b->b_classid;
+	if (!(b = rtnl_tc_data(TC_CAST(cls))))
+		return 0;
+
+	return b->b_target;
 }
 
-int rtnl_basic_set_ematch(struct rtnl_cls *cls, struct rtnl_ematch_tree *tree)
+void rtnl_basic_set_ematch(struct rtnl_cls *cls, struct rtnl_ematch_tree *tree)
 {
-	struct rtnl_basic *b = rtnl_cls_data(cls);
+	struct rtnl_basic *b;
+
+	if (!(b = rtnl_tc_data(TC_CAST(cls))))
+		return;
 
 	if (b->b_ematch) {
 		rtnl_ematch_tree_free(b->b_ematch);
@@ -173,26 +205,67 @@
 
 	if (tree)
 		b->b_mask |= BASIC_ATTR_EMATCH;
-
-	return 0;
 }
 
 struct rtnl_ematch_tree *rtnl_basic_get_ematch(struct rtnl_cls *cls)
 {
-	struct rtnl_basic *b = rtnl_cls_data(cls);
+	struct rtnl_basic *b;
+
+	if (!(b = rtnl_tc_data(TC_CAST(cls))))
+		return NULL;
+
 	return b->b_ematch;
 }
 
+int rtnl_basic_add_action(struct rtnl_cls *cls, struct rtnl_act *act)
+{
+	struct rtnl_basic *b;
+
+	if (!act)
+		return 0;
+
+	if (!(b = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	b->b_mask |= BASIC_ATTR_ACTION;
+	/* In case user frees it */
+	rtnl_act_get(act);
+	return rtnl_act_append(&b->b_act, act);
+}
+
+int rtnl_basic_del_action(struct rtnl_cls *cls, struct rtnl_act *act)
+{
+	struct rtnl_basic *b;
+	int ret;
+
+	if (!act)
+		return 0;
+
+	if (!(b = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	if (!(b->b_mask & BASIC_ATTR_ACTION))
+		return -NLE_INVAL;
+	ret = rtnl_act_remove(&b->b_act, act);
+	if (ret)
+		return ret;
+
+	if (!b->b_act)
+		b->b_mask &= ~BASIC_ATTR_ACTION;
+	rtnl_act_put(act);
+	return 0;
+}
 /** @} */
 
-static struct rtnl_cls_ops basic_ops = {
-	.co_kind		= "basic",
-	.co_size		= sizeof(struct rtnl_basic),
-	.co_msg_parser		= basic_msg_parser,
-	.co_clone		= basic_clone,
-	.co_free_data		= basic_free_data,
-	.co_get_opts		= basic_get_opts,
-	.co_dump = {
+static struct rtnl_tc_ops basic_ops = {
+	.to_kind		= "basic",
+	.to_type		= RTNL_TC_TYPE_CLS,
+	.to_size		= sizeof(struct rtnl_basic),
+	.to_msg_parser		= basic_msg_parser,
+	.to_clone		= basic_clone,
+	.to_free_data		= basic_free_data,
+	.to_msg_fill		= basic_msg_fill,
+	.to_dump = {
 	    [NL_DUMP_LINE]	= basic_dump_line,
 	    [NL_DUMP_DETAILS]	= basic_dump_details,
 	},
@@ -200,12 +273,12 @@
 
 static void __init basic_init(void)
 {
-	rtnl_cls_register(&basic_ops);
+	rtnl_tc_register(&basic_ops);
 }
 
 static void __exit basic_exit(void)
 {
-	rtnl_cls_unregister(&basic_ops);
+	rtnl_tc_unregister(&basic_ops);
 }
 
 /** @} */
diff --git a/lib/route/cls/cgroup.c b/lib/route/cls/cgroup.c
index e5f38b8..c5b7561 100644
--- a/lib/route/cls/cgroup.c
+++ b/lib/route/cls/cgroup.c
@@ -6,23 +6,23 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2009-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup cls_api
- * @defgroup cgroup Control Groups Classifier
+ * @ingroup cls
+ * @defgroup cls_cgroup Control Groups Classifier
  *
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/classifier.h>
-#include <netlink/route/classifier-modules.h>
 #include <netlink/route/cls/cgroup.h>
 #include <netlink/route/cls/ematch.h>
 
@@ -34,29 +34,36 @@
 	[TCA_CGROUP_EMATCHES]	= { .type = NLA_NESTED },
 };
 
-static void cgroup_free_data(struct rtnl_cls *cls)
+static int cgroup_clone(void *dst, void *src)
 {
-	struct rtnl_cgroup *cg = rtnl_cls_data(cls);
-
-	rtnl_ematch_tree_free(cg->cg_ematch);
+	return -NLE_OPNOTSUPP;
 }
 
-static int cgroup_msg_parser(struct rtnl_cls *cls)
+static void cgroup_free_data(struct rtnl_tc *tc, void *data)
 {
-	struct rtnl_cgroup *cg = rtnl_cls_data(cls);
+	struct rtnl_cgroup *c = data;
+
+	if (!c)
+		return;
+
+	rtnl_ematch_tree_free(c->cg_ematch);
+}
+
+static int cgroup_msg_parser(struct rtnl_tc *tc, void *data)
+{
 	struct nlattr *tb[TCA_CGROUP_MAX + 1];
+	struct rtnl_cgroup *c = data;
 	int err;
 
-	err = tca_parse(tb, TCA_CGROUP_MAX, (struct rtnl_tca *) cls,
-			cgroup_policy);
+	err = tca_parse(tb, TCA_CGROUP_MAX, tc, cgroup_policy);
 	if (err < 0)
 		return err;
 
 	if (tb[TCA_CGROUP_EMATCHES]) {
-		if ((err = rtnl_ematch_parse(tb[TCA_CGROUP_EMATCHES],
-					     &cg->cg_ematch)) < 0)
+		if ((err = rtnl_ematch_parse_attr(tb[TCA_CGROUP_EMATCHES],
+						  &c->cg_ematch)) < 0)
 			return err;
-		cg->cg_mask |= CGROUP_ATTR_EMATCH;
+		c->cg_mask |= CGROUP_ATTR_EMATCH;
 	}
 
 #if 0
@@ -68,61 +75,102 @@
 	return 0;
 }
 
-static void cgroup_dump_line(struct rtnl_cls *cls, struct nl_dump_params *p)
+static void cgroup_dump_line(struct rtnl_tc *tc, void *data,
+			     struct nl_dump_params *p)
 {
-	struct rtnl_cgroup *cg = rtnl_cls_data(cls);
+	struct rtnl_cgroup *c = data;
 
-	if (cg->cg_mask & CGROUP_ATTR_EMATCH)
+	if (!c)
+		return;
+
+	if (c->cg_mask & CGROUP_ATTR_EMATCH)
 		nl_dump(p, " ematch");
 	else
 		nl_dump(p, " match-all");
 }
 
-static void cgroup_dump_details(struct rtnl_cls *cls, struct nl_dump_params *p)
+static void cgroup_dump_details(struct rtnl_tc *tc, void *data,
+				struct nl_dump_params *p)
 {
-	struct rtnl_cgroup *cg = rtnl_cls_data(cls);
+	struct rtnl_cgroup *c = data;
 
-	if (cg->cg_mask & CGROUP_ATTR_EMATCH) {
-		nl_dump(p, "\n");
+	if (!c)
+		return;
+
+	if (c->cg_mask & CGROUP_ATTR_EMATCH) {
 		nl_dump_line(p, "    ematch ");
-		rtnl_ematch_tree_dump(cg->cg_ematch, p);
-	}
+
+		if (c->cg_ematch)
+			rtnl_ematch_tree_dump(c->cg_ematch, p);
+		else
+			nl_dump(p, "<no tree>");
+	} else
+		nl_dump(p, "no options");
 }
 
+static int cgroup_fill_msg(struct rtnl_tc *tc, void *data,
+			   struct nl_msg *msg)
+{
+	struct rtnl_cgroup *c = data;
+
+	if (!c)
+		BUG();
+
+	if (!(tc->ce_mask & TCA_ATTR_HANDLE))
+		return -NLE_MISSING_ATTR;
+
+	if (c->cg_mask & CGROUP_ATTR_EMATCH)
+		return rtnl_ematch_fill_attr(msg, TCA_CGROUP_EMATCHES,
+					     c->cg_ematch);
+
+	return 0;
+}
+
+
 /**
  * @name Attribute Modifications
  * @{
  */
 
-int rtnl_cgroup_set_ematch(struct rtnl_cls *cls, struct rtnl_ematch_tree *tree)
+void rtnl_cgroup_set_ematch(struct rtnl_cls *cls, struct rtnl_ematch_tree *tree)
 {
-	struct rtnl_cgroup *cg = rtnl_cls_data(cls);
+	struct rtnl_cgroup *c;
 
-	if (cg->cg_ematch) {
-		rtnl_ematch_tree_free(cg->cg_ematch);
-		cg->cg_mask &= ~CGROUP_ATTR_EMATCH;
+	if (!(c = rtnl_tc_data(TC_CAST(cls))))
+		BUG();
+
+	if (c->cg_ematch) {
+		rtnl_ematch_tree_free(c->cg_ematch);
+		c->cg_mask &= ~CGROUP_ATTR_EMATCH;
 	}
 
-	cg->cg_ematch = tree;
+	c->cg_ematch = tree;
 
 	if (tree)
-		cg->cg_mask |= CGROUP_ATTR_EMATCH;
-
-	return 0;
+		c->cg_mask |= CGROUP_ATTR_EMATCH;
 }
 
 struct rtnl_ematch_tree *rtnl_cgroup_get_ematch(struct rtnl_cls *cls)
 {
-	struct rtnl_cgroup *cg = rtnl_cls_data(cls);
-	return cg->cg_ematch;
+	struct rtnl_cgroup *c;
+
+	if (!(c = rtnl_tc_data(TC_CAST(cls))))
+		BUG();
+
+	return c->cg_ematch;
 }
 
-static struct rtnl_cls_ops cgroup_ops = {
-	.co_kind		= "cgroup",
-	.co_size		= sizeof(struct rtnl_cgroup),
-	.co_msg_parser		= cgroup_msg_parser,
-	.co_free_data		= cgroup_free_data,
-	.co_dump = {
+/** @} */
+
+static struct rtnl_tc_ops cgroup_ops = {
+	.to_kind		= "cgroup",
+	.to_type		= RTNL_TC_TYPE_CLS,
+	.to_size		= sizeof(struct rtnl_cgroup),
+	.to_clone		= cgroup_clone,
+	.to_msg_parser		= cgroup_msg_parser,
+	.to_free_data		= cgroup_free_data,
+	.to_msg_fill		= cgroup_fill_msg,
+	.to_dump = {
 	    [NL_DUMP_LINE]	= cgroup_dump_line,
 	    [NL_DUMP_DETAILS]	= cgroup_dump_details,
 	},
@@ -130,12 +178,12 @@
 
 static void __init cgroup_init(void)
 {
-	rtnl_cls_register(&cgroup_ops);
+	rtnl_tc_register(&cgroup_ops);
 }
 
 static void __exit cgroup_exit(void)
 {
-	rtnl_cls_unregister(&cgroup_ops);
+	rtnl_tc_unregister(&cgroup_ops);
 }
 
 /** @} */
diff --git a/lib/route/cls/ematch.c b/lib/route/cls/ematch.c
index cb77b16..6cbe274 100644
--- a/lib/route/cls/ematch.c
+++ b/lib/route/cls/ematch.c
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
@@ -16,15 +16,18 @@
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/route/classifier.h>
-#include <netlink/route/classifier-modules.h>
 #include <netlink/route/cls/ematch.h>
+#include <netlink/route/cls/ematch/cmp.h>
+
+#include "ematch_syntax.h"
+#include "ematch_grammar.h"
 
 /**
- * @name Module Registration
+ * @name Module API
  * @{
  */
 
@@ -34,6 +37,9 @@
  * Register ematch module
  * @arg ops		Module operations.
  *
+ * This function must be called by each ematch module at initialization
+ * time. It registers the calling module as available module.
+ *
  * @return 0 on success or a negative error code.
  */
 int rtnl_ematch_register(struct rtnl_ematch_ops *ops)
@@ -41,35 +47,19 @@
 	if (rtnl_ematch_lookup_ops(ops->eo_kind))
 		return -NLE_EXIST;
 
+	NL_DBG(1, "ematch module \"%s\" registered\n", ops->eo_name);
+
 	nl_list_add_tail(&ops->eo_list, &ematch_ops_list);
 
 	return 0;
 }
 
 /**
- * Unregister ematch module
- * @arg ops		Module operations.
- *
- * @return 0 on success or a negative error code.
- */
-int rtnl_ematch_unregister(struct rtnl_ematch_ops *ops)
-{
-	struct rtnl_ematch_ops *o;
-
-	nl_list_for_each_entry(o, &ematch_ops_list, eo_list) {
-		if (ops->eo_kind == o->eo_kind) {
-			nl_list_del(&o->eo_list);
-			return 0;
-		}
-	}
-
-	return -NLE_OBJ_NOTFOUND;
-}
-
-/**
- * Lookup ematch module by kind
+ * Lookup ematch module by identification number.
  * @arg kind		Module kind.
  *
+ * Searches the list of registered ematch modules for match and returns it.
+ *
  * @return Module operations or NULL if not found.
  */
 struct rtnl_ematch_ops *rtnl_ematch_lookup_ops(int kind)
@@ -87,9 +77,11 @@
  * Lookup ematch module by name
  * @arg name		Name of ematch module.
  *
+ * Searches the list of registered ematch modules for a match and returns it.
+ *
  * @return Module operations or NULL if not fuond.
  */
-struct rtnl_ematch_ops *rtnl_ematch_lookup_ops_name(const char *name)
+struct rtnl_ematch_ops *rtnl_ematch_lookup_ops_by_name(const char *name)
 {
 	struct rtnl_ematch_ops *ops;
 
@@ -106,53 +98,122 @@
  * @name Match
  */
 
-struct rtnl_ematch *rtnl_ematch_alloc(struct rtnl_ematch_ops *ops)
+/**
+ * Allocate ematch object.
+ *
+ * Allocates and initializes an ematch object.
+ *
+ * @return New ematch object or NULL.
+ */
+struct rtnl_ematch *rtnl_ematch_alloc(void)
 {
 	struct rtnl_ematch *e;
-	size_t len = sizeof(*e) + (ops ? ops->eo_datalen : 0);
 
-	if (!(e = calloc(1, len)))
+	if (!(e = calloc(1, sizeof(*e))))
 		return NULL;
 
+	NL_DBG(2, "allocated ematch %p\n", e);
+
 	NL_INIT_LIST_HEAD(&e->e_list);
 	NL_INIT_LIST_HEAD(&e->e_childs);
 
-	if (ops) {
-		e->e_ops = ops;
-		e->e_kind = ops->eo_kind;
-	}
-
 	return e;
 }
 
 /**
  * Add ematch to the end of the parent's list of children.
- * @arg parent		Parent ematch.
- * @arg child		Ematch to be added as new child of parent.
+ * @arg parent		parent ematch object
+ * @arg child		ematch object to be added to parent
+ *
+ * The parent must be a container ematch.
  */
-void rtnl_ematch_add_child(struct rtnl_ematch *parent,
+int rtnl_ematch_add_child(struct rtnl_ematch *parent,
 			   struct rtnl_ematch *child)
 {
+	if (parent->e_kind != TCF_EM_CONTAINER)
+		return -NLE_OPNOTSUPP;
+
+	NL_DBG(2, "added ematch %p \"%s\" to container %p\n",
+		  child, child->e_ops->eo_name, parent);
+
 	nl_list_add_tail(&child->e_list, &parent->e_childs);
+
+	return 0;
 }
 
 /**
- * Remove ematch from the list it is linked to.
- * @arg ematch		Ematch to be unlinked.
+ * Remove ematch from the list of ematches it is linked to.
+ * @arg ematch		ematch object
  */
 void rtnl_ematch_unlink(struct rtnl_ematch *ematch)
 {
+	NL_DBG(2, "unlinked ematch %p from any lists\n", ematch);
+
+	if (!nl_list_empty(&ematch->e_childs))
+		NL_DBG(1, "warning: ematch %p with childs was unlinked\n",
+			  ematch);
+
 	nl_list_del(&ematch->e_list);
+	nl_init_list_head(&ematch->e_list);
 }
 
 void rtnl_ematch_free(struct rtnl_ematch *ematch)
 {
-	if (!ematch)
-		return;
-
+	NL_DBG(2, "freed ematch %p\n", ematch);
+	rtnl_ematch_unlink(ematch);
+	free(ematch->e_data);
 	free(ematch);
 }
 
+int rtnl_ematch_set_ops(struct rtnl_ematch *ematch, struct rtnl_ematch_ops *ops)
+{
+	if (ematch->e_ops)
+		return -NLE_EXIST;
+
+	ematch->e_ops = ops;
+	ematch->e_kind = ops->eo_kind;
+
+	if (ops->eo_datalen) {
+		ematch->e_data = calloc(1, ops->eo_datalen);
+		if (!ematch->e_data)
+			return -NLE_NOMEM;
+
+		ematch->e_datalen = ops->eo_datalen;
+	}
+
+	return 0;
+}
+
+int rtnl_ematch_set_kind(struct rtnl_ematch *ematch, uint16_t kind)
+{
+	struct rtnl_ematch_ops *ops;
+
+	if (ematch->e_kind)
+		return -NLE_EXIST;
+
+	ematch->e_kind = kind;
+
+	if ((ops = rtnl_ematch_lookup_ops(kind)))
+		rtnl_ematch_set_ops(ematch, ops);
+
+	return 0;
+}
+
+int rtnl_ematch_set_name(struct rtnl_ematch *ematch, const char *name)
+{
+	struct rtnl_ematch_ops *ops;
+
+	if (ematch->e_kind)
+		return -NLE_EXIST;
+
+	if (!(ops = rtnl_ematch_lookup_ops_by_name(name)))
+		return -NLE_OPNOTSUPP;
+
+	rtnl_ematch_set_ops(ematch, ops);
+
+	return 0;
+}
+
 void rtnl_ematch_set_flags(struct rtnl_ematch *ematch, uint16_t flags)
 {
 	ematch->e_flags |= flags;
@@ -179,16 +240,22 @@
  * @name Tree
  */
 
+/**
+ * Allocate ematch tree object
+ * @arg progid		program id
+ */
 struct rtnl_ematch_tree *rtnl_ematch_tree_alloc(uint16_t progid)
 {
 	struct rtnl_ematch_tree *tree;
 
 	if (!(tree = calloc(1, sizeof(*tree))))
 		return NULL;
-
+	
 	NL_INIT_LIST_HEAD(&tree->et_list);
 	tree->et_progid = progid;
 
+	NL_DBG(2, "allocated new ematch tree %p, progid=%u\n", tree, progid);
+
 	return tree;
 }
 
@@ -203,17 +270,31 @@
 	}
 }
 
+/**
+ * Free ematch tree object
+ * @arg tree		ematch tree object
+ *
+ * This function frees the ematch tree and all ematches attached to it.
+ */
 void rtnl_ematch_tree_free(struct rtnl_ematch_tree *tree)
 {
 	if (!tree)
 		return;
 
 	free_ematch_list(&tree->et_list);
+
+	NL_DBG(2, "Freed ematch tree %p\n", tree);
+
 	free(tree);
 }
 
-void rtnl_ematch_tree_add_tail(struct rtnl_ematch_tree *tree,
-			       struct rtnl_ematch *ematch)
+/**
+ * Add ematch object to the end of the ematch tree
+ * @arg tree		ematch tree object
+ * @arg ematch		ematch object to add
+ */
+void rtnl_ematch_tree_add(struct rtnl_ematch_tree *tree,
+			  struct rtnl_ematch *ematch)
 {
 	nl_list_add_tail(&ematch->e_list, &tree->et_list);
 }
@@ -256,7 +337,7 @@
  *
  * @return 0 on success or a negative error code.
  */
-int rtnl_ematch_parse(struct nlattr *attr, struct rtnl_ematch_tree **result)
+int rtnl_ematch_parse_attr(struct nlattr *attr, struct rtnl_ematch_tree **result)
 {
 	struct nlattr *a, *tb[TCA_EMATCH_TREE_MAX+1];
 	struct tcf_ematch_tree_hdr *thdr;
@@ -264,6 +345,8 @@
 	struct rtnl_ematch **index;
 	int nmatches = 0, err, remaining;
 
+	NL_DBG(2, "Parsing attribute %p as ematch tree\n", attr);
+
 	err = nla_parse_nested(tb, TCA_EMATCH_TREE_MAX, attr, tree_policy);
 	if (err < 0)
 		return err;
@@ -274,12 +357,22 @@
 	thdr = nla_data(tb[TCA_EMATCH_TREE_HDR]);
 
 	/* Ignore empty trees */
-	if (thdr->nmatches == 0)
+	if (thdr->nmatches == 0) {
+		NL_DBG(2, "Ignoring empty ematch configuration\n");
 		return 0;
+	}
 
 	if (!tb[TCA_EMATCH_TREE_LIST])
 		return -NLE_MISSING_ATTR;
 
+	NL_DBG(2, "ematch tree found with nmatches=%u, progid=%u\n",
+		  thdr->nmatches, thdr->progid);
+
+	/*
+	 * Do some basic sanity checking since we will allocate
+	 * index[thdr->nmatches]. Calculate how many ematch headers fit into
+	 * the provided data and make sure nmatches does not exceed it.
+	 */
 	if (thdr->nmatches > (nla_len(tb[TCA_EMATCH_TREE_LIST]) /
 			      nla_total_size(sizeof(struct tcf_ematch_hdr))))
 		return -NLE_INVAL;
@@ -299,11 +392,15 @@
 		void *data;
 		size_t len;
 
+		NL_DBG(3, "parsing ematch attribute %d, len=%u\n",
+			  nmatches+1, nla_len(a));
+
 		if (nla_len(a) < sizeof(*hdr)) {
 			err = -NLE_INVAL;
 			goto errout;
 		}
 
+		/* Quit as soon as we've parsed more matches than expected */
 		if (nmatches >= thdr->nmatches) {
 			err = -NLE_RANGE;
 			goto errout;
@@ -313,13 +410,20 @@
 		data = nla_data(a) + NLA_ALIGN(sizeof(*hdr));
 		len = nla_len(a) - NLA_ALIGN(sizeof(*hdr));
 
-		ops = rtnl_ematch_lookup_ops(hdr->kind);
-		if (ops && ops->eo_datalen && len < ops->eo_datalen) {
+		NL_DBG(3, "ematch attribute matchid=%u, kind=%u, flags=%u\n",
+			  hdr->matchid, hdr->kind, hdr->flags);
+
+		/*
+		 * Container matches contain a reference to another sequence
+		 * of matches. Ensure that the reference is within boundries.
+		 */
+		if (hdr->kind == TCF_EM_CONTAINER &&
+		    *((uint32_t *) data) >= thdr->nmatches) {
 			err = -NLE_INVAL;
 			goto errout;
 		}
 
-		if (!(ematch = rtnl_ematch_alloc(ops))) {
+		if (!(ematch = rtnl_ematch_alloc())) {
 			err = -NLE_NOMEM;
 			goto errout;
 		}
@@ -328,15 +432,23 @@
 		ematch->e_kind = hdr->kind;
 		ematch->e_flags = hdr->flags;
 
-		if (ops && (err = ops->eo_parse(ematch, data, len)) < 0)
-			goto errout;
+		if ((ops = rtnl_ematch_lookup_ops(hdr->kind))) {
+			if (ops->eo_minlen && len < ops->eo_minlen) {
+				rtnl_ematch_free(ematch);
+				err = -NLE_INVAL;
+				goto errout;
+			}
 
-		if (hdr->kind == TCF_EM_CONTAINER &&
-		    container_ref(ematch) >= thdr->nmatches) {
-			err = -NLE_INVAL;
-			goto errout;
+			rtnl_ematch_set_ops(ematch, ops);
+
+			if (ops->eo_parse &&
+			    (err = ops->eo_parse(ematch, data, len)) < 0) {
+				rtnl_ematch_free(ematch);
+				goto errout;
+			}
 		}
 
+		NL_DBG(3, "index[%d] = %p\n", nmatches, ematch);
 		index[nmatches++] = ematch;
 	}
 
@@ -367,7 +479,7 @@
 
 	nl_list_for_each_entry(match, head, e_list) {
 		if (match->e_flags & TCF_EM_INVERT)
-			nl_dump(p, "NOT ");
+			nl_dump(p, "!");
 
 		if (match->e_kind == TCF_EM_CONTAINER) {
 			nl_dump(p, "(");
@@ -376,12 +488,10 @@
 		} else if (!match->e_ops) {
 			nl_dump(p, "[unknown ematch %d]", match->e_kind);
 		} else {
-			nl_dump(p, "%s(", match->e_ops->eo_name);
-
 			if (match->e_ops->eo_dump)
 				match->e_ops->eo_dump(match, p);
-
-			nl_dump(p, ")");
+			else
+				nl_dump(p, "[data]");
 		}
 
 		switch (match->e_flags & TCF_EM_REL_MASK) {
@@ -401,10 +511,190 @@
 void rtnl_ematch_tree_dump(struct rtnl_ematch_tree *tree,
 			   struct nl_dump_params *p)
 {
+	if (!tree)
+		BUG();
+
 	dump_ematch_sequence(&tree->et_list, p);
 	nl_dump(p, "\n");
 }
 
+static int update_container_index(struct nl_list_head *list, int *index)
+{
+	struct rtnl_ematch *e;
+
+	nl_list_for_each_entry(e, list, e_list)
+		e->e_index = (*index)++;
+
+	nl_list_for_each_entry(e, list, e_list) {
+		if (e->e_kind == TCF_EM_CONTAINER) {
+			int err;
+
+			if (nl_list_empty(&e->e_childs))
+				return -NLE_OBJ_NOTFOUND;
+
+			*((uint32_t *) e->e_data) = *index;
+
+			err = update_container_index(&e->e_childs, index);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
+static int fill_ematch_sequence(struct nl_msg *msg, struct nl_list_head *list)
+{
+	struct rtnl_ematch *e;
+
+	nl_list_for_each_entry(e, list, e_list) {
+		struct tcf_ematch_hdr match = {
+			.matchid = e->e_id,
+			.kind = e->e_kind,
+			.flags = e->e_flags,
+		};
+		struct nlattr *attr;
+		int err = 0;
+
+		if (!(attr = nla_nest_start(msg, e->e_index + 1)))
+			return -NLE_NOMEM;
+
+		if (nlmsg_append(msg, &match, sizeof(match), 0) < 0)
+			return -NLE_NOMEM;
+
+		if (e->e_ops->eo_fill)
+			err = e->e_ops->eo_fill(e, msg);
+		else if (e->e_flags & TCF_EM_SIMPLE)
+			err = nlmsg_append(msg, e->e_data, 4, 0);
+		else if (e->e_datalen > 0)
+			err = nlmsg_append(msg, e->e_data, e->e_datalen, 0);
+
+		NL_DBG(3, "msg %p: added ematch [%d] id=%d kind=%d flags=%d\n",
+			  msg, e->e_index, match.matchid, match.kind, match.flags);
+
+		if (err < 0)
+			return -NLE_NOMEM;
+
+		nla_nest_end(msg, attr);
+	}
+
+	nl_list_for_each_entry(e, list, e_list) {
+		if (e->e_kind == TCF_EM_CONTAINER &&
+		    fill_ematch_sequence(msg, &e->e_childs) < 0)
+			return -NLE_NOMEM;
+	}
+
+	return 0;
+}
+
+int rtnl_ematch_fill_attr(struct nl_msg *msg, int attrid,
+			  struct rtnl_ematch_tree *tree)
+{
+	struct tcf_ematch_tree_hdr thdr = {
+		.progid = tree->et_progid,
+	};
+	struct nlattr *list, *topattr;
+	int err, index = 0;
+
+	/* Assign index number to each ematch to allow for references
+	 * to be made while constructing the sequence of matches. */
+	err = update_container_index(&tree->et_list, &index);
+	if (err < 0)
+		return err;
+
+	if (!(topattr = nla_nest_start(msg, attrid)))
+		goto nla_put_failure;
+
+	thdr.nmatches = index;
+	NLA_PUT(msg, TCA_EMATCH_TREE_HDR, sizeof(thdr), &thdr);
+
+	if (!(list = nla_nest_start(msg, TCA_EMATCH_TREE_LIST)))
+		goto nla_put_failure;
+
+	if (fill_ematch_sequence(msg, &tree->et_list) < 0)
+		goto nla_put_failure;
+
+	nla_nest_end(msg, list);
+
+	nla_nest_end(msg, topattr);
+
+	return 0;
+
+nla_put_failure:
+	return -NLE_NOMEM;
+}
+
 /** @} */
 
+extern int ematch_parse(void *, char **, struct nl_list_head *);
+
+int rtnl_ematch_parse_expr(const char *expr, char **errp,
+			   struct rtnl_ematch_tree **result)
+{
+	struct rtnl_ematch_tree *tree;
+	YY_BUFFER_STATE buf = NULL;
+	yyscan_t scanner = NULL;
+	int err;
+
+	NL_DBG(2, "Parsing ematch expression \"%s\"\n", expr);
+
+	if (!(tree = rtnl_ematch_tree_alloc(RTNL_EMATCH_PROGID)))
+		return -NLE_FAILURE;
+
+	if ((err = ematch_lex_init(&scanner)) < 0) {
+		err = -NLE_FAILURE;
+		goto errout;
+	}
+
+	buf = ematch__scan_string(expr, scanner);
+
+	if ((err = ematch_parse(scanner, errp, &tree->et_list)) != 0) {
+		ematch__delete_buffer(buf, scanner);
+		err = -NLE_PARSE_ERR;
+		goto errout;
+	}
+
+	ematch_lex_destroy(scanner);
+	*result = tree;
+
+	return 0;
+
+errout:
+	if (scanner)
+		ematch_lex_destroy(scanner);
+
+	rtnl_ematch_tree_free(tree);
+
+	return err;
+}
+
+static const char *layer_txt[] = {
+	[TCF_LAYER_LINK]	= "eth",
+	[TCF_LAYER_NETWORK]	= "ip",
+	[TCF_LAYER_TRANSPORT]	= "tcp",
+};
+
+char *rtnl_ematch_offset2txt(uint8_t layer, uint16_t offset, char *buf, size_t len)
+{
+	snprintf(buf, len, "%s+%u",
+		 (layer <= TCF_LAYER_MAX) ? layer_txt[layer] : "?",
+		 offset);
+
+	return buf;
+}
+
+static const char *operand_txt[] = {
+	[TCF_EM_OPND_EQ] = "=",
+	[TCF_EM_OPND_LT] = "<",
+	[TCF_EM_OPND_GT] = ">",
+};
+
+char *rtnl_ematch_opnd2txt(uint8_t opnd, char *buf, size_t len)
+{
+	snprintf(buf, len, "%s",
+		opnd < ARRAY_SIZE(operand_txt) ? operand_txt[opnd] : "?");
+
+	return buf;
+}
+
 /** @} */
diff --git a/lib/route/cls/ematch/cmp.c b/lib/route/cls/ematch/cmp.c
index ec25320..2997cdb 100644
--- a/lib/route/cls/ematch/cmp.c
+++ b/lib/route/cls/ematch/cmp.c
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
@@ -16,88 +16,70 @@
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/route/cls/ematch.h>
 #include <linux/tc_ematch/tc_em_cmp.h>
 
-void rtnl_ematch_cmp_set(struct rtnl_ematch *ematch,
-			 struct tcf_em_cmp *cfg)
+void rtnl_ematch_cmp_set(struct rtnl_ematch *e, struct tcf_em_cmp *cfg)
 {
-	memcpy(rtnl_ematch_data(ematch), cfg, sizeof(*cfg));
+	memcpy(rtnl_ematch_data(e), cfg, sizeof(*cfg));
 }
 
-struct tcf_em_cmp *rtnl_ematch_cmp_get(struct rtnl_ematch *ematch)
+struct tcf_em_cmp *rtnl_ematch_cmp_get(struct rtnl_ematch *e)
 {
-	return rtnl_ematch_data(ematch);
+	return rtnl_ematch_data(e);
 }
 
-static const char *align_txt(struct tcf_em_cmp *cmp)
+static int cmp_parse(struct rtnl_ematch *e, void *data, size_t len)
 {
-	switch (cmp->align) {
-	case TCF_EM_ALIGN_U8:
-		return "u8";
-	case TCF_EM_ALIGN_U16:
-		return (cmp->flags & TCF_EM_CMP_TRANS) ? "h16" : "u16";
-	case TCF_EM_ALIGN_U32:
-		return (cmp->flags & TCF_EM_CMP_TRANS) ? "h32" : "u32";
-	default:
-		return (cmp->flags & TCF_EM_CMP_TRANS) ? "h?" : "u?";
-	}
-}
-
-static const char *layer_txt(struct tcf_em_cmp *cmp)
-{
-	switch (cmp->layer) {
-	case TCF_LAYER_LINK:
-		return "link";
-	case TCF_LAYER_NETWORK:
-		return "network";
-	case TCF_LAYER_TRANSPORT:
-		return "transport";
-	default:
-		return "?";
-	}
-}
-
-static const char *relation_txt(struct tcf_em_cmp *cmp)
-{
-	switch (cmp->opnd) {
-	case TCF_EM_OPND_EQ:
-		return "eq";
-	case TCF_EM_OPND_LT:
-		return "lt";
-	case TCF_EM_OPND_GT:
-		return "gt";
-	default:
-		return "?";
-	}
-}
-
-static int cmp_parse(struct rtnl_ematch *m, void *data, size_t len)
-{
-	memcpy(rtnl_ematch_data(m), data, len);
+	memcpy(rtnl_ematch_data(e), data, len);
 
 	return 0;
 }
 
-static void cmp_dump(struct rtnl_ematch *m, struct nl_dump_params *p)
-{
-	struct tcf_em_cmp *cmp = rtnl_ematch_data(m);
+static const char *align_txt[] = {
+	[TCF_EM_ALIGN_U8] = "u8",
+	[TCF_EM_ALIGN_U16] = "u16",
+	[TCF_EM_ALIGN_U32] = "u32"
+};
 
-	nl_dump(p, "%s at %s+%u ",
-		align_txt(cmp), layer_txt(cmp), cmp->off);
+static const char *layer_txt[] = {
+	[TCF_LAYER_LINK] = "eth",
+	[TCF_LAYER_NETWORK] = "ip",
+	[TCF_LAYER_TRANSPORT] = "tcp"
+};
+
+static const char *operand_txt[] = {
+	[TCF_EM_OPND_EQ] = "=",
+	[TCF_EM_OPND_LT] = "<",
+	[TCF_EM_OPND_GT] = ">",
+};
+
+static void cmp_dump(struct rtnl_ematch *e, struct nl_dump_params *p)
+{
+	struct tcf_em_cmp *cmp = rtnl_ematch_data(e);
+
+	if (cmp->flags & TCF_EM_CMP_TRANS)
+		nl_dump(p, "ntoh%c(", (cmp->align == TCF_EM_ALIGN_U32) ? 'l' : 's');
+
+	nl_dump(p, "%s at %s+%u",
+		align_txt[cmp->align], layer_txt[cmp->layer], cmp->off);
 
 	if (cmp->mask)
-		nl_dump(p, "& 0x%x ", cmp->mask);
+		nl_dump(p, " & 0x%x", cmp->mask);
 
-	nl_dump(p, "%s %u", relation_txt(cmp), cmp->val);
+	if (cmp->flags & TCF_EM_CMP_TRANS)
+		nl_dump(p, ")");
+
+	nl_dump(p, " %s %u", operand_txt[cmp->opnd], cmp->val);
 }
 
 static struct rtnl_ematch_ops cmp_ops = {
 	.eo_kind	= TCF_EM_CMP,
 	.eo_name	= "cmp",
+	.eo_minlen	= sizeof(struct tcf_em_cmp),
 	.eo_datalen	= sizeof(struct tcf_em_cmp),
 	.eo_parse	= cmp_parse,
 	.eo_dump	= cmp_dump,
@@ -108,9 +90,4 @@
 	rtnl_ematch_register(&cmp_ops);
 }
 
-static void __exit cmp_exit(void)
-{
-	rtnl_ematch_unregister(&cmp_ops);
-}
-
 /** @} */
diff --git a/lib/route/cls/ematch/container.c b/lib/route/cls/ematch/container.c
index 54d836f..813391a 100644
--- a/lib/route/cls/ematch/container.c
+++ b/lib/route/cls/ematch/container.c
@@ -6,34 +6,42 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2013 Thomas Graf <tgraf@suug.ch>
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/route/cls/ematch.h>
 
-static int container_parse(struct rtnl_ematch *m, void *data, size_t len)
+static int container_parse(struct rtnl_ematch *e, void *data, size_t len __attribute__((unused)))
 {
-	memcpy(m->e_data, data, sizeof(uint32_t));
+	/*
+	The kernel may provide more than 4 bytes of data in the future and we want
+	older libnl versions to be ok with that. We want interfaces to be growable
+	so we only ever enforce a minimum data length and copy as much as we are
+	aware of. Thomas Graf.
+	*/
+	memcpy(e->e_data, data, sizeof(uint32_t));
 
 	return 0;
 }
 
+static int container_fill(struct rtnl_ematch *e, struct nl_msg *msg)
+{
+	return nlmsg_append(msg, e->e_data, sizeof(uint32_t), 0);
+}
+
 static struct rtnl_ematch_ops container_ops = {
 	.eo_kind	= TCF_EM_CONTAINER,
 	.eo_name	= "container",
+	.eo_minlen	= sizeof(uint32_t),
 	.eo_datalen	= sizeof(uint32_t),
 	.eo_parse	= container_parse,
+	.eo_fill	= container_fill,
 };
 
 static void __init container_init(void)
 {
 	rtnl_ematch_register(&container_ops);
 }
-
-static void __exit container_exit(void)
-{
-	rtnl_ematch_unregister(&container_ops);
-}
diff --git a/lib/route/cls/ematch/meta.c b/lib/route/cls/ematch/meta.c
new file mode 100644
index 0000000..44f11b9
--- /dev/null
+++ b/lib/route/cls/ematch/meta.c
@@ -0,0 +1,334 @@
+/*
+ * lib/route/cls/ematch/meta.c		Metadata Match
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup ematch
+ * @defgroup em_meta Metadata Match
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/route/cls/ematch.h>
+#include <netlink/route/cls/ematch/meta.h>
+
+struct rtnl_meta_value
+{
+	uint8_t			mv_type;
+	uint8_t			mv_shift;
+	uint16_t		mv_id;
+	size_t			mv_len;
+};
+
+struct meta_data
+{
+	struct rtnl_meta_value *	left;
+	struct rtnl_meta_value *	right;
+	uint8_t				opnd;
+};
+
+static struct rtnl_meta_value *meta_alloc(uint8_t type, uint16_t id,
+					  uint8_t shift, void *data,
+					  size_t len)
+{
+	struct rtnl_meta_value *value;
+
+	if (!(value = calloc(1, sizeof(*value) + len)))
+		return NULL;
+
+	value->mv_type = type;
+	value->mv_id = id;
+	value->mv_shift = shift;
+	value->mv_len = len;
+
+	memcpy(value + 1, data, len);
+
+	return value;
+}
+
+struct rtnl_meta_value *rtnl_meta_value_alloc_int(uint64_t value)
+{
+	return meta_alloc(TCF_META_TYPE_INT, TCF_META_ID_VALUE, 0, &value, 8);
+}
+
+struct rtnl_meta_value *rtnl_meta_value_alloc_var(void *data, size_t len)
+{
+	return meta_alloc(TCF_META_TYPE_VAR, TCF_META_ID_VALUE, 0, data, len);
+}
+
+struct rtnl_meta_value *rtnl_meta_value_alloc_id(uint8_t type, uint16_t id,
+						 uint8_t shift, uint64_t mask)
+{
+	size_t masklen = 0;
+
+	if (id > TCF_META_ID_MAX)
+		return NULL;
+
+	if (mask) {
+		if (type == TCF_META_TYPE_VAR)
+			return NULL;
+
+		masklen = 8;
+	}
+
+	return meta_alloc(type, id, shift, &mask, masklen);
+}
+
+void rtnl_meta_value_put(struct rtnl_meta_value *mv)
+{
+	free(mv);
+}
+
+void rtnl_ematch_meta_set_lvalue(struct rtnl_ematch *e, struct rtnl_meta_value *v)
+{
+	struct meta_data *m = rtnl_ematch_data(e);
+	m->left = v;
+}
+
+void rtnl_ematch_meta_set_rvalue(struct rtnl_ematch *e, struct rtnl_meta_value *v)
+{
+	struct meta_data *m = rtnl_ematch_data(e);
+	m->right = v;
+}
+
+void rtnl_ematch_meta_set_operand(struct rtnl_ematch *e, uint8_t opnd)
+{
+	struct meta_data *m = rtnl_ematch_data(e);
+	m->opnd = opnd;
+}
+
+static struct nla_policy meta_policy[TCA_EM_META_MAX+1] = {
+	[TCA_EM_META_HDR]	= { .minlen = sizeof(struct tcf_meta_hdr) },
+	[TCA_EM_META_LVALUE]	= { .minlen = 1, },
+	[TCA_EM_META_RVALUE]	= { .minlen = 1, },
+};
+
+static int meta_parse(struct rtnl_ematch *e, void *data, size_t len)
+{
+	struct meta_data *m = rtnl_ematch_data(e);
+	struct nlattr *tb[TCA_EM_META_MAX+1];
+	struct rtnl_meta_value *v;
+	struct tcf_meta_hdr *hdr;
+	void *vdata = NULL;
+	size_t vlen = 0;
+	int err;
+
+	if ((err = nla_parse(tb, TCA_EM_META_MAX, data, len, meta_policy)) < 0)
+		return err;
+
+	if (!tb[TCA_EM_META_HDR])
+		return -NLE_MISSING_ATTR;
+
+	hdr = nla_data(tb[TCA_EM_META_HDR]);
+
+	if (tb[TCA_EM_META_LVALUE]) {
+		vdata = nla_data(tb[TCA_EM_META_LVALUE]);
+		vlen = nla_len(tb[TCA_EM_META_LVALUE]);
+	}
+
+	v = meta_alloc(TCF_META_TYPE(hdr->left.kind),
+		       TCF_META_ID(hdr->left.kind),
+		       hdr->left.shift, vdata, vlen);
+	if (!v)
+		return -NLE_NOMEM;
+
+	m->left = v;
+
+	vlen = 0;
+	if (tb[TCA_EM_META_RVALUE]) {
+		vdata = nla_data(tb[TCA_EM_META_RVALUE]);
+		vlen = nla_len(tb[TCA_EM_META_RVALUE]);
+	}
+
+	v = meta_alloc(TCF_META_TYPE(hdr->right.kind),
+		       TCF_META_ID(hdr->right.kind),
+		       hdr->right.shift, vdata, vlen);
+	if (!v) {
+		rtnl_meta_value_put(m->left);
+		return -NLE_NOMEM;
+	}
+
+	m->right = v;
+	m->opnd = hdr->left.op;
+
+	return 0;
+}
+
+static const struct trans_tbl meta_int[] = {
+	__ADD(TCF_META_ID_RANDOM, random)
+	__ADD(TCF_META_ID_LOADAVG_0, loadavg_0)
+	__ADD(TCF_META_ID_LOADAVG_1, loadavg_1)
+	__ADD(TCF_META_ID_LOADAVG_2, loadavg_2)
+	__ADD(TCF_META_ID_DEV, dev)
+	__ADD(TCF_META_ID_PRIORITY, prio)
+	__ADD(TCF_META_ID_PROTOCOL, proto)
+	__ADD(TCF_META_ID_PKTTYPE, pkttype)
+	__ADD(TCF_META_ID_PKTLEN, pktlen)
+	__ADD(TCF_META_ID_DATALEN, datalen)
+	__ADD(TCF_META_ID_MACLEN, maclen)
+	__ADD(TCF_META_ID_NFMARK, mark)
+	__ADD(TCF_META_ID_TCINDEX, tcindex)
+	__ADD(TCF_META_ID_RTCLASSID, rtclassid)
+	__ADD(TCF_META_ID_RTIIF, rtiif)
+	__ADD(TCF_META_ID_SK_FAMILY, sk_family)
+	__ADD(TCF_META_ID_SK_STATE, sk_state)
+	__ADD(TCF_META_ID_SK_REUSE, sk_reuse)
+	__ADD(TCF_META_ID_SK_REFCNT, sk_refcnt)
+	__ADD(TCF_META_ID_SK_RCVBUF, sk_rcvbuf)
+	__ADD(TCF_META_ID_SK_SNDBUF, sk_sndbuf)
+	__ADD(TCF_META_ID_SK_SHUTDOWN, sk_sutdown)
+	__ADD(TCF_META_ID_SK_PROTO, sk_proto)
+	__ADD(TCF_META_ID_SK_TYPE, sk_type)
+	__ADD(TCF_META_ID_SK_RMEM_ALLOC, sk_rmem_alloc)
+	__ADD(TCF_META_ID_SK_WMEM_ALLOC, sk_wmem_alloc)
+	__ADD(TCF_META_ID_SK_WMEM_QUEUED, sk_wmem_queued)
+	__ADD(TCF_META_ID_SK_RCV_QLEN, sk_rcv_qlen)
+	__ADD(TCF_META_ID_SK_SND_QLEN, sk_snd_qlen)
+	__ADD(TCF_META_ID_SK_ERR_QLEN, sk_err_qlen)
+	__ADD(TCF_META_ID_SK_FORWARD_ALLOCS, sk_forward_allocs)
+	__ADD(TCF_META_ID_SK_ALLOCS, sk_allocs)
+	__ADD(TCF_META_ID_SK_ROUTE_CAPS, sk_route_caps)
+	__ADD(TCF_META_ID_SK_HASH, sk_hash)
+	__ADD(TCF_META_ID_SK_LINGERTIME, sk_lingertime)
+	__ADD(TCF_META_ID_SK_ACK_BACKLOG, sk_ack_backlog)
+	__ADD(TCF_META_ID_SK_MAX_ACK_BACKLOG, sk_max_ack_backlog)
+	__ADD(TCF_META_ID_SK_PRIO, sk_prio)
+	__ADD(TCF_META_ID_SK_RCVLOWAT, sk_rcvlowat)
+	__ADD(TCF_META_ID_SK_RCVTIMEO, sk_rcvtimeo)
+	__ADD(TCF_META_ID_SK_SNDTIMEO, sk_sndtimeo)
+	__ADD(TCF_META_ID_SK_SENDMSG_OFF, sk_sendmsg_off)
+	__ADD(TCF_META_ID_SK_WRITE_PENDING, sk_write_pending)
+	__ADD(TCF_META_ID_VLAN_TAG, vlan)
+	__ADD(TCF_META_ID_RXHASH, rxhash)
+};
+
+static char *int_id2str(int id, char *buf, size_t size)
+{
+	return __type2str(id, buf, size, meta_int, ARRAY_SIZE(meta_int));
+}
+
+static const struct trans_tbl meta_var[] = {
+	__ADD(TCF_META_ID_DEV,devname)
+	__ADD(TCF_META_ID_SK_BOUND_IF,sk_bound_if)
+};
+
+static char *var_id2str(int id, char *buf, size_t size)
+{
+	return __type2str(id, buf, size, meta_var, ARRAY_SIZE(meta_var));
+}
+
+static void dump_value(struct rtnl_meta_value *v, struct nl_dump_params *p)
+{
+	char buf[32];
+
+	switch (v->mv_type) {
+		case TCF_META_TYPE_INT:
+			if (v->mv_id == TCF_META_ID_VALUE) {
+				nl_dump(p, "%u",
+					*(uint32_t *) (v + 1));
+			} else {
+				nl_dump(p, "%s",
+					int_id2str(v->mv_id, buf, sizeof(buf)));
+
+				if (v->mv_shift)
+					nl_dump(p, " >> %u", v->mv_shift);
+
+				if (v->mv_len == 4)
+					nl_dump(p, " & %#x", *(uint32_t *) (v + 1));
+				else if (v->mv_len == 8)
+					nl_dump(p, " & %#x", *(uint64_t *) (v + 1));
+			}
+		break;
+
+		case TCF_META_TYPE_VAR:
+			if (v->mv_id == TCF_META_ID_VALUE) {
+				nl_dump(p, "%s", (char *) (v + 1));
+			} else {
+				nl_dump(p, "%s",
+					var_id2str(v->mv_id, buf, sizeof(buf)));
+
+				if (v->mv_shift)
+					nl_dump(p, " >> %u", v->mv_shift);
+			}
+		break;
+	}
+}
+
+static void meta_dump(struct rtnl_ematch *e, struct nl_dump_params *p)
+{
+	struct meta_data *m = rtnl_ematch_data(e);
+	char buf[32];
+
+	nl_dump(p, "meta(");
+	dump_value(m->left, p);
+
+	nl_dump(p, " %s ", rtnl_ematch_opnd2txt(m->opnd, buf, sizeof(buf)));
+
+	dump_value(m->right, p);
+	nl_dump(p, ")");
+}
+
+static int meta_fill(struct rtnl_ematch *e, struct nl_msg *msg)
+{
+	struct meta_data *m = rtnl_ematch_data(e);
+	struct tcf_meta_hdr hdr;
+
+	if (!(m->left && m->right))
+		return -NLE_MISSING_ATTR;
+
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.left.kind = (m->left->mv_type << 12) & TCF_META_TYPE_MASK;
+	hdr.left.kind |= m->left->mv_id & TCF_META_ID_MASK;
+	hdr.left.shift = m->left->mv_shift;
+	hdr.left.op = m->opnd;
+	hdr.right.kind = (m->right->mv_type << 12) & TCF_META_TYPE_MASK;
+	hdr.right.kind |= m->right->mv_id & TCF_META_ID_MASK;
+
+	NLA_PUT(msg, TCA_EM_META_HDR, sizeof(hdr), &hdr);
+
+	if (m->left->mv_len)
+		NLA_PUT(msg, TCA_EM_META_LVALUE, m->left->mv_len, (m->left + 1));
+	
+	if (m->right->mv_len)
+		NLA_PUT(msg, TCA_EM_META_RVALUE, m->right->mv_len, (m->right + 1));
+
+	return 0;
+
+nla_put_failure:
+	return -NLE_NOMEM;
+}
+
+static void meta_free(struct rtnl_ematch *e)
+{
+	struct meta_data *m = rtnl_ematch_data(e);
+	free(m->left);
+	free(m->right);
+}
+
+static struct rtnl_ematch_ops meta_ops = {
+	.eo_kind	= TCF_EM_META,
+	.eo_name	= "meta",
+	.eo_minlen	= sizeof(struct tcf_meta_hdr),
+	.eo_datalen	= sizeof(struct meta_data),
+	.eo_parse	= meta_parse,
+	.eo_dump	= meta_dump,
+	.eo_fill	= meta_fill,
+	.eo_free	= meta_free,
+};
+
+static void __init meta_init(void)
+{
+	rtnl_ematch_register(&meta_ops);
+}
+
+/** @} */
diff --git a/lib/route/cls/ematch/nbyte.c b/lib/route/cls/ematch/nbyte.c
new file mode 100644
index 0000000..8852489
--- /dev/null
+++ b/lib/route/cls/ematch/nbyte.c
@@ -0,0 +1,139 @@
+/*
+ * lib/route/cls/ematch/nbyte.c		Nbyte comparison
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup ematch
+ * @defgroup em_nbyte N-Byte Comparison
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/route/cls/ematch.h>
+#include <netlink/route/cls/ematch/nbyte.h>
+
+struct nbyte_data
+{
+	struct tcf_em_nbyte	cfg;
+	uint8_t *		pattern;
+};
+
+void rtnl_ematch_nbyte_set_offset(struct rtnl_ematch *e, uint8_t layer,
+				  uint16_t offset)
+{
+	struct nbyte_data *n = rtnl_ematch_data(e);
+	n->cfg.off = offset;
+	n->cfg.layer = layer;
+}
+
+uint16_t rtnl_ematch_nbyte_get_offset(struct rtnl_ematch *e)
+{
+	return ((struct nbyte_data *) rtnl_ematch_data(e))->cfg.off;
+}
+
+uint8_t rtnl_ematch_nbyte_get_layer(struct rtnl_ematch *e)
+{
+	return ((struct nbyte_data *) rtnl_ematch_data(e))->cfg.layer;
+}
+
+void rtnl_ematch_nbyte_set_pattern(struct rtnl_ematch *e,
+				   uint8_t *pattern, size_t len)
+{
+	struct nbyte_data *n = rtnl_ematch_data(e);
+
+	if (n->pattern)
+		free(n->pattern);
+
+	n->pattern = pattern;
+	n->cfg.len = len;
+}
+
+uint8_t *rtnl_ematch_nbyte_get_pattern(struct rtnl_ematch *e)
+{
+	return ((struct nbyte_data *) rtnl_ematch_data(e))->pattern;
+}
+
+size_t rtnl_ematch_nbyte_get_len(struct rtnl_ematch *e)
+{
+	return ((struct nbyte_data *) rtnl_ematch_data(e))->cfg.len;
+}
+
+static const char *layer_txt(struct tcf_em_nbyte *nbyte)
+{
+	switch (nbyte->layer) {
+	case TCF_LAYER_LINK:
+		return "link";
+	case TCF_LAYER_NETWORK:
+		return "net";
+	case TCF_LAYER_TRANSPORT:
+		return "trans";
+	default:
+		return "?";
+	}
+}
+
+static int nbyte_parse(struct rtnl_ematch *e, void *data, size_t len)
+{
+	struct nbyte_data *n = rtnl_ematch_data(e);
+	size_t hdrlen = sizeof(struct tcf_em_nbyte);
+	size_t plen = len - hdrlen;
+
+	memcpy(&n->cfg, data, hdrlen);
+	if (plen > 0) {
+		if (!(n->pattern = calloc(1, plen)))
+			return -NLE_NOMEM;
+
+		memcpy(n->pattern, data + hdrlen, plen);
+	}
+
+	return 0;
+}
+
+static void nbyte_dump(struct rtnl_ematch *e, struct nl_dump_params *p)
+{
+	struct nbyte_data *n = rtnl_ematch_data(e);
+	int i;
+
+	nl_dump(p, "pattern(%u:[", n->cfg.len);
+
+	for (i = 0; i < n->cfg.len; i++) {
+		nl_dump(p, "%02x", n->pattern[i]);
+		if (i+1 < n->cfg.len)
+			nl_dump(p, " ");
+	}
+
+	nl_dump(p, "] at %s+%u)", layer_txt(&n->cfg), n->cfg.off);
+}
+
+static void nbyte_free(struct rtnl_ematch *e)
+{
+	struct nbyte_data *n = rtnl_ematch_data(e);
+	free(n->pattern);
+}
+
+static struct rtnl_ematch_ops nbyte_ops = {
+	.eo_kind	= TCF_EM_NBYTE,
+	.eo_name	= "nbyte",
+	.eo_minlen	= sizeof(struct tcf_em_nbyte),
+	.eo_datalen	= sizeof(struct nbyte_data),
+	.eo_parse	= nbyte_parse,
+	.eo_dump	= nbyte_dump,
+	.eo_free	= nbyte_free,
+};
+
+static void __init nbyte_init(void)
+{
+	rtnl_ematch_register(&nbyte_ops);
+}
+
+/** @} */
diff --git a/lib/route/cls/ematch/text.c b/lib/route/cls/ematch/text.c
new file mode 100644
index 0000000..e8cdcae
--- /dev/null
+++ b/lib/route/cls/ematch/text.c
@@ -0,0 +1,183 @@
+/*
+ * lib/route/cls/ematch/text.c		Text Search
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup ematch
+ * @defgroup em_text Text Search
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/route/cls/ematch.h>
+#include <netlink/route/cls/ematch/text.h>
+
+struct text_data
+{
+	struct tcf_em_text	cfg;
+	char *			pattern;
+};
+
+void rtnl_ematch_text_set_from(struct rtnl_ematch *e, uint8_t layer,
+			       uint16_t offset)
+{
+	struct text_data *t = rtnl_ematch_data(e);
+	t->cfg.from_offset = offset;
+	t->cfg.from_layer = layer;
+}
+
+uint16_t rtnl_ematch_text_get_from_offset(struct rtnl_ematch *e)
+{
+	return ((struct text_data *) rtnl_ematch_data(e))->cfg.from_offset;
+}
+
+uint8_t rtnl_ematch_text_get_from_layer(struct rtnl_ematch *e)
+{
+	return ((struct text_data *) rtnl_ematch_data(e))->cfg.from_layer;
+}
+
+void rtnl_ematch_text_set_to(struct rtnl_ematch *e, uint8_t layer,
+			       uint16_t offset)
+{
+	struct text_data *t = rtnl_ematch_data(e);
+	t->cfg.to_offset = offset;
+	t->cfg.to_layer = layer;
+}
+
+uint16_t rtnl_ematch_text_get_to_offset(struct rtnl_ematch *e)
+{
+	return ((struct text_data *) rtnl_ematch_data(e))->cfg.to_offset;
+}
+
+uint8_t rtnl_ematch_text_get_to_layer(struct rtnl_ematch *e)
+{
+	return ((struct text_data *) rtnl_ematch_data(e))->cfg.to_layer;
+}
+
+void rtnl_ematch_text_set_pattern(struct rtnl_ematch *e,
+				  char *pattern, size_t len)
+{
+	struct text_data *t = rtnl_ematch_data(e);
+
+	if (t->pattern)
+		free(t->pattern);
+
+	t->pattern = pattern;
+	t->cfg.pattern_len = len;
+}
+
+char *rtnl_ematch_text_get_pattern(struct rtnl_ematch *e)
+{
+	return ((struct text_data *) rtnl_ematch_data(e))->pattern;
+}
+
+size_t rtnl_ematch_text_get_len(struct rtnl_ematch *e)
+{
+	return ((struct text_data *) rtnl_ematch_data(e))->cfg.pattern_len;
+}
+
+void rtnl_ematch_text_set_algo(struct rtnl_ematch *e, const char *algo)
+{
+	struct text_data *t = rtnl_ematch_data(e);
+
+	strncpy(t->cfg.algo, algo, sizeof(t->cfg.algo));
+}
+
+char *rtnl_ematch_text_get_algo(struct rtnl_ematch *e)
+{
+	struct text_data *t = rtnl_ematch_data(e);
+
+	return t->cfg.algo[0] ? t->cfg.algo : NULL;
+}
+
+static int text_parse(struct rtnl_ematch *e, void *data, size_t len)
+{
+	struct text_data *t = rtnl_ematch_data(e);
+	size_t hdrlen = sizeof(struct tcf_em_text);
+	size_t plen = len - hdrlen;
+
+	memcpy(&t->cfg, data, hdrlen);
+
+	if (t->cfg.pattern_len > plen)
+		return -NLE_INVAL;
+
+	if (t->cfg.pattern_len > 0) {
+		if (!(t->pattern = calloc(1, t->cfg.pattern_len)))
+			return -NLE_NOMEM;
+
+		memcpy(t->pattern, data + hdrlen, t->cfg.pattern_len);
+	}
+
+	return 0;
+}
+
+static void text_dump(struct rtnl_ematch *e, struct nl_dump_params *p)
+{
+	struct text_data *t = rtnl_ematch_data(e);
+	char buf[64];
+
+	nl_dump(p, "text(%s \"%s\"",
+		t->cfg.algo[0] ? t->cfg.algo : "no-algo",
+		t->pattern ? : "no-pattern");
+
+	if (t->cfg.from_layer || t->cfg.from_offset) {
+		nl_dump(p, " from %s",
+			rtnl_ematch_offset2txt(t->cfg.from_layer,
+					       t->cfg.from_offset,
+					       buf, sizeof(buf)));
+	}
+
+	if (t->cfg.to_layer || t->cfg.to_offset) {
+		nl_dump(p, " to %s",
+			rtnl_ematch_offset2txt(t->cfg.to_layer,
+					       t->cfg.to_offset,
+					       buf, sizeof(buf)));
+	}
+
+	nl_dump(p, ")");
+}
+
+static int text_fill(struct rtnl_ematch *e, struct nl_msg *msg)
+{
+	struct text_data *t = rtnl_ematch_data(e);
+	int err;
+
+	if ((err = nlmsg_append(msg, &t->cfg, sizeof(t->cfg), 0)) < 0)
+		return err;
+
+	return nlmsg_append(msg, t->pattern, t->cfg.pattern_len, 0);
+}
+
+static void text_free(struct rtnl_ematch *e)
+{
+	struct text_data *t = rtnl_ematch_data(e);
+	free(t->pattern);
+}
+
+static struct rtnl_ematch_ops text_ops = {
+	.eo_kind	= TCF_EM_TEXT,
+	.eo_name	= "text",
+	.eo_minlen	= sizeof(struct tcf_em_text),
+	.eo_datalen	= sizeof(struct text_data),
+	.eo_parse	= text_parse,
+	.eo_dump	= text_dump,
+	.eo_fill	= text_fill,
+	.eo_free	= text_free,
+};
+
+static void __init text_init(void)
+{
+	rtnl_ematch_register(&text_ops);
+}
+
+/** @} */
diff --git a/lib/route/cls/ematch_grammar.l b/lib/route/cls/ematch_grammar.l
new file mode 100644
index 0000000..96ef1a0
--- /dev/null
+++ b/lib/route/cls/ematch_grammar.l
@@ -0,0 +1,162 @@
+/*
+ * lib/route/cls/ematch_grammar.l	ematch expression grammar
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+%{
+ #include <netlink-private/netlink.h>
+ #include <netlink-private/tc.h>
+ #include <netlink/netlink.h>
+ #include <netlink/route/cls/ematch.h>
+ #include <netlink/route/cls/ematch/cmp.h>
+ #include "ematch_syntax.h"
+%}
+
+%option 8bit
+%option reentrant
+%option warn
+%option noyywrap
+%option noinput
+%option nounput
+%option bison-bridge
+%option prefix="ematch_"
+
+%x QUOTE
+
+%%
+
+[ \t\r\n]+
+
+\"			{
+				NL_DBG(4, "Beginning of quote\n");
+				yylval->q.len = 32;
+				if (!(yylval->q.data = calloc(1, yylval->q.len)))
+					return ERROR;
+
+				yylval->q.index = 0;
+				BEGIN(QUOTE);
+			}
+
+<QUOTE>[^\\\n\"]+	{
+				memcpy(yylval->q.data + yylval->q.index, yytext,
+				       strlen(yytext));
+				yylval->q.index += strlen(yytext);
+			}
+
+<QUOTE>\"		{
+				BEGIN(0);
+				return QUOTED;
+			}
+
+
+[[:digit:]]+		|
+0[xX][[:xdigit:]]+	{
+				yylval->i = strtoul(yytext, NULL, 0);
+				return NUMBER;
+			}
+
+eq			|
+"="			return KW_EQ;
+gt			|
+">"			return KW_GT;
+lt			|
+"<"			return KW_LT;
+
+[aA][nN][dD]		|
+"&&"			{ yylval->i = TCF_EM_REL_AND; return LOGIC; }
+[oO][rR]		|
+"||"			{ yylval->i = TCF_EM_REL_OR; return LOGIC; }
+[nN][oO][tT]		|
+"!"			return NOT;
+
+[cC][mM][pP]		{ yylval->i = TCF_EM_CMP; return EMATCH_CMP; }
+[pP][aA][tT][tT][eE][rR][nN] { yylval->i = TCF_EM_NBYTE; return EMATCH_NBYTE; }
+[tT][eE][xX][tT]	{ yylval->i = TCF_EM_TEXT; return EMATCH_TEXT; }
+[mM][eE][tT][aA]	{ yylval->i = TCF_EM_META; return EMATCH_META; }
+
+"("			return KW_OPEN;
+")"			return KW_CLOSE;
+[mM][aA][sS][kK]	|
+"&"			return KW_MASK;
+[sS][hH][iI][fF][tT]	|
+">>"			return KW_SHIFT;
+[aA][tT]		return KW_AT;
+"+"			return KW_PLUS;
+[fF][rR][oO][mM]	return KW_FROM;
+[tT][oO]		return KW_TO;
+
+[uU]8			{ yylval->i = TCF_EM_ALIGN_U8; return ALIGN; }
+[uU]16			{ yylval->i = TCF_EM_ALIGN_U16; return ALIGN; }
+[uU]32			{ yylval->i = TCF_EM_ALIGN_U32; return ALIGN; }
+
+[lL][iI][nN][kK]	|
+[eE][tT][hH]		{ yylval->i = TCF_LAYER_LINK; return LAYER; }
+[nN][eE][tT]		|
+[iI][pP]6		|
+[iI][pP]		{ yylval->i = TCF_LAYER_NETWORK; return LAYER; }
+[tT][rR][aA][nN][sS][pP][oO][rR][tT] |
+[tT][cC][pP]		{ yylval->i = TCF_LAYER_TRANSPORT; return LAYER; }
+
+random			return META_RANDOM;
+loadavg_0		return META_LOADAVG_0;
+loadavg_1		return META_LOADAVG_1;
+loadavg_2		return META_LOADAVG_2;
+dev			return META_DEV;
+prio			return META_PRIO;
+proto			return META_PROTO;
+pkttype			return META_PKTTYPE;
+pktlen			return META_PKTLEN;
+datalen			return META_DATALEN;
+maclen			return META_MACLEN;
+mark			return META_MARK;
+tcindex			return META_TCINDEX;
+rtclassid		return META_RTCLASSID;
+rtiif			return META_RTIIF;
+sk_family		return META_SK_FAMILY;
+sk_state		return META_SK_STATE;
+sk_reuse		return META_SK_REUSE;
+sk_refcnt		return META_SK_REFCNT;
+sk_rcvbuf		return META_SK_RCVBUF;
+sk_sndbuf		return META_SK_SNDBUF;
+sk_shutdown		return META_SK_SHUTDOWN;
+sk_proto		return META_SK_PROTO;
+sk_type			return META_SK_TYPE;
+sk_rmem_alloc		return META_SK_RMEM_ALLOC;
+sk_wmem_alloc		return META_SK_WMEM_ALLOC;
+sk_wmem_queued		return META_SK_WMEM_QUEUED;
+sk_rcv_qlen		return META_SK_RCV_QLEN;
+sk_snd_qlen		return META_SK_SND_QLEN;
+sk_err_qlen		return META_SK_ERR_QLEN;
+sk_forward_allocs	return META_SK_FORWARD_ALLOCS;
+sk_allocs		return META_SK_ALLOCS;
+sk_route_caps		return META_SK_ROUTE_CAPS;
+sk_hash			return META_SK_HASH;
+sk_lingertime		return META_SK_LINGERTIME;
+sk_ack_backlog		return META_SK_ACK_BACKLOG;
+sk_max_ack_backlog	return META_SK_MAX_ACK_BACKLOG;
+sk_prio			return META_SK_PRIO;
+sk_rcvlowat		return META_SK_RCVLOWAT;
+sk_rcvtimeo		return META_SK_RCVTIMEO;
+sk_sndtimeo		return META_SK_SNDTIMEO;
+sk_sendmsg_off		return META_SK_SENDMSG_OFF;
+sk_write_pending	return META_SK_WRITE_PENDING;
+vlan			return META_VLAN;
+rxhash			return META_RXHASH;
+
+devname			return META_DEVNAME;
+sk_bound_if		return META_SK_BOUND_IF;
+
+
+[^ \t\r\n+()=<>&|\"]+	{
+				yylval->s = strdup(yytext);
+				if (yylval->s == NULL)
+					return ERROR;
+				NL_DBG(4, "lex STR=%s\n", yylval->s);
+				return STR;
+			}
diff --git a/lib/route/cls/ematch_syntax.y b/lib/route/cls/ematch_syntax.y
new file mode 100644
index 0000000..da21039
--- /dev/null
+++ b/lib/route/cls/ematch_syntax.y
@@ -0,0 +1,501 @@
+/*
+ * lib/route/cls/ematch_syntax.y	ematch expression syntax
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+%{
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/route/pktloc.h>
+#include <netlink/route/cls/ematch.h>
+#include <netlink/route/cls/ematch/cmp.h>
+#include <netlink/route/cls/ematch/nbyte.h>
+#include <netlink/route/cls/ematch/text.h>
+#include <netlink/route/cls/ematch/meta.h>
+
+#define META_ALLOC rtnl_meta_value_alloc_id
+#define META_ID(name) TCF_META_ID_##name
+#define META_INT TCF_META_TYPE_INT
+#define META_VAR TCF_META_TYPE_VAR
+%}
+
+%error-verbose
+%define api.pure
+%name-prefix "ematch_"
+
+%parse-param {void *scanner}
+%parse-param {char **errp}
+%parse-param {struct nl_list_head *root}
+%lex-param {void *scanner}
+
+%union {
+	struct tcf_em_cmp	cmp;
+	struct ematch_quoted	q;
+	struct rtnl_ematch *	e;
+	struct rtnl_pktloc *	loc;
+	struct rtnl_meta_value *mv;
+	uint32_t		i;
+	uint64_t		i64;
+	char *			s;
+}
+
+%{
+extern int ematch_lex(YYSTYPE *, void *);
+
+static void yyerror(void *scanner, char **errp, struct nl_list_head *root, const char *msg)
+{
+	if (msg)
+            *errp = strdup(msg);
+        else
+	    *errp = NULL;
+}
+%}
+
+%token <i> ERROR LOGIC NOT OPERAND NUMBER ALIGN LAYER
+%token <i> KW_OPEN "("
+%token <i> KW_CLOSE ")"
+%token <i> KW_PLUS "+"
+%token <i> KW_MASK "mask"
+%token <i> KW_SHIFT ">>"
+%token <i> KW_AT "at"
+%token <i> EMATCH_CMP "cmp"
+%token <i> EMATCH_NBYTE "pattern"
+%token <i> EMATCH_TEXT "text"
+%token <i> EMATCH_META "meta"
+%token <i> KW_EQ "="
+%token <i> KW_GT ">"
+%token <i> KW_LT "<"
+%token <i> KW_FROM "from"
+%token <i> KW_TO "to"
+
+%token <i> META_RANDOM "random"
+%token <i> META_LOADAVG_0 "loadavg_0"
+%token <i> META_LOADAVG_1 "loadavg_1"
+%token <i> META_LOADAVG_2 "loadavg_2"
+%token <i> META_DEV "dev"
+%token <i> META_PRIO "prio"
+%token <i> META_PROTO "proto"
+%token <i> META_PKTTYPE "pkttype"
+%token <i> META_PKTLEN "pktlen"
+%token <i> META_DATALEN "datalen"
+%token <i> META_MACLEN "maclen"
+%token <i> META_MARK "mark"
+%token <i> META_TCINDEX "tcindex"
+%token <i> META_RTCLASSID "rtclassid"
+%token <i> META_RTIIF "rtiif"
+%token <i> META_SK_FAMILY "sk_family"
+%token <i> META_SK_STATE "sk_state"
+%token <i> META_SK_REUSE "sk_reuse"
+%token <i> META_SK_REFCNT "sk_refcnt"
+%token <i> META_SK_RCVBUF "sk_rcvbuf"
+%token <i> META_SK_SNDBUF "sk_sndbuf"
+%token <i> META_SK_SHUTDOWN "sk_shutdown"
+%token <i> META_SK_PROTO "sk_proto"
+%token <i> META_SK_TYPE "sk_type"
+%token <i> META_SK_RMEM_ALLOC "sk_rmem_alloc"
+%token <i> META_SK_WMEM_ALLOC "sk_wmem_alloc"
+%token <i> META_SK_WMEM_QUEUED "sk_wmem_queued"
+%token <i> META_SK_RCV_QLEN "sk_rcv_qlen"
+%token <i> META_SK_SND_QLEN "sk_snd_qlen"
+%token <i> META_SK_ERR_QLEN "sk_err_qlen"
+%token <i> META_SK_FORWARD_ALLOCS "sk_forward_allocs"
+%token <i> META_SK_ALLOCS "sk_allocs"
+%token <i> META_SK_ROUTE_CAPS "sk_route_caps"
+%token <i> META_SK_HASH "sk_hash"
+%token <i> META_SK_LINGERTIME "sk_lingertime"
+%token <i> META_SK_ACK_BACKLOG "sk_ack_backlog"
+%token <i> META_SK_MAX_ACK_BACKLOG "sk_max_ack_backlog"
+%token <i> META_SK_PRIO "sk_prio"
+%token <i> META_SK_RCVLOWAT "sk_rcvlowat"
+%token <i> META_SK_RCVTIMEO "sk_rcvtimeo"
+%token <i> META_SK_SNDTIMEO "sk_sndtimeo"
+%token <i> META_SK_SENDMSG_OFF "sk_sendmsg_off"
+%token <i> META_SK_WRITE_PENDING "sk_write_pending"
+%token <i> META_VLAN "vlan"
+%token <i> META_RXHASH "rxhash"
+%token <i> META_DEVNAME "devname"
+%token <i> META_SK_BOUND_IF "sk_bound_if"
+
+%token <s> STR
+
+%token <q> QUOTED
+
+%type <i> align operand shift meta_int_id meta_var_id
+%type <i64> mask
+%type <e> expr match ematch
+%type <cmp> cmp_expr cmp_match
+%type <loc> pktloc text_from text_to
+%type <q> pattern
+%type <mv> meta_value
+
+%destructor { free($$); NL_DBG(2, "string destructor\n"); } <s>
+%destructor { rtnl_pktloc_put($$); NL_DBG(2, "pktloc destructor\n"); } <loc>
+%destructor { free($$.data); NL_DBG(2, "quoted destructor\n"); } <q>
+%destructor { rtnl_meta_value_put($$); NL_DBG(2, "meta value destructor\n"); } <mv>
+
+%start input
+
+%%
+
+input:
+	/* empty */
+	| expr
+		{
+			nl_list_add_tail(root, &$1->e_list);
+		}
+	;
+
+expr:
+	match
+		{
+			$$ = $1;
+		}
+	| match LOGIC expr
+		{
+			rtnl_ematch_set_flags($1, $2);
+
+			/* make ematch new head */
+			nl_list_add_tail(&$1->e_list, &$3->e_list);
+
+			$$ = $1;
+		}
+	;
+
+match:
+	NOT ematch
+		{
+			rtnl_ematch_set_flags($2, TCF_EM_INVERT);
+			$$ = $2;
+		}
+	| ematch
+		{
+			$$ = $1;
+		}
+	;
+
+ematch:
+	/* CMP */
+	cmp_match
+		{
+			struct rtnl_ematch *e;
+
+			if (!(e = rtnl_ematch_alloc())) {
+				*errp = strdup("Unable to allocate ematch object");
+				YYABORT;
+			}
+
+			if (rtnl_ematch_set_kind(e, TCF_EM_CMP) < 0)
+				BUG();
+
+			rtnl_ematch_cmp_set(e, &$1);
+			$$ = e;
+		}
+	| EMATCH_NBYTE "(" pktloc KW_EQ pattern ")"
+		{
+			struct rtnl_ematch *e;
+
+			if (!(e = rtnl_ematch_alloc())) {
+				*errp = strdup("Unable to allocate ematch object");
+				YYABORT;
+			}
+
+			if (rtnl_ematch_set_kind(e, TCF_EM_NBYTE) < 0)
+				BUG();
+
+			rtnl_ematch_nbyte_set_offset(e, $3->layer, $3->offset);
+			rtnl_pktloc_put($3);
+			rtnl_ematch_nbyte_set_pattern(e, (uint8_t *) $5.data, $5.index);
+
+			$$ = e;
+		}
+	| EMATCH_TEXT "(" STR QUOTED text_from text_to ")"
+		{
+			struct rtnl_ematch *e;
+
+			if (!(e = rtnl_ematch_alloc())) {
+				*errp = strdup("Unable to allocate ematch object");
+				YYABORT;
+			}
+
+			if (rtnl_ematch_set_kind(e, TCF_EM_TEXT) < 0)
+				BUG();
+
+			rtnl_ematch_text_set_algo(e, $3);
+			rtnl_ematch_text_set_pattern(e, $4.data, $4.index);
+
+			if ($5) {
+				rtnl_ematch_text_set_from(e, $5->layer, $5->offset);
+				rtnl_pktloc_put($5);
+			}
+
+			if ($6) {
+				rtnl_ematch_text_set_to(e, $6->layer, $6->offset);
+				rtnl_pktloc_put($6);
+			}
+
+			$$ = e;
+		}
+	| EMATCH_META "(" meta_value operand meta_value ")"
+		{
+			struct rtnl_ematch *e;
+
+			if (!(e = rtnl_ematch_alloc())) {
+				*errp = strdup("Unable to allocate ematch object");
+				YYABORT;
+			}
+
+			if (rtnl_ematch_set_kind(e, TCF_EM_META) < 0)
+				BUG();
+
+			rtnl_ematch_meta_set_lvalue(e, $3);
+			rtnl_ematch_meta_set_rvalue(e, $5);
+			rtnl_ematch_meta_set_operand(e, $4);
+
+			$$ = e;
+		}
+	/* CONTAINER */
+	| "(" expr ")"
+		{
+			struct rtnl_ematch *e;
+
+			if (!(e = rtnl_ematch_alloc())) {
+				*errp = strdup("Unable to allocate ematch object");
+				YYABORT;
+			}
+
+			if (rtnl_ematch_set_kind(e, TCF_EM_CONTAINER) < 0)
+				BUG();
+
+			/* Make e->childs the list head of a the ematch sequence */
+			nl_list_add_tail(&e->e_childs, &$2->e_list);
+
+			$$ = e;
+		}
+	;
+
+/*
+ * CMP match
+ *
+ * match  := cmp(expr) | expr
+ * expr   := pktloc (=|>|<) NUMBER
+ * pktloc := alias | definition
+ *
+ */
+cmp_match:
+	EMATCH_CMP "(" cmp_expr ")"
+		{ $$ = $3; }
+	| cmp_expr
+		{ $$ = $1; }
+	;
+
+cmp_expr:
+	pktloc operand NUMBER
+		{
+			if ($1->align == TCF_EM_ALIGN_U16 ||
+			    $1->align == TCF_EM_ALIGN_U32)
+				$$.flags = TCF_EM_CMP_TRANS;
+
+			memset(&$$, 0, sizeof($$));
+
+			$$.mask = $1->mask;
+			$$.off = $1->offset;
+			$$.align = $1->align;
+			$$.layer = $1->layer;
+			$$.opnd = $2;
+			$$.val = $3;
+
+			rtnl_pktloc_put($1);
+		}
+	;
+
+text_from:
+	/* empty */
+		{ $$ = NULL; }
+	| "from" pktloc
+		{ $$ = $2; }
+	;
+
+text_to:
+	/* empty */
+		{ $$ = NULL; }
+	| "to" pktloc
+		{ $$ = $2; }
+	;
+
+meta_value:
+	QUOTED
+		{ $$ = rtnl_meta_value_alloc_var($1.data, $1.len); }
+	| NUMBER
+		{ $$ = rtnl_meta_value_alloc_int($1); }
+	| meta_int_id shift mask
+		{ $$ = META_ALLOC(META_INT, $1, $2, $3); }
+	| meta_var_id shift
+		{ $$ = META_ALLOC(META_VAR, $1, $2, 0); }
+	;
+
+meta_int_id:
+	META_RANDOM			{ $$ = META_ID(RANDOM); }
+	|META_LOADAVG_0			{ $$ = META_ID(LOADAVG_0); }
+	|META_LOADAVG_1			{ $$ = META_ID(LOADAVG_1); }
+	|META_LOADAVG_2			{ $$ = META_ID(LOADAVG_2); }
+	| META_DEV			{ $$ = META_ID(DEV); }
+	| META_PRIO			{ $$ = META_ID(PRIORITY); }
+	| META_PROTO			{ $$ = META_ID(PROTOCOL); }
+	| META_PKTTYPE			{ $$ = META_ID(PKTTYPE); }
+	| META_PKTLEN			{ $$ = META_ID(PKTLEN); }
+	| META_DATALEN			{ $$ = META_ID(DATALEN); }
+	| META_MACLEN			{ $$ = META_ID(MACLEN); }
+	| META_MARK			{ $$ = META_ID(NFMARK); }
+	| META_TCINDEX			{ $$ = META_ID(TCINDEX); }
+	| META_RTCLASSID		{ $$ = META_ID(RTCLASSID); }
+	| META_RTIIF			{ $$ = META_ID(RTIIF); }
+	| META_SK_FAMILY		{ $$ = META_ID(SK_FAMILY); }
+	| META_SK_STATE			{ $$ = META_ID(SK_STATE); }
+	| META_SK_REUSE			{ $$ = META_ID(SK_REUSE); }
+	| META_SK_REFCNT		{ $$ = META_ID(SK_REFCNT); }
+	| META_SK_RCVBUF		{ $$ = META_ID(SK_RCVBUF); }
+	| META_SK_SNDBUF		{ $$ = META_ID(SK_SNDBUF); }
+	| META_SK_SHUTDOWN		{ $$ = META_ID(SK_SHUTDOWN); }
+	| META_SK_PROTO			{ $$ = META_ID(SK_PROTO); }
+	| META_SK_TYPE			{ $$ = META_ID(SK_TYPE); }
+	| META_SK_RMEM_ALLOC		{ $$ = META_ID(SK_RMEM_ALLOC); }
+	| META_SK_WMEM_ALLOC		{ $$ = META_ID(SK_WMEM_ALLOC); }
+	| META_SK_WMEM_QUEUED		{ $$ = META_ID(SK_WMEM_QUEUED); }
+	| META_SK_RCV_QLEN		{ $$ = META_ID(SK_RCV_QLEN); }
+	| META_SK_SND_QLEN		{ $$ = META_ID(SK_SND_QLEN); }
+	| META_SK_ERR_QLEN		{ $$ = META_ID(SK_ERR_QLEN); }
+	| META_SK_FORWARD_ALLOCS	{ $$ = META_ID(SK_FORWARD_ALLOCS); }
+	| META_SK_ALLOCS		{ $$ = META_ID(SK_ALLOCS); }
+	| META_SK_ROUTE_CAPS		{ $$ = META_ID(SK_ROUTE_CAPS); }
+	| META_SK_HASH			{ $$ = META_ID(SK_HASH); }
+	| META_SK_LINGERTIME		{ $$ = META_ID(SK_LINGERTIME); }
+	| META_SK_ACK_BACKLOG		{ $$ = META_ID(SK_ACK_BACKLOG); }
+	| META_SK_MAX_ACK_BACKLOG	{ $$ = META_ID(SK_MAX_ACK_BACKLOG); }
+	| META_SK_PRIO			{ $$ = META_ID(SK_PRIO); }
+	| META_SK_RCVLOWAT		{ $$ = META_ID(SK_RCVLOWAT); }
+	| META_SK_RCVTIMEO		{ $$ = META_ID(SK_RCVTIMEO); }
+	| META_SK_SNDTIMEO		{ $$ = META_ID(SK_SNDTIMEO); }
+	| META_SK_SENDMSG_OFF		{ $$ = META_ID(SK_SENDMSG_OFF); }
+	| META_SK_WRITE_PENDING		{ $$ = META_ID(SK_WRITE_PENDING); }
+	| META_VLAN			{ $$ = META_ID(VLAN_TAG); }
+	| META_RXHASH			{ $$ = META_ID(RXHASH); }
+	;
+
+meta_var_id:
+	META_DEVNAME		{ $$ = META_ID(DEV); }
+	| META_SK_BOUND_IF	{ $$ = META_ID(SK_BOUND_IF); }
+	;
+
+/*
+ * pattern
+ */
+pattern:
+	QUOTED
+		{
+			$$ = $1;
+		}
+	| STR
+		{
+			struct nl_addr *addr;
+
+			if (nl_addr_parse($1, AF_UNSPEC, &addr) == 0) {
+				$$.len = nl_addr_get_len(addr);
+
+				$$.index = min_t(int, $$.len, nl_addr_get_prefixlen(addr)/8);
+
+				if (!($$.data = calloc(1, $$.len))) {
+					nl_addr_put(addr);
+					YYABORT;
+				}
+
+				memcpy($$.data, nl_addr_get_binary_addr(addr), $$.len);
+				nl_addr_put(addr);
+			} else {
+				if (asprintf(errp, "invalid pattern \"%s\"", $1) == -1)
+					*errp = NULL;
+				YYABORT;
+			}
+		}
+	;
+
+/*
+ * packet location
+ */
+
+pktloc:
+	STR
+		{
+			struct rtnl_pktloc *loc;
+
+			if (rtnl_pktloc_lookup($1, &loc) < 0) {
+				if (asprintf(errp, "Packet location \"%s\" not found", $1) == -1)
+					*errp = NULL;
+				YYABORT;
+			}
+
+			$$ = loc;
+		}
+	/* [u8|u16|u32|NUM at] LAYER + OFFSET [mask MASK] */
+	| align LAYER "+" NUMBER mask
+		{
+			struct rtnl_pktloc *loc;
+
+			if ($5 && (!$1 || $1 > TCF_EM_ALIGN_U32)) {
+				*errp = strdup("mask only allowed for alignments u8|u16|u32");
+				YYABORT;
+			}
+
+			if (!(loc = rtnl_pktloc_alloc())) {
+				*errp = strdup("Unable to allocate packet location object");
+				YYABORT;
+			}
+
+			loc->name = strdup("<USER-DEFINED>");
+			loc->align = $1;
+			loc->layer = $2;
+			loc->offset = $4;
+			loc->mask = $5;
+
+			$$ = loc;
+		}
+	;
+
+align:
+	/* empty */
+		{ $$ = 0; }
+	| ALIGN "at"
+		{ $$ = $1; }
+	| NUMBER "at"
+		{ $$ = $1; }
+	;
+
+mask:
+	/* empty */
+		{ $$ = 0; }
+	| KW_MASK NUMBER
+		{ $$ = $2; }
+	;
+
+shift:
+	/* empty */
+		{ $$ = 0; }
+	| KW_SHIFT NUMBER
+		{ $$ = $2; }
+	;
+
+operand:
+	KW_EQ
+		{ $$ = TCF_EM_OPND_EQ; }
+	| KW_GT
+		{ $$ = TCF_EM_OPND_GT; }
+	| KW_LT
+		{ $$ = TCF_EM_OPND_LT; }
+	;
diff --git a/lib/route/cls/fw.c b/lib/route/cls/fw.c
index 8cf25b9..b569d4f 100644
--- a/lib/route/cls/fw.c
+++ b/lib/route/cls/fw.c
@@ -6,23 +6,23 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  * Copyright (c) 2006 Petr Gotthard <petr.gotthard@siemens.com>
  * Copyright (c) 2006 Siemens AG Oesterreich
  */
 
 /**
- * @ingroup cls_api
- * @defgroup fw Firewall Classifier
+ * @ingroup cls
+ * @defgroup cls_fw Firewall Classifier
  *
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/classifier.h>
-#include <netlink/route/classifier-modules.h>
 #include <netlink/route/cls/fw.h>
 
 /** @cond SKIP */
@@ -30,21 +30,23 @@
 #define FW_ATTR_ACTION       0x002
 #define FW_ATTR_POLICE       0x004
 #define FW_ATTR_INDEV        0x008
+#define FW_ATTR_MASK         0x010
 /** @endcond */
 
 static struct nla_policy fw_policy[TCA_FW_MAX+1] = {
 	[TCA_FW_CLASSID]	= { .type = NLA_U32 },
 	[TCA_FW_INDEV]		= { .type = NLA_STRING,
 				    .maxlen = IFNAMSIZ },
+	[TCA_FW_MASK]		= { .type = NLA_U32 },
 };
 
-static int fw_msg_parser(struct rtnl_cls *cls)
+static int fw_msg_parser(struct rtnl_tc *tc, void *data)
 {
-	struct rtnl_fw *f = rtnl_cls_data(cls);
 	struct nlattr *tb[TCA_FW_MAX + 1];
+	struct rtnl_fw *f = data;
 	int err;
 
-	err = tca_parse(tb, TCA_FW_MAX, (struct rtnl_tca *) cls, fw_policy);
+	err = tca_parse(tb, TCA_FW_MAX, tc, fw_policy);
 	if (err < 0)
 		return err;
 
@@ -72,21 +74,25 @@
 		f->cf_mask |= FW_ATTR_INDEV;
 	}
 
+	if (tb[TCA_FW_MASK]) {
+		f->cf_fwmask = nla_get_u32(tb[TCA_FW_MASK]);
+		f->cf_mask |= FW_ATTR_MASK;
+	}
+
 	return 0;
 }
 
-static void fw_free_data(struct rtnl_cls *cls)
+static void fw_free_data(struct rtnl_tc *tc, void *data)
 {
-	struct rtnl_fw *f = rtnl_cls_data(cls);
+	struct rtnl_fw *f = data;
 
 	nl_data_free(f->cf_act);
 	nl_data_free(f->cf_police);
 }
 
-static int fw_clone(struct rtnl_cls *_dst, struct rtnl_cls *_src)
+static int fw_clone(void *_dst, void *_src)
 {
-	struct rtnl_fw *dst = rtnl_cls_data(_dst);
-	struct rtnl_fw *src = rtnl_cls_data(_src);
+	struct rtnl_fw *dst = _dst, *src = _src;
 
 	if (src->cf_act && !(dst->cf_act = nl_data_clone(src->cf_act)))
 		return -NLE_NOMEM;
@@ -97,28 +103,41 @@
 	return 0;
 }
 
-static void fw_dump_line(struct rtnl_cls *cls, struct nl_dump_params *p)
+static void fw_dump_line(struct rtnl_tc *tc, void *data,
+			 struct nl_dump_params *p)
 {
-	struct rtnl_fw *f = rtnl_cls_data(cls);
-	char buf[32];
+	struct rtnl_fw *f = data;
 
-	if (f->cf_mask & FW_ATTR_CLASSID)
+	if (!f)
+		return;
+
+	if (f->cf_mask & FW_ATTR_CLASSID) {
+		char buf[32];
+
 		nl_dump(p, " target %s",
 			rtnl_tc_handle2str(f->cf_classid, buf, sizeof(buf)));
+	}
+
+	if (f->cf_mask & FW_ATTR_MASK)
+		nl_dump(p, " mask 0x%x", f->cf_fwmask);
 }
 
-static void fw_dump_details(struct rtnl_cls *cls, struct nl_dump_params *p)
+static void fw_dump_details(struct rtnl_tc *tc, void *data,
+			    struct nl_dump_params *p)
 {
-	struct rtnl_fw *f = rtnl_cls_data(cls);
+	struct rtnl_fw *f = data;
 
-	if (f->cf_mask & FW_ATTR_INDEV)
+	if (f && f->cf_mask & FW_ATTR_INDEV)
 		nl_dump(p, "indev %s ", f->cf_indev);
 }
 
-static int fw_get_opts(struct rtnl_cls *cls, struct nl_msg *msg)
+static int fw_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
 {
-	struct rtnl_fw *f = rtnl_cls_data(cls);
-	
+	struct rtnl_fw *f = data;
+
+	if (!f)
+		return 0;
+
 	if (f->cf_mask & FW_ATTR_CLASSID)
 		NLA_PUT_U32(msg, TCA_FW_CLASSID, f->cf_classid);
 
@@ -131,10 +150,13 @@
 	if (f->cf_mask & FW_ATTR_INDEV)
 		NLA_PUT_STRING(msg, TCA_FW_INDEV, f->cf_indev);
 
+	if (f->cf_mask & FW_ATTR_MASK)
+		NLA_PUT_U32(msg, TCA_FW_MASK, f->cf_fwmask);
+
 	return 0;
 
 nla_put_failure:
-	return -NLE_NOMEM;
+	return -NLE_MSGSIZE;
 }
 
 /**
@@ -144,7 +166,10 @@
 
 int rtnl_fw_set_classid(struct rtnl_cls *cls, uint32_t classid)
 {
-	struct rtnl_fw *f = rtnl_cls_data(cls);
+	struct rtnl_fw *f;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
 	
 	f->cf_classid = classid;
 	f->cf_mask |= FW_ATTR_CLASSID;
@@ -152,16 +177,30 @@
 	return 0;
 }
 
+int rtnl_fw_set_mask(struct rtnl_cls *cls, uint32_t mask)
+{
+	struct rtnl_fw *f;
+
+	if (!(f = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+	
+	f->cf_fwmask = mask;
+	f->cf_mask |= FW_ATTR_MASK;
+
+	return 0;
+}
+
 /** @} */
 
-static struct rtnl_cls_ops fw_ops = {
-	.co_kind		= "fw",
-	.co_size		= sizeof(struct rtnl_fw),
-	.co_msg_parser		= fw_msg_parser,
-	.co_free_data		= fw_free_data,
-	.co_clone		= fw_clone,
-	.co_get_opts		= fw_get_opts,
-	.co_dump = {
+static struct rtnl_tc_ops fw_ops = {
+	.to_kind		= "fw",
+	.to_type		= RTNL_TC_TYPE_CLS,
+	.to_size		= sizeof(struct rtnl_fw),
+	.to_msg_parser		= fw_msg_parser,
+	.to_msg_fill		= fw_msg_fill,
+	.to_free_data		= fw_free_data,
+	.to_clone		= fw_clone,
+	.to_dump = {
 	    [NL_DUMP_LINE]	= fw_dump_line,
 	    [NL_DUMP_DETAILS]	= fw_dump_details,
 	},
@@ -169,12 +208,12 @@
 
 static void __init fw_init(void)
 {
-	rtnl_cls_register(&fw_ops);
+	rtnl_tc_register(&fw_ops);
 }
 
 static void __exit fw_exit(void)
 {
-	rtnl_cls_unregister(&fw_ops);
+	rtnl_tc_unregister(&fw_ops);
 }
 
 /** @} */
diff --git a/lib/route/cls/police.c b/lib/route/cls/police.c
index 051c6b2..1f5d284 100644
--- a/lib/route/cls/police.c
+++ b/lib/route/cls/police.c
@@ -6,16 +6,15 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
-#include <netlink/route/tc.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/classifier.h>
-#include <netlink/route/classifier-modules.h>
 #include <netlink/route/cls/police.h>
 
 /**
@@ -23,7 +22,7 @@
  * @{
  */
 
-static struct trans_tbl police_types[] = {
+static const struct trans_tbl police_types[] = {
 	__ADD(TC_POLICE_UNSPEC,unspec)
 	__ADD(TC_POLICE_OK,ok)
 	__ADD(TC_POLICE_RECLASSIFY,reclassify)
diff --git a/lib/route/cls/u32.c b/lib/route/cls/u32.c
index 80b8851..0a4e83c 100644
--- a/lib/route/cls/u32.c
+++ b/lib/route/cls/u32.c
@@ -6,27 +6,27 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  * Copyright (c) 2005-2006 Petr Gotthard <petr.gotthard@siemens.com>
  * Copyright (c) 2005-2006 Siemens AG Oesterreich
  */
 
 /**
- * @ingroup cls_api
- * @defgroup u32 Universal 32-bit Classifier
+ * @ingroup cls
+ * @defgroup cls_u32 Universal 32-bit Classifier
  *
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/utils.h>
-#include <netlink/route/tc.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/classifier.h>
-#include <netlink/route/classifier-modules.h>
 #include <netlink/route/cls/u32.h>
+#include <netlink/route/action.h>
 
 /** @cond SKIP */
 #define U32_ATTR_DIVISOR      0x001
@@ -64,13 +64,13 @@
 	[TCA_U32_PCNT]		= { .minlen = sizeof(struct tc_u32_pcnt) },
 };
 
-static int u32_msg_parser(struct rtnl_cls *cls)
+static int u32_msg_parser(struct rtnl_tc *tc, void *data)
 {
-	struct rtnl_u32 *u = rtnl_cls_data(cls);
+	struct rtnl_u32 *u = data;
 	struct nlattr *tb[TCA_U32_MAX + 1];
 	int err;
 
-	err = tca_parse(tb, TCA_U32_MAX, (struct rtnl_tca *) cls, u32_policy);
+	err = tca_parse(tb, TCA_U32_MAX, tc, u32_policy);
 	if (err < 0)
 		return err;
 
@@ -102,10 +102,10 @@
 	}
 
 	if (tb[TCA_U32_ACT]) {
-		u->cu_act = nl_data_alloc_attr(tb[TCA_U32_ACT]);
-		if (!u->cu_act)
-			goto errout_nomem;
 		u->cu_mask |= U32_ATTR_ACTION;
+		err = rtnl_act_parse(&u->cu_act, tb[TCA_U32_ACT]);
+		if (err)
+			return err;
 	}
 
 	if (tb[TCA_U32_POLICE]) {
@@ -117,7 +117,7 @@
 
 	if (tb[TCA_U32_PCNT]) {
 		struct tc_u32_sel *sel;
-		int pcnt_size;
+		size_t pcnt_size;
 
 		if (!tb[TCA_U32_SEL]) {
 			err = -NLE_MISSING_ATTR;
@@ -151,27 +151,31 @@
 	return err;
 }
 
-static void u32_free_data(struct rtnl_cls *cls)
+static void u32_free_data(struct rtnl_tc *tc, void *data)
 {
-	struct rtnl_u32 *u = rtnl_cls_data(cls);
+	struct rtnl_u32 *u = data;
 
+	if (u->cu_act)
+		rtnl_act_put_all(&u->cu_act);
 	nl_data_free(u->cu_selector);
-	nl_data_free(u->cu_act);
 	nl_data_free(u->cu_police);
 	nl_data_free(u->cu_pcnt);
 }
 
-static int u32_clone(struct rtnl_cls *_dst, struct rtnl_cls *_src)
+static int u32_clone(void *_dst, void *_src)
 {
-	struct rtnl_u32 *dst = rtnl_cls_data(_dst);
-	struct rtnl_u32 *src = rtnl_cls_data(_src);
+	struct rtnl_u32 *dst = _dst, *src = _src;
 
 	if (src->cu_selector &&
 	    !(dst->cu_selector = nl_data_clone(src->cu_selector)))
 		return -NLE_NOMEM;
 
-	if (src->cu_act && !(dst->cu_act = nl_data_clone(src->cu_act)))
-		return -NLE_NOMEM;
+	if (src->cu_act) {
+		if (!(dst->cu_act = rtnl_act_alloc()))
+			return -NLE_NOMEM;
+
+		memcpy(dst->cu_act, src->cu_act, sizeof(struct rtnl_act));
+	}
 
 	if (src->cu_police && !(dst->cu_police = nl_data_clone(src->cu_police)))
 		return -NLE_NOMEM;
@@ -182,10 +186,14 @@
 	return 0;
 }
 
-static void u32_dump_line(struct rtnl_cls *cls, struct nl_dump_params *p)
+static void u32_dump_line(struct rtnl_tc *tc, void *data,
+			  struct nl_dump_params *p)
 {
-	struct rtnl_u32 *u = rtnl_cls_data(cls);
+	struct rtnl_u32 *u = data;
 	char buf[32];
+	
+	if (!u)
+		return;
 
 	if (u->cu_mask & U32_ATTR_DIVISOR)
 		nl_dump(p, " divisor %u", u->cu_divisor);
@@ -195,7 +203,7 @@
 }
 
 static void print_selector(struct nl_dump_params *p, struct tc_u32_sel *sel,
-			   struct rtnl_cls *cls, struct rtnl_u32 *u)
+			   struct rtnl_u32 *u)
 {
 	int i;
 	struct tc_u32_key *key;
@@ -253,11 +261,15 @@
 	}
 }
 
-static void u32_dump_details(struct rtnl_cls *cls, struct nl_dump_params *p)
+static void u32_dump_details(struct rtnl_tc *tc, void *data,
+			     struct nl_dump_params *p)
 {
-	struct rtnl_u32 *u = rtnl_cls_data(cls);
+	struct rtnl_u32 *u = data;
 	struct tc_u32_sel *s;
 
+	if (!u)
+		return;
+
 	if (!(u->cu_mask & U32_ATTR_SELECTOR)) {
 		nl_dump(p, "no-selector\n");
 		return;
@@ -277,33 +289,32 @@
 	if (u->cu_mask & U32_ATTR_INDEV)
 		nl_dump(p, "indev %s ", u->cu_indev);
 
-	print_selector(p, s, cls, u);
+	print_selector(p, s, u);
 	nl_dump(p, "\n");
-
-#if 0	
-#define U32_ATTR_ACTION       0x040
-#define U32_ATTR_POLICE       0x080
-
-	struct nl_data   act;
-	struct nl_data   police;
-#endif
 }
 
-static void u32_dump_stats(struct rtnl_cls *cls, struct nl_dump_params *p)
+static void u32_dump_stats(struct rtnl_tc *tc, void *data,
+			   struct nl_dump_params *p)
 {
-	struct rtnl_u32 *u = rtnl_cls_data(cls);
+	struct rtnl_u32 *u = data;
+
+	if (!u)
+		return;
 
 	if (u->cu_mask & U32_ATTR_PCNT) {
 		struct tc_u32_pcnt *pc = u->cu_pcnt->d_data;
 		nl_dump(p, "\n");
-		nl_dump_line(p, "    hit %8llu count %8llu\n",
+		nl_dump_line(p, "    hit %8" PRIu64 " count %8" PRIu64 "\n",
 			     pc->rhit, pc->rcnt);
 	}
 }
 
-static int u32_get_opts(struct rtnl_cls *cls, struct nl_msg *msg)
+static int u32_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
 {
-	struct rtnl_u32 *u = rtnl_cls_data(cls);
+	struct rtnl_u32 *u = data;
+
+	if (!u)
+		return 0;
 	
 	if (u->cu_mask & U32_ATTR_DIVISOR)
 		NLA_PUT_U32(msg, TCA_U32_DIVISOR, u->cu_divisor);
@@ -320,8 +331,13 @@
 	if (u->cu_mask & U32_ATTR_SELECTOR)
 		NLA_PUT_DATA(msg, TCA_U32_SEL, u->cu_selector);
 
-	if (u->cu_mask & U32_ATTR_ACTION)
-		NLA_PUT_DATA(msg, TCA_U32_ACT, u->cu_act);
+	if (u->cu_mask & U32_ATTR_ACTION) {
+		int err;
+
+		err = rtnl_act_fill(msg, TCA_U32_ACT, u->cu_act);
+		if (err)
+			return err;
+	}
 
 	if (u->cu_mask & U32_ATTR_POLICE)
 		NLA_PUT_DATA(msg, TCA_U32_POLICE, u->cu_police);
@@ -345,12 +361,15 @@
 {
 	uint32_t handle = (htid << 20) | (hash << 12) | nodeid;
 
-	tca_set_handle((struct rtnl_tca *) cls, handle );
+	rtnl_tc_set_handle((struct rtnl_tc *) cls, handle );
 }
  
 int rtnl_u32_set_classid(struct rtnl_cls *cls, uint32_t classid)
 {
-	struct rtnl_u32 *u = rtnl_cls_data(cls);
+	struct rtnl_u32 *u;
+
+	if (!(u = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
 	
 	u->cu_classid = classid;
 	u->cu_mask |= U32_ATTR_CLASSID;
@@ -358,6 +377,130 @@
 	return 0;
 }
 
+int rtnl_u32_set_divisor(struct rtnl_cls *cls, uint32_t divisor)
+{
+	struct rtnl_u32 *u;
+
+	if (!(u = (struct rtnl_u32 *) rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	u->cu_divisor = divisor;
+	u->cu_mask |= U32_ATTR_DIVISOR;
+	return 0;
+}
+
+int rtnl_u32_set_link(struct rtnl_cls *cls, uint32_t link)
+{
+	struct rtnl_u32 *u;
+
+	if (!(u = (struct rtnl_u32 *) rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	u->cu_link = link;
+	u->cu_mask |= U32_ATTR_LINK;
+	return 0;
+}
+
+int rtnl_u32_set_hashtable(struct rtnl_cls *cls, uint32_t ht)
+{
+	struct rtnl_u32 *u;
+
+	if (!(u = (struct rtnl_u32 *) rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	u->cu_hash = ht;
+	u->cu_mask |= U32_ATTR_HASH;
+	return 0;
+}
+
+int rtnl_u32_set_hashmask(struct rtnl_cls *cls, uint32_t hashmask, uint32_t offset)
+{
+	struct rtnl_u32 *u;
+	struct tc_u32_sel *sel;
+	int err;
+
+	hashmask = htonl(hashmask);
+
+	if (!(u = (struct rtnl_u32 *) rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	sel = u32_selector_alloc(u);
+	if (!sel)
+		return -NLE_NOMEM;
+
+	err = nl_data_append(u->cu_selector, NULL, sizeof(struct tc_u32_key));
+	if(err < 0)
+		return err;
+
+	sel = u32_selector(u);
+
+	sel->hmask = hashmask;
+	sel->hoff = offset;
+	return 0;
+}
+
+int rtnl_u32_set_cls_terminal(struct rtnl_cls *cls)
+{
+	struct rtnl_u32 *u;
+	struct tc_u32_sel *sel;
+	int err;
+
+	if (!(u = (struct rtnl_u32 *) rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	sel = u32_selector_alloc(u);
+	if (!sel)
+		return -NLE_NOMEM;
+
+	err = nl_data_append(u->cu_selector, NULL, sizeof(struct tc_u32_key));
+	if(err < 0)
+		return err;
+
+	sel = u32_selector(u);
+
+	sel->flags |= TC_U32_TERMINAL;
+	return 0;
+}
+
+int rtnl_u32_add_action(struct rtnl_cls *cls, struct rtnl_act *act)
+{
+	struct rtnl_u32 *u;
+
+	if (!act)
+		return 0;
+
+	if (!(u = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	u->cu_mask |= U32_ATTR_ACTION;
+	/* In case user frees it */
+	rtnl_act_get(act);
+	return rtnl_act_append(&u->cu_act, act);
+}
+
+int rtnl_u32_del_action(struct rtnl_cls *cls, struct rtnl_act *act)
+{
+	struct rtnl_u32 *u;
+	int ret;
+
+	if (!act)
+		return 0;
+
+	if (!(u = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	if (!(u->cu_mask & U32_ATTR_ACTION))
+		return -NLE_INVAL;
+
+	ret = rtnl_act_remove(&u->cu_act, act);
+	if (ret)
+		return ret;
+
+	if (!u->cu_act)
+		u->cu_mask &= ~U32_ATTR_ACTION;
+	rtnl_act_put(act);
+	return 0;
+}
 /** @} */
 
 /**
@@ -368,7 +511,10 @@
 int rtnl_u32_set_flags(struct rtnl_cls *cls, int flags)
 {
 	struct tc_u32_sel *sel;
-	struct rtnl_u32 *u = rtnl_cls_data(cls);
+	struct rtnl_u32 *u;
+
+	if (!(u = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
 
 	sel = u32_selector_alloc(u);
 	if (!sel)
@@ -398,9 +544,12 @@
 		     int off, int offmask)
 {
 	struct tc_u32_sel *sel;
-	struct rtnl_u32 *u = rtnl_cls_data(cls);
+	struct rtnl_u32 *u;
 	int err;
 
+	if (!(u = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
 	sel = u32_selector_alloc(u);
 	if (!sel)
 		return -NLE_NOMEM;
@@ -422,6 +571,42 @@
 	return 0;
 }
 
+/**
+ * Get the 32-bit key from the selector
+ *
+ * @arg cls	classifier to be retrieve
+ * @arg index	the index of the array of keys, start with 0
+ * @arg val	pointer to store value after masked (network byte-order)
+ * @arg mask	pointer to store the mask (network byte-order)
+ * @arg off	pointer to store the offset
+ * @arg offmask	pointer to store offset mask
+ *
+*/
+int rtnl_u32_get_key(struct rtnl_cls *cls, uint8_t index,
+		     uint32_t *val, uint32_t *mask, int *off, int *offmask)
+{
+	struct tc_u32_sel *sel;
+	struct rtnl_u32 *u;
+
+	if (!(u = rtnl_tc_data(TC_CAST(cls))))
+		return -NLE_NOMEM;
+
+	if (!(u->cu_mask & U32_ATTR_SELECTOR))
+		return -NLE_INVAL;
+
+	/* the selector might have been moved by realloc */
+	sel = u32_selector(u);
+	if (index >= sel->nkeys)
+		return -NLE_RANGE;
+
+	*mask = sel->keys[index].mask;
+	*val = sel->keys[index].val;
+	*off = sel->keys[index].off;
+	*offmask = sel->keys[index].offmask;
+	return 0;
+}
+
+
 int rtnl_u32_add_key_uint8(struct rtnl_cls *cls, uint8_t val, uint8_t mask,
 			   int off, int offmask)
 {
@@ -469,14 +654,14 @@
 				off & ~3, offmask);
 }
 
-int rtnl_u32_add_key_in_addr(struct rtnl_cls *cls, struct in_addr *addr,
+int rtnl_u32_add_key_in_addr(struct rtnl_cls *cls, const struct in_addr *addr,
 			     uint8_t bitmask, int off, int offmask)
 {
 	uint32_t mask = 0xFFFFFFFF << (32 - bitmask);
 	return rtnl_u32_add_key(cls, addr->s_addr, htonl(mask), off, offmask);
 }
 
-int rtnl_u32_add_key_in6_addr(struct rtnl_cls *cls, struct in6_addr *addr,
+int rtnl_u32_add_key_in6_addr(struct rtnl_cls *cls, const struct in6_addr *addr,
 			      uint8_t bitmask, int off, int offmask)
 {
 	int i, err;
@@ -501,14 +686,15 @@
 
 /** @} */
 
-static struct rtnl_cls_ops u32_ops = {
-	.co_kind		= "u32",
-	.co_size		= sizeof(struct rtnl_u32),
-	.co_msg_parser		= u32_msg_parser,
-	.co_free_data		= u32_free_data,
-	.co_clone		= u32_clone,
-	.co_get_opts		= u32_get_opts,
-	.co_dump = {
+static struct rtnl_tc_ops u32_ops = {
+	.to_kind		= "u32",
+	.to_type		= RTNL_TC_TYPE_CLS,
+	.to_size		= sizeof(struct rtnl_u32),
+	.to_msg_parser		= u32_msg_parser,
+	.to_free_data		= u32_free_data,
+	.to_clone		= u32_clone,
+	.to_msg_fill		= u32_msg_fill,
+	.to_dump = {
 	    [NL_DUMP_LINE]	= u32_dump_line,
 	    [NL_DUMP_DETAILS]	= u32_dump_details,
 	    [NL_DUMP_STATS]	= u32_dump_stats,
@@ -517,12 +703,12 @@
 
 static void __init u32_init(void)
 {
-	rtnl_cls_register(&u32_ops);
+	rtnl_tc_register(&u32_ops);
 }
 
 static void __exit u32_exit(void)
 {
-	rtnl_cls_unregister(&u32_ops);
+	rtnl_tc_unregister(&u32_ops);
 }
 
 /** @} */
diff --git a/lib/route/cls_api.c b/lib/route/cls_api.c
deleted file mode 100644
index 73f05df..0000000
--- a/lib/route/cls_api.c
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * lib/route/cls_api.c       Classifier Module API
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @ingroup cls
- * @defgroup cls_api Classifier Modules
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/utils.h>
-#include <netlink/route/tc.h>
-#include <netlink/route/classifier.h>
-#include <netlink/route/classifier-modules.h>
-#include <netlink/route/link.h>
-
-static struct rtnl_cls_ops *cls_ops_list;
-
-/**
- * @name Classifier Module API
- * @{
- */
-
-/**
- * Register a classifier module
- * @arg cops		classifier module operations
- */
-int rtnl_cls_register(struct rtnl_cls_ops *cops)
-{
-	struct rtnl_cls_ops *o, **op;
-
-	if (!cops->co_kind)
-		BUG();
-
-	for (op = &cls_ops_list; (o = *op) != NULL; op = &o->co_next)
-		if (!strcasecmp(cops->co_kind, o->co_kind))
-			return -NLE_EXIST;
-
-	cops->co_next = NULL;
-	*op = cops;
-
-	return 0;
-}
-
-/**
- * Unregister a classifier module
- * @arg cops		classifier module operations
- */
-int rtnl_cls_unregister(struct rtnl_cls_ops *cops)
-{
-	struct rtnl_cls_ops *o, **op;
-
-	for (op = &cls_ops_list; (o = *op) != NULL; op = &o->co_next)
-		if (!strcasecmp(cops->co_kind, o->co_kind))
-			break;
-
-	if (!o)
-		return -NLE_OBJ_NOTFOUND;
-
-	*op = cops->co_next;
-
-	return 0;
-}
-
-struct rtnl_cls_ops *__rtnl_cls_lookup_ops(const char *kind)
-{
-	struct rtnl_cls_ops *cops;
-
-	for (cops = cls_ops_list; cops; cops = cops->co_next)
-		if (!strcmp(kind, cops->co_kind))
-			return cops;
-
-	return NULL;
-}
-
-/**
- * Lookup classifier operations for a classifier object
- * @arg cls		Classifier object.
- *
- * @return Classifier operations or NULL if not found.
- */
-struct rtnl_cls_ops *rtnl_cls_lookup_ops(struct rtnl_cls *cls)
-{
-	if (!cls->c_ops)
-		cls->c_ops = __rtnl_cls_lookup_ops(cls->c_kind);
-
-	return cls->c_ops;
-}
-
-
-/** @} */
-
-/** @} */
diff --git a/lib/route/cls_obj.c b/lib/route/cls_obj.c
deleted file mode 100644
index c8218c0..0000000
--- a/lib/route/cls_obj.c
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * lib/route/cls_api.c       Classifier Object
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @ingroup cls
- * @defgroup cls_obj Classifier Object
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/utils.h>
-#include <netlink/route/tc.h>
-#include <netlink/route/classifier.h>
-#include <netlink/route/classifier-modules.h>
-#include <netlink/route/link.h>
-
-/** @cond SKIP */
-#define CLS_ATTR_PRIO		(TCA_ATTR_MAX << 1)
-#define CLS_ATTR_PROTOCOL	(TCA_ATTR_MAX << 2)
-/** @endcond */
-
-static void cls_free_data(struct nl_object *obj)
-{
-	struct rtnl_cls *cls = (struct rtnl_cls *) obj;
-	struct rtnl_cls_ops *cops;
-	
-	tca_free_data((struct rtnl_tca *) cls);
-
-	cops = rtnl_cls_lookup_ops(cls);
-	if (cops && cops->co_free_data)
-		cops->co_free_data(cls);
-
-	nl_data_free(cls->c_subdata);
-}
-
-static int cls_clone(struct nl_object *_dst, struct nl_object *_src)
-{
-	struct rtnl_cls *dst = nl_object_priv(_dst);
-	struct rtnl_cls *src = nl_object_priv(_src);
-	struct rtnl_cls_ops *cops;
-	int err;
-	
-	err = tca_clone((struct rtnl_tca *) dst, (struct rtnl_tca *) src);
-	if (err < 0)
-		goto errout;
-
-	if (src->c_subdata) {
-		if (!(dst->c_subdata = nl_data_clone(src->c_subdata))) {
-			err = -NLE_NOMEM;
-			goto errout;
-		}
-	}
-
-	cops = rtnl_cls_lookup_ops(src);
-	if (cops && cops->co_clone)
-		err = cops->co_clone(dst, src);
-errout:
-	return err;
-}
-
-static void cls_dump_line(struct nl_object *obj, struct nl_dump_params *p)
-{
-	char buf[32];
-	struct rtnl_cls *cls = (struct rtnl_cls *) obj;
-	struct rtnl_cls_ops *cops;
-
-	tca_dump_line((struct rtnl_tca *) cls, "cls", p);
-
-	nl_dump(p, " prio %u protocol %s", cls->c_prio,
-		nl_ether_proto2str(cls->c_protocol, buf, sizeof(buf)));
-
-	cops = rtnl_cls_lookup_ops(cls);
-	if (cops && cops->co_dump[NL_DUMP_LINE])
-		cops->co_dump[NL_DUMP_LINE](cls, p);
-	nl_dump(p, "\n");
-}
-
-static void cls_dump_details(struct nl_object *obj, struct nl_dump_params *p)
-{
-	struct rtnl_cls *cls = (struct rtnl_cls *) obj;
-	struct rtnl_cls_ops *cops;
-
-	cls_dump_line(obj, p);
-	tca_dump_details((struct rtnl_tca *) cls, p);
-
-	cops = rtnl_cls_lookup_ops(cls);
-	if (cops && cops->co_dump[NL_DUMP_DETAILS])
-		cops->co_dump[NL_DUMP_DETAILS](cls, p);
-	else
-		nl_dump(p, "no options\n");
-}
-
-static void cls_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
-{
-	struct rtnl_cls *cls = (struct rtnl_cls *) obj;
-	struct rtnl_cls_ops *cops;
-
-	cls_dump_details(obj, p);
-	tca_dump_stats((struct rtnl_tca *) cls, p);
-	nl_dump(p, "\n");
-
-	cops = rtnl_cls_lookup_ops(cls);
-	if (cops && cops->co_dump[NL_DUMP_STATS])
-		cops->co_dump[NL_DUMP_STATS](cls, p);
-}
-
-/**
- * @name Allocation/Freeing
- * @{
- */
-
-struct rtnl_cls *rtnl_cls_alloc(void)
-{
-	return (struct rtnl_cls *) nl_object_alloc(&cls_obj_ops);
-}
-
-void rtnl_cls_put(struct rtnl_cls *cls)
-{
-	nl_object_put((struct nl_object *) cls);
-}
-
-/** @} */
-
-
-/**
- * @name Attributes
- * @{
- */
-
-void rtnl_cls_set_ifindex(struct rtnl_cls *f, int ifindex)
-{
-	tca_set_ifindex((struct rtnl_tca *) f, ifindex);
-}
-
-int rtnl_cls_get_ifindex(struct rtnl_cls *cls)
-{
-	return cls->c_ifindex;
-}
-
-void rtnl_cls_set_handle(struct rtnl_cls *f, uint32_t handle)
-{
-	tca_set_handle((struct rtnl_tca *) f, handle);
-}
-
-void rtnl_cls_set_parent(struct rtnl_cls *f, uint32_t parent)
-{
-	tca_set_parent((struct rtnl_tca *) f, parent);
-}
-
-uint32_t rtnl_cls_get_parent(struct rtnl_cls *cls)
-{
-	return cls->c_parent;
-}
-
-int rtnl_cls_set_kind(struct rtnl_cls *cls, const char *kind)
-{
-	if (cls->ce_mask & TCA_ATTR_KIND)
-		return -NLE_EXIST;
-
-	tca_set_kind((struct rtnl_tca *) cls, kind);
-
-	/* Force allocation of data */
-	rtnl_cls_data(cls);
-
-	return 0;
-}
-
-struct rtnl_cls_ops *rtnl_cls_get_ops(struct rtnl_cls *cls)
-{
-	return cls->c_ops;
-}
-
-void rtnl_cls_set_prio(struct rtnl_cls *cls, uint16_t prio)
-{
-	cls->c_prio = prio;
-	cls->ce_mask |= CLS_ATTR_PRIO;
-}
-
-uint16_t rtnl_cls_get_prio(struct rtnl_cls *cls)
-{
-	if (cls->ce_mask & CLS_ATTR_PRIO)
-		return cls->c_prio;
-	else
-		return 0;
-}
-
-void rtnl_cls_set_protocol(struct rtnl_cls *cls, uint16_t protocol)
-{
-	cls->c_protocol = protocol;
-	cls->ce_mask |= CLS_ATTR_PROTOCOL;
-}
-
-uint16_t rtnl_cls_get_protocol(struct rtnl_cls *cls)
-{
-	if (cls->ce_mask & CLS_ATTR_PROTOCOL)
-		return cls->c_protocol;
-	else
-		return ETH_P_ALL;
-}
-
-void *rtnl_cls_data(struct rtnl_cls *cls)
-{
-	if (!cls->c_subdata) {
-		struct rtnl_cls_ops *ops = cls->c_ops;
-
-		if (!ops) {
-			if (!cls->c_kind[0])
-				BUG();
-
-			ops = __rtnl_cls_lookup_ops(cls->c_kind);
-			if (ops == NULL)
-				return NULL;
-
-			cls->c_ops = ops;
-		}
-
-		if (!ops->co_size)
-			BUG();
-
-		if (!(cls->c_subdata = nl_data_alloc(NULL, ops->co_size)))
-			return NULL;
-	}
-
-	return nl_data_get(cls->c_subdata);
-}
-
-/** @} */
-
-struct nl_object_ops cls_obj_ops = {
-	.oo_name		= "route/cls",
-	.oo_size		= sizeof(struct rtnl_cls),
-	.oo_free_data		= cls_free_data,
-	.oo_clone		= cls_clone,
-	.oo_dump = {
-	    [NL_DUMP_LINE]	= cls_dump_line,
-	    [NL_DUMP_DETAILS]	= cls_dump_details,
-	    [NL_DUMP_STATS]	= cls_dump_stats,
-	},
-	.oo_compare		= tca_compare,
-	.oo_id_attrs		= (TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE),
-};
-
-/** @} */
diff --git a/lib/route/link.c b/lib/route/link.c
index cf488e5..3d31ffc 100644
--- a/lib/route/link.c
+++ b/lib/route/link.c
@@ -6,188 +6,199 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup rtnl
  * @defgroup link Links (Interfaces)
- * @brief
  *
- * @par Link Identification
- * A link can be identified by either its interface index or by its
- * name. The kernel favours the interface index but falls back to the
- * interface name if the interface index is lesser-than 0 for kernels
- * >= 2.6.11. Therefore you can request changes without mapping a
- * interface name to the corresponding index first.
- *
- * @par Changeable Attributes
- * @anchor link_changeable
- *  - Link layer address
- *  - Link layer broadcast address
- *  - device mapping (ifmap) (>= 2.6.9)
- *  - MTU (>= 2.6.9)
- *  - Transmission queue length (>= 2.6.9)
- *  - Weight (>= 2.6.9)
- *  - Link name (only via access through interface index) (>= 2.6.9)
- *  - Flags (>= 2.6.9)
- *    - IFF_DEBUG
- *    - IFF_NOTRAILERS
- *    - IFF_NOARP
- *    - IFF_DYNAMIC
- *    - IFF_MULTICAST
- *    - IFF_PORTSEL
- *    - IFF_AUTOMEDIA
- *    - IFF_UP
- *    - IFF_PROMISC
- *    - IFF_ALLMULTI
- *
- * @par Link Flags (linux/if.h)
- * @anchor link_flags
- * @code
- *   IFF_UP            Status of link (up|down)
- *   IFF_BROADCAST     Indicates this link allows broadcasting
- *   IFF_MULTICAST     Indicates this link allows multicasting
- *   IFF_ALLMULTI      Indicates this link is doing multicast routing
- *   IFF_DEBUG         Tell the driver to do debugging (currently unused)
- *   IFF_LOOPBACK      This is the loopback link
- *   IFF_POINTOPOINT   Point-to-point link
- *   IFF_NOARP         Link is unable to perform ARP
- *   IFF_PROMISC       Status of promiscious mode flag
- *   IFF_MASTER        Used by teql
- *   IFF_SLAVE         Used by teql
- *   IFF_PORTSEL       Indicates this link allows port selection
- *   IFF_AUTOMEDIA     Indicates this link selects port automatically
- *   IFF_DYNAMIC       Indicates the address of this link is dynamic
- *   IFF_RUNNING       Link is running and carrier is ok.
- *   IFF_NOTRAILERS    Unused, BSD compat.
- * @endcode
- *
- * @par Notes on IFF_PROMISC and IFF_ALLMULTI flags
- * Although you can query the status of IFF_PROMISC and IFF_ALLMULTI
- * they do not represent the actual state in the kernel but rather
- * whether the flag has been enabled/disabled by userspace. The link
- * may be in promiscious mode even if IFF_PROMISC is not set in a link
- * dump request response because promiscity might be needed by the driver
- * for a period of time.
- *
- * @note The unit of the transmission queue length depends on the
- *       link type, a common unit is \a packets.
- *
- * @par 1) Retrieving information about available links
- * @code
- * // The first step is to retrieve a list of all available interfaces within
- * // the kernel and put them into a cache.
- * struct nl_cache *cache = rtnl_link_alloc_cache(sk);
- *
- * // In a second step, a specific link may be looked up by either interface
- * // index or interface name.
- * struct rtnl_link *link = rtnl_link_get_by_name(cache, "lo");
- *
- * // rtnl_link_get_by_name() is the short version for translating the
- * // interface name to an interface index first like this:
- * int ifindex = rtnl_link_name2i(cache, "lo");
- * struct rtnl_link *link = rtnl_link_get(cache, ifindex);
- *
- * // After successful usage, the object must be given back to the cache
- * rtnl_link_put(link);
- * @endcode
- *
- * @par 2) Changing link attributes
- * @code
- * // In order to change any attributes of an existing link, we must allocate
- * // a new link to hold the change requests:
- * struct rtnl_link *request = rtnl_link_alloc();
- *
- * // Now we can go on and specify the attributes we want to change:
- * rtnl_link_set_weight(request, 300);
- * rtnl_link_set_mtu(request, 1360);
- *
- * // We can also shut an interface down administratively
- * rtnl_link_unset_flags(request, rtnl_link_str2flags("up"));
- *
- * // Actually, we should know which link to change, so let's look it up
- * struct rtnl_link *old = rtnl_link_get(cache, "eth0");
- *
- * // Two ways exist to commit this change request, the first one is to
- * // build the required netlink message and send it out in one single
- * // step:
- * rtnl_link_change(sk, old, request);
- *
- * // An alternative way is to build the netlink message and send it
- * // out yourself using nl_send_auto_complete()
- * struct nl_msg *msg = rtnl_link_build_change_request(old, request);
- * nl_send_auto_complete(sk, nlmsg_hdr(msg));
- * nlmsg_free(msg);
- *
- * // Don't forget to give back the link object ;->
- * rtnl_link_put(old);
- * @endcode
- *
- * @par 3) Link Type Specific Attributes
- * @code
- * // Some link types offer additional parameters and statistics specific
- * // to their type. F.e. a VLAN link can be configured like this:
- * //
- * // Allocate a new link and set the info type to "vlan". This is required
- * // to prepare the link to hold vlan specific attributes.
- * struct rtnl_link *request = rtnl_link_alloc();
- * rtnl_link_set_info_type(request, "vlan");
- *
- * // Now vlan specific attributes can be set:
- * rtnl_link_vlan_set_id(request, 10);
- * rtnl_link_vlan_set_ingress_map(request, 2, 8);
- *
- * // Of course the attributes can also be read, check the info type
- * // to make sure you are using the right access functions:
- * char *type = rtnl_link_get_info_type(link);
- * if (!strcmp(type, "vlan"))
- * 	int id = rtnl_link_vlan_get_id(link);
- * @endcode
+ * @details
+ * @route_doc{route_link, Link Documentation}
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/utils.h>
 #include <netlink/object.h>
+#include <netlink/hashtable.h>
+#include <netlink/data.h>
 #include <netlink/route/rtnl.h>
 #include <netlink/route/link.h>
-#include <netlink/route/link/info-api.h>
+#include <netlink-private/route/link/api.h>
 
 /** @cond SKIP */
-#define LINK_ATTR_MTU     0x0001
-#define LINK_ATTR_LINK    0x0002
-#define LINK_ATTR_TXQLEN  0x0004
-#define LINK_ATTR_WEIGHT  0x0008
-#define LINK_ATTR_MASTER  0x0010
-#define LINK_ATTR_QDISC   0x0020
-#define LINK_ATTR_MAP     0x0040
-#define LINK_ATTR_ADDR    0x0080
-#define LINK_ATTR_BRD     0x0100
-#define LINK_ATTR_FLAGS   0x0200
-#define LINK_ATTR_IFNAME  0x0400
-#define LINK_ATTR_IFINDEX 0x0800
-#define LINK_ATTR_FAMILY  0x1000
-#define LINK_ATTR_ARPTYPE 0x2000
-#define LINK_ATTR_STATS   0x4000
-#define LINK_ATTR_CHANGE  0x8000
-#define LINK_ATTR_OPERSTATE 0x10000
-#define LINK_ATTR_LINKMODE  0x20000
-#define LINK_ATTR_LINKINFO  0x40000
+#define LINK_ATTR_MTU		(1 <<  0)
+#define LINK_ATTR_LINK		(1 <<  1)
+#define LINK_ATTR_TXQLEN	(1 <<  2)
+#define LINK_ATTR_WEIGHT	(1 <<  3)
+#define LINK_ATTR_MASTER	(1 <<  4)
+#define LINK_ATTR_QDISC		(1 <<  5)
+#define LINK_ATTR_MAP		(1 <<  6)
+#define LINK_ATTR_ADDR		(1 <<  7)
+#define LINK_ATTR_BRD		(1 <<  8)
+#define LINK_ATTR_FLAGS		(1 <<  9)
+#define LINK_ATTR_IFNAME	(1 << 10)
+#define LINK_ATTR_IFINDEX	(1 << 11)
+#define LINK_ATTR_FAMILY	(1 << 12)
+#define LINK_ATTR_ARPTYPE	(1 << 13)
+#define LINK_ATTR_STATS		(1 << 14)
+#define LINK_ATTR_CHANGE	(1 << 15)
+#define LINK_ATTR_OPERSTATE	(1 << 16)
+#define LINK_ATTR_LINKMODE	(1 << 17)
+#define LINK_ATTR_LINKINFO	(1 << 18)
+#define LINK_ATTR_IFALIAS	(1 << 19)
+#define LINK_ATTR_NUM_VF	(1 << 20)
+#define LINK_ATTR_PROMISCUITY	(1 << 21)
+#define LINK_ATTR_NUM_TX_QUEUES	(1 << 22)
+#define LINK_ATTR_NUM_RX_QUEUES	(1 << 23)
+#define LINK_ATTR_GROUP		(1 << 24)
+#define LINK_ATTR_CARRIER	(1 << 25)
+#define LINK_ATTR_PROTINFO	(1 << 26)
+#define LINK_ATTR_AF_SPEC	(1 << 27)
+#define LINK_ATTR_PHYS_PORT_ID	(1 << 28)
+#define LINK_ATTR_NS_FD		(1 << 29)
+#define LINK_ATTR_NS_PID	(1 << 30)
 
 static struct nl_cache_ops rtnl_link_ops;
 static struct nl_object_ops link_obj_ops;
 /** @endcond */
 
+static struct rtnl_link_af_ops *af_lookup_and_alloc(struct rtnl_link *link,
+						    int family)
+{
+	struct rtnl_link_af_ops *af_ops;
+	void *data;
+
+	af_ops = rtnl_link_af_ops_lookup(family);
+	if (!af_ops)
+		return NULL;
+
+	if (!(data = rtnl_link_af_alloc(link, af_ops))) {
+		rtnl_link_af_ops_put(af_ops);
+		return NULL;
+	}
+
+	return af_ops;
+}
+
+static int af_free(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
+		    void *data, void *arg)
+{
+	if (ops->ao_free)
+		ops->ao_free(link, data);
+
+	rtnl_link_af_ops_put(ops);
+
+	return 0;
+}
+
+static int af_clone(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
+		    void *data, void *arg)
+{
+	struct rtnl_link *dst = arg;
+
+	if (ops->ao_clone &&
+	    !(dst->l_af_data[ops->ao_family] = ops->ao_clone(dst, data)))
+		return -NLE_NOMEM;
+
+	return 0;
+}
+
+static int af_fill(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
+		   void *data, void *arg)
+{
+	struct nl_msg *msg = arg;
+	struct nlattr *af_attr;
+	int err;
+
+	if (!ops->ao_fill_af)
+		return 0;
+
+	if (!(af_attr = nla_nest_start(msg, ops->ao_family)))
+		return -NLE_MSGSIZE;
+
+	if ((err = ops->ao_fill_af(link, arg, data)) < 0)
+		return err;
+
+	nla_nest_end(msg, af_attr);
+
+	return 0;
+}
+
+static int af_dump_line(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
+			 void *data, void *arg)
+{
+	struct nl_dump_params *p = arg;
+
+	if (ops->ao_dump[NL_DUMP_LINE])
+		ops->ao_dump[NL_DUMP_LINE](link, p, data);
+
+	return 0;
+}
+
+static int af_dump_details(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
+			   void *data, void *arg)
+{
+	struct nl_dump_params *p = arg;
+
+	if (ops->ao_dump[NL_DUMP_DETAILS])
+		ops->ao_dump[NL_DUMP_DETAILS](link, p, data);
+
+	return 0;
+}
+
+static int af_dump_stats(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
+			 void *data, void *arg)
+{
+	struct nl_dump_params *p = arg;
+
+	if (ops->ao_dump[NL_DUMP_STATS])
+		ops->ao_dump[NL_DUMP_STATS](link, p, data);
+
+	return 0;
+}
+
+static int do_foreach_af(struct rtnl_link *link,
+			 int (*cb)(struct rtnl_link *,
+			 	   struct rtnl_link_af_ops *, void *, void *),
+			 void *arg)
+{
+	int i, err;
+
+	for (i = 0; i < AF_MAX; i++) {
+		if (link->l_af_data[i]) {
+			struct rtnl_link_af_ops *ops;
+
+			if (!(ops = rtnl_link_af_ops_lookup(i)))
+				BUG();
+
+			err = cb(link, ops, link->l_af_data[i], arg);
+
+			rtnl_link_af_ops_put(ops);
+
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
 static void release_link_info(struct rtnl_link *link)
 {
 	struct rtnl_link_info_ops *io = link->l_info_ops;
 
 	if (io != NULL) {
-		io->io_refcnt--;
-		io->io_free(link);
+		if (io->io_free)
+			io->io_free(link);
+		else {
+			/* Catch missing io_free() implementations */
+			BUG_ON(link->l_info);
+		}
+		rtnl_link_info_ops_put(io);
 		link->l_info_ops = NULL;
 	}
 }
@@ -202,8 +213,18 @@
 		if ((io = link->l_info_ops) != NULL)
 			release_link_info(link);
 
+		/* proto info af reference */
+		rtnl_link_af_ops_put(link->l_af_ops);
+
 		nl_addr_put(link->l_addr);
 		nl_addr_put(link->l_bcast);
+
+		free(link->l_ifalias);
+		free(link->l_info_kind);
+
+		do_foreach_af(link, af_free, NULL);
+
+		nl_data_free(link->l_phys_port_id);
 	}
 }
 
@@ -221,30 +242,57 @@
 		if (!(dst->l_bcast = nl_addr_clone(src->l_bcast)))
 			return -NLE_NOMEM;
 
+	if (src->l_ifalias)
+		if (!(dst->l_ifalias = strdup(src->l_ifalias)))
+			return -NLE_NOMEM;
+
+	if (src->l_info_kind)
+		if (!(dst->l_info_kind = strdup(src->l_info_kind)))
+			return -NLE_NOMEM;
+
 	if (src->l_info_ops && src->l_info_ops->io_clone) {
 		err = src->l_info_ops->io_clone(dst, src);
 		if (err < 0)
 			return err;
 	}
 
+	if ((err = do_foreach_af(src, af_clone, dst)) < 0)
+		return err;
+
+	if (src->l_phys_port_id)
+		if (!(dst->l_phys_port_id = nl_data_clone(src->l_phys_port_id)))
+			return -NLE_NOMEM;
+
 	return 0;
 }
 
-static struct nla_policy link_policy[IFLA_MAX+1] = {
-	[IFLA_IFNAME]	= { .type = NLA_STRING,
-			    .maxlen = IFNAMSIZ },
-	[IFLA_MTU]	= { .type = NLA_U32 },
-	[IFLA_TXQLEN]	= { .type = NLA_U32 },
-	[IFLA_LINK]	= { .type = NLA_U32 },
-	[IFLA_WEIGHT]	= { .type = NLA_U32 },
-	[IFLA_MASTER]	= { .type = NLA_U32 },
-	[IFLA_OPERSTATE]= { .type = NLA_U8 },
-	[IFLA_LINKMODE] = { .type = NLA_U8 },
-	[IFLA_LINKINFO]	= { .type = NLA_NESTED },
-	[IFLA_QDISC]	= { .type = NLA_STRING,
-			    .maxlen = IFQDISCSIZ },
-	[IFLA_STATS]	= { .minlen = sizeof(struct rtnl_link_stats) },
-	[IFLA_MAP]	= { .minlen = sizeof(struct rtnl_link_ifmap) },
+struct nla_policy rtln_link_policy[IFLA_MAX+1] = {
+	[IFLA_IFNAME]		= { .type = NLA_STRING,
+				    .maxlen = IFNAMSIZ },
+	[IFLA_MTU]		= { .type = NLA_U32 },
+	[IFLA_TXQLEN]		= { .type = NLA_U32 },
+	[IFLA_LINK]		= { .type = NLA_U32 },
+	[IFLA_WEIGHT]		= { .type = NLA_U32 },
+	[IFLA_MASTER]		= { .type = NLA_U32 },
+	[IFLA_OPERSTATE]	= { .type = NLA_U8 },
+	[IFLA_LINKMODE] 	= { .type = NLA_U8 },
+	[IFLA_LINKINFO]		= { .type = NLA_NESTED },
+	[IFLA_QDISC]		= { .type = NLA_STRING,
+				    .maxlen = IFQDISCSIZ },
+	[IFLA_STATS]		= { .minlen = sizeof(struct rtnl_link_stats) },
+	[IFLA_STATS64]		= { .minlen = sizeof(struct rtnl_link_stats64)},
+	[IFLA_MAP]		= { .minlen = sizeof(struct rtnl_link_ifmap) },
+	[IFLA_IFALIAS]		= { .type = NLA_STRING, .maxlen = IFALIASZ },
+	[IFLA_NUM_VF]		= { .type = NLA_U32 },
+	[IFLA_AF_SPEC]		= { .type = NLA_NESTED },
+	[IFLA_PROMISCUITY]	= { .type = NLA_U32 },
+	[IFLA_NUM_TX_QUEUES]	= { .type = NLA_U32 },
+	[IFLA_NUM_RX_QUEUES]	= { .type = NLA_U32 },
+	[IFLA_GROUP]		= { .type = NLA_U32 },
+	[IFLA_CARRIER]		= { .type = NLA_U8 },
+	[IFLA_PHYS_PORT_ID]	= { .type = NLA_UNSPEC },
+	[IFLA_NET_NS_PID]	= { .type = NLA_U32 },
+	[IFLA_NET_NS_FD]	= { .type = NLA_U32 },
 };
 
 static struct nla_policy link_info_policy[IFLA_INFO_MAX+1] = {
@@ -253,68 +301,86 @@
 	[IFLA_INFO_XSTATS]	= { .type = NLA_NESTED },
 };
 
-static int link_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
-			   struct nlmsghdr *n, struct nl_parser_param *pp)
+int rtnl_link_info_parse(struct rtnl_link *link, struct nlattr **tb)
 {
-	struct rtnl_link *link;
-	struct ifinfomsg *ifi;
-	struct nlattr *tb[IFLA_MAX+1];
-	int err;
-
-	link = rtnl_link_alloc();
-	if (link == NULL) {
-		err = -NLE_NOMEM;
-		goto errout;
-	}
-		
-	link->ce_msgtype = n->nlmsg_type;
-
-	err = nlmsg_parse(n, sizeof(*ifi), tb, IFLA_MAX, link_policy);
-	if (err < 0)
-		goto errout;
-
-	if (tb[IFLA_IFNAME] == NULL) {
-		err = -NLE_MISSING_ATTR;
-		goto errout;
-	}
+	if (tb[IFLA_IFNAME] == NULL)
+		return -NLE_MISSING_ATTR;
 
 	nla_strlcpy(link->l_name, tb[IFLA_IFNAME], IFNAMSIZ);
 
-	ifi = nlmsg_data(n);
-	link->l_family = ifi->ifi_family;
-	link->l_arptype = ifi->ifi_type;
-	link->l_index = ifi->ifi_index;
-	link->l_flags = ifi->ifi_flags;
-	link->l_change = ifi->ifi_change;
-	link->ce_mask = (LINK_ATTR_IFNAME | LINK_ATTR_FAMILY |
-			  LINK_ATTR_ARPTYPE| LINK_ATTR_IFINDEX |
-			  LINK_ATTR_FLAGS | LINK_ATTR_CHANGE);
 
 	if (tb[IFLA_STATS]) {
 		struct rtnl_link_stats *st = nla_data(tb[IFLA_STATS]);
-		
+
 		link->l_stats[RTNL_LINK_RX_PACKETS]	= st->rx_packets;
-		link->l_stats[RTNL_LINK_RX_BYTES]	= st->rx_bytes;
-		link->l_stats[RTNL_LINK_RX_ERRORS]	= st->rx_errors;
-		link->l_stats[RTNL_LINK_RX_DROPPED]	= st->rx_dropped;
-		link->l_stats[RTNL_LINK_RX_COMPRESSED]	= st->rx_compressed;
-		link->l_stats[RTNL_LINK_RX_FIFO_ERR]	= st->rx_fifo_errors;
 		link->l_stats[RTNL_LINK_TX_PACKETS]	= st->tx_packets;
+		link->l_stats[RTNL_LINK_RX_BYTES]	= st->rx_bytes;
 		link->l_stats[RTNL_LINK_TX_BYTES]	= st->tx_bytes;
+		link->l_stats[RTNL_LINK_RX_ERRORS]	= st->rx_errors;
 		link->l_stats[RTNL_LINK_TX_ERRORS]	= st->tx_errors;
+		link->l_stats[RTNL_LINK_RX_DROPPED]	= st->rx_dropped;
 		link->l_stats[RTNL_LINK_TX_DROPPED]	= st->tx_dropped;
-		link->l_stats[RTNL_LINK_TX_COMPRESSED]	= st->tx_compressed;
-		link->l_stats[RTNL_LINK_TX_FIFO_ERR]	= st->tx_fifo_errors;
+		link->l_stats[RTNL_LINK_MULTICAST]	= st->multicast;
+		link->l_stats[RTNL_LINK_COLLISIONS]	= st->collisions;
+
 		link->l_stats[RTNL_LINK_RX_LEN_ERR]	= st->rx_length_errors;
 		link->l_stats[RTNL_LINK_RX_OVER_ERR]	= st->rx_over_errors;
 		link->l_stats[RTNL_LINK_RX_CRC_ERR]	= st->rx_crc_errors;
 		link->l_stats[RTNL_LINK_RX_FRAME_ERR]	= st->rx_frame_errors;
+		link->l_stats[RTNL_LINK_RX_FIFO_ERR]	= st->rx_fifo_errors;
 		link->l_stats[RTNL_LINK_RX_MISSED_ERR]	= st->rx_missed_errors;
+
 		link->l_stats[RTNL_LINK_TX_ABORT_ERR]	= st->tx_aborted_errors;
 		link->l_stats[RTNL_LINK_TX_CARRIER_ERR]	= st->tx_carrier_errors;
+		link->l_stats[RTNL_LINK_TX_FIFO_ERR]	= st->tx_fifo_errors;
 		link->l_stats[RTNL_LINK_TX_HBEAT_ERR]	= st->tx_heartbeat_errors;
 		link->l_stats[RTNL_LINK_TX_WIN_ERR]	= st->tx_window_errors;
-		link->l_stats[RTNL_LINK_MULTICAST]	= st->multicast;
+
+		link->l_stats[RTNL_LINK_RX_COMPRESSED]	= st->rx_compressed;
+		link->l_stats[RTNL_LINK_TX_COMPRESSED]	= st->tx_compressed;
+
+		link->ce_mask |= LINK_ATTR_STATS;
+	}
+
+	if (tb[IFLA_STATS64]) {
+		/*
+		 * This structure contains 64bit parameters, and per the
+		 * documentation in lib/attr.c, must not be accessed
+		 * directly (because of alignment to 4 instead of 8).
+		 * Therefore, copy the data to the stack and access it from
+		 * there, where it will be aligned to 8.
+		 */
+		struct rtnl_link_stats64 st;
+
+		nla_memcpy(&st, tb[IFLA_STATS64], 
+			   sizeof(struct rtnl_link_stats64));
+		
+		link->l_stats[RTNL_LINK_RX_PACKETS]	= st.rx_packets;
+		link->l_stats[RTNL_LINK_TX_PACKETS]	= st.tx_packets;
+		link->l_stats[RTNL_LINK_RX_BYTES]	= st.rx_bytes;
+		link->l_stats[RTNL_LINK_TX_BYTES]	= st.tx_bytes;
+		link->l_stats[RTNL_LINK_RX_ERRORS]	= st.rx_errors;
+		link->l_stats[RTNL_LINK_TX_ERRORS]	= st.tx_errors;
+		link->l_stats[RTNL_LINK_RX_DROPPED]	= st.rx_dropped;
+		link->l_stats[RTNL_LINK_TX_DROPPED]	= st.tx_dropped;
+		link->l_stats[RTNL_LINK_MULTICAST]	= st.multicast;
+		link->l_stats[RTNL_LINK_COLLISIONS]	= st.collisions;
+
+		link->l_stats[RTNL_LINK_RX_LEN_ERR]	= st.rx_length_errors;
+		link->l_stats[RTNL_LINK_RX_OVER_ERR]	= st.rx_over_errors;
+		link->l_stats[RTNL_LINK_RX_CRC_ERR]	= st.rx_crc_errors;
+		link->l_stats[RTNL_LINK_RX_FRAME_ERR]	= st.rx_frame_errors;
+		link->l_stats[RTNL_LINK_RX_FIFO_ERR]	= st.rx_fifo_errors;
+		link->l_stats[RTNL_LINK_RX_MISSED_ERR]	= st.rx_missed_errors;
+
+		link->l_stats[RTNL_LINK_TX_ABORT_ERR]	= st.tx_aborted_errors;
+		link->l_stats[RTNL_LINK_TX_CARRIER_ERR]	= st.tx_carrier_errors;
+		link->l_stats[RTNL_LINK_TX_FIFO_ERR]	= st.tx_fifo_errors;
+		link->l_stats[RTNL_LINK_TX_HBEAT_ERR]	= st.tx_heartbeat_errors;
+		link->l_stats[RTNL_LINK_TX_WIN_ERR]	= st.tx_window_errors;
+
+		link->l_stats[RTNL_LINK_RX_COMPRESSED]	= st.rx_compressed;
+		link->l_stats[RTNL_LINK_TX_COMPRESSED]	= st.tx_compressed;
 
 		link->ce_mask |= LINK_ATTR_STATS;
 	}
@@ -331,10 +397,8 @@
 
 	if (tb[IFLA_ADDRESS]) {
 		link->l_addr = nl_addr_alloc_attr(tb[IFLA_ADDRESS], AF_UNSPEC);
-		if (link->l_addr == NULL) {
-			err = -NLE_NOMEM;
-			goto errout;
-		}
+		if (link->l_addr == NULL)
+			return -NLE_NOMEM;
 		nl_addr_set_family(link->l_addr,
 				   nl_addr_guess_family(link->l_addr));
 		link->ce_mask |= LINK_ATTR_ADDR;
@@ -343,10 +407,8 @@
 	if (tb[IFLA_BROADCAST]) {
 		link->l_bcast = nl_addr_alloc_attr(tb[IFLA_BROADCAST],
 						   AF_UNSPEC);
-		if (link->l_bcast == NULL) {
-			err = -NLE_NOMEM;
-			goto errout;
-		}
+		if (link->l_bcast == NULL)
+			return -NLE_NOMEM;
 		nl_addr_set_family(link->l_bcast,
 				   nl_addr_guess_family(link->l_bcast));
 		link->ce_mask |= LINK_ATTR_BRD;
@@ -378,6 +440,11 @@
 		link->ce_mask |= LINK_ATTR_MASTER;
 	}
 
+	if (tb[IFLA_CARRIER]) {
+		link->l_carrier = nla_get_u8(tb[IFLA_CARRIER]);
+		link->ce_mask |= LINK_ATTR_CARRIER;
+	}
+
 	if (tb[IFLA_OPERSTATE]) {
 		link->l_operstate = nla_get_u8(tb[IFLA_OPERSTATE]);
 		link->ce_mask |= LINK_ATTR_OPERSTATE;
@@ -388,6 +455,82 @@
 		link->ce_mask |= LINK_ATTR_LINKMODE;
 	}
 
+	if (tb[IFLA_IFALIAS]) {
+		link->l_ifalias = nla_strdup(tb[IFLA_IFALIAS]);
+		if (link->l_ifalias == NULL)
+			return -NLE_NOMEM;
+		link->ce_mask |= LINK_ATTR_IFALIAS;
+	}
+
+	if (tb[IFLA_NET_NS_FD]) {
+		link->l_ns_fd = nla_get_u32(tb[IFLA_NET_NS_FD]);
+		link->ce_mask |= LINK_ATTR_NS_FD;
+	}
+
+	if (tb[IFLA_NET_NS_PID]) {
+		link->l_ns_pid = nla_get_u32(tb[IFLA_NET_NS_PID]);
+		link->ce_mask |= LINK_ATTR_NS_PID;
+	}
+
+	return 0;
+}
+
+static int link_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
+			   struct nlmsghdr *n, struct nl_parser_param *pp)
+{
+	struct rtnl_link *link;
+	struct ifinfomsg *ifi;
+	struct nlattr *tb[IFLA_MAX+1];
+	struct rtnl_link_af_ops *af_ops = NULL;
+	int err, family;
+	struct nla_policy real_link_policy[IFLA_MAX+1];
+
+	memcpy(&real_link_policy, rtln_link_policy, sizeof(rtln_link_policy));
+
+	link = rtnl_link_alloc();
+	if (link == NULL) {
+		err = -NLE_NOMEM;
+		goto errout;
+	}
+
+	link->ce_msgtype = n->nlmsg_type;
+
+	if (!nlmsg_valid_hdr(n, sizeof(*ifi)))
+		return -NLE_MSG_TOOSHORT;
+
+	ifi = nlmsg_data(n);
+	link->l_family = family = ifi->ifi_family;
+	link->l_arptype = ifi->ifi_type;
+	link->l_index = ifi->ifi_index;
+	link->l_flags = ifi->ifi_flags;
+	link->l_change = ifi->ifi_change;
+	link->ce_mask = (LINK_ATTR_IFNAME | LINK_ATTR_FAMILY |
+			  LINK_ATTR_ARPTYPE| LINK_ATTR_IFINDEX |
+			  LINK_ATTR_FLAGS | LINK_ATTR_CHANGE);
+
+	if ((af_ops = af_lookup_and_alloc(link, family))) {
+		if (af_ops->ao_protinfo_policy) {
+			memcpy(&real_link_policy[IFLA_PROTINFO],
+			       af_ops->ao_protinfo_policy,
+			       sizeof(struct nla_policy));
+		}
+
+		link->l_af_ops = af_ops;
+	}
+
+	err = nlmsg_parse(n, sizeof(*ifi), tb, IFLA_MAX, real_link_policy);
+	if (err < 0)
+		return err;
+
+	err = rtnl_link_info_parse(link, tb);
+	if (err < 0)
+		return err;
+
+	if (tb[IFLA_NUM_VF]) {
+		link->l_num_vf = nla_get_u32(tb[IFLA_NUM_VF]);
+		link->ce_mask |= LINK_ATTR_NUM_VF;
+	}
+
 	if (tb[IFLA_LINKINFO]) {
 		struct nlattr *li[IFLA_INFO_MAX+1];
 
@@ -396,42 +539,123 @@
 		if (err < 0)
 			goto errout;
 
-		if (li[IFLA_INFO_KIND] &&
-		    (li[IFLA_INFO_DATA] || li[IFLA_INFO_XSTATS])) {
+		if (li[IFLA_INFO_KIND]) {
 			struct rtnl_link_info_ops *ops;
-			char *kind;
+			char *kind = nla_get_string(li[IFLA_INFO_KIND]);
+			int af;
 
-			kind = nla_get_string(li[IFLA_INFO_KIND]);
+			err = rtnl_link_set_type(link, kind);
+			if (err < 0)
+				goto errout;
+
+			if ((af = nl_str2af(kind)) >= 0 &&
+				!af_ops && (af_ops = af_lookup_and_alloc(link, af))) {
+
+				if (af_ops->ao_protinfo_policy) {
+					tb[IFLA_PROTINFO] = (struct nlattr *)af_ops->ao_protinfo_policy;
+				}
+				link->l_family = family = af;
+				link->l_af_ops = af_ops;
+			}
+
 			ops = rtnl_link_info_ops_lookup(kind);
-			if (ops != NULL) {
-				ops->io_refcnt++;
-				link->l_info_ops = ops;
-				err = ops->io_parse(link, li[IFLA_INFO_DATA],
-						    li[IFLA_INFO_XSTATS]);
-				if (err < 0)
-					goto errout;
-			} else {
-				/* XXX: Warn about unparsed info? */
+			link->l_info_ops = ops;
+
+			if (ops) {
+				if (ops->io_parse &&
+				    (li[IFLA_INFO_DATA] || li[IFLA_INFO_XSTATS])) {
+					err = ops->io_parse(link, li[IFLA_INFO_DATA],
+							    li[IFLA_INFO_XSTATS]);
+					if (err < 0)
+						goto errout;
+				} else {
+					/* XXX: Warn about unparsed info? */
+				}
 			}
 		}
+		link->ce_mask |= LINK_ATTR_LINKINFO;
+	}
+
+	if (tb[IFLA_PROTINFO] && af_ops && af_ops->ao_parse_protinfo) {
+		err = af_ops->ao_parse_protinfo(link, tb[IFLA_PROTINFO],
+						link->l_af_data[link->l_family]);
+		if (err < 0)
+			goto errout;
+		link->ce_mask |= LINK_ATTR_PROTINFO;
+	}
+
+	if (tb[IFLA_AF_SPEC]) {
+		struct nlattr *af_attr;
+		int remaining;
+
+		nla_for_each_nested(af_attr, tb[IFLA_AF_SPEC], remaining) {
+			af_ops = af_lookup_and_alloc(link, nla_type(af_attr));
+			if (af_ops && af_ops->ao_parse_af) {
+				char *af_data = link->l_af_data[nla_type(af_attr)];
+
+				err = af_ops->ao_parse_af(link, af_attr, af_data);
+				if (err < 0)
+					goto errout;
+			}
+
+		}
+		link->ce_mask |= LINK_ATTR_AF_SPEC;
+	}
+
+	if (tb[IFLA_PROMISCUITY]) {
+		link->l_promiscuity = nla_get_u32(tb[IFLA_PROMISCUITY]);
+		link->ce_mask |= LINK_ATTR_PROMISCUITY;
+	}
+
+	if (tb[IFLA_NUM_TX_QUEUES]) {
+		link->l_num_tx_queues = nla_get_u32(tb[IFLA_NUM_TX_QUEUES]);
+		link->ce_mask |= LINK_ATTR_NUM_TX_QUEUES;
+	}
+
+	if (tb[IFLA_NUM_RX_QUEUES]) {
+		link->l_num_rx_queues = nla_get_u32(tb[IFLA_NUM_RX_QUEUES]);
+		link->ce_mask |= LINK_ATTR_NUM_RX_QUEUES;
+	}
+
+	if (tb[IFLA_GROUP]) {
+		link->l_group = nla_get_u32(tb[IFLA_GROUP]);
+		link->ce_mask |= LINK_ATTR_GROUP;
+	}
+
+	if (tb[IFLA_PHYS_PORT_ID]) {
+		link->l_phys_port_id = nl_data_alloc_attr(tb[IFLA_PHYS_PORT_ID]);
+		if (link->l_phys_port_id == NULL) {
+			err = -NLE_NOMEM;
+			goto errout;
+		}
+		link->ce_mask |= LINK_ATTR_PHYS_PORT_ID;
 	}
 
 	err = pp->pp_cb((struct nl_object *) link, pp);
 errout:
+	rtnl_link_af_ops_put(af_ops);
 	rtnl_link_put(link);
 	return err;
 }
 
 static int link_request_update(struct nl_cache *cache, struct nl_sock *sk)
 {
-	return nl_rtgen_request(sk, RTM_GETLINK, AF_UNSPEC, NLM_F_DUMP);
+	int family = cache->c_iarg1;
+
+	return nl_rtgen_request(sk, RTM_GETLINK, family, NLM_F_DUMP);
 }
 
 static void link_dump_line(struct nl_object *obj, struct nl_dump_params *p)
 {
 	char buf[128];
-	struct nl_cache *cache = dp_cache(obj);
+	struct nl_cache *cache = obj->ce_cache;
 	struct rtnl_link *link = (struct rtnl_link *) obj;
+	int fetched_cache = 0;
+
+	if (!cache) {
+		cache = nl_cache_mngt_require_safe("route/link");
+		fetched_cache = 1;
+	}
 
 	nl_dump_line(p, "%s %s ", link->l_name,
 		     nl_llproto2str(link->l_arptype, buf, sizeof(buf)));
@@ -440,10 +664,13 @@
 		nl_dump(p, "%s ", nl_addr2str(link->l_addr, buf, sizeof(buf)));
 
 	if (link->ce_mask & LINK_ATTR_MASTER) {
-		struct rtnl_link *master = rtnl_link_get(cache, link->l_master);
-		nl_dump(p, "master %s ", master ? master->l_name : "inv");
-		if (master)
-			rtnl_link_put(master);
+		if (cache) {
+			struct rtnl_link *master = rtnl_link_get(cache, link->l_master);
+			nl_dump(p, "master %s ", master ? master->l_name : "inv");
+			if (master)
+				rtnl_link_put(master);
+		} else
+			nl_dump(p, "master %d ", link->l_master);
 	}
 
 	rtnl_link_flags2str(link->l_flags, buf, sizeof(buf));
@@ -451,16 +678,27 @@
 		nl_dump(p, "<%s> ", buf);
 
 	if (link->ce_mask & LINK_ATTR_LINK) {
-		struct rtnl_link *ll = rtnl_link_get(cache, link->l_link);
-		nl_dump(p, "slave-of %s ", ll ? ll->l_name : "NONE");
-		if (ll)
-			rtnl_link_put(ll);
+		if (cache) {
+			struct rtnl_link *ll = rtnl_link_get(cache, link->l_link);
+			nl_dump(p, "slave-of %s ", ll ? ll->l_name : "NONE");
+			if (ll)
+				rtnl_link_put(ll);
+		} else
+			nl_dump(p, "slave-of %d ", link->l_link);
 	}
 
+	if (link->ce_mask & LINK_ATTR_GROUP)
+		nl_dump(p, "group %u ", link->l_group);
+
 	if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_LINE])
 		link->l_info_ops->io_dump[NL_DUMP_LINE](link, p);
 
+	do_foreach_af(link, af_dump_line, p);
+
 	nl_dump(p, "\n");
+
+	if (fetched_cache)
+		nl_cache_put(cache);
 }
 
 static void link_dump_details(struct nl_object *obj, struct nl_dump_params *p)
@@ -482,10 +720,22 @@
 	if (link->ce_mask & LINK_ATTR_IFINDEX)
 		nl_dump(p, "index %u ", link->l_index);
 
+	if (link->ce_mask & LINK_ATTR_PROMISCUITY && link->l_promiscuity > 0)
+		nl_dump(p, "promisc-mode (%u users) ", link->l_promiscuity);
 
 	nl_dump(p, "\n");
+
+	if (link->ce_mask & LINK_ATTR_IFALIAS)
+		nl_dump_line(p, "    alias %s\n", link->l_ifalias);
+
 	nl_dump_line(p, "    ");
 
+	if (link->ce_mask & LINK_ATTR_NUM_TX_QUEUES)
+		nl_dump(p, "txq %u ", link->l_num_tx_queues);
+
+	if (link->ce_mask & LINK_ATTR_NUM_RX_QUEUES)
+		nl_dump(p, "rxq %u ", link->l_num_rx_queues);
+
 	if (link->ce_mask & LINK_ATTR_BRD)
 		nl_dump(p, "brd %s ", nl_addr2str(link->l_bcast, buf,
 						   sizeof(buf)));
@@ -496,11 +746,21 @@
 		nl_dump(p, "state %s ", buf);
 	}
 
-	nl_dump(p, "mode %s\n",
+	if (link->ce_mask & LINK_ATTR_NUM_VF)
+		nl_dump(p, "num-vf %u ", link->l_num_vf);
+
+	nl_dump(p, "mode %s ",
 		rtnl_link_mode2str(link->l_linkmode, buf, sizeof(buf)));
 
+	nl_dump(p, "carrier %s",
+		rtnl_link_carrier2str(link->l_carrier, buf, sizeof(buf)));
+
+	nl_dump(p, "\n");
+
 	if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_DETAILS])
 		link->l_info_ops->io_dump[NL_DUMP_DETAILS](link, p);
+
+	do_foreach_af(link, af_dump_details, p);
 }
 
 static void link_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
@@ -516,7 +776,7 @@
 
 	res = nl_cancel_down_bytes(link->l_stats[RTNL_LINK_RX_BYTES], &unit);
 
-	strcpy(fmt, "     RX %X.2f %s %10llu %10llu %10llu %10llu %10llu\n");
+	strcpy(fmt, "     RX %X.2f %s %10" PRIu64 " %10" PRIu64 " %10" PRIu64 " %10" PRIu64 " %10" PRIu64 "\n");
 	fmt[9] = *unit == 'B' ? '9' : '7';
 	
 	nl_dump_line(p, fmt, res, unit,
@@ -528,7 +788,7 @@
 
 	res = nl_cancel_down_bytes(link->l_stats[RTNL_LINK_TX_BYTES], &unit);
 
-	strcpy(fmt, "     TX %X.2f %s %10llu %10llu %10llu %10llu %10llu\n");
+	strcpy(fmt, "     TX %X.2f %s %10" PRIu64 " %10" PRIu64 " %10" PRIu64 " %10" PRIu64 " %10" PRIu64 "\n");
 	fmt[9] = *unit == 'B' ? '9' : '7';
 	
 	nl_dump_line(p, fmt, res, unit,
@@ -560,77 +820,12 @@
 		link->l_stats[RTNL_LINK_TX_CARRIER_ERR],
 		link->l_stats[RTNL_LINK_TX_HBEAT_ERR],
 		link->l_stats[RTNL_LINK_TX_WIN_ERR],
-		link->l_stats[RTNL_LINK_TX_COLLISIONS]);
+		link->l_stats[RTNL_LINK_COLLISIONS]);
 
 	if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_STATS])
 		link->l_info_ops->io_dump[NL_DUMP_STATS](link, p);
-}
 
-static void link_dump_env(struct nl_object *obj, struct nl_dump_params *p)
-{
-	struct rtnl_link *link = (struct rtnl_link *) obj;
-	struct nl_cache *cache = dp_cache(obj);
-	char buf[128];
-	int i;
-
-	nl_dump_line(p, "LINK_NAME=%s\n", link->l_name);
-	nl_dump_line(p, "LINK_IFINDEX=%u\n", link->l_index);
-	nl_dump_line(p, "LINK_FAMILY=%s\n",
-		     nl_af2str(link->l_family, buf, sizeof(buf)));
-	nl_dump_line(p, "LINK_TYPE=%s\n",
-		     nl_llproto2str(link->l_arptype, buf, sizeof(buf)));
-	if (link->ce_mask & LINK_ATTR_ADDR)
-		nl_dump_line(p, "LINK_ADDRESS=%s\n",
-			     nl_addr2str(link->l_addr, buf, sizeof(buf)));
-	nl_dump_line(p, "LINK_MTU=%u\n", link->l_mtu);
-	nl_dump_line(p, "LINK_TXQUEUELEN=%u\n", link->l_txqlen);
-	nl_dump_line(p, "LINK_WEIGHT=%u\n", link->l_weight);
-
-	rtnl_link_flags2str(link->l_flags & ~IFF_RUNNING, buf, sizeof(buf));
-	if (buf[0])
-		nl_dump_line(p, "LINK_FLAGS=%s\n", buf);
-
-	if (link->ce_mask & LINK_ATTR_QDISC)
-		nl_dump_line(p, "LINK_QDISC=%s\n", link->l_qdisc);
-
-	if (link->ce_mask & LINK_ATTR_LINK) {
-		struct rtnl_link *ll = rtnl_link_get(cache, link->l_link);
-
-		nl_dump_line(p, "LINK_LINK_IFINDEX=%d\n", link->l_link);
-		if (ll) {
-			nl_dump_line(p, "LINK_LINK_IFNAME=%s\n", ll->l_name);
-			rtnl_link_put(ll);
-		}
-	}
-
-	if (link->ce_mask & LINK_ATTR_MASTER) {
-		struct rtnl_link *master = rtnl_link_get(cache, link->l_master);
-		nl_dump_line(p, "LINK_MASTER=%s\n",
-			     master ? master->l_name : "none");
-		if (master)
-			rtnl_link_put(master);
-	}
-
-	if (link->ce_mask & LINK_ATTR_BRD)
-		nl_dump_line(p, "LINK_BROADCAST=%s\n",
-			     nl_addr2str(link->l_bcast, buf, sizeof(buf)));
-
-	if (link->ce_mask & LINK_ATTR_STATS) {
-		for (i = 0; i <= RTNL_LINK_STATS_MAX; i++) {
-			char *c = buf;
-
-			sprintf(buf, "LINK_");
-			rtnl_link_stat2str(i, buf + 5, sizeof(buf) - 5);
-			while (*c) {
-				*c = toupper(*c);
-				c++;
-			}
-			nl_dump_line(p, "%s=%" PRIu64 "\n", buf, link->l_stats[i]);
-		}
-	}
-
-	if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_ENV])
-		link->l_info_ops->io_dump[NL_DUMP_ENV](link, p);
+	do_foreach_af(link, af_dump_stats, p);
 }
 
 #if 0
@@ -679,6 +874,29 @@
 }
 #endif
 
+
+static void link_keygen(struct nl_object *obj, uint32_t *hashkey,
+        uint32_t table_sz)
+{
+	struct rtnl_link *link = (struct rtnl_link *) obj;
+	unsigned int lkey_sz;
+	struct link_hash_key {
+		uint32_t	l_index;
+		uint32_t	l_family;
+	} __attribute__((packed)) lkey;
+
+	lkey_sz = sizeof(lkey);
+	lkey.l_index = link->l_index;
+	lkey.l_family = link->l_family;
+
+	*hashkey = nl_hash(&lkey, lkey_sz, 0) % table_sz;
+
+	NL_DBG(5, "link %p key (dev %d fam %d) keysz %d, hash 0x%x\n",
+	       link, lkey.l_index, lkey.l_family, lkey_sz, *hashkey);
+
+	return;
+}
+
 static int link_compare(struct nl_object *_a, struct nl_object *_b,
 			uint32_t attrs, int flags)
 {
@@ -701,6 +919,12 @@
 	diff |= LINK_DIFF(IFNAME,	strcmp(a->l_name, b->l_name));
 	diff |= LINK_DIFF(ADDR,		nl_addr_cmp(a->l_addr, b->l_addr));
 	diff |= LINK_DIFF(BRD,		nl_addr_cmp(a->l_bcast, b->l_bcast));
+	diff |= LINK_DIFF(IFALIAS,	strcmp(a->l_ifalias, b->l_ifalias));
+	diff |= LINK_DIFF(NUM_VF,	a->l_num_vf != b->l_num_vf);
+	diff |= LINK_DIFF(PROMISCUITY,	a->l_promiscuity != b->l_promiscuity);
+	diff |= LINK_DIFF(NUM_TX_QUEUES,a->l_num_tx_queues != b->l_num_tx_queues);
+	diff |= LINK_DIFF(NUM_RX_QUEUES,a->l_num_rx_queues != b->l_num_rx_queues);
+	diff |= LINK_DIFF(GROUP,	a->l_group != b->l_group);
 
 	if (flags & LOOSE_COMPARISON)
 		diff |= LINK_DIFF(FLAGS,
@@ -708,12 +932,25 @@
 	else
 		diff |= LINK_DIFF(FLAGS, a->l_flags != b->l_flags);
 
-#undef LINK_DIFF
+	/*
+	 * Compare LINK_ATTR_PROTINFO af_data
+	 */
+	if (a->l_family == b->l_family) {
+		if (rtnl_link_af_data_compare(a, b, a->l_family) != 0)
+			goto protinfo_mismatch;
+	}
 
+out:
 	return diff;
+
+protinfo_mismatch:
+	diff |= LINK_DIFF(PROTINFO, 1);
+	goto out;
+
+#undef LINK_DIFF
 }
 
-static struct trans_tbl link_attrs[] = {
+static const struct trans_tbl link_attrs[] = {
 	__ADD(LINK_ATTR_MTU, mtu)
 	__ADD(LINK_ATTR_LINK, link)
 	__ADD(LINK_ATTR_TXQLEN, txqlen)
@@ -732,6 +969,14 @@
 	__ADD(LINK_ATTR_CHANGE, change)
 	__ADD(LINK_ATTR_OPERSTATE, operstate)
 	__ADD(LINK_ATTR_LINKMODE, linkmode)
+	__ADD(LINK_ATTR_IFALIAS, ifalias)
+	__ADD(LINK_ATTR_NUM_VF, num_vf)
+	__ADD(LINK_ATTR_PROMISCUITY, promiscuity)
+	__ADD(LINK_ATTR_NUM_TX_QUEUES, num_tx_queues)
+	__ADD(LINK_ATTR_NUM_RX_QUEUES, num_rx_queues)
+	__ADD(LINK_ATTR_GROUP, group)
+	__ADD(LINK_ATTR_CARRIER, carrier)
+	__ADD(LINK_ATTR_PHYS_PORT_ID, phys_port_id)
 };
 
 static char *link_attrs2str(int attrs, char *buf, size_t len)
@@ -741,24 +986,7 @@
 }
 
 /**
- * @name Allocation/Freeing
- * @{
- */
-
-struct rtnl_link *rtnl_link_alloc(void)
-{
-	return (struct rtnl_link *) nl_object_alloc(&link_obj_ops);
-}
-
-void rtnl_link_put(struct rtnl_link *link)
-{
-	nl_object_put((struct nl_object *) link);
-}
-
-/** @} */
-
-/**
- * @name Cache Management
+ * @name Get / List
  * @{
  */
 
@@ -766,27 +994,61 @@
 /**
  * Allocate link cache and fill in all configured links.
  * @arg sk		Netlink socket.
+ * @arg family		Link address family or AF_UNSPEC
  * @arg result		Pointer to store resulting cache.
  *
- * Allocates a new link cache, initializes it properly and updates it
- * to include all links currently configured in the kernel.
+ * Allocates and initializes a new link cache. If \c sk is valid, a netlink
+ * message is sent to the kernel requesting a full dump of all configured
+ * links. The returned messages are parsed and filled into the cache. If
+ * the operation succeeds, the resulting cache will contain a link object for
+ * each link configured in the kernel. If \c sk is NULL, returns 0 but the
+ * cache is still empty.
  *
+ * If \c family is set to an address family other than \c AF_UNSPEC the
+ * contents of the cache can be limited to a specific address family.
+ * Currently the following address families are supported:
+ * - AF_BRIDGE
+ * - AF_INET6
+ *
+ * @route_doc{link_list, Get List of Links}
+ * @see rtnl_link_get()
+ * @see rtnl_link_get_by_name()
  * @return 0 on success or a negative error code.
  */
-int rtnl_link_alloc_cache(struct nl_sock *sk, struct nl_cache **result)
+int rtnl_link_alloc_cache(struct nl_sock *sk, int family, struct nl_cache **result)
 {
-	return nl_cache_alloc_and_fill(&rtnl_link_ops, sk, result);
+	struct nl_cache * cache;
+	int err;
+	
+	cache = nl_cache_alloc(&rtnl_link_ops);
+	if (!cache)
+		return -NLE_NOMEM;
+
+	cache->c_iarg1 = family;
+	
+	if (sk && (err = nl_cache_refill(sk, cache)) < 0) {
+		nl_cache_free(cache);
+		return err;
+	}
+
+	*result = cache;
+	return 0;
 }
 
 /**
- * Look up link by interface index in the provided cache
- * @arg cache		link cache
- * @arg ifindex		link interface index
+ * Lookup link in cache by interface index
+ * @arg cache		Link cache
+ * @arg ifindex		Interface index
  *
- * The caller owns a reference on the returned object and
- * must give the object back via rtnl_link_put().
+ * Searches through the provided cache looking for a link with matching
+ * interface index.
  *
- * @return pointer to link inside the cache or NULL if no match was found.
+ * @attention The reference counter of the returned link object will be
+ *            incremented. Use rtnl_link_put() to release the reference.
+ *
+ * @route_doc{link_list, Get List of Links}
+ * @see rtnl_link_get_by_name()
+ * @return Link object or NULL if no match was found.
  */
 struct rtnl_link *rtnl_link_get(struct nl_cache *cache, int ifindex)
 {
@@ -806,14 +1068,19 @@
 }
 
 /**
- * Look up link by link name in the provided cache
- * @arg cache		link cache
- * @arg name		link name
+ * Lookup link in cache by link name
+ * @arg cache		Link cache
+ * @arg name		Name of link
  *
- * The caller owns a reference on the returned object and
- * must give the object back via rtnl_link_put().
+ * Searches through the provided cache looking for a link with matching
+ * link name
  *
- * @return pointer to link inside the cache or NULL if no match was found.
+ * @attention The reference counter of the returned link object will be
+ *            incremented. Use rtnl_link_put() to release the reference.
+ *
+ * @route_doc{link_list, Get List of Links}
+ * @see rtnl_link_get()
+ * @return Link object or NULL if no match was found.
  */
 struct rtnl_link *rtnl_link_get_by_name(struct nl_cache *cache,
 					 const char *name)
@@ -833,91 +1100,44 @@
 	return NULL;
 }
 
-/** @} */
-
 /**
- * @name Link Modifications
- * @{
- */
-
-/**
- * Builds a netlink change request message to change link attributes
- * @arg old		link to be changed
- * @arg tmpl		template with requested changes
- * @arg flags		additional netlink message flags
+ * Construct RTM_GETLINK netlink message
+ * @arg ifindex		Interface index
+ * @arg name		Name of link
+ * @arg result		Pointer to store resulting netlink message
  *
- * Builds a new netlink message requesting a change of link attributes.
- * The netlink message header isn't fully equipped with all relevant
- * fields and must be sent out via nl_send_auto_complete() or
- * supplemented as needed.
- * \a old must point to a link currently configured in the kernel
- * and \a tmpl must contain the attributes to be changed set via
- * \c rtnl_link_set_* functions.
+ * The behaviour of this function is identical to rtnl_link_get_kernel()
+ * with the exception that it will not send the message but return it in
+ * the provided return pointer instead.
  *
- * @return New netlink message
- * @note Not all attributes can be changed, see
- *       \ref link_changeable "Changeable Attributes" for more details.
+ * @see rtnl_link_get_kernel()
+ *
+ * @return 0 on success or a negative error code.
  */
-int rtnl_link_build_change_request(struct rtnl_link *old,
-				   struct rtnl_link *tmpl, int flags,
-				   struct nl_msg **result)
+int rtnl_link_build_get_request(int ifindex, const char *name,
+				struct nl_msg **result)
 {
+	struct ifinfomsg ifi;
 	struct nl_msg *msg;
-	struct ifinfomsg ifi = {
-		.ifi_family = old->l_family,
-		.ifi_index = old->l_index,
-	};
 
-	if (tmpl->ce_mask & LINK_ATTR_FLAGS) {
-		ifi.ifi_flags = old->l_flags & ~tmpl->l_flag_mask;
-		ifi.ifi_flags |= tmpl->l_flags;
+	if (ifindex <= 0 && !name) {
+		APPBUG("ifindex or name must be specified");
+		return -NLE_MISSING_ATTR;
 	}
 
-	msg = nlmsg_alloc_simple(RTM_SETLINK, flags);
-	if (!msg)
+	memset(&ifi, 0, sizeof(ifi));
+
+	if (!(msg = nlmsg_alloc_simple(RTM_GETLINK, 0)))
 		return -NLE_NOMEM;
 
+	if (ifindex > 0)
+		ifi.ifi_index = ifindex;
+
 	if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
 
-	if (tmpl->ce_mask & LINK_ATTR_ADDR)
-		NLA_PUT_ADDR(msg, IFLA_ADDRESS, tmpl->l_addr);
-
-	if (tmpl->ce_mask & LINK_ATTR_BRD)
-		NLA_PUT_ADDR(msg, IFLA_BROADCAST, tmpl->l_bcast);
-
-	if (tmpl->ce_mask & LINK_ATTR_MTU)
-		NLA_PUT_U32(msg, IFLA_MTU, tmpl->l_mtu);
-
-	if (tmpl->ce_mask & LINK_ATTR_TXQLEN)
-		NLA_PUT_U32(msg, IFLA_TXQLEN, tmpl->l_txqlen);
-
-	if (tmpl->ce_mask & LINK_ATTR_WEIGHT)
-		NLA_PUT_U32(msg, IFLA_WEIGHT, tmpl->l_weight);
-
-	if (tmpl->ce_mask & LINK_ATTR_IFNAME)
-		NLA_PUT_STRING(msg, IFLA_IFNAME, tmpl->l_name);
-
-	if (tmpl->ce_mask & LINK_ATTR_OPERSTATE)
-		NLA_PUT_U8(msg, IFLA_OPERSTATE, tmpl->l_operstate);
-
-	if (tmpl->ce_mask & LINK_ATTR_LINKMODE)
-		NLA_PUT_U8(msg, IFLA_LINKMODE, tmpl->l_linkmode);
-
-	if ((tmpl->ce_mask & LINK_ATTR_LINKINFO) && tmpl->l_info_ops &&
-	    tmpl->l_info_ops->io_put_attrs) {
-		struct nlattr *info;
-
-		if (!(info = nla_nest_start(msg, IFLA_LINKINFO)))
-			goto nla_put_failure;
-
-		NLA_PUT_STRING(msg, IFLA_INFO_KIND, tmpl->l_info_ops->io_name);
-
-		if (tmpl->l_info_ops->io_put_attrs(msg, tmpl) < 0)
-			goto nla_put_failure;
-
-		nla_nest_end(msg, info);
-	}
+	if (name)
+		NLA_PUT_STRING(msg, IFLA_IFNAME, name);
 
 	*result = msg;
 	return 0;
@@ -928,55 +1148,62 @@
 }
 
 /**
- * Change link attributes
- * @arg sk		Netlink socket.
- * @arg old		link to be changed
- * @arg tmpl		template with requested changes
- * @arg flags		additional netlink message flags
+ * Get a link object directly from kernel
+ * @arg sk		Netlink socket
+ * @arg ifindex		Interface index
+ * @arg name		Name of link
+ * @arg result		Pointer to store resulting link object
  *
- * Builds a new netlink message by calling rtnl_link_build_change_request(),
- * sends the request to the kernel and waits for the next ACK to be
- * received, i.e. blocks until the request has been processed.
+ * This function builds a \c RTM_GETLINK netlink message to request
+ * a specific link directly from the kernel. The returned answer is
+ * parsed into a struct rtnl_link object and returned via the result
+ * pointer or -NLE_OBJ_NOTFOUND is returned if no matching link was
+ * found.
  *
- * @return 0 on success or a negative error code
- * @note Not all attributes can be changed, see
- *       \ref link_changeable "Changeable Attributes" for more details.
+ * @route_doc{link_direct_lookup, Lookup Single Link (Direct Lookup)}
+ * @return 0 on success or a negative error code.
  */
-int rtnl_link_change(struct nl_sock *sk, struct rtnl_link *old,
-		     struct rtnl_link *tmpl, int flags)
+int rtnl_link_get_kernel(struct nl_sock *sk, int ifindex, const char *name,
+			 struct rtnl_link **result)
 {
-	struct nl_msg *msg;
+	struct nl_msg *msg = NULL;
+	struct nl_object *obj;
 	int err;
-	
-	if ((err = rtnl_link_build_change_request(old, tmpl, flags, &msg)) < 0)
+
+	if ((err = rtnl_link_build_get_request(ifindex, name, &msg)) < 0)
 		return err;
-	
-	err = nl_send_auto_complete(sk, msg);
+
+	err = nl_send_auto(sk, msg);
 	nlmsg_free(msg);
 	if (err < 0)
 		return err;
 
-	return wait_for_ack(sk);
+	if ((err = nl_pickup(sk, link_msg_parser, &obj)) < 0)
+		return err;
+
+	/* We have used link_msg_parser(), object is definitely a link */
+	*result = (struct rtnl_link *) obj;
+
+	/* If an object has been returned, we also need to wait for the ACK */
+	 if (err == 0 && obj)
+		 wait_for_ack(sk);
+
+	return 0;
 }
 
-/** @} */
-
 /**
- * @name Name <-> Index Translations
- * @{
- */
-
-/**
- * Translate an interface index to the corresponding link name
- * @arg cache		link cache
- * @arg ifindex		link interface index
- * @arg dst		destination buffer
- * @arg len		length of destination buffer
+ * Translate interface index to corresponding link name
+ * @arg cache		Link cache
+ * @arg ifindex		Interface index
+ * @arg dst		String to store name
+ * @arg len		Length of destination string
  *
  * Translates the specified interface index to the corresponding
- * link name and stores the name in the destination buffer.
+ * link name and stores the name in the destination string.
  *
- * @return link name or NULL if no match was found.
+ * @route_doc{link_translate_ifindex, Translating interface index to link name}
+ * @see rtnl_link_name2i()
+ * @return Name of link or NULL if no match was found.
  */
 char * rtnl_link_i2name(struct nl_cache *cache, int ifindex, char *dst,
 			size_t len)
@@ -993,11 +1220,13 @@
 }
 
 /**
- * Translate a link name to the corresponding interface index
- * @arg cache		link cache
- * @arg name		link name
+ * Translate link name to corresponding interface index
+ * @arg cache		Link cache
+ * @arg name		Name of link
  *
- * @return interface index or 0 if no match was found.
+ * @route_doc{link_translate_ifindex, Translating interface index to link name}
+ * @see rtnl_link_i2name()
+ * @return Interface index or 0 if no match was found.
  */
 int rtnl_link_name2i(struct nl_cache *cache, const char *name)
 {
@@ -1015,12 +1244,1273 @@
 
 /** @} */
 
+int rtnl_link_fill_info(struct nl_msg *msg, struct rtnl_link *link)
+{
+	if (link->ce_mask & LINK_ATTR_ADDR)
+		NLA_PUT_ADDR(msg, IFLA_ADDRESS, link->l_addr);
+
+	if (link->ce_mask & LINK_ATTR_BRD)
+		NLA_PUT_ADDR(msg, IFLA_BROADCAST, link->l_bcast);
+
+	if (link->ce_mask & LINK_ATTR_MTU)
+		NLA_PUT_U32(msg, IFLA_MTU, link->l_mtu);
+
+	if (link->ce_mask & LINK_ATTR_TXQLEN)
+		NLA_PUT_U32(msg, IFLA_TXQLEN, link->l_txqlen);
+
+	if (link->ce_mask & LINK_ATTR_WEIGHT)
+		NLA_PUT_U32(msg, IFLA_WEIGHT, link->l_weight);
+
+	if (link->ce_mask & LINK_ATTR_IFNAME)
+		NLA_PUT_STRING(msg, IFLA_IFNAME, link->l_name);
+
+	if (link->ce_mask & LINK_ATTR_OPERSTATE)
+		NLA_PUT_U8(msg, IFLA_OPERSTATE, link->l_operstate);
+
+	if (link->ce_mask & LINK_ATTR_CARRIER)
+		NLA_PUT_U8(msg, IFLA_CARRIER, link->l_carrier);
+
+	if (link->ce_mask & LINK_ATTR_LINKMODE)
+		NLA_PUT_U8(msg, IFLA_LINKMODE, link->l_linkmode);
+
+	if (link->ce_mask & LINK_ATTR_IFALIAS)
+		NLA_PUT_STRING(msg, IFLA_IFALIAS, link->l_ifalias);
+
+	if (link->ce_mask & LINK_ATTR_LINK)
+		NLA_PUT_U32(msg, IFLA_LINK, link->l_link);
+
+	if (link->ce_mask & LINK_ATTR_MASTER)
+		NLA_PUT_U32(msg, IFLA_MASTER, link->l_master);
+
+	if (link->ce_mask & LINK_ATTR_NUM_TX_QUEUES)
+		NLA_PUT_U32(msg, IFLA_NUM_TX_QUEUES, link->l_num_tx_queues);
+
+	if (link->ce_mask & LINK_ATTR_NUM_RX_QUEUES)
+		NLA_PUT_U32(msg, IFLA_NUM_RX_QUEUES, link->l_num_rx_queues);
+
+	if (link->ce_mask & LINK_ATTR_NS_FD)
+		NLA_PUT_U32(msg, IFLA_NET_NS_FD, link->l_ns_fd);
+
+	if (link->ce_mask & LINK_ATTR_NS_PID)
+		NLA_PUT_U32(msg, IFLA_NET_NS_PID, link->l_ns_pid);
+
+	return 0;
+
+nla_put_failure:
+	return -NLE_MSGSIZE;
+}
+
+static int build_link_msg(int cmd, struct ifinfomsg *hdr,
+			  struct rtnl_link *link, int flags, struct nl_msg **result)
+{
+	struct nl_msg *msg;
+	struct nlattr *af_spec;
+
+	msg = nlmsg_alloc_simple(cmd, flags);
+	if (!msg)
+		return -NLE_NOMEM;
+
+	if (nlmsg_append(msg, hdr, sizeof(*hdr), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	if (rtnl_link_fill_info(msg, link))
+		goto nla_put_failure;
+
+	if (link->ce_mask & LINK_ATTR_GROUP)
+		NLA_PUT_U32(msg, IFLA_GROUP, link->l_group);
+
+	if (link->ce_mask & LINK_ATTR_LINKINFO) {
+		struct nlattr *info;
+
+		if (!(info = nla_nest_start(msg, IFLA_LINKINFO)))
+			goto nla_put_failure;
+
+		NLA_PUT_STRING(msg, IFLA_INFO_KIND, link->l_info_kind);
+
+		if (link->l_info_ops) {
+			if (link->l_info_ops->io_put_attrs &&
+			    link->l_info_ops->io_put_attrs(msg, link) < 0)
+				goto nla_put_failure;
+		}
+
+		nla_nest_end(msg, info);
+	}
+
+	if (!(af_spec = nla_nest_start(msg, IFLA_AF_SPEC)))
+		goto nla_put_failure;
+
+	if (do_foreach_af(link, af_fill, msg) < 0)
+		goto nla_put_failure;
+
+	nla_nest_end(msg, af_spec);
+
+	*result = msg;
+	return 0;
+
+nla_put_failure:
+	nlmsg_free(msg);
+	return -NLE_MSGSIZE;
+}
+
 /**
- * @name Link Flags Translations
+ * @name Add / Modify
  * @{
  */
 
-static struct trans_tbl link_flags[] = {
+/**
+ * Build a netlink message requesting the addition of new virtual link
+ * @arg link		new link to add
+ * @arg flags		additional netlink message flags
+ * @arg result		pointer to store resulting netlink message
+ *
+ * The behaviour of this function is identical to rtnl_link_add() with
+ * the exception that it will not send the message but return it in the
+ * provided return pointer instead.
+ *
+ * @see rtnl_link_add()
+ *
+ * @note This operation is not supported on all kernel versions.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_build_add_request(struct rtnl_link *link, int flags,
+				struct nl_msg **result)
+{
+	struct ifinfomsg ifi = {
+		.ifi_family = link->l_family,
+		.ifi_index = link->l_index,
+		.ifi_flags = link->l_flags,
+	};
+
+	return build_link_msg(RTM_NEWLINK, &ifi, link, flags, result);
+}
+
+/**
+ * Add virtual link
+ * @arg sk		netlink socket.
+ * @arg link		new link to add
+ * @arg flags		additional netlink message flags
+ *
+ * Builds a \c RTM_NEWLINK netlink message requesting the addition of
+ * a new virtual link.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @copydoc auto_ack_warning
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_add(struct nl_sock *sk, struct rtnl_link *link, int flags)
+{
+	struct nl_msg *msg;
+	int err;
+	
+	err = rtnl_link_build_add_request(link, flags, &msg);
+	if (err < 0)
+		return err;
+
+	return nl_send_sync(sk, msg);
+}
+
+/**
+ * Build a netlink message requesting the modification of link
+ * @arg orig		original link to change
+ * @arg changes		link containing the changes to be made
+ * @arg flags		additional netlink message flags
+ * @arg result		pointer to store resulting netlink message
+ *
+ * The behaviour of this function is identical to rtnl_link_change() with
+ * the exception that it will not send the message but return it in the
+ * provided return pointer instead.
+ *
+ * @see rtnl_link_change()
+ *
+ * @note The resulting message will have message type set to RTM_NEWLINK
+ *       which may not work with older kernels. You may have to modify it
+ *       to RTM_SETLINK (does not allow changing link info attributes) to
+ *       have the change request work with older kernels.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_build_change_request(struct rtnl_link *orig,
+				   struct rtnl_link *changes, int flags,
+				   struct nl_msg **result)
+{
+	struct ifinfomsg ifi = {
+		.ifi_family = orig->l_family,
+		.ifi_index = orig->l_index,
+	};
+	int err;
+
+	if (changes->ce_mask & LINK_ATTR_FLAGS) {
+		ifi.ifi_flags = orig->l_flags & ~changes->l_flag_mask;
+		ifi.ifi_flags |= changes->l_flags;
+	}
+
+	if (changes->l_family && changes->l_family != orig->l_family) {
+		APPBUG("link change: family is immutable");
+		return -NLE_IMMUTABLE;
+	}
+
+	/* Avoid unnecessary name change requests */
+	if (orig->ce_mask & LINK_ATTR_IFINDEX &&
+	    orig->ce_mask & LINK_ATTR_IFNAME &&
+	    changes->ce_mask & LINK_ATTR_IFNAME &&
+	    !strcmp(orig->l_name, changes->l_name))
+		changes->ce_mask &= ~LINK_ATTR_IFNAME;
+
+	if ((err = build_link_msg(RTM_NEWLINK, &ifi, changes, flags, result)) < 0)
+		goto errout;
+
+	return 0;
+
+errout:
+	return err;
+}
+
+/**
+ * Change link
+ * @arg sk		netlink socket.
+ * @arg orig		original link to be changed
+ * @arg changes		link containing the changes to be made
+ * @arg flags		additional netlink message flags
+ *
+ * Builds a \c RTM_NEWLINK netlink message requesting the change of
+ * a network link. If -EOPNOTSUPP is returned by the kernel, the
+ * message type will be changed to \c RTM_SETLINK and the message is
+ * resent to work around older kernel versions.
+ *
+ * The link to be changed is looked up based on the interface index
+ * supplied in the \p orig link. Optionaly the link name is used but
+ * only if no interface index is provided, otherwise providing an
+ * link name will result in the link name being changed.
+ *
+ * If no matching link exists, the function will return
+ * -NLE_OBJ_NOTFOUND.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @copydoc auto_ack_warning
+ *
+ * @note The link name can only be changed if the link has been put
+ *       in opertional down state. (~IF_UP)
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_change(struct nl_sock *sk, struct rtnl_link *orig,
+		     struct rtnl_link *changes, int flags)
+{
+	struct nl_msg *msg;
+	int err;
+	
+	err = rtnl_link_build_change_request(orig, changes, flags, &msg);
+	if (err < 0)
+		return err;
+
+retry:
+	err = nl_send_auto_complete(sk, msg);
+	if (err < 0)
+		goto errout;
+
+	err = wait_for_ack(sk);
+	if (err == -NLE_OPNOTSUPP && msg->nm_nlh->nlmsg_type == RTM_NEWLINK) {
+		msg->nm_nlh->nlmsg_type = RTM_SETLINK;
+		goto retry;
+	}
+
+errout:
+	nlmsg_free(msg);
+	return err;
+}
+
+/** @} */
+
+/**
+ * @name Delete
+ * @{
+ */
+
+/**
+ * Build a netlink message requesting the deletion of a link
+ * @arg link		Link to delete
+ * @arg result		Pointer to store resulting netlink message
+ *
+ * The behaviour of this function is identical to rtnl_link_delete() with
+ * the exception that it will not send the message but return it in the
+ * provided return pointer instead.
+ *
+ * @see rtnl_link_delete()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_build_delete_request(const struct rtnl_link *link,
+				   struct nl_msg **result)
+{
+	struct nl_msg *msg;
+	struct ifinfomsg ifi = {
+		.ifi_index = link->l_index,
+	};
+
+	if (!(link->ce_mask & (LINK_ATTR_IFINDEX | LINK_ATTR_IFNAME))) {
+		APPBUG("ifindex or name must be specified");
+		return -NLE_MISSING_ATTR;
+	}
+
+	if (!(msg = nlmsg_alloc_simple(RTM_DELLINK, 0)))
+		return -NLE_NOMEM;
+
+	if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	if (link->ce_mask & LINK_ATTR_IFNAME)
+		NLA_PUT_STRING(msg, IFLA_IFNAME, link->l_name);
+
+	*result = msg;
+	return 0;
+
+nla_put_failure:
+	nlmsg_free(msg);
+	return -NLE_MSGSIZE;
+}
+
+/**
+ * Delete link
+ * @arg sk		Netlink socket
+ * @arg link		Link to delete
+ *
+ * Builds a \c RTM_DELLINK netlink message requesting the deletion of
+ * a network link which has been previously added to the kernel and
+ * sends the message to the kernel.
+ *
+ * If no matching link exists, the function will return
+ * -NLE_OBJ_NOTFOUND.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @copydoc auto_ack_warning
+ *
+ * @note Only virtual links such as dummy interface or vlan interfaces
+ *       can be deleted. It is not possible to delete physical interfaces
+ *       such as ethernet interfaces or the loopback device.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_delete(struct nl_sock *sk, const struct rtnl_link *link)
+{
+	struct nl_msg *msg;
+	int err;
+	
+	if ((err = rtnl_link_build_delete_request(link, &msg)) < 0)
+		return err;
+
+	return nl_send_sync(sk, msg);
+}
+
+/** @} */
+
+/**
+ * @name Link Object
+ * @{
+ */
+
+/**
+ * Allocate link object
+ *
+ * @see rtnl_link_put()
+ * @return New link object or NULL if allocation failed
+ */
+struct rtnl_link *rtnl_link_alloc(void)
+{
+	return (struct rtnl_link *) nl_object_alloc(&link_obj_ops);
+}
+
+/**
+ * Return a link object reference
+ * @arg link		Link object
+ */
+void rtnl_link_put(struct rtnl_link *link)
+{
+	nl_object_put((struct nl_object *) link);
+}
+
+/**
+ * Set name of link object
+ * @arg link		Link object
+ * @arg name		New name
+ *
+ * @note To change the name of a link in the kernel, set the interface
+ *       index to the link you wish to change, modify the link name using
+ *       this function and pass the link object to rtnl_link_change() or
+ *       rtnl_link_add().
+ *
+ * @route_doc{link_attr_name, Link Name}
+ * @see rtnl_link_get_name()
+ * @see rtnl_link_set_ifindex()
+ */
+void rtnl_link_set_name(struct rtnl_link *link, const char *name)
+{
+	strncpy(link->l_name, name, sizeof(link->l_name) - 1);
+	link->ce_mask |= LINK_ATTR_IFNAME;
+}
+
+/**
+ * Return name of link object
+ * @arg link		Link object
+ *
+ * @route_doc{link_attr_name, Link Name}
+ * @see rtnl_link_set_name()
+ * @return Link name or NULL if name is not specified
+ */
+char *rtnl_link_get_name(struct rtnl_link *link)
+{
+	return link->ce_mask & LINK_ATTR_IFNAME ? link->l_name : NULL;
+}
+
+/**
+ * Set the group identifier of a link object
+ * @arg link		Link object
+ * @arg group		Group identifier
+ */
+void rtnl_link_set_group(struct rtnl_link *link, uint32_t group)
+{
+	link->l_group = group;
+	link->ce_mask |= LINK_ATTR_GROUP;
+}
+
+/**
+ * Return the group identifier of link object
+ * @arg link		Link object
+ *
+ * @return Group identifier or 0 if not set.
+ */
+uint32_t rtnl_link_get_group(struct rtnl_link *link)
+{
+	return link->l_group;
+}
+
+static inline void __assign_addr(struct rtnl_link *link, struct nl_addr **pos,
+				 struct nl_addr *new, int flag)
+{
+	if (*pos)
+		nl_addr_put(*pos);
+
+	nl_addr_get(new);
+	*pos = new;
+
+	link->ce_mask |= flag;
+}
+
+/**
+ * Set link layer address of link object
+ * @arg link		Link object
+ * @arg addr		New link layer address
+ *
+ * The function increments the reference counter of the address object
+ * and overwrites any existing link layer address previously assigned.
+ *
+ * @route_doc{link_attr_address, Link layer address}
+ * @see rtnl_link_get_addr()
+ */
+void rtnl_link_set_addr(struct rtnl_link *link, struct nl_addr *addr)
+{
+	__assign_addr(link, &link->l_addr, addr, LINK_ATTR_ADDR);
+}
+
+/**
+ * Return link layer address of link object
+ * @arg link		Link object
+ *
+ * @copydoc pointer_lifetime_warning
+ * @route_doc{link_attr_address, Link Layer Address}
+ * @see rtnl_link_set_addr()
+ * @return Link layer address or NULL if not set.
+ */
+struct nl_addr *rtnl_link_get_addr(struct rtnl_link *link)
+{
+	return link->ce_mask & LINK_ATTR_ADDR ? link->l_addr : NULL;
+}
+
+/**
+ * Set link layer broadcast address of link object
+ * @arg link		Link object
+ * @arg addr		New broadcast address
+ *
+ * The function increments the reference counter of the address object
+ * and overwrites any existing link layer broadcast address previously
+ * assigned.
+ *
+ * @route_doc{link_attr_broadcast, Link Layer Broadcast Address}
+ * @see rtnl_link_get_broadcast()
+ */
+void rtnl_link_set_broadcast(struct rtnl_link *link, struct nl_addr *addr)
+{
+	__assign_addr(link, &link->l_bcast, addr, LINK_ATTR_BRD);
+}
+
+/**
+ * Return link layer broadcast address of link object
+ * @arg link		Link object
+ *
+ * @copydoc pointer_lifetime_warning
+ * @route_doc{link_attr_address, Link Layer Address}
+ * @see rtnl_link_set_broadcast()
+ * @return Link layer address or NULL if not set.
+ */
+struct nl_addr *rtnl_link_get_broadcast(struct rtnl_link *link)
+{
+	return link->ce_mask & LINK_ATTR_BRD ? link->l_bcast : NULL;
+}
+
+/**
+ * Set flags of link object
+ * @arg link		Link object
+ * @arg flags		Flags
+ *
+ * @see rtnl_link_get_flags()
+ * @see rtnl_link_unset_flags()
+ */
+void rtnl_link_set_flags(struct rtnl_link *link, unsigned int flags)
+{
+	link->l_flag_mask |= flags;
+	link->l_flags |= flags;
+	link->ce_mask |= LINK_ATTR_FLAGS;
+}
+
+/**
+ * Unset flags of link object
+ * @arg link		Link object
+ * @arg flags		Flags
+ *
+ * @see rtnl_link_set_flags()
+ * @see rtnl_link_get_flags()
+ */
+void rtnl_link_unset_flags(struct rtnl_link *link, unsigned int flags)
+{
+	link->l_flag_mask |= flags;
+	link->l_flags &= ~flags;
+	link->ce_mask |= LINK_ATTR_FLAGS;
+}
+
+/**
+ * Return flags of link object
+ * @arg link		Link object
+ *
+ * @route_doc{link_attr_flags, Link Flags}
+ * @see rtnl_link_set_flags()
+ * @see rtnl_link_unset_flags()
+ * @return Link flags or 0 if none have been set.
+ */
+unsigned int rtnl_link_get_flags(struct rtnl_link *link)
+{
+	return link->l_flags;
+}
+
+/**
+ * Set address family of link object
+ *
+ * @see rtnl_link_get_family()
+ */
+void rtnl_link_set_family(struct rtnl_link *link, int family)
+{
+	link->l_family = family;
+	link->ce_mask |= LINK_ATTR_FAMILY;
+
+	if (link->l_af_ops) {
+		af_free(link, link->l_af_ops,
+			link->l_af_data[link->l_af_ops->ao_family], NULL);
+		link->l_af_data[link->l_af_ops->ao_family] = NULL;
+	}
+
+	link->l_af_ops = af_lookup_and_alloc(link, family);
+}
+
+/**
+ * Return address family of link object
+ * @arg link		Link object
+ *
+ * @see rtnl_link_set_family()
+ * @return Address family or \c AF_UNSPEC if not specified.
+ */
+int rtnl_link_get_family(struct rtnl_link *link)
+{
+	return link->ce_mask & LINK_ATTR_FAMILY ? link->l_family : AF_UNSPEC;
+}
+
+/**
+ * Set hardware type of link object
+ * @arg link		Link object
+ * @arg arptype		New hardware type \c (ARPHRD_*)
+ *
+ * @route_doc{link_attr_arptype, Hardware Type}
+ * @copydoc read_only_attribute
+ * @see rtnl_link_get_arptype()
+ */
+void rtnl_link_set_arptype(struct rtnl_link *link, unsigned int arptype)
+{
+	link->l_arptype = arptype;
+	link->ce_mask |= LINK_ATTR_ARPTYPE;
+}
+
+/**
+ * Get hardware type of link object
+ * @arg link		Link object
+ *
+ * @route_doc{link_attr_arptype, Hardware Type}
+ * @see rtnl_link_set_arptype()
+ * @return Hardware type \c (ARPHRD_ETHER *) or \c ARPHRD_VOID
+ */
+unsigned int rtnl_link_get_arptype(struct rtnl_link *link)
+{
+	if (link->ce_mask & LINK_ATTR_ARPTYPE)
+		return link->l_arptype;
+	else
+		return ARPHRD_VOID;
+}
+
+/**
+ * Set interface index of link object
+ * @arg link		Link object
+ * @arg ifindex		Interface index
+ *
+ * @route_doc{link_attr_ifindex, Interface Index}
+ * @see rtnl_link_get_ifindex()
+ */
+void rtnl_link_set_ifindex(struct rtnl_link *link, int ifindex)
+{
+	link->l_index = ifindex;
+	link->ce_mask |= LINK_ATTR_IFINDEX;
+}
+
+
+/**
+ * Return interface index of link object
+ * @arg link		Link object
+ *
+ * @route_doc{link_attr_ifindex, Interface Index}
+ * @see rtnl_link_set_ifindex()
+ * @return Interface index or 0 if not set.
+ */
+int rtnl_link_get_ifindex(struct rtnl_link *link)
+{
+	return link->l_index;
+}
+
+/**
+ * Set Maximum Transmission Unit of link object
+ * @arg link		Link object
+ * @arg mtu		New MTU value in number of bytes
+ *
+ * @route_doc{link_attr_mtu, Maximum Transmission Unit}
+ * @see rtnl_link_get_mtu()
+ */
+void rtnl_link_set_mtu(struct rtnl_link *link, unsigned int mtu)
+{
+	link->l_mtu = mtu;
+	link->ce_mask |= LINK_ATTR_MTU;
+}
+
+/**
+ * Return maximum transmission unit of link object
+ * @arg link		Link object
+ *
+ * @route_doc{link_attr_mtu, Maximum Transmission Unit}
+ * @see rtnl_link_set_mtu()
+ * @return MTU in bytes or 0 if not set
+ */
+unsigned int rtnl_link_get_mtu(struct rtnl_link *link)
+{
+	return link->l_mtu;
+}
+
+/**
+ * Set transmission queue length
+ * @arg link		Link object
+ * @arg txqlen		New queue length
+ *
+ * The unit is dependant on the link type. The most common units is number
+ * of packets.
+ *
+ * @route_doc{link_attr_txqlen, Transmission Queue Length}
+ */
+void rtnl_link_set_txqlen(struct rtnl_link *link, unsigned int txqlen)
+{
+	link->l_txqlen = txqlen;
+	link->ce_mask |= LINK_ATTR_TXQLEN;
+}
+
+/**
+ * Return transmission queue length
+ * @arg link		Link object
+ *
+ * The unit is dependant on the link type. The most common units is number
+ * of packets.
+ *
+ * @route_doc{link_attr_txqlen, Transmission Queue Length}
+ * @return queue length or 0 if not specified.
+ */
+unsigned int rtnl_link_get_txqlen(struct rtnl_link *link)
+{
+	return link->ce_mask & LINK_ATTR_TXQLEN ? link->l_txqlen : 0;
+}
+
+void rtnl_link_set_link(struct rtnl_link *link, int ifindex)
+{
+	link->l_link = ifindex;
+	link->ce_mask |= LINK_ATTR_LINK;
+}
+
+int rtnl_link_get_link(struct rtnl_link *link)
+{
+	return link->l_link;
+}
+
+/**
+ * Set master link of link object
+ * @arg link		Link object
+ * @arg ifindex		Interface index of master link
+ *
+ * @see rtnl_link_get_master()
+ */
+void rtnl_link_set_master(struct rtnl_link *link, int ifindex)
+{
+	link->l_master = ifindex;
+	link->ce_mask |= LINK_ATTR_MASTER;
+}
+
+/**
+ * Return master link of link object
+ * @arg link		Link object
+ *
+ * @see rtnl_link_set_master()
+ * @return Interface index of master link or 0 if not specified
+ */
+int rtnl_link_get_master(struct rtnl_link *link)
+{
+	return link->l_master;
+}
+
+/**
+ * Set carrier of link object
+ * @arg link		Link object
+ * @arg status		New carrier status
+ *
+ * @see rtnl_link_get_carrier()
+ */
+void rtnl_link_set_carrier(struct rtnl_link *link, uint8_t status)
+{
+	link->l_carrier = status;
+	link->ce_mask |= LINK_ATTR_CARRIER;
+}
+
+/**
+ * Return carrier status of link object
+ * @arg link		Link object
+ *
+ * @see rtnl_link_set_master()
+ * @return Carrier state.
+ */
+uint8_t rtnl_link_get_carrier(struct rtnl_link *link)
+{
+	return link->l_carrier;
+}
+
+/**
+ * Set operational status of link object
+ * @arg link		Link object
+ * @arg status		New opertional status
+ *
+ * @route_doc{link_attr_operstate, Operational Status}}
+ * @see rtnl_link_get_operstate()
+ */
+void rtnl_link_set_operstate(struct rtnl_link *link, uint8_t status)
+{
+	link->l_operstate = status;
+	link->ce_mask |= LINK_ATTR_OPERSTATE;
+}
+
+/**
+ * Return operational status of link object
+ * @arg link		Link object
+ *
+ * @route_doc{link_attr_operstate, Operational Status}
+ * @see rtnl_link_set_operstate()
+ * @return Opertional state or \c IF_OPER_UNKNOWN
+ */
+uint8_t rtnl_link_get_operstate(struct rtnl_link *link)
+{
+	return link->l_operstate;
+}
+
+/**
+ * Set link mode of link object
+ * @arg link		Link object
+ * @arg mode		New link mode
+ *
+ * @route_doc{link_attr_mode, Mode}
+ * @see rtnl_link_get_linkmode()
+ */
+void rtnl_link_set_linkmode(struct rtnl_link *link, uint8_t mode)
+{
+	link->l_linkmode = mode;
+	link->ce_mask |= LINK_ATTR_LINKMODE;
+}
+
+/**
+ * Return link mode of link object
+ * @arg link		Link object
+ *
+ * @route_doc{link_attr_mode, Mode}
+ * @see rtnl_link_get_linkmode()
+ * @return Link mode or \c IF_LINK_MODE_DEFAULT
+ */
+uint8_t rtnl_link_get_linkmode(struct rtnl_link *link)
+{
+	return link->l_linkmode;
+}
+
+/**
+ * Return alias name of link object (SNMP IfAlias)
+ * @arg link		Link object
+ *
+ * @route_doc{link_attr_alias, Alias}
+ * @see rtnl_link_set_ifalias()
+ * @return Alias name or NULL if not set.
+ */
+const char *rtnl_link_get_ifalias(struct rtnl_link *link)
+{
+	return link->l_ifalias;
+}
+
+/**
+ * Set alias name of link object (SNMP IfAlias)
+ * @arg link		Link object
+ * @arg alias		Alias name or NULL to unset
+ *
+ * Sets the alias name of the link to the specified name. The alias
+ * name can be unset by specyfing NULL as the alias. The name will
+ * be strdup()ed, so no need to provide a persistent character string.
+ *
+ * @route_doc{link_attr_alias, Alias}
+ * @see rtnl_link_get_ifalias()
+ */
+void rtnl_link_set_ifalias(struct rtnl_link *link, const char *alias)
+{
+	free(link->l_ifalias);
+
+	if (alias) {
+		link->l_ifalias = strdup(alias);
+		link->ce_mask |= LINK_ATTR_IFALIAS;
+	} else {
+		link->l_ifalias = NULL;
+		link->ce_mask &= ~LINK_ATTR_IFALIAS;
+	}
+}
+
+/**
+ * Set queueing discipline name of link object
+ * @arg link		Link object
+ * @arg name		Name of queueing discipline
+ *
+ * @copydoc read_only_attribute
+ *
+ * For more information on how to modify the qdisc of a link, see section
+ * @ref_route{route_tc, Traffic Control}.
+ *
+ * @route_doc{link_attr_qdisc, Queueing Discipline Name}
+ * @see rtnl_link_get_qdisc()
+ */
+void rtnl_link_set_qdisc(struct rtnl_link *link, const char *name)
+{
+	strncpy(link->l_qdisc, name, sizeof(link->l_qdisc) - 1);
+	link->ce_mask |= LINK_ATTR_QDISC;
+}
+
+/**
+ * Return name of queueing discipline of link object
+ * @arg link		Link object
+ *
+ * @route_doc{link_attr_qdisc, Queueing Discipline Name}
+ * @see rtnl_link_set_qdisc()
+ * @return Name of qdisc or NULL if not specified.
+ */
+char *rtnl_link_get_qdisc(struct rtnl_link *link)
+{
+	return link->ce_mask & LINK_ATTR_QDISC ? link->l_qdisc : NULL;
+}
+
+
+/**
+ * Return number of PCI virtual functions of link object
+ * @arg link		Link object
+ * @arg num_vf		Pointer to store number of VFs
+ *
+ * @return 0 on success or -NLE_OPNOTSUPP if not available
+ */
+int rtnl_link_get_num_vf(struct rtnl_link *link, uint32_t *num_vf)
+{
+	if (link->ce_mask & LINK_ATTR_NUM_VF) {
+		*num_vf = link->l_num_vf;
+		return 0;
+	} else
+		return -NLE_OPNOTSUPP;
+}
+
+/**
+ * Return value of link statistics counter
+ * @arg link		Link object
+ * @arg id		Identifier of statistical counter
+ *
+ * @return Value of counter or 0 if not specified.
+ */
+uint64_t rtnl_link_get_stat(struct rtnl_link *link, rtnl_link_stat_id_t id)
+{
+	if (id > RTNL_LINK_STATS_MAX)
+		return 0;
+
+	return link->l_stats[id];
+}
+
+/**
+ * Set value of link statistics counter
+ * @arg link		Link object
+ * @arg id		Identifier of statistical counter
+ * @arg value		New value
+ *
+ * \note Changing the value of a statistical counter will not change the
+ *       value in the kernel.
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_set_stat(struct rtnl_link *link, rtnl_link_stat_id_t id,
+		       const uint64_t value)
+{
+	if (id > RTNL_LINK_STATS_MAX)
+		return -NLE_INVAL;
+
+	link->l_stats[id] = value;
+
+	return 0;
+}
+
+/**
+ * Set type of link object
+ * @arg link		Link object
+ * @arg type		Name of link type
+ *
+ * Looks up the link type module and prepares the link to store type
+ * specific attributes. If a type has been assigned already it will
+ * be released with all link type specific attributes lost.
+ *
+ * @route_doc{link_modules, Link Modules}
+ * @return 0 on success or a negative errror code.
+ */
+int rtnl_link_set_type(struct rtnl_link *link, const char *type)
+{
+	struct rtnl_link_info_ops *io;
+	int err;
+	char *kind;
+
+	free(link->l_info_kind);
+	link->ce_mask &= ~LINK_ATTR_LINKINFO;
+	if (link->l_info_ops)
+		release_link_info(link);
+
+	if (!type)
+		return 0;
+
+	kind = strdup(type);
+	if (!kind)
+		return -NLE_NOMEM;
+
+	io = rtnl_link_info_ops_lookup(type);
+	if (io) {
+		if (io->io_alloc && (err = io->io_alloc(link)) < 0)
+			goto errout;
+
+		link->l_info_ops = io;
+	}
+
+	link->l_info_kind = kind;
+	link->ce_mask |= LINK_ATTR_LINKINFO;
+
+	return 0;
+
+errout:
+	free(kind);
+	return err;
+}
+
+/**
+ * Return type of link
+ * @arg link		Link object
+ *
+ * @route_doc{link_modules, Link Modules}
+ * @return Name of link type or NULL if not specified.
+ */
+char *rtnl_link_get_type(struct rtnl_link *link)
+{
+	return link->l_info_kind;
+}
+
+/**
+ * Set link promiscuity count
+ * @arg link		Link object
+ * @arg count		New promiscuity count
+ *
+ * @copydoc read_only_attribute
+ *
+ * @see rtnl_link_get_promiscuity()
+ */
+void rtnl_link_set_promiscuity(struct rtnl_link *link, uint32_t count)
+{
+	link->l_promiscuity = count;
+	link->ce_mask |= LINK_ATTR_PROMISCUITY;
+}
+
+/**
+ * Return link promiscuity count
+ * @arg link		Link object
+ *
+ * @see rtnl_link_set_promiscuity()
+ * @return Link promiscuity count or 0
+ */
+uint32_t rtnl_link_get_promiscuity(struct rtnl_link *link)
+{
+	return link->l_promiscuity;
+}
+
+/**
+ * Set number of TX queues
+ * @arg link		Link object
+ * @arg nqueues		Number of queues
+ *
+ * Sets the number of TX queues of the link object. The value is considered
+ * by the kernel when creating network devices that can be created via
+ * netlink. The value will be passed on to alloc_netdev_mqs()
+ *
+ * Therefore use of rtnl_link_set_num_tx_queues() only makes sense in
+ * combination with rtnl_link_add() or if the link object is used as a filter.
+ *
+ * @see rtnl_link_get_num_tx_queues()
+ */
+void rtnl_link_set_num_tx_queues(struct rtnl_link *link, uint32_t nqueues)
+{
+	link->l_num_tx_queues = nqueues;
+	link->ce_mask |= LINK_ATTR_NUM_TX_QUEUES;
+}
+
+/**
+ * Return number of TX queues
+ * @arg link		Link object
+ *
+ * @return Number of TX queues or 0
+ */
+uint32_t rtnl_link_get_num_tx_queues(struct rtnl_link *link)
+{
+	return link->l_num_tx_queues;
+}
+
+/**
+ * Set number of RX queues
+ * @arg link		Link object
+ * @arg nqueues		Number of queues
+ *
+ * Sets the number of RX queues of the link object. The value is considered
+ * by the kernel when creating network devices that can be created via
+ * netlink. The value will be passed on to alloc_netdev_mqs()
+ *
+ * Therefore use of rtnl_link_set_num_rx_queues() only makes sense in
+ * combination with rtnl_link_add() or if the link object is used as a filter.
+ *
+ * @see rtnl_link_get_num_rx_queues()
+ */
+void rtnl_link_set_num_rx_queues(struct rtnl_link *link, uint32_t nqueues)
+{
+	link->l_num_rx_queues = nqueues;
+	link->ce_mask |= LINK_ATTR_NUM_RX_QUEUES;
+}
+
+/**
+ * Return number of RX queues
+ * @arg link		Link object
+ *
+ * @return Number of RX queues or 0
+ */
+uint32_t rtnl_link_get_num_rx_queues(struct rtnl_link *link)
+{
+	return link->l_num_rx_queues;
+}
+
+/**
+ * Return physical port id of link object
+ * @arg link		Link object
+ *
+ * @return Physical port id or NULL if not set.
+ */
+struct nl_data *rtnl_link_get_phys_port_id(struct rtnl_link *link)
+{
+	return link->l_phys_port_id;
+}
+
+void rtnl_link_set_ns_fd(struct rtnl_link *link, int fd)
+{
+	link->l_ns_fd = fd;
+	link->ce_mask |= LINK_ATTR_NS_FD;
+}
+
+int rtnl_link_get_ns_fd(struct rtnl_link *link)
+{
+	return link->l_ns_fd;
+}
+
+void rtnl_link_set_ns_pid(struct rtnl_link *link, pid_t pid)
+{
+	link->l_ns_pid = pid;
+	link->ce_mask |= LINK_ATTR_NS_PID;
+}
+
+pid_t rtnl_link_get_ns_pid(struct rtnl_link *link)
+{
+	return link->l_ns_pid;
+}
+
+/** @} */
+
+/**
+ * @name Master/Slave
+ * @{
+ */
+
+/**
+ * Enslave slave link to master link
+ * @arg sock		netlink socket
+ * @arg master		ifindex of master link
+ * @arg slave		ifindex of slave link
+ *
+ * This function is identical to rtnl_link_enslave() except that
+ * it takes interface indices instead of rtnl_link objects.
+ *
+ * @see rtnl_link_enslave()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_enslave_ifindex(struct nl_sock *sock, int master, int slave)
+{
+	struct rtnl_link *link;
+	int err;
+
+	if (!(link = rtnl_link_alloc()))
+		return -NLE_NOMEM;
+
+	rtnl_link_set_ifindex(link, slave);
+	rtnl_link_set_master(link, master);
+	
+	if ((err = rtnl_link_change(sock, link, link, 0)) < 0)
+		goto errout;
+
+	rtnl_link_put(link);
+
+	/*
+	 * Due to the kernel not signaling whether this opertion is
+	 * supported or not, we will retrieve the attribute to see  if the
+	 * request was successful. If the master assigned remains unchanged
+	 * we will return NLE_OPNOTSUPP to allow performing backwards
+	 * compatibility of some sort.
+	 */
+	if ((err = rtnl_link_get_kernel(sock, slave, NULL, &link)) < 0)
+		return err;
+
+	if (rtnl_link_get_master(link) != master)
+		err = -NLE_OPNOTSUPP;
+
+errout:
+	rtnl_link_put(link);
+
+	return err;
+}
+
+/**
+ * Enslave slave link to master link
+ * @arg sock		netlink socket
+ * @arg master		master link
+ * @arg slave		slave link
+ *
+ * Constructs a RTM_NEWLINK or RTM_SETLINK message adding the slave to
+ * the master and sends the request via the specified netlink socket.
+ *
+ * @note The feature of enslaving/releasing via netlink has only been added
+ *       recently to the kernel (Feb 2011). Also, the kernel does not signal
+ *       if the operation is not supported. Therefore this function will
+ *       verify if the master assignment has changed and will return
+ *       -NLE_OPNOTSUPP if it did not.
+ *
+ * @see rtnl_link_enslave_ifindex()
+ * @see rtnl_link_release()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_enslave(struct nl_sock *sock, struct rtnl_link *master,
+		      struct rtnl_link *slave)
+{
+	return rtnl_link_enslave_ifindex(sock, rtnl_link_get_ifindex(master),
+					 rtnl_link_get_ifindex(slave));
+}
+
+/**
+ * Release slave link from its master
+ * @arg sock		netlink socket
+ * @arg slave		slave link
+ *
+ * This function is identical to rtnl_link_release() except that
+ * it takes an interface index instead of a rtnl_link object.
+ *
+ * @see rtnl_link_release()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_release_ifindex(struct nl_sock *sock, int slave)
+{
+	return rtnl_link_enslave_ifindex(sock, 0, slave);
+}
+
+/**
+ * Release slave link from its master
+ * @arg sock		netlink socket
+ * @arg slave		slave link
+ *
+ * Constructs a RTM_NEWLINK or RTM_SETLINK message releasing the slave from
+ * its master and sends the request via the specified netlink socket.
+ *
+ * @note The feature of enslaving/releasing via netlink has only been added
+ *       recently to the kernel (Feb 2011). Also, the kernel does not signal
+ *       if the operation is not supported. Therefore this function will
+ *       verify if the master assignment has changed and will return
+ *       -NLE_OPNOTSUPP if it did not.
+ *
+ * @see rtnl_link_release_ifindex()
+ * @see rtnl_link_enslave()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_release(struct nl_sock *sock, struct rtnl_link *slave)
+{
+	return rtnl_link_release_ifindex(sock, rtnl_link_get_ifindex(slave));
+}
+
+/** @} */
+
+/**
+ * @name Utilities
+ * @{
+ */
+
+static const struct trans_tbl link_flags[] = {
 	__ADD(IFF_LOOPBACK, loopback)
 	__ADD(IFF_BROADCAST, broadcast)
 	__ADD(IFF_POINTOPOINT, pointopoint)
@@ -1042,7 +2532,7 @@
 	__ADD(IFF_ECHO, echo)
 };
 
-char * rtnl_link_flags2str(int flags, char *buf, size_t len)
+char *rtnl_link_flags2str(int flags, char *buf, size_t len)
 {
 	return __flags2str(flags, buf, len, link_flags,
 			   ARRAY_SIZE(link_flags));
@@ -1053,14 +2543,7 @@
 	return __str2flags(name, link_flags, ARRAY_SIZE(link_flags));
 }
 
-/** @} */
-
-/**
- * @name Link Statistics Translations
- * @{
- */
-
-static struct trans_tbl link_stats[] = {
+static const struct trans_tbl link_stats[] = {
 	__ADD(RTNL_LINK_RX_PACKETS, rx_packets)
 	__ADD(RTNL_LINK_TX_PACKETS, tx_packets)
 	__ADD(RTNL_LINK_RX_BYTES, rx_bytes)
@@ -1082,8 +2565,48 @@
 	__ADD(RTNL_LINK_TX_CARRIER_ERR, tx_carrier_err)
 	__ADD(RTNL_LINK_TX_HBEAT_ERR, tx_hbeat_err)
 	__ADD(RTNL_LINK_TX_WIN_ERR, tx_win_err)
-	__ADD(RTNL_LINK_TX_COLLISIONS, tx_collision)
+	__ADD(RTNL_LINK_COLLISIONS, collisions)
 	__ADD(RTNL_LINK_MULTICAST, multicast)
+	__ADD(RTNL_LINK_IP6_INPKTS, Ip6InReceives)
+	__ADD(RTNL_LINK_IP6_INHDRERRORS, Ip6InHdrErrors)
+	__ADD(RTNL_LINK_IP6_INTOOBIGERRORS, Ip6InTooBigErrors)
+	__ADD(RTNL_LINK_IP6_INNOROUTES, Ip6InNoRoutes)
+	__ADD(RTNL_LINK_IP6_INADDRERRORS, Ip6InAddrErrors)
+	__ADD(RTNL_LINK_IP6_INUNKNOWNPROTOS, Ip6InUnknownProtos)
+	__ADD(RTNL_LINK_IP6_INTRUNCATEDPKTS, Ip6InTruncatedPkts)
+	__ADD(RTNL_LINK_IP6_INDISCARDS, Ip6InDiscards)
+	__ADD(RTNL_LINK_IP6_INDELIVERS, Ip6InDelivers)
+	__ADD(RTNL_LINK_IP6_OUTFORWDATAGRAMS, Ip6OutForwDatagrams)
+	__ADD(RTNL_LINK_IP6_OUTPKTS, Ip6OutRequests)
+	__ADD(RTNL_LINK_IP6_OUTDISCARDS, Ip6OutDiscards)
+	__ADD(RTNL_LINK_IP6_OUTNOROUTES, Ip6OutNoRoutes)
+	__ADD(RTNL_LINK_IP6_REASMTIMEOUT, Ip6ReasmTimeout)
+	__ADD(RTNL_LINK_IP6_REASMREQDS, Ip6ReasmReqds)
+	__ADD(RTNL_LINK_IP6_REASMOKS, Ip6ReasmOKs)
+	__ADD(RTNL_LINK_IP6_REASMFAILS, Ip6ReasmFails)
+	__ADD(RTNL_LINK_IP6_FRAGOKS, Ip6FragOKs)
+	__ADD(RTNL_LINK_IP6_FRAGFAILS, Ip6FragFails)
+	__ADD(RTNL_LINK_IP6_FRAGCREATES, Ip6FragCreates)
+	__ADD(RTNL_LINK_IP6_INMCASTPKTS, Ip6InMcastPkts)
+	__ADD(RTNL_LINK_IP6_OUTMCASTPKTS, Ip6OutMcastPkts)
+	__ADD(RTNL_LINK_IP6_INBCASTPKTS, Ip6InBcastPkts)
+	__ADD(RTNL_LINK_IP6_OUTBCASTPKTS, Ip6OutBcastPkts)
+	__ADD(RTNL_LINK_IP6_INOCTETS, Ip6InOctets)
+	__ADD(RTNL_LINK_IP6_OUTOCTETS, Ip6OutOctets)
+	__ADD(RTNL_LINK_IP6_INMCASTOCTETS, Ip6InMcastOctets)
+	__ADD(RTNL_LINK_IP6_OUTMCASTOCTETS, Ip6OutMcastOctets)
+	__ADD(RTNL_LINK_IP6_INBCASTOCTETS, Ip6InBcastOctets)
+	__ADD(RTNL_LINK_IP6_OUTBCASTOCTETS, Ip6OutBcastOctets)
+	__ADD(RTNL_LINK_ICMP6_INMSGS, ICMP6_InMsgs)
+	__ADD(RTNL_LINK_ICMP6_INERRORS, ICMP6_InErrors)
+	__ADD(RTNL_LINK_ICMP6_OUTMSGS, ICMP6_OutMsgs)
+	__ADD(RTNL_LINK_ICMP6_OUTERRORS, ICMP6_OutErrors)
+	__ADD(RTNL_LINK_ICMP6_CSUMERRORS, ICMP6_InCsumErrors)
+	__ADD(RTNL_LINK_IP6_CSUMERRORS, Ip6_InCsumErrors)
+	__ADD(RTNL_LINK_IP6_NOECTPKTS, Ip6_InNoECTPkts)
+	__ADD(RTNL_LINK_IP6_ECT1PKTS, Ip6_InECT1Pkts)
+	__ADD(RTNL_LINK_IP6_ECT0PKTS, Ip6_InECT0Pkts)
+	__ADD(RTNL_LINK_IP6_CEPKTS, Ip6_InCEPkts)
 };
 
 char *rtnl_link_stat2str(int st, char *buf, size_t len)
@@ -1096,14 +2619,7 @@
 	return __str2type(name, link_stats, ARRAY_SIZE(link_stats));
 }
 
-/** @} */
-
-/**
- * @name Link Operstate Translations
- * @{
- */
-
-static struct trans_tbl link_operstates[] = {
+static const struct trans_tbl link_operstates[] = {
 	__ADD(IF_OPER_UNKNOWN, unknown)
 	__ADD(IF_OPER_NOTPRESENT, notpresent)
 	__ADD(IF_OPER_DOWN, down)
@@ -1113,7 +2629,7 @@
 	__ADD(IF_OPER_UP, up)
 };
 
-char *rtnl_link_operstate2str(int st, char *buf, size_t len)
+char *rtnl_link_operstate2str(uint8_t st, char *buf, size_t len)
 {
 	return __type2str(st, buf, len, link_operstates,
 			  ARRAY_SIZE(link_operstates));
@@ -1125,19 +2641,17 @@
 			  ARRAY_SIZE(link_operstates));
 }
 
-/** @} */
-
-/**
- * @name Link Mode Translations
- * @{
- */
-
-static struct trans_tbl link_modes[] = {
+static const struct trans_tbl link_modes[] = {
 	__ADD(IF_LINK_MODE_DEFAULT, default)
 	__ADD(IF_LINK_MODE_DORMANT, dormant)
 };
 
-char *rtnl_link_mode2str(int st, char *buf, size_t len)
+static const struct trans_tbl carrier_states[] = {
+	__ADD(IF_CARRIER_DOWN, down)
+	__ADD(IF_CARRIER_UP, up)
+};
+
+char *rtnl_link_mode2str(uint8_t st, char *buf, size_t len)
 {
 	return __type2str(st, buf, len, link_modes, ARRAY_SIZE(link_modes));
 }
@@ -1147,276 +2661,54 @@
 	return __str2type(name, link_modes, ARRAY_SIZE(link_modes));
 }
 
+char *rtnl_link_carrier2str(uint8_t st, char *buf, size_t len)
+{
+	return __type2str(st, buf, len, carrier_states,
+			  ARRAY_SIZE(carrier_states));
+}
+
+int rtnl_link_str2carrier(const char *name)
+{
+	return __str2type(name, carrier_states, ARRAY_SIZE(carrier_states));
+}
+
 /** @} */
 
 /**
- * @name Attributes
- * @{
+ * @name Deprecated Functions
  */
 
-void rtnl_link_set_qdisc(struct rtnl_link *link, const char *qdisc)
+/**
+ * @deprecated Use of this function is deprecated, use rtnl_link_set_type()
+ */
+int rtnl_link_set_info_type(struct rtnl_link *link, const char *type)
 {
-	strncpy(link->l_qdisc, qdisc, sizeof(link->l_qdisc) - 1);
-	link->ce_mask |= LINK_ATTR_QDISC;
+	return rtnl_link_set_type(link, type);
 }
 
-char *rtnl_link_get_qdisc(struct rtnl_link *link)
+/**
+ * @deprecated Use of this function is deprecated, use rtnl_link_get_type()
+ */
+char *rtnl_link_get_info_type(struct rtnl_link *link)
 {
-	if (link->ce_mask & LINK_ATTR_QDISC)
-		return link->l_qdisc;
-	else
-		return NULL;
+	return rtnl_link_get_type(link);
 }
 
-void rtnl_link_set_name(struct rtnl_link *link, const char *name)
-{
-	strncpy(link->l_name, name, sizeof(link->l_name) - 1);
-	link->ce_mask |= LINK_ATTR_IFNAME;
-}
-
-char *rtnl_link_get_name(struct rtnl_link *link)
-{
-	if (link->ce_mask & LINK_ATTR_IFNAME)
-		return link->l_name;
-	else
-		return NULL;
-}
-
-static inline void __assign_addr(struct rtnl_link *link, struct nl_addr **pos,
-				 struct nl_addr *new, int flag)
-{
-	if (*pos)
-		nl_addr_put(*pos);
-
-	nl_addr_get(new);
-	*pos = new;
-
-	link->ce_mask |= flag;
-}
-
-void rtnl_link_set_addr(struct rtnl_link *link, struct nl_addr *addr)
-{
-	__assign_addr(link, &link->l_addr, addr, LINK_ATTR_ADDR);
-}
-
-struct nl_addr *rtnl_link_get_addr(struct rtnl_link *link)
-{
-	if (link->ce_mask & LINK_ATTR_ADDR)
-		return link->l_addr;
-	else
-		return NULL;
-}
-
-void rtnl_link_set_broadcast(struct rtnl_link *link, struct nl_addr *brd)
-{
-	__assign_addr(link, &link->l_bcast, brd, LINK_ATTR_BRD);
-}
-
-struct nl_addr *rtnl_link_get_broadcast(struct rtnl_link *link)
-{
-	if (link->ce_mask & LINK_ATTR_BRD)
-		return link->l_bcast;
-	else
-		return NULL;
-}
-
-void rtnl_link_set_flags(struct rtnl_link *link, unsigned int flags)
-{
-	link->l_flag_mask |= flags;
-	link->l_flags |= flags;
-	link->ce_mask |= LINK_ATTR_FLAGS;
-}
-
-void rtnl_link_unset_flags(struct rtnl_link *link, unsigned int flags)
-{
-	link->l_flag_mask |= flags;
-	link->l_flags &= ~flags;
-	link->ce_mask |= LINK_ATTR_FLAGS;
-}
-
-unsigned int rtnl_link_get_flags(struct rtnl_link *link)
-{
-	return link->l_flags;
-}
-
-void rtnl_link_set_family(struct rtnl_link *link, int family)
-{
-	link->l_family = family;
-	link->ce_mask |= LINK_ATTR_FAMILY;
-}
-
-int rtnl_link_get_family(struct rtnl_link *link)
-{
-	if (link->l_family & LINK_ATTR_FAMILY)
-		return link->l_family;
-	else
-		return AF_UNSPEC;
-}
-
-void rtnl_link_set_arptype(struct rtnl_link *link, unsigned int arptype)
-{
-	link->l_arptype = arptype;
-}
-
-unsigned int rtnl_link_get_arptype(struct rtnl_link *link)
-{
-	return link->l_arptype;
-}
-
-void rtnl_link_set_ifindex(struct rtnl_link *link, int ifindex)
-{
-	link->l_index = ifindex;
-	link->ce_mask |= LINK_ATTR_IFINDEX;
-}
-
-int rtnl_link_get_ifindex(struct rtnl_link *link)
-{
-	return link->l_index;
-}
-
-void rtnl_link_set_mtu(struct rtnl_link *link, unsigned int mtu)
-{
-	link->l_mtu = mtu;
-	link->ce_mask |= LINK_ATTR_MTU;
-}
-
-unsigned int rtnl_link_get_mtu(struct rtnl_link *link)
-{
-	if (link->ce_mask & LINK_ATTR_MTU)
-		return link->l_mtu;
-	else
-		return 0;
-}
-
-void rtnl_link_set_txqlen(struct rtnl_link *link, unsigned int txqlen)
-{
-	link->l_txqlen = txqlen;
-	link->ce_mask |= LINK_ATTR_TXQLEN;
-}
-
-unsigned int rtnl_link_get_txqlen(struct rtnl_link *link)
-{
-	if (link->ce_mask & LINK_ATTR_TXQLEN)
-		return link->l_txqlen;
-	else
-		return UINT_MAX;
-}
-
+/**
+ * @deprecated The weight attribute is unused and obsoleted in all recent kernels
+ */
 void rtnl_link_set_weight(struct rtnl_link *link, unsigned int weight)
 {
 	link->l_weight = weight;
 	link->ce_mask |= LINK_ATTR_WEIGHT;
 }
 
+/**
+ * @deprecated The weight attribute is unused and obsoleted in all recent kernels
+ */
 unsigned int rtnl_link_get_weight(struct rtnl_link *link)
 {
-	if (link->ce_mask & LINK_ATTR_WEIGHT)
-		return link->l_weight;
-	else
-		return UINT_MAX;
-}
-
-void rtnl_link_set_link(struct rtnl_link *link, int ifindex)
-{
-	link->l_link = ifindex;
-	link->ce_mask |= LINK_ATTR_LINK;
-}
-
-int rtnl_link_get_link(struct rtnl_link *link)
-{
-	return link->l_link;
-}
-
-void rtnl_link_set_master(struct rtnl_link *link, int ifindex)
-{
-	link->l_master = ifindex;
-	link->ce_mask |= LINK_ATTR_MASTER;
-}
-
-int rtnl_link_get_master(struct rtnl_link *link)
-{
-	return link->l_master;
-}
-
-void rtnl_link_set_operstate(struct rtnl_link *link, uint8_t operstate)
-{
-	link->l_operstate = operstate;
-	link->ce_mask |= LINK_ATTR_OPERSTATE;
-}
-
-uint8_t rtnl_link_get_operstate(struct rtnl_link *link)
-{
-	if (link->ce_mask & LINK_ATTR_OPERSTATE)
-		return link->l_operstate;
-	else
-		return IF_OPER_UNKNOWN;
-}
-
-void rtnl_link_set_linkmode(struct rtnl_link *link, uint8_t linkmode)
-{
-	link->l_linkmode = linkmode;
-	link->ce_mask |= LINK_ATTR_LINKMODE;
-}
-
-uint8_t rtnl_link_get_linkmode(struct rtnl_link *link)
-{
-	if (link->ce_mask & LINK_ATTR_LINKMODE)
-		return link->l_linkmode;
-	else
-		return IF_LINK_MODE_DEFAULT;
-}
-
-uint64_t rtnl_link_get_stat(struct rtnl_link *link, int id)
-{
-	if (id < 0 || id > RTNL_LINK_STATS_MAX)
-		return 0;
-
-	return link->l_stats[id];
-}
-
-/**
- * Specify the info type of a link
- * @arg link	link object
- * @arg type	info type
- *
- * Looks up the info type and prepares the link to store info type
- * specific attributes. If an info type has been assigned already
- * it will be released with all changes lost.
- *
- * @return 0 on success or a negative errror code.
- */
-int rtnl_link_set_info_type(struct rtnl_link *link, const char *type)
-{
-	struct rtnl_link_info_ops *io;
-	int err;
-
-	if ((io = rtnl_link_info_ops_lookup(type)) == NULL)
-		return -NLE_OPNOTSUPP;
-
-	if (link->l_info_ops)
-		release_link_info(link);
-
-	if ((err = io->io_alloc(link)) < 0)
-		return err;
-
-	link->l_info_ops = io;
-
-	return 0;
-}
-
-/**
- * Return info type of a link
- * @arg link	link object
- *
- * @note The returned pointer is only valid as long as the link exists
- * @return Info type name or NULL if unknown.
- */
-char *rtnl_link_get_info_type(struct rtnl_link *link)
-{
-	if (link->l_info_ops)
-		return link->l_info_ops->io_name;
-	else
-		return NULL;
+	return link->l_weight;
 }
 
 /** @} */
@@ -1430,15 +2722,16 @@
 	    [NL_DUMP_LINE]	= link_dump_line,
 	    [NL_DUMP_DETAILS]	= link_dump_details,
 	    [NL_DUMP_STATS]	= link_dump_stats,
-	    [NL_DUMP_ENV]	= link_dump_env,
 	},
 	.oo_compare		= link_compare,
+	.oo_keygen		= link_keygen,
 	.oo_attrs2str		= link_attrs2str,
-	.oo_id_attrs		= LINK_ATTR_IFINDEX,
+	.oo_id_attrs		= LINK_ATTR_IFINDEX | LINK_ATTR_FAMILY,
 };
 
 static struct nl_af_group link_groups[] = {
 	{ AF_UNSPEC,	RTNLGRP_LINK },
+	{ AF_BRIDGE,    RTNLGRP_LINK },
 	{ END_OF_GROUP_LIST },
 };
 
@@ -1449,6 +2742,7 @@
 					{ RTM_NEWLINK, NL_ACT_NEW, "new" },
 					{ RTM_DELLINK, NL_ACT_DEL, "del" },
 					{ RTM_GETLINK, NL_ACT_GET, "get" },
+					{ RTM_SETLINK, NL_ACT_CHANGE, "set" },
 					END_OF_MSGTYPES_LIST,
 				  },
 	.co_protocol		= NETLINK_ROUTE,
diff --git a/lib/route/link/api.c b/lib/route/link/api.c
index a0e7679..6d1e12f 100644
--- a/lib/route/link/api.c
+++ b/lib/route/link/api.c
@@ -11,8 +11,8 @@
 
 /**
  * @ingroup link
- * @defgroup link_info Link Info API
- * @brief
+ * @defgroup link_API Link Modules API
+ * @brief API for modules implementing specific link types/semantics.
  *
  * @par 1) Registering/Unregistering a new link info type
  * @code
@@ -39,60 +39,359 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/link.h>
-#include <netlink/route/link/info-api.h>
+#include <netlink-private/route/link/api.h>
 
-static struct rtnl_link_info_ops *info_ops;
+static NL_LIST_HEAD(info_ops);
 
-struct rtnl_link_info_ops *rtnl_link_info_ops_lookup(const char *name)
+/* lock protecting info_ops and af_ops */
+static NL_RW_LOCK(info_lock);
+
+static struct rtnl_link_info_ops *__rtnl_link_info_ops_lookup(const char *name)
 {
 	struct rtnl_link_info_ops *ops;
 
-	for (ops = info_ops; ops; ops = ops->io_next)
+	nl_list_for_each_entry(ops, &info_ops, io_list)
 		if (!strcmp(ops->io_name, name))
 			return ops;
 
 	return NULL;
 }
 
+/**
+ * @name Link Info Modules
+ * @{
+ */
+
+/**
+ * Return operations of a specific link info type
+ * @arg name		Name of link info type.
+ *
+ * @note The returned pointer must be given back using rtnl_link_info_ops_put()
+ *
+ * @return Pointer to operations or NULL if unavailable.
+ */
+struct rtnl_link_info_ops *rtnl_link_info_ops_lookup(const char *name)
+{
+	struct rtnl_link_info_ops *ops;
+
+	nl_write_lock(&info_lock);
+	if ((ops = __rtnl_link_info_ops_lookup(name)))
+		ops->io_refcnt++;
+	nl_write_unlock(&info_lock);
+
+	return ops;
+}
+
+/**
+ * Give back reference to a set of operations.
+ * @arg ops		Link info operations.
+ */
+void rtnl_link_info_ops_put(struct rtnl_link_info_ops *ops)
+{
+	if (ops)
+		ops->io_refcnt--;
+}
+
+/**
+ * Register operations for a link info type
+ * @arg ops		Link info operations
+ *
+ * This function must be called by modules implementing a specific link
+ * info type. It will make the operations implemented by the module
+ * available for everyone else.
+ *
+ * @return 0 on success or a negative error code.
+ * @return -NLE_INVAL Link info name not specified.
+ * @return -NLE_EXIST Operations for address family already registered.
+ */
 int rtnl_link_register_info(struct rtnl_link_info_ops *ops)
 {
+	int err = 0;
+
 	if (ops->io_name == NULL)
 		return -NLE_INVAL;
 
-	if (rtnl_link_info_ops_lookup(ops->io_name))
-		return -NLE_EXIST;
+	nl_write_lock(&info_lock);
+	if (__rtnl_link_info_ops_lookup(ops->io_name)) {
+		err = -NLE_EXIST;
+		goto errout;
+	}
 
 	NL_DBG(1, "Registered link info operations %s\n", ops->io_name);
 
-	ops->io_next = info_ops;
-	info_ops = ops;
+	nl_list_add_tail(&ops->io_list, &info_ops);
+errout:
+	nl_write_unlock(&info_lock);
 
-	return 0;
+	return err;
 }
 
+/**
+ * Unregister operations for a link info type
+ * @arg ops		Link info operations
+ *
+ * This function must be called if a module implementing a specific link
+ * info type is unloaded or becomes unavailable. It must provide a
+ * set of operations which have previously been registered using
+ * rtnl_link_register_info().
+ *
+ * @return 0 on success or a negative error code
+ * @return _NLE_OPNOTSUPP Link info operations not registered.
+ * @return -NLE_BUSY Link info operations still in use.
+ */
 int rtnl_link_unregister_info(struct rtnl_link_info_ops *ops)
 {
-	struct rtnl_link_info_ops *t, **tp;
+	struct rtnl_link_info_ops *t;
+	int err = -NLE_OPNOTSUPP;
 
-	for (tp = &info_ops; (t=*tp) != NULL; tp = &t->io_next)
-		if (t == ops)
-			break;
+	nl_write_lock(&info_lock);
 
-	if (!t)
-		return -NLE_OPNOTSUPP;
+	nl_list_for_each_entry(t, &info_ops, io_list) {
+		if (t == ops) {
+			if (t->io_refcnt > 0) {
+				err = -NLE_BUSY;
+				goto errout;
+			}
 
-	if (t->io_refcnt > 0)
-		return -NLE_BUSY;
+			nl_list_del(&t->io_list);
 
-	NL_DBG(1, "Unregistered link info perations %s\n", ops->io_name);
+			NL_DBG(1, "Unregistered link info operations %s\n",
+				ops->io_name);
+			err = 0;
+			goto errout;
+		}
+	}
 
-	*tp = t->io_next;
-	return 0;
+errout:
+	nl_write_unlock(&info_lock);
+
+	return err;
 }
 
 /** @} */
 
+/**
+ * @name Link Address Family Modules
+ * @{
+ */
+
+static struct rtnl_link_af_ops *af_ops[AF_MAX];
+
+/**
+ * Return operations of a specific link address family
+ * @arg family		Address family
+ *
+ * @note The returned pointer must be given back using rtnl_link_af_ops_put()
+ *
+ * @return Pointer to operations or NULL if unavailable.
+ */
+struct rtnl_link_af_ops *rtnl_link_af_ops_lookup(const unsigned int family)
+{
+	if (family == AF_UNSPEC || family >= AF_MAX)
+		return NULL;
+
+	nl_write_lock(&info_lock);
+	if (af_ops[family])
+		af_ops[family]->ao_refcnt++;
+	nl_write_unlock(&info_lock);
+
+	return af_ops[family];
+}
+
+/**
+ * Give back reference to a set of operations.
+ * @arg ops		Address family operations.
+ */
+void rtnl_link_af_ops_put(struct rtnl_link_af_ops *ops)
+{
+	if (ops)
+		ops->ao_refcnt--;
+}
+
+/**
+ * Allocate and return data buffer for link address family modules
+ * @arg link		Link object
+ * @arg ops		Address family operations
+ *
+ * This function must be called by link address family modules in all
+ * cases where the API does not provide the data buffer as argument
+ * already. This typically includes set functions the module provides.
+ * Calling this function is strictly required to ensure proper allocation
+ * of the buffer upon first use. Link objects will NOT proactively
+ * allocate a data buffer for each registered link address family.
+ *
+ * @return Pointer to data buffer or NULL on error.
+ */
+void *rtnl_link_af_alloc(struct rtnl_link *link,
+			 const struct rtnl_link_af_ops *ops)
+{
+	int family;
+
+	if (!link || !ops)
+		BUG();
+
+	family = ops->ao_family;
+
+	if (!link->l_af_data[family]) {
+		if (!ops->ao_alloc)
+			BUG();
+		
+		link->l_af_data[family] = ops->ao_alloc(link);
+		if (!link->l_af_data[family])
+			return NULL;
+	}
+
+	return link->l_af_data[family];
+}
+
+/**
+ * Return data buffer for link address family modules
+ * @arg link		Link object
+ * @arg ops		Address family operations
+ *
+ * This function returns a pointer to the data buffer for the specified link
+ * address family module or NULL if the buffer was not allocated yet. This
+ * function is typically used by get functions of modules which are not
+ * interested in having the data buffer allocated if no values have been set
+ * yet.
+ *
+ * @return Pointer to data buffer or NULL on error.
+ */
+void *rtnl_link_af_data(const struct rtnl_link *link,
+			const struct rtnl_link_af_ops *ops)
+{
+	if (!link || !ops)
+		BUG();
+
+	return link->l_af_data[ops->ao_family];
+}
+
+/**
+ * Register operations for a link address family
+ * @arg ops		Address family operations
+ *
+ * This function must be called by modules implementing a specific link
+ * address family. It will make the operations implemented by the module
+ * available for everyone else.
+ *
+ * @return 0 on success or a negative error code.
+ * @return -NLE_INVAL Address family is out of range (0..AF_MAX)
+ * @return -NLE_EXIST Operations for address family already registered.
+ */
+int rtnl_link_af_register(struct rtnl_link_af_ops *ops)
+{
+	int err = 0;
+
+	if (ops->ao_family == AF_UNSPEC || ops->ao_family >= AF_MAX)
+		return -NLE_INVAL;
+
+	nl_write_lock(&info_lock);
+	if (af_ops[ops->ao_family]) {
+		err = -NLE_EXIST;
+		goto errout;
+	}
+
+	ops->ao_refcnt = 0;
+	af_ops[ops->ao_family] = ops;
+
+	NL_DBG(1, "Registered link address family operations %u\n",
+		ops->ao_family);
+
+errout:
+	nl_write_unlock(&info_lock);
+
+	return err;
+}
+
+/**
+ * Unregister operations for a link address family
+ * @arg ops		Address family operations
+ *
+ * This function must be called if a module implementing a specific link
+ * address family is unloaded or becomes unavailable. It must provide a
+ * set of operations which have previously been registered using
+ * rtnl_link_af_register().
+ *
+ * @return 0 on success or a negative error code
+ * @return -NLE_INVAL ops is NULL
+ * @return -NLE_OBJ_NOTFOUND Address family operations not registered.
+ * @return -NLE_BUSY Address family operations still in use.
+ */
+int rtnl_link_af_unregister(struct rtnl_link_af_ops *ops)
+{
+	int err = -NLE_INVAL;
+
+	if (!ops)
+		return err;
+
+	nl_write_lock(&info_lock);
+	if (!af_ops[ops->ao_family]) {
+		err = -NLE_OBJ_NOTFOUND;
+		goto errout;
+	}
+
+	if (ops->ao_refcnt > 0) {
+		err = -NLE_BUSY;
+		goto errout;
+	}
+
+	af_ops[ops->ao_family] = NULL;
+
+	NL_DBG(1, "Unregistered link address family operations %u\n",
+		ops->ao_family);
+
+errout:
+	nl_write_unlock(&info_lock);
+
+	return err;
+}
+
+/**
+ * Compare af data for a link address family
+ * @arg a		Link object a
+ * @arg b		Link object b
+ * @arg family		af data family
+ *
+ * This function will compare af_data between two links
+ * a and b of family given by arg family
+ *
+ * @return 0 if address family specific data matches or is not present
+ * or != 0 if it mismatches.
+ */
+int rtnl_link_af_data_compare(struct rtnl_link *a, struct rtnl_link *b,
+			      int family)
+{
+	struct rtnl_link_af_ops *af_ops;
+	int ret = 0;
+
+	if (!a->l_af_data[family] && !b->l_af_data[family])
+		return 0;
+
+	if (!a->l_af_data[family] || !b->l_af_data[family])
+		return ~0;
+
+	af_ops = rtnl_link_af_ops_lookup(family);
+	if (!af_ops)
+		return ~0;
+
+	if (af_ops->ao_compare == NULL) {
+		ret = ~0;
+		goto out;
+	}
+
+	ret = af_ops->ao_compare(a, b, family, ~0, 0);
+
+out:
+	rtnl_link_af_ops_put(af_ops);
+
+	return ret;
+}
+
+/** @} */
+
+/** @} */
+
diff --git a/lib/route/link/bonding.c b/lib/route/link/bonding.c
new file mode 100644
index 0000000..f4c520b
--- /dev/null
+++ b/lib/route/link/bonding.c
@@ -0,0 +1,227 @@
+/*
+ * lib/route/link/bonding.c	Bonding Link Module
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2011-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup bonding Bonding
+ *
+ * @details
+ * \b Link Type Name: "bond"
+ *
+ * @route_doc{link_bonding, Bonding Documentation}
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink-private/route/link/api.h>
+
+/**
+ * Allocate link object of type bond
+ *
+ * @return Allocated link object or NULL.
+ */
+struct rtnl_link *rtnl_link_bond_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	if (!(link = rtnl_link_alloc()))
+		return NULL;
+
+	if ((err = rtnl_link_set_type(link, "bond")) < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+
+/**
+ * Create a new kernel bonding device
+ * @arg sock		netlink socket
+ * @arg name		name of bonding device or NULL
+ * @arg opts		bonding options (currently unused)
+ *
+ * Creates a new bonding device in the kernel. If no name is
+ * provided, the kernel will automatically pick a name of the
+ * form "type%d" (e.g. bond0, vlan1, etc.)
+ *
+ * The \a opts argument is currently unused. In the future, it
+ * may be used to carry additional bonding options to be set
+ * when creating the bonding device.
+ *
+ * @note When letting the kernel assign a name, it will become
+ *       difficult to retrieve the interface afterwards because
+ *       you have to guess the name the kernel has chosen. It is
+ *       therefore not recommended to not provide a device name.
+ *
+ * @see rtnl_link_bond_enslave()
+ * @see rtnl_link_bond_release()
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_bond_add(struct nl_sock *sock, const char *name,
+		       struct rtnl_link *opts)
+{
+	struct rtnl_link *link;
+	int err;
+
+	if (!(link = rtnl_link_bond_alloc()))
+		return -NLE_NOMEM;
+
+	if (!name && opts)
+		name = rtnl_link_get_name(opts);
+
+	if (name)
+		rtnl_link_set_name(link, name);
+
+	err = rtnl_link_add(sock, link, NLM_F_CREATE);
+
+	rtnl_link_put(link);
+
+	return err;
+}
+
+/**
+ * Add a link to a bond (enslave)
+ * @arg sock		netlink socket
+ * @arg master		ifindex of bonding master
+ * @arg slave		ifindex of slave link to add to bond
+ *
+ * This function is identical to rtnl_link_bond_enslave() except that
+ * it takes interface indices instead of rtnl_link objcets.
+ *
+ * @see rtnl_link_bond_enslave()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_bond_enslave_ifindex(struct nl_sock *sock, int master,
+				   int slave)
+{
+	struct rtnl_link *link;
+	int err;
+
+	if (!(link = rtnl_link_bond_alloc()))
+		return -NLE_NOMEM;
+
+	rtnl_link_set_ifindex(link, slave);
+	rtnl_link_set_master(link, master);
+	
+	if ((err = rtnl_link_change(sock, link, link, 0)) < 0)
+		goto errout;
+
+	rtnl_link_put(link);
+
+	/*
+	 * Due to the kernel not signaling whether this opertion is
+	 * supported or not, we will retrieve the attribute to see  if the
+	 * request was successful. If the master assigned remains unchanged
+	 * we will return NLE_OPNOTSUPP to allow performing backwards
+	 * compatibility of some sort.
+	 */
+	if ((err = rtnl_link_get_kernel(sock, slave, NULL, &link)) < 0)
+		return err;
+
+	if (rtnl_link_get_master(link) != master)
+		err = -NLE_OPNOTSUPP;
+
+errout:
+	rtnl_link_put(link);
+
+	return err;
+}
+
+/**
+ * Add a link to a bond (enslave)
+ * @arg sock		netlink socket
+ * @arg master		bonding master
+ * @arg slave		slave link to add to bond
+ *
+ * Constructs a RTM_NEWLINK or RTM_SETLINK message adding the slave to
+ * the master and sends the request via the specified netlink socket.
+ *
+ * @note The feature of enslaving/releasing via netlink has only been added
+ *       recently to the kernel (Feb 2011). Also, the kernel does not signal
+ *       if the operation is not supported. Therefore this function will
+ *       verify if the master assignment has changed and will return
+ *       -NLE_OPNOTSUPP if it did not.
+ *
+ * @see rtnl_link_bond_enslave_ifindex()
+ * @see rtnl_link_bond_release()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_bond_enslave(struct nl_sock *sock, struct rtnl_link *master,
+			   struct rtnl_link *slave)
+{
+	return rtnl_link_bond_enslave_ifindex(sock,
+				rtnl_link_get_ifindex(master),
+				rtnl_link_get_ifindex(slave));
+}
+
+/**
+ * Release a link from a bond
+ * @arg sock		netlink socket
+ * @arg slave		slave link to be released
+ *
+ * This function is identical to rtnl_link_bond_release() except that
+ * it takes an interface index instead of a rtnl_link object.
+ *
+ * @see rtnl_link_bond_release()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_bond_release_ifindex(struct nl_sock *sock, int slave)
+{
+	return rtnl_link_bond_enslave_ifindex(sock, 0, slave);
+}
+
+/**
+ * Release a link from a bond
+ * @arg sock		netlink socket
+ * @arg slave		slave link to be released
+ *
+ * Constructs a RTM_NEWLINK or RTM_SETLINK message releasing the slave from
+ * its master and sends the request via the specified netlink socket.
+ *
+ * @note The feature of enslaving/releasing via netlink has only been added
+ *       recently to the kernel (Feb 2011). Also, the kernel does not signal
+ *       if the operation is not supported. Therefore this function will
+ *       verify if the master assignment has changed and will return
+ *       -NLE_OPNOTSUPP if it did not.
+ *
+ * @see rtnl_link_bond_release_ifindex()
+ * @see rtnl_link_bond_enslave()
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_bond_release(struct nl_sock *sock, struct rtnl_link *slave)
+{
+	return rtnl_link_bond_release_ifindex(sock,
+				rtnl_link_get_ifindex(slave));
+}
+
+static struct rtnl_link_info_ops bonding_info_ops = {
+	.io_name		= "bond",
+};
+
+static void __init bonding_init(void)
+{
+	rtnl_link_register_info(&bonding_info_ops);
+}
+
+static void __exit bonding_exit(void)
+{
+	rtnl_link_unregister_info(&bonding_info_ops);
+}
+
+/** @} */
diff --git a/lib/route/link/bridge.c b/lib/route/link/bridge.c
new file mode 100644
index 0000000..4ca5f6e
--- /dev/null
+++ b/lib/route/link/bridge.c
@@ -0,0 +1,526 @@
+/*
+ * lib/route/link/bridge.c	AF_BRIDGE link support
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup bridge Bridging
+ *
+ * @details
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/route/rtnl.h>
+#include <netlink/route/link/bridge.h>
+#include <netlink-private/route/link/api.h>
+#include <linux/if_bridge.h>
+
+/** @cond SKIP */
+#define BRIDGE_ATTR_PORT_STATE		(1 << 0)
+#define BRIDGE_ATTR_PRIORITY		(1 << 1)
+#define BRIDGE_ATTR_COST		(1 << 2)
+#define BRIDGE_ATTR_FLAGS		(1 << 3)
+
+#define PRIV_FLAG_NEW_ATTRS		(1 << 0)
+
+struct bridge_data
+{
+	uint8_t			b_port_state;
+	uint8_t			b_priv_flags; /* internal flags */
+	uint16_t		b_priority;
+	uint32_t		b_cost;
+	uint32_t		b_flags;
+	uint32_t		b_flags_mask;
+	uint32_t                ce_mask; /* HACK to support attr macros */
+};
+
+static struct rtnl_link_af_ops bridge_ops;
+
+#define IS_BRIDGE_LINK_ASSERT(link) \
+	if (!rtnl_link_is_bridge(link)) { \
+		APPBUG("A function was expecting a link object of type bridge."); \
+		return -NLE_OPNOTSUPP; \
+	}
+
+static inline struct bridge_data *bridge_data(struct rtnl_link *link)
+{
+	return rtnl_link_af_data(link, &bridge_ops);
+}
+
+static void *bridge_alloc(struct rtnl_link *link)
+{
+	return calloc(1, sizeof(struct bridge_data));
+}
+
+static void *bridge_clone(struct rtnl_link *link, void *data)
+{
+	struct bridge_data *bd;
+
+	if ((bd = bridge_alloc(link)))
+		memcpy(bd, data, sizeof(*bd));
+
+	return bd;
+}
+
+static void bridge_free(struct rtnl_link *link, void *data)
+{
+	free(data);
+}
+
+static struct nla_policy br_attrs_policy[IFLA_BRPORT_MAX+1] = {
+	[IFLA_BRPORT_STATE]		= { .type = NLA_U8 },
+	[IFLA_BRPORT_PRIORITY]		= { .type = NLA_U16 },
+	[IFLA_BRPORT_COST]		= { .type = NLA_U32 },
+	[IFLA_BRPORT_MODE]		= { .type = NLA_U8 },
+	[IFLA_BRPORT_GUARD]		= { .type = NLA_U8 },
+	[IFLA_BRPORT_PROTECT]		= { .type = NLA_U8 },
+	[IFLA_BRPORT_FAST_LEAVE]	= { .type = NLA_U8 },
+};
+
+static void check_flag(struct rtnl_link *link, struct nlattr *attrs[],
+		       int type, int flag)
+{
+	if (attrs[type] && nla_get_u8(attrs[type]))
+		rtnl_link_bridge_set_flags(link, flag);
+}
+
+static int bridge_parse_protinfo(struct rtnl_link *link, struct nlattr *attr,
+				 void *data)
+{
+	struct bridge_data *bd = data;
+	struct nlattr *br_attrs[IFLA_BRPORT_MAX+1];
+	int err;
+
+	/* Backwards compatibility */
+	if (!nla_is_nested(attr)) {
+		if (nla_len(attr) < 1)
+			return -NLE_RANGE;
+
+		bd->b_port_state = nla_get_u8(attr);
+		bd->ce_mask |= BRIDGE_ATTR_PORT_STATE;
+
+		return 0;
+	}
+
+	if ((err = nla_parse_nested(br_attrs, IFLA_BRPORT_MAX, attr,
+	     br_attrs_policy)) < 0)
+		return err;
+
+	bd->b_priv_flags |= PRIV_FLAG_NEW_ATTRS;
+
+	if (br_attrs[IFLA_BRPORT_STATE]) {
+		bd->b_port_state = nla_get_u8(br_attrs[IFLA_BRPORT_STATE]);
+		bd->ce_mask |= BRIDGE_ATTR_PORT_STATE;
+	}
+
+	if (br_attrs[IFLA_BRPORT_PRIORITY]) {
+		bd->b_priority = nla_get_u16(br_attrs[IFLA_BRPORT_PRIORITY]);
+		bd->ce_mask |= BRIDGE_ATTR_PRIORITY;
+	}
+
+	if (br_attrs[IFLA_BRPORT_COST]) {
+		bd->b_cost = nla_get_u32(br_attrs[IFLA_BRPORT_COST]);
+		bd->ce_mask |= BRIDGE_ATTR_COST;
+	}
+
+	check_flag(link, br_attrs, IFLA_BRPORT_MODE, RTNL_BRIDGE_HAIRPIN_MODE);
+	check_flag(link, br_attrs, IFLA_BRPORT_GUARD, RTNL_BRIDGE_BPDU_GUARD);
+	check_flag(link, br_attrs, IFLA_BRPORT_PROTECT, RTNL_BRIDGE_ROOT_BLOCK);
+	check_flag(link, br_attrs, IFLA_BRPORT_FAST_LEAVE, RTNL_BRIDGE_FAST_LEAVE);
+
+	return 0;
+}
+
+static void bridge_dump_details(struct rtnl_link *link,
+				struct nl_dump_params *p, void *data)
+{
+	struct bridge_data *bd = data;
+
+	nl_dump_line(p, "    bridge: ");
+
+	if (bd->ce_mask & BRIDGE_ATTR_PORT_STATE)
+		nl_dump(p, "port-state %u ", bd->b_port_state);
+
+	if (bd->ce_mask & BRIDGE_ATTR_PRIORITY)
+		nl_dump(p, "prio %u ", bd->b_priority);
+
+	if (bd->ce_mask & BRIDGE_ATTR_COST)
+		nl_dump(p, "cost %u ", bd->b_cost);
+
+	nl_dump(p, "\n");
+}
+
+static int bridge_compare(struct rtnl_link *_a, struct rtnl_link *_b,
+			  int family, uint32_t attrs, int flags)
+{
+	struct bridge_data *a = bridge_data(_a);
+	struct bridge_data *b = bridge_data(_b);
+	int diff = 0;
+
+#define BRIDGE_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, BRIDGE_ATTR_##ATTR, a, b, EXPR)
+	diff |= BRIDGE_DIFF(PORT_STATE,	a->b_port_state != b->b_port_state);
+	diff |= BRIDGE_DIFF(PRIORITY, a->b_priority != b->b_priority);
+	diff |= BRIDGE_DIFF(COST, a->b_cost != b->b_cost);
+
+	if (flags & LOOSE_COMPARISON)
+		diff |= BRIDGE_DIFF(FLAGS,
+				  (a->b_flags ^ b->b_flags) & b->b_flags_mask);
+	else
+		diff |= BRIDGE_DIFF(FLAGS, a->b_flags != b->b_flags);
+#undef BRIDGE_DIFF
+
+	return diff;
+}
+/** @endcond */
+
+/**
+ * Allocate link object of type bridge
+ *
+ * @return Allocated link object or NULL.
+ */
+struct rtnl_link *rtnl_link_bridge_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	if (!(link = rtnl_link_alloc()))
+		return NULL;
+
+	if ((err = rtnl_link_set_type(link, "bridge")) < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+		
+/** 
+ * Create a new kernel bridge device
+ * @arg sk              netlink socket
+ * @arg name            name of the bridge device or NULL
+ *
+ * Creates a new bridge device in the kernel. If no name is
+ * provided, the kernel will automatically pick a name of the
+ * form "type%d" (e.g. bridge0, vlan1, etc.)
+ *
+ * @return 0 on success or a negative error code
+*/
+int rtnl_link_bridge_add(struct nl_sock *sk, const char *name)
+{
+	int err;
+	struct rtnl_link *link;
+
+	if (!(link = rtnl_link_bridge_alloc()))
+		return -NLE_NOMEM;
+
+	if(name)
+		rtnl_link_set_name(link, name);
+
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	rtnl_link_put(link);
+
+	return err;
+}
+
+/**
+ * Check if a link is a bridge
+ * @arg link		Link object
+ *
+ * @return 1 if the link is a bridge, 0 otherwise.
+ */
+int rtnl_link_is_bridge(struct rtnl_link *link)
+{
+	return link->l_family == AF_BRIDGE &&
+	       link->l_af_ops == &bridge_ops;
+}
+
+/**
+ * Check if bridge has extended information
+ * @arg link		Link object of type bridge
+ *
+ * Checks if the bridge object has been constructed based on
+ * information that is only available in newer kernels. This
+ * affectes the following functions:
+ *  - rtnl_link_bridge_get_cost()
+ *  - rtnl_link_bridge_get_priority()
+ *  - rtnl_link_bridge_get_flags()
+ *
+ * @return 1 if extended information is available, otherwise 0 is returned.
+ */
+int rtnl_link_bridge_has_ext_info(struct rtnl_link *link)
+{
+	struct bridge_data *bd;
+
+	if (!rtnl_link_is_bridge(link))
+		return 0;
+
+	bd = bridge_data(link);
+	return !!(bd->b_priv_flags & PRIV_FLAG_NEW_ATTRS);
+}
+
+/**
+ * Set Spanning Tree Protocol (STP) port state
+ * @arg link		Link object of type bridge
+ * @arg state		New STP port state
+ *
+ * The value of state must be one of the following:
+ *   - BR_STATE_DISABLED
+ *   - BR_STATE_LISTENING
+ *   - BR_STATE_LEARNING
+ *   - BR_STATE_FORWARDING
+ *   - BR_STATE_BLOCKING
+ *
+ * @see rtnl_link_bridge_get_port_state()
+ *
+ * @return 0 on success or a negative error code.
+ * @retval -NLE_OPNOTSUPP Link is not a bridge
+ * @retval -NLE_INVAL Invalid state value (0..BR_STATE_BLOCKING)
+ */
+int rtnl_link_bridge_set_port_state(struct rtnl_link *link, uint8_t state)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	if (state > BR_STATE_BLOCKING)
+		return -NLE_INVAL;
+
+	bd->b_port_state = state;
+	bd->ce_mask |= BRIDGE_ATTR_PORT_STATE;
+
+	return 0;
+}
+
+/**
+ * Get Spanning Tree Protocol (STP) port state
+ * @arg link		Link object of type bridge
+ *
+ * @see rtnl_link_bridge_set_port_state()
+ *
+ * @return The STP port state or a negative error code.
+ * @retval -NLE_OPNOTSUPP Link is not a bridge
+ */
+int rtnl_link_bridge_get_port_state(struct rtnl_link *link)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	return bd->b_port_state;
+}
+
+/**
+ * Set priority
+ * @arg link		Link object of type bridge
+ * @arg prio		Bridge priority
+ *
+ * @see rtnl_link_bridge_get_priority()
+ *
+ * @return 0 on success or a negative error code.
+ * @retval -NLE_OPNOTSUPP Link is not a bridge
+ */
+int rtnl_link_bridge_set_priority(struct rtnl_link *link, uint16_t prio)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	bd->b_priority = prio;
+	bd->ce_mask |= BRIDGE_ATTR_PRIORITY;
+
+	return 0;
+}
+
+/**
+ * Get priority
+ * @arg link		Link object of type bridge
+ *
+ * @see rtnl_link_bridge_set_priority()
+ *
+ * @return 0 on success or a negative error code.
+ * @retval -NLE_OPNOTSUPP Link is not a bridge
+ */
+int rtnl_link_bridge_get_priority(struct rtnl_link *link)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	return bd->b_priority;
+}
+
+/**
+ * Set Spanning Tree Protocol (STP) path cost
+ * @arg link		Link object of type bridge
+ * @arg cost		New STP path cost value
+ *
+ * @see rtnl_link_bridge_get_cost()
+ *
+ * @return The bridge priority or a negative error code.
+ * @retval -NLE_OPNOTSUPP Link is not a bridge
+ */
+int rtnl_link_bridge_set_cost(struct rtnl_link *link, uint32_t cost)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	bd->b_cost = cost;
+	bd->ce_mask |= BRIDGE_ATTR_COST;
+
+	return 0;
+}
+
+/**
+ * Get Spanning Tree Protocol (STP) path cost
+ * @arg link		Link object of type bridge
+ * @arg cost		Pointer to store STP cost value
+ *
+ * @see rtnl_link_bridge_set_cost()
+ *
+ * @return 0 on success or a negative error code.
+ * @retval -NLE_OPNOTSUPP Link is not a bridge
+ * @retval -NLE_INVAL `cost` is not a valid pointer
+ */
+int rtnl_link_bridge_get_cost(struct rtnl_link *link, uint32_t *cost)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	if (!cost)
+		return -NLE_INVAL;
+
+	*cost = bd->b_cost;
+
+	return 0;
+}
+
+/**
+ * Unset flags
+ * @arg link		Link object of type bridge
+ * @arg flags		Bridging flags to unset
+ *
+ * @see rtnl_link_bridge_set_flags()
+ * @see rtnl_link_bridge_get_flags()
+ *
+ * @return 0 on success or a negative error code.
+ * @retval -NLE_OPNOTSUPP Link is not a bridge
+ */
+int rtnl_link_bridge_unset_flags(struct rtnl_link *link, unsigned int flags)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	bd->b_flags_mask |= flags;
+	bd->b_flags &= ~flags;
+	bd->ce_mask |= BRIDGE_ATTR_FLAGS;
+
+	return 0;
+}
+
+/**
+ * Set flags
+ * @arg link		Link object of type bridge
+ * @arg flags		Bridging flags to set
+ *
+ * Valid flags are:
+ *   - RTNL_BRIDGE_HAIRPIN_MODE
+ *   - RTNL_BRIDGE_BPDU_GUARD
+ *   - RTNL_BRIDGE_ROOT_BLOCK
+ *   - RTNL_BRIDGE_FAST_LEAVE
+ *
+ * @see rtnl_link_bridge_unset_flags()
+ * @see rtnl_link_bridge_get_flags()
+ *
+ * @return 0 on success or a negative error code.
+ * @retval -NLE_OPNOTSUPP Link is not a bridge
+ */
+int rtnl_link_bridge_set_flags(struct rtnl_link *link, unsigned int flags)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	bd->b_flags_mask |= flags;
+	bd->b_flags |= flags;
+	bd->ce_mask |= BRIDGE_ATTR_FLAGS;
+
+	return 0;
+}
+
+/**
+ * Get flags
+ * @arg link		Link object of type bridge
+ *
+ * @see rtnl_link_bridge_set_flags()
+ * @see rtnl_link_bridge_unset_flags()
+ *
+ * @return Flags or a negative error code.
+ * @retval -NLE_OPNOTSUPP Link is not a bridge
+ */
+int rtnl_link_bridge_get_flags(struct rtnl_link *link)
+{
+	struct bridge_data *bd = bridge_data(link);
+
+	IS_BRIDGE_LINK_ASSERT(link);
+
+	return bd->b_flags;
+}
+
+static const struct trans_tbl bridge_flags[] = {
+	__ADD(RTNL_BRIDGE_HAIRPIN_MODE, hairpin_mode)
+	__ADD(RTNL_BRIDGE_BPDU_GUARD, 	bpdu_guard)
+	__ADD(RTNL_BRIDGE_ROOT_BLOCK,	root_block)
+	__ADD(RTNL_BRIDGE_FAST_LEAVE,	fast_leave)
+};
+
+/**
+ * @name Flag Translation
+ * @{
+ */
+
+char *rtnl_link_bridge_flags2str(int flags, char *buf, size_t len)
+{
+	return __flags2str(flags, buf, len, bridge_flags, ARRAY_SIZE(bridge_flags));
+}
+
+int rtnl_link_bridge_str2flags(const char *name)
+{
+	return __str2flags(name, bridge_flags, ARRAY_SIZE(bridge_flags));
+}
+
+/** @} */
+
+static struct rtnl_link_af_ops bridge_ops = {
+	.ao_family			= AF_BRIDGE,
+	.ao_alloc			= &bridge_alloc,
+	.ao_clone			= &bridge_clone,
+	.ao_free			= &bridge_free,
+	.ao_parse_protinfo		= &bridge_parse_protinfo,
+	.ao_dump[NL_DUMP_DETAILS]	= &bridge_dump_details,
+	.ao_compare			= &bridge_compare,
+};
+
+static void __init bridge_init(void)
+{
+	rtnl_link_af_register(&bridge_ops);
+}
+
+static void __exit bridge_exit(void)
+{
+	rtnl_link_af_unregister(&bridge_ops);
+}
+
+/** @} */
diff --git a/lib/route/link/can.c b/lib/route/link/can.c
new file mode 100644
index 0000000..60da42d
--- /dev/null
+++ b/lib/route/link/can.c
@@ -0,0 +1,784 @@
+/*
+ * lib/route/link/can.c		CAN Link Info
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2012 Benedikt Spranger <b.spranger@linutronix.de>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup can CAN
+ * Controller Area Network link module
+ *
+ * @details
+ * \b Link Type Name: "can"
+ *
+ * @route_doc{link_can, CAN Documentation}
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink/object.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+#include <netlink/route/link/can.h>
+
+#include <linux/can/netlink.h>
+
+/** @cond SKIP */
+#define CAN_HAS_BITTIMING		(1<<0)
+#define CAN_HAS_BITTIMING_CONST		(1<<1)
+#define CAN_HAS_CLOCK			(1<<2)
+#define CAN_HAS_STATE			(1<<3)
+#define CAN_HAS_CTRLMODE		(1<<4)
+#define CAN_HAS_RESTART_MS		(1<<5)
+#define CAN_HAS_RESTART			(1<<6)
+#define CAN_HAS_BERR_COUNTER		(1<<7)
+
+struct can_info {
+	uint32_t			ci_state;
+	uint32_t			ci_restart;
+	uint32_t			ci_restart_ms;
+	struct can_ctrlmode		ci_ctrlmode;
+	struct can_bittiming		ci_bittiming;
+	struct can_bittiming_const	ci_bittiming_const;
+	struct can_clock		ci_clock;
+	struct can_berr_counter		ci_berr_counter;
+	uint32_t			ci_mask;
+};
+
+/** @endcond */
+
+static struct nla_policy can_policy[IFLA_CAN_MAX + 1] = {
+	[IFLA_CAN_STATE]	= { .type = NLA_U32 },
+	[IFLA_CAN_CTRLMODE]	= { .minlen = sizeof(struct can_ctrlmode) },
+	[IFLA_CAN_RESTART_MS]	= { .type = NLA_U32 },
+	[IFLA_CAN_RESTART]	= { .type = NLA_U32 },
+	[IFLA_CAN_BITTIMING]	= { .minlen = sizeof(struct can_bittiming) },
+	[IFLA_CAN_BITTIMING_CONST]
+				= { .minlen = sizeof(struct can_bittiming_const) },
+	[IFLA_CAN_CLOCK]	= { .minlen = sizeof(struct can_clock) },
+	[IFLA_CAN_BERR_COUNTER]	= { .minlen = sizeof(struct can_berr_counter) },
+};
+
+static int can_alloc(struct rtnl_link *link)
+{
+	struct can_info *ci;
+
+	ci = calloc(1, sizeof(*ci));
+	if (!ci)
+		return -NLE_NOMEM;
+
+	link->l_info = ci;
+
+	return 0;
+}
+
+static int can_parse(struct rtnl_link *link, struct nlattr *data,
+		     struct nlattr *xstats)
+{
+	struct nlattr *tb[IFLA_CAN_MAX+1];
+	struct can_info *ci;
+	int err;
+
+	NL_DBG(3, "Parsing CAN link info");
+
+	if ((err = nla_parse_nested(tb, IFLA_CAN_MAX, data, can_policy)) < 0)
+		goto errout;
+
+	if ((err = can_alloc(link)) < 0)
+		goto errout;
+
+	ci = link->l_info;
+
+	if (tb[IFLA_CAN_STATE]) {
+		ci->ci_state = nla_get_u32(tb[IFLA_CAN_STATE]);
+		ci->ci_mask |= CAN_HAS_STATE;
+	}
+
+	if (tb[IFLA_CAN_RESTART]) {
+		ci->ci_restart = nla_get_u32(tb[IFLA_CAN_RESTART]);
+		ci->ci_mask |= CAN_HAS_RESTART;
+	}
+
+	if (tb[IFLA_CAN_RESTART_MS]) {
+		ci->ci_restart_ms = nla_get_u32(tb[IFLA_CAN_RESTART_MS]);
+		ci->ci_mask |= CAN_HAS_RESTART_MS;
+	}
+
+	if (tb[IFLA_CAN_CTRLMODE]) {
+		nla_memcpy(&ci->ci_ctrlmode, tb[IFLA_CAN_CTRLMODE],
+			   sizeof(ci->ci_ctrlmode));
+		ci->ci_mask |= CAN_HAS_CTRLMODE;
+	}
+
+	if (tb[IFLA_CAN_BITTIMING]) {
+		nla_memcpy(&ci->ci_bittiming, tb[IFLA_CAN_BITTIMING],
+			   sizeof(ci->ci_bittiming));
+		ci->ci_mask |= CAN_HAS_BITTIMING;
+	}
+
+	if (tb[IFLA_CAN_BITTIMING_CONST]) {
+		nla_memcpy(&ci->ci_bittiming_const,
+			   tb[IFLA_CAN_BITTIMING_CONST],
+			   sizeof(ci->ci_bittiming_const));
+		ci->ci_mask |= CAN_HAS_BITTIMING_CONST;
+	}
+
+	if (tb[IFLA_CAN_CLOCK]) {
+		nla_memcpy(&ci->ci_clock, tb[IFLA_CAN_CLOCK],
+			   sizeof(ci->ci_clock));
+		ci->ci_mask |= CAN_HAS_CLOCK;
+	}
+
+	if (tb[IFLA_CAN_BERR_COUNTER]) {
+		nla_memcpy(&ci->ci_berr_counter, tb[IFLA_CAN_BERR_COUNTER],
+			   sizeof(ci->ci_berr_counter));
+		ci->ci_mask |= CAN_HAS_BERR_COUNTER;
+	}
+
+	err = 0;
+errout:
+	return err;
+}
+
+static void can_free(struct rtnl_link *link)
+{
+	struct can_info *ci = link->l_info;
+
+	free(ci);
+	link->l_info = NULL;
+}
+
+static char *print_can_state (uint32_t state)
+{
+	char *text;
+
+	switch (state)
+	{
+	case CAN_STATE_ERROR_ACTIVE:
+		text = "error active";
+		break;
+	case CAN_STATE_ERROR_WARNING:
+		text = "error warning";
+		break;
+	case CAN_STATE_ERROR_PASSIVE:
+		text = "error passive";
+		break;
+	case CAN_STATE_BUS_OFF:
+		text = "bus off";
+		break;
+	case CAN_STATE_STOPPED:
+		text = "stopped";
+		break;
+	case CAN_STATE_SLEEPING:
+		text = "sleeping";
+		break;
+	default:
+		text = "unknown state";
+	}
+
+	return text;
+}
+
+static void can_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	struct can_info *ci = link->l_info;
+	char buf [64];
+
+	rtnl_link_can_ctrlmode2str(ci->ci_ctrlmode.flags, buf, sizeof(buf));
+	nl_dump(p, "bitrate %d %s <%s>",
+		ci->ci_bittiming.bitrate, print_can_state(ci->ci_state), buf);
+}
+
+static void can_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	struct can_info *ci = link->l_info;
+	char buf [64];
+
+	rtnl_link_can_ctrlmode2str(ci->ci_ctrlmode.flags, buf, sizeof(buf));
+	nl_dump(p, "    bitrate %d %s <%s>",
+		ci->ci_bittiming.bitrate, print_can_state(ci->ci_state), buf);
+
+	if (ci->ci_mask & CAN_HAS_RESTART) {
+		if (ci->ci_restart)
+			nl_dump_line(p,"    restarting\n");
+	}
+
+	if (ci->ci_mask & CAN_HAS_RESTART_MS) {
+		nl_dump_line(p,"    restart interval %d ms\n",
+			     ci->ci_restart_ms);
+	}
+
+	if (ci->ci_mask & CAN_HAS_BITTIMING) {
+		nl_dump_line(p,"    sample point %f %%\n",
+			     ((float) ci->ci_bittiming.sample_point)/10);
+		nl_dump_line(p,"    time quanta %d ns\n",
+			     ci->ci_bittiming.tq);
+		nl_dump_line(p,"    propagation segment %d tq\n",
+			     ci->ci_bittiming.prop_seg);
+		nl_dump_line(p,"    phase buffer segment1 %d tq\n",
+			     ci->ci_bittiming.phase_seg1);
+		nl_dump_line(p,"    phase buffer segment2 %d tq\n",
+			     ci->ci_bittiming.phase_seg2);
+		nl_dump_line(p,"    synchronisation jump width %d tq\n",
+			     ci->ci_bittiming.sjw);
+		nl_dump_line(p,"    bitrate prescaler %d\n",
+			     ci->ci_bittiming.brp);
+	}
+
+	if (ci->ci_mask & CAN_HAS_BITTIMING_CONST) {
+		nl_dump_line(p,"    minimum tsig1 %d tq\n",
+			     ci->ci_bittiming_const.tseg1_min);
+		nl_dump_line(p,"    maximum tsig1 %d tq\n",
+			     ci->ci_bittiming_const.tseg1_max);
+		nl_dump_line(p,"    minimum tsig2 %d tq\n",
+			     ci->ci_bittiming_const.tseg2_min);
+		nl_dump_line(p,"    maximum tsig2 %d tq\n",
+			     ci->ci_bittiming_const.tseg2_max);
+		nl_dump_line(p,"    maximum sjw %d tq\n",
+			     ci->ci_bittiming_const.sjw_max);
+		nl_dump_line(p,"    minimum brp %d\n",
+			     ci->ci_bittiming_const.brp_min);
+		nl_dump_line(p,"    maximum brp %d\n",
+			     ci->ci_bittiming_const.brp_max);
+		nl_dump_line(p,"    brp increment %d\n",
+			     ci->ci_bittiming_const.brp_inc);
+	}
+
+	if (ci->ci_mask & CAN_HAS_CLOCK) {
+		nl_dump_line(p,"    base freq %d Hz\n", ci->ci_clock);
+
+	}
+
+	if (ci->ci_mask & CAN_HAS_BERR_COUNTER) {
+		nl_dump_line(p,"    bus error RX %d\n",
+			     ci->ci_berr_counter.rxerr);
+		nl_dump_line(p,"    bus error TX %d\n",
+			     ci->ci_berr_counter.txerr);
+	}
+
+	return;
+}
+
+static int can_clone(struct rtnl_link *dst, struct rtnl_link *src)
+{
+	struct can_info *cdst, *csrc = src->l_info;
+	int ret;
+
+	dst->l_info = NULL;
+	ret = rtnl_link_set_type(dst, "can");
+	if (ret < 0)
+		return ret;
+
+	cdst = malloc(sizeof(*cdst));
+	if (!cdst)
+		return -NLE_NOMEM;
+
+	*cdst = *csrc;
+	dst->l_info = cdst;
+
+	return 0;
+}
+
+static int can_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
+{
+	struct can_info *ci = link->l_info;
+	struct nlattr *data;
+
+	data = nla_nest_start(msg, IFLA_INFO_DATA);
+	if (!data)
+		return -NLE_MSGSIZE;
+
+	if (ci->ci_mask & CAN_HAS_RESTART)
+		NLA_PUT_U32(msg, CAN_HAS_RESTART, ci->ci_restart);
+
+	if (ci->ci_mask & CAN_HAS_RESTART_MS)
+		NLA_PUT_U32(msg, CAN_HAS_RESTART_MS, ci->ci_restart_ms);
+
+	if (ci->ci_mask & CAN_HAS_CTRLMODE)
+		NLA_PUT(msg, CAN_HAS_CTRLMODE, sizeof(ci->ci_ctrlmode),
+			&ci->ci_ctrlmode);
+
+	if (ci->ci_mask & CAN_HAS_BITTIMING)
+		NLA_PUT(msg, CAN_HAS_BITTIMING, sizeof(ci->ci_bittiming),
+			&ci->ci_bittiming);
+
+	if (ci->ci_mask & CAN_HAS_BITTIMING_CONST)
+		NLA_PUT(msg, CAN_HAS_BITTIMING_CONST,
+			sizeof(ci->ci_bittiming_const),
+			&ci->ci_bittiming_const);
+
+	if (ci->ci_mask & CAN_HAS_CLOCK)
+		NLA_PUT(msg, CAN_HAS_CLOCK, sizeof(ci->ci_clock),
+			&ci->ci_clock);
+
+	nla_nest_end(msg, data);
+
+nla_put_failure:
+
+	return 0;
+}
+
+static struct rtnl_link_info_ops can_info_ops = {
+	.io_name		= "can",
+	.io_alloc		= can_alloc,
+	.io_parse		= can_parse,
+	.io_dump = {
+	    [NL_DUMP_LINE]	= can_dump_line,
+	    [NL_DUMP_DETAILS]	= can_dump_details,
+	},
+	.io_clone		= can_clone,
+	.io_put_attrs		= can_put_attrs,
+	.io_free		= can_free,
+};
+
+/** @cond SKIP */
+#define IS_CAN_LINK_ASSERT(link) \
+	if ((link)->l_info_ops != &can_info_ops) { \
+		APPBUG("Link is not a CAN link. set type \"can\" first."); \
+		return -NLE_OPNOTSUPP; \
+	}
+/** @endcond */
+
+/**
+ * @name CAN Object
+ * @{
+ */
+
+/**
+ * Check if link is a CAN link
+ * @arg link		Link object
+ *
+ * @return True if link is a CAN link, otherwise false is returned.
+ */
+int rtnl_link_is_can(struct rtnl_link *link)
+{
+	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "can");
+}
+
+/**
+ * Restart CAN device
+ * @arg link            Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_restart(struct rtnl_link *link)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+
+	ci->ci_restart = 1;
+	ci->ci_restart |= CAN_HAS_RESTART;
+
+	return 0;
+}
+
+/**
+ * Get CAN base frequency
+ * @arg link            Link object
+ * @arg freq		frequency in Hz
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_freq(struct rtnl_link *link, uint32_t *freq)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+	if (!freq)
+		return -NLE_INVAL;
+
+	if (ci->ci_mask & CAN_HAS_CLOCK)
+		*freq = ci->ci_clock.freq;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Get CAN state
+ * @arg link            Link object
+ * @arg state		CAN bus state
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_state(struct rtnl_link *link, uint32_t *state)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+	if (!state)
+		return -NLE_INVAL;
+
+	*state = ci->ci_state;
+
+	return 0;
+}
+
+/**
+ * Get CAN RX bus error count
+ * @arg link            Link object
+ *
+ * @return RX bus error count on success or a negative error code
+ */
+int rtnl_link_can_berr_rx(struct rtnl_link *link)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+
+	if (ci->ci_mask & CAN_HAS_BERR_COUNTER)
+		return ci->ci_berr_counter.rxerr;
+	else
+		return -NLE_AGAIN;
+}
+
+/**
+ * Get CAN TX bus error count
+ * @arg link            Link object
+ *
+ * @return TX bus error count on success or a negative error code
+ */
+int rtnl_link_can_berr_tx(struct rtnl_link *link)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+
+	if (ci->ci_mask & CAN_HAS_BERR_COUNTER)
+		return ci->ci_berr_counter.txerr;
+	else
+		return -NLE_AGAIN;
+}
+
+/**
+ * Get CAN bus error count
+ * @arg link            Link object
+ * @arg berr		Bus error count
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_berr(struct rtnl_link *link, struct can_berr_counter *berr)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+	if (!berr)
+		return -NLE_INVAL;
+
+	if (ci->ci_mask & CAN_HAS_BERR_COUNTER)
+		*berr = ci->ci_berr_counter;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Get CAN harware-dependent bit-timing constant
+ * @arg link            Link object
+ * @arg bt_const	Bit-timing constant
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_get_bt_const(struct rtnl_link *link,
+			       struct can_bittiming_const *bt_const)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+	if (!bt_const)
+		return -NLE_INVAL;
+
+	if (ci->ci_mask & CAN_HAS_BITTIMING_CONST)
+		*bt_const = ci->ci_bittiming_const;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Get CAN device bit-timing
+ * @arg link            Link object
+ * @arg bit_timing	CAN bit-timing
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_get_bittiming(struct rtnl_link *link,
+				struct can_bittiming *bit_timing)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+	if (!bit_timing)
+		return -NLE_INVAL;
+
+	if (ci->ci_mask & CAN_HAS_BITTIMING)
+		*bit_timing = ci->ci_bittiming;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Set CAN device bit-timing
+ * @arg link            Link object
+ * @arg bit_timing	CAN bit-timing
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_set_bittiming(struct rtnl_link *link,
+				struct can_bittiming *bit_timing)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+	if (!bit_timing)
+		return -NLE_INVAL;
+
+	ci->ci_bittiming = *bit_timing;
+	ci->ci_mask |= CAN_HAS_BITTIMING;
+
+	return 0;
+}
+
+/**
+ * Get CAN device bit-timing
+ * @arg link            Link object
+ * @arg bitrate		CAN bitrate
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_get_bitrate(struct rtnl_link *link, uint32_t *bitrate)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+	if (!bitrate)
+		return -NLE_INVAL;
+
+	if (ci->ci_mask & CAN_HAS_BITTIMING)
+		*bitrate = ci->ci_bittiming.bitrate;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Set CAN device bit-rate
+ * @arg link            Link object
+ * @arg bitrate		CAN bitrate
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_set_bitrate(struct rtnl_link *link, uint32_t bitrate)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+
+	ci->ci_bittiming.bitrate = bitrate;
+	ci->ci_mask |= CAN_HAS_BITTIMING;
+
+	return 0;
+}
+
+/**
+ * Get CAN device sample point
+ * @arg link            Link object
+ * @arg sp		CAN sample point
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_get_sample_point(struct rtnl_link *link, uint32_t *sp)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+	if (!sp)
+		return -NLE_INVAL;
+
+	if (ci->ci_mask & CAN_HAS_BITTIMING)
+		*sp = ci->ci_bittiming.sample_point;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Set CAN device sample point
+ * @arg link            Link object
+ * @arg sp		CAN sample point
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_set_sample_point(struct rtnl_link *link, uint32_t sp)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+
+	ci->ci_bittiming.sample_point = sp;
+	ci->ci_mask |= CAN_HAS_BITTIMING;
+
+	return 0;
+}
+
+/**
+ * Get CAN device restart intervall
+ * @arg link            Link object
+ * @arg interval	Restart intervall in ms
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_get_restart_ms(struct rtnl_link *link, uint32_t *interval)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+	if (!interval)
+		return -NLE_INVAL;
+
+	if (ci->ci_mask & CAN_HAS_RESTART_MS)
+		*interval = ci->ci_restart_ms;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Set CAN device restart intervall
+ * @arg link            Link object
+ * @arg interval	Restart intervall in ms
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_set_restart_ms(struct rtnl_link *link, uint32_t interval)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+
+	ci->ci_restart_ms = interval;
+	ci->ci_mask |= CAN_HAS_RESTART_MS;
+
+	return 0;
+}
+
+/**
+ * Get CAN control mode
+ * @arg link            Link object
+ * @arg ctrlmode	CAN control mode
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_get_ctrlmode(struct rtnl_link *link, uint32_t *ctrlmode)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+	if (!ctrlmode)
+		return -NLE_INVAL;
+
+	if (ci->ci_mask & CAN_HAS_CTRLMODE)
+		*ctrlmode = ci->ci_ctrlmode.flags;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Set a CAN Control Mode
+ * @arg link            Link object
+ * @arg ctrlmode	CAN control mode
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_set_ctrlmode(struct rtnl_link *link, uint32_t ctrlmode)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+
+	ci->ci_ctrlmode.flags |= ctrlmode;
+	ci->ci_ctrlmode.mask |= ctrlmode;
+	ci->ci_mask |= CAN_HAS_CTRLMODE;
+
+	return 0;
+}
+
+/**
+ * Unset a CAN Control Mode
+ * @arg link            Link object
+ * @arg ctrlmode	CAN control mode
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_can_unset_ctrlmode(struct rtnl_link *link, uint32_t ctrlmode)
+{
+	struct can_info *ci = link->l_info;
+
+	IS_CAN_LINK_ASSERT(link);
+
+	ci->ci_ctrlmode.flags &= ~ctrlmode;
+	ci->ci_ctrlmode.mask |= ctrlmode;
+	ci->ci_mask |= CAN_HAS_CTRLMODE;
+
+	return 0;
+}
+
+/** @} */
+
+/**
+ * @name Control Mode Translation
+ * @{
+ */
+
+static const struct trans_tbl can_ctrlmode[] = {
+	__ADD(CAN_CTRLMODE_LOOPBACK, loopback)
+	__ADD(CAN_CTRLMODE_LISTENONLY, listen-only)
+	__ADD(CAN_CTRLMODE_3_SAMPLES, triple-sampling)
+	__ADD(CAN_CTRLMODE_ONE_SHOT, one-shot)
+	__ADD(CAN_CTRLMODE_BERR_REPORTING, berr-reporting)
+};
+
+char *rtnl_link_can_ctrlmode2str(int ctrlmode, char *buf, size_t len)
+{
+	return __flags2str(ctrlmode, buf, len, can_ctrlmode,
+			   ARRAY_SIZE(can_ctrlmode));
+}
+
+int rtnl_link_can_str2ctrlmode(const char *name)
+{
+	return __str2flags(name, can_ctrlmode, ARRAY_SIZE(can_ctrlmode));
+}
+
+/** @} */
+
+static void __init can_init(void)
+{
+	rtnl_link_register_info(&can_info_ops);
+}
+
+static void __exit can_exit(void)
+{
+	rtnl_link_unregister_info(&can_info_ops);
+}
+
+/** @} */
diff --git a/lib/route/link/dummy.c b/lib/route/link/dummy.c
new file mode 100644
index 0000000..1fd9f5a
--- /dev/null
+++ b/lib/route/link/dummy.c
@@ -0,0 +1,40 @@
+/*
+ * lib/route/link/dummy.c	Dummy Interfaces
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup dummy Dummy
+ *
+ * @details
+ * \b Link Type Name: "dummy"
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink-private/route/link/api.h>
+
+static struct rtnl_link_info_ops dummy_info_ops = {
+	.io_name		= "dummy",
+};
+
+static void __init dummy_init(void)
+{
+	rtnl_link_register_info(&dummy_info_ops);
+}
+
+static void __exit dummy_exit(void)
+{
+	rtnl_link_unregister_info(&dummy_info_ops);
+}
+
+/** @} */
diff --git a/lib/route/link/inet.c b/lib/route/link/inet.c
new file mode 100644
index 0000000..e94342f
--- /dev/null
+++ b/lib/route/link/inet.c
@@ -0,0 +1,295 @@
+/*
+ * lib/route/link/inet.c	AF_INET link operations
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup link_API
+ * @defgroup link_inet IPv4 Link Module
+ * @brief Implementation of IPv4 specific link attributes
+ *
+ *
+ *
+ * @par Example: Reading the value of IPV4_DEVCONF_FORWARDING
+ * @code
+ * struct nl_cache *cache;
+ * struct rtnl_link *link;
+ * uint32_t value;
+ *
+ * // Allocate a link cache
+ * rtnl_link_alloc_cache(sock, AF_UNSPEC, &cache);
+ *
+ * // Search for the link we wish to see the value from
+ * link = rtnl_link_get_by_name(cache, "eth0");
+ *
+ * // Read the value of the setting IPV4_DEVCONF_FORWARDING
+ * if (rtnl_link_inet_get_conf(link, IPV4_DEVCONF_FORWARDING, &value) < 0)
+ *         // Error: Unable to read config setting
+ *
+ * printf("forwarding is %s\n", value ? "enabled" : "disabled");
+ * @endcode
+ *
+ * @par Example: Changing the value of IPV4_DEVCONF_FOWARDING
+ * @code
+ * //
+ * // ... Continueing from the previous example ...
+ * //
+ *
+ * struct rtnl_link *new;
+ *
+ * // Allocate a new link to store the changes we wish to make.
+ * new = rtnl_link_alloc();
+ *
+ * // Set IPV4_DEVCONF_FORWARDING to '1'
+ * rtnl_link_inet_set_conf(new, IPV4_DEVCONF_FORWARDING, 1);
+ *
+ * // Send the change request to the kernel.
+ * rtnl_link_change(sock, link, new, 0);
+ * @endcode
+ *
+ * @{
+ */
+
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+
+/** @cond SKIP */
+struct inet_data
+{
+	uint8_t			i_confset[IPV4_DEVCONF_MAX];
+	uint32_t		i_conf[IPV4_DEVCONF_MAX];
+};
+/** @endcond */
+
+static void *inet_alloc(struct rtnl_link *link)
+{
+	return calloc(1, sizeof(struct inet_data));
+}
+
+static void *inet_clone(struct rtnl_link *link, void *data)
+{
+	struct inet_data *id;
+
+	if ((id = inet_alloc(link)))
+		memcpy(id, data, sizeof(*id));
+
+	return id;
+}
+
+static void inet_free(struct rtnl_link *link, void *data)
+{
+	free(data);
+}
+
+static struct nla_policy inet_policy[IFLA_INET6_MAX+1] = {
+	[IFLA_INET_CONF]	= { .minlen = 4 },
+};
+
+static int inet_parse_af(struct rtnl_link *link, struct nlattr *attr, void *data)
+{
+	struct inet_data *id = data;
+	struct nlattr *tb[IFLA_INET_MAX+1];
+	int err;
+
+	err = nla_parse_nested(tb, IFLA_INET_MAX, attr, inet_policy);
+	if (err < 0)
+		return err;
+	if (tb[IFLA_INET_CONF] && nla_len(tb[IFLA_INET_CONF]) % 4)
+		return -EINVAL;
+
+	if (tb[IFLA_INET_CONF]) {
+		int i;
+		int len = min_t(int, IPV4_DEVCONF_MAX, nla_len(tb[IFLA_INET_CONF]) / 4);
+
+		for (i = 0; i < len; i++)
+			id->i_confset[i] = 1;
+		nla_memcpy(&id->i_conf, tb[IFLA_INET_CONF], sizeof(id->i_conf));
+	}
+
+	return 0;
+}
+
+static int inet_fill_af(struct rtnl_link *link, struct nl_msg *msg, void *data)
+{
+	struct inet_data *id = data;
+	struct nlattr *nla;
+	int i;
+
+	if (!(nla = nla_nest_start(msg, IFLA_INET_CONF)))
+		return -NLE_MSGSIZE;
+
+	for (i = 0; i < IPV4_DEVCONF_MAX; i++)
+		if (id->i_confset[i])
+			NLA_PUT_U32(msg, i+1, id->i_conf[i]);
+
+	nla_nest_end(msg, nla);
+
+	return 0;
+
+nla_put_failure:
+	return -NLE_MSGSIZE;
+}
+
+static const struct trans_tbl inet_devconf[] = {
+	__ADD(IPV4_DEVCONF_FORWARDING, forwarding)
+	__ADD(IPV4_DEVCONF_MC_FORWARDING, mc_forwarding)
+	__ADD(IPV4_DEVCONF_PROXY_ARP, proxy_arp)
+	__ADD(IPV4_DEVCONF_ACCEPT_REDIRECTS, accept_redirects)
+	__ADD(IPV4_DEVCONF_SECURE_REDIRECTS, secure_redirects)
+	__ADD(IPV4_DEVCONF_SEND_REDIRECTS, send_redirects)
+	__ADD(IPV4_DEVCONF_SHARED_MEDIA, shared_media)
+	__ADD(IPV4_DEVCONF_RP_FILTER, rp_filter)
+	__ADD(IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE, accept_source_route)
+	__ADD(IPV4_DEVCONF_BOOTP_RELAY, bootp_relay)
+	__ADD(IPV4_DEVCONF_LOG_MARTIANS, log_martians)
+	__ADD(IPV4_DEVCONF_TAG, tag)
+	__ADD(IPV4_DEVCONF_ARPFILTER, arpfilter)
+	__ADD(IPV4_DEVCONF_MEDIUM_ID, medium_id)
+	__ADD(IPV4_DEVCONF_NOXFRM, noxfrm)
+	__ADD(IPV4_DEVCONF_NOPOLICY, nopolicy)
+	__ADD(IPV4_DEVCONF_FORCE_IGMP_VERSION, force_igmp_version)
+	__ADD(IPV4_DEVCONF_ARP_ANNOUNCE, arp_announce)
+	__ADD(IPV4_DEVCONF_ARP_IGNORE, arp_ignore)
+	__ADD(IPV4_DEVCONF_PROMOTE_SECONDARIES, promote_secondaries)
+	__ADD(IPV4_DEVCONF_ARP_ACCEPT, arp_accept)
+	__ADD(IPV4_DEVCONF_ARP_NOTIFY, arp_notify)
+	__ADD(IPV4_DEVCONF_ACCEPT_LOCAL, accept_local)
+	__ADD(IPV4_DEVCONF_SRC_VMARK, src_vmark)
+	__ADD(IPV4_DEVCONF_PROXY_ARP_PVLAN, proxy_arp_pvlan)
+	__ADD(IPV4_DEVCONF_ROUTE_LOCALNET, route_localnet)
+	__ADD(IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL, igmpv2_unsolicited_report_interval)
+	__ADD(IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL, igmpv3_unsolicited_report_interval)
+};
+
+const char *rtnl_link_inet_devconf2str(int type, char *buf, size_t len)
+{
+	return __type2str(type, buf, len, inet_devconf,
+			  ARRAY_SIZE(inet_devconf));
+}
+
+int rtnl_link_inet_str2devconf(const char *name)
+{
+	return __str2type(name, inet_devconf, ARRAY_SIZE(inet_devconf));
+}
+
+static void inet_dump_details(struct rtnl_link *link,
+			      struct nl_dump_params *p, void *data)
+{
+	struct inet_data *id = data;
+	char buf[64];
+	int i, n = 0;
+
+	nl_dump_line(p, "    ipv4 devconf:\n");
+	nl_dump_line(p, "      ");
+
+	for (i = 0; i < IPV4_DEVCONF_MAX; i++) {
+		nl_dump_line(p, "%-19s %3u",
+			rtnl_link_inet_devconf2str(i+1, buf, sizeof(buf)),
+			id->i_confset[i] ? id->i_conf[i] : 0);
+
+		if (++n == 3) {
+			nl_dump(p, "\n");
+			nl_dump_line(p, "      ");
+			n = 0;
+		} else
+			nl_dump(p, "  ");
+	}
+
+	if (n != 0)
+		nl_dump(p, "\n");
+}
+
+static struct rtnl_link_af_ops inet_ops = {
+	.ao_family			= AF_INET,
+	.ao_alloc			= &inet_alloc,
+	.ao_clone			= &inet_clone,
+	.ao_free			= &inet_free,
+	.ao_parse_af			= &inet_parse_af,
+	.ao_fill_af			= &inet_fill_af,
+	.ao_dump[NL_DUMP_DETAILS]	= &inet_dump_details,
+};
+
+/**
+ * Get value of a ipv4 link configuration setting
+ * @arg link		Link object
+ * @arg cfgid		Configuration identifier
+ * @arg res		Result pointer
+ *
+ * Stores the value of the specified configuration setting in the provided
+ * result pointer.
+ *
+ * @return 0 on success or a negative error code.
+ * @return -NLE_RANGE cfgid is out of range, 1..IPV4_DEVCONF_MAX
+ * @return -NLE_NOATTR configuration setting not available
+ * @return -NLE_INVAL cfgid not set. If the link was received via netlink,
+ *                    it means that the cfgid is not supported.
+ */
+int rtnl_link_inet_get_conf(struct rtnl_link *link, const unsigned int cfgid,
+			    uint32_t *res)
+{
+	struct inet_data *id;
+
+	if (cfgid == 0 || cfgid > IPV4_DEVCONF_MAX)
+		return -NLE_RANGE;
+
+	if (!(id = rtnl_link_af_alloc(link, &inet_ops)))
+		return -NLE_NOATTR;
+
+	if (!id->i_confset[cfgid - 1])
+		return -NLE_INVAL;
+	*res = id->i_conf[cfgid - 1];
+
+	return 0;
+}
+
+/**
+ * Change value of a ipv4 link configuration setting
+ * @arg link		Link object
+ * @arg cfgid		Configuration identifier
+ * @arg value		New value
+ *
+ * Changes the value in the per link ipv4 configuration array. 
+ *
+ * @return 0 on success or a negative error code.
+ * @return -NLE_RANGE cfgid is out of range, 1..IPV4_DEVCONF_MAX
+ * @return -NLE_NOMEM memory allocation failed
+ */
+int rtnl_link_inet_set_conf(struct rtnl_link *link, const unsigned int cfgid,
+			    uint32_t value)
+{
+	struct inet_data *id;
+
+	if (!(id = rtnl_link_af_alloc(link, &inet_ops)))
+		return -NLE_NOMEM;
+
+	if (cfgid == 0 || cfgid > IPV4_DEVCONF_MAX)
+		return -NLE_RANGE;
+
+	id->i_confset[cfgid - 1] = 1;
+	id->i_conf[cfgid - 1] = value;
+
+	return 0;
+}
+
+
+static void __init inet_init(void)
+{
+	rtnl_link_af_register(&inet_ops);
+}
+
+static void __exit inet_exit(void)
+{
+	rtnl_link_af_unregister(&inet_ops);
+}
+
+/** @} */
diff --git a/lib/route/link/inet6.c b/lib/route/link/inet6.c
new file mode 100644
index 0000000..6fa2741
--- /dev/null
+++ b/lib/route/link/inet6.c
@@ -0,0 +1,484 @@
+/*
+ * lib/route/link/inet6.c	AF_INET6 link operations
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+
+struct inet6_data
+{
+	uint32_t		i6_flags;
+	struct ifla_cacheinfo	i6_cacheinfo;
+	uint32_t		i6_conf[DEVCONF_MAX];
+};
+
+static void *inet6_alloc(struct rtnl_link *link)
+{
+	return calloc(1, sizeof(struct inet6_data));
+}
+
+static void *inet6_clone(struct rtnl_link *link, void *data)
+{
+	struct inet6_data *i6;
+
+	if ((i6 = inet6_alloc(link)))
+		memcpy(i6, data, sizeof(*i6));
+
+	return i6;
+}
+
+static void inet6_free(struct rtnl_link *link, void *data)
+{
+	free(data);
+}
+
+static struct nla_policy inet6_policy[IFLA_INET6_MAX+1] = {
+	[IFLA_INET6_FLAGS]	= { .type = NLA_U32 },
+	[IFLA_INET6_CACHEINFO]	= { .minlen = sizeof(struct ifla_cacheinfo) },
+	[IFLA_INET6_CONF]	= { .minlen = 4 },
+	[IFLA_INET6_STATS]	= { .minlen = 8 },
+	[IFLA_INET6_ICMP6STATS]	= { .minlen = 8 },
+};
+
+static const uint8_t map_stat_id_from_IPSTATS_MIB_v1[__IPSTATS_MIB_MAX] = {
+	/* 14a196807482e6fc74f15fc03176d5c08880588f^:include/linux/snmp.h
+	 * version before the API change in commit 14a196807482e6fc74f15fc03176d5c08880588f.
+	 * This version was valid since commit edf391ff17232f097d72441c9ad467bcb3b5db18, which
+	 * predates support for parsing IFLA_PROTINFO in libnl3. Such an even older meaning of
+	 * the flags is not supported in libnl3. */
+	[ 1] = RTNL_LINK_IP6_INPKTS,                    /* IPSTATS_MIB_INPKTS                   */
+	[ 2] = RTNL_LINK_IP6_INHDRERRORS,               /* IPSTATS_MIB_INHDRERRORS              */
+	[ 3] = RTNL_LINK_IP6_INTOOBIGERRORS,            /* IPSTATS_MIB_INTOOBIGERRORS           */
+	[ 4] = RTNL_LINK_IP6_INNOROUTES,                /* IPSTATS_MIB_INNOROUTES               */
+	[ 5] = RTNL_LINK_IP6_INADDRERRORS,              /* IPSTATS_MIB_INADDRERRORS             */
+	[ 6] = RTNL_LINK_IP6_INUNKNOWNPROTOS,           /* IPSTATS_MIB_INUNKNOWNPROTOS          */
+	[ 7] = RTNL_LINK_IP6_INTRUNCATEDPKTS,           /* IPSTATS_MIB_INTRUNCATEDPKTS          */
+	[ 8] = RTNL_LINK_IP6_INDISCARDS,                /* IPSTATS_MIB_INDISCARDS               */
+	[ 9] = RTNL_LINK_IP6_INDELIVERS,                /* IPSTATS_MIB_INDELIVERS               */
+	[10] = RTNL_LINK_IP6_OUTFORWDATAGRAMS,          /* IPSTATS_MIB_OUTFORWDATAGRAMS         */
+	[11] = RTNL_LINK_IP6_OUTPKTS,                   /* IPSTATS_MIB_OUTPKTS                  */
+	[12] = RTNL_LINK_IP6_OUTDISCARDS,               /* IPSTATS_MIB_OUTDISCARDS              */
+	[13] = RTNL_LINK_IP6_OUTNOROUTES,               /* IPSTATS_MIB_OUTNOROUTES              */
+	[14] = RTNL_LINK_IP6_REASMTIMEOUT,              /* IPSTATS_MIB_REASMTIMEOUT             */
+	[15] = RTNL_LINK_IP6_REASMREQDS,                /* IPSTATS_MIB_REASMREQDS               */
+	[16] = RTNL_LINK_IP6_REASMOKS,                  /* IPSTATS_MIB_REASMOKS                 */
+	[17] = RTNL_LINK_IP6_REASMFAILS,                /* IPSTATS_MIB_REASMFAILS               */
+	[18] = RTNL_LINK_IP6_FRAGOKS,                   /* IPSTATS_MIB_FRAGOKS                  */
+	[19] = RTNL_LINK_IP6_FRAGFAILS,                 /* IPSTATS_MIB_FRAGFAILS                */
+	[20] = RTNL_LINK_IP6_FRAGCREATES,               /* IPSTATS_MIB_FRAGCREATES              */
+	[21] = RTNL_LINK_IP6_INMCASTPKTS,               /* IPSTATS_MIB_INMCASTPKTS              */
+	[22] = RTNL_LINK_IP6_OUTMCASTPKTS,              /* IPSTATS_MIB_OUTMCASTPKTS             */
+	[23] = RTNL_LINK_IP6_INBCASTPKTS,               /* IPSTATS_MIB_INBCASTPKTS              */
+	[24] = RTNL_LINK_IP6_OUTBCASTPKTS,              /* IPSTATS_MIB_OUTBCASTPKTS             */
+	[25] = RTNL_LINK_IP6_INOCTETS,                  /* IPSTATS_MIB_INOCTETS                 */
+	[26] = RTNL_LINK_IP6_OUTOCTETS,                 /* IPSTATS_MIB_OUTOCTETS                */
+	[27] = RTNL_LINK_IP6_INMCASTOCTETS,             /* IPSTATS_MIB_INMCASTOCTETS            */
+	[28] = RTNL_LINK_IP6_OUTMCASTOCTETS,            /* IPSTATS_MIB_OUTMCASTOCTETS           */
+	[29] = RTNL_LINK_IP6_INBCASTOCTETS,             /* IPSTATS_MIB_INBCASTOCTETS            */
+	[30] = RTNL_LINK_IP6_OUTBCASTOCTETS,            /* IPSTATS_MIB_OUTBCASTOCTETS           */
+};
+
+static const uint8_t map_stat_id_from_IPSTATS_MIB_v2[__IPSTATS_MIB_MAX] = {
+	/* d8ec26d7f8287f5788a494f56e8814210f0e64be:include/uapi/linux/snmp.h
+	 * version since the API change in commit 14a196807482e6fc74f15fc03176d5c08880588f */
+	[ 1] = RTNL_LINK_IP6_INPKTS,                    /* IPSTATS_MIB_INPKTS                   */
+	[ 2] = RTNL_LINK_IP6_INOCTETS,                  /* IPSTATS_MIB_INOCTETS                 */
+	[ 3] = RTNL_LINK_IP6_INDELIVERS,                /* IPSTATS_MIB_INDELIVERS               */
+	[ 4] = RTNL_LINK_IP6_OUTFORWDATAGRAMS,          /* IPSTATS_MIB_OUTFORWDATAGRAMS         */
+	[ 5] = RTNL_LINK_IP6_OUTPKTS,                   /* IPSTATS_MIB_OUTPKTS                  */
+	[ 6] = RTNL_LINK_IP6_OUTOCTETS,                 /* IPSTATS_MIB_OUTOCTETS                */
+	[ 7] = RTNL_LINK_IP6_INHDRERRORS,               /* IPSTATS_MIB_INHDRERRORS              */
+	[ 8] = RTNL_LINK_IP6_INTOOBIGERRORS,            /* IPSTATS_MIB_INTOOBIGERRORS           */
+	[ 9] = RTNL_LINK_IP6_INNOROUTES,                /* IPSTATS_MIB_INNOROUTES               */
+	[10] = RTNL_LINK_IP6_INADDRERRORS,              /* IPSTATS_MIB_INADDRERRORS             */
+	[11] = RTNL_LINK_IP6_INUNKNOWNPROTOS,           /* IPSTATS_MIB_INUNKNOWNPROTOS          */
+	[12] = RTNL_LINK_IP6_INTRUNCATEDPKTS,           /* IPSTATS_MIB_INTRUNCATEDPKTS          */
+	[13] = RTNL_LINK_IP6_INDISCARDS,                /* IPSTATS_MIB_INDISCARDS               */
+	[14] = RTNL_LINK_IP6_OUTDISCARDS,               /* IPSTATS_MIB_OUTDISCARDS              */
+	[15] = RTNL_LINK_IP6_OUTNOROUTES,               /* IPSTATS_MIB_OUTNOROUTES              */
+	[16] = RTNL_LINK_IP6_REASMTIMEOUT,              /* IPSTATS_MIB_REASMTIMEOUT             */
+	[17] = RTNL_LINK_IP6_REASMREQDS,                /* IPSTATS_MIB_REASMREQDS               */
+	[18] = RTNL_LINK_IP6_REASMOKS,                  /* IPSTATS_MIB_REASMOKS                 */
+	[19] = RTNL_LINK_IP6_REASMFAILS,                /* IPSTATS_MIB_REASMFAILS               */
+	[20] = RTNL_LINK_IP6_FRAGOKS,                   /* IPSTATS_MIB_FRAGOKS                  */
+	[21] = RTNL_LINK_IP6_FRAGFAILS,                 /* IPSTATS_MIB_FRAGFAILS                */
+	[22] = RTNL_LINK_IP6_FRAGCREATES,               /* IPSTATS_MIB_FRAGCREATES              */
+	[23] = RTNL_LINK_IP6_INMCASTPKTS,               /* IPSTATS_MIB_INMCASTPKTS              */
+	[24] = RTNL_LINK_IP6_OUTMCASTPKTS,              /* IPSTATS_MIB_OUTMCASTPKTS             */
+	[25] = RTNL_LINK_IP6_INBCASTPKTS,               /* IPSTATS_MIB_INBCASTPKTS              */
+	[26] = RTNL_LINK_IP6_OUTBCASTPKTS,              /* IPSTATS_MIB_OUTBCASTPKTS             */
+	[27] = RTNL_LINK_IP6_INMCASTOCTETS,             /* IPSTATS_MIB_INMCASTOCTETS            */
+	[28] = RTNL_LINK_IP6_OUTMCASTOCTETS,            /* IPSTATS_MIB_OUTMCASTOCTETS           */
+	[29] = RTNL_LINK_IP6_INBCASTOCTETS,             /* IPSTATS_MIB_INBCASTOCTETS            */
+	[30] = RTNL_LINK_IP6_OUTBCASTOCTETS,            /* IPSTATS_MIB_OUTBCASTOCTETS           */
+	[31] = RTNL_LINK_IP6_CSUMERRORS,                /* IPSTATS_MIB_CSUMERRORS               */
+	[32] = RTNL_LINK_IP6_NOECTPKTS,                 /* IPSTATS_MIB_NOECTPKTS                */
+	[33] = RTNL_LINK_IP6_ECT1PKTS,                  /* IPSTATS_MIB_ECT1PKTS                 */
+	[34] = RTNL_LINK_IP6_ECT0PKTS,                  /* IPSTATS_MIB_ECT0PKTS                 */
+	[35] = RTNL_LINK_IP6_CEPKTS,                    /* IPSTATS_MIB_CEPKTS                   */
+};
+
+static int inet6_parse_protinfo(struct rtnl_link *link, struct nlattr *attr,
+				void *data)
+{
+	struct inet6_data *i6 = data;
+	struct nlattr *tb[IFLA_INET6_MAX+1];
+	int err;
+
+	err = nla_parse_nested(tb, IFLA_INET6_MAX, attr, inet6_policy);
+	if (err < 0)
+		return err;
+	if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4)
+		return -EINVAL;
+	if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8)
+		return -EINVAL;
+	if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8)
+		return -EINVAL;
+
+	if (tb[IFLA_INET6_FLAGS])
+		i6->i6_flags = nla_get_u32(tb[IFLA_INET6_FLAGS]);
+
+	if (tb[IFLA_INET6_CACHEINFO])
+		nla_memcpy(&i6->i6_cacheinfo, tb[IFLA_INET6_CACHEINFO],
+			   sizeof(i6->i6_cacheinfo));
+
+	if (tb[IFLA_INET6_CONF])
+		nla_memcpy(&i6->i6_conf, tb[IFLA_INET6_CONF],
+			   sizeof(i6->i6_conf));
+ 
+	/*
+	 * Due to 32bit data alignment, these addresses must be copied to an
+	 * aligned location prior to access.
+	 */
+	if (tb[IFLA_INET6_STATS]) {
+		unsigned char *cnt = nla_data(tb[IFLA_INET6_STATS]);
+		uint64_t stat;
+		int i;
+		int len = nla_len(tb[IFLA_INET6_STATS]) / 8;
+		const uint8_t *map_stat_id = map_stat_id_from_IPSTATS_MIB_v2;
+
+		if (len < 32 ||
+		    (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) < 6)) {
+			/* kernel commit 14a196807482e6fc74f15fc03176d5c08880588f reordered the values.
+			 * The later commit 6a5dc9e598fe90160fee7de098fa319665f5253e added values
+			 * IPSTATS_MIB_CSUMERRORS/ICMP6_MIB_CSUMERRORS. If the netlink is shorter
+			 * then this, assume that the kernel uses the previous meaning of the
+			 * enumeration. */
+			map_stat_id = map_stat_id_from_IPSTATS_MIB_v1;
+		}
+
+		len = min_t(int, __IPSTATS_MIB_MAX, len);
+		for (i = 1; i < len; i++) {
+			memcpy(&stat, &cnt[i * sizeof(stat)], sizeof(stat));
+			rtnl_link_set_stat(link, map_stat_id[i], stat);
+		}
+	}
+
+	if (tb[IFLA_INET6_ICMP6STATS]) {
+		unsigned char *cnt = nla_data(tb[IFLA_INET6_ICMP6STATS]);
+		uint64_t stat;
+		int i;
+		int len = min_t(int, __ICMP6_MIB_MAX, nla_len(tb[IFLA_INET6_ICMP6STATS]) / 8);
+
+		for (i = 1; i < len; i++) {
+			memcpy(&stat, &cnt[i * sizeof(stat)], sizeof(stat));
+			rtnl_link_set_stat(link, RTNL_LINK_ICMP6_INMSGS + i - 1,
+					   stat);
+		}
+	}
+
+	return 0;
+}
+
+/* These live in include/net/if_inet6.h and should be moved to include/linux */
+#define IF_RA_OTHERCONF	0x80
+#define IF_RA_MANAGED	0x40
+#define IF_RA_RCVD	0x20
+#define IF_RS_SENT	0x10
+#define IF_READY	0x80000000
+
+static const struct trans_tbl inet6_flags[] = {
+	__ADD(IF_RA_OTHERCONF, ra_otherconf)
+	__ADD(IF_RA_MANAGED, ra_managed)
+	__ADD(IF_RA_RCVD, ra_rcvd)
+	__ADD(IF_RS_SENT, rs_sent)
+	__ADD(IF_READY, ready)
+};
+
+static char *inet6_flags2str(int flags, char *buf, size_t len)
+{
+	return __flags2str(flags, buf, len, inet6_flags,
+			   ARRAY_SIZE(inet6_flags));
+}
+
+static const struct trans_tbl inet6_devconf[] = {
+	__ADD(DEVCONF_FORWARDING, forwarding)
+	__ADD(DEVCONF_HOPLIMIT, hoplimit)
+	__ADD(DEVCONF_MTU6, mtu6)
+	__ADD(DEVCONF_ACCEPT_RA, accept_ra)
+	__ADD(DEVCONF_ACCEPT_REDIRECTS, accept_redirects)
+	__ADD(DEVCONF_AUTOCONF, autoconf)
+	__ADD(DEVCONF_DAD_TRANSMITS, dad_transmits)
+	__ADD(DEVCONF_RTR_SOLICITS, rtr_solicits)
+	__ADD(DEVCONF_RTR_SOLICIT_INTERVAL, rtr_solicit_interval)
+	__ADD(DEVCONF_RTR_SOLICIT_DELAY, rtr_solicit_delay)
+	__ADD(DEVCONF_USE_TEMPADDR, use_tempaddr)
+	__ADD(DEVCONF_TEMP_VALID_LFT, temp_valid_lft)
+	__ADD(DEVCONF_TEMP_PREFERED_LFT, temp_prefered_lft)
+	__ADD(DEVCONF_REGEN_MAX_RETRY, regen_max_retry)
+	__ADD(DEVCONF_MAX_DESYNC_FACTOR, max_desync_factor)
+	__ADD(DEVCONF_MAX_ADDRESSES, max_addresses)
+	__ADD(DEVCONF_FORCE_MLD_VERSION, force_mld_version)
+	__ADD(DEVCONF_ACCEPT_RA_DEFRTR, accept_ra_defrtr)
+	__ADD(DEVCONF_ACCEPT_RA_PINFO, accept_ra_pinfo)
+	__ADD(DEVCONF_ACCEPT_RA_RTR_PREF, accept_ra_rtr_pref)
+	__ADD(DEVCONF_RTR_PROBE_INTERVAL, rtr_probe_interval)
+	__ADD(DEVCONF_ACCEPT_RA_RT_INFO_MAX_PLEN, accept_ra_rt_info)
+	__ADD(DEVCONF_PROXY_NDP, proxy_ndp)
+	__ADD(DEVCONF_OPTIMISTIC_DAD, optimistic_dad)
+	__ADD(DEVCONF_ACCEPT_SOURCE_ROUTE, accept_source_route)
+	__ADD(DEVCONF_MC_FORWARDING, mc_forwarding)
+	__ADD(DEVCONF_DISABLE_IPV6, disable_ipv6)
+	__ADD(DEVCONF_ACCEPT_DAD, accept_dad)
+	__ADD(DEVCONF_FORCE_TLLAO, force_tllao)
+};
+
+static char *inet6_devconf2str(int type, char *buf, size_t len)
+{
+	return __type2str(type, buf, len, inet6_devconf,
+			  ARRAY_SIZE(inet6_devconf));
+}
+
+
+static void inet6_dump_details(struct rtnl_link *link,
+				struct nl_dump_params *p, void *data)
+{
+	struct inet6_data *i6 = data;
+	char buf[64], buf2[64];
+	int i, n = 0;
+
+	nl_dump_line(p, "    ipv6 max-reasm-len %s",
+		nl_size2str(i6->i6_cacheinfo.max_reasm_len, buf, sizeof(buf)));
+
+	nl_dump(p, " <%s>\n",
+		inet6_flags2str(i6->i6_flags, buf, sizeof(buf)));
+
+
+	nl_dump_line(p, "      create-stamp %.2fs reachable-time %s",
+		(double) i6->i6_cacheinfo.tstamp / 100.,
+		nl_msec2str(i6->i6_cacheinfo.reachable_time, buf, sizeof(buf)));
+
+	nl_dump(p, " retrans-time %s\n",
+		nl_msec2str(i6->i6_cacheinfo.retrans_time, buf, sizeof(buf)));
+
+	nl_dump_line(p, "      devconf:\n");
+	nl_dump_line(p, "      ");
+
+	for (i = 0; i < DEVCONF_MAX; i++) {
+		uint32_t value = i6->i6_conf[i];
+		int x, offset;
+
+		switch (i) {
+		case DEVCONF_TEMP_VALID_LFT:
+		case DEVCONF_TEMP_PREFERED_LFT:
+			nl_msec2str((uint64_t) value * 1000., buf2, sizeof(buf2));
+			break;
+
+		case DEVCONF_RTR_PROBE_INTERVAL:
+		case DEVCONF_RTR_SOLICIT_INTERVAL:
+		case DEVCONF_RTR_SOLICIT_DELAY:
+			nl_msec2str(value, buf2, sizeof(buf2));
+			break;
+
+		default:
+			snprintf(buf2, sizeof(buf2), "%u", value);
+			break;
+			
+		}
+
+		inet6_devconf2str(i, buf, sizeof(buf));
+
+		offset = 23 - strlen(buf2);
+		if (offset < 0)
+			offset = 0;
+
+		for (x = strlen(buf); x < offset; x++)
+			buf[x] = ' ';
+
+		strncpy(&buf[offset], buf2, strlen(buf2));
+
+		nl_dump_line(p, "%s", buf);
+
+		if (++n == 3) {
+			nl_dump(p, "\n");
+			nl_dump_line(p, "      ");
+			n = 0;
+		} else
+			nl_dump(p, "  ");
+	}
+
+	if (n != 0)
+		nl_dump(p, "\n");
+}
+
+static void inet6_dump_stats(struct rtnl_link *link,
+			     struct nl_dump_params *p, void *data)
+{
+	double octets;
+	char *octetsUnit;
+
+	nl_dump(p, "    IPv6:       InPkts           InOctets     "
+		   "    InDiscards         InDelivers\n");
+	nl_dump(p, "    %18" PRIu64 " ", link->l_stats[RTNL_LINK_IP6_INPKTS]);
+
+	octets = nl_cancel_down_bytes(link->l_stats[RTNL_LINK_IP6_INOCTETS],
+				      &octetsUnit);
+	if (octets)
+		nl_dump(p, "%14.2f %3s ", octets, octetsUnit);
+	else
+		nl_dump(p, "%16" PRIu64 " B ", 0);
+	
+	nl_dump(p, "%18" PRIu64 " %18" PRIu64 "\n",
+		link->l_stats[RTNL_LINK_IP6_INDISCARDS],
+		link->l_stats[RTNL_LINK_IP6_INDELIVERS]);
+
+	nl_dump(p, "               OutPkts          OutOctets     "
+		   "   OutDiscards        OutForwards\n");
+
+	nl_dump(p, "    %18" PRIu64 " ", link->l_stats[RTNL_LINK_IP6_OUTPKTS]);
+
+	octets = nl_cancel_down_bytes(link->l_stats[RTNL_LINK_IP6_OUTOCTETS],
+				      &octetsUnit);
+	if (octets)
+		nl_dump(p, "%14.2f %3s ", octets, octetsUnit);
+	else
+		nl_dump(p, "%16" PRIu64 " B ", 0);
+
+	nl_dump(p, "%18" PRIu64 " %18" PRIu64 "\n",
+		link->l_stats[RTNL_LINK_IP6_OUTDISCARDS],
+		link->l_stats[RTNL_LINK_IP6_OUTFORWDATAGRAMS]);
+
+	nl_dump(p, "           InMcastPkts      InMcastOctets     "
+		   "   InBcastPkts     InBcastOctests\n");
+
+	nl_dump(p, "    %18" PRIu64 " ", link->l_stats[RTNL_LINK_IP6_INMCASTPKTS]);
+
+	octets = nl_cancel_down_bytes(link->l_stats[RTNL_LINK_IP6_INMCASTOCTETS],
+				      &octetsUnit);
+	if (octets)
+		nl_dump(p, "%14.2f %3s ", octets, octetsUnit);
+	else
+		nl_dump(p, "%16" PRIu64 " B ", 0);
+
+	nl_dump(p, "%18" PRIu64 " ", link->l_stats[RTNL_LINK_IP6_INBCASTPKTS]);
+	octets = nl_cancel_down_bytes(link->l_stats[RTNL_LINK_IP6_INBCASTOCTETS],
+				      &octetsUnit);
+	if (octets)
+		nl_dump(p, "%14.2f %3s\n", octets, octetsUnit);
+	else
+		nl_dump(p, "%16" PRIu64 " B\n", 0);
+
+	nl_dump(p, "          OutMcastPkts     OutMcastOctets     "
+		   "  OutBcastPkts    OutBcastOctests\n");
+
+	nl_dump(p, "    %18" PRIu64 " ", link->l_stats[RTNL_LINK_IP6_OUTMCASTPKTS]);
+
+	octets = nl_cancel_down_bytes(link->l_stats[RTNL_LINK_IP6_OUTMCASTOCTETS],
+				      &octetsUnit);
+	if (octets)
+		nl_dump(p, "%14.2f %3s ", octets, octetsUnit);
+	else
+		nl_dump(p, "%16" PRIu64 " B ", 0);
+
+	nl_dump(p, "%18" PRIu64 " ", link->l_stats[RTNL_LINK_IP6_OUTBCASTPKTS]);
+	octets = nl_cancel_down_bytes(link->l_stats[RTNL_LINK_IP6_OUTBCASTOCTETS],
+				      &octetsUnit);
+	if (octets)
+		nl_dump(p, "%14.2f %3s\n", octets, octetsUnit);
+	else
+		nl_dump(p, "%16" PRIu64 " B\n", 0);
+
+	nl_dump(p, "              ReasmOKs         ReasmFails     "
+		   "    ReasmReqds       ReasmTimeout\n");
+	nl_dump(p, "    %18" PRIu64 " %18" PRIu64 " %18" PRIu64 " %18" PRIu64 "\n",
+		link->l_stats[RTNL_LINK_IP6_REASMOKS],
+		link->l_stats[RTNL_LINK_IP6_REASMFAILS],
+		link->l_stats[RTNL_LINK_IP6_REASMREQDS],
+		link->l_stats[RTNL_LINK_IP6_REASMTIMEOUT]);
+
+	nl_dump(p, "               FragOKs          FragFails    "
+		   "    FragCreates\n");
+	nl_dump(p, "    %18" PRIu64 " %18" PRIu64 " %18" PRIu64 "\n",
+		link->l_stats[RTNL_LINK_IP6_FRAGOKS],
+		link->l_stats[RTNL_LINK_IP6_FRAGFAILS],
+		link->l_stats[RTNL_LINK_IP6_FRAGCREATES]);
+
+	nl_dump(p, "           InHdrErrors      InTooBigErrors   "
+		   "     InNoRoutes       InAddrErrors\n");
+	nl_dump(p, "    %18" PRIu64 " %18" PRIu64 " %18" PRIu64 " %18" PRIu64 "\n",
+		link->l_stats[RTNL_LINK_IP6_INHDRERRORS],
+		link->l_stats[RTNL_LINK_IP6_INTOOBIGERRORS],
+		link->l_stats[RTNL_LINK_IP6_INNOROUTES],
+		link->l_stats[RTNL_LINK_IP6_INADDRERRORS]);
+
+	nl_dump(p, "       InUnknownProtos     InTruncatedPkts   "
+		   "    OutNoRoutes       InCsumErrors\n");
+	nl_dump(p, "    %18" PRIu64 " %18" PRIu64 " %18" PRIu64 " %18" PRIu64 "\n",
+		link->l_stats[RTNL_LINK_IP6_INUNKNOWNPROTOS],
+		link->l_stats[RTNL_LINK_IP6_INTRUNCATEDPKTS],
+		link->l_stats[RTNL_LINK_IP6_OUTNOROUTES],
+		link->l_stats[RTNL_LINK_IP6_CSUMERRORS]);
+
+	nl_dump(p, "           InNoECTPkts          InECT1Pkts   "
+		   "     InECT0Pkts           InCEPkts\n");
+	nl_dump(p, "    %18" PRIu64 " %18" PRIu64 " %18" PRIu64 " %18" PRIu64 "\n",
+		link->l_stats[RTNL_LINK_IP6_NOECTPKTS],
+		link->l_stats[RTNL_LINK_IP6_ECT1PKTS],
+		link->l_stats[RTNL_LINK_IP6_ECT0PKTS],
+		link->l_stats[RTNL_LINK_IP6_CEPKTS]);
+
+	nl_dump(p, "    ICMPv6:     InMsgs           InErrors        "
+		   "    OutMsgs          OutErrors       InCsumErrors\n");
+	nl_dump(p, "    %18" PRIu64 " %18" PRIu64 " %18" PRIu64 " %18" PRIu64 " %18" PRIu64 "\n",
+		link->l_stats[RTNL_LINK_ICMP6_INMSGS],
+		link->l_stats[RTNL_LINK_ICMP6_INERRORS],
+		link->l_stats[RTNL_LINK_ICMP6_OUTMSGS],
+		link->l_stats[RTNL_LINK_ICMP6_OUTERRORS],
+		link->l_stats[RTNL_LINK_ICMP6_CSUMERRORS]);
+}
+
+static const struct nla_policy protinfo_policy = {
+	.type			= NLA_NESTED,
+};
+
+static struct rtnl_link_af_ops inet6_ops = {
+	.ao_family			= AF_INET6,
+	.ao_alloc			= &inet6_alloc,
+	.ao_clone			= &inet6_clone,
+	.ao_free			= &inet6_free,
+	.ao_parse_protinfo		= &inet6_parse_protinfo,
+	.ao_parse_af			= &inet6_parse_protinfo,
+	.ao_dump[NL_DUMP_DETAILS]	= &inet6_dump_details,
+	.ao_dump[NL_DUMP_STATS]		= &inet6_dump_stats,
+	.ao_protinfo_policy		= &protinfo_policy,
+};
+
+static void __init inet6_init(void)
+{
+	rtnl_link_af_register(&inet6_ops);
+}
+
+static void __exit inet6_exit(void)
+{
+	rtnl_link_af_unregister(&inet6_ops);
+}
diff --git a/lib/route/link/ip6tnl.c b/lib/route/link/ip6tnl.c
new file mode 100644
index 0000000..9fe1367
--- /dev/null
+++ b/lib/route/link/ip6tnl.c
@@ -0,0 +1,688 @@
+/*
+ * lib/route/link/ip6tnl.c        IP6TNL Link Info
+ *
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation version 2.1
+ *      of the License.
+ *
+ * Copyright (c) 2014 Susant Sahani <susant@redhat.com>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup ip6tnl IP6TNL
+ * ip6tnl link module
+ *
+ * @details
+ * \b Link Type Name: "ip6tnl"
+ *
+ * @route_doc{link_ip6tnl, IP6TNL Documentation}
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink/object.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+#include <linux/if_tunnel.h>
+#include <netinet/in.h>
+
+#define IP6_TNL_ATTR_LINK          (1 << 0)
+#define IP6_TNL_ATTR_LOCAL         (1 << 1)
+#define IP6_TNL_ATTR_REMOTE        (1 << 2)
+#define IP6_TNL_ATTR_TTL           (1 << 3)
+#define IP6_TNL_ATTR_TOS           (1 << 4)
+#define IP6_TNL_ATTR_ENCAPLIMIT    (1 << 5)
+#define IP6_TNL_ATTR_FLAGS         (1 << 6)
+#define IP6_TNL_ATTR_PROTO         (1 << 7)
+#define IP6_TNL_ATTR_FLOWINFO      (1 << 8)
+
+struct ip6_tnl_info
+{
+	uint8_t                 ttl;
+	uint8_t                 tos;
+	uint8_t                 encap_limit;
+	uint8_t                 proto;
+	uint32_t                flags;
+	uint32_t                link;
+	uint32_t                flowinfo;
+	struct in6_addr         local;
+	struct in6_addr         remote;
+	uint32_t                ip6_tnl_mask;
+};
+
+static struct nla_policy ip6_tnl_policy[IFLA_IPTUN_MAX + 1] = {
+	[IFLA_IPTUN_LINK]         = { .type = NLA_U32 },
+	[IFLA_IPTUN_LOCAL]        = { .minlen = sizeof(struct in6_addr) },
+	[IFLA_IPTUN_REMOTE]       = { .minlen = sizeof(struct in6_addr) },
+	[IFLA_IPTUN_TTL]          = { .type = NLA_U8 },
+	[IFLA_IPTUN_TOS]          = { .type = NLA_U8 },
+	[IFLA_IPTUN_ENCAP_LIMIT]  = { .type = NLA_U8 },
+	[IFLA_IPTUN_FLOWINFO]     = { .type = NLA_U32 },
+	[IFLA_IPTUN_FLAGS]        = { .type = NLA_U32 },
+	[IFLA_IPTUN_PROTO]        = { .type = NLA_U8 },
+};
+
+static int ip6_tnl_alloc(struct rtnl_link *link)
+{
+	struct ip6_tnl_info *ip6_tnl;
+
+	ip6_tnl = calloc(1, sizeof(*ip6_tnl));
+	if (!ip6_tnl)
+		return -NLE_NOMEM;
+
+	link->l_info = ip6_tnl;
+
+	return 0;
+}
+
+static int ip6_tnl_parse(struct rtnl_link *link, struct nlattr *data,
+			 struct nlattr *xstats)
+{
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
+	struct ip6_tnl_info *ip6_tnl;
+	int err;
+
+	NL_DBG(3, "Parsing IP6_TNL link info");
+
+	err = nla_parse_nested(tb, IFLA_IPTUN_MAX, data, ip6_tnl_policy);
+	if (err < 0)
+		goto errout;
+
+	err = ip6_tnl_alloc(link);
+	if (err < 0)
+		goto errout;
+
+	ip6_tnl = link->l_info;
+
+	if (tb[IFLA_IPTUN_LINK]) {
+		ip6_tnl->link = nla_get_u32(tb[IFLA_IPTUN_LINK]);
+		ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_LINK;
+	}
+
+	if (tb[IFLA_IPTUN_LOCAL]) {
+		nla_memcpy(&ip6_tnl->local, tb[IFLA_IPTUN_LOCAL], sizeof(struct in6_addr));
+		ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_LOCAL;
+	}
+
+	if (tb[IFLA_IPTUN_REMOTE]) {
+		nla_memcpy(&ip6_tnl->remote, tb[IFLA_IPTUN_REMOTE], sizeof(struct in6_addr));
+		ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_REMOTE;
+	}
+
+	if (tb[IFLA_IPTUN_TTL]) {
+		ip6_tnl->ttl = nla_get_u8(tb[IFLA_IPTUN_TTL]);
+		ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_TTL;
+	}
+
+	if (tb[IFLA_IPTUN_TOS]) {
+		ip6_tnl->tos = nla_get_u8(tb[IFLA_IPTUN_TOS]);
+		ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_TOS;
+	}
+
+	if (tb[IFLA_IPTUN_ENCAP_LIMIT]) {
+		ip6_tnl->encap_limit = nla_get_u8(tb[IFLA_IPTUN_ENCAP_LIMIT]);
+		ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_ENCAPLIMIT;
+	}
+
+	if (tb[IFLA_IPTUN_FLAGS]) {
+		ip6_tnl->flags = nla_get_u32(tb[IFLA_IPTUN_FLAGS]);
+		ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_FLAGS;
+	}
+
+	if (tb[IFLA_IPTUN_FLOWINFO]) {
+		ip6_tnl->flowinfo = nla_get_u32(tb[IFLA_IPTUN_FLOWINFO]);
+		ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_FLOWINFO;
+	}
+
+	if (tb[IFLA_IPTUN_PROTO]) {
+		ip6_tnl->proto = nla_get_u8(tb[IFLA_IPTUN_PROTO]);
+		ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_PROTO;
+	}
+
+	err = 0;
+
+errout:
+	return err;
+}
+
+static int ip6_tnl_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+	struct nlattr *data;
+
+	data = nla_nest_start(msg, IFLA_INFO_DATA);
+	if (!data)
+		return -NLE_MSGSIZE;
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_LINK)
+		NLA_PUT_U32(msg, IFLA_IPTUN_LINK, ip6_tnl->link);
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_LOCAL)
+		NLA_PUT(msg, IFLA_IPTUN_LOCAL, sizeof(struct in6_addr), &ip6_tnl->local);
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_REMOTE)
+		NLA_PUT(msg, IFLA_IPTUN_REMOTE, sizeof(struct in6_addr), &ip6_tnl->remote);
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_TTL)
+		NLA_PUT_U8(msg, IFLA_IPTUN_TTL, ip6_tnl->ttl);
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_TOS)
+		NLA_PUT_U8(msg, IFLA_IPTUN_TOS, ip6_tnl->tos);
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_ENCAPLIMIT)
+		NLA_PUT_U8(msg, IFLA_IPTUN_ENCAP_LIMIT, ip6_tnl->encap_limit);
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_FLAGS)
+		NLA_PUT_U32(msg, IFLA_IPTUN_FLAGS, ip6_tnl->flags);
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_FLOWINFO)
+		NLA_PUT_U32(msg, IFLA_IPTUN_FLOWINFO, ip6_tnl->flowinfo);
+
+	/* kernel crashes if this attribure is missing  temporary fix */
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_PROTO)
+		NLA_PUT_U8(msg, IFLA_IPTUN_PROTO, ip6_tnl->proto);
+	else
+		NLA_PUT_U8(msg, IFLA_IPTUN_PROTO, 0);
+
+	nla_nest_end(msg, data);
+
+nla_put_failure:
+	return 0;
+}
+
+static void ip6_tnl_free(struct rtnl_link *link)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	free(ip6_tnl);
+	link->l_info = NULL;
+}
+
+static void ip6_tnl_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	nl_dump(p, "ip6_tnl : %s", link->l_name);
+}
+
+static void ip6_tnl_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+	char *name, addr[INET6_ADDRSTRLEN];
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_LINK) {
+		nl_dump(p, "      link ");
+		name = rtnl_link_get_name(link);
+		if (name)
+			nl_dump_line(p, "%s\n", name);
+		else
+			nl_dump_line(p, "%u\n", ip6_tnl->link);
+	}
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_LOCAL) {
+		nl_dump(p, "      local ");
+
+		if(inet_ntop(AF_INET6, &ip6_tnl->local, addr, INET6_ADDRSTRLEN))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ip6_tnl->local);
+	}
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_REMOTE) {
+		nl_dump(p, "      remote ");
+
+		if(inet_ntop(AF_INET6, &ip6_tnl->remote, addr, INET6_ADDRSTRLEN))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ip6_tnl->remote);
+	}
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_TTL) {
+		nl_dump(p, "      ttl ");
+		nl_dump_line(p, "%d\n", ip6_tnl->ttl);
+	}
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_TOS) {
+		nl_dump(p, "      tos ");
+		nl_dump_line(p, "%d\n", ip6_tnl->tos);
+	}
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_ENCAPLIMIT) {
+		nl_dump(p, "      encaplimit ");
+		nl_dump_line(p, "%d\n", ip6_tnl->encap_limit);
+	}
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_FLAGS) {
+		nl_dump(p, "      flags ");
+		nl_dump_line(p, " (%x)\n", ip6_tnl->flags);
+	}
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_FLOWINFO) {
+		nl_dump(p, "      flowinfo ");
+		nl_dump_line(p, " (%x)\n", ip6_tnl->flowinfo);
+	}
+
+	if (ip6_tnl->ip6_tnl_mask & IP6_TNL_ATTR_PROTO) {
+		nl_dump(p, "    proto   ");
+		nl_dump_line(p, " (%x)\n", ip6_tnl->proto);
+	}
+}
+
+static int ip6_tnl_clone(struct rtnl_link *dst, struct rtnl_link *src)
+{
+	struct ip6_tnl_info *ip6_tnl_dst, *ip6_tnl_src = src->l_info;
+	int err;
+
+	dst->l_info = NULL;
+
+	err = rtnl_link_set_type(dst, "ip6tnl");
+	if (err < 0)
+		return err;
+
+	ip6_tnl_dst = dst->l_info;
+
+	if (!ip6_tnl_dst || !ip6_tnl_src)
+		BUG();
+
+	memcpy(ip6_tnl_dst, ip6_tnl_src, sizeof(struct ip6_tnl_info));
+
+	return 0;
+}
+
+static struct rtnl_link_info_ops ip6_tnl_info_ops = {
+	.io_name                = "ip6tnl",
+	.io_alloc               = ip6_tnl_alloc,
+	.io_parse               = ip6_tnl_parse,
+	.io_dump = {
+		[NL_DUMP_LINE]  = ip6_tnl_dump_line,
+		[NL_DUMP_DETAILS] = ip6_tnl_dump_details,
+	},
+	.io_clone               = ip6_tnl_clone,
+	.io_put_attrs           = ip6_tnl_put_attrs,
+	.io_free                = ip6_tnl_free,
+};
+
+#define IS_IP6_TNL_LINK_ASSERT(link)\
+	if ((link)->l_info_ops != &ip6_tnl_info_ops) {\
+		APPBUG("Link is not a ip6_tnl link. set type \"ip6tnl\" first.");\
+		return -NLE_OPNOTSUPP;\
+	}
+
+struct rtnl_link *rtnl_link_ip6_tnl_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_alloc();
+	if (!link)
+		return NULL;
+
+	err = rtnl_link_set_type(link, "ip6tnl");
+	if (err < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+
+/**
+ * Check if link is a IP6_TNL link
+ * @arg link            Link object
+ *
+ * @return True if link is a IP6_TNL link, otherwise false is returned.
+ */
+int rtnl_link_is_ip6_tnl(struct rtnl_link *link)
+{
+	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "ip6tnl");
+}
+
+/**
+ * Create a new ip6_tnl tunnel device
+ * @arg sock            netlink socket
+ * @arg name            name of the tunnel device
+ *
+ * Creates a new ip6_tnl tunnel device in the kernel
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_add(struct nl_sock *sk, const char *name)
+{
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_ip6_tnl_alloc();
+	if (!link)
+		return -NLE_NOMEM;
+
+	if(name)
+		rtnl_link_set_name(link, name);
+
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	rtnl_link_put(link);
+
+	return err;
+}
+
+/**
+ * Set IP6_TNL tunnel interface index
+ * @arg link            Link object
+ * @arg index           interface index
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_set_link(struct rtnl_link *link, uint32_t index)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	ip6_tnl->link = index;
+	ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_LINK;
+
+	return 0;
+}
+
+/**
+ * Get IP6_TNL tunnel interface index
+ * @arg link            Link object
+ *
+ * @return interface index value
+ */
+uint32_t rtnl_link_ip6_tnl_get_link(struct rtnl_link *link)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	return ip6_tnl->link;
+}
+
+/**
+ * Set IP6_TNL tunnel local address
+ * @arg link            Link object
+ * @arg addr            local address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_set_local(struct rtnl_link *link, struct in6_addr *addr)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	memcpy(&ip6_tnl->local, addr, sizeof(struct in6_addr));
+	ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_LOCAL;
+
+	return 0;
+}
+
+/**
+ * Get IP6_TNL tunnel local address
+ * @arg link            Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_get_local(struct rtnl_link *link, struct in6_addr *addr)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	memcpy(addr, &ip6_tnl->local, sizeof(struct in6_addr));
+
+	return 0;
+}
+
+/**
+ * Set IP6_TNL tunnel remote address
+ * @arg link            Link object
+ * @arg remote          remote address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_set_remote(struct rtnl_link *link, struct in6_addr *addr)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	memcpy(&ip6_tnl->remote, addr, sizeof(struct in6_addr));
+	ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_REMOTE;
+
+	return 0;
+}
+
+/**
+ * Get IP6_TNL tunnel remote address
+ * @arg link            Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_get_remote(struct rtnl_link *link, struct in6_addr *addr)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	memcpy(addr, &ip6_tnl->remote, sizeof(struct in6_addr));
+
+	return 0;
+}
+
+/**
+ * Set IP6_TNL tunnel ttl
+ * @arg link            Link object
+ * @arg ttl             tunnel ttl
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_set_ttl(struct rtnl_link *link, uint8_t ttl)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	ip6_tnl->ttl = ttl;
+	ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_TTL;
+
+	return 0;
+}
+
+/**
+ * Get IP6_TNL tunnel ttl
+ * @arg link            Link object
+ *
+ * @return ttl value
+ */
+uint8_t rtnl_link_ip6_tnl_get_ttl(struct rtnl_link *link)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	return ip6_tnl->ttl;
+}
+
+/**
+ * Set IP6_TNL tunnel tos
+ * @arg link            Link object
+ * @arg tos             tunnel tos
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_set_tos(struct rtnl_link *link, uint8_t tos)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	ip6_tnl->tos = tos;
+	ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_TOS;
+
+	return 0;
+}
+
+/**
+ * Get IP6_TNL tunnel tos
+ * @arg link            Link object
+ *
+ * @return tos value
+ */
+uint8_t rtnl_link_ip6_tnl_get_tos(struct rtnl_link *link)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	return ip6_tnl->tos;
+}
+
+/**
+ * Set IP6_TNL tunnel encap limit
+ * @arg link            Link object
+ * @arg encap_limit     encaplimit value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_set_encaplimit(struct rtnl_link *link, uint8_t encap_limit)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	ip6_tnl->encap_limit = encap_limit;
+	ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_ENCAPLIMIT;
+
+	return 0;
+}
+
+/**
+ * Get IP6_TNL encaplimit
+ * @arg link            Link object
+ *
+ * @return encaplimit value
+ */
+uint8_t rtnl_link_ip6_tnl_get_encaplimit(struct rtnl_link *link)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	return ip6_tnl->encap_limit;
+}
+
+/**
+ * Set IP6_TNL tunnel flowinfo
+ * @arg link            Link object
+ * @arg flowinfo        flowinfo value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_set_flowinfo(struct rtnl_link *link, uint32_t flowinfo)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	ip6_tnl->flowinfo = flowinfo;
+	ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_FLOWINFO;
+
+	return 0;
+}
+
+/**
+ * Get IP6_TNL flowinfo
+ * @arg link            Link object
+ *
+ * @return flowinfo value
+ */
+uint32_t rtnl_link_ip6_tnl_get_flowinfo(struct rtnl_link *link)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	return ip6_tnl->flowinfo;
+}
+
+/**
+ * Set IP6_TNL tunnel flags
+ * @arg link            Link object
+ * @arg flags           tunnel flags
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_set_flags(struct rtnl_link *link, uint32_t flags)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	ip6_tnl->flags = flags;
+	ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_FLAGS;
+
+	return 0;
+}
+
+/**
+ * Get IP6_TNL path flags
+ * @arg link            Link object
+ *
+ * @return flags value
+ */
+uint32_t rtnl_link_ip6_tnl_get_flags(struct rtnl_link *link)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	return ip6_tnl->flags;
+}
+
+/**
+ * Set IP6_TNL tunnel proto
+ * @arg link            Link object
+ * @arg proto           tunnel proto
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ip6_tnl_set_proto(struct rtnl_link *link, uint8_t proto)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	ip6_tnl->proto = proto;
+	ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_PROTO;
+
+	return 0;
+}
+
+/**
+ * Get IP6_TNL proto
+ * @arg link            Link object
+ *
+ * @return proto value
+ */
+uint8_t rtnl_link_ip6_tnl_get_proto(struct rtnl_link *link)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	return ip6_tnl->proto;
+}
+
+static void __init ip6_tnl_init(void)
+{
+	rtnl_link_register_info(&ip6_tnl_info_ops);
+}
+
+static void __exit ip6_tnl_exit(void)
+{
+	rtnl_link_unregister_info(&ip6_tnl_info_ops);
+}
diff --git a/lib/route/link/ipgre.c b/lib/route/link/ipgre.c
new file mode 100644
index 0000000..74dbb9d
--- /dev/null
+++ b/lib/route/link/ipgre.c
@@ -0,0 +1,727 @@
+/*
+ * lib/route/link/ipgre.c        IPGRE Link Info
+ *
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation version 2.1
+ *      of the License.
+ *
+ * Copyright (c) 2014 Susant Sahani <susant@redhat.com>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup ipgre IPGRE
+ * ipgre link module
+ *
+ * @details
+ * \b Link Type Name: "ipgre"
+ *
+ * @route_doc{link_ipgre, IPGRE Documentation}
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink/object.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+#include <linux/if_tunnel.h>
+
+#define IPGRE_ATTR_LINK          (1 << 0)
+#define IPGRE_ATTR_IFLAGS        (1 << 1)
+#define IPGRE_ATTR_OFLAGS        (1 << 2)
+#define IPGRE_ATTR_IKEY          (1 << 3)
+#define IPGRE_ATTR_OKEY          (1 << 4)
+#define IPGRE_ATTR_LOCAL         (1 << 5)
+#define IPGRE_ATTR_REMOTE        (1 << 6)
+#define IPGRE_ATTR_TTL           (1 << 7)
+#define IPGRE_ATTR_TOS           (1 << 8)
+#define IPGRE_ATTR_PMTUDISC      (1 << 9)
+
+struct ipgre_info
+{
+	uint8_t    ttl;
+	uint8_t    tos;
+	uint8_t    pmtudisc;
+	uint16_t   iflags;
+	uint16_t   oflags;
+	uint32_t   ikey;
+	uint32_t   okey;
+	uint32_t   link;
+	uint32_t   local;
+	uint32_t   remote;
+	uint32_t   ipgre_mask;
+};
+
+static  struct nla_policy ipgre_policy[IFLA_GRE_MAX + 1] = {
+	[IFLA_GRE_LINK]     = { .type = NLA_U32 },
+	[IFLA_GRE_IFLAGS]   = { .type = NLA_U16 },
+	[IFLA_GRE_OFLAGS]   = { .type = NLA_U16 },
+	[IFLA_GRE_IKEY]     = { .type = NLA_U32 },
+	[IFLA_GRE_OKEY]     = { .type = NLA_U32 },
+	[IFLA_GRE_LOCAL]    = { .type = NLA_U32 },
+	[IFLA_GRE_REMOTE]   = { .type = NLA_U32 },
+	[IFLA_GRE_TTL]      = { .type = NLA_U8 },
+	[IFLA_GRE_TOS]      = { .type = NLA_U8 },
+	[IFLA_GRE_PMTUDISC] = { .type = NLA_U8 },
+};
+
+static int ipgre_alloc(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre;
+
+	ipgre = calloc(1, sizeof(*ipgre));
+	if (!ipgre)
+		return -NLE_NOMEM;
+
+	link->l_info = ipgre;
+
+	return 0;
+}
+
+static int ipgre_parse(struct rtnl_link *link, struct nlattr *data,
+		       struct nlattr *xstats)
+{
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
+	struct ipgre_info *ipgre;
+	int err;
+
+	NL_DBG(3, "Parsing IPGRE link info");
+
+	err = nla_parse_nested(tb, IFLA_GRE_MAX, data, ipgre_policy);
+	if (err < 0)
+		goto errout;
+
+	err = ipgre_alloc(link);
+	if (err < 0)
+		goto errout;
+
+	ipgre = link->l_info;
+
+	if (tb[IFLA_GRE_LINK]) {
+		ipgre->link = nla_get_u32(tb[IFLA_GRE_LINK]);
+		ipgre->ipgre_mask |= IPGRE_ATTR_LINK;
+	}
+
+	if (tb[IFLA_GRE_IFLAGS]) {
+		ipgre->iflags = nla_get_u16(tb[IFLA_GRE_IFLAGS]);
+		ipgre->ipgre_mask |= IPGRE_ATTR_IFLAGS;
+	}
+
+	if (tb[IFLA_GRE_OFLAGS]) {
+		ipgre->oflags = nla_get_u16(tb[IFLA_GRE_OFLAGS]);
+		ipgre->ipgre_mask |= IPGRE_ATTR_OFLAGS;
+	}
+
+	if (tb[IFLA_GRE_IKEY]) {
+		ipgre->ikey = nla_get_u32(tb[IFLA_GRE_IKEY]);
+		ipgre->ipgre_mask |= IPGRE_ATTR_IKEY;
+	}
+
+	if (tb[IFLA_GRE_OKEY]) {
+		ipgre->okey = nla_get_u32(tb[IFLA_GRE_OKEY]);
+		ipgre->ipgre_mask |= IPGRE_ATTR_OKEY;
+	}
+
+	if (tb[IFLA_GRE_LOCAL]) {
+		ipgre->local = nla_get_u32(tb[IFLA_GRE_LOCAL]);
+		ipgre->ipgre_mask |= IPGRE_ATTR_LOCAL;
+	}
+
+	if (tb[IFLA_GRE_LOCAL]) {
+		ipgre->remote = nla_get_u32(tb[IFLA_GRE_LOCAL]);
+		ipgre->ipgre_mask |= IPGRE_ATTR_REMOTE;
+	}
+
+	if (tb[IFLA_GRE_TTL]) {
+		ipgre->ttl = nla_get_u8(tb[IFLA_GRE_TTL]);
+		ipgre->ipgre_mask |= IPGRE_ATTR_TTL;
+	}
+
+	if (tb[IFLA_GRE_TOS]) {
+		ipgre->tos = nla_get_u8(tb[IFLA_GRE_TOS]);
+		ipgre->ipgre_mask |= IPGRE_ATTR_TOS;
+	}
+
+	if (tb[IFLA_GRE_PMTUDISC]) {
+		ipgre->pmtudisc = nla_get_u8(tb[IFLA_GRE_PMTUDISC]);
+		ipgre->ipgre_mask |= IPGRE_ATTR_PMTUDISC;
+	}
+
+	err = 0;
+
+ errout:
+	return err;
+}
+
+static int ipgre_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+	struct nlattr *data;
+
+	data = nla_nest_start(msg, IFLA_INFO_DATA);
+	if (!data)
+		return -NLE_MSGSIZE;
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_LINK)
+		NLA_PUT_U32(msg, IFLA_GRE_LINK, ipgre->link);
+
+	if (ipgre->ipgre_mask & IFLA_GRE_IFLAGS)
+		NLA_PUT_U16(msg, IFLA_GRE_IFLAGS, ipgre->iflags);
+
+	if (ipgre->ipgre_mask & IFLA_GRE_OFLAGS)
+		NLA_PUT_U16(msg, IFLA_GRE_OFLAGS, ipgre->oflags);
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_IKEY)
+		NLA_PUT_U32(msg, IFLA_GRE_IKEY, ipgre->ikey);
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_OKEY)
+		NLA_PUT_U32(msg, IFLA_GRE_OKEY, ipgre->okey);
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_LOCAL)
+		NLA_PUT_U32(msg, IFLA_GRE_LOCAL, ipgre->local);
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_REMOTE)
+		NLA_PUT_U32(msg, IFLA_GRE_REMOTE, ipgre->remote);
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_TTL)
+		NLA_PUT_U8(msg, IFLA_GRE_TTL, ipgre->ttl);
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_TOS)
+		NLA_PUT_U8(msg, IFLA_GRE_TOS, ipgre->tos);
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_PMTUDISC)
+		NLA_PUT_U8(msg, IFLA_GRE_PMTUDISC, ipgre->pmtudisc);
+
+	nla_nest_end(msg, data);
+
+ nla_put_failure:
+
+	return 0;
+}
+
+static void ipgre_free(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	free(ipgre);
+	link->l_info = NULL;
+}
+
+static void ipgre_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	nl_dump(p, "ipgre : %s", link->l_name);
+}
+
+static void ipgre_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	struct ipgre_info *ipgre = link->l_info;
+	char *name, addr[INET_ADDRSTRLEN];
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_LINK) {
+		nl_dump(p, "      link ");
+		name = rtnl_link_get_name(link);
+		if (name)
+			nl_dump_line(p, "%s\n", name);
+		else
+			nl_dump_line(p, "%u\n", ipgre->link);
+	}
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_IFLAGS) {
+		nl_dump(p, "      iflags ");
+		nl_dump_line(p, "%x\n", ipgre->iflags);
+	}
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_OFLAGS) {
+		nl_dump(p, "      oflags ");
+		nl_dump_line(p, "%x\n", ipgre->oflags);
+	}
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_IKEY) {
+		nl_dump(p, "    ikey   ");
+		nl_dump_line(p, "%x\n",ipgre->ikey);
+	}
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_OKEY) {
+		nl_dump(p, "      okey ");
+		nl_dump_line(p, "%x\n", ipgre->okey);
+	}
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_LOCAL) {
+		nl_dump(p, "      local ");
+		if(inet_ntop(AF_INET, &ipgre->local, addr, sizeof(addr)))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ntohs(ipgre->local));
+	}
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_REMOTE) {
+		nl_dump(p, "      remote ");
+		if(inet_ntop(AF_INET, &ipgre->remote, addr, sizeof(addr)))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ntohs(ipgre->remote));
+	}
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_TTL) {
+		nl_dump(p, "      ttl ");
+		nl_dump_line(p, "%u\n", ipgre->ttl);
+	}
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_TOS) {
+		nl_dump(p, "      tos ");
+		nl_dump_line(p, "%u\n", ipgre->tos);
+	}
+
+	if (ipgre->ipgre_mask & IPGRE_ATTR_PMTUDISC) {
+		nl_dump(p, "      pmtudisc ");
+		nl_dump_line(p, "enabled (%#x)\n", ipgre->pmtudisc);
+	}
+}
+
+static int ipgre_clone(struct rtnl_link *dst, struct rtnl_link *src)
+{
+	struct ipgre_info *ipgre_dst, *ipgre_src = src->l_info;
+	int err;
+
+	dst->l_info = NULL;
+
+	err = rtnl_link_set_type(dst, "gre");
+	if (err < 0)
+		return err;
+
+	ipgre_dst = dst->l_info;
+
+	if (!ipgre_dst || !ipgre_src)
+		BUG();
+
+	memcpy(ipgre_dst, ipgre_src, sizeof(struct ipgre_info));
+
+	return 0;
+}
+
+static struct rtnl_link_info_ops ipgre_info_ops = {
+	.io_name                = "gre",
+	.io_alloc               = ipgre_alloc,
+	.io_parse               = ipgre_parse,
+	.io_dump = {
+		[NL_DUMP_LINE]  = ipgre_dump_line,
+		[NL_DUMP_DETAILS] = ipgre_dump_details,
+	},
+	.io_clone               = ipgre_clone,
+	.io_put_attrs           = ipgre_put_attrs,
+	.io_free                = ipgre_free,
+};
+
+#define IS_IPGRE_LINK_ASSERT(link)                                          \
+        if ((link)->l_info_ops != &ipgre_info_ops) {                        \
+                APPBUG("Link is not a ipgre link. set type \"gre\" first.");\
+                return -NLE_OPNOTSUPP;                                      \
+        }
+
+struct rtnl_link *rtnl_link_ipgre_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_alloc();
+	if (!link)
+		return NULL;
+
+	err = rtnl_link_set_type(link, "gre");
+	if (err < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+
+/**
+ * Check if link is a IPGRE link
+ * @arg link            Link object
+ *
+ * @return True if link is a IPGRE link, otherwise 0 is returned.
+ */
+int rtnl_link_is_ipgre(struct rtnl_link *link)
+{
+	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "gre");
+}
+/**
+ * Create a new ipip tunnel device
+ * @arg sock            netlink socket
+ * @arg name            name of the tunnel deviceL
+ *
+ * Creates a new ipip tunnel device in the kernel
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_add(struct nl_sock *sk, const char *name)
+{
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_ipgre_alloc();
+	if (!link)
+		return -NLE_NOMEM;
+
+	if(name)
+		rtnl_link_set_name(link, name);
+
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	rtnl_link_put(link);
+
+	return err;
+}
+/**
+ * Set IPGRE tunnel interface index
+ * @arg link            Link object
+ * @arg index           interface index
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_set_link(struct rtnl_link *link,  uint32_t index)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	ipgre->link = index;
+	ipgre->ipgre_mask |= IPGRE_ATTR_LINK;
+
+	return 0;
+}
+
+/**
+ * Get IPGRE tunnel interface index
+ * @arg link            Link object
+ *
+ * @return interface index
+ */
+uint32_t rtnl_link_ipgre_get_link(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	return ipgre->link;
+}
+
+/**
+ * Set IPGRE tunnel set iflags
+ * @arg link            Link object
+ * @arg iflags          gre iflags
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_set_iflags(struct rtnl_link *link, uint16_t iflags)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	ipgre->iflags = iflags;
+	ipgre->ipgre_mask |= IPGRE_ATTR_IFLAGS;
+
+	return 0;
+}
+
+/**
+ * Get IPGRE tunnel iflags
+ * @arg link            Link object
+ *
+ * @return iflags
+ */
+uint16_t rtnl_link_ipgre_get_iflags(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	return ipgre->iflags;
+}
+
+/**
+ * Set IPGRE tunnel set oflags
+ * @arg link            Link object
+ * @arg iflags          gre oflags
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_set_oflags(struct rtnl_link *link, uint16_t oflags)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	ipgre->oflags = oflags;
+	ipgre->ipgre_mask |= IPGRE_ATTR_OFLAGS;
+
+	return 0;
+}
+
+/**
+ * Get IPGRE tunnel oflags
+ * @arg link            Link object
+ *
+ * @return oflags
+ */
+uint16_t rtnl_link_ipgre_get_oflags(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	return ipgre->oflags;
+}
+
+/**
+ * Set IPGRE tunnel set ikey
+ * @arg link            Link object
+ * @arg ikey            gre ikey
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_set_ikey(struct rtnl_link *link, uint32_t ikey)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	ipgre->ikey = ikey;
+	ipgre->ipgre_mask |= IPGRE_ATTR_IKEY;
+
+	return 0;
+}
+
+/**
+ * Get IPGRE tunnel ikey
+ * @arg link            Link object
+ *
+ * @return ikey
+ */
+uint32_t rtnl_link_ipgre_get_ikey(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	return ipgre->ikey;
+}
+
+/**
+ * Set IPGRE tunnel set okey
+ * @arg link            Link object
+ * @arg okey            gre okey
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_set_okey(struct rtnl_link *link, uint32_t okey)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	ipgre->okey = okey;
+	ipgre->ipgre_mask |= IPGRE_ATTR_OKEY;
+
+	return 0;
+}
+
+/**
+ * Get IPGRE tunnel okey
+ * @arg link            Link object
+ *
+ * @return okey value
+ */
+uint32_t rtnl_link_ipgre_get_okey(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	return ipgre->okey;
+}
+
+/**
+ * Set IPGRE tunnel local address
+ * @arg link            Link object
+ * @arg addr            local address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_set_local(struct rtnl_link *link, uint32_t addr)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	ipgre->local = addr;
+	ipgre->ipgre_mask |= IPGRE_ATTR_LOCAL;
+
+	return 0;
+}
+
+/**
+ * Get IPGRE tunnel local address
+ * @arg link            Link object
+ *
+ * @return local address
+ */
+uint32_t rtnl_link_ipgre_get_local(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	return ipgre->local;
+}
+
+/**
+ * Set IPGRE tunnel remote address
+ * @arg link            Link object
+ * @arg remote          remote address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_set_remote(struct rtnl_link *link, uint32_t remote)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	ipgre->remote = remote;
+	ipgre->ipgre_mask |= IPGRE_ATTR_REMOTE;
+
+	return 0;
+}
+
+/**
+ * Get IPGRE tunnel remote address
+ * @arg link            Link object
+ *
+ * @return remote address  on success or a negative error code
+ */
+uint32_t rtnl_link_ipgre_get_remote(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	return ipgre->remote;
+}
+
+/**
+ * Set IPGRE tunnel ttl
+ * @arg link            Link object
+ * @arg ttl             tunnel ttl
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_set_ttl(struct rtnl_link *link, uint8_t ttl)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	ipgre->ttl = ttl;
+	ipgre->ipgre_mask |= IPGRE_ATTR_TTL;
+
+	return 0;
+}
+
+/**
+ * Set IPGRE tunnel ttl
+ * @arg link            Link object
+ *
+ * @return ttl value
+ */
+uint8_t rtnl_link_ipgre_get_ttl(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	return ipgre->ttl;
+}
+
+/**
+ * Set IPGRE tunnel tos
+ * @arg link            Link object
+ * @arg tos             tunnel tos
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_set_tos(struct rtnl_link *link, uint8_t tos)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	ipgre->tos = tos;
+	ipgre->ipgre_mask |= IPGRE_ATTR_TOS;
+
+	return 0;
+}
+
+/**
+ * Get IPGRE tunnel tos
+ * @arg link            Link object
+ *
+ * @return tos value
+ */
+uint8_t rtnl_link_ipgre_get_tos(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	return ipgre->tos;
+}
+
+/**
+ * Set IPGRE tunnel path MTU discovery
+ * @arg link            Link object
+ * @arg pmtudisc        path MTU discovery
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipgre_set_pmtudisc(struct rtnl_link *link, uint8_t pmtudisc)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	ipgre->pmtudisc = pmtudisc;
+	ipgre->ipgre_mask |= IPGRE_ATTR_PMTUDISC;
+
+	return 0;
+}
+
+/**
+ * Get IPGRE path MTU discovery
+ * @arg link            Link object
+ *
+ * @return pmtudisc value
+ */
+uint8_t rtnl_link_get_pmtudisc(struct rtnl_link *link)
+{
+	struct ipgre_info *ipgre = link->l_info;
+
+	IS_IPGRE_LINK_ASSERT(link);
+
+	return ipgre->pmtudisc;
+}
+
+static void __init ipgre_init(void)
+{
+	rtnl_link_register_info(&ipgre_info_ops);
+}
+
+static void __exit ipgre_exit(void)
+{
+	rtnl_link_unregister_info(&ipgre_info_ops);
+}
diff --git a/lib/route/link/ipip.c b/lib/route/link/ipip.c
new file mode 100644
index 0000000..ecf86ad
--- /dev/null
+++ b/lib/route/link/ipip.c
@@ -0,0 +1,528 @@
+/*
+ * lib/route/link/ipip.c        IPIP Link Info
+ *
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation version 2.1
+ *      of the License.
+ *
+ * Copyright (c) 2014 Susant Sahani <susant@redhat.com>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup ipip IPIP
+ * ipip link module
+ *
+ * @details
+ * \b Link Type Name: "ipip"
+ *
+ * @route_doc{link_ipip, IPIP Documentation}
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink/object.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+#include <linux/if_tunnel.h>
+
+#define IPIP_ATTR_LINK          (1 << 0)
+#define IPIP_ATTR_LOCAL         (1 << 1)
+#define IPIP_ATTR_REMOTE        (1 << 2)
+#define IPIP_ATTR_TTL           (1 << 3)
+#define IPIP_ATTR_TOS           (1 << 4)
+#define IPIP_ATTR_PMTUDISC      (1 << 5)
+
+struct ipip_info
+{
+	uint8_t    ttl;
+	uint8_t    tos;
+	uint8_t    pmtudisc;
+	uint32_t   link;
+	uint32_t   local;
+	uint32_t   remote;
+	uint32_t   ipip_mask;
+};
+
+static struct nla_policy ipip_policy[IFLA_IPTUN_MAX + 1] = {
+	[IFLA_IPTUN_LINK]       = { .type = NLA_U32 },
+	[IFLA_IPTUN_LOCAL]      = { .type = NLA_U32 },
+	[IFLA_IPTUN_REMOTE]     = { .type = NLA_U32 },
+	[IFLA_IPTUN_TTL]        = { .type = NLA_U8 },
+	[IFLA_IPTUN_TOS]        = { .type = NLA_U8 },
+	[IFLA_IPTUN_PMTUDISC]   = { .type = NLA_U8 },
+};
+
+static int ipip_alloc(struct rtnl_link *link)
+{
+	struct ipip_info *ipip;
+
+	ipip = calloc(1, sizeof(*ipip));
+	if (!ipip)
+		return -NLE_NOMEM;
+
+	link->l_info = ipip;
+
+	return 0;
+}
+
+static int ipip_parse(struct rtnl_link *link, struct nlattr *data,
+                      struct nlattr *xstats)
+{
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
+	struct ipip_info *ipip;
+	int err;
+
+	NL_DBG(3, "Parsing IPIP link info");
+
+	err = nla_parse_nested(tb, IFLA_IPTUN_MAX, data, ipip_policy);
+	if (err < 0)
+		goto errout;
+
+	err = ipip_alloc(link);
+	if (err < 0)
+		goto errout;
+
+	ipip = link->l_info;
+
+	if (tb[IFLA_IPTUN_LINK]) {
+		ipip->link = nla_get_u32(tb[IFLA_IPTUN_LINK]);
+		ipip->ipip_mask |= IPIP_ATTR_LINK;
+	}
+
+	if (tb[IFLA_IPTUN_LOCAL]) {
+		ipip->local = nla_get_u32(tb[IFLA_IPTUN_LOCAL]);
+		ipip->ipip_mask |= IPIP_ATTR_LOCAL;
+	}
+
+	if (tb[IFLA_IPTUN_REMOTE]) {
+		ipip->remote = nla_get_u32(tb[IFLA_IPTUN_REMOTE]);
+		ipip->ipip_mask |= IPIP_ATTR_REMOTE;
+	}
+
+	if (tb[IFLA_IPTUN_TTL]) {
+		ipip->ttl = nla_get_u8(tb[IFLA_IPTUN_TTL]);
+		ipip->ipip_mask |= IPIP_ATTR_TTL;
+	}
+
+	if (tb[IFLA_IPTUN_TOS]) {
+		ipip->tos = nla_get_u8(tb[IFLA_IPTUN_TOS]);
+		ipip->ipip_mask |= IPIP_ATTR_TOS;
+	}
+
+	if (tb[IFLA_IPTUN_PMTUDISC]) {
+		ipip->pmtudisc = nla_get_u8(tb[IFLA_IPTUN_PMTUDISC]);
+		ipip->ipip_mask |= IPIP_ATTR_PMTUDISC;
+	}
+
+	err = 0;
+
+errout:
+	return err;
+}
+
+static int ipip_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
+{
+	struct ipip_info *ipip = link->l_info;
+	struct nlattr *data;
+
+	data = nla_nest_start(msg, IFLA_INFO_DATA);
+	if (!data)
+		return -NLE_MSGSIZE;
+
+	if (ipip->ipip_mask & IPIP_ATTR_LINK)
+		NLA_PUT_U32(msg, IFLA_IPTUN_LINK, ipip->link);
+
+	if (ipip->ipip_mask & IPIP_ATTR_LOCAL)
+		NLA_PUT_U32(msg, IFLA_IPTUN_LOCAL, ipip->local);
+
+	if (ipip->ipip_mask & IPIP_ATTR_REMOTE)
+		NLA_PUT_U32(msg, IFLA_IPTUN_REMOTE, ipip->remote);
+
+	if (ipip->ipip_mask & IPIP_ATTR_TTL)
+		NLA_PUT_U8(msg, IFLA_IPTUN_TTL, ipip->ttl);
+
+	if (ipip->ipip_mask & IPIP_ATTR_TOS)
+		NLA_PUT_U8(msg, IFLA_IPTUN_TOS, ipip->tos);
+
+	if (ipip->ipip_mask & IPIP_ATTR_PMTUDISC)
+		NLA_PUT_U8(msg, IFLA_IPTUN_PMTUDISC, ipip->pmtudisc);
+
+	nla_nest_end(msg, data);
+
+nla_put_failure:
+	return 0;
+}
+
+static void ipip_free(struct rtnl_link *link)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	free(ipip);
+	link->l_info = NULL;
+}
+
+static void ipip_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	nl_dump(p, "ipip : %s", link->l_name);
+}
+
+static void ipip_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	struct ipip_info *ipip = link->l_info;
+	char *name, addr[INET_ADDRSTRLEN];
+
+	if (ipip->ipip_mask & IPIP_ATTR_LINK) {
+		nl_dump(p, "      link ");
+		name = rtnl_link_get_name(link);
+		if (name)
+			nl_dump_line(p, "%s\n", name);
+		else
+			nl_dump_line(p, "%u\n", ipip->link);
+	}
+
+	if (ipip->ipip_mask & IPIP_ATTR_LOCAL) {
+		nl_dump(p, "      local ");
+		if(inet_ntop(AF_INET, &ipip->local, addr, sizeof(addr)))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ntohs(ipip->local));
+	}
+
+	if (ipip->ipip_mask & IPIP_ATTR_REMOTE) {
+		nl_dump(p, "      remote ");
+		if(inet_ntop(AF_INET, &ipip->remote, addr, sizeof(addr)))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ntohs(ipip->remote));
+	}
+
+	if (ipip->ipip_mask & IPIP_ATTR_TTL) {
+		nl_dump(p, "      ttl ");
+		nl_dump_line(p, "%u\n", ipip->ttl);
+	}
+
+	if (ipip->ipip_mask & IPIP_ATTR_TOS) {
+		nl_dump(p, "      tos ");
+		nl_dump_line(p, "%u\n", ipip->tos);
+	}
+
+	if (ipip->ipip_mask & IPIP_ATTR_PMTUDISC) {
+		nl_dump(p, "      pmtudisc ");
+		nl_dump_line(p, "enabled (%#x)\n", ipip->pmtudisc);
+	}
+}
+
+static int ipip_clone(struct rtnl_link *dst, struct rtnl_link *src)
+{
+	struct ipip_info *ipip_dst, *ipip_src = src->l_info;
+	int err;
+
+	dst->l_info = NULL;
+
+	err = rtnl_link_set_type(dst, "ipip");
+	if (err < 0)
+		return err;
+
+	ipip_dst = dst->l_info;
+
+	if (!ipip_dst || !ipip_src)
+		BUG();
+
+	memcpy(ipip_dst, ipip_src, sizeof(struct ipip_info));
+
+	return 0;
+}
+
+static struct rtnl_link_info_ops ipip_info_ops = {
+	.io_name                = "ipip",
+	.io_alloc               = ipip_alloc,
+	.io_parse               = ipip_parse,
+	.io_dump = {
+		[NL_DUMP_LINE]  = ipip_dump_line,
+		[NL_DUMP_DETAILS] = ipip_dump_details,
+	},
+	.io_clone               = ipip_clone,
+	.io_put_attrs           = ipip_put_attrs,
+	.io_free                = ipip_free,
+};
+
+#define IS_IPIP_LINK_ASSERT(link)                                            \
+        if ((link)->l_info_ops != &ipip_info_ops) {                          \
+                APPBUG("Link is not a ipip link. set type \"ipip\" first."); \
+                return -NLE_OPNOTSUPP;                                       \
+        }
+
+struct rtnl_link *rtnl_link_ipip_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_alloc();
+	if (!link)
+		return NULL;
+
+	err = rtnl_link_set_type(link, "ipip");
+	if (err < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+
+/**
+ * Check if link is a IPIP link
+ * @arg link            Link object
+ *
+ * @return True if link is a IPIP link, otherwise false is returned.
+ */
+int rtnl_link_is_ipip(struct rtnl_link *link)
+{
+	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "ipip");
+}
+
+/**
+ * Create a new ipip tunnel device
+ * @arg sock            netlink socket
+ * @arg name            name of the tunnel deviceL
+ *
+ * Creates a new ipip tunnel device in the kernel
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipip_add(struct nl_sock *sk, const char *name)
+{
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_ipip_alloc();
+	if (!link)
+		return -NLE_NOMEM;
+
+	if(name)
+		rtnl_link_set_name(link, name);
+
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	rtnl_link_put(link);
+
+	return err;
+}
+
+/**
+ * Set IPIP tunnel interface index
+ * @arg link            Link object
+ * @arg index           interface index
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipip_set_link(struct rtnl_link *link,  uint32_t index)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	ipip->link = index;
+	ipip->ipip_mask |= IPIP_ATTR_LINK;
+
+	return 0;
+}
+
+/**
+ * Get IPIP tunnel interface index
+ * @arg link            Link object
+ *
+ * @return interface index value
+ */
+uint32_t rtnl_link_ipip_get_link(struct rtnl_link *link)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	return ipip->link;
+}
+
+/**
+ * Set IPIP tunnel local address
+ * @arg link            Link object
+ * @arg addr            local address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipip_set_local(struct rtnl_link *link, uint32_t addr)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	ipip->local = addr;
+	ipip->ipip_mask |= IPIP_ATTR_LOCAL;
+
+	return 0;
+}
+
+/**
+ * Get IPIP tunnel local address
+ * @arg link            Link object
+ *
+ * @return local address value
+ */
+uint32_t rtnl_link_ipip_get_local(struct rtnl_link *link)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	return ipip->local;
+}
+
+/**
+ * Set IPIP tunnel remote address
+ * @arg link            Link object
+ * @arg remote          remote address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipip_set_remote(struct rtnl_link *link, uint32_t addr)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	ipip->remote = addr;
+	ipip->ipip_mask |= IPIP_ATTR_REMOTE;
+
+	return 0;
+}
+
+/**
+ * Get IPIP tunnel remote address
+ * @arg link            Link object
+ *
+ * @return remote address
+ */
+uint32_t rtnl_link_ipip_get_remote(struct rtnl_link *link)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	return ipip->remote;
+}
+
+/**
+ * Set IPIP tunnel ttl
+ * @arg link            Link object
+ * @arg ttl             tunnel ttl
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipip_set_ttl(struct rtnl_link *link, uint8_t ttl)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	ipip->ttl = ttl;
+	ipip->ipip_mask |= IPIP_ATTR_TTL;
+
+	return 0;
+}
+
+/**
+ * Get IPIP tunnel ttl
+ * @arg link            Link object
+ *
+ * @return ttl value
+ */
+uint8_t rtnl_link_ipip_get_ttl(struct rtnl_link *link)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	return ipip->ttl;
+}
+
+/**
+ * Set IPIP tunnel tos
+ * @arg link            Link object
+ * @arg tos             tunnel tos
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipip_set_tos(struct rtnl_link *link, uint8_t tos)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	ipip->tos = tos;
+	ipip->ipip_mask |= IPIP_ATTR_TOS;
+
+	return 0;
+}
+
+/**
+ * Get IPIP tunnel tos
+ * @arg link            Link object
+ *
+ * @return tos value
+ */
+uint8_t rtnl_link_ipip_get_tos(struct rtnl_link *link)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	return ipip->tos;
+}
+
+/**
+ * Set IPIP tunnel path MTU discovery
+ * @arg link            Link object
+ * @arg pmtudisc        path MTU discovery
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipip_set_pmtudisc(struct rtnl_link *link, uint8_t pmtudisc)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	ipip->pmtudisc = pmtudisc;
+	ipip->ipip_mask |= IPIP_ATTR_PMTUDISC;
+
+	return 0;
+}
+
+/**
+ * Get IPIP path MTU discovery
+ * @arg link            Link object
+ *
+ * @return pmtudisc value
+ */
+uint8_t rtnl_link_ipip_get_pmtudisc(struct rtnl_link *link)
+{
+	struct ipip_info *ipip = link->l_info;
+
+	IS_IPIP_LINK_ASSERT(link);
+
+	return ipip->pmtudisc;
+}
+
+static void __init ipip_init(void)
+{
+	rtnl_link_register_info(&ipip_info_ops);
+}
+
+static void __exit ipip_exit(void)
+{
+	rtnl_link_unregister_info(&ipip_info_ops);
+}
diff --git a/lib/route/link/ipvti.c b/lib/route/link/ipvti.c
new file mode 100644
index 0000000..71f61c3
--- /dev/null
+++ b/lib/route/link/ipvti.c
@@ -0,0 +1,477 @@
+ /*
+ * lib/route/link/ipvti.c	 IPVTI Link Info
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2014 Susant Sahani <susant@redhat.com>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup ipvti IPVTI
+ * ipvti link module
+ *
+ * @details
+ * \b Link Type Name: "ipvti"
+ *
+ * @route_doc{link_ipvti, IPVTI Documentation}
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink/object.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+#include <linux/if_tunnel.h>
+
+#define IPVTI_ATTR_LINK		 (1 << 0)
+#define IPVTI_ATTR_IKEY		 (1 << 1)
+#define IPVTI_ATTR_OKEY		 (1 << 2)
+#define IPVTI_ATTR_LOCAL	 (1 << 3)
+#define IPVTI_ATTR_REMOTE	 (1 << 4)
+
+struct ipvti_info
+{
+	uint32_t   link;
+	uint32_t   ikey;
+	uint32_t   okey;
+	uint32_t   local;
+	uint32_t   remote;
+	uint32_t   ipvti_mask;
+};
+
+static	struct nla_policy ipvti_policy[IFLA_GRE_MAX + 1] = {
+	[IFLA_VTI_LINK]     = { .type = NLA_U32 },
+	[IFLA_VTI_IKEY]     = { .type = NLA_U32 },
+	[IFLA_VTI_OKEY]     = { .type = NLA_U32 },
+	[IFLA_VTI_LOCAL]    = { .type = NLA_U32 },
+	[IFLA_VTI_REMOTE]   = { .type = NLA_U32 },
+};
+
+static int ipvti_alloc(struct rtnl_link *link)
+{
+	struct ipvti_info *ipvti;
+
+	ipvti = calloc(1, sizeof(*ipvti));
+	if (!ipvti)
+		return -NLE_NOMEM;
+
+	link->l_info = ipvti;
+
+	return 0;
+}
+
+static int ipvti_parse(struct rtnl_link *link, struct nlattr *data,
+		       struct nlattr *xstats)
+{
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
+	struct ipvti_info *ipvti;
+	int err;
+
+	NL_DBG(3, "Parsing IPVTI link info");
+
+	err = nla_parse_nested(tb, IFLA_GRE_MAX, data, ipvti_policy);
+	if (err < 0)
+		goto errout;
+
+	err = ipvti_alloc(link);
+	if (err < 0)
+		goto errout;
+
+	ipvti = link->l_info;
+
+	if (tb[IFLA_VTI_LINK]) {
+		ipvti->link = nla_get_u32(tb[IFLA_VTI_LINK]);
+		ipvti->ipvti_mask |= IPVTI_ATTR_LINK;
+	}
+
+	if (tb[IFLA_VTI_IKEY]) {
+		ipvti->ikey = nla_get_u32(tb[IFLA_VTI_IKEY]);
+		ipvti->ipvti_mask |= IPVTI_ATTR_IKEY;
+	}
+
+	if (tb[IFLA_VTI_OKEY]) {
+		ipvti->okey = nla_get_u32(tb[IFLA_VTI_OKEY]);
+		ipvti->ipvti_mask |= IPVTI_ATTR_OKEY;
+	}
+
+	if (tb[IFLA_VTI_LOCAL]) {
+		ipvti->local = nla_get_u32(tb[IFLA_VTI_LOCAL]);
+		ipvti->ipvti_mask |= IPVTI_ATTR_LOCAL;
+	}
+
+	if (tb[IFLA_VTI_REMOTE]) {
+		ipvti->remote = nla_get_u32(tb[IFLA_VTI_REMOTE]);
+		ipvti->ipvti_mask |= IPVTI_ATTR_REMOTE;
+	}
+
+	err = 0;
+
+ errout:
+	return err;
+}
+
+static int ipvti_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
+{
+	struct ipvti_info *ipvti = link->l_info;
+	struct nlattr *data;
+
+	data = nla_nest_start(msg, IFLA_INFO_DATA);
+	if (!data)
+		return -NLE_MSGSIZE;
+
+	if (ipvti->ipvti_mask & IPVTI_ATTR_LINK)
+		NLA_PUT_U32(msg, IFLA_VTI_LINK, ipvti->link);
+
+	if (ipvti->ipvti_mask & IPVTI_ATTR_IKEY)
+		NLA_PUT_U32(msg, IFLA_VTI_IKEY, ipvti->ikey);
+
+	if (ipvti->ipvti_mask & IFLA_VTI_IKEY)
+		NLA_PUT_U32(msg, IFLA_VTI_OKEY, ipvti->okey);
+
+	if (ipvti->ipvti_mask & IPVTI_ATTR_LOCAL)
+		NLA_PUT_U32(msg, IFLA_VTI_LOCAL, ipvti->local);
+
+	if (ipvti->ipvti_mask & IPVTI_ATTR_REMOTE)
+		NLA_PUT_U32(msg, IFLA_VTI_REMOTE, ipvti->remote);
+
+	nla_nest_end(msg, data);
+
+nla_put_failure:
+
+	return 0;
+}
+
+static void ipvti_free(struct rtnl_link *link)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	free(ipvti);
+	link->l_info = NULL;
+}
+
+static void ipvti_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	nl_dump(p, "ipvti : %s", link->l_name);
+}
+
+static void ipvti_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	struct ipvti_info *ipvti = link->l_info;
+	char *name, addr[INET_ADDRSTRLEN];
+
+	if (ipvti->ipvti_mask & IPVTI_ATTR_LINK) {
+		nl_dump(p, "      link ");
+		name = rtnl_link_get_name(link);
+		if (name)
+			nl_dump_line(p, "%s\n", name);
+		else
+			nl_dump_line(p, "%u\n", ipvti->link);
+	}
+
+	if (ipvti->ipvti_mask & IPVTI_ATTR_IKEY) {
+		nl_dump(p, "      ikey   ");
+		nl_dump_line(p, "%x\n",ipvti->ikey);
+	}
+
+	if (ipvti->ipvti_mask & IPVTI_ATTR_OKEY) {
+		nl_dump(p, "      okey ");
+		nl_dump_line(p, "%x\n", ipvti->okey);
+	}
+
+	if (ipvti->ipvti_mask & IPVTI_ATTR_LOCAL) {
+		nl_dump(p, "      local ");
+		if(inet_ntop(AF_INET, &ipvti->local, addr, sizeof(addr)))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ntohs(ipvti->local));
+	}
+
+	if (ipvti->ipvti_mask & IPVTI_ATTR_REMOTE) {
+		nl_dump(p, "      remote ");
+		if(inet_ntop(AF_INET, &ipvti->remote, addr, sizeof(addr)))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ntohs(ipvti->remote));
+	}
+}
+
+static int ipvti_clone(struct rtnl_link *dst, struct rtnl_link *src)
+{
+	struct ipvti_info *ipvti_dst, *ipvti_src = src->l_info;
+	int err;
+
+	dst->l_info = NULL;
+
+	err = rtnl_link_set_type(dst, "vti");
+	if (err < 0)
+		return err;
+
+	ipvti_dst = dst->l_info;
+
+	if (!ipvti_dst || !ipvti_src)
+		BUG();
+
+	memcpy(ipvti_dst, ipvti_src, sizeof(struct ipvti_info));
+
+	return 0;
+}
+
+static struct rtnl_link_info_ops ipvti_info_ops = {
+	.io_name                = "vti",
+	.io_alloc               = ipvti_alloc,
+	.io_parse               = ipvti_parse,
+	.io_dump = {
+		[NL_DUMP_LINE]  = ipvti_dump_line,
+		[NL_DUMP_DETAILS] = ipvti_dump_details,
+	},
+	.io_clone               = ipvti_clone,
+	.io_put_attrs           = ipvti_put_attrs,
+	.io_free                = ipvti_free,
+};
+
+#define IS_IPVTI_LINK_ASSERT(link)                                          \
+        if ((link)->l_info_ops != &ipvti_info_ops) {                        \
+                APPBUG("Link is not a ipvti link. set type \vti\" first."); \
+                return -NLE_OPNOTSUPP;                                      \
+        }
+
+struct rtnl_link *rtnl_link_ipvti_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_alloc();
+	if (!link)
+		return NULL;
+
+	err = rtnl_link_set_type(link, "vti");
+	if (err < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+
+/**
+ * Check if link is a IPVTI link
+ * @arg link            Link object
+ *
+ * @return True if link is a IPVTI link, otherwise 0 is returned.
+ */
+int rtnl_link_is_ipvti(struct rtnl_link *link)
+{
+	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "vti");
+}
+/**
+ * Create a new ipvti tunnel device
+ * @arg sock            netlink socket
+ * @arg name            name of the tunnel deviceL
+ *
+ * Creates a new ipvti tunnel device in the kernel
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipvti_add(struct nl_sock *sk, const char *name)
+{
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_ipvti_alloc();
+	if (!link)
+		return -NLE_NOMEM;
+
+	if(name)
+		rtnl_link_set_name(link, name);
+
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	rtnl_link_put(link);
+
+	return err;
+}
+/**
+ * Set IPVTI tunnel interface index
+ * @arg link            Link object
+ * @arg index           interface index
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipvti_set_link(struct rtnl_link *link, uint32_t index)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	IS_IPVTI_LINK_ASSERT(link);
+
+	ipvti->link = index;
+	ipvti->ipvti_mask |= IPVTI_ATTR_LINK;
+
+	return 0;
+}
+
+/**
+ * Get IPVTI tunnel interface index
+ * @arg link            Link object
+ *
+ * @return interface index
+ */
+uint32_t rtnl_link_ipvti_get_link(struct rtnl_link *link)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	IS_IPVTI_LINK_ASSERT(link);
+
+	return ipvti->link;
+}
+
+/**
+ * Set IPVTI tunnel set ikey
+ * @arg link            Link object
+ * @arg ikey            gre ikey
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipvti_set_ikey(struct rtnl_link *link, uint32_t ikey)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	IS_IPVTI_LINK_ASSERT(link);
+
+	ipvti->ikey = ikey;
+	ipvti->ipvti_mask |= IPVTI_ATTR_IKEY;
+
+	return 0;
+}
+
+/**
+ * Get IPVTI tunnel ikey
+ * @arg link            Link object
+ *
+ * @return ikey
+ */
+uint32_t rtnl_link_ipvti_get_ikey(struct rtnl_link *link)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	IS_IPVTI_LINK_ASSERT(link);
+
+	return ipvti->ikey;
+}
+
+/**
+ * Set IPVTI tunnel set okey
+ * @arg link            Link object
+ * @arg okey            gre okey
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipvti_set_okey(struct rtnl_link *link, uint32_t okey)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	IS_IPVTI_LINK_ASSERT(link);
+
+	ipvti->okey = okey;
+	ipvti->ipvti_mask |= IPVTI_ATTR_OKEY;
+
+	return 0;
+}
+
+/**
+ * Get IPVTI tunnel okey
+ * @arg link            Link object
+ *
+ * @return okey value
+ */
+uint32_t rtnl_link_ipvti_get_okey(struct rtnl_link *link)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	IS_IPVTI_LINK_ASSERT(link);
+
+	return ipvti->okey;
+}
+
+/**
+ * Set IPVTI tunnel local address
+ * @arg link            Link object
+ * @arg addr            local address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipvti_set_local(struct rtnl_link *link, uint32_t addr)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	IS_IPVTI_LINK_ASSERT(link);
+
+	ipvti->local = addr;
+	ipvti->ipvti_mask |= IPVTI_ATTR_LOCAL;
+
+	return 0;
+}
+
+/**
+ * Get IPVTI tunnel local address
+ * @arg link            Link object
+ *
+ * @return local address
+ */
+uint32_t rtnl_link_ipvti_get_local(struct rtnl_link *link)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	IS_IPVTI_LINK_ASSERT(link);
+
+	return ipvti->local;
+}
+
+/**
+ * Set IPVTI tunnel remote address
+ * @arg link            Link object
+ * @arg remote          remote address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_ipvti_set_remote(struct rtnl_link *link, uint32_t remote)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	IS_IPVTI_LINK_ASSERT(link);
+
+	ipvti->remote = remote;
+	ipvti->ipvti_mask |= IPVTI_ATTR_REMOTE;
+
+	return 0;
+}
+
+/**
+ * Get IPVTI tunnel remote address
+ * @arg link            Link object
+ *
+ * @return remote address  on success or a negative error code
+ */
+uint32_t rtnl_link_ipvti_get_remote(struct rtnl_link *link)
+{
+	struct ipvti_info *ipvti = link->l_info;
+
+	IS_IPVTI_LINK_ASSERT(link);
+
+	return ipvti->remote;
+}
+
+static void __init ipvti_init(void)
+{
+	rtnl_link_register_info(&ipvti_info_ops);
+}
+
+static void __exit ipvti_exit(void)
+{
+	rtnl_link_unregister_info(&ipvti_info_ops);
+}
diff --git a/lib/route/link/macvlan.c b/lib/route/link/macvlan.c
new file mode 100644
index 0000000..2340903
--- /dev/null
+++ b/lib/route/link/macvlan.c
@@ -0,0 +1,367 @@
+/*
+ * lib/route/link/macvlan.c	MACVLAN Link Info
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Michael Braun <michael-dev@fami-braun.de>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup macvlan MACVLAN
+ * MAC-based Virtual LAN link module
+ *
+ * @details
+ * \b Link Type Name: "macvlan"
+ *
+ * @route_doc{link_macvlan, MACVLAN Documentation}
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink/object.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+#include <netlink/route/link/macvlan.h>
+
+#include <linux/if_link.h>
+
+/** @cond SKIP */
+#define MACVLAN_HAS_MODE	(1<<0)
+#define MACVLAN_HAS_FLAGS	(1<<1)
+
+struct macvlan_info
+{
+	uint32_t		mvi_mode;
+	uint16_t		mvi_flags; // there currently is only one flag and kernel has no flags_mask yet
+	uint32_t		mvi_mask;
+};
+
+/** @endcond */
+
+static struct nla_policy macvlan_policy[IFLA_MACVLAN_MAX+1] = {
+	[IFLA_MACVLAN_MODE]	= { .type = NLA_U32 },
+	[IFLA_MACVLAN_FLAGS]	= { .type = NLA_U16 },
+};
+
+static int macvlan_alloc(struct rtnl_link *link)
+{
+	struct macvlan_info *mvi;
+
+	if ((mvi = calloc(1, sizeof(*mvi))) == NULL)
+		return -NLE_NOMEM;
+
+	link->l_info = mvi;
+
+	return 0;
+}
+
+static int macvlan_parse(struct rtnl_link *link, struct nlattr *data,
+                         struct nlattr *xstats)
+{
+	struct nlattr *tb[IFLA_MACVLAN_MAX+1];
+	struct macvlan_info *mvi;
+	int err;
+
+	NL_DBG(3, "Parsing MACVLAN link info");
+
+	if ((err = nla_parse_nested(tb, IFLA_MACVLAN_MAX, data, macvlan_policy)) < 0)
+		goto errout;
+
+	if ((err = macvlan_alloc(link)) < 0)
+		goto errout;
+
+	mvi = link->l_info;
+
+	if (tb[IFLA_MACVLAN_MODE]) {
+		mvi->mvi_mode = nla_get_u32(tb[IFLA_MACVLAN_MODE]);
+		mvi->mvi_mask |= MACVLAN_HAS_MODE;
+	}
+
+	if (tb[IFLA_MACVLAN_FLAGS]) {
+		mvi->mvi_mode = nla_get_u16(tb[IFLA_MACVLAN_FLAGS]);
+		mvi->mvi_mask |= MACVLAN_HAS_FLAGS;
+	}
+
+	err = 0;
+errout:
+	return err;
+}
+
+static void macvlan_free(struct rtnl_link *link)
+{
+	free(link->l_info);
+	link->l_info = NULL;
+}
+
+static void macvlan_dump(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	char buf[64];
+	struct macvlan_info *mvi = link->l_info;
+
+	if (mvi->mvi_mask & MACVLAN_HAS_MODE) {
+		rtnl_link_macvlan_mode2str(mvi->mvi_mode, buf, sizeof(buf));
+		nl_dump(p, "macvlan-mode %s", buf);
+	}
+
+	if (mvi->mvi_mask & MACVLAN_HAS_FLAGS) {
+		rtnl_link_macvlan_flags2str(mvi->mvi_flags, buf, sizeof(buf));
+		nl_dump(p, "macvlan-flags %s", buf);
+	}
+}
+
+static int macvlan_clone(struct rtnl_link *dst, struct rtnl_link *src)
+{
+	struct macvlan_info *vdst, *vsrc = src->l_info;
+	int err;
+
+	dst->l_info = NULL;
+	if ((err = rtnl_link_set_type(dst, "macvlan")) < 0)
+		return err;
+	vdst = dst->l_info;
+
+	if (!vdst || !vsrc)
+		return -NLE_NOMEM;
+
+	memcpy(vdst, vsrc, sizeof(struct macvlan_info));
+
+	return 0;
+}
+
+static int macvlan_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
+{
+	struct macvlan_info *mvi = link->l_info;
+	struct nlattr *data;
+
+	if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
+		return -NLE_MSGSIZE;
+
+	if (mvi->mvi_mask & MACVLAN_HAS_MODE)
+		NLA_PUT_U32(msg, IFLA_MACVLAN_MODE, mvi->mvi_mode);
+
+	if (mvi->mvi_mask & MACVLAN_HAS_FLAGS)
+		NLA_PUT_U16(msg, IFLA_MACVLAN_FLAGS, mvi->mvi_flags);
+
+	nla_nest_end(msg, data);
+
+nla_put_failure:
+
+	return 0;
+}
+
+static struct rtnl_link_info_ops macvlan_info_ops = {
+	.io_name		= "macvlan",
+	.io_alloc		= macvlan_alloc,
+	.io_parse		= macvlan_parse,
+	.io_dump = {
+	    [NL_DUMP_LINE]	= macvlan_dump,
+	    [NL_DUMP_DETAILS]	= macvlan_dump,
+	},
+	.io_clone		= macvlan_clone,
+	.io_put_attrs		= macvlan_put_attrs,
+	.io_free		= macvlan_free,
+};
+
+/** @cond SKIP */
+#define IS_MACVLAN_LINK_ASSERT(link) \
+	if ((link)->l_info_ops != &macvlan_info_ops) { \
+		APPBUG("Link is not a macvlan link. set type \"macvlan\" first."); \
+		return -NLE_OPNOTSUPP; \
+	}
+/** @endcond */
+
+/**
+ * @name MACVLAN Object
+ * @{
+ */
+
+/**
+ * Allocate link object of type MACVLAN
+ *
+ * @return Allocated link object or NULL.
+ */
+struct rtnl_link *rtnl_link_macvlan_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	if (!(link = rtnl_link_alloc()))
+		return NULL;
+
+	if ((err = rtnl_link_set_type(link, "macvlan")) < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+
+/**
+ * Check if link is a MACVLAN link
+ * @arg link		Link object
+ *
+ * @return True if link is a MACVLAN link, otherwise false is returned.
+ */
+int rtnl_link_is_macvlan(struct rtnl_link *link)
+{
+	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "macvlan");
+}
+
+/**
+ * Set MACVLAN MODE
+ * @arg link		Link object
+ * @arg mode		MACVLAN mode
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_macvlan_set_mode(struct rtnl_link *link, uint32_t mode)
+{
+	struct macvlan_info *mvi = link->l_info;
+
+	IS_MACVLAN_LINK_ASSERT(link);
+
+	mvi->mvi_mode = mode;
+	mvi->mvi_mask |= MACVLAN_HAS_MODE;
+
+	return 0;
+}
+
+/**
+ * Get MACVLAN Mode
+ * @arg link		Link object
+ *
+ * @return MACVLAN mode, 0 if not set or a negative error code.
+ */
+uint32_t rtnl_link_macvlan_get_mode(struct rtnl_link *link)
+{
+	struct macvlan_info *mvi = link->l_info;
+
+	IS_MACVLAN_LINK_ASSERT(link);
+
+	if (mvi->mvi_mask & MACVLAN_HAS_MODE)
+		return mvi->mvi_mode;
+	else
+		return 0;
+}
+
+/**
+ * Set MACVLAN flags
+ * @arg link		Link object
+ * @arg flags		MACVLAN flags
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_macvlan_set_flags(struct rtnl_link *link, uint16_t flags)
+{
+	struct macvlan_info *mvi = link->l_info;
+
+	IS_MACVLAN_LINK_ASSERT(link);
+
+	mvi->mvi_flags |= flags;
+	mvi->mvi_mask |= MACVLAN_HAS_FLAGS;
+
+	return 0;
+}
+
+/**
+ * Unset MACVLAN flags
+ * @arg link		Link object
+ * @arg flags		MACVLAN flags
+ *
+ * Note: kernel currently only has a single flag and lacks flags_mask to
+ * indicate which flags shall be changed (it always all).
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_link_macvlan_unset_flags(struct rtnl_link *link, uint16_t flags)
+{
+	struct macvlan_info *mvi = link->l_info;
+
+	IS_MACVLAN_LINK_ASSERT(link);
+
+	mvi->mvi_flags &= ~flags;
+	mvi->mvi_mask |= MACVLAN_HAS_FLAGS;
+
+	return 0;
+}
+
+/**
+ * Get MACVLAN flags
+ * @arg link		Link object
+ *
+ * @return MACVLAN flags, 0 if none set, or a negative error code.
+ */
+uint16_t rtnl_link_macvlan_get_flags(struct rtnl_link *link)
+{
+	struct macvlan_info *mvi = link->l_info;
+
+	IS_MACVLAN_LINK_ASSERT(link);
+
+	return mvi->mvi_flags;
+}
+
+/** @} */
+
+static const struct trans_tbl macvlan_flags[] = {
+	__ADD(MACVLAN_FLAG_NOPROMISC, nopromisc)
+};
+
+static const struct trans_tbl macvlan_modes[] = {
+	__ADD(MACVLAN_MODE_PRIVATE, private)
+	__ADD(MACVLAN_MODE_VEPA, vepa)
+	__ADD(MACVLAN_MODE_BRIDGE, bridge)
+	__ADD(MACVLAN_MODE_PASSTHRU, passthru)
+};
+
+/**
+ * @name Flag Translation
+ * @{
+ */
+
+char *rtnl_link_macvlan_flags2str(int flags, char *buf, size_t len)
+{
+	return __flags2str(flags, buf, len, macvlan_flags, ARRAY_SIZE(macvlan_flags));
+}
+
+int rtnl_link_macvlan_str2flags(const char *name)
+{
+	return __str2flags(name, macvlan_flags, ARRAY_SIZE(macvlan_flags));
+}
+
+/** @} */
+
+/**
+ * @name Mode Translation
+ * @{
+ */
+
+char *rtnl_link_macvlan_mode2str(int mode, char *buf, size_t len)
+{
+	return __type2str(mode, buf, len, macvlan_modes, ARRAY_SIZE(macvlan_modes));
+}
+
+int rtnl_link_macvlan_str2mode(const char *name)
+{
+	return __str2type(name, macvlan_modes, ARRAY_SIZE(macvlan_modes));
+}
+
+/** @} */
+
+static void __init macvlan_init(void)
+{
+	rtnl_link_register_info(&macvlan_info_ops);
+}
+
+static void __exit macvlan_exit(void)
+{
+	rtnl_link_unregister_info(&macvlan_info_ops);
+}
+
+/** @} */
diff --git a/lib/route/link/sit.c b/lib/route/link/sit.c
new file mode 100644
index 0000000..694c177
--- /dev/null
+++ b/lib/route/link/sit.c
@@ -0,0 +1,624 @@
+/*
+ * lib/route/link/sit.c        SIT Link Info
+ *
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation version 2.1
+ *      of the License.
+ *
+ * Copyright (c) 2014 Susant Sahani <susant@redhat.com>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup sit SIT
+ * sit link module
+ *
+ * @details
+ * \b Link Type Name: "sit"
+ *
+ * @route_doc{link_sit, SIT Documentation}
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink/object.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+#include <linux/if_tunnel.h>
+
+#define SIT_ATTR_LINK          (1 << 0)
+#define SIT_ATTR_LOCAL         (1 << 1)
+#define SIT_ATTR_REMOTE        (1 << 2)
+#define SIT_ATTR_TTL           (1 << 3)
+#define SIT_ATTR_TOS           (1 << 4)
+#define SIT_ATTR_PMTUDISC      (1 << 5)
+#define SIT_ATTR_FLAGS         (1 << 6)
+#define SIT_ATTR_PROTO         (1 << 7)
+
+struct sit_info
+{
+	uint8_t    ttl;
+	uint8_t    tos;
+	uint8_t    pmtudisc;
+	uint8_t    proto;
+	uint16_t   flags;
+	uint32_t   link;
+	uint32_t   local;
+	uint32_t   remote;
+	uint32_t   sit_mask;
+};
+
+static struct nla_policy sit_policy[IFLA_IPTUN_MAX + 1] = {
+	[IFLA_IPTUN_LINK]       = { .type = NLA_U32 },
+	[IFLA_IPTUN_LOCAL]      = { .type = NLA_U32 },
+	[IFLA_IPTUN_REMOTE]     = { .type = NLA_U32 },
+	[IFLA_IPTUN_TTL]        = { .type = NLA_U8 },
+	[IFLA_IPTUN_TOS]        = { .type = NLA_U8 },
+	[IFLA_IPTUN_PMTUDISC]   = { .type = NLA_U8 },
+	[IFLA_IPTUN_FLAGS]      = { .type = NLA_U16 },
+	[IFLA_IPTUN_PROTO]      = { .type = NLA_U8 },
+};
+
+static int sit_alloc(struct rtnl_link *link)
+{
+	struct sit_info *sit;
+
+	sit = calloc(1, sizeof(*sit));
+	if (!sit)
+		return -NLE_NOMEM;
+
+	link->l_info = sit;
+
+	return 0;
+}
+
+static int sit_parse(struct rtnl_link *link, struct nlattr *data,
+		     struct nlattr *xstats)
+{
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
+	struct sit_info *sit;
+	int err;
+
+	NL_DBG(3, "Parsing SIT link info");
+
+	err = nla_parse_nested(tb, IFLA_IPTUN_MAX, data, sit_policy);
+	if (err < 0)
+		goto errout;
+
+	err = sit_alloc(link);
+	if (err < 0)
+		goto errout;
+
+	sit = link->l_info;
+
+	if (tb[IFLA_IPTUN_LINK]) {
+		sit->link = nla_get_u32(tb[IFLA_IPTUN_LINK]);
+		sit->sit_mask |= SIT_ATTR_LINK;
+	}
+
+	if (tb[IFLA_IPTUN_LOCAL]) {
+		sit->local = nla_get_u32(tb[IFLA_IPTUN_LOCAL]);
+		sit->sit_mask |= SIT_ATTR_LOCAL;
+	}
+
+	if (tb[IFLA_IPTUN_REMOTE]) {
+		sit->remote = nla_get_u32(tb[IFLA_IPTUN_REMOTE]);
+		sit->sit_mask |= SIT_ATTR_REMOTE;
+	}
+
+	if (tb[IFLA_IPTUN_TTL]) {
+		sit->ttl = nla_get_u8(tb[IFLA_IPTUN_TTL]);
+		sit->sit_mask |= SIT_ATTR_TTL;
+	}
+
+	if (tb[IFLA_IPTUN_TOS]) {
+		sit->tos = nla_get_u8(tb[IFLA_IPTUN_TOS]);
+		sit->sit_mask |= SIT_ATTR_TOS;
+	}
+
+	if (tb[IFLA_IPTUN_PMTUDISC]) {
+		sit->pmtudisc = nla_get_u8(tb[IFLA_IPTUN_PMTUDISC]);
+		sit->sit_mask |= SIT_ATTR_PMTUDISC;
+	}
+
+	if (tb[IFLA_IPTUN_FLAGS]) {
+		sit->flags = nla_get_u16(tb[IFLA_IPTUN_FLAGS]);
+		sit->sit_mask |= SIT_ATTR_FLAGS;
+	}
+
+	if (tb[IFLA_IPTUN_PROTO]) {
+		sit->proto = nla_get_u8(tb[IFLA_IPTUN_PROTO]);
+		sit->sit_mask |= SIT_ATTR_PROTO;
+	}
+
+	err = 0;
+
+ errout:
+	return err;
+}
+
+static int sit_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
+{
+	struct sit_info *sit = link->l_info;
+	struct nlattr *data;
+
+	data = nla_nest_start(msg, IFLA_INFO_DATA);
+	if (!data)
+		return -NLE_MSGSIZE;
+
+	if (sit->sit_mask & SIT_ATTR_LINK)
+		NLA_PUT_U32(msg, IFLA_IPTUN_LINK, sit->link);
+
+	if (sit->sit_mask & SIT_ATTR_LOCAL)
+		NLA_PUT_U32(msg, IFLA_IPTUN_LOCAL, sit->local);
+
+	if (sit->sit_mask & SIT_ATTR_REMOTE)
+		NLA_PUT_U32(msg, IFLA_IPTUN_REMOTE, sit->remote);
+
+	if (sit->sit_mask & SIT_ATTR_TTL)
+		NLA_PUT_U8(msg, IFLA_IPTUN_TTL, sit->ttl);
+
+	if (sit->sit_mask & SIT_ATTR_TOS)
+		NLA_PUT_U8(msg, IFLA_IPTUN_TOS, sit->tos);
+
+	if (sit->sit_mask & SIT_ATTR_PMTUDISC)
+		NLA_PUT_U8(msg, IFLA_IPTUN_PMTUDISC, sit->pmtudisc);
+
+	if (sit->sit_mask & SIT_ATTR_FLAGS)
+		NLA_PUT_U16(msg, IFLA_IPTUN_FLAGS, sit->flags);
+
+	if (sit->sit_mask & SIT_ATTR_PROTO)
+		NLA_PUT_U8(msg, IFLA_IPTUN_PROTO, sit->proto);
+
+	nla_nest_end(msg, data);
+
+nla_put_failure:
+
+	return 0;
+}
+
+static void sit_free(struct rtnl_link *link)
+{
+	struct sit_info *sit = link->l_info;
+
+	free(sit);
+	link->l_info = NULL;
+}
+
+static void sit_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	nl_dump(p, "sit : %s", link->l_name);
+}
+
+static void sit_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	struct sit_info *sit = link->l_info;
+	char *name, addr[INET_ADDRSTRLEN];
+
+	if (sit->sit_mask & SIT_ATTR_LINK) {
+		nl_dump(p, "      link ");
+		name = rtnl_link_get_name(link);
+		if (name)
+			nl_dump_line(p, "%s\n", name);
+		else
+			nl_dump_line(p, "%u\n", sit->link);
+	}
+
+	if (sit->sit_mask & SIT_ATTR_LOCAL) {
+		nl_dump(p, "      local ");
+		if(inet_ntop(AF_INET, &sit->local, addr, sizeof(addr)))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ntohs(sit->local));
+	}
+
+	if (sit->sit_mask & SIT_ATTR_REMOTE) {
+		nl_dump(p, "      remote ");
+		if(inet_ntop(AF_INET, &sit->remote, addr, sizeof(addr)))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ntohs(sit->remote));
+	}
+
+	if (sit->sit_mask & SIT_ATTR_TTL) {
+		nl_dump(p, "      ttl ");
+		nl_dump_line(p, "%u\n", sit->ttl);
+	}
+
+	if (sit->sit_mask & SIT_ATTR_TOS) {
+		nl_dump(p, "      tos ");
+		nl_dump_line(p, "%u\n", sit->tos);
+	}
+
+	if (sit->sit_mask & SIT_ATTR_FLAGS) {
+		nl_dump(p, "      flags ");
+		nl_dump_line(p, " (%x)\n", sit->flags);
+	}
+
+	if (sit->sit_mask & SIT_ATTR_PROTO) {
+		nl_dump(p, "    proto   ");
+		nl_dump_line(p, " (%x)\n", sit->proto);
+	}
+}
+
+static int sit_clone(struct rtnl_link *dst, struct rtnl_link *src)
+{
+	struct sit_info *sit_dst, *sit_src = src->l_info;
+	int err;
+
+	dst->l_info = NULL;
+
+	err = rtnl_link_set_type(dst, "sit");
+	if (err < 0)
+		return err;
+
+	sit_dst = dst->l_info;
+
+	if (!sit_dst || !sit_src)
+		return -NLE_NOMEM;
+
+	memcpy(sit_dst, sit_src, sizeof(struct sit_info));
+
+	return 0;
+}
+
+static struct rtnl_link_info_ops sit_info_ops = {
+	.io_name                = "sit",
+	.io_alloc               = sit_alloc,
+	.io_parse               = sit_parse,
+	.io_dump = {
+		[NL_DUMP_LINE]  = sit_dump_line,
+		[NL_DUMP_DETAILS] = sit_dump_details,
+	},
+	.io_clone               = sit_clone,
+	.io_put_attrs           = sit_put_attrs,
+	.io_free                = sit_free,
+};
+
+#define IS_SIT_LINK_ASSERT(link)                                           \
+        if ((link)->l_info_ops != &sit_info_ops) {                         \
+                APPBUG("Link is not a sit link. set type \"sit\" first."); \
+                return -NLE_OPNOTSUPP;                                     \
+        }
+
+struct rtnl_link *rtnl_link_sit_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_alloc();
+	if (!link)
+		return NULL;
+
+	err = rtnl_link_set_type(link, "sit");
+	if (err < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+
+/**
+ * Check if link is a SIT link
+ * @arg link            Link object
+ *
+ * @return True if link is a SIT link, otherwise false is returned.
+ */
+int rtnl_link_is_sit(struct rtnl_link *link)
+{
+	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "sit");
+}
+
+/**
+ * Create a new sit tunnel device
+ * @arg sock            netlink socket
+ * @arg name            name of the tunnel device
+ *
+ * Creates a new sit tunnel device in the kernel
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_sit_add(struct nl_sock *sk, const char *name)
+{
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_sit_alloc();
+	if (!link)
+		return -NLE_NOMEM;
+
+	if(name)
+		rtnl_link_set_name(link, name);
+
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	rtnl_link_put(link);
+
+	return err;
+}
+
+/**
+ * Set SIT tunnel interface index
+ * @arg link            Link object
+ * @arg index           interface index
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_sit_set_link(struct rtnl_link *link,  uint32_t index)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	sit->link = index;
+	sit->sit_mask |= SIT_ATTR_LINK;
+
+	return 0;
+}
+
+/**
+ * Get SIT tunnel interface index
+ * @arg link            Link object
+ *
+ * @return interface index value
+ */
+uint32_t rtnl_link_sit_get_link(struct rtnl_link *link)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	return sit->link;
+}
+
+/**
+ * Set SIT tunnel local address
+ * @arg link            Link object
+ * @arg addr            local address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_sit_set_local(struct rtnl_link *link, uint32_t addr)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	sit->local = addr;
+	sit->sit_mask |= SIT_ATTR_LOCAL;
+
+	return 0;
+}
+
+/**
+ * Get SIT tunnel local address
+ * @arg link            Link object
+ *
+ * @return local address value
+ */
+uint32_t rtnl_link_sit_get_local(struct rtnl_link *link)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	return sit->local;
+}
+
+/**
+ * Set SIT tunnel remote address
+ * @arg link            Link object
+ * @arg remote          remote address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_sit_set_remote(struct rtnl_link *link, uint32_t addr)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	sit->remote = addr;
+	sit->sit_mask |= SIT_ATTR_REMOTE;
+
+	return 0;
+}
+
+/**
+ * Get SIT tunnel remote address
+ * @arg link            Link object
+ *
+ * @return remote address
+ */
+uint32_t rtnl_link_sit_get_remote(struct rtnl_link *link)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	return sit->remote;
+}
+
+/**
+ * Set SIT tunnel ttl
+ * @arg link            Link object
+ * @arg ttl             tunnel ttl
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_sit_set_ttl(struct rtnl_link *link, uint8_t ttl)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	sit->ttl = ttl;
+	sit->sit_mask |= SIT_ATTR_TTL;
+
+	return 0;
+}
+
+/**
+ * Get SIT tunnel ttl
+ * @arg link            Link object
+ *
+ * @return ttl value
+ */
+uint8_t rtnl_link_sit_get_ttl(struct rtnl_link *link)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	return sit->ttl;
+}
+
+/**
+ * Set SIT tunnel tos
+ * @arg link            Link object
+ * @arg tos             tunnel tos
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_sit_set_tos(struct rtnl_link *link, uint8_t tos)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	sit->tos = tos;
+	sit->sit_mask |= SIT_ATTR_TOS;
+
+	return 0;
+}
+
+/**
+ * Get SIT tunnel tos
+ * @arg link            Link object
+ *
+ * @return tos value
+ */
+uint8_t rtnl_link_sit_get_tos(struct rtnl_link *link)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	return sit->tos;
+}
+
+/**
+ * Set SIT tunnel path MTU discovery
+ * @arg link            Link object
+ * @arg pmtudisc        path MTU discovery
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_sit_set_pmtudisc(struct rtnl_link *link, uint8_t pmtudisc)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	sit->pmtudisc = pmtudisc;
+	sit->sit_mask |= SIT_ATTR_PMTUDISC;
+
+	return 0;
+}
+
+/**
+ * Get SIT path MTU discovery
+ * @arg link            Link object
+ *
+ * @return pmtudisc value
+ */
+uint8_t rtnl_link_sit_get_pmtudisc(struct rtnl_link *link)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	return sit->pmtudisc;
+}
+
+/**
+ * Set SIT tunnel flags
+ * @arg link            Link object
+ * @arg flags           tunnel flags
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_sit_set_flags(struct rtnl_link *link, uint16_t flags)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	sit->flags = flags;
+	sit->sit_mask |= SIT_ATTR_FLAGS;
+
+	return 0;
+}
+
+/**
+ * Get SIT path flags
+ * @arg link            Link object
+ *
+ * @return flags value
+ */
+uint16_t rtnl_link_sit_get_flags(struct rtnl_link *link)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	return sit->flags;
+}
+
+/**
+ * Set SIT tunnel proto
+ * @arg link            Link object
+ * @arg proto           tunnel proto
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_sit_set_proto(struct rtnl_link *link, uint8_t proto)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	sit->proto = proto;
+	sit->sit_mask |= SIT_ATTR_PROTO;
+
+	return 0;
+}
+
+/**
+ * Get SIT proto
+ * @arg link            Link object
+ *
+ * @return proto value
+ */
+uint8_t rtnl_link_sit_get_proto(struct rtnl_link *link)
+{
+	struct sit_info *sit = link->l_info;
+
+	IS_SIT_LINK_ASSERT(link);
+
+	return sit->proto;
+}
+
+static void __init sit_init(void)
+{
+	rtnl_link_register_info(&sit_info_ops);
+}
+
+static void __exit sit_exit(void)
+{
+	rtnl_link_unregister_info(&sit_info_ops);
+}
diff --git a/lib/route/link/veth.c b/lib/route/link/veth.c
new file mode 100644
index 0000000..e7e4a26
--- /dev/null
+++ b/lib/route/link/veth.c
@@ -0,0 +1,308 @@
+/*
+ * lib/route/link/veth.c	Virtual Ethernet
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup veth VETH
+ * Virtual Ethernet
+ *
+ * @details
+ * \b Link Type Name: "veth"
+ *
+ * @route_doc{link_veth, VETH Documentation}
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink/object.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+#include <netlink/route/link/veth.h>
+
+#include <linux/if_link.h>
+
+static struct nla_policy veth_policy[VETH_INFO_MAX+1] = {
+	[VETH_INFO_PEER]	= { .minlen = sizeof(struct ifinfomsg) },
+};
+
+static int veth_parse(struct rtnl_link *link, struct nlattr *data,
+		      struct nlattr *xstats)
+{
+	struct nlattr *tb[VETH_INFO_MAX+1];
+	struct nlattr *peer_tb[IFLA_MAX + 1];
+	struct rtnl_link *peer = link->l_info;
+	int err;
+
+	NL_DBG(3, "Parsing veth link info");
+
+	if ((err = nla_parse_nested(tb, VETH_INFO_MAX, data, veth_policy)) < 0)
+		goto errout;
+
+	if (tb[VETH_INFO_PEER]) {
+		struct nlattr *nla_peer;
+		struct ifinfomsg *ifi;
+
+		nla_peer = tb[VETH_INFO_PEER];
+		ifi = nla_data(nla_peer);
+
+		peer->l_family = ifi->ifi_family;
+		peer->l_arptype = ifi->ifi_type;
+		peer->l_index = ifi->ifi_index;
+		peer->l_flags = ifi->ifi_flags;
+		peer->l_change = ifi->ifi_change;
+		err = nla_parse(peer_tb, IFLA_MAX,
+				nla_data(nla_peer) + sizeof(struct ifinfomsg),
+				nla_len(nla_peer) - sizeof(struct ifinfomsg),
+				rtln_link_policy);
+		if (err < 0)
+			goto errout;
+
+		err = rtnl_link_info_parse(peer, peer_tb);
+		if (err < 0)
+			goto errout;
+	}
+
+	err = 0;
+
+errout:
+	return err;
+}
+
+static void veth_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
+{
+}
+
+static void veth_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	struct rtnl_link *peer = link->l_info;
+	char *name;
+	name = rtnl_link_get_name(peer);
+	nl_dump(p, "      peer ");
+	if (name)
+		nl_dump_line(p, "%s\n", name);
+	else
+		nl_dump_line(p, "%u\n", peer->l_index);
+}
+
+static int veth_clone(struct rtnl_link *dst, struct rtnl_link *src)
+{
+	struct rtnl_link *dst_peer = NULL, *src_peer = src->l_info;
+
+	/* we are calling nl_object_clone() recursively, this should
+	 * happen only once */
+	if (src_peer) {
+		src_peer->l_info = NULL;
+		dst_peer = (struct rtnl_link *)nl_object_clone(OBJ_CAST(src_peer));
+		if (!dst_peer)
+			return -NLE_NOMEM;
+		src_peer->l_info = src;
+		dst_peer->l_info = dst;
+	}
+	dst->l_info = dst_peer;
+	return 0;
+}
+
+static int veth_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
+{
+	struct rtnl_link *peer = link->l_info;
+	struct ifinfomsg ifi;
+	struct nlattr *data, *info_peer;
+
+	memset(&ifi, 0, sizeof ifi);
+	ifi.ifi_family = peer->l_family;
+	ifi.ifi_type = peer->l_arptype;
+	ifi.ifi_index = peer->l_index;
+	ifi.ifi_flags = peer->l_flags;
+	ifi.ifi_change = peer->l_change;
+
+	if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
+		return -NLE_MSGSIZE;
+	if (!(info_peer = nla_nest_start(msg, VETH_INFO_PEER)))
+		return -NLE_MSGSIZE;
+	if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
+		return -NLE_MSGSIZE;
+	rtnl_link_fill_info(msg, peer);
+	nla_nest_end(msg, info_peer);
+	nla_nest_end(msg, data);
+
+	return 0;
+}
+
+static int veth_alloc(struct rtnl_link *link)
+{
+	struct rtnl_link *peer;
+	int err;
+
+	/* return early if we are in recursion */
+	if (link->l_info)
+		return 0;
+
+	if (!(peer = rtnl_link_alloc()))
+		return -NLE_NOMEM;
+
+	/* We don't need to hold a reference here, as link and
+	 * its peer should always be freed together.
+	 */
+	peer->l_info = link;
+	if ((err = rtnl_link_set_type(peer, "veth")) < 0) {
+		rtnl_link_put(peer);
+		return err;
+	}
+
+	link->l_info = peer;
+	return 0;
+}
+
+static void veth_free(struct rtnl_link *link)
+{
+	struct rtnl_link *peer = link->l_info;
+	if (peer) {
+		link->l_info = NULL;
+		/* avoid calling this recursively */
+		peer->l_info = NULL;
+		rtnl_link_put(peer);
+	}
+	/* the caller should finally free link */
+}
+
+static struct rtnl_link_info_ops veth_info_ops = {
+	.io_name		= "veth",
+	.io_parse		= veth_parse,
+	.io_dump = {
+	    [NL_DUMP_LINE]	= veth_dump_line,
+	    [NL_DUMP_DETAILS]	= veth_dump_details,
+	},
+	.io_alloc		= veth_alloc,
+	.io_clone		= veth_clone,
+	.io_put_attrs		= veth_put_attrs,
+	.io_free		= veth_free,
+};
+
+/** @cond SKIP */
+
+#define IS_VETH_LINK_ASSERT(link) \
+	if ((link)->l_info_ops != &veth_info_ops) { \
+		APPBUG("Link is not a veth link. set type \"veth\" first."); \
+		return NULL; \
+	}
+/** @endcond */
+
+/**
+ * @name VETH Object
+ * @{
+ */
+
+/**
+ * Allocate link object of type veth
+ *
+ * @return Allocated link object or NULL.
+ */
+struct rtnl_link *rtnl_link_veth_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	if (!(link = rtnl_link_alloc()))
+		return NULL;
+	if ((err = rtnl_link_set_type(link, "veth")) < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+
+/**
+ * Get the peer link of a veth link
+ *
+ * @return the peer link object.
+ */
+struct rtnl_link *rtnl_link_veth_get_peer(struct rtnl_link *link)
+{
+	IS_VETH_LINK_ASSERT(link);
+	nl_object_get(OBJ_CAST(link->l_info));
+	return link->l_info;
+}
+
+/**
+ * Release a veth link and its peer
+ *
+ */
+void rtnl_link_veth_release(struct rtnl_link *link)
+{
+	veth_free(link);
+	rtnl_link_put(link);
+}
+
+/**
+ * Check if link is a veth link
+ * @arg link		Link object
+ *
+ * @return True if link is a veth link, otherwise false is returned.
+ */
+int rtnl_link_is_veth(struct rtnl_link *link)
+{
+	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "veth");
+}
+
+/**
+ * Create a new kernel veth device
+ * @arg sock		netlink socket
+ * @arg name		name of the veth device or NULL
+ * @arg peer_name	name of its peer or NULL
+ * @arg pid		pid of the process in the new netns
+ *
+ * Creates a new veth device pair in the kernel and move the peer
+ * to the network namespace where the process is. If no name is
+ * provided, the kernel will automatically pick a name of the
+ * form "veth%d" (e.g. veth0, veth1, etc.)
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_veth_add(struct nl_sock *sock, const char *name,
+                       const char *peer_name, pid_t pid)
+{
+	struct rtnl_link *link, *peer;
+	int err = -NLE_NOMEM;
+
+	if (!(link = rtnl_link_veth_alloc()))
+		return -NLE_NOMEM;
+	peer = link->l_info;
+
+	if (name && peer_name) {
+		rtnl_link_set_name(link, name);
+		rtnl_link_set_name(peer, peer_name);
+	}
+
+	rtnl_link_set_ns_pid(peer, pid);
+	err = rtnl_link_add(sock, link, NLM_F_CREATE | NLM_F_EXCL);
+
+	rtnl_link_put(link);
+	return err;
+}
+
+/** @} */
+
+static void __init veth_init(void)
+{
+	rtnl_link_register_info(&veth_info_ops);
+}
+
+static void __exit veth_exit(void)
+{
+	rtnl_link_unregister_info(&veth_info_ops);
+}
+
+/** @} */
diff --git a/lib/route/link/vlan.c b/lib/route/link/vlan.c
index c466afe..b9f0c66 100644
--- a/lib/route/link/vlan.c
+++ b/lib/route/link/vlan.c
@@ -6,24 +6,29 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup link_info
+ * @ingroup link
  * @defgroup vlan VLAN
- * @brief
+ * Virtual LAN link module
+ *
+ * @details
+ * \b Link Type Name: "vlan"
+ *
+ * @route_doc{link_vlan, VLAN Documentation}
  *
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/attr.h>
 #include <netlink/utils.h>
 #include <netlink/object.h>
 #include <netlink/route/rtnl.h>
-#include <netlink/route/link/info-api.h>
+#include <netlink-private/route/link/api.h>
 #include <netlink/route/link/vlan.h>
 
 #include <linux/if_vlan.h>
@@ -33,10 +38,12 @@
 #define VLAN_HAS_FLAGS		(1<<1)
 #define VLAN_HAS_INGRESS_QOS	(1<<2)
 #define VLAN_HAS_EGRESS_QOS	(1<<3)
+#define VLAN_HAS_PROTOCOL	(1<<4)
 
 struct vlan_info
 {
 	uint16_t		vi_vlan_id;
+	uint16_t		vi_protocol;
 	uint32_t		vi_flags;
 	uint32_t		vi_flags_mask;
 	uint32_t		vi_ingress_qos[VLAN_PRIO_MAX+1];
@@ -45,27 +52,15 @@
 	struct vlan_map * 	vi_egress_qos;
 	uint32_t		vi_mask;
 };
+
 /** @endcond */
 
-static struct trans_tbl vlan_flags[] = {
-	__ADD(VLAN_FLAG_REORDER_HDR, reorder_hdr)
-};
-
-char *rtnl_link_vlan_flags2str(int flags, char *buf, size_t len)
-{
-	return __flags2str(flags, buf, len, vlan_flags, ARRAY_SIZE(vlan_flags));
-}
-
-int rtnl_link_vlan_str2flags(const char *name)
-{
-	return __str2flags(name, vlan_flags, ARRAY_SIZE(vlan_flags));
-}
-
 static struct nla_policy vlan_policy[IFLA_VLAN_MAX+1] = {
 	[IFLA_VLAN_ID]		= { .type = NLA_U16 },
 	[IFLA_VLAN_FLAGS]	= { .minlen = sizeof(struct ifla_vlan_flags) },
 	[IFLA_VLAN_INGRESS_QOS]	= { .type = NLA_NESTED },
 	[IFLA_VLAN_EGRESS_QOS]	= { .type = NLA_NESTED },
+	[IFLA_VLAN_PROTOCOL]	= { .type = NLA_U16 },
 };
 
 static int vlan_alloc(struct rtnl_link *link)
@@ -102,6 +97,11 @@
 		vi->vi_mask |= VLAN_HAS_ID;
 	}
 
+	if (tb[IFLA_VLAN_PROTOCOL]) {
+		vi->vi_protocol = nla_get_u16(tb[IFLA_VLAN_PROTOCOL]);
+		vi->vi_mask |= VLAN_HAS_PROTOCOL;
+	}
+
 	if (tb[IFLA_VLAN_FLAGS]) {
 		struct ifla_vlan_flags flags;
 		nla_memcpy(&flags, tb[IFLA_VLAN_FLAGS], sizeof(flags));
@@ -122,7 +122,7 @@
 				return -NLE_INVAL;
 
 			map = nla_data(nla);
-			if (map->from < 0 || map->from > VLAN_PRIO_MAX) {
+			if (map->from > VLAN_PRIO_MAX) {
 				return -NLE_INVAL;
 			}
 
@@ -145,7 +145,7 @@
 
 		/* align to have a little reserve */
 		vi->vi_egress_size = (i + 32) & ~31;
-		vi->vi_egress_qos = calloc(vi->vi_egress_size, sizeof(*map));
+		vi->vi_egress_qos = calloc(vi->vi_egress_size, sizeof(*vi->vi_egress_qos));
 		if (vi->vi_egress_qos == NULL)
 			return -NLE_NOMEM;
 
@@ -189,11 +189,17 @@
 static void vlan_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
 {
 	struct vlan_info *vi = link->l_info;
-	int i, printed;
+	int printed;
+	uint32_t i;
 	char buf[64];
 
 	rtnl_link_vlan_flags2str(vi->vi_flags, buf, sizeof(buf));
-	nl_dump_line(p, "    vlan-info id %d <%s>\n", vi->vi_vlan_id, buf);
+	nl_dump_line(p, "    vlan-info id %d <%s>", vi->vi_vlan_id, buf);
+
+	if (vi->vi_mask & VLAN_HAS_PROTOCOL)
+		nl_dump_line(p, "    vlan protocol <%d>", vi->vi_protocol);
+
+	nl_dump(p, "\n");
 
 	if (vi->vi_mask & VLAN_HAS_INGRESS_QOS) {
 		nl_dump_line(p, 
@@ -241,7 +247,7 @@
 	int err;
 
 	dst->l_info = NULL;
-	if ((err = rtnl_link_set_info_type(dst, "vlan")) < 0)
+	if ((err = rtnl_link_set_type(dst, "vlan")) < 0)
 		return err;
 	vdst = dst->l_info;
 
@@ -299,7 +305,7 @@
 	if (vi->vi_mask & VLAN_HAS_EGRESS_QOS) {
 		struct ifla_vlan_qos_mapping map;
 		struct nlattr *qos;
-		int i;
+		uint32_t i;
 
 		if (!(qos = nla_nest_start(msg, IFLA_VLAN_EGRESS_QOS)))
 			goto nla_put_failure;
@@ -334,12 +340,63 @@
 	.io_free		= vlan_free,
 };
 
-int rtnl_link_vlan_set_id(struct rtnl_link *link, int id)
+/** @cond SKIP */
+#define IS_VLAN_LINK_ASSERT(link) \
+	if ((link)->l_info_ops != &vlan_info_ops) { \
+		APPBUG("Link is not a vlan link. set type \"vlan\" first."); \
+		return -NLE_OPNOTSUPP; \
+	}
+/** @endcond */
+
+/**
+ * @name VLAN Object
+ * @{
+ */
+
+/**
+ * Allocate link object of type VLAN
+ *
+ * @return Allocated link object or NULL.
+ */
+struct rtnl_link *rtnl_link_vlan_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	if (!(link = rtnl_link_alloc()))
+		return NULL;
+
+	if ((err = rtnl_link_set_type(link, "vlan")) < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+
+/**
+ * Check if link is a VLAN link
+ * @arg link		Link object
+ *
+ * @return True if link is a VLAN link, otherwise false is returned.
+ */
+int rtnl_link_is_vlan(struct rtnl_link *link)
+{
+	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "vlan");
+}
+
+/**
+ * Set VLAN ID
+ * @arg link		Link object
+ * @arg id		VLAN identifier
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vlan_set_id(struct rtnl_link *link, uint16_t id)
 {
 	struct vlan_info *vi = link->l_info;
 
-	if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
-		return -NLE_OPNOTSUPP;
+	IS_VLAN_LINK_ASSERT(link);
 
 	vi->vi_vlan_id = id;
 	vi->vi_mask |= VLAN_HAS_ID;
@@ -347,12 +404,17 @@
 	return 0;
 }
 
+/**
+ * Get VLAN Id
+ * @arg link		Link object
+ *
+ * @return VLAN id, 0 if not set or a negative error code.
+ */
 int rtnl_link_vlan_get_id(struct rtnl_link *link)
 {
 	struct vlan_info *vi = link->l_info;
 
-	if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
-		return -NLE_OPNOTSUPP;
+	IS_VLAN_LINK_ASSERT(link);
 
 	if (vi->vi_mask & VLAN_HAS_ID)
 		return vi->vi_vlan_id;
@@ -360,12 +422,55 @@
 		return 0;
 }
 
+/**
+ * Set VLAN protocol
+ * @arg link		Link object
+ * @arg protocol	VLAN protocol
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vlan_set_protocol(struct rtnl_link *link, uint16_t protocol)
+{
+	struct vlan_info *vi = link->l_info;
+
+	IS_VLAN_LINK_ASSERT(link);
+
+	vi->vi_protocol = protocol;
+	vi->vi_mask |= VLAN_HAS_PROTOCOL;
+
+	return 0;
+}
+
+/**
+ * Get VLAN protocol
+ * @arg link		Link object
+ *
+ * @return VLAN protocol, 0 if not set or a negative error code.
+ */
+int rtnl_link_vlan_get_protocol(struct rtnl_link *link)
+{
+	struct vlan_info *vi = link->l_info;
+
+	IS_VLAN_LINK_ASSERT(link);
+
+	if (vi->vi_mask & VLAN_HAS_PROTOCOL)
+		return vi->vi_protocol;
+	else
+		return 0;
+}
+
+/**
+ * Set VLAN flags
+ * @arg link		Link object
+ * @arg flags		VLAN flags
+ *
+ * @return 0 on success or a negative error code.
+ */
 int rtnl_link_vlan_set_flags(struct rtnl_link *link, unsigned int flags)
 {
 	struct vlan_info *vi = link->l_info;
 
-	if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
-		return -NLE_OPNOTSUPP;
+	IS_VLAN_LINK_ASSERT(link);
 
 	vi->vi_flags_mask |= flags;
 	vi->vi_flags |= flags;
@@ -374,12 +479,18 @@
 	return 0;
 }
 
+/**
+ * Unset VLAN flags
+ * @arg link		Link object
+ * @arg flags		VLAN flags
+ *
+ * @return 0 on success or a negative error code.
+ */
 int rtnl_link_vlan_unset_flags(struct rtnl_link *link, unsigned int flags)
 {
 	struct vlan_info *vi = link->l_info;
 
-	if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
-		return -NLE_OPNOTSUPP;
+	IS_VLAN_LINK_ASSERT(link);
 
 	vi->vi_flags_mask |= flags;
 	vi->vi_flags &= ~flags;
@@ -388,23 +499,34 @@
 	return 0;
 }
 
-unsigned int rtnl_link_vlan_get_flags(struct rtnl_link *link)
+/**
+ * Get VLAN flags
+ * @arg link		Link object
+ *
+ * @return VLAN flags, 0 if none set, or a negative error code.
+ */
+int rtnl_link_vlan_get_flags(struct rtnl_link *link)
 {
 	struct vlan_info *vi = link->l_info;
 
-	if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
-		return -NLE_OPNOTSUPP;
+	IS_VLAN_LINK_ASSERT(link);
 
 	return vi->vi_flags;
 }
 
+/** @} */
+
+/**
+ * @name Quality of Service
+ * @{
+ */
+
 int rtnl_link_vlan_set_ingress_map(struct rtnl_link *link, int from,
 				   uint32_t to)
 {
 	struct vlan_info *vi = link->l_info;
 
-	if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
-		return -NLE_OPNOTSUPP;
+	IS_VLAN_LINK_ASSERT(link);
 
 	if (from < 0 || from > VLAN_PRIO_MAX)
 		return -NLE_INVAL;
@@ -478,6 +600,30 @@
 	}
 }
 
+/** @} */
+
+static const struct trans_tbl vlan_flags[] = {
+	__ADD(VLAN_FLAG_REORDER_HDR, reorder_hdr)
+};
+
+/**
+ * @name Flag Translation
+ * @{
+ */
+
+char *rtnl_link_vlan_flags2str(int flags, char *buf, size_t len)
+{
+	return __flags2str(flags, buf, len, vlan_flags, ARRAY_SIZE(vlan_flags));
+}
+
+int rtnl_link_vlan_str2flags(const char *name)
+{
+	return __str2flags(name, vlan_flags, ARRAY_SIZE(vlan_flags));
+}
+
+/** @} */
+
+
 static void __init vlan_init(void)
 {
 	rtnl_link_register_info(&vlan_info_ops);
diff --git a/lib/route/link/vxlan.c b/lib/route/link/vxlan.c
new file mode 100644
index 0000000..f3e3538
--- /dev/null
+++ b/lib/route/link/vxlan.c
@@ -0,0 +1,1157 @@
+/*
+ * lib/route/link/vxlan.c	VXLAN Link Info
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Yasunobu Chiba <yasu@dsl.gr.jp>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup vxlan VXLAN
+ * Virtual eXtensible Local Area Network link module
+ *
+ * @details
+ * \b Link Type Name: "vxlan"
+ *
+ * @route_doc{link_vxlan, VXLAN Documentation}
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink/attr.h>
+#include <netlink/utils.h>
+#include <netlink/object.h>
+#include <netlink/route/rtnl.h>
+#include <netlink-private/route/link/api.h>
+#include <netlink/route/link/vxlan.h>
+
+#include <linux/if_link.h>
+
+/** @cond SKIP */
+#define VXLAN_HAS_ID	(1<<0)
+#define VXLAN_HAS_GROUP	(1<<1)
+#define VXLAN_HAS_LINK	(1<<2)
+#define VXLAN_HAS_LOCAL	(1<<3)
+#define VXLAN_HAS_TTL	(1<<4)
+#define VXLAN_HAS_TOS	(1<<5)
+#define VXLAN_HAS_LEARNING	(1<<6)
+#define VXLAN_HAS_AGEING	(1<<7)
+#define VXLAN_HAS_LIMIT		(1<<8)
+#define VXLAN_HAS_PORT_RANGE	(1<<9)
+#define VXLAN_HAS_PROXY	(1<<10)
+#define VXLAN_HAS_RSC	(1<<11)
+#define VXLAN_HAS_L2MISS	(1<<12)
+#define VXLAN_HAS_L3MISS	(1<<13)
+
+struct vxlan_info
+{
+	uint32_t		vxi_id;
+	uint32_t		vxi_group;
+	uint32_t		vxi_link;
+	uint32_t		vxi_local;
+	uint8_t			vxi_ttl;
+	uint8_t			vxi_tos;
+	uint8_t			vxi_learning;
+	uint32_t		vxi_ageing;
+	uint32_t		vxi_limit;
+	struct ifla_vxlan_port_range	vxi_port_range;
+	uint8_t			vxi_proxy;
+	uint8_t			vxi_rsc;
+	uint8_t			vxi_l2miss;
+	uint8_t			vxi_l3miss;
+	uint32_t		vxi_mask;
+};
+
+/** @endcond */
+
+static struct nla_policy vxlan_policy[IFLA_VXLAN_MAX+1] = {
+	[IFLA_VXLAN_ID]	= { .type = NLA_U32 },
+	[IFLA_VXLAN_GROUP] = { .minlen = sizeof(uint32_t) },
+	[IFLA_VXLAN_LINK] = { .type = NLA_U32 },
+	[IFLA_VXLAN_LOCAL] = { .minlen = sizeof(uint32_t) },
+	[IFLA_VXLAN_TTL] = { .type = NLA_U8 },
+	[IFLA_VXLAN_TOS] = { .type = NLA_U8 },
+	[IFLA_VXLAN_LEARNING] = { .type = NLA_U8 },
+	[IFLA_VXLAN_AGEING] = { .type = NLA_U32 },
+	[IFLA_VXLAN_LIMIT] = { .type = NLA_U32 },
+	[IFLA_VXLAN_PORT_RANGE] = { .minlen = sizeof(struct ifla_vxlan_port_range) },
+	[IFLA_VXLAN_PROXY] = { .type = NLA_U8 },
+	[IFLA_VXLAN_RSC] = { .type = NLA_U8 },
+	[IFLA_VXLAN_L2MISS] = { .type = NLA_U8 },
+	[IFLA_VXLAN_L3MISS] = { .type = NLA_U8 },
+};
+
+static int vxlan_alloc(struct rtnl_link *link)
+{
+	struct vxlan_info *vxi;
+
+	if ((vxi = calloc(1, sizeof(*vxi))) == NULL)
+		return -NLE_NOMEM;
+
+	link->l_info = vxi;
+
+	return 0;
+}
+
+static int vxlan_parse(struct rtnl_link *link, struct nlattr *data,
+		      struct nlattr *xstats)
+{
+	struct nlattr *tb[IFLA_VXLAN_MAX+1];
+	struct vxlan_info *vxi;
+	int err;
+
+	NL_DBG(3, "Parsing VXLAN link info");
+
+	if ((err = nla_parse_nested(tb, IFLA_VXLAN_MAX, data, vxlan_policy)) < 0)
+		goto errout;
+
+	if ((err = vxlan_alloc(link)) < 0)
+		goto errout;
+
+	vxi = link->l_info;
+
+	if (tb[IFLA_VXLAN_ID]) {
+		vxi->vxi_id = nla_get_u32(tb[IFLA_VXLAN_ID]);
+		vxi->vxi_mask |= VXLAN_HAS_ID;
+	}
+
+	if (tb[IFLA_VXLAN_GROUP]) {
+		nla_memcpy(&vxi->vxi_group, tb[IFLA_VXLAN_GROUP],
+				   sizeof(vxi->vxi_group));
+		vxi->vxi_mask |= VXLAN_HAS_GROUP;
+	}
+
+	if (tb[IFLA_VXLAN_LINK]) {
+		vxi->vxi_link = nla_get_u32(tb[IFLA_VXLAN_LINK]);
+		vxi->vxi_mask |= VXLAN_HAS_LINK;
+	}
+
+	if (tb[IFLA_VXLAN_LOCAL]) {
+		nla_memcpy(&vxi->vxi_local, tb[IFLA_VXLAN_LOCAL],
+				   sizeof(vxi->vxi_local));
+		vxi->vxi_mask |= VXLAN_HAS_LOCAL;
+	}
+
+	if (tb[IFLA_VXLAN_TTL]) {
+		vxi->vxi_ttl = nla_get_u8(tb[IFLA_VXLAN_TTL]);
+		vxi->vxi_mask |= VXLAN_HAS_TTL;
+	}
+
+	if (tb[IFLA_VXLAN_TOS]) {
+		vxi->vxi_tos = nla_get_u8(tb[IFLA_VXLAN_TOS]);
+		vxi->vxi_mask |= VXLAN_HAS_TOS;
+	}
+
+	if (tb[IFLA_VXLAN_LEARNING]) {
+		vxi->vxi_learning = nla_get_u8(tb[IFLA_VXLAN_LEARNING]);
+		vxi->vxi_mask |= VXLAN_HAS_LEARNING;
+	}
+
+	if (tb[IFLA_VXLAN_AGEING]) {
+		vxi->vxi_ageing = nla_get_u32(tb[IFLA_VXLAN_AGEING]);
+		vxi->vxi_mask |= VXLAN_HAS_AGEING;
+	}
+
+	if (tb[IFLA_VXLAN_LIMIT]) {
+		vxi->vxi_limit = nla_get_u32(tb[IFLA_VXLAN_LIMIT]);
+		vxi->vxi_mask |= VXLAN_HAS_LIMIT;
+	}
+
+	if (tb[IFLA_VXLAN_PORT_RANGE]) {
+		nla_memcpy(&vxi->vxi_port_range, tb[IFLA_VXLAN_PORT_RANGE],
+				   sizeof(vxi->vxi_port_range));
+		vxi->vxi_mask |= VXLAN_HAS_PORT_RANGE;
+	}
+
+	if (tb[IFLA_VXLAN_PROXY]) {
+		vxi->vxi_proxy = nla_get_u8(tb[IFLA_VXLAN_PROXY]);
+		vxi->vxi_mask |= VXLAN_HAS_PROXY;
+	}
+
+	if (tb[IFLA_VXLAN_RSC]) {
+		vxi->vxi_rsc = nla_get_u8(tb[IFLA_VXLAN_RSC]);
+		vxi->vxi_mask |= VXLAN_HAS_RSC;
+	}
+
+	if (tb[IFLA_VXLAN_L2MISS]) {
+		vxi->vxi_l2miss = nla_get_u8(tb[IFLA_VXLAN_L2MISS]);
+		vxi->vxi_mask |= VXLAN_HAS_L2MISS;
+	}
+
+	if (tb[IFLA_VXLAN_L3MISS]) {
+		vxi->vxi_l3miss = nla_get_u8(tb[IFLA_VXLAN_L3MISS]);
+		vxi->vxi_mask |= VXLAN_HAS_L3MISS;
+	}
+
+	err = 0;
+
+errout:
+	return err;
+}
+
+static void vxlan_free(struct rtnl_link *link)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	free(vxi);
+	link->l_info = NULL;
+}
+
+static void vxlan_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	nl_dump(p, "vxlan-id %u", vxi->vxi_id);
+}
+
+static void vxlan_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
+{
+	struct vxlan_info *vxi = link->l_info;
+	char *name, addr[INET_ADDRSTRLEN];
+
+	nl_dump_line(p, "    vxlan-id %u\n", vxi->vxi_id);
+
+	if (vxi->vxi_mask & VXLAN_HAS_GROUP) {
+		nl_dump(p, "      group ");
+		if(inet_ntop(AF_INET, &vxi->vxi_group, addr, sizeof(addr)))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ntohs(vxi->vxi_group));
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_LINK) {
+		nl_dump(p, "      link ");
+		name = rtnl_link_get_name(link);
+		if (name)
+			nl_dump_line(p, "%s\n", name);
+		else
+			nl_dump_line(p, "%u\n", vxi->vxi_link);
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_LOCAL) {
+		nl_dump(p, "      local ");
+		if(inet_ntop(AF_INET, &vxi->vxi_local, addr, sizeof(addr)))
+			nl_dump_line(p, "%s\n", addr);
+		else
+			nl_dump_line(p, "%#x\n", ntohs(vxi->vxi_local));
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_TTL) {
+		nl_dump(p, "      ttl ");
+		if(vxi->vxi_ttl)
+			nl_dump_line(p, "%u\n", vxi->vxi_ttl);
+		else
+			nl_dump_line(p, "inherit\n");
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_TOS) {
+		nl_dump(p, "      tos ");
+		if (vxi->vxi_tos == 1)
+			nl_dump_line(p, "inherit\n", vxi->vxi_tos);
+		else
+			nl_dump_line(p, "%#x\n", vxi->vxi_tos);
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_LEARNING) {
+		nl_dump(p, "      learning ");
+		if (vxi->vxi_learning)
+			nl_dump_line(p, "enabled (%#x)\n", vxi->vxi_learning);
+		else
+			nl_dump_line(p, "disabled\n");
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_AGEING) {
+		nl_dump(p, "      ageing ");
+		if (vxi->vxi_ageing)
+			nl_dump_line(p, "%u seconds\n", vxi->vxi_ageing);
+		else
+			nl_dump_line(p, "disabled\n");
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_LIMIT) {
+		nl_dump(p, "      limit ");
+		if (vxi->vxi_limit)
+			nl_dump_line(p, "%u\n", vxi->vxi_limit);
+		else
+			nl_dump_line(p, "unlimited\n");
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_PORT_RANGE)
+		nl_dump_line(p, "      port range %u - %u\n",
+					 ntohs(vxi->vxi_port_range.low),
+					 ntohs(vxi->vxi_port_range.high));
+
+	if (vxi->vxi_mask & VXLAN_HAS_PROXY) {
+		nl_dump(p, "      proxy ");
+		if (vxi->vxi_proxy)
+			nl_dump_line(p, "enabled (%#x)\n", vxi->vxi_proxy);
+		else
+			nl_dump_line(p, "disabled\n");
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_RSC) {
+		nl_dump(p, "      rsc ");
+		if (vxi->vxi_rsc)
+			nl_dump_line(p, "enabled (%#x)\n", vxi->vxi_rsc);
+		else
+			nl_dump_line(p, "disabled\n");
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_L2MISS) {
+		nl_dump(p, "      l2miss ");
+		if (vxi->vxi_l2miss)
+			nl_dump_line(p, "enabled (%#x)\n", vxi->vxi_l2miss);
+		else
+			nl_dump_line(p, "disabled\n");
+	}
+
+	if (vxi->vxi_mask & VXLAN_HAS_L3MISS) {
+		nl_dump(p, "      l3miss ");
+		if (vxi->vxi_l3miss)
+			nl_dump_line(p, "enabled (%#x)\n", vxi->vxi_l3miss);
+		else
+			nl_dump_line(p, "disabled\n");
+	}
+}
+
+static int vxlan_clone(struct rtnl_link *dst, struct rtnl_link *src)
+{
+	struct vxlan_info *vdst, *vsrc = src->l_info;
+	int err;
+
+	dst->l_info = NULL;
+	if ((err = rtnl_link_set_type(dst, "vxlan")) < 0)
+		return err;
+	vdst = dst->l_info;
+
+	if (!vdst || !vsrc)
+		return -NLE_NOMEM;
+
+	memcpy(vdst, vsrc, sizeof(struct vxlan_info));
+
+	return 0;
+}
+
+static int vxlan_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
+{
+	struct vxlan_info *vxi = link->l_info;
+	struct nlattr *data;
+
+	if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
+		return -NLE_MSGSIZE;
+
+	if (vxi->vxi_mask & VXLAN_HAS_ID)
+		NLA_PUT_U32(msg, IFLA_VXLAN_ID, vxi->vxi_id);
+
+	if (vxi->vxi_mask & VXLAN_HAS_GROUP)
+		NLA_PUT(msg, IFLA_VXLAN_GROUP, sizeof(vxi->vxi_group), &vxi->vxi_group);
+
+	if (vxi->vxi_mask & VXLAN_HAS_LINK)
+		NLA_PUT_U32(msg, IFLA_VXLAN_LINK, vxi->vxi_link);
+
+	if (vxi->vxi_mask & VXLAN_HAS_LOCAL)
+		NLA_PUT(msg, IFLA_VXLAN_LOCAL, sizeof(vxi->vxi_local), &vxi->vxi_local);
+
+	if (vxi->vxi_mask & VXLAN_HAS_TTL)
+		NLA_PUT_U8(msg, IFLA_VXLAN_TTL, vxi->vxi_ttl);
+
+	if (vxi->vxi_mask & VXLAN_HAS_TOS)
+		NLA_PUT_U8(msg, IFLA_VXLAN_TOS, vxi->vxi_tos);
+
+	if (vxi->vxi_mask & VXLAN_HAS_LEARNING)
+		NLA_PUT_U8(msg, IFLA_VXLAN_LEARNING, vxi->vxi_learning);
+
+	if (vxi->vxi_mask & VXLAN_HAS_AGEING)
+		NLA_PUT_U32(msg, IFLA_VXLAN_AGEING, vxi->vxi_ageing);
+
+	if (vxi->vxi_mask & VXLAN_HAS_LIMIT)
+		NLA_PUT_U32(msg, IFLA_VXLAN_LIMIT, vxi->vxi_limit);
+
+	if (vxi->vxi_mask & VXLAN_HAS_PORT_RANGE)
+		NLA_PUT(msg, IFLA_VXLAN_PORT_RANGE, sizeof(vxi->vxi_port_range),
+				&vxi->vxi_port_range);
+
+	if (vxi->vxi_mask & VXLAN_HAS_PROXY)
+		NLA_PUT_U8(msg, IFLA_VXLAN_PROXY, vxi->vxi_proxy);
+
+	if (vxi->vxi_mask & VXLAN_HAS_RSC)
+		NLA_PUT_U8(msg, IFLA_VXLAN_RSC, vxi->vxi_rsc);
+
+	if (vxi->vxi_mask & VXLAN_HAS_L2MISS)
+		NLA_PUT_U8(msg, IFLA_VXLAN_L2MISS, vxi->vxi_l2miss);
+
+	if (vxi->vxi_mask & VXLAN_HAS_L3MISS)
+		NLA_PUT_U8(msg, IFLA_VXLAN_L3MISS, vxi->vxi_l3miss);
+
+	nla_nest_end(msg, data);
+
+nla_put_failure:
+
+	return 0;
+}
+
+static struct rtnl_link_info_ops vxlan_info_ops = {
+	.io_name		= "vxlan",
+	.io_alloc		= vxlan_alloc,
+	.io_parse		= vxlan_parse,
+	.io_dump = {
+	    [NL_DUMP_LINE]	= vxlan_dump_line,
+	    [NL_DUMP_DETAILS]	= vxlan_dump_details,
+	},
+	.io_clone		= vxlan_clone,
+	.io_put_attrs		= vxlan_put_attrs,
+	.io_free		= vxlan_free,
+};
+
+/** @cond SKIP */
+#define IS_VXLAN_LINK_ASSERT(link) \
+	if ((link)->l_info_ops != &vxlan_info_ops) { \
+		APPBUG("Link is not a vxlan link. set type \"vxlan\" first."); \
+		return -NLE_OPNOTSUPP; \
+	}
+/** @endcond */
+
+/**
+ * @name VXLAN Object
+ * @{
+ */
+
+/**
+ * Allocate link object of type VXLAN
+ *
+ * @return Allocated link object or NULL.
+ */
+struct rtnl_link *rtnl_link_vxlan_alloc(void)
+{
+	struct rtnl_link *link;
+	int err;
+
+	if (!(link = rtnl_link_alloc()))
+		return NULL;
+
+	if ((err = rtnl_link_set_type(link, "vxlan")) < 0) {
+		rtnl_link_put(link);
+		return NULL;
+	}
+
+	return link;
+}
+
+/**
+ * Check if link is a VXLAN link
+ * @arg link		Link object
+ *
+ * @return True if link is a VXLAN link, otherwise false is returned.
+ */
+int rtnl_link_is_vxlan(struct rtnl_link *link)
+{
+	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "vxlan");
+}
+
+/**
+ * Set VXLAN Network Identifier
+ * @arg link		Link object
+ * @arg id		VXLAN network identifier (or VXLAN segment identifier)
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_id(struct rtnl_link *link, uint32_t id)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (id > VXLAN_ID_MAX)
+		return -NLE_INVAL;
+
+	vxi->vxi_id = id;
+	vxi->vxi_mask |= VXLAN_HAS_ID;
+
+	return 0;
+}
+
+/**
+ * Get VXLAN Network Identifier
+ * @arg link		Link object
+ * @arg id			Pointer to store network identifier
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_get_id(struct rtnl_link *link, uint32_t *id)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if(!id)
+		return -NLE_INVAL;
+
+	if (vxi->vxi_mask & VXLAN_HAS_ID)
+		*id = vxi->vxi_id;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Set VXLAN multicast IP address
+ * @arg link		Link object
+ * @arg addr		Multicast IP address to join
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_group(struct rtnl_link *link, struct nl_addr *addr)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if ((nl_addr_get_family(addr) != AF_INET) ||
+		(nl_addr_get_len(addr) != sizeof(vxi->vxi_group)))
+		return -NLE_INVAL;
+
+	memcpy(&vxi->vxi_group, nl_addr_get_binary_addr(addr),
+		   sizeof(vxi->vxi_group));
+	vxi->vxi_mask |= VXLAN_HAS_GROUP;
+
+	return 0;
+}
+
+/**
+ * Get VXLAN multicast IP address
+ * @arg link		Link object
+ * @arg addr		Pointer to store multicast IP address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_get_group(struct rtnl_link *link, struct nl_addr **addr)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!addr)
+		return -NLE_INVAL;
+
+	if (!(vxi->vxi_mask & VXLAN_HAS_GROUP))
+		return -NLE_AGAIN;
+
+	*addr = nl_addr_build(AF_INET, &vxi->vxi_group, sizeof(vxi->vxi_group));
+
+	return 0;
+}
+
+/**
+ * Set physical device to use for VXLAN
+ * @arg link		Link object
+ * @arg index		Interface index
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_link(struct rtnl_link *link, uint32_t index)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	vxi->vxi_link = index;
+	vxi->vxi_mask |= VXLAN_HAS_LINK;
+
+	return 0;
+}
+
+/**
+ * Get physical device to use for VXLAN
+ * @arg link		Link object
+ * @arg index		Pointer to store interface index
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_get_link(struct rtnl_link *link, uint32_t *index)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!index)
+		return -NLE_INVAL;
+
+	if (!(vxi->vxi_mask & VXLAN_HAS_LINK))
+		return -NLE_AGAIN;
+
+	*index = vxi->vxi_link;
+
+	return 0;
+}
+
+/**
+ * Set source address to use for VXLAN
+ * @arg link		Link object
+ * @arg addr		Local address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_local(struct rtnl_link *link, struct nl_addr *addr)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if ((nl_addr_get_family(addr) != AF_INET) ||
+		(nl_addr_get_len(addr) != sizeof(vxi->vxi_local)))
+		return -NLE_INVAL;
+
+	memcpy(&vxi->vxi_local, nl_addr_get_binary_addr(addr),
+		   sizeof(vxi->vxi_local));
+	vxi->vxi_mask |= VXLAN_HAS_LOCAL;
+
+	return 0;
+}
+
+/**
+ * Get source address to use for VXLAN
+ * @arg link		Link object
+ * @arg addr		Pointer to store local address
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_get_local(struct rtnl_link *link, struct nl_addr **addr)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!addr)
+		return -NLE_INVAL;
+
+	if (!(vxi->vxi_mask & VXLAN_HAS_LOCAL))
+		return -NLE_AGAIN;
+
+	*addr = nl_addr_build(AF_INET, &vxi->vxi_local, sizeof(vxi->vxi_local));
+
+	return 0;
+}
+
+/**
+ * Set IP TTL value to use for VXLAN
+ * @arg link		Link object
+ * @arg ttl			TTL value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_ttl(struct rtnl_link *link, uint8_t ttl)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	vxi->vxi_ttl = ttl;
+	vxi->vxi_mask |= VXLAN_HAS_TTL;
+
+	return 0;
+}
+
+/**
+ * Get IP TTL value to use for VXLAN
+ * @arg link		Link object
+ *
+ * @return TTL value on success or a negative error code
+ */
+int rtnl_link_vxlan_get_ttl(struct rtnl_link *link)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!(vxi->vxi_mask & VXLAN_HAS_TTL))
+		return -NLE_AGAIN;
+
+	return vxi->vxi_ttl;
+}
+
+/**
+ * Set IP ToS value to use for VXLAN
+ * @arg link		Link object
+ * @arg tos		ToS value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_tos(struct rtnl_link *link, uint8_t tos)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	vxi->vxi_tos = tos;
+	vxi->vxi_mask |= VXLAN_HAS_TOS;
+
+	return 0;
+}
+
+/**
+ * Get IP ToS value to use for VXLAN
+ * @arg link		Link object
+ *
+ * @return ToS value on success or a negative error code
+ */
+int rtnl_link_vxlan_get_tos(struct rtnl_link *link)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!(vxi->vxi_mask & VXLAN_HAS_TOS))
+		return -NLE_AGAIN;
+
+	return vxi->vxi_tos;
+}
+
+/**
+ * Set VXLAN learning status
+ * @arg link		Link object
+ * @arg learning	Learning status value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_learning(struct rtnl_link *link, uint8_t learning)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	vxi->vxi_learning = learning;
+	vxi->vxi_mask |= VXLAN_HAS_LEARNING;
+
+	return 0;
+}
+
+/**
+ * Get VXLAN learning status
+ * @arg link		Link object
+ *
+ * @return Learning status value on success or a negative error code
+ */
+int rtnl_link_vxlan_get_learning(struct rtnl_link *link)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!(vxi->vxi_mask & VXLAN_HAS_LEARNING))
+		return -NLE_AGAIN;
+
+	return vxi->vxi_learning;
+}
+
+/**
+ * Enable VXLAN address learning
+ * @arg link		Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_enable_learning(struct rtnl_link *link)
+{
+	return rtnl_link_vxlan_set_learning(link, 1);
+}
+
+/**
+ * Disable VXLAN address learning
+ * @arg link		Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_disable_learning(struct rtnl_link *link)
+{
+	return rtnl_link_vxlan_set_learning(link, 0);
+}
+
+/**
+ * Set expiration timer value to use for VXLAN
+ * @arg link		Link object
+ * @arg expiry		Expiration timer value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_ageing(struct rtnl_link *link, uint32_t expiry)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	vxi->vxi_ageing = expiry;
+	vxi->vxi_mask |= VXLAN_HAS_AGEING;
+
+	return 0;
+}
+
+/**
+ * Get expiration timer value to use for VXLAN
+ * @arg link		Link object
+ * @arg expiry		Pointer to store expiration timer value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_get_ageing(struct rtnl_link *link, uint32_t *expiry)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!expiry)
+		return -NLE_INVAL;
+
+	if (vxi->vxi_mask & VXLAN_HAS_AGEING)
+		*expiry = vxi->vxi_ageing;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Set maximum number of forwarding database entries to use for VXLAN
+ * @arg link		Link object
+ * @arg limit		Maximum number
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_limit(struct rtnl_link *link, uint32_t limit)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	vxi->vxi_limit = limit;
+	vxi->vxi_mask |= VXLAN_HAS_LIMIT;
+
+	return 0;
+}
+
+/**
+ * Get maximum number of forwarding database entries to use for VXLAN
+ * @arg link		Link object
+ * @arg limit		Pointer to store maximum number
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_get_limit(struct rtnl_link *link, uint32_t *limit)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!limit)
+		return -NLE_INVAL;
+
+	if (vxi->vxi_mask & VXLAN_HAS_LIMIT)
+		*limit = vxi->vxi_limit;
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Set range of UDP port numbers to use for VXLAN
+ * @arg link		Link object
+ * @arg range		Port number range
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_port_range(struct rtnl_link *link,
+								   struct ifla_vxlan_port_range *range)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!range)
+		return -NLE_INVAL;
+
+	memcpy(&vxi->vxi_port_range, range, sizeof(vxi->vxi_port_range));
+	vxi->vxi_mask |= VXLAN_HAS_PORT_RANGE;
+
+	return 0;
+}
+
+/**
+ * Get range of UDP port numbers to use for VXLAN
+ * @arg link		Link object
+ * @arg range		Pointer to store port range
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_get_port_range(struct rtnl_link *link,
+								   struct ifla_vxlan_port_range *range)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!range)
+		return -NLE_INVAL;
+
+	if (vxi->vxi_mask & VXLAN_HAS_PORT_RANGE)
+		memcpy(range, &vxi->vxi_port_range, sizeof(*range));
+	else
+		return -NLE_AGAIN;
+
+	return 0;
+}
+
+/**
+ * Set ARP proxy status to use for VXLAN
+ * @arg link		Link object
+ * @arg proxy		Status value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_proxy(struct rtnl_link *link, uint8_t proxy)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	vxi->vxi_proxy = proxy;
+	vxi->vxi_mask |= VXLAN_HAS_PROXY;
+
+	return 0;
+}
+
+/**
+ * Get ARP proxy status to use for VXLAN
+ * @arg link		Link object
+ *
+ * @return Status value on success or a negative error code
+ */
+int rtnl_link_vxlan_get_proxy(struct rtnl_link *link)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!(vxi->vxi_mask & VXLAN_HAS_PROXY))
+		return -NLE_AGAIN;
+
+	return vxi->vxi_proxy;
+}
+
+/**
+ * Enable ARP proxy
+ * @arg link		Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_enable_proxy(struct rtnl_link *link)
+{
+	return rtnl_link_vxlan_set_proxy(link, 1);
+}
+
+/**
+ * Disable ARP proxy
+ * @arg link		Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_disable_proxy(struct rtnl_link *link)
+{
+	return rtnl_link_vxlan_set_proxy(link, 0);
+}
+
+/**
+ * Set Route Short Circuit status to use for VXLAN
+ * @arg link		Link object
+ * @arg rsc			Status value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_rsc(struct rtnl_link *link, uint8_t rsc)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	vxi->vxi_rsc = rsc;
+	vxi->vxi_mask |= VXLAN_HAS_RSC;
+
+	return 0;
+}
+
+/**
+ * Get Route Short Circuit status to use for VXLAN
+ * @arg link		Link object
+ *
+ * @return Status value on success or a negative error code
+ */
+int rtnl_link_vxlan_get_rsc(struct rtnl_link *link)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!(vxi->vxi_mask & VXLAN_HAS_RSC))
+		return -NLE_AGAIN;
+
+	return vxi->vxi_rsc;
+}
+
+/**
+ * Enable Route Short Circuit
+ * @arg link		Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_enable_rsc(struct rtnl_link *link)
+{
+	return rtnl_link_vxlan_set_rsc(link, 1);
+}
+
+/**
+ * Disable Route Short Circuit
+ * @arg link		Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_disable_rsc(struct rtnl_link *link)
+{
+	return rtnl_link_vxlan_set_rsc(link, 0);
+}
+
+/**
+ * Set netlink LLADDR miss notification status to use for VXLAN
+ * @arg link		Link object
+ * @arg miss		Status value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_l2miss(struct rtnl_link *link, uint8_t miss)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	vxi->vxi_l2miss = miss;
+	vxi->vxi_mask |= VXLAN_HAS_L2MISS;
+
+	return 0;
+}
+
+/**
+ * Get netlink LLADDR miss notification status to use for VXLAN
+ * @arg link		Link object
+ *
+ * @return Status value on success or a negative error code
+ */
+int rtnl_link_vxlan_get_l2miss(struct rtnl_link *link)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!(vxi->vxi_mask & VXLAN_HAS_L2MISS))
+		return -NLE_AGAIN;
+
+	return vxi->vxi_l2miss;
+}
+
+/**
+ * Enable netlink LLADDR miss notifications
+ * @arg link		Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_enable_l2miss(struct rtnl_link *link)
+{
+	return rtnl_link_vxlan_set_l2miss(link, 1);
+}
+
+/**
+ * Disable netlink LLADDR miss notifications
+ * @arg link		Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_disable_l2miss(struct rtnl_link *link)
+{
+	return rtnl_link_vxlan_set_l2miss(link, 0);
+}
+
+/**
+ * Set netlink IP ADDR miss notification status to use for VXLAN
+ * @arg link		Link object
+ * @arg miss		Status value
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_set_l3miss(struct rtnl_link *link, uint8_t miss)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	vxi->vxi_l3miss = miss;
+	vxi->vxi_mask |= VXLAN_HAS_L3MISS;
+
+	return 0;
+}
+
+/**
+ * Get netlink IP ADDR miss notification status to use for VXLAN
+ * @arg link		Link object
+ *
+ * @return Status value on success or a negative error code
+ */
+int rtnl_link_vxlan_get_l3miss(struct rtnl_link *link)
+{
+	struct vxlan_info *vxi = link->l_info;
+
+	IS_VXLAN_LINK_ASSERT(link);
+
+	if (!(vxi->vxi_mask & VXLAN_HAS_L3MISS))
+		return -NLE_AGAIN;
+
+	return vxi->vxi_l3miss;
+}
+
+/**
+ * Enable netlink IP DDR miss notifications
+ * @arg link		Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_enable_l3miss(struct rtnl_link *link)
+{
+	return rtnl_link_vxlan_set_l3miss(link, 1);
+}
+
+/**
+ * Disable netlink IP ADDR miss notifications
+ * @arg link		Link object
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_link_vxlan_disable_l3miss(struct rtnl_link *link)
+{
+	return rtnl_link_vxlan_set_l3miss(link, 0);
+}
+
+/** @} */
+
+static void __init vxlan_init(void)
+{
+	rtnl_link_register_info(&vxlan_info_ops);
+}
+
+static void __exit vxlan_exit(void)
+{
+	rtnl_link_unregister_info(&vxlan_info_ops);
+}
+
+/** @} */
diff --git a/lib/route/neigh.c b/lib/route/neigh.c
index d4dc82c..ad26b4d 100644
--- a/lib/route/neigh.c
+++ b/lib/route/neigh.c
@@ -32,6 +32,7 @@
  *
  * @par Neighbour Flags
  * @code
+ * NTF_USE
  * NTF_PROXY
  * NTF_ROUTER
  * @endcode
@@ -147,12 +148,14 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
+#include <netlink/hashtable.h>
 #include <netlink/route/rtnl.h>
 #include <netlink/route/neighbour.h>
 #include <netlink/route/link.h>
+#include <netlink/hashtable.h>
 
 /** @cond SKIP */
 #define NEIGH_ATTR_FLAGS        0x01
@@ -164,6 +167,7 @@
 #define NEIGH_ATTR_FAMILY       0x40
 #define NEIGH_ATTR_TYPE         0x80
 #define NEIGH_ATTR_PROBES       0x100
+#define NEIGH_ATTR_MASTER       0x200
 
 static struct nl_cache_ops rtnl_neigh_ops;
 static struct nl_object_ops neigh_obj_ops;
@@ -196,6 +200,59 @@
 	return 0;
 }
 
+static void neigh_keygen(struct nl_object *obj, uint32_t *hashkey,
+			 uint32_t table_sz)
+{
+	struct rtnl_neigh *neigh = (struct rtnl_neigh *) obj;
+	unsigned int nkey_sz;
+	struct nl_addr *addr = NULL;
+	struct neigh_hash_key {
+		uint32_t	n_family;
+		uint32_t	n_ifindex;
+		char		n_addr[0];
+	} __attribute__((packed)) *nkey;
+#ifdef NL_DEBUG
+	char buf[INET6_ADDRSTRLEN+5];
+#endif
+
+	if (neigh->n_family == AF_BRIDGE) {
+		if (neigh->n_lladdr)
+			addr = neigh->n_lladdr;
+	} else if (neigh->n_dst) {
+		addr = neigh->n_dst;
+	}
+
+	nkey_sz = sizeof(*nkey);
+	if (addr)
+		nkey_sz += nl_addr_get_len(addr);
+
+	nkey = calloc(1, nkey_sz);
+	if (!nkey) {
+		*hashkey = 0;
+		return;
+	}
+	nkey->n_family = neigh->n_family;
+	if (neigh->n_family == AF_BRIDGE)
+		nkey->n_ifindex = neigh->n_master;
+	else
+		nkey->n_ifindex = neigh->n_ifindex;
+	if (addr)
+		memcpy(nkey->n_addr,
+			nl_addr_get_binary_addr(addr),
+			nl_addr_get_len(addr));
+
+	*hashkey = nl_hash(nkey, nkey_sz, 0) % table_sz;
+
+	NL_DBG(5, "neigh %p key (fam %d dev %d addr %s) keysz %d hash 0x%x\n",
+		neigh, nkey->n_family, nkey->n_ifindex,
+		nl_addr2str(addr, buf, sizeof(buf)),
+		nkey_sz, *hashkey);
+
+	free(nkey);
+
+	return;
+}
+
 static int neigh_compare(struct nl_object *_a, struct nl_object *_b,
 			uint32_t attrs, int flags)
 {
@@ -210,6 +267,7 @@
 	diff |= NEIGH_DIFF(TYPE,	a->n_type != b->n_type);
 	diff |= NEIGH_DIFF(LLADDR,	nl_addr_cmp(a->n_lladdr, b->n_lladdr));
 	diff |= NEIGH_DIFF(DST,		nl_addr_cmp(a->n_dst, b->n_dst));
+	diff |= NEIGH_DIFF(MASTER,	a->n_master != b->n_master);
 
 	if (flags & LOOSE_COMPARISON) {
 		diff |= NEIGH_DIFF(STATE,
@@ -226,7 +284,7 @@
 	return diff;
 }
 
-static struct trans_tbl neigh_attrs[] = {
+static const struct trans_tbl neigh_attrs[] = {
 	__ADD(NEIGH_ATTR_FLAGS, flags)
 	__ADD(NEIGH_ATTR_STATE, state)
 	__ADD(NEIGH_ATTR_LLADDR, lladdr)
@@ -244,6 +302,16 @@
 			   ARRAY_SIZE(neigh_attrs));
 }
 
+static uint32_t neigh_id_attrs_get(struct nl_object *obj)
+{
+	struct rtnl_neigh *neigh = (struct rtnl_neigh *)obj;
+
+	if (neigh->n_family == AF_BRIDGE)
+		return (NEIGH_ATTR_LLADDR | NEIGH_ATTR_FAMILY | NEIGH_ATTR_MASTER);
+	else
+		return (NEIGH_ATTR_IFINDEX | NEIGH_ATTR_DST | NEIGH_ATTR_FAMILY);
+}
+
 static struct nla_policy neigh_policy[NDA_MAX+1] = {
 	[NDA_CACHEINFO]	= { .minlen = sizeof(struct nda_cacheinfo) },
 	[NDA_PROBES]	= { .type = NLA_U32 },
@@ -253,6 +321,21 @@
 			    struct nlmsghdr *n, struct nl_parser_param *pp)
 {
 	struct rtnl_neigh *neigh;
+	int err;
+
+	if ((err = rtnl_neigh_parse(n, &neigh)) < 0)
+		return err;
+
+	err = pp->pp_cb((struct nl_object *) neigh, pp);
+
+	rtnl_neigh_put(neigh);
+	return err;
+}
+
+
+int rtnl_neigh_parse(struct nlmsghdr *n, struct rtnl_neigh **result)
+{
+	struct rtnl_neigh *neigh;
 	struct nlattr *tb[NDA_MAX + 1];
 	struct ndmsg *nm;
 	int err;
@@ -316,7 +399,27 @@
 		neigh->ce_mask |= NEIGH_ATTR_PROBES;
 	}
 
-	err = pp->pp_cb((struct nl_object *) neigh, pp);
+	/*
+	 * Get the bridge index for AF_BRIDGE family entries
+	 */
+	if (neigh->n_family == AF_BRIDGE) {
+		struct nl_cache *lcache = nl_cache_mngt_require_safe("route/link");
+		if (lcache ) {
+			struct rtnl_link *link = rtnl_link_get(lcache,
+							neigh->n_ifindex);
+			if (link) {
+				neigh->n_master = link->l_master;
+				rtnl_link_put(link);
+				neigh->ce_mask |= NEIGH_ATTR_MASTER;
+			}
+
+			nl_cache_put(lcache);
+		}
+	}
+
+	*result = neigh;
+	return 0;
+
 errout:
 	rtnl_neigh_put(neigh);
 	return err;
@@ -324,7 +427,9 @@
 
 static int neigh_request_update(struct nl_cache *c, struct nl_sock *h)
 {
-	return nl_rtgen_request(h, RTM_GETNEIGH, AF_UNSPEC, NLM_F_DUMP);
+	int family = c->c_iarg1;
+
+	return nl_rtgen_request(h, RTM_GETNEIGH, family, NLM_F_DUMP);
 }
 
 
@@ -335,9 +440,10 @@
 	struct nl_cache *link_cache;
 	char state[128], flags[64];
 
-	link_cache = nl_cache_mngt_require("route/link");
+	link_cache = nl_cache_mngt_require_safe("route/link");
 
-	nl_dump_line(p, "%s ", nl_addr2str(n->n_dst, dst, sizeof(dst)));
+	if (n->n_family != AF_BRIDGE)
+		nl_dump_line(p, "%s ", nl_addr2str(n->n_dst, dst, sizeof(dst)));
 
 	if (link_cache)
 		nl_dump(p, "dev %s ",
@@ -360,13 +466,16 @@
 	if (state[0] || flags[0])
 		nl_dump(p, ">");
 	nl_dump(p, "\n");
+
+	if (link_cache)
+		nl_cache_put(link_cache);
 }
 
 static void neigh_dump_details(struct nl_object *a, struct nl_dump_params *p)
 {
 	char rtn_type[32];
 	struct rtnl_neigh *n = (struct rtnl_neigh *) a;
-	int hz = nl_get_hz();
+	int hz = nl_get_user_hz();
 
 	neigh_dump_line(a, p);
 
@@ -383,51 +492,6 @@
 	neigh_dump_details(a, p);
 }
 
-static void neigh_dump_env(struct nl_object *obj, struct nl_dump_params *p)
-{
-	struct rtnl_neigh *neigh = (struct rtnl_neigh *) obj;
-	char buf[128];
-
-	nl_dump_line(p, "NEIGH_FAMILY=%s\n",
-		     nl_af2str(neigh->n_family, buf, sizeof(buf)));
-
-	if (neigh->ce_mask & NEIGH_ATTR_LLADDR)
-		nl_dump_line(p, "NEIGHT_LLADDR=%s\n",
-			     nl_addr2str(neigh->n_lladdr, buf, sizeof(buf)));
-
-	if (neigh->ce_mask & NEIGH_ATTR_DST)
-		nl_dump_line(p, "NEIGH_DST=%s\n",
-			     nl_addr2str(neigh->n_dst, buf, sizeof(buf)));
-
-	if (neigh->ce_mask & NEIGH_ATTR_IFINDEX) {
-		struct nl_cache *link_cache;
-
-		nl_dump_line(p, "NEIGH_IFINDEX=%u\n", neigh->n_ifindex);
-
-		link_cache = nl_cache_mngt_require("route/link");
-		if (link_cache)
-			nl_dump_line(p, "NEIGH_IFNAME=%s\n",
-				     rtnl_link_i2name(link_cache,
-						      neigh->n_ifindex,
-						      buf, sizeof(buf)));
-	}
-
-	if (neigh->ce_mask & NEIGH_ATTR_PROBES)
-		nl_dump_line(p, "NEIGH_PROBES=%u\n", neigh->n_probes);
-
-	if (neigh->ce_mask & NEIGH_ATTR_TYPE)
-		nl_dump_line(p, "NEIGH_TYPE=%s\n",
-			     nl_rtntype2str(neigh->n_type, buf, sizeof(buf)));
-
-	rtnl_neigh_flags2str(neigh->n_flags, buf, sizeof(buf));
-	if (buf[0])
-		nl_dump_line(p, "NEIGH_FLAGS=%s\n", buf);
-
-	rtnl_neigh_state2str(neigh->n_state, buf, sizeof(buf));
-	if (buf[0])
-		nl_dump_line(p, "NEIGH_STATE=%s\n", buf);
-}
-
 /**
  * @name Neighbour Object Allocation/Freeage
  * @{
@@ -452,7 +516,7 @@
 
 /**
  * Build a neighbour cache including all neighbours currently configured in the kernel.
- * @arg sk		Netlink socket.
+ * @arg sock		Netlink socket.
  * @arg result		Pointer to store resulting cache.
  *
  * Allocates a new neighbour cache, initializes it properly and updates it
@@ -470,6 +534,7 @@
  * @arg cache		neighbour cache
  * @arg ifindex		interface index the neighbour is on
  * @arg dst		destination address of the neighbour
+ *
  * @return neighbour handle or NULL if no match was found.
  */
 struct rtnl_neigh * rtnl_neigh_get(struct nl_cache *cache, int ifindex,
@@ -504,10 +569,16 @@
 		.ndm_state = NUD_PERMANENT,
 	};
 
-	if (!(tmpl->ce_mask & NEIGH_ATTR_DST))
-		return -NLE_MISSING_ATTR;
+	if (tmpl->n_family != AF_BRIDGE) {
+		if (!(tmpl->ce_mask & NEIGH_ATTR_DST))
+			return -NLE_MISSING_ATTR;
+		nhdr.ndm_family = nl_addr_get_family(tmpl->n_dst);
+	}
+	else
+		nhdr.ndm_family = AF_BRIDGE;
 
-	nhdr.ndm_family = nl_addr_get_family(tmpl->n_dst);
+	if (tmpl->ce_mask & NEIGH_ATTR_FLAGS)
+		nhdr.ndm_flags = tmpl->n_flags;
 
 	if (tmpl->ce_mask & NEIGH_ATTR_STATE)
 		nhdr.ndm_state = tmpl->n_state;
@@ -519,7 +590,8 @@
 	if (nlmsg_append(msg, &nhdr, sizeof(nhdr), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
 
-	NLA_PUT_ADDR(msg, NDA_DST, tmpl->n_dst);
+	if (tmpl->n_family != AF_BRIDGE)
+		NLA_PUT_ADDR(msg, NDA_DST, tmpl->n_dst);
 
 	if (tmpl->ce_mask & NEIGH_ATTR_LLADDR)
 		NLA_PUT_ADDR(msg, NDA_LLADDR, tmpl->n_lladdr);
@@ -655,7 +727,7 @@
  * @{
  */
 
-static struct trans_tbl neigh_states[] = {
+static const struct trans_tbl neigh_states[] = {
 	__ADD(NUD_INCOMPLETE, incomplete)
 	__ADD(NUD_REACHABLE, reachable)
 	__ADD(NUD_STALE, stale)
@@ -684,7 +756,8 @@
  * @{
  */
 
-static struct trans_tbl neigh_flags[] = {
+static const struct trans_tbl neigh_flags[] = {
+	__ADD(NTF_USE, use)
 	__ADD(NTF_PROXY, proxy)
 	__ADD(NTF_ROUTER, router)
 };
@@ -846,15 +919,17 @@
 	    [NL_DUMP_LINE]	= neigh_dump_line,
 	    [NL_DUMP_DETAILS]	= neigh_dump_details,
 	    [NL_DUMP_STATS]	= neigh_dump_stats,
-	    [NL_DUMP_ENV]	= neigh_dump_env,
 	},
 	.oo_compare		= neigh_compare,
+	.oo_keygen		= neigh_keygen,
 	.oo_attrs2str		= neigh_attrs2str,
 	.oo_id_attrs		= (NEIGH_ATTR_IFINDEX | NEIGH_ATTR_DST | NEIGH_ATTR_FAMILY),
+	.oo_id_attrs_get	= neigh_id_attrs_get
 };
 
 static struct nl_af_group neigh_groups[] = {
 	{ AF_UNSPEC, RTNLGRP_NEIGH },
+	{ AF_BRIDGE, RTNLGRP_NEIGH },
 	{ END_OF_GROUP_LIST },
 };
 
diff --git a/lib/route/neightbl.c b/lib/route/neightbl.c
index 9599faa..f9c9c27 100644
--- a/lib/route/neightbl.c
+++ b/lib/route/neightbl.c
@@ -16,7 +16,7 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/rtnl.h>
@@ -143,7 +143,7 @@
 	ntbl->nt_family = rtmsg->rtgen_family;
 
 	if (tb[NDTA_NAME] == NULL) {
-		return -NLE_MISSING_ATTR;
+		err = -NLE_MISSING_ATTR;
 		goto errout;
 	}
 
@@ -237,7 +237,7 @@
 	if (ntbl->nt_parms.ntp_mask & NEIGHTBLPARM_ATTR_IFINDEX) {
 		struct nl_cache *link_cache;
 		
-		link_cache = nl_cache_mngt_require("route/link");
+		link_cache = nl_cache_mngt_require_safe("route/link");
 
 		if (link_cache) {
 			char buf[32];
@@ -245,6 +245,7 @@
 				rtnl_link_i2name(link_cache,
 						 ntbl->nt_parms.ntp_ifindex,
 						 buf, sizeof(buf)));
+			nl_cache_put(link_cache);
 		} else
 			nl_dump(p, "<%u> ", ntbl->nt_parms.ntp_ifindex);
 	} else
@@ -332,21 +333,32 @@
 	if (!(ntbl->ce_mask & NEIGHTBL_ATTR_STATS))
 		return;
 
-	nl_dump_line(p, "    lookups %lld hits %lld failed %lld " \
-		    "allocations %lld destroys %lld\n",
+	nl_dump_line(p, "   " \
+                    " lookups %" PRIu64 \
+                    " hits %" PRIu64 \
+                    " failed %" PRIu64 \
+		    " allocations %" PRIu64 \
+                    " destroys %" PRIu64 \
+                    "\n",
 		ntbl->nt_stats.ndts_lookups,
 		ntbl->nt_stats.ndts_hits,
 		ntbl->nt_stats.ndts_res_failed,
 		ntbl->nt_stats.ndts_allocs,
 		ntbl->nt_stats.ndts_destroys);
 
-	nl_dump_line(p, "    hash-grows %lld forced-gc-runs %lld " \
-		    "periodic-gc-runs %lld\n",
+	nl_dump_line(p, "   " \
+                        " hash-grows %" PRIu64 \
+                        " forced-gc-runs %" PRIu64 \
+                        " periodic-gc-runs %" PRIu64 \
+                        "\n",
 		ntbl->nt_stats.ndts_hash_grows,
 		ntbl->nt_stats.ndts_forced_gc_runs,
 		ntbl->nt_stats.ndts_periodic_gc_runs);
 
-	nl_dump_line(p, "    rcv-unicast-probes %lld rcv-multicast-probes %lld\n",
+	nl_dump_line(p, "   " \
+                        " rcv-unicast-probes %" PRIu64 \
+                        " rcv-multicast-probes %" PRIu64 \
+                        "\n",
 		ntbl->nt_stats.ndts_rcv_probes_ucast,
 		ntbl->nt_stats.ndts_rcv_probes_mcast);
 }
diff --git a/lib/route/nexthop.c b/lib/route/nexthop.c
index 788e255..d3ca499 100644
--- a/lib/route/nexthop.c
+++ b/lib/route/nexthop.c
@@ -15,7 +15,7 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/rtnl.h>
@@ -109,7 +109,7 @@
 	struct nl_cache *link_cache;
 	char buf[128];
 
-	link_cache = nl_cache_mngt_require("route/link");
+	link_cache = nl_cache_mngt_require_safe("route/link");
 
 	nl_dump(dp, "via");
 
@@ -128,6 +128,9 @@
 	}
 
 	nl_dump(dp, " ");
+
+	if (link_cache)
+		nl_cache_put(link_cache);
 }
 
 static void nh_dump_details(struct rtnl_nexthop *nh, struct nl_dump_params *dp)
@@ -135,7 +138,7 @@
 	struct nl_cache *link_cache;
 	char buf[128];
 
-	link_cache = nl_cache_mngt_require("route/link");
+	link_cache = nl_cache_mngt_require_safe("route/link");
 
 	nl_dump(dp, "nexthop");
 
@@ -164,44 +167,11 @@
 	if (nh->ce_mask & NH_ATTR_FLAGS)
 		nl_dump(dp, " <%s>", rtnl_route_nh_flags2str(nh->rtnh_flags,
 							buf, sizeof(buf)));
+
+	if (link_cache)
+		nl_cache_put(link_cache);
 }
 
-static void nh_dump_env(struct rtnl_nexthop *nh, struct nl_dump_params *dp)
-{
-	struct nl_cache *link_cache;
-	char buf[128];
-
-	link_cache = nl_cache_mngt_require("route/link");
-
-	if (nh->ce_mask & NH_ATTR_GATEWAY)
-		nl_dump_line(dp, "ROUTE_NH%d_VIA=%s\n", dp->dp_ivar,
-			nl_addr2str(nh->rtnh_gateway, buf, sizeof(buf)));
-
-	if(nh->ce_mask & NH_ATTR_IFINDEX) {
-		if (link_cache) {
-			nl_dump_line(dp, "ROUTE_NH%d_DEV=%s\n", dp->dp_ivar,
-					rtnl_link_i2name(link_cache,
-						 nh->rtnh_ifindex,
-						 buf, sizeof(buf)));
-		} else
-			nl_dump_line(dp, "ROUTE_NH%d_DEV=%d\n", dp->dp_ivar,
-					nh->rtnh_ifindex);
-	}
-
-	if (nh->ce_mask & NH_ATTR_WEIGHT)
-		nl_dump_line(dp, "ROUTE_NH%d_WEIGHT=%u\n", dp->dp_ivar,
-				nh->rtnh_weight);
-
-	if (nh->ce_mask & NH_ATTR_REALMS)
-		nl_dump_line(dp, "ROUTE_NH%d_REALM=%04x:%04x\n", dp->dp_ivar,
-			RTNL_REALM_FROM(nh->rtnh_realms),
-			RTNL_REALM_TO(nh->rtnh_realms));
-
-	if (nh->ce_mask & NH_ATTR_FLAGS)
-		nl_dump_line(dp, "ROUTE_NH%d_FLAGS=<%s>\n", dp->dp_ivar,
-			rtnl_route_nh_flags2str(nh->rtnh_flags,
-							buf, sizeof(buf)));
-}
 void rtnl_route_nh_dump(struct rtnl_nexthop *nh, struct nl_dump_params *dp)
 {
 	switch (dp->dp_type) {
@@ -215,10 +185,6 @@
 			nh_dump_details(nh, dp);
 		break;
 
-	case NL_DUMP_ENV:
-		nh_dump_env(nh, dp);
-		break;
-	
 	default:
 		break;
 	}
@@ -251,6 +217,7 @@
 	return nh->rtnh_ifindex;
 }	
 
+/* FIXME: Convert to return an int */
 void rtnl_route_nh_set_gateway(struct rtnl_nexthop *nh, struct nl_addr *addr)
 {
 	struct nl_addr *old = nh->rtnh_gateway;
@@ -309,7 +276,7 @@
  * @{
  */
 
-static struct trans_tbl nh_flags[] = {
+static const struct trans_tbl nh_flags[] = {
 	__ADD(RTNH_F_DEAD, dead)
 	__ADD(RTNH_F_PERVASIVE, pervasive)
 	__ADD(RTNH_F_ONLINK, onlink)
diff --git a/lib/route/pktloc.c b/lib/route/pktloc.c
index f0d0155..27d63be 100644
--- a/lib/route/pktloc.c
+++ b/lib/route/pktloc.c
@@ -2,10 +2,11 @@
  * lib/route/pktloc.c     Packet Location Aliasing
  *
  *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU General Public License as
- *	published by the Free Software Foundation version 2 of the License.
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
  *
- * Copyright (c) 2008-2010 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2013 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
@@ -20,7 +21,7 @@
  * library and provides a well defined set of definitions for most common
  * protocol fields.
  *
- * @subsection pktloc_examples Examples
+ * @section pktloc_examples Examples
  * @par Example 1.1 Looking up a packet location
  * @code
  * struct rtnl_pktloc *loc;
@@ -30,8 +31,8 @@
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/pktloc.h>
@@ -39,13 +40,13 @@
 #include "pktloc_syntax.h"
 #include "pktloc_grammar.h"
 
-/** @cond */
+/** @cond SKIP */
 #define PKTLOC_NAME_HT_SIZ 256
 
 static struct nl_list_head pktloc_name_ht[PKTLOC_NAME_HT_SIZ];
 
 /* djb2 */
-unsigned int pktloc_hash(const char *str)
+static unsigned int pktloc_hash(const char *str)
 {
 	unsigned long hash = 5381;
 	int c;
@@ -56,16 +57,25 @@
 	return hash % PKTLOC_NAME_HT_SIZ;
 }
 
-
-void rtnl_pktloc_add(struct rtnl_pktloc *loc)
+static int __pktloc_lookup(const char *name, struct rtnl_pktloc **result)
 {
-	nl_list_add_tail(&loc->list, &pktloc_name_ht[pktloc_hash(loc->name)]);
+	struct rtnl_pktloc *loc;
+	int hash;
+
+	hash = pktloc_hash(name);
+	nl_list_for_each_entry(loc, &pktloc_name_ht[hash], list) {
+		if (!strcasecmp(loc->name, name)) {
+			loc->refcnt++;
+			*result = loc;
+			return 0;
+		}
+	}
+
+	return -NLE_OBJ_NOTFOUND;
 }
 
 extern int pktloc_parse(void *scanner);
 
-/** @endcond */
-
 static void rtnl_pktloc_free(struct rtnl_pktloc *loc)
 {
 	if (!loc)
@@ -77,15 +87,16 @@
 
 static int read_pktlocs(void)
 {
-	YY_BUFFER_STATE buf;
+	YY_BUFFER_STATE buf = NULL;
 	yyscan_t scanner = NULL;
 	static time_t last_read;
-	struct stat st = {0};
+	struct stat st;
 	char *path;
 	int i, err;
 	FILE *fd;
 
-	asprintf(&path, "%s/pktloc", SYSCONFDIR);
+	if (build_sysconf_path(&path, "pktloc") < 0)
+		return -NLE_NOMEM;
 
 	/* if stat fails, just try to read the file */
 	if (stat(path, &st) == 0) {
@@ -94,39 +105,54 @@
 			return 0;
 	}
 
-	if (!(fd = fopen(path, "r")))
-		return -NLE_PKTLOC_FILE;
+	NL_DBG(2, "Reading packet location file \"%s\"\n", path);
+
+	if (!(fd = fopen(path, "r"))) {
+		err = -NLE_PKTLOC_FILE;
+		goto errout;
+	}
 
 	for (i = 0; i < PKTLOC_NAME_HT_SIZ; i++) {
 		struct rtnl_pktloc *loc, *n;
 
 		nl_list_for_each_entry_safe(loc, n, &pktloc_name_ht[i], list)
-			rtnl_pktloc_free(loc);
+			rtnl_pktloc_put(loc);
 
 		nl_init_list_head(&pktloc_name_ht[i]);
 	}
 
-	if ((err = pktloc_lex_init(&scanner)) < 0)
-		return -NLE_FAILURE;
+	if ((err = pktloc_lex_init(&scanner)) < 0) {
+		err = -NLE_FAILURE;
+		goto errout_close;
+	}
 
 	buf = pktloc__create_buffer(fd, YY_BUF_SIZE, scanner);
 	pktloc__switch_to_buffer(buf, scanner);
 
-	if ((err = pktloc_parse(scanner)) < 0)
-		return -NLE_FAILURE;
+	if ((err = pktloc_parse(scanner)) != 0) {
+		pktloc__delete_buffer(buf, scanner);
+		err = -NLE_PARSE_ERR;
+		goto errout_scanner;
+	}
 
-	if (scanner)
-		pktloc_lex_destroy(scanner);
-
-	free(path);
 	last_read = st.st_mtime;
 
-	return 0;
+errout_scanner:
+	pktloc_lex_destroy(scanner);
+errout_close:
+	fclose(fd);
+errout:
+	free(path);
+
+	return err;
 }
 
+/** @endcond */
+
 /**
  * Lookup packet location alias
  * @arg name		Name of packet location.
+ * @arg result		Result pointer
  *
  * Tries to find a matching packet location alias for the supplied
  * packet location name.
@@ -134,27 +160,90 @@
  * The file containing the packet location definitions is automatically
  * re-read if its modification time has changed since the last call.
  *
+ * The returned packet location has to be returned after use by calling
+ * rtnl_pktloc_put() in order to allow freeing its memory after the last
+ * user has abandoned it.
+ *
  * @return 0 on success or a negative error code.
  * @retval NLE_PKTLOC_FILE Unable to open packet location file.
  * @retval NLE_OBJ_NOTFOUND No matching packet location alias found.
  */
 int rtnl_pktloc_lookup(const char *name, struct rtnl_pktloc **result)
 {
-	struct rtnl_pktloc *loc;
-	int hash, err;
+	int err;
 
 	if ((err = read_pktlocs()) < 0)
 		return err;
+	
+	return __pktloc_lookup(name, result);
+}
 
-	hash = pktloc_hash(name);
-	nl_list_for_each_entry(loc, &pktloc_name_ht[hash], list) {
-		if (!strcasecmp(loc->name, name)) {
-			*result = loc;
-			return 0;
-		}
+/**
+ * Allocate packet location object
+ */
+struct rtnl_pktloc *rtnl_pktloc_alloc(void)
+{
+	struct rtnl_pktloc *loc;
+
+	if (!(loc = calloc(1, sizeof(*loc))))
+		return NULL;
+
+	loc->refcnt = 1;
+	nl_init_list_head(&loc->list);
+
+	return loc;
+}
+
+/**
+ * Return reference of a packet location
+ * @arg loc		packet location object.
+ */
+void rtnl_pktloc_put(struct rtnl_pktloc *loc)
+{
+	if (!loc)
+		return;
+
+	loc->refcnt--;
+	if (loc->refcnt <= 0)
+		rtnl_pktloc_free(loc);
+}
+
+/**
+ * Add a packet location to the hash table
+ * @arg loc		packet location object
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_pktloc_add(struct rtnl_pktloc *loc)
+{
+	struct rtnl_pktloc *l;
+
+	if (__pktloc_lookup(loc->name, &l) == 0) {
+		rtnl_pktloc_put(l);
+		return -NLE_EXIST;
 	}
 
-	return -NLE_OBJ_NOTFOUND;
+	NL_DBG(2, "New packet location entry \"%s\" align=%u layer=%u "
+		  "offset=%u mask=%#x shift=%u refnt=%u\n",
+		  loc->name, loc->align, loc->layer, loc->offset,
+		  loc->mask, loc->shift, loc->refcnt);
+
+	nl_list_add_tail(&loc->list, &pktloc_name_ht[pktloc_hash(loc->name)]);
+
+	return 0;
+}
+
+void rtnl_pktloc_foreach(void (*cb)(struct rtnl_pktloc *, void *), void *arg)
+{
+	struct rtnl_pktloc *loc;
+	int i;
+
+	/* ignore errors */
+	read_pktlocs();
+
+	for (i = 0; i < PKTLOC_NAME_HT_SIZ; i++)
+		nl_list_for_each_entry(loc, &pktloc_name_ht[i], list)
+			cb(loc, arg);
 }
 
 static int __init pktloc_init(void)
@@ -166,3 +255,5 @@
 	
 	return 0;
 }
+
+/** @} */
diff --git a/lib/route/pktloc_grammar.l b/lib/route/pktloc_grammar.l
index f710430..cbb42b3 100644
--- a/lib/route/pktloc_grammar.l
+++ b/lib/route/pktloc_grammar.l
@@ -1,6 +1,6 @@
 %{
- #include <netlink-local.h>
- #include <netlink-tc.h>
+ #include <netlink-private/netlink.h>
+ #include <netlink-private/tc.h>
  #include <netlink/netlink.h>
  #include <netlink/utils.h>
  #include <netlink/route/pktloc.h>
@@ -11,6 +11,7 @@
 %option reentrant
 %option warn
 %option noyywrap
+%option noinput
 %option nounput
 %option bison-bridge
 %option bison-locations
@@ -30,10 +31,18 @@
 
 "+"			{ return yylval->i = yytext[0]; }
 
-[lL][iI][nN][kK]	{ yylval->i = TCF_LAYER_LINK; return LAYER; }
-[nN][eE][tT]		{ yylval->i = TCF_LAYER_NETWORK; return LAYER; }
+[uU]8			{ yylval->i = TCF_EM_ALIGN_U8; return ALIGN; }
+[uU]16			{ yylval->i = TCF_EM_ALIGN_U16; return ALIGN; }
+[uU]32			{ yylval->i = TCF_EM_ALIGN_U32; return ALIGN; }
+
+[lL][iI][nN][kK]	|
+[eE][tT][hH]		{ yylval->i = TCF_LAYER_LINK; return LAYER; }
+[nN][eE][tT]		|
+[iI][pP]		{ yylval->i = TCF_LAYER_NETWORK; return LAYER; }
+[tT][rR][aA][nN][sS][pP][oO][rR][tT] |
 [tT][cC][pP]		{ yylval->i = TCF_LAYER_TRANSPORT; return LAYER; }
 
+
 [^ \t\r\n+]+		{
 				yylval->s = strdup(yytext);
 				if (yylval->s == NULL)
diff --git a/lib/route/pktloc_syntax.y b/lib/route/pktloc_syntax.y
index 05d609a..25d8710 100644
--- a/lib/route/pktloc_syntax.y
+++ b/lib/route/pktloc_syntax.y
@@ -1,6 +1,6 @@
 %{
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/pktloc.h>
@@ -13,6 +13,7 @@
 
 %parse-param {void *scanner}
 %lex-param {void *scanner}
+%expect 1
 
 %union {
 	struct rtnl_pktloc *l;
@@ -22,18 +23,17 @@
 
 %{
 extern int pktloc_lex(YYSTYPE *, YYLTYPE *, void *);
-extern void rtnl_pktloc_add(struct rtnl_pktloc *);
 
 static void yyerror(YYLTYPE *locp, void *scanner, const char *msg)
 {
-	/* FIXME */
+	NL_DBG(1, "Error while parsing packet location file: %s\n", msg);
 }
 %}
 
-%token <i> ERROR NUMBER LAYER
+%token <i> ERROR NUMBER LAYER ALIGN
 %token <s> NAME
 
-%type <i> mask layer
+%type <i> mask layer align shift
 %type <l> location
 
 %destructor { free($$); } NAME
@@ -43,56 +43,44 @@
 %%
 
 input:
-	def
-		{ }
-	;
-
-def:
 	/* empty */
-		{ }
-	| location def
-		{ }
+	| location input
 	;
 
 location:
-	NAME NAME layer NUMBER mask
+	NAME align layer NUMBER mask shift
 		{
 			struct rtnl_pktloc *loc;
 
-			if (!(loc = calloc(1, sizeof(*loc)))) {
-				/* FIXME */
+			if (!(loc = rtnl_pktloc_alloc())) {
+				NL_DBG(1, "Allocating a packet location "
+					  "object failed.\n");
+				YYABORT;
 			}
 
-			if (!strcasecmp($2, "u8"))
-				loc->align = TCF_EM_ALIGN_U8;
-			else if (!strcasecmp($2, "h8")) {
-				loc->align = TCF_EM_ALIGN_U8;
-				loc->flags = TCF_EM_CMP_TRANS;
-			} else if (!strcasecmp($2, "u16"))
-				loc->align = TCF_EM_ALIGN_U16;
-			else if (!strcasecmp($2, "h16")) {
-				loc->align = TCF_EM_ALIGN_U16;
-				loc->flags = TCF_EM_CMP_TRANS;
-			} else if (!strcasecmp($2, "u32"))
-				loc->align = TCF_EM_ALIGN_U32;
-			else if (!strcasecmp($2, "h32")) {
-				loc->align = TCF_EM_ALIGN_U32;
-				loc->flags = TCF_EM_CMP_TRANS;
-			}
-			
-			free($2);
-
 			loc->name = $1;
+			loc->align = $2;
 			loc->layer = $3;
 			loc->offset = $4;
 			loc->mask = $5;
+			loc->shift = $6;
 
-			rtnl_pktloc_add(loc);
+			if (rtnl_pktloc_add(loc) < 0) {
+				NL_DBG(1, "Duplicate packet location entry "
+					  "\"%s\"\n", $1);
+			}
 
 			$$ = loc;
 		}
 	;
 
+align:
+	ALIGN
+		{ $$ = $1; }
+	| NUMBER
+		{ $$ = $1; }
+	;
+
 layer:
 	/* empty */
 		{ $$ = TCF_LAYER_NETWORK; }
@@ -106,3 +94,10 @@
 	| NUMBER
 		{ $$ = $1; }
 	;
+
+shift:
+	/* empty */
+		{ $$ = 0; }
+	| NUMBER
+		{ $$ = $1; }
+	;
diff --git a/lib/route/qdisc.c b/lib/route/qdisc.c
index cfeaf05..b8b6fa5 100644
--- a/lib/route/qdisc.c
+++ b/lib/route/qdisc.c
@@ -6,125 +6,44 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup tc
  * @defgroup qdisc Queueing Disciplines
- *
- * @par Qdisc Handles
- * In general, qdiscs are identified by the major part of a traffic control
- * handle (the upper 16 bits). A few special values exist though:
- *  - \c TC_H_ROOT: root qdisc (directly attached to the device)
- *  - \c TC_H_INGRESS: ingress qdisc (directly attached to the device)
- *  - \c TC_H_UNSPEC: unspecified qdisc (no reference)
- *
- * @par 1) Adding a Qdisc
- * @code
- * // Allocate a new empty qdisc to be filled out
- * struct rtnl_qdisc *qdisc = rtnl_qdisc_alloc();
- *
- * // ... specify the kind of the Qdisc
- * rtnl_qdisc_set_kind(qdisc, "pfifo");
- *
- * // Specify the device the qdisc should be attached to
- * rtnl_qdisc_set_ifindex(qdisc, ifindex);
- *
- * // ... specify the parent qdisc
- * rtnl_qdisc_set_parent(qdisc, TC_H_ROOT);
- *
- * // Specifying the handle is not required but makes reidentifying easier
- * // and may help to avoid adding a qdisc twice.
- * rtnl_qdisc_set_handle(qdisc, 0x000A0000);
- *
- * // Now on to specify the qdisc specific options, see the relevant qdisc
- * // modules for documentation, in this example we set the upper limit of
- * // the packet fifo qdisc to 64
- * rtnl_qdisc_fifo_set_limit(qdisc, 64);
- *
- * rtnl_qdisc_add(handle, qdisc, NLM_R_REPLACE);
- *
- * // Free up the memory
- * rtnl_qdisc_put(qdisc);
- * @endcode
- *
- * @par 2) Deleting a Qdisc
- * @code
- * // Allocate a new empty qdisc to be filled out with the parameters
- * // specifying the qdisc to be deleted. Alternatively a fully equiped
- * // Qdisc object from a cache can be used.
- * struct rtnl_qdisc *qdisc = rtnl_qdisc_alloc();
- *
- * // The interface index of the device the qdisc is on and the parent handle
- * // are the least required fields to be filled out.
- * // Note: Specify TC_H_ROOT or TC_H_INGRESS as parent handle to delete the
- * //       root respectively root ingress qdisc.
- * rtnl_qdisc_set_ifindex(qdisc, ifindex);
- * rtnl_qdisc_set_parent(qdisc, parent_handle);
- *
- * // If required for identification, the handle can be specified as well.
- * rtnl_qdisc_set_handle(qdisc, qdisc_handle);
- *
- * // Not required but maybe helpful as sanity check, the kind of the qdisc
- * // can be specified to avoid mistakes.
- * rtnl_qdisc_set_kind(qdisc, "pfifo");
- *
- * // Finally delete the qdisc with rtnl_qdisc_delete(), alternatively
- * // rtnl_qdisc_build_delete_request() can be invoked to generate an
- * // appropritate netlink message to send out.
- * rtnl_qdisc_delete(handle, qdisc);
- *
- * // Free up the memory
- * rtnl_qdisc_put(qdisc);
- * @endcode
- *
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/link.h>
-#include <netlink/route/tc.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/qdisc.h>
 #include <netlink/route/class.h>
 #include <netlink/route/classifier.h>
-#include <netlink/route/qdisc-modules.h>
 
 static struct nl_cache_ops rtnl_qdisc_ops;
+static struct nl_object_ops qdisc_obj_ops;
 
 static int qdisc_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
 			    struct nlmsghdr *n, struct nl_parser_param *pp)
 {
-	int err;
 	struct rtnl_qdisc *qdisc;
-	struct rtnl_qdisc_ops *qops;
+	int err;
 
-	qdisc = rtnl_qdisc_alloc();
-	if (!qdisc) {
-		err = -NLE_NOMEM;
+	if (!(qdisc = rtnl_qdisc_alloc()))
+		return -NLE_NOMEM;
+
+	if ((err = rtnl_tc_msg_parse(n, TC_CAST(qdisc))) < 0)
 		goto errout;
-	}
 
-	qdisc->ce_msgtype = n->nlmsg_type;
-
-	err = tca_msg_parser(n, (struct rtnl_tca *) qdisc);
-	if (err < 0)
-		goto errout_free;
-
-	qops = rtnl_qdisc_lookup_ops(qdisc);
-	if (qops && qops->qo_msg_parser) {
-		err = qops->qo_msg_parser(qdisc);
-		if (err < 0)
-			goto errout_free;
-	}
-
-	err = pp->pp_cb((struct nl_object *) qdisc, pp);
-errout_free:
-	rtnl_qdisc_put(qdisc);
+	err = pp->pp_cb(OBJ_CAST(qdisc), pp);
 errout:
+	rtnl_qdisc_put(qdisc);
+
 	return err;
 }
 
@@ -140,87 +59,107 @@
 }
 
 /**
- * @name QDisc Addition
+ * @name Allocation/Freeing
  * @{
  */
 
-static int qdisc_build(struct rtnl_qdisc *qdisc, int type, int flags,
-		       struct nl_msg **result)
+struct rtnl_qdisc *rtnl_qdisc_alloc(void)
 {
-	struct rtnl_qdisc_ops *qops;
-	int err;
+	struct rtnl_tc *tc;
 
-	err = tca_build_msg((struct rtnl_tca *) qdisc, type, flags, result);
-	if (err < 0)
-		return err;
+	tc = TC_CAST(nl_object_alloc(&qdisc_obj_ops));
+	if (tc)
+		tc->tc_type = RTNL_TC_TYPE_QDISC;
 
-	qops = rtnl_qdisc_lookup_ops(qdisc);
-	if (qops && qops->qo_get_opts) {
-		struct nl_msg *opts;
-		
-		opts = qops->qo_get_opts(qdisc);
-		if (opts) {
-			err = nla_put_nested(*result, TCA_OPTIONS, opts);
-			nlmsg_free(opts);
-			if (err < 0)
-				goto errout;
-		}
-	}
-	/* Some qdiscs don't accept properly nested messages (e.g. netem). To
-	 * accomodate for this, they can complete the message themselves.
-	 */		
-	else if (qops && qops->qo_build_msg) {
-		err = qops->qo_build_msg(qdisc, *result);
-		if (err < 0)
-			goto errout;
+	return (struct rtnl_qdisc *) tc;
+}
+
+void rtnl_qdisc_put(struct rtnl_qdisc *qdisc)
+{
+	nl_object_put((struct nl_object *) qdisc);
+}
+
+/** @} */
+
+/**
+ * @name Addition / Modification / Deletion
+ * @{
+ */
+
+static int build_qdisc_msg(struct rtnl_qdisc *qdisc, int type, int flags,
+			   struct nl_msg **result)
+{
+	if (!(qdisc->ce_mask & TCA_ATTR_IFINDEX)) {
+		APPBUG("ifindex must be specified");
+		return -NLE_MISSING_ATTR;
 	}
 
-	return 0;
-errout:
-	nlmsg_free(*result);
-
-	return err;
+	return rtnl_tc_msg_build(TC_CAST(qdisc), type, flags, result);
 }
 
 /**
- * Build a netlink message to add a new qdisc
- * @arg qdisc		qdisc to add 
- * @arg flags		additional netlink message flags
- * @arg result		Pointer to store resulting message.
+ * Build a netlink message requesting the addition of a qdisc
+ * @arg qdisc		Qdisc to add 
+ * @arg flags		Additional netlink message flags
+ * @arg result		Pointer to store resulting netlink message
  *
- * Builds a new netlink message requesting an addition of a qdisc.
- * The netlink message header isn't fully equipped with all relevant
- * fields and must be sent out via nl_send_auto_complete() or
- * supplemented as needed. 
+ * The behaviour of this function is identical to rtnl_qdisc_add() with
+ * the exception that it will not send the message but return it int the
+ * provided return pointer instead.
  *
- * Common message flags used:
- *  - NLM_F_REPLACE - replace a potential existing qdisc
+ * @see rtnl_qdisc_add()
  *
  * @return 0 on success or a negative error code.
  */
 int rtnl_qdisc_build_add_request(struct rtnl_qdisc *qdisc, int flags,
 				 struct nl_msg **result)
 {
-	return qdisc_build(qdisc, RTM_NEWQDISC, NLM_F_CREATE | flags, result);
+	if (!(qdisc->ce_mask & (TCA_ATTR_HANDLE | TCA_ATTR_PARENT))) {
+		APPBUG("handle or parent must be specified");
+		return -NLE_MISSING_ATTR;
+	}
+
+	return build_qdisc_msg(qdisc, RTM_NEWQDISC, flags, result);
 }
 
 /**
- * Add a new qdisc
- * @arg sk		Netlink socket.
- * @arg qdisc		qdisc to delete
- * @arg flags		additional netlink message flags
+ * Add qdisc
+ * @arg sk		Netlink socket
+ * @arg qdisc		Qdisc to add 
+ * @arg flags		Additional netlink message flags
  *
- * Builds a netlink message by calling rtnl_qdisc_build_add_request(),
- * sends the request to the kernel and waits for the ACK to be
- * received and thus blocks until the request has been processed.
+ * Builds a \c RTM_NEWQDISC netlink message requesting the addition
+ * of a new qdisc and sends the message to the kernel. The configuration
+ * of the qdisc is derived from the attributes of the specified qdisc.
  *
- * Common message flags used:
- *  - NLM_F_REPLACE - replace a potential existing qdisc
+ * The following flags may be specified:
+ *  - \c NLM_F_CREATE:  Create qdisc if it does not exist, otherwise 
+ *                      -NLE_OBJ_NOTFOUND is returned.
+ *  - \c NLM_F_REPLACE: If another qdisc is already attached to the
+ *                      parent, replace it even if the handles mismatch.
+ *  - \c NLM_F_EXCL:    Return -NLE_EXISTS if a qdisc with matching
+ *                      handle exists already.
  *
- * @return 0 on success or a negative error code
+ * Existing qdiscs with matching handles will be updated, unless the
+ * flag \c NLM_F_EXCL is specified. If their handles do not match, the
+ * error -NLE_EXISTS is returned unless the flag \c NLM_F_REPLACE is
+ * specified in which case the existing qdisc is replaced with the new
+ * one.  If no matching qdisc exists, it will be created if the flag
+ * \c NLM_F_CREATE is set, otherwise the error -NLE_OBJ_NOTFOUND is
+ * returned. 
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
+ *       this function to return immediately after sending. In this case,
+ *       it is the responsibility of the caller to handle any error
+ *       messages returned.
+ *
+ * @return 0 on success or a negative error code.
  */
-int rtnl_qdisc_add(struct nl_sock *sk, struct rtnl_qdisc *qdisc,
-		   int flags)
+int rtnl_qdisc_add(struct nl_sock *sk, struct rtnl_qdisc *qdisc, int flags)
 {
 	struct nl_msg *msg;
 	int err;
@@ -228,86 +167,106 @@
 	if ((err = rtnl_qdisc_build_add_request(qdisc, flags, &msg)) < 0)
 		return err;
 
-	err = nl_send_auto_complete(sk, msg);
-	nlmsg_free(msg);
-	if (err < 0)
-		return err;
-
-	return wait_for_ack(sk);
+	return nl_send_sync(sk, msg);
 }
 
-/** @} */
-
 /**
- * @name QDisc Modification
- * @{
- */
-
-/**
- * Build a netlink message to change attributes of a existing qdisc
- * @arg qdisc		qdisc to change
- * @arg new		new qdisc attributes
- * @arg result		Pointer to store resulting message.
+ * Build netlink message requesting the update of a qdisc
+ * @arg qdisc		Qdisc to update
+ * @arg new		Qdisc with updated attributes
+ * @arg flags		Additional netlink message flags
+ * @arg result		Pointer to store resulting netlink message
  *
- * Builds a new netlink message requesting an change of qdisc
- * attributes. The netlink message header isn't fully equipped
- * with all relevant fields and must be sent out via
- * nl_send_auto_complete() or supplemented as needed. 
+ * The behaviour of this function is identical to rtnl_qdisc_update() with
+ * the exception that it will not send the message but return it in the
+ * provided return pointer instead.
+ *
+ * @see rtnl_qdisc_update()
  *
  * @return 0 on success or a negative error code.
  */
-int rtnl_qdisc_build_change_request(struct rtnl_qdisc *qdisc,
-				    struct rtnl_qdisc *new,
+int rtnl_qdisc_build_update_request(struct rtnl_qdisc *qdisc,
+				    struct rtnl_qdisc *new, int flags,
 				    struct nl_msg **result)
 {
-	return qdisc_build(qdisc, RTM_NEWQDISC, NLM_F_REPLACE, result);
+	if (flags & (NLM_F_CREATE | NLM_F_EXCL)) {
+		APPBUG("NLM_F_CREATE and NLM_F_EXCL may not be used here, "
+		       "use rtnl_qdisc_add()");
+		return -NLE_INVAL;
+	}
+
+	if (!(qdisc->ce_mask & TCA_ATTR_IFINDEX)) {
+		APPBUG("ifindex must be specified");
+		return -NLE_MISSING_ATTR;
+	}
+
+	if (!(qdisc->ce_mask & (TCA_ATTR_HANDLE | TCA_ATTR_PARENT))) {
+		APPBUG("handle or parent must be specified");
+		return -NLE_MISSING_ATTR;
+	}
+
+	rtnl_tc_set_ifindex(TC_CAST(new), qdisc->q_ifindex);
+
+	if (qdisc->ce_mask & TCA_ATTR_HANDLE)
+		rtnl_tc_set_handle(TC_CAST(new), qdisc->q_handle);
+
+	if (qdisc->ce_mask & TCA_ATTR_PARENT)
+		rtnl_tc_set_parent(TC_CAST(new), qdisc->q_parent);
+
+	return build_qdisc_msg(new, RTM_NEWQDISC, flags, result);
 }
 
 /**
- * Change attributes of a qdisc
- * @arg sk		Netlink socket.
- * @arg qdisc		qdisc to change
- * @arg new		new qdisc attributes
+ * Update qdisc
+ * @arg sk		Netlink socket
+ * @arg qdisc		Qdisc to update
+ * @arg new		Qdisc with updated attributes
+ * @arg flags		Additional netlink message flags
  *
- * Builds a netlink message by calling rtnl_qdisc_build_change_request(),
- * sends the request to the kernel and waits for the ACK to be
- * received and thus blocks until the request has been processed.
+ * Builds a \c RTM_NEWQDISC netlink message requesting the update
+ * of an existing qdisc and sends the message to the kernel.
  *
- * @return 0 on success or a negative error code
+ * This function is a varation of rtnl_qdisc_add() to update qdiscs
+ * if the qdisc to be updated is available as qdisc object. The
+ * behaviour is identical to the one of rtnl_qdisc_add except that
+ * before constructing the message, it copies the \c ifindex,
+ * \c handle, and \c parent from the original \p qdisc to the \p new
+ * qdisc.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
+ *       this function to return immediately after sending. In this case,
+ *       it is the responsibility of the caller to handle any error
+ *       messages returned.
+ *
+ * @return 0 on success or a negative error code.
  */
-int rtnl_qdisc_change(struct nl_sock *sk, struct rtnl_qdisc *qdisc,
-		      struct rtnl_qdisc *new)
+int rtnl_qdisc_update(struct nl_sock *sk, struct rtnl_qdisc *qdisc,
+		      struct rtnl_qdisc *new, int flags)
 {
 	struct nl_msg *msg;
 	int err;
 
-	if ((err = rtnl_qdisc_build_change_request(qdisc, new, &msg)) < 0)
-		return err;
-
-	err = nl_send_auto_complete(sk, msg);
-	nlmsg_free(msg);
+	err = rtnl_qdisc_build_update_request(qdisc, new, flags, &msg);
 	if (err < 0)
 		return err;
 
-	return wait_for_ack(sk);
+	return nl_send_sync(sk, msg);
 }
 
-/** @} */
-
 /**
- * @name QDisc Deletion
- * @{
- */
-
-/**
- * Build a netlink request message to delete a qdisc
- * @arg qdisc		qdisc to delete
- * @arg result		Pointer to store resulting message.
+ * Build netlink message requesting the deletion of a qdisc
+ * @arg qdisc		Qdisc to delete
+ * @arg result		Pointer to store resulting netlink message
  *
- * Builds a new netlink message requesting a deletion of a qdisc.
- * The netlink message header isn't fully equipped with all relevant
- * fields and must thus be sent out via nl_send_auto_complete()
- * or supplemented as needed.
+ * The behaviour of this function is identical to rtnl_qdisc_delete() with
+ * the exception that it will not send the message but return it in the
+ * provided return pointer instead.
+ *
+ * @see rtnl_qdisc_delete()
  *
  * @return 0 on success or a negative error code.
  */
@@ -316,38 +275,67 @@
 {
 	struct nl_msg *msg;
 	struct tcmsg tchdr;
-	int required = TCA_ATTR_IFINDEX | TCA_ATTR_PARENT;
+	uint32_t required = TCA_ATTR_IFINDEX | TCA_ATTR_PARENT;
 
-	if ((qdisc->ce_mask & required) != required)
-		BUG();
+	if ((qdisc->ce_mask & required) != required) {
+		APPBUG("ifindex and parent must be specified");
+		return -NLE_MISSING_ATTR;
+	}
 
-	msg = nlmsg_alloc_simple(RTM_DELQDISC, 0);
-	if (!msg)
+	if (!(msg = nlmsg_alloc_simple(RTM_DELQDISC, 0)))
 		return -NLE_NOMEM;
 
+	memset(&tchdr, 0, sizeof(tchdr));
+
 	tchdr.tcm_family = AF_UNSPEC;
-	tchdr.tcm_handle = qdisc->q_handle;
-	tchdr.tcm_parent = qdisc->q_parent;
 	tchdr.tcm_ifindex = qdisc->q_ifindex;
-	if (nlmsg_append(msg, &tchdr, sizeof(tchdr), NLMSG_ALIGNTO) < 0) {
-		nlmsg_free(msg);
-		return -NLE_MSGSIZE;
-	}
+	tchdr.tcm_parent = qdisc->q_parent;
+
+	if (qdisc->ce_mask & TCA_ATTR_HANDLE)
+		tchdr.tcm_handle = qdisc->q_handle;
+
+	if (nlmsg_append(msg, &tchdr, sizeof(tchdr), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	if (qdisc->ce_mask & TCA_ATTR_KIND)
+		NLA_PUT_STRING(msg, TCA_KIND, qdisc->q_kind);
 
 	*result = msg;
 	return 0;
+
+nla_put_failure:
+	nlmsg_free(msg);
+	return -NLE_MSGSIZE;
 }
 
 /**
- * Delete a qdisc
- * @arg sk		Netlink socket.
- * @arg qdisc		qdisc to delete
+ * Delete qdisc
+ * @arg sk		Netlink socket
+ * @arg qdisc		Qdisc to add 
  *
- * Builds a netlink message by calling rtnl_qdisc_build_delete_request(),
- * sends the request to the kernel and waits for the ACK to be
- * received and thus blocks until the request has been processed.
+ * Builds a \c RTM_NEWQDISC netlink message requesting the deletion
+ * of a qdisc and sends the message to the kernel.
  *
- * @return 0 on success or a negative error code
+ * The message is constructed out of the following attributes:
+ * - \c ifindex and \c parent
+ * - \c handle (optional, must match if provided)
+ * - \c kind (optional, must match if provided)
+ *
+ * All other qdisc attributes including all qdisc type specific
+ * attributes are ignored.
+ *
+ * After sending, the function will wait for the ACK or an eventual
+ * error message to be received and will therefore block until the
+ * operation has been completed.
+ *
+ * @note It is not possible to delete default qdiscs.
+ *
+ * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
+ *       this function to return immediately after sending. In this case,
+ *       it is the responsibility of the caller to handle any error
+ *       messages returned.
+ *
+ * @return 0 on success or a negative error code.
  */
 int rtnl_qdisc_delete(struct nl_sock *sk, struct rtnl_qdisc *qdisc)
 {
@@ -357,29 +345,23 @@
 	if ((err = rtnl_qdisc_build_delete_request(qdisc, &msg)) < 0)
 		return err;
 
-	err = nl_send_auto_complete(sk, msg);
-	nlmsg_free(msg);
-	if (err < 0)
-		return err;
-
-	return wait_for_ack(sk);
+	return nl_send_sync(sk, msg);
 }
 
 /** @} */
 
 /**
- * @name Qdisc Cache Management
+ * @name Cache Related Functions
  * @{
  */
 
 /**
- * Build a qdisc cache including all qdiscs currently configured in
- * the kernel
- * @arg sk		Netlink socket.
- * @arg result		Pointer to store resulting message.
+ * Allocate a cache and fill it with all configured qdiscs
+ * @arg sk		Netlink socket
+ * @arg result		Pointer to store the created cache
  *
- * Allocates a new cache, initializes it properly and updates it to
- * include all qdiscs currently configured in the kernel.
+ * Allocates a new qdisc cache and fills it with a list of all configured
+ * qdiscs on all network devices. Release the cache with nl_cache_free().
  *
  * @return 0 on success or a negative error code.
  */
@@ -389,14 +371,21 @@
 }
 
 /**
- * Look up qdisc by its parent in the provided cache
- * @arg cache		qdisc cache
- * @arg ifindex		interface the qdisc is attached to
- * @arg parent		parent handle
+ * Search qdisc by interface index and parent
+ * @arg cache		Qdisc cache
+ * @arg ifindex		Interface index
+ * @arg parent		Handle of parent qdisc
+ *
+ * Searches a qdisc cache previously allocated with rtnl_qdisc_alloc_cache()
+ * and searches for a qdisc matching the interface index and parent qdisc.
+ *
+ * The reference counter is incremented before returning the qdisc, therefore
+ * the reference must be given back with rtnl_qdisc_put() after usage.
+ *
  * @return pointer to qdisc inside the cache or NULL if no match was found.
  */
-struct rtnl_qdisc * rtnl_qdisc_get_by_parent(struct nl_cache *cache,
-					     int ifindex, uint32_t parent)
+struct rtnl_qdisc *rtnl_qdisc_get_by_parent(struct nl_cache *cache,
+					    int ifindex, uint32_t parent)
 {
 	struct rtnl_qdisc *q;
 
@@ -414,14 +403,21 @@
 }
 
 /**
- * Look up qdisc by its handle in the provided cache
- * @arg cache		qdisc cache
- * @arg ifindex		interface the qdisc is attached to
- * @arg handle		qdisc handle
- * @return pointer to qdisc inside the cache or NULL if no match was found.
+ * Search qdisc by interface index and handle
+ * @arg cache		Qdisc cache
+ * @arg ifindex		Interface index
+ * @arg handle		Handle
+ *
+ * Searches a qdisc cache previously allocated with rtnl_qdisc_alloc_cache()
+ * and searches for a qdisc matching the interface index and handle.
+ *
+ * The reference counter is incremented before returning the qdisc, therefore
+ * the reference must be given back with rtnl_qdisc_put() after usage.
+ *
+ * @return Qdisc or NULL if no match was found.
  */
-struct rtnl_qdisc * rtnl_qdisc_get(struct nl_cache *cache,
-				   int ifindex, uint32_t handle)
+struct rtnl_qdisc *rtnl_qdisc_get(struct nl_cache *cache, int ifindex,
+				  uint32_t handle)
 {
 	struct rtnl_qdisc *q;
 
@@ -440,6 +436,101 @@
 
 /** @} */
 
+/**
+ * @name Deprecated Functions
+ * @{
+ */
+
+/**
+ * Call a callback for each child class of a qdisc (deprecated)
+ *
+ * @deprecated Use of this function is deprecated, it does not allow
+ *             to handle the out of memory situation that can occur.
+ */
+void rtnl_qdisc_foreach_child(struct rtnl_qdisc *qdisc, struct nl_cache *cache,
+			      void (*cb)(struct nl_object *, void *), void *arg)
+{
+	struct rtnl_class *filter;
+	
+	filter = rtnl_class_alloc();
+	if (!filter)
+		return;
+
+	rtnl_tc_set_parent(TC_CAST(filter), qdisc->q_handle);
+	rtnl_tc_set_ifindex(TC_CAST(filter), qdisc->q_ifindex);
+	rtnl_tc_set_kind(TC_CAST(filter), qdisc->q_kind);
+
+	nl_cache_foreach_filter(cache, OBJ_CAST(filter), cb, arg);
+
+	rtnl_class_put(filter);
+}
+
+/**
+ * Call a callback for each filter attached to the qdisc (deprecated)
+ *
+ * @deprecated Use of this function is deprecated, it does not allow
+ *             to handle the out of memory situation that can occur.
+ */
+void rtnl_qdisc_foreach_cls(struct rtnl_qdisc *qdisc, struct nl_cache *cache,
+			    void (*cb)(struct nl_object *, void *), void *arg)
+{
+	struct rtnl_cls *filter;
+
+	if (!(filter = rtnl_cls_alloc()))
+		return;
+
+	rtnl_tc_set_ifindex(TC_CAST(filter), qdisc->q_ifindex);
+	rtnl_tc_set_parent(TC_CAST(filter), qdisc->q_parent);
+
+	nl_cache_foreach_filter(cache, OBJ_CAST(filter), cb, arg);
+	rtnl_cls_put(filter);
+}
+
+/**
+ * Build a netlink message requesting the update of a qdisc
+ *
+ * @deprecated Use of this function is deprecated in favour of
+ *             rtnl_qdisc_build_update_request() due to the missing
+ *             possibility of specifying additional flags.
+ */
+int rtnl_qdisc_build_change_request(struct rtnl_qdisc *qdisc,
+				    struct rtnl_qdisc *new,
+				    struct nl_msg **result)
+{
+	return rtnl_qdisc_build_update_request(qdisc, new, NLM_F_REPLACE,
+					       result);
+}
+
+/**
+ * Change attributes of a qdisc
+ *
+ * @deprecated Use of this function is deprecated in favour of
+ *             rtnl_qdisc_update() due to the missing possibility of
+ *             specifying additional flags.
+ */
+int rtnl_qdisc_change(struct nl_sock *sk, struct rtnl_qdisc *qdisc,
+		      struct rtnl_qdisc *new)
+{
+	return rtnl_qdisc_update(sk, qdisc, new, NLM_F_REPLACE);
+}
+
+/** @} */
+
+static void qdisc_dump_details(struct rtnl_tc *tc, struct nl_dump_params *p)
+{
+	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc;
+
+	nl_dump(p, "refcnt %u ", qdisc->q_info);
+}
+
+static struct rtnl_tc_type_ops qdisc_ops = {
+	.tt_type		= RTNL_TC_TYPE_QDISC,
+	.tt_dump_prefix		= "qdisc",
+	.tt_dump = {
+	    [NL_DUMP_DETAILS]	= qdisc_dump_details,
+	},
+};
+
 static struct nl_cache_ops rtnl_qdisc_ops = {
 	.co_name		= "route/qdisc",
 	.co_hdrsize		= sizeof(struct tcmsg),
@@ -450,19 +541,36 @@
 					END_OF_MSGTYPES_LIST,
 				  },
 	.co_protocol		= NETLINK_ROUTE,
+	.co_groups		= tc_groups,
 	.co_request_update	= qdisc_request_update,
 	.co_msg_parser		= qdisc_msg_parser,
 	.co_obj_ops		= &qdisc_obj_ops,
 };
 
+static struct nl_object_ops qdisc_obj_ops = {
+	.oo_name		= "route/qdisc",
+	.oo_size		= sizeof(struct rtnl_qdisc),
+	.oo_free_data		= rtnl_tc_free_data,
+	.oo_clone		= rtnl_tc_clone,
+	.oo_dump = {
+	    [NL_DUMP_LINE]	= rtnl_tc_dump_line,
+	    [NL_DUMP_DETAILS]	= rtnl_tc_dump_details,
+	    [NL_DUMP_STATS]	= rtnl_tc_dump_stats,
+	},
+	.oo_compare		= rtnl_tc_compare,
+	.oo_id_attrs		= (TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE),
+};
+
 static void __init qdisc_init(void)
 {
+	rtnl_tc_type_register(&qdisc_ops);
 	nl_cache_mngt_register(&rtnl_qdisc_ops);
 }
 
 static void __exit qdisc_exit(void)
 {
 	nl_cache_mngt_unregister(&rtnl_qdisc_ops);
+	rtnl_tc_type_unregister(&qdisc_ops);
 }
 
 /** @} */
diff --git a/lib/route/qdisc/blackhole.c b/lib/route/qdisc/blackhole.c
new file mode 100644
index 0000000..339cf78
--- /dev/null
+++ b/lib/route/qdisc/blackhole.c
@@ -0,0 +1,37 @@
+/*
+ * lib/route/qdisc/blackhole.c	Blackhole Qdisc
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_blackhole Blackhole
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink/netlink.h>
+#include <netlink-private/route/tc-api.h>
+
+static struct rtnl_tc_ops blackhole_ops = {
+	.to_kind		= "blackhole",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+};
+
+static void __init blackhole_init(void)
+{
+	rtnl_tc_register(&blackhole_ops);
+}
+
+static void __exit blackhole_exit(void)
+{
+	rtnl_tc_unregister(&blackhole_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/cbq.c b/lib/route/qdisc/cbq.c
new file mode 100644
index 0000000..95f1761
--- /dev/null
+++ b/lib/route/qdisc/cbq.c
@@ -0,0 +1,204 @@
+/*
+ * lib/route/qdisc/cbq.c	Class Based Queueing
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/class.h>
+#include <netlink/route/link.h>
+#include <netlink/route/qdisc/cbq.h>
+#include <netlink/route/cls/police.h>
+
+/**
+ * @ingroup qdisc
+ * @ingroup class
+ * @defgroup qdisc_cbq Class Based Queueing (CBQ)
+ * @{
+ */
+
+static const struct trans_tbl ovl_strategies[] = {
+	__ADD(TC_CBQ_OVL_CLASSIC,classic)
+	__ADD(TC_CBQ_OVL_DELAY,delay)
+	__ADD(TC_CBQ_OVL_LOWPRIO,lowprio)
+	__ADD(TC_CBQ_OVL_DROP,drop)
+	__ADD(TC_CBQ_OVL_RCLASSIC,rclassic)
+};
+
+/**
+ * Convert a CBQ OVL strategy to a character string
+ * @arg type		CBQ OVL strategy
+ * @arg buf		destination buffer
+ * @arg len		length of destination buffer
+ *
+ * Converts a CBQ OVL strategy to a character string and stores in the
+ * provided buffer. Returns the destination buffer or the type
+ * encoded in hex if no match was found.
+ */
+char *nl_ovl_strategy2str(int type, char *buf, size_t len)
+{
+	return __type2str(type, buf, len, ovl_strategies,
+			    ARRAY_SIZE(ovl_strategies));
+}
+
+/**
+ * Convert a string to a CBQ OVL strategy
+ * @arg name		CBQ OVL stragegy name
+ *
+ * Converts a CBQ OVL stragegy name to it's corresponding CBQ OVL strategy
+ * type. Returns the type or -1 if none was found.
+ */
+int nl_str2ovl_strategy(const char *name)
+{
+	return __str2type(name, ovl_strategies, ARRAY_SIZE(ovl_strategies));
+}
+
+static struct nla_policy cbq_policy[TCA_CBQ_MAX+1] = {
+	[TCA_CBQ_LSSOPT]	= { .minlen = sizeof(struct tc_cbq_lssopt) },
+	[TCA_CBQ_RATE]		= { .minlen = sizeof(struct tc_ratespec) },
+	[TCA_CBQ_WRROPT]	= { .minlen = sizeof(struct tc_cbq_wrropt) },
+	[TCA_CBQ_OVL_STRATEGY]	= { .minlen = sizeof(struct tc_cbq_ovl) },
+	[TCA_CBQ_FOPT]		= { .minlen = sizeof(struct tc_cbq_fopt) },
+	[TCA_CBQ_POLICE]	= { .minlen = sizeof(struct tc_cbq_police) },
+};
+
+static int cbq_msg_parser(struct rtnl_tc *tc, void *data)
+{
+	struct nlattr *tb[TCA_CBQ_MAX + 1];
+	struct rtnl_cbq *cbq = data;
+	int err;
+
+	err = tca_parse(tb, TCA_CBQ_MAX, tc, cbq_policy);
+	if (err < 0)
+		return err;
+
+	nla_memcpy(&cbq->cbq_lss, tb[TCA_CBQ_LSSOPT], sizeof(cbq->cbq_lss));
+	nla_memcpy(&cbq->cbq_rate, tb[TCA_CBQ_RATE], sizeof(cbq->cbq_rate));
+	nla_memcpy(&cbq->cbq_wrr, tb[TCA_CBQ_WRROPT], sizeof(cbq->cbq_wrr));
+	nla_memcpy(&cbq->cbq_fopt, tb[TCA_CBQ_FOPT], sizeof(cbq->cbq_fopt));
+	nla_memcpy(&cbq->cbq_ovl, tb[TCA_CBQ_OVL_STRATEGY],
+		   sizeof(cbq->cbq_ovl));
+	nla_memcpy(&cbq->cbq_police, tb[TCA_CBQ_POLICE],
+		    sizeof(cbq->cbq_police));
+	
+	return 0;
+}
+
+static void cbq_dump_line(struct rtnl_tc *tc, void *data,
+			  struct nl_dump_params *p)
+{
+	struct rtnl_cbq *cbq = data;
+	double r, rbit;
+	char *ru, *rubit;
+
+	if (!cbq)
+		return;
+
+	r = nl_cancel_down_bytes(cbq->cbq_rate.rate, &ru);
+	rbit = nl_cancel_down_bits(cbq->cbq_rate.rate * 8, &rubit);
+
+	nl_dump(p, " rate %.2f%s/s (%.0f%s) prio %u",
+		r, ru, rbit, rubit, cbq->cbq_wrr.priority);
+}
+
+static void cbq_dump_details(struct rtnl_tc *tc, void *data,
+			     struct nl_dump_params *p)
+{
+	struct rtnl_cbq *cbq = data;
+	char *unit, buf[32];
+	double w;
+	uint32_t el;
+
+	if (!cbq)
+		return;
+
+	w = nl_cancel_down_bits(cbq->cbq_wrr.weight * 8, &unit);
+
+	nl_dump(p, "avgpkt %u mpu %u cell %u allot %u weight %.0f%s\n",
+		cbq->cbq_lss.avpkt,
+		cbq->cbq_rate.mpu,
+		1 << cbq->cbq_rate.cell_log,
+		cbq->cbq_wrr.allot, w, unit);
+
+	el = cbq->cbq_lss.ewma_log;
+	nl_dump_line(p, "  minidle %uus maxidle %uus offtime "
+				"%uus level %u ewma_log %u\n",
+		nl_ticks2us(cbq->cbq_lss.minidle >> el),
+		nl_ticks2us(cbq->cbq_lss.maxidle >> el),
+		nl_ticks2us(cbq->cbq_lss.offtime >> el),
+		cbq->cbq_lss.level,
+		cbq->cbq_lss.ewma_log);
+
+	nl_dump_line(p, "  penalty %uus strategy %s ",
+		nl_ticks2us(cbq->cbq_ovl.penalty),
+		nl_ovl_strategy2str(cbq->cbq_ovl.strategy, buf, sizeof(buf)));
+
+	nl_dump(p, "split %s defmap 0x%08x ",
+		rtnl_tc_handle2str(cbq->cbq_fopt.split, buf, sizeof(buf)),
+		cbq->cbq_fopt.defmap);
+	
+	nl_dump(p, "police %s",
+		nl_police2str(cbq->cbq_police.police, buf, sizeof(buf)));
+}
+
+static void cbq_dump_stats(struct rtnl_tc *tc, void *data,
+			   struct nl_dump_params *p)
+{
+	struct tc_cbq_xstats *x;
+	
+	if (!(x = tca_xstats(tc)))
+		return;
+
+	nl_dump_line(p, "            borrows    overact  "
+			"  avgidle  undertime\n");
+	nl_dump_line(p, "         %10u %10u %10u %10u\n",
+		     x->borrows, x->overactions, x->avgidle, x->undertime);
+}
+
+static struct rtnl_tc_ops cbq_qdisc_ops = {
+	.to_kind		= "cbq",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_cbq),
+	.to_msg_parser		= cbq_msg_parser,
+	.to_dump = {
+	    [NL_DUMP_LINE]	= cbq_dump_line,
+	    [NL_DUMP_DETAILS]	= cbq_dump_details,
+	    [NL_DUMP_STATS]	= cbq_dump_stats,
+	},
+};
+
+static struct rtnl_tc_ops cbq_class_ops = {
+	.to_kind		= "cbq",
+	.to_type		= RTNL_TC_TYPE_CLASS,
+	.to_size		= sizeof(struct rtnl_cbq),
+	.to_msg_parser		= cbq_msg_parser,
+	.to_dump = {
+	    [NL_DUMP_LINE]	= cbq_dump_line,
+	    [NL_DUMP_DETAILS]	= cbq_dump_details,
+	    [NL_DUMP_STATS]	= cbq_dump_stats,
+	},
+};
+
+static void __init cbq_init(void)
+{
+	rtnl_tc_register(&cbq_qdisc_ops);
+	rtnl_tc_register(&cbq_class_ops);
+}
+
+static void __exit cbq_exit(void)
+{
+	rtnl_tc_unregister(&cbq_qdisc_ops);
+	rtnl_tc_unregister(&cbq_class_ops);
+}
+
+/** @} */
diff --git a/lib/route/sch/dsmark.c b/lib/route/qdisc/dsmark.c
similarity index 65%
rename from lib/route/sch/dsmark.c
rename to lib/route/qdisc/dsmark.c
index 61b0fea..fd9553d 100644
--- a/lib/route/sch/dsmark.c
+++ b/lib/route/qdisc/dsmark.c
@@ -1,30 +1,29 @@
 /*
- * lib/route/sch/dsmark.c	DSMARK
+ * lib/route/qdisc/dsmark.c	DSMARK
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup qdisc_api
- * @ingroup class_api
- * @defgroup dsmark Differentiated Services Marker (DSMARK)
+ * @ingroup qdisc
+ * @ingroup class
+ * @defgroup qdisc_dsmark Differentiated Services Marker (DSMARK)
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/qdisc.h>
-#include <netlink/route/qdisc-modules.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/class.h>
-#include <netlink/route/class-modules.h>
-#include <netlink/route/sch/dsmark.h>
+#include <netlink/route/qdisc/dsmark.h>
 
 /** @cond SKIP */
 #define SCH_DSMARK_ATTR_INDICES		0x1
@@ -35,20 +34,6 @@
 #define SCH_DSMARK_ATTR_VALUE		0x2
 /** @endcond */
 
-static inline struct rtnl_dsmark_qdisc *dsmark_qdisc(struct rtnl_qdisc *qdisc)
-{
-	return (struct rtnl_dsmark_qdisc *) qdisc->q_subdata;
-}
-
-static inline struct rtnl_dsmark_qdisc *
-dsmark_qdisc_alloc(struct rtnl_qdisc *qdisc)
-{
-	if (!qdisc->q_subdata)
-		qdisc->q_subdata = calloc(1, sizeof(struct rtnl_dsmark_qdisc));
-
-	return dsmark_qdisc(qdisc);
-}
-
 static struct nla_policy dsmark_policy[TCA_DSMARK_MAX+1] = {
 	[TCA_DSMARK_INDICES]		= { .type = NLA_U16 },
 	[TCA_DSMARK_DEFAULT_INDEX]	= { .type = NLA_U16 },
@@ -57,21 +42,16 @@
 	[TCA_DSMARK_MASK]		= { .type = NLA_U8 },
 };
 
-static int dsmark_qdisc_msg_parser(struct rtnl_qdisc *qdisc)
+static int dsmark_qdisc_msg_parser(struct rtnl_tc *tc, void *data)
 {
-	int err;
+	struct rtnl_dsmark_qdisc *dsmark = data;
 	struct nlattr *tb[TCA_DSMARK_MAX + 1];
-	struct rtnl_dsmark_qdisc *dsmark;
+	int err;
 
-	err = tca_parse(tb, TCA_DSMARK_MAX, (struct rtnl_tca *) qdisc,
-			dsmark_policy);
+	err = tca_parse(tb, TCA_DSMARK_MAX, tc, dsmark_policy);
 	if (err < 0)
 		return err;
 
-	dsmark = dsmark_qdisc_alloc(qdisc);
-	if (!dsmark)
-		return -NLE_NOMEM;
-
 	if (tb[TCA_DSMARK_INDICES]) {
 		dsmark->qdm_indices = nla_get_u16(tb[TCA_DSMARK_INDICES]);
 		dsmark->qdm_mask |= SCH_DSMARK_ATTR_INDICES;
@@ -91,35 +71,16 @@
 	return 0;
 }
 
-static inline struct rtnl_dsmark_class *dsmark_class(struct rtnl_class *class)
+static int dsmark_class_msg_parser(struct rtnl_tc *tc, void *data)
 {
-	return (struct rtnl_dsmark_class *) class->c_subdata;
-}
-
-static inline struct rtnl_dsmark_class *
-dsmark_class_alloc(struct rtnl_class *class)
-{
-	if (!class->c_subdata)
-		class->c_subdata = calloc(1, sizeof(struct rtnl_dsmark_class));
-
-	return dsmark_class(class);
-}
-
-static int dsmark_class_msg_parser(struct rtnl_class *class)
-{
-	int err;
+	struct rtnl_dsmark_class *dsmark = data;
 	struct nlattr *tb[TCA_DSMARK_MAX + 1];
-	struct rtnl_dsmark_class *dsmark;
+	int err;
 
-	err = tca_parse(tb, TCA_DSMARK_MAX, (struct rtnl_tca *) class,
-			dsmark_policy);
+	err = tca_parse(tb, TCA_DSMARK_MAX, tc, dsmark_policy);
 	if (err < 0)
 		return err;
 
-	dsmark = dsmark_class_alloc(class);
-	if (!dsmark)
-		return -NLE_NOMEM;
-
 	if (tb[TCA_DSMARK_MASK]) {
 		dsmark->cdm_bmask = nla_get_u8(tb[TCA_DSMARK_MASK]);
 		dsmark->cdm_mask |= SCH_DSMARK_ATTR_MASK;
@@ -133,19 +94,19 @@
 	return 0;
 }
 
-static void dsmark_qdisc_dump_line(struct rtnl_qdisc *qdisc,
+static void dsmark_qdisc_dump_line(struct rtnl_tc *tc, void *data,
 				   struct nl_dump_params *p)
 {
-	struct rtnl_dsmark_qdisc *dsmark = dsmark_qdisc(qdisc);
+	struct rtnl_dsmark_qdisc *dsmark = data;
 
 	if (dsmark && (dsmark->qdm_mask & SCH_DSMARK_ATTR_INDICES))
 		nl_dump(p, " indices 0x%04x", dsmark->qdm_indices);
 }
 
-static void dsmark_qdisc_dump_details(struct rtnl_qdisc *qdisc,
+static void dsmark_qdisc_dump_details(struct rtnl_tc *tc, void *data,
 				      struct nl_dump_params *p)
 {
-	struct rtnl_dsmark_qdisc *dsmark = dsmark_qdisc(qdisc);
+	struct rtnl_dsmark_qdisc *dsmark = data;
 
 	if (!dsmark)
 		return;
@@ -157,10 +118,10 @@
 		nl_dump(p, " set-tc-index");
 }
 
-static void dsmark_class_dump_line(struct rtnl_class *class,
+static void dsmark_class_dump_line(struct rtnl_tc *tc, void *data,
 				   struct nl_dump_params *p)
 {
-	struct rtnl_dsmark_class *dsmark = dsmark_class(class);
+	struct rtnl_dsmark_class *dsmark = data;
 
 	if (!dsmark)
 		return;
@@ -172,17 +133,13 @@
 		nl_dump(p, " mask 0x%02x", dsmark->cdm_bmask);
 }
 
-static struct nl_msg *dsmark_qdisc_get_opts(struct rtnl_qdisc *qdisc)
+static int dsmark_qdisc_msg_fill(struct rtnl_tc *tc, void *data,
+				 struct nl_msg *msg)
 {
-	struct rtnl_dsmark_qdisc *dsmark = dsmark_qdisc(qdisc);
-	struct nl_msg *msg;
+	struct rtnl_dsmark_qdisc *dsmark = data;
 
 	if (!dsmark)
-		return NULL;
-
-	msg = nlmsg_alloc();
-	if (!msg)
-		goto nla_put_failure;
+		return 0;
 
 	if (dsmark->qdm_mask & SCH_DSMARK_ATTR_INDICES)
 		NLA_PUT_U16(msg, TCA_DSMARK_INDICES, dsmark->qdm_indices);
@@ -194,24 +151,19 @@
 	if (dsmark->qdm_mask & SCH_DSMARK_ATTR_SET_TC_INDEX)
 		NLA_PUT_FLAG(msg, TCA_DSMARK_SET_TC_INDEX);
 
-	return msg;
+	return 0;
 
 nla_put_failure:
-	nlmsg_free(msg);
-	return NULL;
+	return -NLE_MSGSIZE;
 }
 
-static struct nl_msg *dsmark_class_get_opts(struct rtnl_class *class)
+static int dsmark_class_msg_fill(struct rtnl_tc *tc, void *data,
+				 struct nl_msg *msg)
 {
-	struct rtnl_dsmark_class *dsmark = dsmark_class(class);
-	struct nl_msg *msg;
+	struct rtnl_dsmark_class *dsmark = data;
 
 	if (!dsmark)
-		return NULL;
-
-	msg = nlmsg_alloc();
-	if (!msg)
-		goto nla_put_failure;
+		return 0;
 
 	if (dsmark->cdm_mask & SCH_DSMARK_ATTR_MASK)
 		NLA_PUT_U8(msg, TCA_DSMARK_MASK, dsmark->cdm_bmask);
@@ -219,11 +171,10 @@
 	if (dsmark->cdm_mask & SCH_DSMARK_ATTR_VALUE)
 		NLA_PUT_U8(msg, TCA_DSMARK_VALUE, dsmark->cdm_value);
 
-	return msg;
+	return 0;
 
 nla_put_failure:
-	nlmsg_free(msg);
-	return NULL;
+	return -NLE_MSGSIZE;
 }
 
 /**
@@ -241,8 +192,7 @@
 {
 	struct rtnl_dsmark_class *dsmark;
 	
-	dsmark = dsmark_class(class);
-	if (!dsmark)
+	if (!(dsmark = rtnl_tc_data(TC_CAST(class))))
 		return -NLE_NOMEM;
 
 	dsmark->cdm_bmask = mask;
@@ -259,9 +209,11 @@
 int rtnl_class_dsmark_get_bitmask(struct rtnl_class *class)
 {
 	struct rtnl_dsmark_class *dsmark;
+	
+	if (!(dsmark = rtnl_tc_data(TC_CAST(class))))
+		return -NLE_NOMEM;
 
-	dsmark = dsmark_class(class);
-	if (dsmark && dsmark->cdm_mask & SCH_DSMARK_ATTR_MASK)
+	if (dsmark->cdm_mask & SCH_DSMARK_ATTR_MASK)
 		return dsmark->cdm_bmask;
 	else
 		return -NLE_NOATTR;
@@ -276,9 +228,8 @@
 int rtnl_class_dsmark_set_value(struct rtnl_class *class, uint8_t value)
 {
 	struct rtnl_dsmark_class *dsmark;
-
-	dsmark = dsmark_class(class);
-	if (!dsmark)
+	
+	if (!(dsmark = rtnl_tc_data(TC_CAST(class))))
 		return -NLE_NOMEM;
 
 	dsmark->cdm_value = value;
@@ -295,9 +246,11 @@
 int rtnl_class_dsmark_get_value(struct rtnl_class *class)
 {
 	struct rtnl_dsmark_class *dsmark;
+	
+	if (!(dsmark = rtnl_tc_data(TC_CAST(class))))
+		return -NLE_NOMEM;
 
-	dsmark = dsmark_class(class);
-	if (dsmark && dsmark->cdm_mask & SCH_DSMARK_ATTR_VALUE)
+	if (dsmark->cdm_mask & SCH_DSMARK_ATTR_VALUE)
 		return dsmark->cdm_value;
 	else
 		return -NLE_NOATTR;
@@ -319,8 +272,7 @@
 {
 	struct rtnl_dsmark_qdisc *dsmark;
 
-	dsmark = dsmark_qdisc(qdisc);
-	if (!dsmark)
+	if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
 		return -NLE_NOMEM;
 
 	dsmark->qdm_indices = indices;
@@ -338,8 +290,10 @@
 {
 	struct rtnl_dsmark_qdisc *dsmark;
 
-	dsmark = dsmark_qdisc(qdisc);
-	if (dsmark && dsmark->qdm_mask & SCH_DSMARK_ATTR_INDICES)
+	if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	if (dsmark->qdm_mask & SCH_DSMARK_ATTR_INDICES)
 		return dsmark->qdm_indices;
 	else
 		return -NLE_NOATTR;
@@ -356,8 +310,7 @@
 {
 	struct rtnl_dsmark_qdisc *dsmark;
 
-	dsmark = dsmark_qdisc(qdisc);
-	if (!dsmark)
+	if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
 		return -NLE_NOMEM;
 
 	dsmark->qdm_default_index = default_index;
@@ -375,8 +328,10 @@
 {
 	struct rtnl_dsmark_qdisc *dsmark;
 
-	dsmark = dsmark_qdisc(qdisc);
-	if (dsmark && dsmark->qdm_mask & SCH_DSMARK_ATTR_DEFAULT_INDEX)
+	if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	if (dsmark->qdm_mask & SCH_DSMARK_ATTR_DEFAULT_INDEX)
 		return dsmark->qdm_default_index;
 	else
 		return -NLE_NOATTR;
@@ -392,8 +347,7 @@
 {
 	struct rtnl_dsmark_qdisc *dsmark;
 
-	dsmark = dsmark_qdisc(qdisc);
-	if (!dsmark)
+	if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
 		return -NLE_NOMEM;
 
 	dsmark->qdm_set_tc_index = !!flag;
@@ -412,8 +366,10 @@
 {
 	struct rtnl_dsmark_qdisc *dsmark;
 
-	dsmark = dsmark_qdisc(qdisc);
-	if (dsmark && dsmark->qdm_mask & SCH_DSMARK_ATTR_SET_TC_INDEX)
+	if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	if (dsmark->qdm_mask & SCH_DSMARK_ATTR_SET_TC_INDEX)
 		return dsmark->qdm_set_tc_index;
 	else
 		return -NLE_NOATTR;
@@ -421,33 +377,37 @@
 
 /** @} */
 
-static struct rtnl_qdisc_ops dsmark_qdisc_ops = {
-	.qo_kind		= "dsmark",
-	.qo_msg_parser		= dsmark_qdisc_msg_parser,
-	.qo_dump = {
+static struct rtnl_tc_ops dsmark_qdisc_ops = {
+	.to_kind		= "dsmark",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_dsmark_qdisc),
+	.to_msg_parser		= dsmark_qdisc_msg_parser,
+	.to_dump = {
 	    [NL_DUMP_LINE]	= dsmark_qdisc_dump_line,
 	    [NL_DUMP_DETAILS]	= dsmark_qdisc_dump_details,
 	},
-	.qo_get_opts		= dsmark_qdisc_get_opts,
+	.to_msg_fill		= dsmark_qdisc_msg_fill,
 };
 
-static struct rtnl_class_ops dsmark_class_ops = {
-	.co_kind		= "dsmark",
-	.co_msg_parser		= dsmark_class_msg_parser,
-	.co_dump[NL_DUMP_LINE]	= dsmark_class_dump_line,
-	.co_get_opts		= dsmark_class_get_opts,
+static struct rtnl_tc_ops dsmark_class_ops = {
+	.to_kind		= "dsmark",
+	.to_type		= RTNL_TC_TYPE_CLASS,
+	.to_size		= sizeof(struct rtnl_dsmark_class),
+	.to_msg_parser		= dsmark_class_msg_parser,
+	.to_dump[NL_DUMP_LINE]	= dsmark_class_dump_line,
+	.to_msg_fill		= dsmark_class_msg_fill,
 };
 
 static void __init dsmark_init(void)
 {
-	rtnl_qdisc_register(&dsmark_qdisc_ops);
-	rtnl_class_register(&dsmark_class_ops);
+	rtnl_tc_register(&dsmark_qdisc_ops);
+	rtnl_tc_register(&dsmark_class_ops);
 }
 
 static void __exit dsmark_exit(void)
 {
-	rtnl_qdisc_unregister(&dsmark_qdisc_ops);
-	rtnl_class_unregister(&dsmark_class_ops);
+	rtnl_tc_unregister(&dsmark_qdisc_ops);
+	rtnl_tc_unregister(&dsmark_class_ops);
 }
 
 /** @} */
diff --git a/lib/route/qdisc/fifo.c b/lib/route/qdisc/fifo.c
new file mode 100644
index 0000000..d94c007
--- /dev/null
+++ b/lib/route/qdisc/fifo.c
@@ -0,0 +1,169 @@
+/*
+ * lib/route/qdisc/fifo.c		(p|b)fifo
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_fifo Packet/Bytes FIFO (pfifo/bfifo)
+ * @brief
+ *
+ * The FIFO qdisc comes in two flavours:
+ * @par bfifo (Byte FIFO)
+ * Allows enqueuing until the currently queued volume in bytes exceeds
+ * the configured limit.backlog contains currently enqueued volume in bytes.
+ *
+ * @par pfifo (Packet FIFO)
+ * Allows enquueing until the currently queued number of packets
+ * exceeds the configured limit.
+ *
+ * The configuration is exactly the same, the decision which of
+ * the two variations is going to be used is made based on the
+ * kind of the qdisc (rtnl_tc_set_kind()).
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/fifo.h>
+#include <netlink/utils.h>
+
+/** @cond SKIP */
+#define SCH_FIFO_ATTR_LIMIT 1
+/** @endcond */
+
+static int fifo_msg_parser(struct rtnl_tc *tc, void *data)
+{
+	struct rtnl_fifo *fifo = data;
+	struct tc_fifo_qopt *opt;
+
+	if (tc->tc_opts->d_size < sizeof(struct tc_fifo_qopt))
+		return -NLE_INVAL;
+
+	opt = (struct tc_fifo_qopt *) tc->tc_opts->d_data;
+	fifo->qf_limit = opt->limit;
+	fifo->qf_mask = SCH_FIFO_ATTR_LIMIT;
+
+	return 0;
+}
+
+static void pfifo_dump_line(struct rtnl_tc *tc, void *data,
+			    struct nl_dump_params *p)
+{
+	struct rtnl_fifo *fifo = data;
+
+	if (fifo)
+		nl_dump(p, " limit %u packets", fifo->qf_limit);
+}
+
+static void bfifo_dump_line(struct rtnl_tc *tc, void *data,
+			    struct nl_dump_params *p)
+{
+	struct rtnl_fifo *fifo = data;
+	char *unit;
+	double r;
+
+	if (!fifo)
+		return;
+
+	r = nl_cancel_down_bytes(fifo->qf_limit, &unit);
+	nl_dump(p, " limit %.1f%s", r, unit);
+}
+
+static int fifo_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+	struct rtnl_fifo *fifo = data;
+	struct tc_fifo_qopt opts = {0};
+
+	if (!fifo || !(fifo->qf_mask & SCH_FIFO_ATTR_LIMIT))
+		return -NLE_INVAL;
+
+	opts.limit = fifo->qf_limit;
+
+	return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD);
+}
+
+/**
+ * @name Attribute Modification
+ * @{
+ */
+
+/**
+ * Set limit of FIFO qdisc.
+ * @arg qdisc		FIFO qdisc to be modified.
+ * @arg limit		New limit.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_fifo_set_limit(struct rtnl_qdisc *qdisc, int limit)
+{
+	struct rtnl_fifo *fifo;
+	
+	if (!(fifo = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+		
+	fifo->qf_limit = limit;
+	fifo->qf_mask |= SCH_FIFO_ATTR_LIMIT;
+
+	return 0;
+}
+
+/**
+ * Get limit of a FIFO qdisc.
+ * @arg qdisc		FIFO qdisc.
+ * @return Numeric limit or a negative error code.
+ */
+int rtnl_qdisc_fifo_get_limit(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_fifo *fifo;
+	
+	if (!(fifo = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+	
+	if (fifo->qf_mask & SCH_FIFO_ATTR_LIMIT)
+		return fifo->qf_limit;
+	else
+		return -NLE_NOATTR;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops pfifo_ops = {
+	.to_kind		= "pfifo",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_fifo),
+	.to_msg_parser		= fifo_msg_parser,
+	.to_dump[NL_DUMP_LINE]	= pfifo_dump_line,
+	.to_msg_fill		= fifo_msg_fill,
+};
+
+static struct rtnl_tc_ops bfifo_ops = {
+	.to_kind		= "bfifo",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_fifo),
+	.to_msg_parser		= fifo_msg_parser,
+	.to_dump[NL_DUMP_LINE]	= bfifo_dump_line,
+	.to_msg_fill		= fifo_msg_fill,
+};
+
+static void __init fifo_init(void)
+{
+	rtnl_tc_register(&pfifo_ops);
+	rtnl_tc_register(&bfifo_ops);
+}
+
+static void __exit fifo_exit(void)
+{
+	rtnl_tc_unregister(&pfifo_ops);
+	rtnl_tc_unregister(&bfifo_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/fq_codel.c b/lib/route/qdisc/fq_codel.c
new file mode 100644
index 0000000..ade20e5
--- /dev/null
+++ b/lib/route/qdisc/fq_codel.c
@@ -0,0 +1,377 @@
+/*
+ * lib/route/qdisc/fq_codel.c		fq_codel
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_fq_codel Fair Queue CoDel
+ * @brief
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/fq_codel.h>
+#include <netlink/utils.h>
+
+/** @cond SKIP */
+#define SCH_FQ_CODEL_ATTR_TARGET	0x1
+#define SCH_FQ_CODEL_ATTR_LIMIT		0x2
+#define SCH_FQ_CODEL_ATTR_INTERVAL	0x4
+#define SCH_FQ_CODEL_ATTR_FLOWS		0x8
+#define SCH_FQ_CODEL_ATTR_QUANTUM	0x10
+#define SCH_FQ_CODEL_ATTR_ECN		0x20
+/** @endcond */
+
+static struct nla_policy fq_codel_policy[TCA_FQ_CODEL_MAX + 1] = {
+	[TCA_FQ_CODEL_TARGET]   = { .type = NLA_U32 },
+	[TCA_FQ_CODEL_LIMIT]    = { .type = NLA_U32 },
+	[TCA_FQ_CODEL_INTERVAL] = { .type = NLA_U32 },
+	[TCA_FQ_CODEL_ECN]      = { .type = NLA_U32 },
+	[TCA_FQ_CODEL_FLOWS]    = { .type = NLA_U32 },
+	[TCA_FQ_CODEL_QUANTUM]  = { .type = NLA_U32 },
+};
+
+static int fq_codel_msg_parser(struct rtnl_tc *tc, void *data)
+{
+	struct rtnl_fq_codel *fq_codel = data;
+	struct nlattr *tb[TCA_FQ_CODEL_MAX + 1];
+	int err;
+
+	err = tca_parse(tb, TCA_FQ_CODEL_MAX, tc, fq_codel_policy);
+	if (err < 0)
+		return err;
+
+	if (tb[TCA_FQ_CODEL_TARGET]) {
+		fq_codel->fq_target =  nla_get_u32(tb[TCA_FQ_CODEL_TARGET]);
+		fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_TARGET;
+	}
+
+	if (tb[TCA_FQ_CODEL_INTERVAL]) {
+		fq_codel->fq_interval =  nla_get_u32(tb[TCA_FQ_CODEL_INTERVAL]);
+		fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_INTERVAL;
+	}
+
+	if (tb[TCA_FQ_CODEL_LIMIT]) {
+		fq_codel->fq_limit =  nla_get_u32(tb[TCA_FQ_CODEL_LIMIT]);
+		fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_LIMIT;
+	}
+
+	if (tb[TCA_FQ_CODEL_QUANTUM]) {
+		fq_codel->fq_quantum =  nla_get_u32(tb[TCA_FQ_CODEL_QUANTUM]);
+		fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_QUANTUM;
+	}
+
+	if (tb[TCA_FQ_CODEL_FLOWS]) {
+		fq_codel->fq_flows =  nla_get_u32(tb[TCA_FQ_CODEL_FLOWS]);
+		fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_FLOWS;
+	}
+
+	if (tb[TCA_FQ_CODEL_ECN]) {
+		fq_codel->fq_ecn =  nla_get_u32(tb[TCA_FQ_CODEL_ECN]);
+		fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_ECN;
+	}
+
+	return 0;
+}
+
+static void fq_codel_dump_line(struct rtnl_tc *tc, void *data,
+			    struct nl_dump_params *p)
+{
+	struct rtnl_fq_codel *fq_codel = data;
+
+	if (!fq_codel)
+		return;
+
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_LIMIT)
+		nl_dump(p, " limit %u packets", fq_codel->fq_limit);
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_TARGET)
+		nl_dump(p, " target %u", fq_codel->fq_target);
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_INTERVAL)
+		nl_dump(p, " interval %u", fq_codel->fq_interval);
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_ECN)
+		nl_dump(p, " ecn %u", fq_codel->fq_ecn);
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_FLOWS)
+		nl_dump(p, " flows %u", fq_codel->fq_flows);
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_QUANTUM)
+		nl_dump(p, " quantum %u", fq_codel->fq_quantum);
+}
+
+static int fq_codel_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+	struct rtnl_fq_codel *fq_codel = data;
+
+	if (!fq_codel)
+		return -NLE_INVAL;
+
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_LIMIT)
+		NLA_PUT_U32(msg, TCA_FQ_CODEL_LIMIT, fq_codel->fq_limit);
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_INTERVAL)
+		NLA_PUT_U32(msg, TCA_FQ_CODEL_INTERVAL, fq_codel->fq_interval);
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_TARGET)
+		NLA_PUT_U32(msg, TCA_FQ_CODEL_TARGET, fq_codel->fq_target);
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_QUANTUM)
+		NLA_PUT_U32(msg, TCA_FQ_CODEL_QUANTUM, fq_codel->fq_quantum);
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_FLOWS)
+		NLA_PUT_U32(msg, TCA_FQ_CODEL_FLOWS, fq_codel->fq_flows);
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_ECN)
+		NLA_PUT_U32(msg, TCA_FQ_CODEL_ECN, fq_codel->fq_ecn);
+	return 0;
+
+nla_put_failure:
+	return -NLE_MSGSIZE;
+
+}
+
+/**
+ * @name Attribute Modification
+ * @{
+ */
+
+/**
+ * Set limit of fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc to be modified.
+ * @arg limit		New limit.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_fq_codel_set_limit(struct rtnl_qdisc *qdisc, int limit)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	fq_codel->fq_limit = limit;
+	fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_LIMIT;
+
+	return 0;
+}
+
+/**
+ * Get limit of a fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc.
+ * @return Numeric limit or a negative error code.
+ */
+int rtnl_qdisc_fq_codel_get_limit(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_LIMIT)
+		return fq_codel->fq_limit;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set target of fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc to be modified.
+ * @arg target		New target.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_fq_codel_set_target(struct rtnl_qdisc *qdisc, uint32_t target)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	fq_codel->fq_target = target;
+	fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_TARGET;
+
+	return 0;
+}
+
+/**
+ * Get target of a fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc.
+ * @return Numeric target or zero.
+ */
+uint32_t rtnl_qdisc_fq_codel_get_target(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if ((fq_codel = rtnl_tc_data(TC_CAST(qdisc))) &&
+	    fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_TARGET)
+		return fq_codel->fq_target;
+	else
+		return 0;
+}
+
+/**
+ * Set interval of fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc to be modified.
+ * @arg interval	New interval.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_fq_codel_set_interval(struct rtnl_qdisc *qdisc, uint32_t interval)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	fq_codel->fq_interval = interval;
+	fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_INTERVAL;
+
+	return 0;
+}
+
+/**
+ * Get target of a fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc.
+ * @return Numeric interval or zero.
+ */
+uint32_t rtnl_qdisc_fq_codel_get_interval(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if ((fq_codel = rtnl_tc_data(TC_CAST(qdisc))) &&
+	     fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_INTERVAL)
+		return fq_codel->fq_interval;
+	else
+		return 0;
+}
+
+/**
+ * Set quantum of fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc to be modified.
+ * @arg quantum		New quantum.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_fq_codel_set_quantum(struct rtnl_qdisc *qdisc, uint32_t quantum)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	fq_codel->fq_quantum = quantum;
+	fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_QUANTUM;
+
+	return 0;
+}
+
+/**
+ * Get quantum of a fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc.
+ * @return Numeric quantum or zero.
+ */
+uint32_t rtnl_qdisc_fq_codel_get_quantum(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if ((fq_codel = rtnl_tc_data(TC_CAST(qdisc))) &&
+	    (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_QUANTUM))
+		return fq_codel->fq_quantum;
+	else
+		return 0;
+}
+
+/**
+ * Set flows of fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc to be modified.
+ * @arg flows		New flows value.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_fq_codel_set_flows(struct rtnl_qdisc *qdisc, int flows)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	fq_codel->fq_flows = flows;
+	fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_FLOWS;
+
+	return 0;
+}
+
+/**
+ * Get flows of a fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc.
+ * @return Numeric flows or a negative error code.
+ */
+int rtnl_qdisc_fq_codel_get_flows(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_FLOWS)
+		return fq_codel->fq_flows;
+	else
+		return -NLE_NOATTR;
+}
+/**
+ * Set ecn of fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc to be modified.
+ * @arg ecn		New ecn value.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_fq_codel_set_ecn(struct rtnl_qdisc *qdisc, int ecn)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	fq_codel->fq_ecn = ecn;
+	fq_codel->fq_mask |= SCH_FQ_CODEL_ATTR_ECN;
+
+	return 0;
+}
+
+/**
+ * Get ecn of a fq_codel qdisc.
+ * @arg qdisc		fq_codel qdisc.
+ * @return Numeric ecn or a negative error code.
+ */
+int rtnl_qdisc_fq_codel_get_ecn(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_fq_codel *fq_codel;
+
+	if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	if (fq_codel->fq_mask & SCH_FQ_CODEL_ATTR_ECN)
+		return fq_codel->fq_ecn;
+	else
+		return -NLE_NOATTR;
+}
+/** @} */
+
+static struct rtnl_tc_ops fq_codel_ops = {
+	.to_kind		= "fq_codel",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_fq_codel),
+	.to_msg_parser		= fq_codel_msg_parser,
+	.to_dump[NL_DUMP_LINE]	= fq_codel_dump_line,
+	.to_msg_fill		= fq_codel_msg_fill,
+};
+
+static void __init fq_codel_init(void)
+{
+	rtnl_tc_register(&fq_codel_ops);
+}
+
+static void __exit fq_codel_exit(void)
+{
+	rtnl_tc_unregister(&fq_codel_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/htb.c b/lib/route/qdisc/htb.c
new file mode 100644
index 0000000..5a61a4e
--- /dev/null
+++ b/lib/route/qdisc/htb.c
@@ -0,0 +1,643 @@
+/*
+ * lib/route/qdisc/htb.c	HTB Qdisc
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2005-2006 Petr Gotthard <petr.gotthard@siemens.com>
+ * Copyright (c) 2005-2006 Siemens AG Oesterreich
+ */
+
+/**
+ * @ingroup qdisc
+ * @ingroup class
+ * @defgroup qdisc_htb Hierachical Token Bucket (HTB)
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+#include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/class.h>
+#include <netlink/route/link.h>
+#include <netlink/route/qdisc/htb.h>
+
+/** @cond SKIP */
+#define SCH_HTB_HAS_RATE2QUANTUM	0x01
+#define SCH_HTB_HAS_DEFCLS		0x02
+
+#define SCH_HTB_HAS_PRIO		0x001
+#define SCH_HTB_HAS_RATE		0x002
+#define SCH_HTB_HAS_CEIL		0x004
+#define SCH_HTB_HAS_RBUFFER		0x008
+#define SCH_HTB_HAS_CBUFFER		0x010
+#define SCH_HTB_HAS_QUANTUM		0x020
+#define SCH_HTB_HAS_LEVEL		0x040
+/** @endcond */
+
+static struct nla_policy htb_policy[TCA_HTB_MAX+1] = {
+	[TCA_HTB_INIT]	= { .minlen = sizeof(struct tc_htb_glob) },
+	[TCA_HTB_PARMS] = { .minlen = sizeof(struct tc_htb_opt) },
+};
+
+static int htb_qdisc_msg_parser(struct rtnl_tc *tc, void *data)
+{
+	struct nlattr *tb[TCA_HTB_MAX + 1];
+	struct rtnl_htb_qdisc *htb = data;
+	int err;
+
+	if ((err = tca_parse(tb, TCA_HTB_MAX, tc, htb_policy)) < 0)
+		return err;
+	
+	if (tb[TCA_HTB_INIT]) {
+		struct tc_htb_glob opts;
+
+		nla_memcpy(&opts, tb[TCA_HTB_INIT], sizeof(opts));
+		htb->qh_rate2quantum = opts.rate2quantum;
+		htb->qh_defcls = opts.defcls;
+		htb->qh_direct_pkts = opts.direct_pkts;
+
+		htb->qh_mask = (SCH_HTB_HAS_RATE2QUANTUM | SCH_HTB_HAS_DEFCLS);
+	}
+
+	return 0;
+}
+
+static int htb_class_msg_parser(struct rtnl_tc *tc, void *data)
+{
+	struct nlattr *tb[TCA_HTB_MAX + 1];
+	struct rtnl_htb_class *htb = data;
+	int err;
+
+	if ((err = tca_parse(tb, TCA_HTB_MAX, tc, htb_policy)) < 0)
+		return err;
+	
+	if (tb[TCA_HTB_PARMS]) {
+		struct tc_htb_opt opts;
+
+		nla_memcpy(&opts, tb[TCA_HTB_PARMS], sizeof(opts));
+		htb->ch_prio = opts.prio;
+		rtnl_copy_ratespec(&htb->ch_rate, &opts.rate);
+		rtnl_copy_ratespec(&htb->ch_ceil, &opts.ceil);
+		htb->ch_rbuffer = rtnl_tc_calc_bufsize(nl_ticks2us(opts.buffer),
+						       opts.rate.rate);
+		htb->ch_cbuffer = rtnl_tc_calc_bufsize(nl_ticks2us(opts.cbuffer),
+						       opts.ceil.rate);
+		htb->ch_quantum = opts.quantum;
+		htb->ch_level = opts.level;
+
+		rtnl_tc_set_mpu(tc, htb->ch_rate.rs_mpu);
+		rtnl_tc_set_overhead(tc, htb->ch_rate.rs_overhead);
+
+		htb->ch_mask = (SCH_HTB_HAS_PRIO | SCH_HTB_HAS_RATE |
+				SCH_HTB_HAS_CEIL | SCH_HTB_HAS_RBUFFER |
+				SCH_HTB_HAS_CBUFFER | SCH_HTB_HAS_QUANTUM |
+				SCH_HTB_HAS_LEVEL);
+	}
+
+	return 0;
+}
+
+static void htb_qdisc_dump_line(struct rtnl_tc *tc, void *data,
+				struct nl_dump_params *p)
+{
+	struct rtnl_htb_qdisc *htb = data;
+
+	if (!htb)
+		return;
+
+	if (htb->qh_mask & SCH_HTB_HAS_RATE2QUANTUM)
+		nl_dump(p, " r2q %u", htb->qh_rate2quantum);
+
+	if (htb->qh_mask & SCH_HTB_HAS_DEFCLS) {
+		char buf[64];
+		nl_dump(p, " default-class %s",
+			rtnl_tc_handle2str(htb->qh_defcls, buf, sizeof(buf)));
+	}
+}
+
+static void htb_class_dump_line(struct rtnl_tc *tc, void *data,
+				struct nl_dump_params *p)
+{
+	struct rtnl_htb_class *htb = data;
+
+	if (!htb)
+		return;
+
+	if (htb->ch_mask & SCH_HTB_HAS_RATE) {
+		double r, rbit;
+		char *ru, *rubit;
+
+		r = nl_cancel_down_bytes(htb->ch_rate.rs_rate, &ru);
+		rbit = nl_cancel_down_bits(htb->ch_rate.rs_rate*8, &rubit);
+
+		nl_dump(p, " rate %.2f%s/s (%.0f%s) log %u",
+			r, ru, rbit, rubit, 1<<htb->ch_rate.rs_cell_log);
+	}
+}
+
+static void htb_class_dump_details(struct rtnl_tc *tc, void *data,
+				   struct nl_dump_params *p)
+{
+	struct rtnl_htb_class *htb = data;
+
+	if (!htb)
+		return;
+
+	/* line 1 */
+	if (htb->ch_mask & SCH_HTB_HAS_CEIL) {
+		double r, rbit;
+		char *ru, *rubit;
+
+		r = nl_cancel_down_bytes(htb->ch_ceil.rs_rate, &ru);
+		rbit = nl_cancel_down_bits(htb->ch_ceil.rs_rate*8, &rubit);
+
+		nl_dump(p, " ceil %.2f%s/s (%.0f%s) log %u",
+			r, ru, rbit, rubit, 1<<htb->ch_ceil.rs_cell_log);
+	}
+
+	if (htb->ch_mask & SCH_HTB_HAS_PRIO)
+		nl_dump(p, " prio %u", htb->ch_prio);
+
+	if (htb->ch_mask & SCH_HTB_HAS_RBUFFER) {
+		double b;
+		char *bu;
+
+		b = nl_cancel_down_bytes(htb->ch_rbuffer, &bu);
+		nl_dump(p, " rbuffer %.2f%s", b, bu);
+	}
+
+	if (htb->ch_mask & SCH_HTB_HAS_CBUFFER) {
+		double b;
+		char *bu;
+
+		b = nl_cancel_down_bytes(htb->ch_cbuffer, &bu);
+		nl_dump(p, " cbuffer %.2f%s", b, bu);
+	}
+
+	if (htb->ch_mask & SCH_HTB_HAS_QUANTUM)
+		nl_dump(p, " quantum %u", htb->ch_quantum);
+}
+
+static int htb_qdisc_msg_fill(struct rtnl_tc *tc, void *data,
+			      struct nl_msg *msg)
+{
+	struct rtnl_htb_qdisc *htb = data;
+	struct tc_htb_glob opts = {
+        	.version = TC_HTB_PROTOVER,
+	        .rate2quantum = 10,
+        };
+
+	if (htb) {
+		if (htb->qh_mask & SCH_HTB_HAS_RATE2QUANTUM)
+			opts.rate2quantum = htb->qh_rate2quantum;
+
+		if (htb->qh_mask & SCH_HTB_HAS_DEFCLS)
+			opts.defcls = htb->qh_defcls;
+	}
+
+	return nla_put(msg, TCA_HTB_INIT, sizeof(opts), &opts);
+}
+
+static int htb_class_msg_fill(struct rtnl_tc *tc, void *data,
+			      struct nl_msg *msg)
+{
+	struct rtnl_htb_class *htb = data;
+	uint32_t mtu, rtable[RTNL_TC_RTABLE_SIZE], ctable[RTNL_TC_RTABLE_SIZE];
+	struct tc_htb_opt opts;
+	int buffer, cbuffer;
+
+	if (!htb || !(htb->ch_mask & SCH_HTB_HAS_RATE))
+		BUG();
+
+	memset(&opts, 0, sizeof(opts));
+
+	/* if not set, zero (0) is used as priority */
+	if (htb->ch_mask & SCH_HTB_HAS_PRIO)
+		opts.prio = htb->ch_prio;
+
+	mtu = rtnl_tc_get_mtu(tc);
+
+	rtnl_tc_build_rate_table(tc, &htb->ch_rate, rtable);
+	rtnl_rcopy_ratespec(&opts.rate, &htb->ch_rate);
+
+	if (htb->ch_mask & SCH_HTB_HAS_CEIL) {
+		rtnl_tc_build_rate_table(tc, &htb->ch_ceil, ctable);
+		rtnl_rcopy_ratespec(&opts.ceil, &htb->ch_ceil);
+	} else {
+		/*
+		 * If not set, configured rate is used as ceil, which implies
+		 * no borrowing.
+		 */
+		memcpy(&opts.ceil, &opts.rate, sizeof(struct tc_ratespec));
+	}
+
+	if (htb->ch_mask & SCH_HTB_HAS_RBUFFER)
+		buffer = htb->ch_rbuffer;
+	else
+		buffer = opts.rate.rate / nl_get_psched_hz() + mtu; /* XXX */
+
+	opts.buffer = nl_us2ticks(rtnl_tc_calc_txtime(buffer, opts.rate.rate));
+
+	if (htb->ch_mask & SCH_HTB_HAS_CBUFFER)
+		cbuffer = htb->ch_cbuffer;
+	else
+		cbuffer = opts.ceil.rate / nl_get_psched_hz() + mtu; /* XXX */
+
+	opts.cbuffer = nl_us2ticks(rtnl_tc_calc_txtime(cbuffer, opts.ceil.rate));
+
+	if (htb->ch_mask & SCH_HTB_HAS_QUANTUM)
+		opts.quantum = htb->ch_quantum;
+
+	NLA_PUT(msg, TCA_HTB_PARMS, sizeof(opts), &opts);
+	NLA_PUT(msg, TCA_HTB_RTAB, sizeof(rtable), &rtable);
+	NLA_PUT(msg, TCA_HTB_CTAB, sizeof(ctable), &ctable);
+
+	return 0;
+
+nla_put_failure:
+	return -NLE_MSGSIZE;
+}
+
+static struct rtnl_tc_ops htb_qdisc_ops;
+static struct rtnl_tc_ops htb_class_ops;
+
+static struct rtnl_htb_qdisc *htb_qdisc_data(struct rtnl_qdisc *qdisc)
+{
+	return rtnl_tc_data_check(TC_CAST(qdisc), &htb_qdisc_ops);
+}
+
+static struct rtnl_htb_class *htb_class_data(struct rtnl_class *class)
+{
+	return rtnl_tc_data_check(TC_CAST(class), &htb_class_ops);
+}
+
+/**
+ * @name Attribute Modifications
+ * @{
+ */
+
+/**
+ * Return rate/quantum ratio of HTB qdisc
+ * @arg qdisc		htb qdisc object
+ *
+ * @return rate/quantum ratio or 0 if unspecified
+ */
+uint32_t rtnl_htb_get_rate2quantum(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_htb_qdisc *htb;
+
+	if ((htb = htb_qdisc_data(qdisc)) &&
+	    htb->qh_mask & SCH_HTB_HAS_RATE2QUANTUM)
+		return htb->qh_rate2quantum;
+
+	return 0;
+}
+
+int rtnl_htb_set_rate2quantum(struct rtnl_qdisc *qdisc, uint32_t rate2quantum)
+{
+	struct rtnl_htb_qdisc *htb;
+
+	if (!(htb = htb_qdisc_data(qdisc)))
+		return -NLE_OPNOTSUPP;
+
+	htb->qh_rate2quantum = rate2quantum;
+	htb->qh_mask |= SCH_HTB_HAS_RATE2QUANTUM;
+
+	return 0;
+}
+
+/**
+ * Return default class of HTB qdisc
+ * @arg qdisc		htb qdisc object
+ *
+ * Returns the classid of the class where all unclassified traffic
+ * goes to.
+ *
+ * @return classid or TC_H_UNSPEC if unspecified.
+ */
+uint32_t rtnl_htb_get_defcls(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_htb_qdisc *htb;
+
+	if ((htb = htb_qdisc_data(qdisc)) &&
+	    htb->qh_mask & SCH_HTB_HAS_DEFCLS)
+		return htb->qh_defcls;
+
+	return TC_H_UNSPEC;
+}
+
+/**
+ * Set default class of the htb qdisc to the specified value
+ * @arg qdisc		qdisc to change
+ * @arg defcls		new default class
+ */
+int rtnl_htb_set_defcls(struct rtnl_qdisc *qdisc, uint32_t defcls)
+{
+	struct rtnl_htb_qdisc *htb;
+
+	if (!(htb = htb_qdisc_data(qdisc)))
+		return -NLE_OPNOTSUPP;
+
+	htb->qh_defcls = defcls;
+	htb->qh_mask |= SCH_HTB_HAS_DEFCLS;
+
+	return 0;
+}
+
+uint32_t rtnl_htb_get_prio(struct rtnl_class *class)
+{
+	struct rtnl_htb_class *htb;
+
+	if ((htb = htb_class_data(class)) && htb->ch_mask & SCH_HTB_HAS_PRIO)
+		return htb->ch_prio;
+
+	return 0;
+}
+
+int rtnl_htb_set_prio(struct rtnl_class *class, uint32_t prio)
+{
+	struct rtnl_htb_class *htb;
+
+	if (!(htb = htb_class_data(class)))
+		return -NLE_OPNOTSUPP;
+
+	htb->ch_prio = prio;
+	htb->ch_mask |= SCH_HTB_HAS_PRIO;
+
+	return 0;
+}
+
+/**
+ * Return rate of HTB class
+ * @arg class		htb class object
+ *
+ * @return Rate in bytes/s or 0 if unspecified.
+ */
+uint32_t rtnl_htb_get_rate(struct rtnl_class *class)
+{
+	struct rtnl_htb_class *htb;
+
+	if ((htb = htb_class_data(class)) && htb->ch_mask & SCH_HTB_HAS_RATE)
+		return htb->ch_rate.rs_rate;
+
+	return 0;
+}
+
+/**
+ * Set rate of HTB class
+ * @arg class		htb class object
+ * @arg rate		new rate in bytes per second
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_htb_set_rate(struct rtnl_class *class, uint32_t rate)
+{
+	struct rtnl_htb_class *htb;
+
+	if (!(htb = htb_class_data(class)))
+		return -NLE_OPNOTSUPP;
+
+	htb->ch_rate.rs_cell_log = UINT8_MAX; /* use default value */
+	htb->ch_rate.rs_rate = rate;
+	htb->ch_mask |= SCH_HTB_HAS_RATE;
+
+	return 0;
+}
+
+/**
+ * Return ceil rate of HTB class
+ * @arg class		htb class object
+ *
+ * @return Ceil rate in bytes/s or 0 if unspecified
+ */
+uint32_t rtnl_htb_get_ceil(struct rtnl_class *class)
+{
+	struct rtnl_htb_class *htb;
+
+	if ((htb = htb_class_data(class)) && htb->ch_mask & SCH_HTB_HAS_CEIL)
+		return htb->ch_ceil.rs_rate;
+
+	return 0;
+}
+
+/**
+ * Set ceil rate of HTB class
+ * @arg class		htb class object
+ * @arg ceil		new ceil rate number of bytes per second
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_htb_set_ceil(struct rtnl_class *class, uint32_t ceil)
+{
+	struct rtnl_htb_class *htb;
+
+	if (!(htb = htb_class_data(class)))
+		return -NLE_OPNOTSUPP;
+
+	htb->ch_ceil.rs_cell_log = UINT8_MAX; /* use default value */
+	htb->ch_ceil.rs_rate = ceil;
+	htb->ch_mask |= SCH_HTB_HAS_CEIL;
+
+	return 0;
+}
+
+/**
+ * Return burst buffer size of HTB class
+ * @arg class		htb class object
+ *
+ * @return Burst buffer size or 0 if unspecified
+ */
+uint32_t rtnl_htb_get_rbuffer(struct rtnl_class *class)
+{
+	struct rtnl_htb_class *htb;
+
+	if ((htb = htb_class_data(class)) &&
+	     htb->ch_mask & SCH_HTB_HAS_RBUFFER)
+		return htb->ch_rbuffer;
+
+	return 0;
+}
+
+/**
+ * Set size of the rate bucket of HTB class.
+ * @arg class		HTB class to be modified.
+ * @arg rbuffer		New size in bytes.
+ */
+int rtnl_htb_set_rbuffer(struct rtnl_class *class, uint32_t rbuffer)
+{
+	struct rtnl_htb_class *htb;
+
+	if (!(htb = htb_class_data(class)))
+		return -NLE_OPNOTSUPP;
+
+	htb->ch_rbuffer = rbuffer;
+	htb->ch_mask |= SCH_HTB_HAS_RBUFFER;
+
+	return 0;
+}
+
+/**
+ * Return ceil burst buffer size of HTB class
+ * @arg class		htb class object
+ *
+ * @return Ceil burst buffer size or 0 if unspecified
+ */
+uint32_t rtnl_htb_get_cbuffer(struct rtnl_class *class)
+{
+	struct rtnl_htb_class *htb;
+
+	if ((htb = htb_class_data(class)) &&
+	     htb->ch_mask & SCH_HTB_HAS_CBUFFER)
+		return htb->ch_cbuffer;
+
+	return 0;
+}
+
+/**
+ * Set size of the ceil bucket of HTB class.
+ * @arg class		HTB class to be modified.
+ * @arg cbuffer		New size in bytes.
+ */
+int rtnl_htb_set_cbuffer(struct rtnl_class *class, uint32_t cbuffer)
+{
+	struct rtnl_htb_class *htb;
+
+	if (!(htb = htb_class_data(class)))
+		return -NLE_OPNOTSUPP;
+
+	htb->ch_cbuffer = cbuffer;
+	htb->ch_mask |= SCH_HTB_HAS_CBUFFER;
+
+	return 0;
+}
+
+/**
+ * Return quantum of HTB class
+ * @arg class		htb class object
+ *
+ * See XXX[quantum def]
+ *
+ * @return Quantum or 0 if unspecified.
+ */
+uint32_t rtnl_htb_get_quantum(struct rtnl_class *class)
+{
+	struct rtnl_htb_class *htb;
+
+	if ((htb = htb_class_data(class)) &&
+	    htb->ch_mask & SCH_HTB_HAS_QUANTUM)
+		return htb->ch_quantum;
+
+	return 0;
+}
+
+/**
+ * Set quantum of HTB class (overwrites value calculated based on r2q)
+ * @arg class		htb class object
+ * @arg quantum		new quantum in number of bytes
+ *
+ * See XXX[quantum def]
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_htb_set_quantum(struct rtnl_class *class, uint32_t quantum)
+{
+	struct rtnl_htb_class *htb;
+
+	if (!(htb = htb_class_data(class)))
+		return -NLE_OPNOTSUPP;
+
+	htb->ch_quantum = quantum;
+	htb->ch_mask |= SCH_HTB_HAS_QUANTUM;
+
+	return 0;
+}
+
+/**
+ * Return level of HTB class
+ * @arg class		htb class object
+ *
+ * Returns the level of the HTB class. Leaf classes are assigned level
+ * 0, root classes have level (TC_HTB_MAXDEPTH - 1). Interior classes
+ * have a level of one less than their parent.
+ *
+ * @return Level or -NLE_OPNOTSUPP
+ */
+int rtnl_htb_get_level(struct rtnl_class *class)
+{
+	struct rtnl_htb_class *htb;
+
+	if ((htb = htb_class_data(class)) && htb->ch_mask & SCH_HTB_HAS_LEVEL)
+		return htb->ch_level;
+
+	return -NLE_OPNOTSUPP;
+}
+
+/**
+ * Set level of HTB class
+ * @arg class		htb class object
+ * @arg level		new level of HTB class
+ *
+ * Sets the level of a HTB class. Note that changing the level of a HTB
+ * class does not change the level of its in kernel counterpart. This
+ * function is provided only to create HTB objects which can be compared
+ * against or filtered upon.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_htb_set_level(struct rtnl_class *class, int level)
+{
+	struct rtnl_htb_class *htb;
+
+	if (!(htb = htb_class_data(class)))
+		return -NLE_OPNOTSUPP;
+
+	htb->ch_level = level;
+	htb->ch_mask |= SCH_HTB_HAS_LEVEL;
+
+	return 0;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops htb_qdisc_ops = {
+	.to_kind		= "htb",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_htb_qdisc),
+	.to_msg_parser		= htb_qdisc_msg_parser,
+	.to_dump[NL_DUMP_LINE]	= htb_qdisc_dump_line,
+	.to_msg_fill		= htb_qdisc_msg_fill,
+};
+
+static struct rtnl_tc_ops htb_class_ops = {
+	.to_kind		= "htb",
+	.to_type		= RTNL_TC_TYPE_CLASS,
+	.to_size		= sizeof(struct rtnl_htb_class),
+	.to_msg_parser		= htb_class_msg_parser,
+	.to_dump = {
+	    [NL_DUMP_LINE]	= htb_class_dump_line,
+	    [NL_DUMP_DETAILS]	= htb_class_dump_details,
+	},
+	.to_msg_fill		= htb_class_msg_fill,
+};
+
+static void __init htb_init(void)
+{
+	rtnl_tc_register(&htb_qdisc_ops);
+	rtnl_tc_register(&htb_class_ops);
+}
+
+static void __exit htb_exit(void)
+{
+	rtnl_tc_unregister(&htb_qdisc_ops);
+	rtnl_tc_unregister(&htb_class_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/ingress.c b/lib/route/qdisc/ingress.c
new file mode 100644
index 0000000..1a63f36
--- /dev/null
+++ b/lib/route/qdisc/ingress.c
@@ -0,0 +1,64 @@
+/*
+ * lib/route/qdisc/ingress.c		ingress
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_ingress Ingress qdisc
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/utils.h>
+
+struct dumb {
+	uint32_t foo;
+};
+
+static int dumb_msg_parser(struct rtnl_tc *tc, void *data)
+{
+	return 0;
+}
+
+static void dumb_dump_line(struct rtnl_tc *tc, void *data,
+			    struct nl_dump_params *p)
+{
+}
+
+static int dumb_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+	return 0;
+}
+
+static struct rtnl_tc_ops ingress_ops = {
+	.to_kind		= "ingress",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct dumb),
+	.to_msg_parser		= dumb_msg_parser,
+	.to_dump[NL_DUMP_LINE]	= dumb_dump_line,
+	.to_msg_fill		= dumb_msg_fill,
+};
+
+static void __init ingress_init(void)
+{
+	rtnl_tc_register(&ingress_ops);
+}
+
+static void __exit ingress_exit(void)
+{
+	rtnl_tc_unregister(&ingress_ops);
+}
+
+/** @} */
diff --git a/lib/route/sch/netem.c b/lib/route/qdisc/netem.c
similarity index 77%
rename from lib/route/sch/netem.c
rename to lib/route/qdisc/netem.c
index 18878a7..06d9fe8 100644
--- a/lib/route/sch/netem.c
+++ b/lib/route/qdisc/netem.c
@@ -1,30 +1,30 @@
 /*
- * lib/route/sch/netem.c		Network Emulator Qdisc
+ * lib/route/qdisc/netem.c		Network Emulator Qdisc
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup qdisc_api
- * @defgroup netem Network Emulator
+ * @ingroup qdisc
+ * @defgroup qdisc_netem Network Emulator
  * @brief
  *
  * For further documentation see http://linux-net.osdl.org/index.php/Netem
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/qdisc.h>
-#include <netlink/route/qdisc-modules.h>
-#include <netlink/route/sch/netem.h>
+#include <netlink/route/qdisc/netem.h>
 
 /** @cond SKIP */
 #define SCH_NETEM_ATTR_LATENCY		0x0001
@@ -43,39 +43,22 @@
 #define SCH_NETEM_ATTR_DIST         0x2000
 /** @endcond */
 
-static inline struct rtnl_netem *netem_qdisc(struct rtnl_qdisc *qdisc)
-{
-	return (struct rtnl_netem *) qdisc->q_subdata;
-}
-
-static inline struct rtnl_netem *netem_alloc(struct rtnl_qdisc *qdisc)
-{
-	if (!qdisc->q_subdata)
-		qdisc->q_subdata = calloc(1, sizeof(struct rtnl_netem));
-
-	return netem_qdisc(qdisc);
-}
-
 static struct nla_policy netem_policy[TCA_NETEM_MAX+1] = {
 	[TCA_NETEM_CORR]	= { .minlen = sizeof(struct tc_netem_corr) },
 	[TCA_NETEM_REORDER]	= { .minlen = sizeof(struct tc_netem_reorder) },
 	[TCA_NETEM_CORRUPT]	= { .minlen = sizeof(struct tc_netem_corrupt) },
 };
 
-static int netem_msg_parser(struct rtnl_qdisc *qdisc)
+static int netem_msg_parser(struct rtnl_tc *tc, void *data)
 {
-	int len, err = 0;
-	struct rtnl_netem *netem;
+	struct rtnl_netem *netem = data;
 	struct tc_netem_qopt *opts;
+	int len, err = 0;
 
-	if (qdisc->q_opts->d_size < sizeof(*opts))
+	if (tc->tc_opts->d_size < sizeof(*opts))
 		return -NLE_INVAL;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
-
-	opts = (struct tc_netem_qopt *) qdisc->q_opts->d_data;
+	opts = (struct tc_netem_qopt *) tc->tc_opts->d_data;
 	netem->qnm_latency = opts->latency;
 	netem->qnm_limit = opts->limit;
 	netem->qnm_loss = opts->loss;
@@ -87,13 +70,13 @@
 			   SCH_NETEM_ATTR_LOSS | SCH_NETEM_ATTR_GAP |
 			   SCH_NETEM_ATTR_DUPLICATE | SCH_NETEM_ATTR_JITTER);
 
-	len = qdisc->q_opts->d_size - sizeof(*opts);
+	len = tc->tc_opts->d_size - sizeof(*opts);
 
 	if (len > 0) {
 		struct nlattr *tb[TCA_NETEM_MAX+1];
 
 		err = nla_parse(tb, TCA_NETEM_MAX, (struct nlattr *)
-				(qdisc->q_opts->d_data + sizeof(*opts)),
+				(tc->tc_opts->d_data + sizeof(*opts)),
 				len, netem_policy);
 		if (err < 0) {
 			free(netem);
@@ -143,52 +126,46 @@
 	return 0;
 }
 
-static void netem_free_data(struct rtnl_qdisc *qdisc)
+static void netem_free_data(struct rtnl_tc *tc, void *data)
 {
-	struct rtnl_netem *netem;
+	struct rtnl_netem *netem = data;
 	
-	if ( ! qdisc ) return;
+	if (!netem)
+		return;
 	
-	netem = netem_qdisc(qdisc);
-	if ( ! netem ) return;
-	
-	if ( netem->qnm_dist.dist_data )
-		free(netem->qnm_dist.dist_data);
-	
-	netem = NULL;
-	
-	free (qdisc->q_subdata);
+	free(netem->qnm_dist.dist_data);
 }
 
-static void netem_dump_line(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
+static void netem_dump_line(struct rtnl_tc *tc, void *data,
+			    struct nl_dump_params *p)
 {
-	struct rtnl_netem *netem = netem_qdisc(qdisc);
+	struct rtnl_netem *netem = data;
 
 	if (netem)
 		nl_dump(p, "limit %d", netem->qnm_limit);
 }
 
-int netem_build_msg(struct rtnl_qdisc *qdisc, struct nl_msg *msg)
+static int netem_msg_fill_raw(struct rtnl_tc *tc, void *data,
+			      struct nl_msg *msg)
 {
 	int err = 0;
 	struct tc_netem_qopt opts;
 	struct tc_netem_corr cor;
 	struct tc_netem_reorder reorder;
 	struct tc_netem_corrupt corrupt;
-	struct rtnl_netem *netem;
+	struct rtnl_netem *netem = data;
 	
 	unsigned char set_correlation = 0, set_reorder = 0,
 		set_corrupt = 0, set_dist = 0;
 
+	if (!netem)
+		BUG();
+
 	memset(&opts, 0, sizeof(opts));
 	memset(&cor, 0, sizeof(cor));
 	memset(&reorder, 0, sizeof(reorder));
 	memset(&corrupt, 0, sizeof(corrupt));
 
-	netem = netem_qdisc(qdisc);
-	if (!netem || !msg)
-		return EFAULT;
-		
 	msg->nm_nlh->nlmsg_flags |= NLM_F_REQUEST;
 	
 	if ( netem->qnm_ro.nmro_probability != 0 ) {
@@ -316,18 +293,15 @@
  * @arg limit		New limit in bytes.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_limit(struct rtnl_qdisc *qdisc, int limit)
+void rtnl_netem_set_limit(struct rtnl_qdisc *qdisc, int limit)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 	
 	netem->qnm_limit = limit;
 	netem->qnm_mask |= SCH_NETEM_ATTR_LIMIT;
-
-	return 0;
 }
 
 /**
@@ -339,8 +313,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_LIMIT))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_LIMIT)
 		return netem->qnm_limit;
 	else
 		return -NLE_NOATTR;
@@ -359,18 +335,15 @@
  * @arg gap		New gap in number of packets.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_gap(struct rtnl_qdisc *qdisc, int gap)
+void rtnl_netem_set_gap(struct rtnl_qdisc *qdisc, int gap)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_gap = gap;
 	netem->qnm_mask |= SCH_NETEM_ATTR_GAP;
-
-	return 0;
 }
 
 /**
@@ -382,8 +355,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_GAP))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_GAP)
 		return netem->qnm_gap;
 	else
 		return -NLE_NOATTR;
@@ -395,18 +370,15 @@
  * @arg prob		New re-ordering probability.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_reorder_probability(struct rtnl_qdisc *qdisc, int prob)
+void rtnl_netem_set_reorder_probability(struct rtnl_qdisc *qdisc, int prob)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_ro.nmro_probability = prob;
 	netem->qnm_mask |= SCH_NETEM_ATTR_RO_PROB;
-
-	return 0;
 }
 
 /**
@@ -418,8 +390,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_RO_PROB))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_RO_PROB)
 		return netem->qnm_ro.nmro_probability;
 	else
 		return -NLE_NOATTR;
@@ -431,18 +405,15 @@
  * @arg prob		New re-ordering correlation probability.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_reorder_correlation(struct rtnl_qdisc *qdisc, int prob)
+void rtnl_netem_set_reorder_correlation(struct rtnl_qdisc *qdisc, int prob)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_ro.nmro_correlation = prob;
 	netem->qnm_mask |= SCH_NETEM_ATTR_RO_CORR;
-
-	return 0;
 }
 
 /**
@@ -454,8 +425,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_RO_CORR))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_RO_CORR)
 		return netem->qnm_ro.nmro_correlation;
 	else
 		return -NLE_NOATTR;
@@ -474,18 +447,15 @@
  * @arg prob		New corruption probability.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_corruption_probability(struct rtnl_qdisc *qdisc, int prob)
+void rtnl_netem_set_corruption_probability(struct rtnl_qdisc *qdisc, int prob)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_crpt.nmcr_probability = prob;
 	netem->qnm_mask |= SCH_NETEM_ATTR_CORRUPT_PROB;
-
-	return 0;
 }
 
 /**
@@ -497,8 +467,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_CORRUPT_PROB))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_CORRUPT_PROB)
 		return netem->qnm_crpt.nmcr_probability;
 	else
 		return -NLE_NOATTR;
@@ -510,18 +482,15 @@
  * @arg prob		New corruption correlation probability.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_corruption_correlation(struct rtnl_qdisc *qdisc, int prob)
+void rtnl_netem_set_corruption_correlation(struct rtnl_qdisc *qdisc, int prob)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_crpt.nmcr_correlation = prob;
 	netem->qnm_mask |= SCH_NETEM_ATTR_CORRUPT_CORR;
-
-	return 0;
 }
 
 /**
@@ -533,8 +502,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_CORRUPT_CORR))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_CORRUPT_CORR)
 		return netem->qnm_crpt.nmcr_correlation;
 	else
 		return -NLE_NOATTR;
@@ -553,18 +524,15 @@
  * @arg prob		New packet loss probability.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_loss(struct rtnl_qdisc *qdisc, int prob)
+void rtnl_netem_set_loss(struct rtnl_qdisc *qdisc, int prob)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_loss = prob;
 	netem->qnm_mask |= SCH_NETEM_ATTR_LOSS;
-
-	return 0;
 }
 
 /**
@@ -576,8 +544,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_LOSS))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_LOSS)
 		return netem->qnm_loss;
 	else
 		return -NLE_NOATTR;
@@ -589,18 +559,15 @@
  * @arg prob	New packet loss correlation.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_loss_correlation(struct rtnl_qdisc *qdisc, int prob)
+void rtnl_netem_set_loss_correlation(struct rtnl_qdisc *qdisc, int prob)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_corr.nmc_loss = prob;
 	netem->qnm_mask |= SCH_NETEM_ATTR_LOSS_CORR;
-
-	return 0;
 }
 
 /**
@@ -612,8 +579,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_LOSS_CORR))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_LOSS_CORR)
 		return netem->qnm_corr.nmc_loss;
 	else
 		return -NLE_NOATTR;
@@ -632,18 +601,15 @@
  * @arg prob	New packet duplication probability.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_duplicate(struct rtnl_qdisc *qdisc, int prob)
+void rtnl_netem_set_duplicate(struct rtnl_qdisc *qdisc, int prob)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_duplicate = prob;
 	netem->qnm_mask |= SCH_NETEM_ATTR_DUPLICATE;
-
-	return 0;
 }
 
 /**
@@ -655,8 +621,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_DUPLICATE))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_DUPLICATE)
 		return netem->qnm_duplicate;
 	else
 		return -NLE_NOATTR;
@@ -668,18 +636,15 @@
  * @arg prob		New packet duplication correlation probability.
  * @return 0 on sucess or a negative error code.
  */
-int rtnl_netem_set_duplicate_correlation(struct rtnl_qdisc *qdisc, int prob)
+void rtnl_netem_set_duplicate_correlation(struct rtnl_qdisc *qdisc, int prob)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_corr.nmc_duplicate = prob;
 	netem->qnm_mask |= SCH_NETEM_ATTR_DUP_CORR;
-
-	return 0;
 }
 
 /**
@@ -691,8 +656,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_DUP_CORR))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_DUP_CORR)
 		return netem->qnm_corr.nmc_duplicate;
 	else
 		return -NLE_NOATTR;
@@ -711,18 +678,15 @@
  * @arg delay		New packet delay in micro seconds.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_delay(struct rtnl_qdisc *qdisc, int delay)
+void rtnl_netem_set_delay(struct rtnl_qdisc *qdisc, int delay)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_latency = nl_us2ticks(delay);
 	netem->qnm_mask |= SCH_NETEM_ATTR_LATENCY;
-
-	return 0;
 }
 
 /**
@@ -734,8 +698,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_LATENCY))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_LATENCY)
 		return nl_ticks2us(netem->qnm_latency);
 	else
 		return -NLE_NOATTR;
@@ -747,18 +713,15 @@
  * @arg jitter		New packet delay jitter in micro seconds.
  * @return 0 on success or a negative error code.
  */
-int rtnl_netem_set_jitter(struct rtnl_qdisc *qdisc, int jitter)
+void rtnl_netem_set_jitter(struct rtnl_qdisc *qdisc, int jitter)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_jitter = nl_us2ticks(jitter);
 	netem->qnm_mask |= SCH_NETEM_ATTR_JITTER;
-
-	return 0;
 }
 
 /**
@@ -770,8 +733,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_JITTER))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_JITTER)
 		return nl_ticks2us(netem->qnm_jitter);
 	else
 		return -NLE_NOATTR;
@@ -782,18 +747,15 @@
  * @arg qdisc		Netem qdisc to be modified.
  * @arg prob		New packet delay correlation probability.
  */
-int rtnl_netem_set_delay_correlation(struct rtnl_qdisc *qdisc, int prob)
+void rtnl_netem_set_delay_correlation(struct rtnl_qdisc *qdisc, int prob)
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	netem->qnm_corr.nmc_delay = prob;
 	netem->qnm_mask |= SCH_NETEM_ATTR_DELAY_CORR;
-
-	return 0;
 }
 
 /**
@@ -805,8 +767,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_DELAY_CORR))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_DELAY_CORR)
 		return netem->qnm_corr.nmc_delay;
 	else
 		return -NLE_NOATTR;
@@ -821,8 +785,10 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_DIST))
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_DIST)
 		return netem->qnm_dist.dist_size;
 	else
 		return -NLE_NOATTR;
@@ -838,12 +804,13 @@
 {
 	struct rtnl_netem *netem;
 
-	netem = netem_qdisc(qdisc);
-	if (netem && (netem->qnm_mask & SCH_NETEM_ATTR_DIST)) {
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (netem->qnm_mask & SCH_NETEM_ATTR_DIST) {
 		*dist_ptr = netem->qnm_dist.dist_data;
 		return 0;
-	}
-	else
+	} else
 		return -NLE_NOATTR;
 }
 
@@ -856,12 +823,12 @@
 int rtnl_netem_set_delay_distribution(struct rtnl_qdisc *qdisc, const char *dist_type) {
 	struct rtnl_netem *netem;
 
-	netem = netem_alloc(qdisc);
-	if (!netem)
-		return -NLE_NOMEM;
+	if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 		
-	FILE *f = NULL;
-	int i, n = 0;
+	FILE *f;
+	int n = 0;
+	size_t i;
 	size_t len = 2048;
 	char *line;
 	char name[NAME_MAX];
@@ -875,9 +842,10 @@
 	/* Check several locations for the dist file */
 	char *test_path[] = { "", "./", "/usr/lib/tc/", "/usr/local/lib/tc/" };
 	
-	for (i = 0; i < sizeof(test_path) && f == NULL; i++) {
+	for (i = 0; i < ARRAY_SIZE(test_path); i++) {
 		snprintf(name, NAME_MAX, "%s%s%s", test_path[i], dist_type, dist_suffix);
-		f = fopen(name, "r");
+		if ((f = fopen(name, "r")))
+			break;
 	}
 	
 	if ( f == NULL )
@@ -917,23 +885,24 @@
 
 /** @} */
 
-static struct rtnl_qdisc_ops netem_ops = {
-	.qo_kind		= "netem",
-	.qo_msg_parser		= netem_msg_parser,
-	.qo_free_data		= netem_free_data,
-	.qo_dump[NL_DUMP_LINE]	= netem_dump_line,
-	.qo_get_opts		= 0,
-	.qo_build_msg	= netem_build_msg
+static struct rtnl_tc_ops netem_ops = {
+	.to_kind		= "netem",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_netem),
+	.to_msg_parser		= netem_msg_parser,
+	.to_free_data		= netem_free_data,
+	.to_dump[NL_DUMP_LINE]	= netem_dump_line,
+	.to_msg_fill_raw	= netem_msg_fill_raw,
 };
 
 static void __init netem_init(void)
 {
-	rtnl_qdisc_register(&netem_ops);
+	rtnl_tc_register(&netem_ops);
 }
 
 static void __exit netem_exit(void)
 {
-	rtnl_qdisc_unregister(&netem_ops);
+	rtnl_tc_unregister(&netem_ops);
 }
 
 /** @} */
diff --git a/lib/route/qdisc/plug.c b/lib/route/qdisc/plug.c
new file mode 100644
index 0000000..9f53637
--- /dev/null
+++ b/lib/route/qdisc/plug.c
@@ -0,0 +1,177 @@
+/*
+ * lib/route/qdisc/plug.c		PLUG Qdisc
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2012 Shriram Rajagopalan <rshriram@cs.ubc.ca>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_plug Plug/Unplug Traffic (PLUG)
+ * @brief
+ *
+ * Queue traffic until an explicit release command.
+ *
+ * There are two ways to use this qdisc:
+ * 1. A simple "instantaneous" plug/unplug operation, by issuing an alternating
+ *    sequence of TCQ_PLUG_BUFFER & TCQ_PLUG_RELEASE_INDEFINITE commands.
+ *
+ * 2. For network output buffering (a.k.a output commit) functionality.
+ *    Output commit property is commonly used by applications using checkpoint
+ *    based fault-tolerance to ensure that the checkpoint from which a system
+ *    is being restored is consistent w.r.t outside world.
+ *
+ *    Consider for e.g. Remus - a Virtual Machine checkpointing system,
+ *    wherein a VM is checkpointed, say every 50ms. The checkpoint is replicated
+ *    asynchronously to the backup host, while the VM continues executing the
+ *    next epoch speculatively.
+ *
+ *    The following is a typical sequence of output buffer operations:
+ *       1.At epoch i, start_buffer(i)
+ *       2. At end of epoch i (i.e. after 50ms):
+ *          2.1 Stop VM and take checkpoint(i).
+ *          2.2 start_buffer(i+1) and Resume VM
+ *       3. While speculatively executing epoch(i+1), asynchronously replicate
+ *          checkpoint(i) to backup host.
+ *       4. When checkpoint_ack(i) is received from backup, release_buffer(i)
+ *    Thus, this Qdisc would receive the following sequence of commands:
+ *       TCQ_PLUG_BUFFER (epoch i)
+ *       .. TCQ_PLUG_BUFFER (epoch i+1)
+ *       ....TCQ_PLUG_RELEASE_ONE (epoch i)
+ *       ......TCQ_PLUG_BUFFER (epoch i+2)
+ *       ........
+ *
+ *
+ * State of the queue, when used for network output buffering:
+ *
+ *                 plug(i+1)            plug(i)          head
+ * ------------------+--------------------+---------------->
+ *                   |                    |
+ *                   |                    |
+ * pkts_current_epoch| pkts_last_epoch    |pkts_to_release
+ * ----------------->|<--------+--------->|+--------------->
+ *                   v                    v
+ *
+ *
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/qdisc/plug.h>
+
+static int plug_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+	struct rtnl_plug *plug = data;
+	struct tc_plug_qopt opts;
+
+	if (!plug)
+		return -NLE_INVAL;
+
+	opts.action = plug->action;
+	opts.limit  = plug->limit;
+
+	return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD);
+}
+
+/**
+ * @name Attribute Modification
+ * @{
+ */
+
+/**
+ * Insert a plug into the qdisc and buffer any incoming
+ * network traffic.
+ * @arg qdisc		PLUG qdisc to be modified.
+ */
+int rtnl_qdisc_plug_buffer(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_plug *plug;
+
+	if (!(plug = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	plug->action = TCQ_PLUG_BUFFER;
+	return 0;
+}
+
+/**
+ * Unplug the qdisc, releasing packets from queue head
+ * to the last complete buffer, while new traffic
+ * continues to be buffered.
+ * @arg qdisc		PLUG qdisc to be modified.
+ */
+int rtnl_qdisc_plug_release_one(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_plug *plug;
+
+	if (!(plug = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	plug->action = TCQ_PLUG_RELEASE_ONE;
+	return 0;
+}
+
+/**
+ * Indefinitely unplug the qdisc, releasing all packets.
+ * Network traffic will not be buffered until the next
+ * buffer command is issued.
+ * @arg qdisc		PLUG qdisc to be modified.
+ */
+int rtnl_qdisc_plug_release_indefinite(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_plug *plug;
+
+	if (!(plug = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+
+	plug->action = TCQ_PLUG_RELEASE_INDEFINITE;
+	return 0;
+}
+
+/**
+ * Set limit of PLUG qdisc.
+ * @arg qdisc		PLUG qdisc to be modified.
+ * @arg limit		New limit.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_plug_set_limit(struct rtnl_qdisc *qdisc, int limit)
+{
+	struct rtnl_plug *plug;
+	
+	if (!(plug = rtnl_tc_data(TC_CAST(qdisc))))
+		return -NLE_NOMEM;
+		
+	plug->action = TCQ_PLUG_LIMIT;
+	plug->limit  = limit;
+
+	return 0;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops plug_ops = {
+	.to_kind		= "plug",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_plug),
+	.to_msg_fill		= plug_msg_fill,
+};
+
+static void __init plug_init(void)
+{
+	rtnl_tc_register(&plug_ops);
+}
+
+static void __exit plug_exit(void)
+{
+	rtnl_tc_unregister(&plug_ops);
+}
+
+/** @} */
diff --git a/lib/route/sch/prio.c b/lib/route/qdisc/prio.c
similarity index 65%
rename from lib/route/sch/prio.c
rename to lib/route/qdisc/prio.c
index 4c9ebcf..54a46f0 100644
--- a/lib/route/sch/prio.c
+++ b/lib/route/qdisc/prio.c
@@ -1,17 +1,17 @@
 /*
- * lib/route/sch/prio.c		PRIO Qdisc/Class
+ * lib/route/qdisc/prio.c		PRIO Qdisc/Class
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup qdisc_api
- * @defgroup prio (Fast) Prio
+ * @ingroup qdisc
+ * @defgroup qdisc_prio (Fast) Prio
  * @brief
  *
  * @par 1) Typical PRIO configuration
@@ -26,45 +26,28 @@
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/qdisc.h>
-#include <netlink/route/qdisc-modules.h>
-#include <netlink/route/sch/prio.h>
+#include <netlink/route/qdisc/prio.h>
 
 /** @cond SKIP */
 #define SCH_PRIO_ATTR_BANDS	1
 #define SCH_PRIO_ATTR_PRIOMAP	2
 /** @endcond */
 
-static inline struct rtnl_prio *prio_qdisc(struct rtnl_qdisc *qdisc)
+static int prio_msg_parser(struct rtnl_tc *tc, void *data)
 {
-	return (struct rtnl_prio *) qdisc->q_subdata;
-}
-
-static inline struct rtnl_prio *prio_alloc(struct rtnl_qdisc *qdisc)
-{
-	if (!qdisc->q_subdata)
-		qdisc->q_subdata = calloc(1, sizeof(struct rtnl_prio));
-
-	return prio_qdisc(qdisc);
-}
-
-static int prio_msg_parser(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_prio *prio;
+	struct rtnl_prio *prio = data;
 	struct tc_prio_qopt *opt;
 
-	if (qdisc->q_opts->d_size < sizeof(*opt))
+	if (tc->tc_opts->d_size < sizeof(*opt))
 		return -NLE_INVAL;
 
-	prio = prio_alloc(qdisc);
-	if (!prio)
-		return -NLE_NOMEM;
-
-	opt = (struct tc_prio_qopt *) qdisc->q_opts->d_data;
+	opt = (struct tc_prio_qopt *) tc->tc_opts->d_data;
 	prio->qp_bands = opt->bands;
 	memcpy(prio->qp_priomap, opt->priomap, sizeof(prio->qp_priomap));
 	prio->qp_mask = (SCH_PRIO_ATTR_BANDS | SCH_PRIO_ATTR_PRIOMAP);
@@ -72,22 +55,19 @@
 	return 0;
 }
 
-static void prio_free_data(struct rtnl_qdisc *qdisc)
+static void prio_dump_line(struct rtnl_tc *tc, void *data,
+			   struct nl_dump_params *p)
 {
-	free(qdisc->q_subdata);
-}
-
-static void prio_dump_line(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
-{
-	struct rtnl_prio *prio = prio_qdisc(qdisc);
+	struct rtnl_prio *prio = data;
 
 	if (prio)
 		nl_dump(p, " bands %u", prio->qp_bands);
 }
 
-static void prio_dump_details(struct rtnl_qdisc *qdisc,struct nl_dump_params *p)
+static void prio_dump_details(struct rtnl_tc *tc, void *data,
+			      struct nl_dump_params *p)
 {
-	struct rtnl_prio *prio = prio_qdisc(qdisc);
+	struct rtnl_prio *prio = data;
 	int i, hp;
 
 	if (!prio)
@@ -121,32 +101,18 @@
 	}
 }
 
-static struct nl_msg *prio_get_opts(struct rtnl_qdisc *qdisc)
+static int prio_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
 {
-	struct rtnl_prio *prio;
+	struct rtnl_prio *prio = data;
 	struct tc_prio_qopt opts;
-	struct nl_msg *msg;
 
-	prio = prio_qdisc(qdisc);
-	if (!prio ||
-	    !(prio->qp_mask & SCH_PRIO_ATTR_PRIOMAP))
-		goto errout;
+	if (!prio || !(prio->qp_mask & SCH_PRIO_ATTR_PRIOMAP))
+		BUG();
 
 	opts.bands = prio->qp_bands;
 	memcpy(opts.priomap, prio->qp_priomap, sizeof(opts.priomap));
 
-	msg = nlmsg_alloc();
-	if (!msg)
-		goto errout;
-
-	if (nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD) < 0) {
-		nlmsg_free(msg);
-		goto errout;
-	}
-
-	return msg;
-errout:
-	return NULL;
+	return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD);
 }
 
 /**
@@ -160,18 +126,15 @@
  * @arg bands		New number of bands.
  * @return 0 on success or a negative error code.
  */
-int rtnl_qdisc_prio_set_bands(struct rtnl_qdisc *qdisc, int bands)
+void rtnl_qdisc_prio_set_bands(struct rtnl_qdisc *qdisc, int bands)
 {
 	struct rtnl_prio *prio;
-	
-	prio = prio_alloc(qdisc);
-	if (!prio)
-		return -NLE_NOMEM;
+
+	if (!(prio = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	prio->qp_bands = bands;
 	prio->qp_mask |= SCH_PRIO_ATTR_BANDS;
-
-	return 0;
 }
 
 /**
@@ -183,8 +146,10 @@
 {
 	struct rtnl_prio *prio;
 
-	prio = prio_qdisc(qdisc);
-	if (prio && prio->qp_mask & SCH_PRIO_ATTR_BANDS)
+	if (!(prio = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (prio->qp_mask & SCH_PRIO_ATTR_BANDS)
 		return prio->qp_bands;
 	else
 		return -NLE_NOMEM;
@@ -203,9 +168,8 @@
 	struct rtnl_prio *prio;
 	int i;
 
-	prio = prio_alloc(qdisc);
-	if (!prio)
-		return -NLE_NOMEM;
+	if (!(prio = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	if (!(prio->qp_mask & SCH_PRIO_ATTR_BANDS))
 		return -NLE_MISSING_ATTR;
@@ -234,8 +198,10 @@
 {
 	struct rtnl_prio *prio;
 
-	prio = prio_qdisc(qdisc);
-	if (prio && prio->qp_mask & SCH_PRIO_ATTR_PRIOMAP)
+	if (!(prio = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (prio->qp_mask & SCH_PRIO_ATTR_PRIOMAP)
 		return prio->qp_priomap;
 	else
 		return NULL;
@@ -248,7 +214,7 @@
  * @{
  */
 
-static struct trans_tbl prios[] = {
+static const struct trans_tbl prios[] = {
 	__ADD(TC_PRIO_BESTEFFORT,besteffort)
 	__ADD(TC_PRIO_FILLER,filler)
 	__ADD(TC_PRIO_BULK,bulk)
@@ -289,38 +255,40 @@
 
 /** @} */
 
-static struct rtnl_qdisc_ops prio_ops = {
-	.qo_kind		= "prio",
-	.qo_msg_parser		= prio_msg_parser,
-	.qo_free_data		= prio_free_data,
-	.qo_dump = {
+static struct rtnl_tc_ops prio_ops = {
+	.to_kind		= "prio",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_prio),
+	.to_msg_parser		= prio_msg_parser,
+	.to_dump = {
 	    [NL_DUMP_LINE]	= prio_dump_line,
 	    [NL_DUMP_DETAILS]	= prio_dump_details,
 	},
-	.qo_get_opts		= prio_get_opts,
+	.to_msg_fill		= prio_msg_fill,
 };
 
-static struct rtnl_qdisc_ops pfifo_fast_ops = {
-	.qo_kind		= "pfifo_fast",
-	.qo_msg_parser		= prio_msg_parser,
-	.qo_free_data		= prio_free_data,
-	.qo_dump = {
+static struct rtnl_tc_ops pfifo_fast_ops = {
+	.to_kind		= "pfifo_fast",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_prio),
+	.to_msg_parser		= prio_msg_parser,
+	.to_dump = {
 	    [NL_DUMP_LINE]	= prio_dump_line,
 	    [NL_DUMP_DETAILS]	= prio_dump_details,
 	},
-	.qo_get_opts		= prio_get_opts,
+	.to_msg_fill		= prio_msg_fill,
 };
 
 static void __init prio_init(void)
 {
-	rtnl_qdisc_register(&prio_ops);
-	rtnl_qdisc_register(&pfifo_fast_ops);
+	rtnl_tc_register(&prio_ops);
+	rtnl_tc_register(&pfifo_fast_ops);
 }
 
 static void __exit prio_exit(void)
 {
-	rtnl_qdisc_unregister(&prio_ops);
-	rtnl_qdisc_unregister(&pfifo_fast_ops);
+	rtnl_tc_unregister(&prio_ops);
+	rtnl_tc_unregister(&pfifo_fast_ops);
 }
 
 /** @} */
diff --git a/lib/route/qdisc/red.c b/lib/route/qdisc/red.c
new file mode 100644
index 0000000..f05626e
--- /dev/null
+++ b/lib/route/qdisc/red.c
@@ -0,0 +1,190 @@
+/*
+ * lib/route/qdisc/red.c		RED Qdisc
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_red Random Early Detection (RED)
+ * @brief
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/red.h>
+
+/** @cond SKIP */
+#define RED_ATTR_LIMIT		0x01
+#define RED_ATTR_QTH_MIN	0x02
+#define RED_ATTR_QTH_MAX	0x04
+#define RED_ATTR_FLAGS		0x08
+#define RED_ATTR_WLOG		0x10
+#define RED_ATTR_PLOG		0x20
+#define RED_ATTR_SCELL_LOG	0x40
+/** @endcond */
+
+static struct nla_policy red_policy[TCA_RED_MAX+1] = {
+	[TCA_RED_PARMS]		= { .minlen = sizeof(struct tc_red_qopt) },
+};
+
+static int red_msg_parser(struct rtnl_tc *tc, void *data)
+{
+	struct nlattr *tb[TCA_RED_MAX+1];
+	struct rtnl_red *red = data;
+	struct tc_red_qopt *opts;
+	int err;
+
+	if (!(tc->ce_mask & TCA_ATTR_OPTS))
+		return 0;
+
+	err = tca_parse(tb, TCA_RED_MAX, tc, red_policy);
+	if (err < 0)
+		return err;
+
+	if (!tb[TCA_RED_PARMS])
+		return -NLE_MISSING_ATTR;
+
+	opts = nla_data(tb[TCA_RED_PARMS]);
+
+	red->qr_limit = opts->limit;
+	red->qr_qth_min = opts->qth_min;
+	red->qr_qth_max = opts->qth_max;
+	red->qr_flags = opts->flags;
+	red->qr_wlog = opts->Wlog;
+	red->qr_plog = opts->Plog;
+	red->qr_scell_log = opts->Scell_log;
+
+	red->qr_mask = (RED_ATTR_LIMIT | RED_ATTR_QTH_MIN | RED_ATTR_QTH_MAX |
+			RED_ATTR_FLAGS | RED_ATTR_WLOG | RED_ATTR_PLOG |
+			RED_ATTR_SCELL_LOG);
+
+	return 0;
+}
+
+static void red_dump_line(struct rtnl_tc *tc, void *data,
+			  struct nl_dump_params *p)
+{
+	struct rtnl_red *red = data;
+
+	if (red) {
+		/* XXX: limit, min, max, flags */
+	}
+}
+
+static void red_dump_details(struct rtnl_tc *tc, void *data,
+			     struct nl_dump_params *p)
+{
+	struct rtnl_red *red = data;
+
+	if (red) {
+		/* XXX: wlog, plog, scell_log */
+	}
+}
+
+static void red_dump_stats(struct rtnl_tc *tc, void *data,
+			   struct nl_dump_params *p)
+{
+	struct rtnl_red *red = data;
+
+	if (red) {
+		/* XXX: xstats */
+	}
+}
+
+static int red_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+	struct rtnl_red *red = data;
+
+	if (!red)
+		BUG();
+
+#if 0
+	memset(&opts, 0, sizeof(opts));
+	opts.quantum = sfq->qs_quantum;
+	opts.perturb_period = sfq->qs_perturb;
+	opts.limit = sfq->qs_limit;
+
+	if (nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD) < 0)
+		goto errout;
+#endif
+
+	return -NLE_OPNOTSUPP;
+}
+
+/**
+ * @name Attribute Access
+ * @{
+ */
+
+/**
+ * Set limit of RED qdisc.
+ * @arg qdisc		RED qdisc to be modified.
+ * @arg limit		New limit in number of packets.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_red_set_limit(struct rtnl_qdisc *qdisc, int limit)
+{
+	struct rtnl_red *red;
+
+	if (!(red = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	red->qr_limit = limit;
+	red->qr_mask |= RED_ATTR_LIMIT;
+}
+
+/**
+ * Get limit of RED qdisc.
+ * @arg qdisc		RED qdisc.
+ * @return Limit or a negative error code.
+ */
+int rtnl_red_get_limit(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_red *red;
+
+	if (!(red = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (red->qr_mask & RED_ATTR_LIMIT)
+		return red->qr_limit;
+	else
+		return -NLE_NOATTR;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops red_ops = {
+	.to_kind		= "red",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_red),
+	.to_msg_parser		= red_msg_parser,
+	.to_dump = {
+	    [NL_DUMP_LINE]	= red_dump_line,
+	    [NL_DUMP_DETAILS]	= red_dump_details,
+	    [NL_DUMP_STATS]	= red_dump_stats,
+	},
+	.to_msg_fill		= red_msg_fill,
+};
+
+static void __init red_init(void)
+{
+	rtnl_tc_register(&red_ops);
+}
+
+static void __exit red_exit(void)
+{
+	rtnl_tc_unregister(&red_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/sfq.c b/lib/route/qdisc/sfq.c
new file mode 100644
index 0000000..acbb4ef
--- /dev/null
+++ b/lib/route/qdisc/sfq.c
@@ -0,0 +1,256 @@
+/*
+ * lib/route/qdisc/sfq.c		SFQ Qdisc
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_sfq Stochastic Fairness Queueing (SFQ)
+ * @brief
+ *
+ * @par Parameter Description
+ * - \b Quantum: Number of bytes to send out per slot and round.
+ * - \b Perturbation: Timer period between changing the hash function.
+ * - \b Limit:  Upper limit of queue in number of packets before SFQ starts
+ *	        dropping packets.
+ * - \b Divisor: Hash table divisor, i.e. size of hash table.
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/sfq.h>
+
+/** @cond SKIP */
+#define SCH_SFQ_ATTR_QUANTUM	0x01
+#define SCH_SFQ_ATTR_PERTURB	0x02
+#define SCH_SFQ_ATTR_LIMIT	0x04
+#define SCH_SFQ_ATTR_DIVISOR	0x08
+#define SCH_SFQ_ATTR_FLOWS	0x10
+/** @endcond */
+
+static int sfq_msg_parser(struct rtnl_tc *tc, void *data)
+{
+	struct rtnl_sfq *sfq = data;
+	struct tc_sfq_qopt *opts;
+
+	if (!(tc->ce_mask & TCA_ATTR_OPTS))
+		return 0;
+
+	if (tc->tc_opts->d_size < sizeof(*opts))
+		return -NLE_INVAL;
+
+	opts = (struct tc_sfq_qopt *) tc->tc_opts->d_data;
+
+	sfq->qs_quantum = opts->quantum;
+	sfq->qs_perturb = opts->perturb_period;
+	sfq->qs_limit = opts->limit;
+	sfq->qs_divisor = opts->divisor;
+	sfq->qs_flows = opts->flows;
+
+	sfq->qs_mask = (SCH_SFQ_ATTR_QUANTUM | SCH_SFQ_ATTR_PERTURB |
+			SCH_SFQ_ATTR_LIMIT | SCH_SFQ_ATTR_DIVISOR |
+			SCH_SFQ_ATTR_FLOWS);
+
+	return 0;
+}
+
+static void sfq_dump_line(struct rtnl_tc *tc, void *data,
+			  struct nl_dump_params *p)
+{
+	struct rtnl_sfq *sfq = data;
+
+	if (sfq)
+		nl_dump(p, " quantum %u perturb %us", sfq->qs_quantum,
+			sfq->qs_perturb);
+}
+
+static void sfq_dump_details(struct rtnl_tc *tc, void *data,
+			     struct nl_dump_params *p)
+{
+	struct rtnl_sfq *sfq = data;
+
+	if (sfq)
+		nl_dump(p, "limit %u divisor %u",
+			sfq->qs_limit, sfq->qs_divisor);
+}
+
+static int sfq_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+	struct rtnl_sfq *sfq = data;
+	struct tc_sfq_qopt opts = {0};
+
+	if (!sfq)
+		BUG();
+
+	opts.quantum = sfq->qs_quantum;
+	opts.perturb_period = sfq->qs_perturb;
+	opts.limit = sfq->qs_limit;
+
+	return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD);
+}
+
+/**
+ * @name Attribute Access
+ * @{
+ */
+
+/**
+ * Set quantum of SFQ qdisc.
+ * @arg qdisc		SFQ qdisc to be modified.
+ * @arg quantum		New quantum in bytes.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfq_set_quantum(struct rtnl_qdisc *qdisc, int quantum)
+{
+	struct rtnl_sfq *sfq;
+	
+	if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfq->qs_quantum = quantum;
+	sfq->qs_mask |= SCH_SFQ_ATTR_QUANTUM;
+}
+
+/**
+ * Get quantum of SFQ qdisc.
+ * @arg qdisc		SFQ qdisc.
+ * @return Quantum in bytes or a negative error code.
+ */
+int rtnl_sfq_get_quantum(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfq *sfq;
+	
+	if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfq->qs_mask & SCH_SFQ_ATTR_QUANTUM)
+		return sfq->qs_quantum;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set limit of SFQ qdisc.
+ * @arg qdisc		SFQ qdisc to be modified.
+ * @arg limit		New limit in number of packets.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfq_set_limit(struct rtnl_qdisc *qdisc, int limit)
+{
+	struct rtnl_sfq *sfq;
+	
+	if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfq->qs_limit = limit;
+	sfq->qs_mask |= SCH_SFQ_ATTR_LIMIT;
+}
+
+/**
+ * Get limit of SFQ qdisc.
+ * @arg qdisc		SFQ qdisc.
+ * @return Limit or a negative error code.
+ */
+int rtnl_sfq_get_limit(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfq *sfq;
+	
+	if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfq->qs_mask & SCH_SFQ_ATTR_LIMIT)
+		return sfq->qs_limit;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set perturbation interval of SFQ qdisc.
+ * @arg qdisc		SFQ qdisc to be modified.
+ * @arg perturb		New perturbation interval in seconds.
+ * @note A value of 0 disables perturbation altogether.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfq_set_perturb(struct rtnl_qdisc *qdisc, int perturb)
+{
+	struct rtnl_sfq *sfq;
+	
+	if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfq->qs_perturb = perturb;
+	sfq->qs_mask |= SCH_SFQ_ATTR_PERTURB;
+}
+
+/**
+ * Get perturbation interval of SFQ qdisc.
+ * @arg qdisc		SFQ qdisc.
+ * @return Perturbation interval in seconds or a negative error code.
+ */
+int rtnl_sfq_get_perturb(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfq *sfq;
+	
+	if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfq->qs_mask & SCH_SFQ_ATTR_PERTURB)
+		return sfq->qs_perturb;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Get divisor of SFQ qdisc.
+ * @arg qdisc		SFQ qdisc.
+ * @return Divisor in number of entries or a negative error code.
+ */
+int rtnl_sfq_get_divisor(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfq *sfq;
+	
+	if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfq->qs_mask & SCH_SFQ_ATTR_DIVISOR)
+		return sfq->qs_divisor;
+	else
+		return -NLE_NOATTR;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops sfq_ops = {
+	.to_kind		= "sfq",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_sfq),
+	.to_msg_parser		= sfq_msg_parser,
+	.to_dump = {
+	    [NL_DUMP_LINE]	= sfq_dump_line,
+	    [NL_DUMP_DETAILS]	= sfq_dump_details,
+	},
+	.to_msg_fill		= sfq_msg_fill,
+};
+
+static void __init sfq_init(void)
+{
+	rtnl_tc_register(&sfq_ops);
+}
+
+static void __exit sfq_exit(void)
+{
+	rtnl_tc_unregister(&sfq_ops);
+}
+
+/** @} */
diff --git a/lib/route/sch/tbf.c b/lib/route/qdisc/tbf.c
similarity index 64%
rename from lib/route/sch/tbf.c
rename to lib/route/qdisc/tbf.c
index eccaf70..eb574d9 100644
--- a/lib/route/sch/tbf.c
+++ b/lib/route/qdisc/tbf.c
@@ -1,78 +1,56 @@
 /*
- * lib/route/sch/tbf.c		TBF Qdisc
+ * lib/route/qdisc/tbf.c		TBF Qdisc
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup qdisc_api
- * @defgroup tbf Token Bucket Filter (TBF)
+ * @ingroup qdisc
+ * @defgroup qdisc_tbf Token Bucket Filter (TBF)
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/utils.h>
-#include <netlink/route/tc.h>
+#include <netlink-private/route/tc-api.h>
 #include <netlink/route/qdisc.h>
-#include <netlink/route/qdisc-modules.h>
 #include <netlink/route/class.h>
-#include <netlink/route/class-modules.h>
 #include <netlink/route/link.h>
-#include <netlink/route/sch/tbf.h>
+#include <netlink/route/qdisc/tbf.h>
 
 /** @cond SKIP */
 #define TBF_ATTR_LIMIT			0x01
 #define TBF_ATTR_RATE			0x02
 #define TBF_ATTR_PEAKRATE		0x10
-#define TBF_ATTR_MPU			0x80
 /** @endcond */
 
-static inline struct rtnl_tbf *tbf_qdisc(struct rtnl_qdisc *qdisc)
-{
-	return (struct rtnl_tbf *) qdisc->q_subdata;
-}
-
-static inline struct rtnl_tbf *tbf_alloc(struct rtnl_qdisc *qdisc)
-{
-	if (!qdisc->q_subdata)
-		qdisc->q_subdata = calloc(1, sizeof(struct rtnl_tbf));
-
-	return tbf_qdisc(qdisc);
-}
-
 static struct nla_policy tbf_policy[TCA_TBF_MAX+1] = {
 	[TCA_TBF_PARMS]	= { .minlen = sizeof(struct tc_tbf_qopt) },
 };
 
-static int tbf_msg_parser(struct rtnl_qdisc *q)
+static int tbf_msg_parser(struct rtnl_tc *tc, void *data)
 {
-	int err;
 	struct nlattr *tb[TCA_TBF_MAX + 1];
-	struct rtnl_tbf *tbf;
+	struct rtnl_tbf *tbf = data;
+	int err;
 
-	err = tca_parse(tb, TCA_TBF_MAX, (struct rtnl_tca *) q, tbf_policy);
-	if (err < 0)
+	if ((err = tca_parse(tb, TCA_TBF_MAX, tc, tbf_policy)) < 0)
 		return err;
 	
-	tbf = tbf_alloc(q);
-	if (!tbf)
-		return -NLE_NOMEM;
-
 	if (tb[TCA_TBF_PARMS]) {
 		struct tc_tbf_qopt opts;
 		int bufsize;
 
 		nla_memcpy(&opts, tb[TCA_TBF_PARMS], sizeof(opts));
 		tbf->qt_limit = opts.limit;
-		tbf->qt_mpu = opts.rate.mpu;
 	
 		rtnl_copy_ratespec(&tbf->qt_rate, &opts.rate);
 		tbf->qt_rate_txtime = opts.buffer;
@@ -86,23 +64,21 @@
 					       opts.peakrate.rate);
 		tbf->qt_peakrate_bucket = bufsize;
 
-		tbf->qt_mask = (TBF_ATTR_LIMIT | TBF_ATTR_MPU | TBF_ATTR_RATE |
-				TBF_ATTR_PEAKRATE);
+		rtnl_tc_set_mpu(tc, tbf->qt_rate.rs_mpu);
+		rtnl_tc_set_overhead(tc, tbf->qt_rate.rs_overhead);
+
+		tbf->qt_mask = (TBF_ATTR_LIMIT | TBF_ATTR_RATE | TBF_ATTR_PEAKRATE);
 	}
 
 	return 0;
 }
 
-static void tbf_free_data(struct rtnl_qdisc *qdisc)
-{
-	free(qdisc->q_subdata);
-}
-
-static void tbf_dump_line(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
+static void tbf_dump_line(struct rtnl_tc *tc, void *data,
+			  struct nl_dump_params *p)
 {
 	double r, rbit, lim;
 	char *ru, *rubit, *limu;
-	struct rtnl_tbf *tbf = tbf_qdisc(qdisc);
+	struct rtnl_tbf *tbf = data;
 
 	if (!tbf)
 		return;
@@ -115,9 +91,10 @@
 		r, ru, rbit, rubit, lim, limu);
 }
 
-static void tbf_dump_details(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
+static void tbf_dump_details(struct rtnl_tc *tc, void *data,
+			     struct nl_dump_params *p)
 {
-	struct rtnl_tbf *tbf = tbf_qdisc(qdisc);
+	struct rtnl_tbf *tbf = data;
 
 	if (!tbf)
 		return;
@@ -128,9 +105,9 @@
 		double cl = nl_cancel_down_bytes(1 << tbf->qt_rate.rs_cell_log,
 						 &cu);
 
-		nl_dump(p, "mpu %u rate-bucket-size %1.f%s "
+		nl_dump(p, "rate-bucket-size %1.f%s "
 			   "rate-cell-size %.1f%s\n",
-			tbf->qt_mpu, bs, bu, cl, cu);
+			bs, bu, cl, cu);
 
 	}
 
@@ -151,59 +128,40 @@
 	}
 }
 
-static struct nl_msg *tbf_get_opts(struct rtnl_qdisc *qdisc)
+static int tbf_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
 {
+	uint32_t rtab[RTNL_TC_RTABLE_SIZE], ptab[RTNL_TC_RTABLE_SIZE];
 	struct tc_tbf_qopt opts;
-	struct rtnl_tbf *tbf;
-	struct nl_msg *msg;
-	uint32_t rtab[RTNL_TC_RTABLE_SIZE];
-	uint32_t ptab[RTNL_TC_RTABLE_SIZE];
+	struct rtnl_tbf *tbf = data;
 	int required = TBF_ATTR_RATE | TBF_ATTR_LIMIT;
 
+	if ((tbf->qt_mask & required) != required)
+		return -NLE_MISSING_ATTR;
+
 	memset(&opts, 0, sizeof(opts));
-
-	tbf = tbf_qdisc(qdisc);
-	if (!tbf)
-		return NULL;
-
-	if (!(tbf->qt_mask & required) != required)
-		return NULL;
-
 	opts.limit = tbf->qt_limit;
 	opts.buffer = tbf->qt_rate_txtime;
-	tbf->qt_rate.rs_mpu = tbf->qt_mpu;
-	rtnl_rcopy_ratespec(&opts.rate, &tbf->qt_rate);
 
-	rtnl_tc_build_rate_table(rtab, tbf->qt_mpu & 0xff, tbf->qt_mpu >> 8,
-				 1 << tbf->qt_rate.rs_cell_log,
-				 tbf->qt_rate.rs_rate);
+	rtnl_tc_build_rate_table(tc, &tbf->qt_rate, rtab);
+	rtnl_rcopy_ratespec(&opts.rate, &tbf->qt_rate);
 
 	if (tbf->qt_mask & TBF_ATTR_PEAKRATE) {
 		opts.mtu = tbf->qt_peakrate_txtime;
-		tbf->qt_peakrate.rs_mpu = tbf->qt_mpu;
+		rtnl_tc_build_rate_table(tc, &tbf->qt_peakrate, ptab);
 		rtnl_rcopy_ratespec(&opts.peakrate, &tbf->qt_peakrate);
 
-		rtnl_tc_build_rate_table(ptab, tbf->qt_mpu & 0xff,
-					 tbf->qt_mpu >> 8,
-					 1 << tbf->qt_peakrate.rs_cell_log,
-					 tbf->qt_peakrate.rs_rate);
 	}
 
-	msg = nlmsg_alloc();
-	if (!msg)
-		goto nla_put_failure;
-
 	NLA_PUT(msg, TCA_TBF_PARMS, sizeof(opts), &opts);
 	NLA_PUT(msg, TCA_TBF_RTAB, sizeof(rtab), rtab);
 
 	if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
 		NLA_PUT(msg, TCA_TBF_PTAB, sizeof(ptab), ptab);
 
-	return msg;
+	return 0;
 
 nla_put_failure:
-	nlmsg_free(msg);
-	return NULL;
+	return -NLE_MSGSIZE;
 }
 
 /**
@@ -217,18 +175,15 @@
  * @arg limit		New limit in bytes.
  * @return 0 on success or a negative error code.
  */
-int rtnl_qdisc_tbf_set_limit(struct rtnl_qdisc *qdisc, int limit)
+void rtnl_qdisc_tbf_set_limit(struct rtnl_qdisc *qdisc, int limit)
 {
 	struct rtnl_tbf *tbf;
 	
-	tbf = tbf_alloc(qdisc);
-	if (!tbf)
-		return -NLE_NOMEM;
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	tbf->qt_limit = limit;
 	tbf->qt_mask |= TBF_ATTR_LIMIT;
-
-	return 0;
 }
 
 static inline double calc_limit(struct rtnl_ratespec *spec, int latency,
@@ -265,9 +220,8 @@
 	struct rtnl_tbf *tbf;
 	double limit, limit2;
 
-	tbf = tbf_alloc(qdisc);
-	if (!tbf)
-		return -NLE_NOMEM;
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	if (!(tbf->qt_mask & TBF_ATTR_RATE))
 		return -NLE_MISSING_ATTR;
@@ -282,7 +236,9 @@
 			limit = limit2;
 	}
 
-	return rtnl_qdisc_tbf_set_limit(qdisc, (int) limit);
+	rtnl_qdisc_tbf_set_limit(qdisc, (int) limit);
+
+	return 0;
 }
 
 /**
@@ -294,63 +250,18 @@
 {
 	struct rtnl_tbf *tbf;
 	
-	tbf = tbf_qdisc(qdisc);
-	if (tbf && (tbf->qt_mask & TBF_ATTR_LIMIT))
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (tbf->qt_mask & TBF_ATTR_LIMIT)
 		return tbf->qt_limit;
 	else
 		return -NLE_NOATTR;
 }
 
-/**
- * Set MPU of TBF qdisc.
- * @arg qdisc		TBF qdisc to be modified.
- * @arg mpu		New MPU in bytes.
- * @return 0 on success or a negative error code.
- */
-int rtnl_qdisc_tbf_set_mpu(struct rtnl_qdisc *qdisc, int mpu)
-{
-	struct rtnl_tbf *tbf;
-	
-	tbf = tbf_alloc(qdisc);
-	if (!tbf)
-		return -NLE_NOMEM;
-
-	tbf->qt_mpu = mpu;
-	tbf->qt_mask |= TBF_ATTR_MPU;
-
-	return 0;
-}
-
-/**
- * Get MPU of TBF qdisc.
- * @arg qdisc		TBF qdisc.
- * @return MPU in bytes or a negative error code.
- */
-int rtnl_qdisc_tbf_get_mpu(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_tbf *tbf;
-	
-	tbf = tbf_qdisc(qdisc);
-	if (tbf && (tbf->qt_mask & TBF_ATTR_MPU))
-		return tbf->qt_mpu;
-	else
-		return -NLE_NOATTR;
-}
-
 static inline int calc_cell_log(int cell, int bucket)
 {
-	if (cell > 0)
 		cell = rtnl_tc_calc_cell_log(cell);
-	else {
-		cell = 0;
-
-		if (!bucket)
-			bucket = 2047; /* defaults to cell_log=3 */
-
-		while ((bucket >> cell) > 255)
-			cell++;
-	}
-
 	return cell;
 }
 
@@ -362,27 +273,25 @@
  * @arg cell		Size of a rate cell or 0 to get default value.
  * @return 0 on success or a negative error code.
  */
-int rtnl_qdisc_tbf_set_rate(struct rtnl_qdisc *qdisc, int rate, int bucket,
+void rtnl_qdisc_tbf_set_rate(struct rtnl_qdisc *qdisc, int rate, int bucket,
 			    int cell)
 {
 	struct rtnl_tbf *tbf;
 	int cell_log;
 	
-	tbf = tbf_alloc(qdisc);
-	if (!tbf)
-		return -NLE_NOMEM;
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
-	cell_log = calc_cell_log(cell, bucket);
-	if (cell_log < 0)
-		return cell_log;
+	if (!cell)
+		cell_log = UINT8_MAX;
+	else
+		cell_log = rtnl_tc_calc_cell_log(cell);
 
 	tbf->qt_rate.rs_rate = rate;
 	tbf->qt_rate_bucket = bucket;
 	tbf->qt_rate.rs_cell_log = cell_log;
-	tbf->qt_rate_txtime = rtnl_tc_calc_txtime(bucket, rate);
+	tbf->qt_rate_txtime = nl_us2ticks(rtnl_tc_calc_txtime(bucket, rate));
 	tbf->qt_mask |= TBF_ATTR_RATE;
-
-	return 0;
 }
 
 /**
@@ -394,8 +303,10 @@
 {
 	struct rtnl_tbf *tbf;
 
-	tbf = tbf_qdisc(qdisc);
-	if (tbf && (tbf->qt_mask & TBF_ATTR_RATE))
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (tbf->qt_mask & TBF_ATTR_RATE)
 		return tbf->qt_rate.rs_rate;
 	else
 		return -1;
@@ -410,8 +321,10 @@
 {
 	struct rtnl_tbf *tbf;
 
-	tbf = tbf_qdisc(qdisc);
-	if (tbf && (tbf->qt_mask & TBF_ATTR_RATE))
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (tbf->qt_mask & TBF_ATTR_RATE)
 		return tbf->qt_rate_bucket;
 	else
 		return -1;
@@ -426,8 +339,10 @@
 {
 	struct rtnl_tbf *tbf;
 
-	tbf = tbf_qdisc(qdisc);
-	if (tbf && (tbf->qt_mask & TBF_ATTR_RATE))
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (tbf->qt_mask & TBF_ATTR_RATE)
 		return (1 << tbf->qt_rate.rs_cell_log);
 	else
 		return -1;
@@ -447,9 +362,8 @@
 	struct rtnl_tbf *tbf;
 	int cell_log;
 	
-	tbf = tbf_alloc(qdisc);
-	if (!tbf)
-		return -NLE_NOMEM;
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
 
 	cell_log = calc_cell_log(cell, bucket);
 	if (cell_log < 0)
@@ -458,7 +372,7 @@
 	tbf->qt_peakrate.rs_rate = rate;
 	tbf->qt_peakrate_bucket = bucket;
 	tbf->qt_peakrate.rs_cell_log = cell_log;
-	tbf->qt_peakrate_txtime = rtnl_tc_calc_txtime(bucket, rate);
+	tbf->qt_peakrate_txtime = nl_us2ticks(rtnl_tc_calc_txtime(bucket, rate));
 	
 	tbf->qt_mask |= TBF_ATTR_PEAKRATE;
 
@@ -474,8 +388,10 @@
 {
 	struct rtnl_tbf *tbf;
 
-	tbf = tbf_qdisc(qdisc);
-	if (tbf && (tbf->qt_mask & TBF_ATTR_PEAKRATE))
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
 		return tbf->qt_peakrate.rs_rate;
 	else
 		return -1;
@@ -490,8 +406,10 @@
 {
 	struct rtnl_tbf *tbf;
 
-	tbf = tbf_qdisc(qdisc);
-	if (tbf && (tbf->qt_mask & TBF_ATTR_PEAKRATE))
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
 		return tbf->qt_peakrate_bucket;
 	else
 		return -1;
@@ -506,8 +424,10 @@
 {
 	struct rtnl_tbf *tbf;
 
-	tbf = tbf_qdisc(qdisc);
-	if (tbf && (tbf->qt_mask & TBF_ATTR_PEAKRATE))
+	if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
 		return (1 << tbf->qt_peakrate.rs_cell_log);
 	else
 		return -1;
@@ -515,25 +435,26 @@
 
 /** @} */
 
-static struct rtnl_qdisc_ops tbf_qdisc_ops = {
-	.qo_kind		= "tbf",
-	.qo_msg_parser		= tbf_msg_parser,
-	.qo_dump = {
+static struct rtnl_tc_ops tbf_tc_ops = {
+	.to_kind		= "tbf",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_tbf),
+	.to_msg_parser		= tbf_msg_parser,
+	.to_dump = {
 	    [NL_DUMP_LINE]	= tbf_dump_line,
 	    [NL_DUMP_DETAILS]	= tbf_dump_details,
 	},
-	.qo_free_data		= tbf_free_data,
-	.qo_get_opts		= tbf_get_opts,
+	.to_msg_fill		= tbf_msg_fill,
 };
 
 static void __init tbf_init(void)
 {
-	rtnl_qdisc_register(&tbf_qdisc_ops);
+	rtnl_tc_register(&tbf_tc_ops);
 }
 
 static void __exit tbf_exit(void)
 {
-	rtnl_qdisc_unregister(&tbf_qdisc_ops);
+	rtnl_tc_unregister(&tbf_tc_ops);
 }
 
 /** @} */
diff --git a/lib/route/qdisc_api.c b/lib/route/qdisc_api.c
deleted file mode 100644
index 089f212..0000000
--- a/lib/route/qdisc_api.c
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * lib/route/qdisc_api.c            Queueing Discipline Module API
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @ingroup qdisc
- * @defgroup qdisc_api Queueing Discipline Modules
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/utils.h>
-#include <netlink/route/link.h>
-#include <netlink/route/tc.h>
-#include <netlink/route/qdisc.h>
-#include <netlink/route/class.h>
-#include <netlink/route/classifier.h>
-#include <netlink/route/qdisc-modules.h>
-
-static struct rtnl_qdisc_ops *qdisc_ops_list;
-
-/**
- * @name Module API
- * @{
- */
-
-/**
- * Register a qdisc module
- * @arg qops		qdisc module operations
- */
-int rtnl_qdisc_register(struct rtnl_qdisc_ops *qops)
-{
-	struct rtnl_qdisc_ops *o, **op;
-
-	if (!qops->qo_kind[0])
-		BUG();
-
-	for (op = &qdisc_ops_list; (o = *op) != NULL; op = &o->qo_next)
-		if (!strcasecmp(qops->qo_kind, o->qo_kind))
-			return -NLE_EXIST;
-
-	qops->qo_next = NULL;
-	*op = qops;
-
-	return 0;
-}
-
-/**
- * Unregister a qdisc module
- * @arg qops		qdisc module operations
- */
-int rtnl_qdisc_unregister(struct rtnl_qdisc_ops *qops)
-{
-	struct rtnl_qdisc_ops *o, **op;
-
-	for (op = &qdisc_ops_list; (o = *op) != NULL; op = &o->qo_next)
-		if (!strcasecmp(qops->qo_kind, o->qo_kind))
-			break;
-
-	if (!o)
-		return -NLE_OBJ_NOTFOUND;
-
-	*op = qops->qo_next;
-
-	return 0;
-}
-
-struct rtnl_qdisc_ops *__rtnl_qdisc_lookup_ops(const char *kind)
-{
-	struct rtnl_qdisc_ops *qops;
-
-	for (qops = qdisc_ops_list; qops; qops = qops->qo_next)
-		if (!strcmp(kind, qops->qo_kind))
-			return qops;
-
-	return NULL;
-}
-
-struct rtnl_qdisc_ops *rtnl_qdisc_lookup_ops(struct rtnl_qdisc *qdisc)
-{
-	if (!qdisc->q_ops)
-		qdisc->q_ops = __rtnl_qdisc_lookup_ops(qdisc->q_kind);
-
-	return qdisc->q_ops;
-}
-
-/** @} */
-
-/** @} */
diff --git a/lib/route/qdisc_obj.c b/lib/route/qdisc_obj.c
deleted file mode 100644
index dc52ae8..0000000
--- a/lib/route/qdisc_obj.c
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * lib/route/qdisc_obj.c            Queueing Discipline Object
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @ingroup qdisc
- * @defgroup qdisc_obj Queueing Discipline Object
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/utils.h>
-#include <netlink/route/link.h>
-#include <netlink/route/tc.h>
-#include <netlink/route/qdisc.h>
-#include <netlink/route/class.h>
-#include <netlink/route/classifier.h>
-#include <netlink/route/qdisc-modules.h>
-
-static void qdisc_free_data(struct nl_object *obj)
-{
-	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) obj;
-	struct rtnl_qdisc_ops *qops;
-
-	tca_free_data((struct rtnl_tca *) qdisc);
-
-	qops = rtnl_qdisc_lookup_ops(qdisc);
-	if (qops && qops->qo_free_data)
-		qops->qo_free_data(qdisc);
-}
-
-static int qdisc_clone(struct nl_object *_dst, struct nl_object *_src)
-{
-	struct rtnl_qdisc *dst = (struct rtnl_qdisc *) _dst;
-	struct rtnl_qdisc *src = (struct rtnl_qdisc *) _src;
-	struct rtnl_qdisc_ops *qops;
-	int err;
-
-	err = tca_clone((struct rtnl_tca *) dst, (struct rtnl_tca *) src);
-	if (err < 0)
-		goto errout;
-
-	qops = rtnl_qdisc_lookup_ops(src);
-	if (qops && qops->qo_clone)
-		err = qops->qo_clone(dst, src);
-errout:
-	return err;
-}
-
-static void qdisc_dump_line(struct nl_object *obj, struct nl_dump_params *p)
-{
-	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) obj;
-	struct rtnl_qdisc_ops *qops;
-	
-	tca_dump_line((struct rtnl_tca *) qdisc, "qdisc", p);
-
-	qops = rtnl_qdisc_lookup_ops(qdisc);
-	if (qops && qops->qo_dump[NL_DUMP_LINE])
-		qops->qo_dump[NL_DUMP_LINE](qdisc, p);
-
-	nl_dump(p, "\n");
-}
-
-static void qdisc_dump_details(struct nl_object *arg, struct nl_dump_params *p)
-{
-	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) arg;
-	struct rtnl_qdisc_ops *qops;
-
-	qdisc_dump_line(arg, p);
-
-	tca_dump_details((struct rtnl_tca *) qdisc, p);
-	nl_dump(p, "refcnt %u ", qdisc->q_info);
-
-	qops = rtnl_qdisc_lookup_ops(qdisc);
-	if (qops && qops->qo_dump[NL_DUMP_DETAILS])
-		qops->qo_dump[NL_DUMP_DETAILS](qdisc, p);
-
-	nl_dump(p, "\n");
-}
-
-static void qdisc_dump_stats(struct nl_object *arg, struct nl_dump_params *p)
-{
-	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) arg;
-	struct rtnl_qdisc_ops *qops;
-
-	qdisc_dump_details(arg, p);
-	tca_dump_stats((struct rtnl_tca *) qdisc, p);
-	nl_dump(p, "\n");
-
-	qops = rtnl_qdisc_lookup_ops(qdisc);
-	if (qops && qops->qo_dump[NL_DUMP_STATS])
-		qops->qo_dump[NL_DUMP_STATS](qdisc, p);
-}
-
-/**
- * @name Allocation/Freeing
- * @{
- */
-
-struct rtnl_qdisc *rtnl_qdisc_alloc(void)
-{
-	return (struct rtnl_qdisc *) nl_object_alloc(&qdisc_obj_ops);
-}
-
-void rtnl_qdisc_put(struct rtnl_qdisc *qdisc)
-{
-	nl_object_put((struct nl_object *) qdisc);
-}
-
-/** @} */
-
-/**
- * @name Iterators
- * @{
- */
-
-/**
- * Call a callback for each child class of a qdisc
- * @arg qdisc		the parent qdisc
- * @arg cache		a class cache including all classes of the interface
- *                      the specified qdisc is attached to
- * @arg cb              callback function
- * @arg arg             argument to be passed to callback function
- */
-void rtnl_qdisc_foreach_child(struct rtnl_qdisc *qdisc, struct nl_cache *cache,
-			      void (*cb)(struct nl_object *, void *), void *arg)
-{
-	struct rtnl_class *filter;
-	
-	filter = rtnl_class_alloc();
-	if (!filter)
-		return;
-
-	rtnl_class_set_parent(filter, qdisc->q_handle);
-	rtnl_class_set_ifindex(filter, qdisc->q_ifindex);
-	rtnl_class_set_kind(filter, qdisc->q_kind);
-
-	nl_cache_foreach_filter(cache, (struct nl_object *) filter, cb, arg);
-
-	rtnl_class_put(filter);
-}
-
-/**
- * Call a callback for each filter attached to the qdisc
- * @arg qdisc		the parent qdisc
- * @arg cache		a filter cache including at least all the filters
- *                      attached to the specified qdisc
- * @arg cb              callback function
- * @arg arg             argument to be passed to callback function
- */
-void rtnl_qdisc_foreach_cls(struct rtnl_qdisc *qdisc, struct nl_cache *cache,
-			    void (*cb)(struct nl_object *, void *), void *arg)
-{
-	struct rtnl_cls *filter;
-
-	filter = rtnl_cls_alloc();
-	if (!filter)
-		return;
-
-	rtnl_cls_set_ifindex(filter, qdisc->q_ifindex);
-	rtnl_cls_set_parent(filter, qdisc->q_parent);
-
-	nl_cache_foreach_filter(cache, (struct nl_object *) filter, cb, arg);
-	rtnl_cls_put(filter);
-}
-
-/** @} */
-
-/**
- * @name Attributes
- * @{
- */
-
-void rtnl_qdisc_set_ifindex(struct rtnl_qdisc *qdisc, int ifindex)
-{
-	tca_set_ifindex((struct rtnl_tca *) qdisc, ifindex);
-}
-
-int rtnl_qdisc_get_ifindex(struct rtnl_qdisc *qdisc)
-{
-	return tca_get_ifindex((struct rtnl_tca *) qdisc);
-}
-
-void rtnl_qdisc_set_handle(struct rtnl_qdisc *qdisc, uint32_t handle)
-{
-	tca_set_handle((struct rtnl_tca *) qdisc, handle);
-}
-
-uint32_t rtnl_qdisc_get_handle(struct rtnl_qdisc *qdisc)
-{
-	return tca_get_handle((struct rtnl_tca *) qdisc);
-}
-
-void rtnl_qdisc_set_parent(struct rtnl_qdisc *qdisc, uint32_t parent)
-{
-	tca_set_parent((struct rtnl_tca *) qdisc, parent);
-}
-
-uint32_t rtnl_qdisc_get_parent(struct rtnl_qdisc *qdisc)
-{
-	return tca_get_parent((struct rtnl_tca *) qdisc);
-}
-
-void rtnl_qdisc_set_kind(struct rtnl_qdisc *qdisc, const char *name)
-{
-	tca_set_kind((struct rtnl_tca *) qdisc, name);
-	qdisc->q_ops = __rtnl_qdisc_lookup_ops(name);
-}
-
-char *rtnl_qdisc_get_kind(struct rtnl_qdisc *qdisc)
-{
-	return tca_get_kind((struct rtnl_tca *) qdisc);
-}
-
-uint64_t rtnl_qdisc_get_stat(struct rtnl_qdisc *qdisc,
-			     enum rtnl_tc_stats_id id)
-{
-	return tca_get_stat((struct rtnl_tca *) qdisc, id);
-}
-
-/** @} */
-
-/**
- * @name Qdisc Specific Options
- * @{
- */
-
-/**
- * Return qdisc specific options for use in TCA_OPTIONS
- * @arg qdisc		qdisc carrying the optiosn
- * 
- * @return new headerless netlink message carrying the options as payload
- */
-struct nl_msg *rtnl_qdisc_get_opts(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_qdisc_ops *ops;
-
-	ops = rtnl_qdisc_lookup_ops(qdisc);
-	if (ops && ops->qo_get_opts)
-		return ops->qo_get_opts(qdisc);
-
-	return NULL;
-}
-
-/** @} */
-
-struct nl_object_ops qdisc_obj_ops = {
-	.oo_name		= "route/qdisc",
-	.oo_size		= sizeof(struct rtnl_qdisc),
-	.oo_free_data		= qdisc_free_data,
-	.oo_clone		= qdisc_clone,
-	.oo_dump = {
-	    [NL_DUMP_LINE]	= qdisc_dump_line,
-	    [NL_DUMP_DETAILS]	= qdisc_dump_details,
-	    [NL_DUMP_STATS]	= qdisc_dump_stats,
-	},
-	.oo_compare		= tca_compare,
-	.oo_id_attrs		= (TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE),
-};
-
-/** @} */
diff --git a/lib/route/route.c b/lib/route/route.c
index c85c225..2985187 100644
--- a/lib/route/route.c
+++ b/lib/route/route.c
@@ -16,7 +16,7 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/utils.h>
@@ -64,13 +64,18 @@
  * @arg sk		Netlink socket.
  * @arg family		Address family of routes to cover or AF_UNSPEC
  * @arg flags		Flags
+ * @arg result		Result pointer
  *
  * Allocates a new cache, initializes it properly and updates it to
  * contain all routes currently configured in the kernel.
  *
+ * Valid flags:
+ *   * ROUTE_CACHE_CONTENT - Cache will contain contents of routing cache
+ *                           instead of actual routes.
+ *
  * @note The caller is responsible for destroying and freeing the
  *       cache after using it.
- * @return The cache or NULL if an error has occured.
+ * @return 0 on success or a negative error code.
  */
 int rtnl_route_alloc_cache(struct nl_sock *sk, int family, int flags,
 			   struct nl_cache **result)
diff --git a/lib/route/route_obj.c b/lib/route/route_obj.c
index 7f26bfd..dd4bf49 100644
--- a/lib/route/route_obj.c
+++ b/lib/route/route_obj.c
@@ -30,11 +30,12 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/cache.h>
 #include <netlink/utils.h>
 #include <netlink/data.h>
+#include <netlink/hashtable.h>
 #include <netlink/route/rtnl.h>
 #include <netlink/route/route.h>
 #include <netlink/route/link.h>
@@ -70,6 +71,7 @@
 	r->rt_table = RT_TABLE_MAIN;
 	r->rt_protocol = RTPROT_STATIC;
 	r->rt_type = RTN_UNICAST;
+	r->rt_prio = 0;
 
 	nl_init_list_head(&r->rt_nexthops);
 }
@@ -110,6 +112,9 @@
 		if (!(dst->rt_pref_src = nl_addr_clone(src->rt_pref_src)))
 			return -NLE_NOMEM;
 
+	/* Will be inc'ed again while adding the nexthops of the source */
+	dst->rt_nr_nh = 0;
+
 	nl_init_list_head(&dst->rt_nexthops);
 	nl_list_for_each_entry(nh, &src->rt_nexthops, rtnh_list) {
 		new = rtnl_route_nh_clone(nh);
@@ -125,12 +130,9 @@
 static void route_dump_line(struct nl_object *a, struct nl_dump_params *p)
 {
 	struct rtnl_route *r = (struct rtnl_route *) a;
-	struct nl_cache *link_cache;
 	int cache = 0, flags;
 	char buf[64];
 
-	link_cache = nl_cache_mngt_require("route/link");
-
 	if (r->rt_flags & RTM_F_CLONED)
 		cache = 1;
 
@@ -206,10 +208,10 @@
 {
 	struct rtnl_route *r = (struct rtnl_route *) a;
 	struct nl_cache *link_cache;
-	char buf[128];
+	char buf[256];
 	int i;
 
-	link_cache = nl_cache_mngt_require("route/link");
+	link_cache = nl_cache_mngt_require_safe("route/link");
 
 	route_dump_line(a, p);
 	nl_dump_line(p, "    ");
@@ -257,7 +259,7 @@
 	if ((r->ce_mask & ROUTE_ATTR_CACHEINFO) && r->rt_cacheinfo.rtci_error) {
 		nl_dump_line(p, "    cacheinfo error %d (%s)\n",
 			r->rt_cacheinfo.rtci_error,
-			strerror(-r->rt_cacheinfo.rtci_error));
+			strerror_r(-r->rt_cacheinfo.rtci_error, buf, sizeof(buf)));
 	}
 
 	if (r->ce_mask & ROUTE_ATTR_METRICS) {
@@ -270,6 +272,9 @@
 					r->rt_metrics[i]);
 		nl_dump(p, "]\n");
 	}
+
+	if (link_cache)
+		nl_cache_put(link_cache);
 }
 
 static void route_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
@@ -284,74 +289,58 @@
 		nl_dump_line(p, "    used %u refcnt %u last-use %us "
 				"expires %us\n",
 			     ci->rtci_used, ci->rtci_clntref,
-			     ci->rtci_last_use / nl_get_hz(),
-			     ci->rtci_expires / nl_get_hz());
+			     ci->rtci_last_use / nl_get_user_hz(),
+			     ci->rtci_expires / nl_get_user_hz());
 	}
 }
 
-static void route_dump_env(struct nl_object *obj, struct nl_dump_params *p)
+static void route_keygen(struct nl_object *obj, uint32_t *hashkey,
+			  uint32_t table_sz)
 {
 	struct rtnl_route *route = (struct rtnl_route *) obj;
-	struct nl_cache *link_cache;
-	char buf[128];
+	unsigned int rkey_sz;
+	struct nl_addr *addr = NULL;
+	struct route_hash_key {
+		uint8_t		rt_family;
+		uint8_t		rt_tos;
+		uint32_t	rt_table;
+		uint32_t	rt_prio;
+		char 		rt_addr[0];
+	} __attribute__((packed)) *rkey;
+#ifdef NL_DEBUG
+	char buf[INET6_ADDRSTRLEN+5];
+#endif
 
-	link_cache = nl_cache_mngt_require("route/link");
+	if (route->rt_dst)
+		addr = route->rt_dst;
 
-	nl_dump_line(p, "ROUTE_FAMILY=%s\n",
-		     nl_af2str(route->rt_family, buf, sizeof(buf)));
-
-	if (route->ce_mask & ROUTE_ATTR_DST)
-		nl_dump_line(p, "ROUTE_DST=%s\n",
-			     nl_addr2str(route->rt_dst, buf, sizeof(buf)));
-
-	if (route->ce_mask & ROUTE_ATTR_SRC)
-		nl_dump_line(p, "ROUTE_SRC=%s\n",
-			     nl_addr2str(route->rt_src, buf, sizeof(buf)));
-
-	if (route->ce_mask & ROUTE_ATTR_PREF_SRC)
-		nl_dump_line(p, "ROUTE_PREFSRC=%s\n",
-			     nl_addr2str(route->rt_pref_src, buf, sizeof(buf)));
-
-	if (route->ce_mask & ROUTE_ATTR_IIF) {
-		if (link_cache) {
-			nl_dump_line(p, "ROUTE_IIF=%s",
-				rtnl_link_i2name(link_cache, route->rt_iif,
-						 buf, sizeof(buf)));
-		} else
-			nl_dump_line(p, "ROUTE_IIF=%d", route->rt_iif);
+	rkey_sz = sizeof(*rkey);
+	if (addr)
+		rkey_sz += nl_addr_get_len(addr);
+	rkey = calloc(1, rkey_sz);
+	if (!rkey) {
+		NL_DBG(2, "Warning: calloc failed for %d bytes...\n", rkey_sz);
+		*hashkey = 0;
+		return;
 	}
+	rkey->rt_family = route->rt_family;
+	rkey->rt_tos = route->rt_tos;
+	rkey->rt_table = route->rt_table;
+	rkey->rt_prio = route->rt_prio;
+	if (addr)
+		memcpy(rkey->rt_addr, nl_addr_get_binary_addr(addr),
+			nl_addr_get_len(addr));
 
-	if (route->ce_mask & ROUTE_ATTR_TOS)
-		nl_dump_line(p, "ROUTE_TOS=%u\n", route->rt_tos);
+	*hashkey = nl_hash(rkey, rkey_sz, 0) % table_sz;
 
-	if (route->ce_mask & ROUTE_ATTR_TABLE)
-		nl_dump_line(p, "ROUTE_TABLE=%u\n",
-			     route->rt_table);
+	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);
 
-	if (route->ce_mask & ROUTE_ATTR_SCOPE)
-		nl_dump_line(p, "ROUTE_SCOPE=%s\n",
-			     rtnl_scope2str(route->rt_scope, buf, sizeof(buf)));
+	free(rkey);
 
-	if (route->ce_mask & ROUTE_ATTR_PRIO)
-		nl_dump_line(p, "ROUTE_PRIORITY=%u\n",
-			     route->rt_prio);
-
-	if (route->ce_mask & ROUTE_ATTR_TYPE)
-		nl_dump_line(p, "ROUTE_TYPE=%s\n",
-			     nl_rtntype2str(route->rt_type, buf, sizeof(buf)));
-
-	if (route->ce_mask & ROUTE_ATTR_MULTIPATH) {
-		struct rtnl_nexthop *nh;
-		int index = 1;
-
-		if (route->rt_nr_nh > 0)
-			nl_dump_line(p, "ROUTE_NR_NH=%u\n", route->rt_nr_nh);
-
-		nl_list_for_each_entry(nh, &route->rt_nexthops, rtnh_list) {
-			p->dp_ivar = index++;
-			rtnl_route_nh_dump(nh, p);
-		}
-	}
+	return;
 }
 
 static int route_compare(struct nl_object *_a, struct nl_object *_b,
@@ -397,13 +386,13 @@
 			if (a->rt_metrics_mask & (1 << i) &&
 			    (!(b->rt_metrics_mask & (1 << i)) ||
 			     a->rt_metrics[i] != b->rt_metrics[i]))
-				ROUTE_DIFF(METRICS, 1);
+				diff |= ROUTE_DIFF(METRICS, 1);
 		}
 
 		diff |= ROUTE_DIFF(FLAGS,
 			  (a->rt_flags ^ b->rt_flags) & b->rt_flag_mask);
 	} else {
-		if (a->rt_nr_nh != a->rt_nr_nh)
+		if (a->rt_nr_nh != b->rt_nr_nh)
 			goto nh_mismatch;
 
 		/* search for a dup in each nh of a */
@@ -411,9 +400,10 @@
 			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_compare(nh_a, nh_b, ~0, 0)) {
 					found = 1;
 					break;
+				}
 			}
 			if (!found)
 				goto nh_mismatch;
@@ -425,9 +415,10 @@
 			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_compare(nh_a, nh_b, ~0, 0)) {
 					found = 1;
 					break;
+				}
 			}
 			if (!found)
 				goto nh_mismatch;
@@ -455,7 +446,104 @@
 #undef ROUTE_DIFF
 }
 
-static struct trans_tbl route_attrs[] = {
+static int route_update(struct nl_object *old_obj, struct nl_object *new_obj)
+{
+	struct rtnl_route *new_route = (struct rtnl_route *) new_obj;
+	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
+	 * separate notifications, one for every nexthop. This update
+	 * function collapses such route msgs into a single
+	 * route with multiple nexthops. The resulting object looks
+	 * similar to a ipv4 ECMP route
+	 */
+	if (new_route->rt_family != AF_INET6 ||
+	    new_route->rt_table == RT_TABLE_LOCAL)
+		return -NLE_OPNOTSUPP;
+
+	/*
+	 * For routes that are already multipath,
+	 * or dont have a nexthop dont do anything
+	 */
+	if (rtnl_route_get_nnexthops(new_route) != 1)
+		return -NLE_OPNOTSUPP;
+
+	/*
+	 * Get the only nexthop entry from the new route. For
+	 * IPv6 we always get a route with a 0th NH
+	 * filled or nothing at all
+	 */
+	new_nh = rtnl_route_nexthop_n(new_route, 0);
+	if (!new_nh || !rtnl_route_nh_get_gateway(new_nh))
+		return -NLE_OPNOTSUPP;
+
+	switch(action) {
+	case RTM_NEWROUTE : {
+		struct rtnl_nexthop *cloned_nh;
+
+		/*
+		 * Add the nexthop to old route
+		 */
+		cloned_nh = rtnl_route_nh_clone(new_nh);
+		if (!cloned_nh)
+			return -NLE_NOMEM;
+		rtnl_route_add_nexthop(old_route, cloned_nh);
+
+		NL_DBG(2, "Route obj %p updated. Added "
+			"nexthop %p via %s\n", old_route, cloned_nh,
+			nl_addr2str(cloned_nh->rtnh_gateway, buf,
+					sizeof(buf)));
+	}
+		break;
+	case RTM_DELROUTE : {
+		struct rtnl_nexthop *old_nh;
+
+		/*
+		 * Only take care of nexthop deletes and not
+		 * route deletes. So, if there is only one nexthop
+		 * quite likely we did not update it. So dont do
+		 * anything and return
+		 */
+		if (rtnl_route_get_nnexthops(old_route) <= 1)
+			return -NLE_OPNOTSUPP;
+
+		/*
+		 * Find the next hop in old route and delete it
+		 */
+		nl_list_for_each_entry(old_nh, &old_route->rt_nexthops,
+			rtnh_list) {
+			if (!rtnl_route_nh_compare(old_nh, new_nh, ~0, 0)) {
+
+				rtnl_route_remove_nexthop(old_route, old_nh);
+
+				NL_DBG(2, "Route obj %p updated. Removed "
+					"nexthop %p via %s\n", old_route,
+					old_nh,
+					nl_addr2str(old_nh->rtnh_gateway, buf,
+					sizeof(buf)));
+
+				rtnl_route_nh_free(old_nh);
+				break;
+			}
+		}
+	}
+		break;
+	default:
+		NL_DBG(2, "Unknown action associated "
+			"to object %p during route update\n", new_obj);
+		return -NLE_OPNOTSUPP;
+	}
+
+	return NLE_SUCCESS;
+}
+
+static const struct trans_tbl route_attrs[] = {
 	__ADD(ROUTE_ATTR_FAMILY, family)
 	__ADD(ROUTE_ATTR_TOS, tos)
 	__ADD(ROUTE_ATTR_TABLE, table)
@@ -752,18 +840,26 @@
 
 void rtnl_route_remove_nexthop(struct rtnl_route *route, struct rtnl_nexthop *nh)
 {
-	route->rt_nr_nh--;
-	nl_list_del(&nh->rtnh_list);
+	if (route->ce_mask & ROUTE_ATTR_MULTIPATH) {
+		route->rt_nr_nh--;
+		nl_list_del(&nh->rtnh_list);
+	}
 }
 
 struct nl_list_head *rtnl_route_get_nexthops(struct rtnl_route *route)
 {
-	return &route->rt_nexthops;
+	if (route->ce_mask & ROUTE_ATTR_MULTIPATH)
+		return &route->rt_nexthops;
+
+	return NULL;
 }
 
 int rtnl_route_get_nnexthops(struct rtnl_route *route)
 {
-	return route->rt_nr_nh;
+	if (route->ce_mask & ROUTE_ATTR_MULTIPATH)
+		return route->rt_nr_nh;
+
+	return 0;
 }
 
 void rtnl_route_foreach_nexthop(struct rtnl_route *r,
@@ -782,7 +878,7 @@
 struct rtnl_nexthop *rtnl_route_nexthop_n(struct rtnl_route *r, int n)
 {
 	struct rtnl_nexthop *nh;
-	int i;
+	uint32_t i;
     
 	if (r->ce_mask & ROUTE_ATTR_MULTIPATH && r->rt_nr_nh > n) {
 		i = 0;
@@ -937,11 +1033,12 @@
 	route->rt_scope = rtm->rtm_scope;
 	route->rt_protocol = rtm->rtm_protocol;
 	route->rt_flags = rtm->rtm_flags;
+	route->rt_prio = 0;
 
 	route->ce_mask |= ROUTE_ATTR_FAMILY | ROUTE_ATTR_TOS |
 			  ROUTE_ATTR_TABLE | ROUTE_ATTR_TYPE |
 			  ROUTE_ATTR_SCOPE | ROUTE_ATTR_PROTOCOL |
-			  ROUTE_ATTR_FLAGS;
+			  ROUTE_ATTR_FLAGS | ROUTE_ATTR_PRIO;
 
 	if (tb[RTA_DST]) {
 		if (!(dst = nl_addr_alloc_attr(tb[RTA_DST], family)))
@@ -972,6 +1069,9 @@
 		nl_addr_put(src);
 	}
 
+	if (tb[RTA_TABLE])
+		rtnl_route_set_table(route, nla_get_u32(tb[RTA_TABLE]));
+
 	if (tb[RTA_IIF])
 		rtnl_route_set_iif(route, nla_get_u32(tb[RTA_IIF]));
 
@@ -1038,6 +1138,7 @@
 	}
 
 	if (old_nh) {
+		rtnl_route_nh_set_flags(old_nh, rtm->rtm_flags & 0xff);
 		if (route->rt_nr_nh == 0) {
 			/* If no nexthops have been provided via RTA_MULTIPATH
 			 * we add it as regular nexthop to maintain backwards
@@ -1097,10 +1198,15 @@
 	if (route->rt_src)
 		rtmsg.rtm_src_len = nl_addr_get_prefixlen(route->rt_src);
 
-
-	if (rtmsg.rtm_scope == RT_SCOPE_NOWHERE)
+	if (!(route->ce_mask & ROUTE_ATTR_SCOPE))
 		rtmsg.rtm_scope = rtnl_route_guess_scope(route);
 
+	if (rtnl_route_get_nnexthops(route) == 1) {
+		struct rtnl_nexthop *nh;
+		nh = rtnl_route_nexthop_n(route, 0);
+		rtmsg.rtm_flags |= nh->rtnh_flags;
+	}
+
 	if (nlmsg_append(msg, &rtmsg, sizeof(rtmsg), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
 
@@ -1136,7 +1242,17 @@
 		nla_nest_end(msg, metrics);
 	}
 
-	if (rtnl_route_get_nnexthops(route) > 0) {
+	if (rtnl_route_get_nnexthops(route) == 1) {
+		struct rtnl_nexthop *nh;
+
+		nh = rtnl_route_nexthop_n(route, 0);
+		if (nh->rtnh_gateway)
+			NLA_PUT_ADDR(msg, RTA_GATEWAY, nh->rtnh_gateway);
+		if (nh->rtnh_ifindex)
+			NLA_PUT_U32(msg, RTA_OIF, nh->rtnh_ifindex);
+		if (nh->rtnh_realms)
+			NLA_PUT_U32(msg, RTA_FLOW, nh->rtnh_realms);
+	} else if (rtnl_route_get_nnexthops(route) > 1) {
 		struct nlattr *multipath;
 		struct rtnl_nexthop *nh;
 
@@ -1185,12 +1301,14 @@
 	    [NL_DUMP_LINE]	= route_dump_line,
 	    [NL_DUMP_DETAILS]	= route_dump_details,
 	    [NL_DUMP_STATS]	= route_dump_stats,
-	    [NL_DUMP_ENV]	= route_dump_env,
 	},
 	.oo_compare		= route_compare,
+	.oo_keygen		= route_keygen,
+	.oo_update		= route_update,
 	.oo_attrs2str		= route_attrs2str,
 	.oo_id_attrs		= (ROUTE_ATTR_FAMILY | ROUTE_ATTR_TOS |
-				   ROUTE_ATTR_TABLE | ROUTE_ATTR_DST),
+				   ROUTE_ATTR_TABLE | ROUTE_ATTR_DST |
+				   ROUTE_ATTR_PRIO),
 };
 /** @endcond */
 
diff --git a/lib/route/route_utils.c b/lib/route/route_utils.c
index 41ae65c..a5b3966 100644
--- a/lib/route/route_utils.c
+++ b/lib/route/route_utils.c
@@ -37,7 +37,7 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/rtnl.h>
@@ -58,6 +58,7 @@
 static void __init init_routing_table_names(void)
 {
 	add_routing_table_name(RT_TABLE_UNSPEC, "unspec");
+	add_routing_table_name(RT_TABLE_COMPAT, "compat");
 	add_routing_table_name(RT_TABLE_DEFAULT, "default");
 	add_routing_table_name(RT_TABLE_MAIN, "main");
 	add_routing_table_name(RT_TABLE_LOCAL, "local");
@@ -138,7 +139,7 @@
  * @{
  */
 
-static struct trans_tbl route_metrices[] = {
+static const struct trans_tbl route_metrices[] = {
 	__ADD(RTAX_UNSPEC, unspec)
 	__ADD(RTAX_LOCK, lock)
 	__ADD(RTAX_MTU, mtu)
diff --git a/lib/route/rtnl.c b/lib/route/rtnl.c
index 2533674..6a55ca1 100644
--- a/lib/route/rtnl.c
+++ b/lib/route/rtnl.c
@@ -6,15 +6,15 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @defgroup rtnl Routing Family
+ * @defgroup rtnl Routing Library (libnl-route)
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/rtnl.h>
@@ -34,15 +34,20 @@
  * Fills out a routing netlink request message and sends it out
  * using nl_send_simple().
  *
- * @return 0 on success or a negative error code.
+ * @return 0 on success or a negative error code. Due to a bug in
+ * older versions, this returned the number of bytes sent. So for
+ * compatibility, treat positive return values as success too.
  */
 int nl_rtgen_request(struct nl_sock *sk, int type, int family, int flags)
 {
+	int err;
 	struct rtgenmsg gmsg = {
 		.rtgen_family = family,
 	};
 
-	return nl_send_simple(sk, type, flags, &gmsg, sizeof(gmsg));
+	err = nl_send_simple(sk, type, flags, &gmsg, sizeof(gmsg));
+
+	return err >= 0 ? 0 : err;
 }
 
 /** @} */
@@ -52,7 +57,7 @@
  * @{
  */
 
-static struct trans_tbl rtntypes[] = {
+static const struct trans_tbl rtntypes[] = {
 	__ADD(RTN_UNSPEC,unspec)
 	__ADD(RTN_UNICAST,unicast)
 	__ADD(RTN_LOCAL,local)
@@ -84,12 +89,12 @@
  * @{
  */
 
-static struct trans_tbl scopes[] = {
+static const struct trans_tbl scopes[] = {
 	__ADD(255,nowhere)
 	__ADD(254,host)
 	__ADD(253,link)
 	__ADD(200,site)
-	__ADD(0,universe)
+	__ADD(0,global)
 };
 
 char *rtnl_scope2str(int scope, char *buf, size_t size)
diff --git a/lib/route/rule.c b/lib/route/rule.c
index 126e96d..b2161a2 100644
--- a/lib/route/rule.c
+++ b/lib/route/rule.c
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
@@ -16,7 +16,7 @@
  * @{
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/rtnl.h>
@@ -25,18 +25,19 @@
 
 /** @cond SKIP */
 #define RULE_ATTR_FAMILY	0x0001
-#define RULE_ATTR_PRIO		0x0002
-#define RULE_ATTR_MARK		0x0004
-#define RULE_ATTR_IIF		0x0008
-#define RULE_ATTR_REALMS	0x0010
-#define RULE_ATTR_SRC		0x0020
-#define RULE_ATTR_DST		0x0040
-#define RULE_ATTR_DSFIELD	0x0080
-#define RULE_ATTR_TABLE		0x0100
-#define RULE_ATTR_TYPE		0x0200
-#define RULE_ATTR_SRC_LEN	0x0400
-#define RULE_ATTR_DST_LEN	0x0800
-#define RULE_ATTR_SRCMAP	0x1000
+#define RULE_ATTR_TABLE		0x0002
+#define RULE_ATTR_ACTION	0x0004
+#define RULE_ATTR_FLAGS		0x0008
+#define RULE_ATTR_IIFNAME	0x0010
+#define RULE_ATTR_OIFNAME	0x0020
+#define RULE_ATTR_PRIO		0x0040
+#define RULE_ATTR_MARK		0x0080
+#define RULE_ATTR_MASK		0x0100
+#define RULE_ATTR_GOTO		0x0200
+#define RULE_ATTR_SRC		0x0400
+#define RULE_ATTR_DST		0x0800
+#define RULE_ATTR_DSFIELD	0x1000
+#define RULE_ATTR_FLOW		0x2000
 
 static struct nl_cache_ops rtnl_rule_ops;
 static struct nl_object_ops rule_obj_ops;
@@ -69,20 +70,23 @@
 	return 0;
 }
 
-static struct nla_policy rule_policy[RTA_MAX+1] = {
-	[RTA_PRIORITY]	= { .type = NLA_U32 },
-	[RTA_FLOW]	= { .type = NLA_U32 },
-	[RTA_PROTOINFO]	= { .type = NLA_U32 },
-	[RTA_IIF]	= { .type = NLA_STRING,
-			    .maxlen = IFNAMSIZ, },
+static struct nla_policy rule_policy[FRA_MAX+1] = {
+	[FRA_TABLE]	= { .type = NLA_U32 },
+	[FRA_IIFNAME]	= { .type = NLA_STRING, .maxlen = IFNAMSIZ },
+	[FRA_OIFNAME]	= { .type = NLA_STRING, .maxlen = IFNAMSIZ },
+	[FRA_PRIORITY]	= { .type = NLA_U32 },
+	[FRA_FWMARK]	= { .type = NLA_U32 },
+	[FRA_FWMASK]	= { .type = NLA_U32 },
+	[FRA_GOTO]	= { .type = NLA_U32 },
+	[FRA_FLOW]	= { .type = NLA_U32 },
 };
 
 static int rule_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
 			   struct nlmsghdr *n, struct nl_parser_param *pp)
 {
 	struct rtnl_rule *rule;
-	struct rtmsg *r;
-	struct nlattr *tb[RTA_MAX+1];
+	struct fib_rule_hdr *frh;
+	struct nlattr *tb[FRA_MAX+1];
 	int err = 1, family;
 
 	rule = rtnl_rule_alloc();
@@ -92,67 +96,81 @@
 	}
 
 	rule->ce_msgtype = n->nlmsg_type;
-	r = nlmsg_data(n);
+	frh = nlmsg_data(n);
 
-	err = nlmsg_parse(n, sizeof(*r), tb, RTA_MAX, rule_policy);
+	err = nlmsg_parse(n, sizeof(*frh), tb, FRA_MAX, rule_policy);
 	if (err < 0)
 		goto errout;
 
-	rule->r_family = family = r->rtm_family;
-	rule->r_type = r->rtm_type;
-	rule->r_dsfield = r->rtm_tos;
-	rule->r_src_len = r->rtm_src_len;
-	rule->r_dst_len = r->rtm_dst_len;
-	rule->r_table = r->rtm_table;
-	rule->ce_mask = (RULE_ATTR_FAMILY | RULE_ATTR_TYPE | RULE_ATTR_DSFIELD |
-			 RULE_ATTR_SRC_LEN | RULE_ATTR_DST_LEN |RULE_ATTR_TYPE |
-			 RULE_ATTR_TABLE);
+	rule->r_family = family = frh->family;
+	rule->r_table = frh->table;
+	rule->r_action = frh->action;
+	rule->r_flags = frh->flags;
 
-	if (tb[RTA_PRIORITY]) {
-		rule->r_prio = nla_get_u32(tb[RTA_PRIORITY]);
+	rule->ce_mask = (RULE_ATTR_FAMILY | RULE_ATTR_TABLE | RULE_ATTR_ACTION |
+			 RULE_ATTR_FLAGS);
+
+	/* ipv4 only */
+	if (frh->tos) {
+		rule->r_dsfield = frh->tos;
+		rule->ce_mask |= RULE_ATTR_DSFIELD;
+	}
+
+	if (tb[FRA_TABLE]) {
+		rule->r_table = nla_get_u32(tb[FRA_TABLE]);
+		rule->ce_mask |= RULE_ATTR_TABLE;
+	}
+
+	if (tb[FRA_IIFNAME]) {
+		nla_strlcpy(rule->r_iifname, tb[FRA_IIFNAME], IFNAMSIZ);
+		rule->ce_mask |= RULE_ATTR_IIFNAME;
+	}
+
+	if (tb[FRA_OIFNAME]) {
+		nla_strlcpy(rule->r_oifname, tb[FRA_OIFNAME], IFNAMSIZ);
+		rule->ce_mask |= RULE_ATTR_OIFNAME;
+	}
+
+	if (tb[FRA_PRIORITY]) {
+		rule->r_prio = nla_get_u32(tb[FRA_PRIORITY]);
 		rule->ce_mask |= RULE_ATTR_PRIO;
 	}
 
-	if (tb[RTA_SRC]) {
-		if (!(rule->r_src = nl_addr_alloc_attr(tb[RTA_SRC], family)))
-			goto errout_enomem;
-		nl_addr_set_prefixlen(rule->r_src, r->rtm_src_len);
-		rule->ce_mask |= RULE_ATTR_SRC;
-	}
-
-	if (tb[RTA_DST]) {
-		if (!(rule->r_dst = nl_addr_alloc_attr(tb[RTA_DST], family)))
-			goto errout_enomem;
-		nl_addr_set_prefixlen(rule->r_dst, r->rtm_dst_len);
-		rule->ce_mask |= RULE_ATTR_DST;
-	}
-
-	if (tb[RTA_PROTOINFO]) {
-		rule->r_mark = nla_get_u32(tb[RTA_PROTOINFO]);
+	if (tb[FRA_FWMARK]) {
+		rule->r_mark = nla_get_u32(tb[FRA_FWMARK]);
 		rule->ce_mask |= RULE_ATTR_MARK;
 	}
 
-	if (tb[RTA_IIF]) {
-		nla_strlcpy(rule->r_iif, tb[RTA_IIF], IFNAMSIZ);
-		rule->ce_mask |= RULE_ATTR_IIF;
+	if (tb[FRA_FWMASK]) {
+		rule->r_mask = nla_get_u32(tb[FRA_FWMASK]);
+		rule->ce_mask |= RULE_ATTR_MASK;
 	}
 
-	if (tb[RTA_FLOW]) {
-		rule->r_realms = nla_get_u32(tb[RTA_FLOW]);
-		rule->ce_mask |= RULE_ATTR_REALMS;
+	if (tb[FRA_GOTO]) {
+		rule->r_goto = nla_get_u32(tb[FRA_GOTO]);
+		rule->ce_mask |= RULE_ATTR_GOTO;
 	}
 
-	if (tb[RTA_GATEWAY]) {
-		rule->r_srcmap = nl_addr_alloc_attr(tb[RTA_GATEWAY], family);
-		if (!rule->r_srcmap)
+	if (tb[FRA_SRC]) {
+		if (!(rule->r_src = nl_addr_alloc_attr(tb[FRA_SRC], family)))
 			goto errout_enomem;
-		rule->ce_mask |= RULE_ATTR_SRCMAP;
+
+		nl_addr_set_prefixlen(rule->r_src, frh->src_len);
+		rule->ce_mask |= RULE_ATTR_SRC;
 	}
 
-	if (tb[RTA_TABLE]) {
-            rule->r_table = nla_get_u32(tb[RTA_TABLE]);
-            rule->ce_mask |= RULE_ATTR_TABLE;
-        }
+	if (tb[FRA_DST]) {
+		if (!(rule->r_dst = nl_addr_alloc_attr(tb[FRA_DST], family)))
+			goto errout_enomem;
+		nl_addr_set_prefixlen(rule->r_dst, frh->dst_len);
+		rule->ce_mask |= RULE_ATTR_DST;
+	}
+
+	/* ipv4 only */
+	if (tb[FRA_FLOW]) {
+		rule->r_flow = nla_get_u32(tb[FRA_FLOW]);
+		rule->ce_mask |= RULE_ATTR_FLOW;
+	}
 
 	err = pp->pp_cb((struct nl_object *) rule, pp);
 errout:
@@ -180,46 +198,44 @@
 	if (r->ce_mask & RULE_ATTR_SRC)
 		nl_dump(p, "from %s ",
 			nl_addr2str(r->r_src, buf, sizeof(buf)));
-	else if (r->ce_mask & RULE_ATTR_SRC_LEN && r->r_src_len)
-		nl_dump(p, "from 0/%d ", r->r_src_len);
 
 	if (r->ce_mask & RULE_ATTR_DST)
 		nl_dump(p, "to %s ",
 			nl_addr2str(r->r_dst, buf, sizeof(buf)));
-	else if (r->ce_mask & RULE_ATTR_DST_LEN && r->r_dst_len)
-		nl_dump(p, "to 0/%d ", r->r_dst_len);
 
-	if (r->ce_mask & RULE_ATTR_DSFIELD && r->r_dsfield)
-		nl_dump(p, "tos %d ", r->r_dsfield);
+	if (r->ce_mask & RULE_ATTR_DSFIELD)
+		nl_dump(p, "tos %u ", r->r_dsfield);
 
-	if (r->ce_mask & RULE_ATTR_MARK)
-		nl_dump(p, "mark %" PRIx64 , r->r_mark);
+	if (r->ce_mask & (RULE_ATTR_MARK | RULE_ATTR_MASK))
+		nl_dump(p, "mark %#x/%#x", r->r_mark, r->r_mask);
 
-	if (r->ce_mask & RULE_ATTR_IIF)
-		nl_dump(p, "iif %s ", r->r_iif);
+	if (r->ce_mask & RULE_ATTR_IIFNAME)
+		nl_dump(p, "iif %s ", r->r_iifname);
+
+	if (r->ce_mask & RULE_ATTR_OIFNAME)
+		nl_dump(p, "oif %s ", r->r_oifname);
 
 	if (r->ce_mask & RULE_ATTR_TABLE)
 		nl_dump(p, "lookup %s ",
 			rtnl_route_table2str(r->r_table, buf, sizeof(buf)));
 
-	if (r->ce_mask & RULE_ATTR_REALMS)
-		nl_dump(p, "realms %s ",
-			rtnl_realms2str(r->r_realms, buf, sizeof(buf)));
+	if (r->ce_mask & RULE_ATTR_FLOW)
+		nl_dump(p, "flow %s ",
+			rtnl_realms2str(r->r_flow, buf, sizeof(buf)));
 
-	nl_dump(p, "action %s\n",
-		nl_rtntype2str(r->r_type, buf, sizeof(buf)));
+	if (r->ce_mask & RULE_ATTR_GOTO)
+		nl_dump(p, "goto %u ", r->r_goto);
+
+	if (r->ce_mask & RULE_ATTR_ACTION)
+		nl_dump(p, "action %s",
+			nl_rtntype2str(r->r_action, buf, sizeof(buf)));
+
+	nl_dump(p, "\n");
 }
 
 static void rule_dump_details(struct nl_object *obj, struct nl_dump_params *p)
 {
-	struct rtnl_rule *rule = (struct rtnl_rule *) obj;
-	char buf[128];
-
 	rule_dump_line(obj, p);
-
-	if (rule->ce_mask & RULE_ATTR_SRCMAP)
-		nl_dump_line(p, "  srcmap %s\n",
-			nl_addr2str(rule->r_srcmap, buf, sizeof(buf)));
 }
 
 static void rule_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
@@ -227,52 +243,7 @@
 	rule_dump_details(obj, p);
 }
 
-static void rule_dump_env(struct nl_object *obj, struct nl_dump_params *p)
-{
-	struct rtnl_rule *rule = (struct rtnl_rule *) obj;
-	char buf[128];
-
-	nl_dump_line(p, "RULE_PRIORITY=%u\n", rule->r_prio);
-	nl_dump_line(p, "RULE_FAMILY=%s\n",
-		     nl_af2str(rule->r_family, buf, sizeof(buf)));
-
-	if (rule->ce_mask & RULE_ATTR_DST)
-		nl_dump_line(p, "RULE_DST=%s\n",
-			     nl_addr2str(rule->r_dst, buf, sizeof(buf)));
-
-	if (rule->ce_mask & RULE_ATTR_DST_LEN)
-		nl_dump_line(p, "RULE_DSTLEN=%u\n", rule->r_dst_len);
-
-	if (rule->ce_mask & RULE_ATTR_SRC)
-		nl_dump_line(p, "RULE_SRC=%s\n",
-			     nl_addr2str(rule->r_src, buf, sizeof(buf)));
-
-	if (rule->ce_mask & RULE_ATTR_SRC_LEN)
-		nl_dump_line(p, "RULE_SRCLEN=%u\n", rule->r_src_len);
-
-	if (rule->ce_mask & RULE_ATTR_IIF)
-		nl_dump_line(p, "RULE_IIF=%s\n", rule->r_iif);
-
-	if (rule->ce_mask & RULE_ATTR_TABLE)
-		nl_dump_line(p, "RULE_TABLE=%u\n", rule->r_table);
-
-	if (rule->ce_mask & RULE_ATTR_REALMS)
-		nl_dump_line(p, "RULE_REALM=%u\n", rule->r_realms);
-
-	if (rule->ce_mask & RULE_ATTR_MARK)
-		nl_dump_line(p, "RULE_MARK=0x%" PRIx64 "\n", rule->r_mark);
-
-	if (rule->ce_mask & RULE_ATTR_DSFIELD)
-		nl_dump_line(p, "RULE_DSFIELD=%u\n", rule->r_dsfield);
-
-	if (rule->ce_mask & RULE_ATTR_TYPE)
-		nl_dump_line(p, "RULE_TYPE=%s\n",
-			     nl_rtntype2str(rule->r_type, buf, sizeof(buf)));
-
-	if (rule->ce_mask & RULE_ATTR_SRCMAP)
-		nl_dump_line(p, "RULE_SRCMAP=%s\n",
-			     nl_addr2str(rule->r_srcmap, buf, sizeof(buf)));
-}
+#define RULE_ATTR_FLAGS		0x0008
 
 static int rule_compare(struct nl_object *_a, struct nl_object *_b,
 			uint32_t attrs, int flags)
@@ -285,36 +256,37 @@
 
 	diff |= RULE_DIFF(FAMILY,	a->r_family != b->r_family);
 	diff |= RULE_DIFF(TABLE,	a->r_table != b->r_table);
-	diff |= RULE_DIFF(REALMS,	a->r_realms != b->r_realms);
-	diff |= RULE_DIFF(DSFIELD,	a->r_dsfield != b->r_dsfield);
-	diff |= RULE_DIFF(TYPE,		a->r_type != b->r_type);
+	diff |= RULE_DIFF(ACTION,	a->r_action != b->r_action);
+	diff |= RULE_DIFF(IIFNAME,	strcmp(a->r_iifname, b->r_iifname));
+	diff |= RULE_DIFF(OIFNAME,	strcmp(a->r_oifname, b->r_oifname));
 	diff |= RULE_DIFF(PRIO,		a->r_prio != b->r_prio);
 	diff |= RULE_DIFF(MARK,		a->r_mark != b->r_mark);
-	diff |= RULE_DIFF(SRC_LEN,	a->r_src_len != b->r_src_len);
-	diff |= RULE_DIFF(DST_LEN,	a->r_dst_len != b->r_dst_len);
+	diff |= RULE_DIFF(MASK,		a->r_mask != b->r_mask);
+	diff |= RULE_DIFF(GOTO,		a->r_goto != b->r_goto);
 	diff |= RULE_DIFF(SRC,		nl_addr_cmp(a->r_src, b->r_src));
 	diff |= RULE_DIFF(DST,		nl_addr_cmp(a->r_dst, b->r_dst));
-	diff |= RULE_DIFF(IIF,		strcmp(a->r_iif, b->r_iif));
+	diff |= RULE_DIFF(DSFIELD,	a->r_dsfield != b->r_dsfield);
+	diff |= RULE_DIFF(FLOW,		a->r_flow != b->r_flow);
 	
 #undef RULE_DIFF
 
 	return diff;
 }
 
-static struct trans_tbl rule_attrs[] = {
+static const struct trans_tbl rule_attrs[] = {
 	__ADD(RULE_ATTR_FAMILY, family)
+	__ADD(RULE_ATTR_TABLE, table)
+	__ADD(RULE_ATTR_ACTION, action)
+	__ADD(RULE_ATTR_IIFNAME, iifname)
+	__ADD(RULE_ATTR_OIFNAME, oifname)
 	__ADD(RULE_ATTR_PRIO, prio)
 	__ADD(RULE_ATTR_MARK, mark)
-	__ADD(RULE_ATTR_IIF, iif)
-	__ADD(RULE_ATTR_REALMS, realms)
+	__ADD(RULE_ATTR_MASK, mask)
+	__ADD(RULE_ATTR_GOTO, goto)
 	__ADD(RULE_ATTR_SRC, src)
 	__ADD(RULE_ATTR_DST, dst)
 	__ADD(RULE_ATTR_DSFIELD, dsfield)
-	__ADD(RULE_ATTR_TABLE, table)
-	__ADD(RULE_ATTR_TYPE, type)
-	__ADD(RULE_ATTR_SRC_LEN, src_len)
-	__ADD(RULE_ATTR_DST_LEN, dst_len)
-	__ADD(RULE_ATTR_SRCMAP, srcmap)
+	__ADD(RULE_ATTR_FLOW, flow)
 };
 
 static char *rule_attrs2str(int attrs, char *buf, size_t len)
@@ -347,7 +319,7 @@
 
 /**
  * Build a rule cache including all rules currently configured in the kernel.
- * @arg sk		Netlink socket.
+ * @arg sock		Netlink socket.
  * @arg family		Address family or AF_UNSPEC.
  * @arg result		Pointer to store resulting cache.
  *
@@ -387,55 +359,61 @@
 			  struct nl_msg **result)
 {
 	struct nl_msg *msg;
-	struct rtmsg rtm = {
-		.rtm_type = RTN_UNSPEC
+	struct fib_rule_hdr frh = {
+		.family = tmpl->r_family,
+		.table = tmpl->r_table,
+		.action = tmpl->r_action,
+		.flags = tmpl->r_flags,
+		.tos = tmpl->r_dsfield,
 	};
 
-	if (cmd == RTM_NEWRULE)
-		rtm.rtm_type = RTN_UNICAST;
-		
-	if (tmpl->ce_mask & RULE_ATTR_FAMILY)
-		rtm.rtm_family = tmpl->r_family;
-
-	if (tmpl->ce_mask & RULE_ATTR_TABLE)
-		rtm.rtm_table = tmpl->r_table;
-
-	if (tmpl->ce_mask & RULE_ATTR_DSFIELD)
-		rtm.rtm_tos = tmpl->r_dsfield;
-
-	if (tmpl->ce_mask & RULE_ATTR_TYPE)
-		rtm.rtm_type = tmpl->r_type;
-
-	if (tmpl->ce_mask & RULE_ATTR_SRC_LEN)
-		rtm.rtm_src_len = tmpl->r_src_len;
-
-	if (tmpl->ce_mask & RULE_ATTR_DST_LEN)
-		rtm.rtm_dst_len = tmpl->r_dst_len;
+	if (!(tmpl->ce_mask & RULE_ATTR_FAMILY))
+		return -NLE_MISSING_ATTR;
 
 	msg = nlmsg_alloc_simple(cmd, flags);
 	if (!msg)
 		return -NLE_NOMEM;
 
-	if (nlmsg_append(msg, &rtm, sizeof(rtm), NLMSG_ALIGNTO) < 0)
-		goto nla_put_failure;
-
-	if (tmpl->ce_mask & RULE_ATTR_SRC)
-		NLA_PUT_ADDR(msg, RTA_SRC, tmpl->r_src);
+	if (tmpl->ce_mask & RULE_ATTR_SRC) 
+		frh.src_len = nl_addr_get_prefixlen(tmpl->r_src);
 
 	if (tmpl->ce_mask & RULE_ATTR_DST)
-		NLA_PUT_ADDR(msg, RTA_DST, tmpl->r_dst);
+		frh.dst_len = nl_addr_get_prefixlen(tmpl->r_dst);
+
+	if (nlmsg_append(msg, &frh, sizeof(frh), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	/* Additional table attribute replacing the 8bit in the header, was
+	 * required to allow more than 256 tables. */
+	NLA_PUT_U32(msg, FRA_TABLE, tmpl->r_table);
+
+	if (tmpl->ce_mask & RULE_ATTR_SRC)
+		NLA_PUT_ADDR(msg, FRA_SRC, tmpl->r_src);
+
+	if (tmpl->ce_mask & RULE_ATTR_DST) 
+		NLA_PUT_ADDR(msg, FRA_DST, tmpl->r_dst);
+
+	if (tmpl->ce_mask & RULE_ATTR_IIFNAME)
+		NLA_PUT_STRING(msg, FRA_IIFNAME, tmpl->r_iifname);
+
+	if (tmpl->ce_mask & RULE_ATTR_OIFNAME)
+		NLA_PUT_STRING(msg, FRA_OIFNAME, tmpl->r_oifname);
 
 	if (tmpl->ce_mask & RULE_ATTR_PRIO)
-		NLA_PUT_U32(msg, RTA_PRIORITY, tmpl->r_prio);
+		NLA_PUT_U32(msg, FRA_PRIORITY, tmpl->r_prio);
 
 	if (tmpl->ce_mask & RULE_ATTR_MARK)
-		NLA_PUT_U32(msg, RTA_PROTOINFO, tmpl->r_mark);
+		NLA_PUT_U32(msg, FRA_FWMARK, tmpl->r_mark);
 
-	if (tmpl->ce_mask & RULE_ATTR_REALMS)
-		NLA_PUT_U32(msg, RTA_FLOW, tmpl->r_realms);
+	if (tmpl->ce_mask & RULE_ATTR_MASK)
+		NLA_PUT_U32(msg, FRA_FWMASK, tmpl->r_mask);
 
-	if (tmpl->ce_mask & RULE_ATTR_IIF)
-		NLA_PUT_STRING(msg, RTA_IIF, tmpl->r_iif);
+	if (tmpl->ce_mask & RULE_ATTR_GOTO)
+		NLA_PUT_U32(msg, FRA_GOTO, tmpl->r_goto);
+
+	if (tmpl->ce_mask & RULE_ATTR_FLOW)
+		NLA_PUT_U32(msg, FRA_FLOW, tmpl->r_flow);
+
 
 	*result = msg;
 	return 0;
@@ -449,6 +427,7 @@
  * Build netlink request message to add a new rule
  * @arg tmpl		template with data of new rule
  * @arg flags		additional netlink message flags
+ * @arg result		Result pointer
  *
  * Builds a new netlink message requesting a addition of a new
  * rule. The netlink message header isn't fully equipped with
@@ -456,7 +435,7 @@
  * or supplemented as needed. \a tmpl must contain the attributes of the new
  * address set via \c rtnl_rule_set_* functions.
  * 
- * @return The netlink message
+ * @return 0 on success or a negative error code.
  */
 int rtnl_rule_build_add_request(struct rtnl_rule *tmpl, int flags,
 				struct nl_msg **result)
@@ -504,6 +483,7 @@
  * Build a netlink request message to delete a rule
  * @arg rule		rule to delete
  * @arg flags		additional netlink message flags
+ * @arg result		Result pointer
  *
  * Builds a new netlink message requesting a deletion of a rule.
  * The netlink message header isn't fully equipped with all relevant
@@ -511,7 +491,7 @@
  * or supplemented as needed. \a rule must point to an existing
  * address.
  *
- * @return The netlink message
+ * @return 0 on success or a negative error code.
  */
 int rtnl_rule_build_delete_request(struct rtnl_rule *rule, int flags,
 				   struct nl_msg **result)
@@ -568,96 +548,63 @@
 		return AF_UNSPEC;
 }
 
-void rtnl_rule_set_prio(struct rtnl_rule *rule, int prio)
+void rtnl_rule_set_prio(struct rtnl_rule *rule, uint32_t prio)
 {
 	rule->r_prio = prio;
 	rule->ce_mask |= RULE_ATTR_PRIO;
 }
 
-int rtnl_rule_get_prio(struct rtnl_rule *rule)
+uint32_t rtnl_rule_get_prio(struct rtnl_rule *rule)
 {
-	if (rule->ce_mask & RULE_ATTR_PRIO)
-		return rule->r_prio;
-	else
-		return -1;
+	return rule->r_prio;
 }
 
-void rtnl_rule_set_mark(struct rtnl_rule *rule, uint64_t mark)
+void rtnl_rule_set_mark(struct rtnl_rule *rule, uint32_t mark)
 {
 	rule->r_mark = mark;
 	rule->ce_mask |= RULE_ATTR_MARK;
 }
 
-uint64_t rtnl_rule_get_mark(struct rtnl_rule *rule)
+uint32_t rtnl_rule_get_mark(struct rtnl_rule *rule)
 {
-	if (rule->ce_mask & RULE_ATTR_MARK)
-		return rule->r_mark;
-	else
-		return UINT_LEAST64_MAX;
+	return rule->r_mark;
 }
 
-void rtnl_rule_set_table(struct rtnl_rule *rule, int table)
+void rtnl_rule_set_mask(struct rtnl_rule *rule, uint32_t mask)
+{
+	rule->r_mask = mask;
+	rule->ce_mask |= RULE_ATTR_MASK;
+}
+
+uint32_t rtnl_rule_get_mask(struct rtnl_rule *rule)
+{
+	return rule->r_mask;
+}
+
+void rtnl_rule_set_table(struct rtnl_rule *rule, uint32_t table)
 {
 	rule->r_table = table;
 	rule->ce_mask |= RULE_ATTR_TABLE;
 }
 
-int rtnl_rule_get_table(struct rtnl_rule *rule)
+uint32_t rtnl_rule_get_table(struct rtnl_rule *rule)
 {
-	if (rule->ce_mask & RULE_ATTR_TABLE)
-		return rule->r_table;
-	else
-		return -1;
+	return rule->r_table;
 }
 
-void rtnl_rule_set_dsfield(struct rtnl_rule *rule, int dsfield)
+void rtnl_rule_set_dsfield(struct rtnl_rule *rule, uint8_t dsfield)
 {
 	rule->r_dsfield = dsfield;
 	rule->ce_mask |= RULE_ATTR_DSFIELD;
 }
 
-int rtnl_rule_get_dsfield(struct rtnl_rule *rule)
+uint8_t rtnl_rule_get_dsfield(struct rtnl_rule *rule)
 {
-	if (rule->ce_mask & RULE_ATTR_DSFIELD)
-		return rule->r_dsfield;
-	else
-		return -1;
-}
-
-void rtnl_rule_set_src_len(struct rtnl_rule *rule, int len)
-{
-	rule->r_src_len = len;
-	if (rule->ce_mask & RULE_ATTR_SRC)
-		nl_addr_set_prefixlen(rule->r_src, len);
-	rule->ce_mask |= RULE_ATTR_SRC_LEN;
-}
-
-int rtnl_rule_get_src_len(struct rtnl_rule *rule)
-{
-	if (rule->ce_mask & RULE_ATTR_SRC_LEN)
-		return rule->r_src_len;
-	else
-		return -1;
-}
-
-void rtnl_rule_set_dst_len(struct rtnl_rule *rule, int len)
-{
-	rule->r_dst_len = len;
-	if (rule->ce_mask & RULE_ATTR_DST)
-		nl_addr_set_prefixlen(rule->r_dst, len);
-	rule->ce_mask |= RULE_ATTR_DST_LEN;
-}
-
-int rtnl_rule_get_dst_len(struct rtnl_rule *rule)
-{
-	if (rule->ce_mask & RULE_ATTR_DST_LEN)
-		return rule->r_dst_len;
-	else
-		return -1;
+	return rule->r_dsfield;
 }
 
 static inline int __assign_addr(struct rtnl_rule *rule, struct nl_addr **pos,
-			        struct nl_addr *new, uint8_t *len, int flag)
+			        struct nl_addr *new, int flag)
 {
 	if (rule->ce_mask & RULE_ATTR_FAMILY) {
 		if (new->a_family != rule->r_family)
@@ -670,7 +617,6 @@
 
 	nl_addr_get(new);
 	*pos = new;
-	*len = nl_addr_get_prefixlen(new);
 
 	rule->ce_mask |= (flag | RULE_ATTR_FAMILY);
 
@@ -679,30 +625,22 @@
 
 int rtnl_rule_set_src(struct rtnl_rule *rule, struct nl_addr *src)
 {
-	return __assign_addr(rule, &rule->r_src, src, &rule->r_src_len,
-			     RULE_ATTR_SRC | RULE_ATTR_SRC_LEN);
+	return __assign_addr(rule, &rule->r_src, src, RULE_ATTR_SRC);
 }
 
 struct nl_addr *rtnl_rule_get_src(struct rtnl_rule *rule)
 {
-	if (rule->ce_mask & RULE_ATTR_SRC)
-		return rule->r_src;
-	else
-		return NULL;
+	return rule->r_src;
 }
 
 int rtnl_rule_set_dst(struct rtnl_rule *rule, struct nl_addr *dst)
 {
-	return __assign_addr(rule, &rule->r_dst, dst, &rule->r_dst_len,
-			     RULE_ATTR_DST | RULE_ATTR_DST_LEN);
+	return __assign_addr(rule, &rule->r_dst, dst, RULE_ATTR_DST);
 }
 
 struct nl_addr *rtnl_rule_get_dst(struct rtnl_rule *rule)
 {
-	if (rule->ce_mask & RULE_ATTR_DST)
-		return rule->r_dst;
-	else
-		return NULL;
+	return rule->r_dst;
 }
 
 int rtnl_rule_set_iif(struct rtnl_rule *rule, const char *dev)
@@ -710,45 +648,68 @@
 	if (strlen(dev) > IFNAMSIZ-1)
 		return -NLE_RANGE;
 
-	strcpy(rule->r_iif, dev);
-	rule->ce_mask |= RULE_ATTR_IIF;
+	strcpy(rule->r_iifname, dev);
+	rule->ce_mask |= RULE_ATTR_IIFNAME;
 	return 0;
 }
 
 char *rtnl_rule_get_iif(struct rtnl_rule *rule)
 {
-	if (rule->ce_mask & RULE_ATTR_IIF)
-		return rule->r_iif;
+	if (rule->ce_mask & RULE_ATTR_IIFNAME)
+		return rule->r_iifname;
 	else
 		return NULL;
 }
 
-void rtnl_rule_set_action(struct rtnl_rule *rule, int type)
+int rtnl_rule_set_oif(struct rtnl_rule *rule, const char *dev)
 {
-	rule->r_type = type;
-	rule->ce_mask |= RULE_ATTR_TYPE;
+	if (strlen(dev) > IFNAMSIZ-1)
+		return -NLE_RANGE;
+
+	strcpy(rule->r_oifname, dev);
+	rule->ce_mask |= RULE_ATTR_OIFNAME;
+	return 0;
 }
 
-int rtnl_rule_get_action(struct rtnl_rule *rule)
+char *rtnl_rule_get_oif(struct rtnl_rule *rule)
 {
-	if (rule->ce_mask & RULE_ATTR_TYPE)
-		return rule->r_type;
+	if (rule->ce_mask & RULE_ATTR_OIFNAME)
+		return rule->r_oifname;
 	else
-		return -NLE_NOATTR;
+		return NULL;
+}
+
+void rtnl_rule_set_action(struct rtnl_rule *rule, uint8_t action)
+{
+	rule->r_action = action;
+	rule->ce_mask |= RULE_ATTR_ACTION;
+}
+
+uint8_t rtnl_rule_get_action(struct rtnl_rule *rule)
+{
+	return rule->r_action;
 }
 
 void rtnl_rule_set_realms(struct rtnl_rule *rule, uint32_t realms)
 {
-	rule->r_realms = realms;
-	rule->ce_mask |= RULE_ATTR_REALMS;
+	rule->r_flow = realms;
+	rule->ce_mask |= RULE_ATTR_FLOW;
 }
 
 uint32_t rtnl_rule_get_realms(struct rtnl_rule *rule)
 {
-	if (rule->ce_mask & RULE_ATTR_REALMS)
-		return rule->r_realms;
-	else
-		return 0;
+	return rule->r_flow;
+}
+
+void rtnl_rule_set_goto(struct rtnl_rule *rule, uint32_t ref)
+{
+	rule->r_goto = ref;
+	rule->ce_mask |= RULE_ATTR_GOTO;
+}
+
+uint32_t rtnl_rule_get_goto(struct rtnl_rule *rule)
+{
+	return rule->r_goto;
 }
 
 /** @} */
@@ -762,7 +723,6 @@
 	    [NL_DUMP_LINE]	= rule_dump_line,
 	    [NL_DUMP_DETAILS]	= rule_dump_details,
 	    [NL_DUMP_STATS]	= rule_dump_stats,
-	    [NL_DUMP_ENV]	= rule_dump_env,
 	},
 	.oo_compare		= rule_compare,
 	.oo_attrs2str		= rule_attrs2str,
@@ -771,7 +731,7 @@
 
 static struct nl_cache_ops rtnl_rule_ops = {
 	.co_name		= "route/rule",
-	.co_hdrsize		= sizeof(struct rtmsg),
+	.co_hdrsize		= sizeof(struct fib_rule_hdr),
 	.co_msgtypes		= {
 					{ RTM_NEWRULE, NL_ACT_NEW, "new" },
 					{ RTM_DELRULE, NL_ACT_DEL, "del" },
diff --git a/lib/route/sch/blackhole.c b/lib/route/sch/blackhole.c
deleted file mode 100644
index a30b693..0000000
--- a/lib/route/sch/blackhole.c
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * lib/route/sch/blackhole.c	Blackhole Qdisc
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @ingroup qdisc_api
- * @defgroup blackhole Blackhole
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/route/qdisc.h>
-#include <netlink/route/qdisc-modules.h>
-
-static struct rtnl_qdisc_ops blackhole_ops = {
-	.qo_kind		= "blackhole",
-};
-
-static void __init blackhole_init(void)
-{
-	rtnl_qdisc_register(&blackhole_ops);
-}
-
-static void __exit blackhole_exit(void)
-{
-	rtnl_qdisc_unregister(&blackhole_ops);
-}
-
-/** @} */
diff --git a/lib/route/sch/cbq.c b/lib/route/sch/cbq.c
deleted file mode 100644
index 1aaa58d..0000000
--- a/lib/route/sch/cbq.c
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * lib/route/sch/cbq.c	Class Based Queueing
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/utils.h>
-#include <netlink/route/qdisc.h>
-#include <netlink/route/qdisc-modules.h>
-#include <netlink/route/class.h>
-#include <netlink/route/class-modules.h>
-#include <netlink/route/link.h>
-#include <netlink/route/sch/cbq.h>
-#include <netlink/route/cls/police.h>
-
-/**
- * @ingroup qdisc_api
- * @ingroup class_api
- * @defgroup cbq Class Based Queueing (CBQ)
- * @{
- */
-
-static struct trans_tbl ovl_strategies[] = {
-	__ADD(TC_CBQ_OVL_CLASSIC,classic)
-	__ADD(TC_CBQ_OVL_DELAY,delay)
-	__ADD(TC_CBQ_OVL_LOWPRIO,lowprio)
-	__ADD(TC_CBQ_OVL_DROP,drop)
-	__ADD(TC_CBQ_OVL_RCLASSIC,rclassic)
-};
-
-/**
- * Convert a CBQ OVL strategy to a character string
- * @arg type		CBQ OVL strategy
- * @arg buf		destination buffer
- * @arg len		length of destination buffer
- *
- * Converts a CBQ OVL strategy to a character string and stores in the
- * provided buffer. Returns the destination buffer or the type
- * encoded in hex if no match was found.
- */
-char *nl_ovl_strategy2str(int type, char *buf, size_t len)
-{
-	return __type2str(type, buf, len, ovl_strategies,
-			    ARRAY_SIZE(ovl_strategies));
-}
-
-/**
- * Convert a string to a CBQ OVL strategy
- * @arg name		CBQ OVL stragegy name
- *
- * Converts a CBQ OVL stragegy name to it's corresponding CBQ OVL strategy
- * type. Returns the type or -1 if none was found.
- */
-int nl_str2ovl_strategy(const char *name)
-{
-	return __str2type(name, ovl_strategies, ARRAY_SIZE(ovl_strategies));
-}
-
-static struct nla_policy cbq_policy[TCA_CBQ_MAX+1] = {
-	[TCA_CBQ_LSSOPT]	= { .minlen = sizeof(struct tc_cbq_lssopt) },
-	[TCA_CBQ_RATE]		= { .minlen = sizeof(struct tc_ratespec) },
-	[TCA_CBQ_WRROPT]	= { .minlen = sizeof(struct tc_cbq_wrropt) },
-	[TCA_CBQ_OVL_STRATEGY]	= { .minlen = sizeof(struct tc_cbq_ovl) },
-	[TCA_CBQ_FOPT]		= { .minlen = sizeof(struct tc_cbq_fopt) },
-	[TCA_CBQ_POLICE]	= { .minlen = sizeof(struct tc_cbq_police) },
-};
-
-static inline struct rtnl_cbq *cbq_qdisc(struct rtnl_tca *tca)
-{
-	return (struct rtnl_cbq *) tca->tc_subdata;
-}
-
-static inline struct rtnl_cbq *cbq_alloc(struct rtnl_tca *tca)
-{
-	if (!tca->tc_subdata)
-		tca->tc_subdata = calloc(1, sizeof(struct rtnl_qdisc));
-
-	return cbq_qdisc(tca);
-}
-
-
-static int cbq_msg_parser(struct rtnl_tca *tca)
-{
-	struct nlattr *tb[TCA_CBQ_MAX + 1];
-	struct rtnl_cbq *cbq;
-	int err;
-
-	err = tca_parse(tb, TCA_CBQ_MAX, tca, cbq_policy);
-	if (err < 0)
-		return err;
-
-	cbq = cbq_alloc(tca);
-	if (!cbq)
-		return -NLE_NOMEM;
-
-	nla_memcpy(&cbq->cbq_lss, tb[TCA_CBQ_LSSOPT], sizeof(cbq->cbq_lss));
-	nla_memcpy(&cbq->cbq_rate, tb[TCA_CBQ_RATE], sizeof(cbq->cbq_rate));
-	nla_memcpy(&cbq->cbq_wrr, tb[TCA_CBQ_WRROPT], sizeof(cbq->cbq_wrr));
-	nla_memcpy(&cbq->cbq_fopt, tb[TCA_CBQ_FOPT], sizeof(cbq->cbq_fopt));
-	nla_memcpy(&cbq->cbq_ovl, tb[TCA_CBQ_OVL_STRATEGY],
-		   sizeof(cbq->cbq_ovl));
-	nla_memcpy(&cbq->cbq_police, tb[TCA_CBQ_POLICE],
-		    sizeof(cbq->cbq_police));
-	
-	return 0;
-}
-
-static int cbq_qdisc_msg_parser(struct rtnl_qdisc *qdisc)
-{
-	return cbq_msg_parser((struct rtnl_tca *) qdisc);
-}
-
-static int cbq_class_msg_parser(struct rtnl_class *class)
-{
-	return cbq_msg_parser((struct rtnl_tca *) class);
-}
-
-static void cbq_qdisc_free_data(struct rtnl_qdisc *qdisc)
-{
-	free(qdisc->q_subdata);
-}
-
-static int cbq_clone(struct rtnl_tca *_dst, struct rtnl_tca *_src)
-{
-	struct rtnl_cbq *src = cbq_qdisc(_src);
-
-	if (src && !cbq_alloc(_dst))
-		return -NLE_NOMEM;
-	else
-		return 0;
-}
-
-static int cbq_qdisc_clone(struct rtnl_qdisc *dst, struct rtnl_qdisc *src)
-{
-	return cbq_clone((struct rtnl_tca *) dst, (struct rtnl_tca *) src);
-}
-
-static void cbq_class_free_data(struct rtnl_class *class)
-{
-	free(class->c_subdata);
-}
-
-static int cbq_class_clone(struct rtnl_class *dst, struct rtnl_class *src)
-{
-	return cbq_clone((struct rtnl_tca *) dst, (struct rtnl_tca *) src);
-}
-
-static void cbq_dump_line(struct rtnl_tca *tca, struct nl_dump_params *p)
-{
-	struct rtnl_cbq *cbq;
-	double r, rbit;
-	char *ru, *rubit;
-
-	cbq = cbq_qdisc(tca);
-	if (!cbq)
-		return;
-
-	r = nl_cancel_down_bytes(cbq->cbq_rate.rate, &ru);
-	rbit = nl_cancel_down_bits(cbq->cbq_rate.rate * 8, &rubit);
-
-	nl_dump(p, " rate %.2f%s/s (%.0f%s) prio %u",
-		r, ru, rbit, rubit, cbq->cbq_wrr.priority);
-}
-
-static void cbq_qdisc_dump_line(struct rtnl_qdisc *qdisc,
-				struct nl_dump_params *p)
-{
-	cbq_dump_line((struct rtnl_tca *) qdisc, p);
-}
-
-static void cbq_class_dump_line(struct rtnl_class *class,
-				struct nl_dump_params *p)
-{
-	cbq_dump_line((struct rtnl_tca *) class, p);
-}
-
-static void cbq_dump_details(struct rtnl_tca *tca, struct nl_dump_params *p)
-{
-	struct rtnl_cbq *cbq;
-	char *unit, buf[32];
-	double w;
-	uint32_t el;
-
-	cbq = cbq_qdisc(tca);
-	if (!cbq)
-		return;
-
-	w = nl_cancel_down_bits(cbq->cbq_wrr.weight * 8, &unit);
-
-	nl_dump(p, "avgpkt %u mpu %u cell %u allot %u weight %.0f%s\n",
-		cbq->cbq_lss.avpkt,
-		cbq->cbq_rate.mpu,
-		1 << cbq->cbq_rate.cell_log,
-		cbq->cbq_wrr.allot, w, unit);
-
-	el = cbq->cbq_lss.ewma_log;
-	nl_dump_line(p, "  minidle %uus maxidle %uus offtime "
-				"%uus level %u ewma_log %u\n",
-		nl_ticks2us(cbq->cbq_lss.minidle >> el),
-		nl_ticks2us(cbq->cbq_lss.maxidle >> el),
-		nl_ticks2us(cbq->cbq_lss.offtime >> el),
-		cbq->cbq_lss.level,
-		cbq->cbq_lss.ewma_log);
-
-	nl_dump_line(p, "  penalty %uus strategy %s ",
-		nl_ticks2us(cbq->cbq_ovl.penalty),
-		nl_ovl_strategy2str(cbq->cbq_ovl.strategy, buf, sizeof(buf)));
-
-	nl_dump(p, "split %s defmap 0x%08x ",
-		rtnl_tc_handle2str(cbq->cbq_fopt.split, buf, sizeof(buf)),
-		cbq->cbq_fopt.defmap);
-	
-	nl_dump(p, "police %s",
-		nl_police2str(cbq->cbq_police.police, buf, sizeof(buf)));
-}
-
-static void cbq_qdisc_dump_details(struct rtnl_qdisc *qdisc,
-				   struct nl_dump_params *p)
-{
-	cbq_dump_details((struct rtnl_tca *) qdisc, p);
-}
-
-static void cbq_class_dump_details(struct rtnl_class *class,
-				   struct nl_dump_params *p)
-{
-	cbq_dump_details((struct rtnl_tca *) class, p);
-}
-
-static void cbq_dump_stats(struct rtnl_tca *tca, struct nl_dump_params *p)
-{
-	struct tc_cbq_xstats *x = tca_xstats(tca);
-
-	if (!x)
-		return;
-
-	nl_dump_line(p, "            borrows    overact  "
-			"  avgidle  undertime\n");
-	nl_dump_line(p, "         %10u %10u %10u %10u\n",
-		     x->borrows, x->overactions, x->avgidle, x->undertime);
-}
-
-static void cbq_qdisc_dump_stats(struct rtnl_qdisc *qdisc,
-				 struct nl_dump_params *p)
-{
-	cbq_dump_stats((struct rtnl_tca *) qdisc, p);
-}
-
-static void cbq_class_dump_stats(struct rtnl_class *class,
-				 struct nl_dump_params *p)
-{
-	cbq_dump_stats((struct rtnl_tca *) class, p);
-}
-
-static struct rtnl_qdisc_ops cbq_qdisc_ops = {
-	.qo_kind		= "cbq",
-	.qo_msg_parser		= cbq_qdisc_msg_parser,
-	.qo_free_data		= cbq_qdisc_free_data,
-	.qo_clone		= cbq_qdisc_clone,
-	.qo_dump = {
-	    [NL_DUMP_LINE]	= cbq_qdisc_dump_line,
-	    [NL_DUMP_DETAILS]	= cbq_qdisc_dump_details,
-	    [NL_DUMP_STATS]	= cbq_qdisc_dump_stats,
-	},
-};
-
-static struct rtnl_class_ops cbq_class_ops = {
-	.co_kind		= "cbq",
-	.co_msg_parser		= cbq_class_msg_parser,
-	.co_free_data		= cbq_class_free_data,
-	.co_clone		= cbq_class_clone,
-	.co_dump = {
-	    [NL_DUMP_LINE]	= cbq_class_dump_line,
-	    [NL_DUMP_DETAILS]	= cbq_class_dump_details,
-	    [NL_DUMP_STATS]	= cbq_class_dump_stats,
-	},
-};
-
-static void __init cbq_init(void)
-{
-	rtnl_qdisc_register(&cbq_qdisc_ops);
-	rtnl_class_register(&cbq_class_ops);
-}
-
-static void __exit cbq_exit(void)
-{
-	rtnl_qdisc_unregister(&cbq_qdisc_ops);
-	rtnl_class_unregister(&cbq_class_ops);
-}
-
-/** @} */
diff --git a/lib/route/sch/fifo.c b/lib/route/sch/fifo.c
deleted file mode 100644
index 464af30..0000000
--- a/lib/route/sch/fifo.c
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * lib/route/sch/fifo.c		(p|b)fifo
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @ingroup qdisc_api
- * @defgroup fifo Packet/Bytes FIFO (pfifo/bfifo)
- * @brief
- *
- * The FIFO qdisc comes in two flavours:
- * @par bfifo (Byte FIFO)
- * Allows enqueuing until the currently queued volume in bytes exceeds
- * the configured limit.backlog contains currently enqueued volume in bytes.
- *
- * @par pfifo (Packet FIFO)
- * Allows enquueing until the currently queued number of packets
- * exceeds the configured limit.
- *
- * The configuration is exactly the same, the decision which of
- * the two variations is going to be used is made based on the
- * kind of the qdisc (rtnl_qdisc_set_kind()).
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/route/qdisc.h>
-#include <netlink/route/qdisc-modules.h>
-#include <netlink/route/sch/fifo.h>
-#include <netlink/utils.h>
-
-/** @cond SKIP */
-#define SCH_FIFO_ATTR_LIMIT 1
-/** @endcond */
-
-static inline struct rtnl_fifo *fifo_qdisc(struct rtnl_qdisc *qdisc)
-{
-	return (struct rtnl_fifo *) qdisc->q_subdata;
-}
-
-static inline struct rtnl_fifo *fifo_alloc(struct rtnl_qdisc *qdisc)
-{
-	if (!qdisc->q_subdata)
-		qdisc->q_subdata = calloc(1, sizeof(struct rtnl_fifo));
-
-	return fifo_qdisc(qdisc);
-}
-
-static int fifo_msg_parser(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_fifo *fifo;
-	struct tc_fifo_qopt *opt;
-
-	if (qdisc->q_opts->d_size < sizeof(struct tc_fifo_qopt))
-		return -NLE_INVAL;
-
-	fifo = fifo_alloc(qdisc);
-	if (!fifo)
-		return -NLE_NOMEM;
-
-	opt = (struct tc_fifo_qopt *) qdisc->q_opts->d_data;
-	fifo->qf_limit = opt->limit;
-	fifo->qf_mask = SCH_FIFO_ATTR_LIMIT;
-
-	return 0;
-}
-
-static void fifo_free_data(struct rtnl_qdisc *qdisc)
-{
-	free(qdisc->q_subdata);
-}
-
-static void pfifo_dump_line(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
-{
-	struct rtnl_fifo *fifo = fifo_qdisc(qdisc);
-
-	if (fifo)
-		nl_dump(p, " limit %u packets", fifo->qf_limit);
-}
-
-static void bfifo_dump_line(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
-{
-	struct rtnl_fifo *fifo = fifo_qdisc(qdisc);
-
-	if (fifo) {
-		char *unit;
-		double r;
-
-		r = nl_cancel_down_bytes(fifo->qf_limit, &unit);
-		nl_dump(p, " limit %.1f%s", r, unit);
-	}
-}
-
-static struct nl_msg *fifo_get_opts(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_fifo *fifo;
-	struct tc_fifo_qopt opts;
-	struct nl_msg *msg;
-
-	fifo = fifo_qdisc(qdisc);
-	if (!fifo || !(fifo->qf_mask & SCH_FIFO_ATTR_LIMIT))
-		return NULL;
-
-	msg = nlmsg_alloc();
-	if (!msg)
-		goto errout;
-
-	memset(&opts, 0, sizeof(opts));
-	opts.limit = fifo->qf_limit;
-
-	if (nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD) < 0)
-		goto errout;
-
-	return msg;
-errout:
-	nlmsg_free(msg);
-	return NULL;
-}
-
-/**
- * @name Attribute Modification
- * @{
- */
-
-/**
- * Set limit of FIFO qdisc.
- * @arg qdisc		FIFO qdisc to be modified.
- * @arg limit		New limit.
- * @return 0 on success or a negative error code.
- */
-int rtnl_qdisc_fifo_set_limit(struct rtnl_qdisc *qdisc, int limit)
-{
-	struct rtnl_fifo *fifo;
-	
-	fifo = fifo_alloc(qdisc);
-	if (!fifo)
-		return -NLE_NOMEM;
-		
-	fifo->qf_limit = limit;
-	fifo->qf_mask |= SCH_FIFO_ATTR_LIMIT;
-
-	return 0;
-}
-
-/**
- * Get limit of a FIFO qdisc.
- * @arg qdisc		FIFO qdisc.
- * @return Numeric limit or a negative error code.
- */
-int rtnl_qdisc_fifo_get_limit(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_fifo *fifo;
-	
-	fifo = fifo_qdisc(qdisc);
-	if (fifo && fifo->qf_mask & SCH_FIFO_ATTR_LIMIT)
-		return fifo->qf_limit;
-	else
-		return -NLE_NOATTR;
-}
-
-/** @} */
-
-static struct rtnl_qdisc_ops pfifo_ops = {
-	.qo_kind		= "pfifo",
-	.qo_msg_parser		= fifo_msg_parser,
-	.qo_free_data		= fifo_free_data,
-	.qo_dump[NL_DUMP_LINE]	= pfifo_dump_line,
-	.qo_get_opts		= fifo_get_opts,
-};
-
-static struct rtnl_qdisc_ops bfifo_ops = {
-	.qo_kind		= "bfifo",
-	.qo_msg_parser		= fifo_msg_parser,
-	.qo_free_data		= fifo_free_data,
-	.qo_dump[NL_DUMP_LINE]	= bfifo_dump_line,
-	.qo_get_opts		= fifo_get_opts,
-};
-
-static void __init fifo_init(void)
-{
-	rtnl_qdisc_register(&pfifo_ops);
-	rtnl_qdisc_register(&bfifo_ops);
-}
-
-static void __exit fifo_exit(void)
-{
-	rtnl_qdisc_unregister(&pfifo_ops);
-	rtnl_qdisc_unregister(&bfifo_ops);
-}
-
-/** @} */
diff --git a/lib/route/sch/htb.c b/lib/route/sch/htb.c
deleted file mode 100644
index a167136..0000000
--- a/lib/route/sch/htb.c
+++ /dev/null
@@ -1,546 +0,0 @@
-/*
- * lib/route/sch/htb.c	HTB Qdisc
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
- * Copyright (c) 2005-2006 Petr Gotthard <petr.gotthard@siemens.com>
- * Copyright (c) 2005-2006 Siemens AG Oesterreich
- */
-
-/**
- * @ingroup qdisc_api
- * @ingroup class_api
- * @defgroup htb Hierachical Token Bucket (HTB)
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/cache.h>
-#include <netlink/utils.h>
-#include <netlink/route/tc.h>
-#include <netlink/route/qdisc.h>
-#include <netlink/route/qdisc-modules.h>
-#include <netlink/route/class.h>
-#include <netlink/route/class-modules.h>
-#include <netlink/route/link.h>
-#include <netlink/route/sch/htb.h>
-
-/** @cond SKIP */
-#define SCH_HTB_HAS_RATE2QUANTUM	0x01
-#define SCH_HTB_HAS_DEFCLS		0x02
-
-#define SCH_HTB_HAS_PRIO		0x001
-#define SCH_HTB_HAS_MTU			0x002
-#define SCH_HTB_HAS_RATE		0x004
-#define SCH_HTB_HAS_CEIL		0x008
-#define SCH_HTB_HAS_RBUFFER		0x010
-#define SCH_HTB_HAS_CBUFFER		0x020
-#define SCH_HTB_HAS_QUANTUM		0x040
-#define SCH_HTB_HAS_OVERHEAD		0x080
-#define SCH_HTB_HAS_MPU			0x100
-/** @endcond */
-
-static inline struct rtnl_htb_qdisc *htb_qdisc(struct rtnl_qdisc *qdisc)
-{
-	if (qdisc->q_subdata == NULL)
-		qdisc->q_subdata = calloc(1, sizeof(struct rtnl_htb_qdisc));
-
-	return (struct rtnl_htb_qdisc *) qdisc->q_subdata;
-}
-
-static struct nla_policy htb_policy[TCA_HTB_MAX+1] = {
-	[TCA_HTB_INIT]	= { .minlen = sizeof(struct tc_htb_glob) },
-	[TCA_HTB_PARMS] = { .minlen = sizeof(struct tc_htb_opt) },
-};
-
-static int htb_qdisc_msg_parser(struct rtnl_qdisc *qdisc)
-{
-	int err;
-	struct nlattr *tb[TCA_HTB_MAX + 1];
-	struct rtnl_htb_qdisc *d;
-
-	err = tca_parse(tb, TCA_HTB_MAX, (struct rtnl_tca *) qdisc, htb_policy);
-	if (err < 0)
-		return err;
-	
-	d = htb_qdisc(qdisc);
-
-	if (tb[TCA_HTB_INIT]) {
-		struct tc_htb_glob opts;
-
-		nla_memcpy(&opts, tb[TCA_HTB_INIT], sizeof(opts));
-		d->qh_rate2quantum = opts.rate2quantum;
-		d->qh_defcls = opts.defcls;
-
-		d->qh_mask = (SCH_HTB_HAS_RATE2QUANTUM | SCH_HTB_HAS_DEFCLS);
-	}
-
-	return 0;
-}
-
-static void htb_qdisc_free_data(struct rtnl_qdisc *qdisc)
-{
-	free(qdisc->q_subdata);
-}
-
-static inline struct rtnl_htb_class *htb_class(struct rtnl_class *class)
-{
-	if (class->c_subdata == NULL)
-		class->c_subdata = calloc(1, sizeof(struct rtnl_htb_class));
-
-	return (struct rtnl_htb_class *) class->c_subdata;
-}
-
-static int htb_class_msg_parser(struct rtnl_class *class)
-{
-	int err;
-	struct nlattr *tb[TCA_HTB_MAX + 1];
-	struct rtnl_htb_class *d;
-
-	err = tca_parse(tb, TCA_HTB_MAX, (struct rtnl_tca *) class, htb_policy);
-	if (err < 0)
-		return err;
-	
-	d = htb_class(class);
-
-	if (tb[TCA_HTB_PARMS]) {
-		struct tc_htb_opt opts;
-
-		nla_memcpy(&opts, tb[TCA_HTB_PARMS], sizeof(opts));
-		d->ch_prio = opts.prio;
-		rtnl_copy_ratespec(&d->ch_rate, &opts.rate);
-		rtnl_copy_ratespec(&d->ch_ceil, &opts.ceil);
-		d->ch_rbuffer = rtnl_tc_calc_bufsize(opts.buffer, opts.rate.rate);
-		d->ch_cbuffer = rtnl_tc_calc_bufsize(opts.cbuffer, opts.ceil.rate);
-		d->ch_quantum = opts.quantum;
-		d->ch_overhead = (opts.rate.mpu >> 8) & 0xff;
-		d->ch_mpu = opts.rate.mpu & 0xff;
-
-		d->ch_mask = (SCH_HTB_HAS_PRIO | SCH_HTB_HAS_RATE |
-			SCH_HTB_HAS_CEIL | SCH_HTB_HAS_RBUFFER |
-			SCH_HTB_HAS_CBUFFER | SCH_HTB_HAS_QUANTUM |
-			SCH_HTB_HAS_OVERHEAD | SCH_HTB_HAS_MPU);
-	}
-
-	return 0;
-}
-
-static void htb_class_free_data(struct rtnl_class *class)
-{
-	free(class->c_subdata);
-}
-
-static void htb_qdisc_dump_line(struct rtnl_qdisc *qdisc,
-				struct nl_dump_params *p)
-{
-	struct rtnl_htb_qdisc *d = (struct rtnl_htb_qdisc *) qdisc->q_subdata;
-
-	if (d == NULL)
-		return;
-
-	if (d->qh_mask & SCH_HTB_HAS_RATE2QUANTUM)
-		nl_dump(p, " r2q %u", d->qh_rate2quantum);
-
-	if (d->qh_mask & SCH_HTB_HAS_DEFCLS) {
-		char buf[32];
-		nl_dump(p, " default %s",
-			rtnl_tc_handle2str(d->qh_defcls, buf, sizeof(buf)));
-	}
-}
-
-static void htb_class_dump_line(struct rtnl_class *class,
-				struct nl_dump_params *p)
-{
-	struct rtnl_htb_class *d = (struct rtnl_htb_class *) class->c_subdata;
-
-	if (d == NULL)
-		return;
-
-	if (d->ch_mask & SCH_HTB_HAS_RATE) {
-		double r, rbit;
-		char *ru, *rubit;
-
-		r = nl_cancel_down_bytes(d->ch_rate.rs_rate, &ru);
-		rbit = nl_cancel_down_bits(d->ch_rate.rs_rate*8, &rubit);
-
-		nl_dump(p, " rate %.2f%s/s (%.0f%s) log %u",
-			r, ru, rbit, rubit, 1<<d->ch_rate.rs_cell_log);
-	}
-}
-
-static void htb_class_dump_details(struct rtnl_class *class,
-				   struct nl_dump_params *p)
-{
-	struct rtnl_htb_class *d = (struct rtnl_htb_class *) class->c_subdata;
-
-	if (d == NULL)
-		return;
-
-	/* line 1 */
-	if (d->ch_mask & SCH_HTB_HAS_CEIL) {
-		double r, rbit;
-		char *ru, *rubit;
-
-		r = nl_cancel_down_bytes(d->ch_ceil.rs_rate, &ru);
-		rbit = nl_cancel_down_bits(d->ch_ceil.rs_rate*8, &rubit);
-
-		nl_dump(p, "    ceil %.2f%s/s (%.0f%s) log %u",
-			r, ru, rbit, rubit, 1<<d->ch_ceil.rs_cell_log);
-	}
-
-	if (d->ch_mask & SCH_HTB_HAS_PRIO)
-		nl_dump(p, " prio %u", d->ch_prio);
-
-	if (d->ch_mask & SCH_HTB_HAS_MTU)
-		nl_dump(p, " mtu %u", d->ch_mtu);
-
-	if (d->ch_mask & SCH_HTB_HAS_RBUFFER) {
-		double b;
-		char *bu;
-
-		b = nl_cancel_down_bytes(d->ch_rbuffer, &bu);
-		nl_dump(p, " rbuffer %.2f%s", b, bu);
-	}
-
-	if (d->ch_mask & SCH_HTB_HAS_CBUFFER) {
-		double b;
-		char *bu;
-
-		b = nl_cancel_down_bytes(d->ch_cbuffer, &bu);
-		nl_dump(p, " cbuffer %.2f%s", b, bu);
-	}
-
-	if (d->ch_mask & SCH_HTB_HAS_QUANTUM)
-		nl_dump(p, " quantum %u", d->ch_quantum);
-
-	if (d->ch_mask & SCH_HTB_HAS_OVERHEAD)
-		nl_dump(p, " overhead %u", d->ch_overhead);
-
-	if (d->ch_mask & SCH_HTB_HAS_MPU)
-		nl_dump(p, " mpu %u", d->ch_mpu);
-}
-
-static struct nl_msg *htb_qdisc_get_opts(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_htb_qdisc *d = (struct rtnl_htb_qdisc *) qdisc->q_subdata;
-	struct tc_htb_glob opts;
-	struct nl_msg *msg;
-
-	if (d == NULL)
-		return NULL;
-
-	msg = nlmsg_alloc();
-	if (msg == NULL)
-		return NULL;
-
-	memset(&opts, 0, sizeof(opts));
-	opts.version = TC_HTB_PROTOVER;
-
-	if (d->qh_mask & SCH_HTB_HAS_RATE2QUANTUM)
-		opts.rate2quantum = d->qh_rate2quantum;
-	if (d->qh_mask & SCH_HTB_HAS_DEFCLS)
-		opts.defcls = d->qh_defcls;
-
-	nla_put(msg, TCA_HTB_INIT, sizeof(opts), &opts);
-
-	return msg;
-}
-
-static uint8_t compute_cell(uint32_t rate, uint32_t mtu)
-{
-	uint8_t cell_log = 0;
-	while (mtu > 255) {
-		mtu >>= 1;
-		cell_log++;
-	}
-
-	return cell_log;
-}
-
-static struct nl_msg *htb_class_get_opts(struct rtnl_class *class)
-{
-	struct rtnl_htb_class *d = (struct rtnl_htb_class *) class->c_subdata;
-	uint32_t mtu, rtable[RTNL_TC_RTABLE_SIZE], ctable[RTNL_TC_RTABLE_SIZE];
-	struct tc_htb_opt opts;
-	struct nl_msg *msg;
-	int buffer, cbuffer;
-	uint8_t overhead = 0, mpu = 0;
-
-	if (d == NULL)
-		return NULL;
-
-	msg = nlmsg_alloc();
-	memset(&opts, 0, sizeof(opts));
-
-	/* if not set, zero (0) is used as priority */
-	if (d->ch_mask & SCH_HTB_HAS_PRIO)
-		opts.prio = d->ch_prio;
-
-	if (d->ch_mask & SCH_HTB_HAS_MTU)
-		mtu = d->ch_mtu;
-	else
-		mtu = 1600; /* eth packet len */
-
-	if (!(d->ch_mask & SCH_HTB_HAS_RATE))
-		BUG();
-
-	rtnl_rcopy_ratespec(&opts.rate, &d->ch_rate);
-	/* if cell_log not set, compute default value */
-	if (opts.rate.cell_log == UINT8_MAX)
-		opts.rate.cell_log = compute_cell(opts.rate.rate, mtu);
-
-	/* if not set, configured rate is used as ceil, which implies no borrowing */
-	if (d->ch_mask & SCH_HTB_HAS_CEIL)
-		rtnl_rcopy_ratespec(&opts.ceil, &d->ch_ceil);
-	else
-		memcpy(&opts.ceil, &opts.rate, sizeof(struct tc_ratespec));
-	/* if cell_log not set, compute default value */
-	if (opts.ceil.cell_log == UINT8_MAX)
-		opts.ceil.cell_log = compute_cell(opts.ceil.rate, mtu);
-
-	if (d->ch_mask & SCH_HTB_HAS_RBUFFER)
-		buffer = d->ch_rbuffer;
-	else
-		buffer = opts.rate.rate / nl_get_hz() + mtu;
-
-	opts.buffer = rtnl_tc_calc_txtime(buffer, opts.rate.rate);
-
-	if (d->ch_mask & SCH_HTB_HAS_CBUFFER)
-		cbuffer = d->ch_cbuffer;
-	else
-		cbuffer = opts.ceil.rate / nl_get_hz() + mtu;
-
-	opts.cbuffer = rtnl_tc_calc_txtime(cbuffer, opts.ceil.rate);
-
-	if (d->ch_mask & SCH_HTB_HAS_QUANTUM)
-		opts.quantum = d->ch_quantum;
-
-	if (d->ch_mask & SCH_HTB_HAS_OVERHEAD)
-		overhead = d->ch_overhead;
-
-	if (d->ch_mask & SCH_HTB_HAS_MPU)
-		mpu = d->ch_mpu;
-	
-	opts.rate.mpu = mpu | (overhead << 8);
-	opts.ceil.mpu = mpu | (overhead << 8);
-
-	nla_put(msg, TCA_HTB_PARMS, sizeof(opts), &opts);
-
-	rtnl_tc_build_rate_table(rtable, mpu, overhead,
-				 1 << opts.rate.cell_log,
-				 opts.rate.rate);
-	nla_put(msg, TCA_HTB_RTAB, sizeof(rtable), &rtable);
-
-	rtnl_tc_build_rate_table(ctable, mpu, overhead,
-				 1 << opts.ceil.cell_log,
-				 opts.ceil.rate);
-	nla_put(msg, TCA_HTB_CTAB, sizeof(ctable), &ctable);
-
-	return msg;
-}
-
-/**
- * @name Attribute Modifications
- * @{
- */
-
-void rtnl_htb_set_rate2quantum(struct rtnl_qdisc *qdisc, uint32_t rate2quantum)
-{
-	struct rtnl_htb_qdisc *d = htb_qdisc(qdisc);
-	if (d == NULL)
-		return;
-
-	d->qh_rate2quantum = rate2quantum;
-	d->qh_mask |= SCH_HTB_HAS_RATE2QUANTUM;
-}
-
-/**
- * Set default class of the htb qdisc to the specified value
- * @arg qdisc		qdisc to change
- * @arg defcls		new default class
- */
-void rtnl_htb_set_defcls(struct rtnl_qdisc *qdisc, uint32_t defcls)
-{
-	struct rtnl_htb_qdisc *d = htb_qdisc(qdisc);
-	if (d == NULL)
-		return;
-
-	d->qh_defcls = defcls;
-	d->qh_mask |= SCH_HTB_HAS_DEFCLS;
-}
-
-void rtnl_htb_set_prio(struct rtnl_class *class, uint32_t prio)
-{
-	struct rtnl_htb_class *d = htb_class(class);
-	if (d == NULL)
-		return;
-
-	d->ch_prio = prio;
-	d->ch_mask |= SCH_HTB_HAS_PRIO;
-}
-
-/**
- * Set MTU of the data link.
- * @arg class		HTB class to be modified.
- * @arg mtu		New MTU in bytes.
- *
- * Sets MTU of the data link controlled by the HTB class.
- * If not set, the Ethernet MTU (1600) is used.
- */
-void rtnl_htb_set_mtu(struct rtnl_class *class, uint32_t mtu)
-{
-	struct rtnl_htb_class *d = htb_class(class);
-	if (d == NULL)
-		return;
-
-	d->ch_mtu = mtu;
-	d->ch_mask |= SCH_HTB_HAS_MTU;
-}
-
-/**
- * Set rate of HTB class.
- * @arg class		HTB class to be modified.
- * @arg rate		New rate in bytes per second.
- */
-void rtnl_htb_set_rate(struct rtnl_class *class, uint32_t rate)
-{
-	struct rtnl_htb_class *d = htb_class(class);
-	if (d == NULL)
-		return;
-
-	d->ch_rate.rs_cell_log = UINT8_MAX; /* use default value */
-	d->ch_rate.rs_rate = rate;
-	d->ch_mask |= SCH_HTB_HAS_RATE;
-}
-
-/**
- * Set ceil of HTB class.
- * @arg class		HTB class to be modified.
- * @arg ceil		New ceil in bytes per second.
- */
-void rtnl_htb_set_ceil(struct rtnl_class *class, uint32_t ceil)
-{
-	struct rtnl_htb_class *d = htb_class(class);
-	if (d == NULL)
-		return;
-
-	d->ch_ceil.rs_cell_log = UINT8_MAX; /* use default value */
-	d->ch_ceil.rs_rate = ceil;
-	d->ch_mask |= SCH_HTB_HAS_CEIL;
-}
-
-/**
- * Set size of the rate bucket of HTB class.
- * @arg class		HTB class to be modified.
- * @arg rbuffer		New size in bytes.
- */
-void rtnl_htb_set_rbuffer(struct rtnl_class *class, uint32_t rbuffer)
-{
-	struct rtnl_htb_class *d = htb_class(class);
-	if (d == NULL)
-		return;
-
-	d->ch_rbuffer = rbuffer;
-	d->ch_mask |= SCH_HTB_HAS_RBUFFER;
-}
-
-/**
- * Set size of the ceil bucket of HTB class.
- * @arg class		HTB class to be modified.
- * @arg cbuffer		New size in bytes.
- */
-void rtnl_htb_set_cbuffer(struct rtnl_class *class, uint32_t cbuffer)
-{
-	struct rtnl_htb_class *d = htb_class(class);
-	if (d == NULL)
-		return;
-
-	d->ch_cbuffer = cbuffer;
-	d->ch_mask |= SCH_HTB_HAS_CBUFFER;
-}
-
-/**
- * Set how much bytes to serve from leaf at once of HTB class {use r2q}.
- * @arg class		HTB class to be modified.
- * @arg quantum		New size in bytes.
- */
-void rtnl_htb_set_quantum(struct rtnl_class *class, uint32_t quantum)
-{
-	struct rtnl_htb_class *d = htb_class(class);
-	if (d == NULL)
-		return;
-
-	d->ch_quantum = quantum;
-	d->ch_mask |= SCH_HTB_HAS_QUANTUM;
-}
-
-/**
- * Set per-packet size overhead used in rate computations of HTB class.
- * @arg class		HTB class to be modified.
- * @arg overhead		Size in bytes.
- */
-void rtnl_htb_set_overhead(struct rtnl_class *class, uint8_t overhead)
-{
-	struct rtnl_htb_class *d = htb_class(class);
-	if (d == NULL)
-		return;
-
-	d->ch_overhead = overhead;
-	d->ch_mask |= SCH_HTB_HAS_OVERHEAD;
-}
-
-/**
- * Set the minimum packet size used in rate computations of HTB class.
- * @arg class		HTB class to be modified.
- * @arg mpu		Size in bytes.
- */
-void rtnl_htb_set_mpu(struct rtnl_class *class, uint8_t mpu)
-{
-	struct rtnl_htb_class *d = htb_class(class);
-	if (d == NULL)
-		return;
-
-	d->ch_mpu = mpu;
-	d->ch_mask |= SCH_HTB_HAS_MPU;
-}
-
-/** @} */
-
-static struct rtnl_qdisc_ops htb_qdisc_ops = {
-	.qo_kind		= "htb",
-	.qo_msg_parser		= htb_qdisc_msg_parser,
-	.qo_free_data		= htb_qdisc_free_data,
-	.qo_dump[NL_DUMP_LINE]	= htb_qdisc_dump_line,
-	.qo_get_opts		= htb_qdisc_get_opts,
-};
-
-static struct rtnl_class_ops htb_class_ops = {
-	.co_kind		= "htb",
-	.co_msg_parser		= htb_class_msg_parser,
-	.co_free_data		= htb_class_free_data,
-	.co_dump = {
-	    [NL_DUMP_LINE]	= htb_class_dump_line,
-	    [NL_DUMP_DETAILS]	= htb_class_dump_details,
-	},
-	.co_get_opts		= htb_class_get_opts,
-};
-
-static void __init htb_init(void)
-{
-	rtnl_qdisc_register(&htb_qdisc_ops);
-	rtnl_class_register(&htb_class_ops);
-}
-
-static void __exit htb_exit(void)
-{
-	rtnl_qdisc_unregister(&htb_qdisc_ops);
-	rtnl_class_unregister(&htb_class_ops);
-}
-
-/** @} */
diff --git a/lib/route/sch/red.c b/lib/route/sch/red.c
deleted file mode 100644
index e4cac79..0000000
--- a/lib/route/sch/red.c
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * lib/route/sch/red.c		RED Qdisc
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @ingroup qdisc_api
- * @defgroup red Random Early Detection (RED)
- * @brief
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/utils.h>
-#include <netlink/route/qdisc.h>
-#include <netlink/route/qdisc-modules.h>
-#include <netlink/route/sch/red.h>
-
-/** @cond SKIP */
-#define RED_ATTR_LIMIT		0x01
-#define RED_ATTR_QTH_MIN	0x02
-#define RED_ATTR_QTH_MAX	0x04
-#define RED_ATTR_FLAGS		0x08
-#define RED_ATTR_WLOG		0x10
-#define RED_ATTR_PLOG		0x20
-#define RED_ATTR_SCELL_LOG	0x40
-/** @endcond */
-
-static inline struct rtnl_red *red_qdisc(struct rtnl_qdisc *qdisc)
-{
-	return (struct rtnl_red *) qdisc->q_subdata;
-}
-
-static inline struct rtnl_red *red_alloc(struct rtnl_qdisc *qdisc)
-{
-	if (!qdisc->q_subdata)
-		qdisc->q_subdata = calloc(1, sizeof(struct rtnl_red));
-
-	return red_qdisc(qdisc);
-}
-
-static struct nla_policy red_policy[TCA_RED_MAX+1] = {
-	[TCA_RED_PARMS]		= { .minlen = sizeof(struct tc_red_qopt) },
-};
-
-static int red_msg_parser(struct rtnl_qdisc *qdisc)
-{
-	struct nlattr *tb[TCA_RED_MAX+1];
-	struct rtnl_red *red;
-	struct tc_red_qopt *opts;
-	int err;
-
-	if (!(qdisc->ce_mask & TCA_ATTR_OPTS))
-		return 0;
-
-	err = tca_parse(tb, TCA_RED_MAX, (struct rtnl_tca *) qdisc, red_policy);
-	if (err < 0)
-		return err;
-
-	if (!tb[TCA_RED_PARMS])
-		return -NLE_MISSING_ATTR;
-
-	red = red_alloc(qdisc);
-	if (!red)
-		return -NLE_NOMEM;
-
-	opts = nla_data(tb[TCA_RED_PARMS]);
-
-	red->qr_limit = opts->limit;
-	red->qr_qth_min = opts->qth_min;
-	red->qr_qth_max = opts->qth_max;
-	red->qr_flags = opts->flags;
-	red->qr_wlog = opts->Wlog;
-	red->qr_plog = opts->Plog;
-	red->qr_scell_log = opts->Scell_log;
-
-	red->qr_mask = (RED_ATTR_LIMIT | RED_ATTR_QTH_MIN | RED_ATTR_QTH_MAX |
-			RED_ATTR_FLAGS | RED_ATTR_WLOG | RED_ATTR_PLOG |
-			RED_ATTR_SCELL_LOG);
-
-	return 0;
-}
-
-static void red_dump_line(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
-{
-	struct rtnl_red *red = red_qdisc(qdisc);
-
-	if (red) {
-		/* XXX: limit, min, max, flags */
-	}
-}
-
-static void red_dump_details(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
-{
-	struct rtnl_red *red = red_qdisc(qdisc);
-
-	if (red) {
-		/* XXX: wlog, plog, scell_log */
-	}
-}
-
-static void red_dump_stats(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
-{
-	struct rtnl_red *red = red_qdisc(qdisc);
-
-	if (red) {
-		/* XXX: xstats */
-	}
-}
-
-static struct nl_msg *red_get_opts(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_red *red;
-	struct nl_msg *msg;
-
-	red = red_qdisc(qdisc);
-	if (!red)
-		return NULL;
-
-	msg = nlmsg_alloc();
-	if (!msg)
-		goto errout;
-
-#if 0
-	memset(&opts, 0, sizeof(opts));
-	opts.quantum = sfq->qs_quantum;
-	opts.perturb_period = sfq->qs_perturb;
-	opts.limit = sfq->qs_limit;
-
-	if (nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD) < 0)
-		goto errout;
-#endif
-
-	return msg;
-errout:
-	nlmsg_free(msg);
-	return NULL;
-}
-
-/**
- * @name Attribute Access
- * @{
- */
-
-/**
- * Set limit of RED qdisc.
- * @arg qdisc		RED qdisc to be modified.
- * @arg limit		New limit in number of packets.
- * @return 0 on success or a negative error code.
- */
-int rtnl_red_set_limit(struct rtnl_qdisc *qdisc, int limit)
-{
-	struct rtnl_red *red;
-
-	red = red_alloc(qdisc);
-	if (!red)
-		return -NLE_NOMEM;
-
-	red->qr_limit = limit;
-	red->qr_mask |= RED_ATTR_LIMIT;
-
-	return 0;
-}
-
-/**
- * Get limit of RED qdisc.
- * @arg qdisc		RED qdisc.
- * @return Limit or a negative error code.
- */
-int rtnl_red_get_limit(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_red *red;
-
-	red = red_qdisc(qdisc);
-	if (red && (red->qr_mask & RED_ATTR_LIMIT))
-		return red->qr_limit;
-	else
-		return -NLE_NOATTR;
-}
-
-/** @} */
-
-static struct rtnl_qdisc_ops red_ops = {
-	.qo_kind		= "red",
-	.qo_msg_parser		= red_msg_parser,
-	.qo_dump = {
-	    [NL_DUMP_LINE]	= red_dump_line,
-	    [NL_DUMP_DETAILS]	= red_dump_details,
-	    [NL_DUMP_STATS]	= red_dump_stats,
-	},
-	.qo_get_opts		= red_get_opts,
-};
-
-static void __init red_init(void)
-{
-	rtnl_qdisc_register(&red_ops);
-}
-
-static void __exit red_exit(void)
-{
-	rtnl_qdisc_unregister(&red_ops);
-}
-
-/** @} */
diff --git a/lib/route/sch/sfq.c b/lib/route/sch/sfq.c
deleted file mode 100644
index 4b47356..0000000
--- a/lib/route/sch/sfq.c
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * lib/route/sch/sfq.c		SFQ Qdisc
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
- */
-
-/**
- * @ingroup qdisc_api
- * @defgroup sfq Stochastic Fairness Queueing (SFQ)
- * @brief
- *
- * @par Parameter Description
- * - \b Quantum: Number of bytes to send out per slot and round.
- * - \b Perturbation: Timer period between changing the hash function.
- * - \b Limit:  Upper limit of queue in number of packets before SFQ starts
- *	        dropping packets.
- * - \b Divisor: Hash table divisor, i.e. size of hash table.
- * @{
- */
-
-#include <netlink-local.h>
-#include <netlink-tc.h>
-#include <netlink/netlink.h>
-#include <netlink/utils.h>
-#include <netlink/route/qdisc.h>
-#include <netlink/route/qdisc-modules.h>
-#include <netlink/route/sch/sfq.h>
-
-/** @cond SKIP */
-#define SCH_SFQ_ATTR_QUANTUM	0x01
-#define SCH_SFQ_ATTR_PERTURB	0x02
-#define SCH_SFQ_ATTR_LIMIT	0x04
-#define SCH_SFQ_ATTR_DIVISOR	0x08
-#define SCH_SFQ_ATTR_FLOWS	0x10
-/** @endcond */
-
-static inline struct rtnl_sfq *sfq_qdisc(struct rtnl_qdisc *qdisc)
-{
-	return (struct rtnl_sfq *) qdisc->q_subdata;
-}
-
-static inline struct rtnl_sfq *sfq_alloc(struct rtnl_qdisc *qdisc)
-{
-	if (!qdisc->q_subdata)
-		qdisc->q_subdata = calloc(1, sizeof(struct rtnl_sfq));
-
-	return sfq_qdisc(qdisc);
-}
-
-static int sfq_msg_parser(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_sfq *sfq;
-	struct tc_sfq_qopt *opts;
-
-	if (!(qdisc->ce_mask & TCA_ATTR_OPTS))
-		return 0;
-
-	if (qdisc->q_opts->d_size < sizeof(*opts))
-		return -NLE_INVAL;
-
-	sfq = sfq_alloc(qdisc);
-	if (!sfq)
-		return -NLE_NOMEM;
-
-	opts = (struct tc_sfq_qopt *) qdisc->q_opts->d_data;
-
-	sfq->qs_quantum = opts->quantum;
-	sfq->qs_perturb = opts->perturb_period;
-	sfq->qs_limit = opts->limit;
-	sfq->qs_divisor = opts->divisor;
-	sfq->qs_flows = opts->flows;
-
-	sfq->qs_mask = (SCH_SFQ_ATTR_QUANTUM | SCH_SFQ_ATTR_PERTURB |
-			SCH_SFQ_ATTR_LIMIT | SCH_SFQ_ATTR_DIVISOR |
-			SCH_SFQ_ATTR_FLOWS);
-
-	return 0;
-}
-
-static void sfq_free_data(struct rtnl_qdisc *qdisc)
-{
-	free(qdisc->q_subdata);
-}
-
-static void sfq_dump_line(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
-{
-	struct rtnl_sfq *sfq = sfq_qdisc(qdisc);
-
-	if (sfq)
-		nl_dump(p, " quantum %u perturb %us", sfq->qs_quantum,
-			nl_ticks2us(sfq->qs_perturb * nl_get_hz()));
-}
-
-static void sfq_dump_details(struct rtnl_qdisc *qdisc, struct nl_dump_params *p)
-{
-	struct rtnl_sfq *sfq = sfq_qdisc(qdisc);
-
-	if (sfq)
-		nl_dump(p, "limit %u divisor %u",
-			sfq->qs_limit, sfq->qs_divisor);
-}
-
-static struct nl_msg *sfq_get_opts(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_sfq *sfq;
-	struct tc_sfq_qopt opts;
-	struct nl_msg *msg;
-
-	sfq = sfq_qdisc(qdisc);
-	if (!sfq)
-		return NULL;
-
-	msg = nlmsg_alloc();
-	if (!msg)
-		goto errout;
-
-	memset(&opts, 0, sizeof(opts));
-	opts.quantum = sfq->qs_quantum;
-	opts.perturb_period = sfq->qs_perturb;
-	opts.limit = sfq->qs_limit;
-
-	if (nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD) < 0)
-		goto errout;
-
-	return msg;
-errout:
-	nlmsg_free(msg);
-	return NULL;
-}
-
-/**
- * @name Attribute Access
- * @{
- */
-
-/**
- * Set quantum of SFQ qdisc.
- * @arg qdisc		SFQ qdisc to be modified.
- * @arg quantum		New quantum in bytes.
- * @return 0 on success or a negative error code.
- */
-int rtnl_sfq_set_quantum(struct rtnl_qdisc *qdisc, int quantum)
-{
-	struct rtnl_sfq *sfq;
-	
-	sfq = sfq_alloc(qdisc);
-	if (!sfq)
-		return -NLE_NOMEM;
-
-	sfq->qs_quantum = quantum;
-	sfq->qs_mask |= SCH_SFQ_ATTR_QUANTUM;
-
-	return 0;
-}
-
-/**
- * Get quantum of SFQ qdisc.
- * @arg qdisc		SFQ qdisc.
- * @return Quantum in bytes or a negative error code.
- */
-int rtnl_sfq_get_quantum(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_sfq *sfq;
-
-	sfq = sfq_qdisc(qdisc);
-	if (sfq && sfq->qs_mask & SCH_SFQ_ATTR_QUANTUM)
-		return sfq->qs_quantum;
-	else
-		return -NLE_NOATTR;
-}
-
-/**
- * Set limit of SFQ qdisc.
- * @arg qdisc		SFQ qdisc to be modified.
- * @arg limit		New limit in number of packets.
- * @return 0 on success or a negative error code.
- */
-int rtnl_sfq_set_limit(struct rtnl_qdisc *qdisc, int limit)
-{
-	struct rtnl_sfq *sfq;
-
-	sfq = sfq_alloc(qdisc);
-	if (!sfq)
-		return -NLE_NOMEM;
-
-	sfq->qs_limit = limit;
-	sfq->qs_mask |= SCH_SFQ_ATTR_LIMIT;
-
-	return 0;
-}
-
-/**
- * Get limit of SFQ qdisc.
- * @arg qdisc		SFQ qdisc.
- * @return Limit or a negative error code.
- */
-int rtnl_sfq_get_limit(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_sfq *sfq;
-
-	sfq = sfq_qdisc(qdisc);
-	if (sfq && sfq->qs_mask & SCH_SFQ_ATTR_LIMIT)
-		return sfq->qs_limit;
-	else
-		return -NLE_NOATTR;
-}
-
-/**
- * Set perturbation interval of SFQ qdisc.
- * @arg qdisc		SFQ qdisc to be modified.
- * @arg perturb		New perturbation interval in seconds.
- * @note A value of 0 disables perturbation altogether.
- * @return 0 on success or a negative error code.
- */
-int rtnl_sfq_set_perturb(struct rtnl_qdisc *qdisc, int perturb)
-{
-	struct rtnl_sfq *sfq;
-
-	sfq = sfq_alloc(qdisc);
-	if (!sfq)
-		return -NLE_NOMEM;
-
-	sfq->qs_perturb = perturb;
-	sfq->qs_mask |= SCH_SFQ_ATTR_PERTURB;
-
-	return 0;
-}
-
-/**
- * Get perturbation interval of SFQ qdisc.
- * @arg qdisc		SFQ qdisc.
- * @return Perturbation interval in seconds or a negative error code.
- */
-int rtnl_sfq_get_perturb(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_sfq *sfq;
-
-	sfq = sfq_qdisc(qdisc);
-	if (sfq && sfq->qs_mask & SCH_SFQ_ATTR_PERTURB)
-		return sfq->qs_perturb;
-	else
-		return -NLE_NOATTR;
-}
-
-/**
- * Get divisor of SFQ qdisc.
- * @arg qdisc		SFQ qdisc.
- * @return Divisor in number of entries or a negative error code.
- */
-int rtnl_sfq_get_divisor(struct rtnl_qdisc *qdisc)
-{
-	struct rtnl_sfq *sfq;
-
-	sfq = sfq_qdisc(qdisc);
-	if (sfq && sfq->qs_mask & SCH_SFQ_ATTR_DIVISOR)
-		return sfq->qs_divisor;
-	else
-		return -NLE_NOATTR;
-}
-
-/** @} */
-
-static struct rtnl_qdisc_ops sfq_ops = {
-	.qo_kind		= "sfq",
-	.qo_msg_parser		= sfq_msg_parser,
-	.qo_free_data		= sfq_free_data,
-	.qo_dump = {
-	    [NL_DUMP_LINE]	= sfq_dump_line,
-	    [NL_DUMP_DETAILS]	= sfq_dump_details,
-	},
-	.qo_get_opts		= sfq_get_opts,
-};
-
-static void __init sfq_init(void)
-{
-	rtnl_qdisc_register(&sfq_ops);
-}
-
-static void __exit sfq_exit(void)
-{
-	rtnl_qdisc_unregister(&sfq_ops);
-}
-
-/** @} */
diff --git a/lib/route/tc.c b/lib/route/tc.c
index 97faef4..0f150cc 100644
--- a/lib/route/tc.c
+++ b/lib/route/tc.c
@@ -6,26 +6,29 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup rtnl
  * @defgroup tc Traffic Control
- * @brief
  * @{
  */
 
-#include <netlink-local.h>
-#include <netlink-tc.h>
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/route/rtnl.h>
 #include <netlink/route/link.h>
 #include <netlink/route/tc.h>
+#include <netlink-private/route/tc-api.h>
 
 /** @cond SKIP */
 
+static struct nl_list_head tc_ops_list[__RTNL_TC_TYPE_MAX];
+static struct rtnl_tc_type_ops *tc_type_ops[__RTNL_TC_TYPE_MAX];
+
 static struct nla_policy tc_policy[TCA_MAX+1] = {
 	[TCA_KIND]	= { .type = NLA_STRING,
 			    .maxlen = TCKINDSIZ },
@@ -33,7 +36,7 @@
 	[TCA_STATS2]	= { .type = NLA_NESTED },
 };
 
-int tca_parse(struct nlattr **tb, int maxattr, struct rtnl_tca *g,
+int tca_parse(struct nlattr **tb, int maxattr, struct rtnl_tc *g,
 	      struct nla_policy *policy)
 {
 	
@@ -55,12 +58,17 @@
 	[TCA_STATS_QUEUE]    = { .minlen = sizeof(struct gnet_stats_queue) },
 };
 
-int tca_msg_parser(struct nlmsghdr *n, struct rtnl_tca *g)
+int rtnl_tc_msg_parse(struct nlmsghdr *n, struct rtnl_tc *tc)
 {
+	struct nl_cache *link_cache;
+	struct rtnl_tc_ops *ops;
 	struct nlattr *tb[TCA_MAX + 1];
+	char kind[TCKINDSIZ];
 	struct tcmsg *tm;
 	int err;
 
+	tc->ce_msgtype = n->nlmsg_type;
+
 	err = nlmsg_parse(n, sizeof(*tm), tb, TCA_MAX, tc_policy);
 	if (err < 0)
 		return err;
@@ -68,25 +76,25 @@
 	if (tb[TCA_KIND] == NULL)
 		return -NLE_MISSING_ATTR;
 
-	nla_strlcpy(g->tc_kind, tb[TCA_KIND], TCKINDSIZ);
+	nla_strlcpy(kind, tb[TCA_KIND], sizeof(kind));
+	rtnl_tc_set_kind(tc, kind);
 
 	tm = nlmsg_data(n);
-	g->tc_family  = tm->tcm_family;
-	g->tc_ifindex = tm->tcm_ifindex;
-	g->tc_handle  = tm->tcm_handle;
-	g->tc_parent  = tm->tcm_parent;
-	g->tc_info    = tm->tcm_info;
+	tc->tc_family  = tm->tcm_family;
+	tc->tc_ifindex = tm->tcm_ifindex;
+	tc->tc_handle  = tm->tcm_handle;
+	tc->tc_parent  = tm->tcm_parent;
+	tc->tc_info    = tm->tcm_info;
 
-	g->ce_mask = (TCA_ATTR_FAMILY | TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE |
-		      TCA_ATTR_PARENT | TCA_ATTR_INFO | TCA_ATTR_KIND);
+	tc->ce_mask |= (TCA_ATTR_FAMILY | TCA_ATTR_IFINDEX | TCA_ATTR_HANDLE|
+		        TCA_ATTR_PARENT | TCA_ATTR_INFO);
 
 	if (tb[TCA_OPTIONS]) {
-		g->tc_opts = nl_data_alloc_attr(tb[TCA_OPTIONS]);
-		if (!g->tc_opts)
+		tc->tc_opts = nl_data_alloc_attr(tb[TCA_OPTIONS]);
+		if (!tc->tc_opts)
 			return -NLE_NOMEM;
-		g->ce_mask |= TCA_ATTR_OPTS;
+		tc->ce_mask |= TCA_ATTR_OPTS;
 	}
-	
 
 	if (tb[TCA_STATS2]) {
 		struct nlattr *tbs[TCA_STATS_MAX + 1];
@@ -100,34 +108,34 @@
 			struct gnet_stats_basic *bs;
 			
 			bs = nla_data(tbs[TCA_STATS_BASIC]);
-			g->tc_stats[RTNL_TC_BYTES]	= bs->bytes;
-			g->tc_stats[RTNL_TC_PACKETS]	= bs->packets;
+			tc->tc_stats[RTNL_TC_BYTES]	= bs->bytes;
+			tc->tc_stats[RTNL_TC_PACKETS]	= bs->packets;
 		}
 
 		if (tbs[TCA_STATS_RATE_EST]) {
 			struct gnet_stats_rate_est *re;
 
 			re = nla_data(tbs[TCA_STATS_RATE_EST]);
-			g->tc_stats[RTNL_TC_RATE_BPS]	= re->bps;
-			g->tc_stats[RTNL_TC_RATE_PPS]	= re->pps;
+			tc->tc_stats[RTNL_TC_RATE_BPS]	= re->bps;
+			tc->tc_stats[RTNL_TC_RATE_PPS]	= re->pps;
 		}
 		
 		if (tbs[TCA_STATS_QUEUE]) {
 			struct gnet_stats_queue *q;
 
 			q = nla_data(tbs[TCA_STATS_QUEUE]);
-			g->tc_stats[RTNL_TC_QLEN]	= q->qlen;
-			g->tc_stats[RTNL_TC_BACKLOG]	= q->backlog;
-			g->tc_stats[RTNL_TC_DROPS]	= q->drops;
-			g->tc_stats[RTNL_TC_REQUEUES]	= q->requeues;
-			g->tc_stats[RTNL_TC_OVERLIMITS]	= q->overlimits;
+			tc->tc_stats[RTNL_TC_QLEN]	= q->qlen;
+			tc->tc_stats[RTNL_TC_BACKLOG]	= q->backlog;
+			tc->tc_stats[RTNL_TC_DROPS]	= q->drops;
+			tc->tc_stats[RTNL_TC_REQUEUES]	= q->requeues;
+			tc->tc_stats[RTNL_TC_OVERLIMITS]	= q->overlimits;
 		}
 
-		g->ce_mask |= TCA_ATTR_STATS;
+		tc->ce_mask |= TCA_ATTR_STATS;
 		
 		if (tbs[TCA_STATS_APP]) {
-			g->tc_xstats = nl_data_alloc_attr(tbs[TCA_STATS_APP]);
-			if (g->tc_xstats == NULL)
+			tc->tc_xstats = nl_data_alloc_attr(tbs[TCA_STATS_APP]);
+			if (tc->tc_xstats == NULL)
 				return -NLE_NOMEM;
 		} else
 			goto compat_xstats;
@@ -135,203 +143,65 @@
 		if (tb[TCA_STATS]) {
 			struct tc_stats *st = nla_data(tb[TCA_STATS]);
 
-			g->tc_stats[RTNL_TC_BYTES]	= st->bytes;
-			g->tc_stats[RTNL_TC_PACKETS]	= st->packets;
-			g->tc_stats[RTNL_TC_RATE_BPS]	= st->bps;
-			g->tc_stats[RTNL_TC_RATE_PPS]	= st->pps;
-			g->tc_stats[RTNL_TC_QLEN]	= st->qlen;
-			g->tc_stats[RTNL_TC_BACKLOG]	= st->backlog;
-			g->tc_stats[RTNL_TC_DROPS]	= st->drops;
-			g->tc_stats[RTNL_TC_OVERLIMITS]	= st->overlimits;
+			tc->tc_stats[RTNL_TC_BYTES]	= st->bytes;
+			tc->tc_stats[RTNL_TC_PACKETS]	= st->packets;
+			tc->tc_stats[RTNL_TC_RATE_BPS]	= st->bps;
+			tc->tc_stats[RTNL_TC_RATE_PPS]	= st->pps;
+			tc->tc_stats[RTNL_TC_QLEN]	= st->qlen;
+			tc->tc_stats[RTNL_TC_BACKLOG]	= st->backlog;
+			tc->tc_stats[RTNL_TC_DROPS]	= st->drops;
+			tc->tc_stats[RTNL_TC_OVERLIMITS]= st->overlimits;
 
-			g->ce_mask |= TCA_ATTR_STATS;
+			tc->ce_mask |= TCA_ATTR_STATS;
 		}
 
 compat_xstats:
 		if (tb[TCA_XSTATS]) {
-			g->tc_xstats = nl_data_alloc_attr(tb[TCA_XSTATS]);
-			if (g->tc_xstats == NULL)
+			tc->tc_xstats = nl_data_alloc_attr(tb[TCA_XSTATS]);
+			if (tc->tc_xstats == NULL)
 				return -NLE_NOMEM;
-			g->ce_mask |= TCA_ATTR_XSTATS;
+			tc->ce_mask |= TCA_ATTR_XSTATS;
 		}
 	}
 
+	ops = rtnl_tc_get_ops(tc);
+	if (ops && ops->to_msg_parser) {
+		void *data = rtnl_tc_data(tc);
 
-	return 0;
-}
-
-void tca_free_data(struct rtnl_tca *tca)
-{
-	nl_data_free(tca->tc_opts);
-	nl_data_free(tca->tc_xstats);
-}
-
-int tca_clone(struct rtnl_tca *dst, struct rtnl_tca *src)
-{
-	if (src->tc_opts) {
-		dst->tc_opts = nl_data_clone(src->tc_opts);
-		if (!dst->tc_opts)
+		if (!data)
 			return -NLE_NOMEM;
+
+		err = ops->to_msg_parser(tc, data);
+		if (err < 0)
+			return err;
 	}
-	
-	if (src->tc_xstats) {
-		dst->tc_xstats = nl_data_clone(src->tc_xstats);
-		if (!dst->tc_xstats)
-			return -NLE_NOMEM;
+
+	if ((link_cache = __nl_cache_mngt_require("route/link"))) {
+		struct rtnl_link *link;
+
+		if ((link = rtnl_link_get(link_cache, tc->tc_ifindex))) {
+			rtnl_tc_set_link(tc, link);
+
+			/* rtnl_tc_set_link incs refcnt */
+			rtnl_link_put(link);
+		}
 	}
 
 	return 0;
 }
 
-void tca_dump_line(struct rtnl_tca *g, const char *type,
-		   struct nl_dump_params *p)
-{
-	char handle[32], parent[32];
-	struct nl_cache *link_cache;
-	
-	link_cache = nl_cache_mngt_require("route/link");
-
-	nl_dump_line(p, "%s %s ", g->tc_kind, type);
-
-	if (link_cache) {
-		char buf[32];
-		nl_dump(p, "dev %s ",
-			rtnl_link_i2name(link_cache, g->tc_ifindex,
-					 buf, sizeof(buf)));
-	} else
-		nl_dump(p, "dev %u ", g->tc_ifindex);
-	
-	nl_dump(p, "handle %s parent %s",
-		rtnl_tc_handle2str(g->tc_handle, handle, sizeof(handle)),
-		rtnl_tc_handle2str(g->tc_parent, parent, sizeof(parent)));
-}
-
-void tca_dump_details(struct rtnl_tca *g, struct nl_dump_params *p)
-{
-	nl_dump_line(p, "  ");
-}
-
-void tca_dump_stats(struct rtnl_tca *g, struct nl_dump_params *p)
-{
-	char *unit, fmt[64];
-	float res;
-	strcpy(fmt, "        %7.2f %s %10u %10u %10u %10u %10u\n");
-
-	nl_dump_line(p, 
-		"    Stats:    bytes    packets      drops overlimits" \
-		"       qlen    backlog\n");
-
-	res = nl_cancel_down_bytes(g->tc_stats[RTNL_TC_BYTES], &unit);
-	if (*unit == 'B')
-		fmt[11] = '9';
-
-	nl_dump_line(p, fmt, res, unit,
-		g->tc_stats[RTNL_TC_PACKETS],
-		g->tc_stats[RTNL_TC_DROPS],
-		g->tc_stats[RTNL_TC_OVERLIMITS],
-		g->tc_stats[RTNL_TC_QLEN],
-		g->tc_stats[RTNL_TC_BACKLOG]);
-
-	res = nl_cancel_down_bytes(g->tc_stats[RTNL_TC_RATE_BPS], &unit);
-
-	strcpy(fmt, "        %7.2f %s/s%9u pps");
-
-	if (*unit == 'B')
-		fmt[11] = '9';
-
-	nl_dump_line(p, fmt, res, unit, g->tc_stats[RTNL_TC_RATE_PPS]);
-}
-
-int tca_compare(struct nl_object *_a, struct nl_object *_b,
-		uint32_t attrs, int flags)
-{
-	struct rtnl_tca *a = (struct rtnl_tca *) _a;
-	struct rtnl_tca *b = (struct rtnl_tca *) _b;
-	int diff = 0;
-
-#define TC_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, TCA_ATTR_##ATTR, a, b, EXPR)
-
-	diff |= TC_DIFF(HANDLE,		a->tc_handle != b->tc_handle);
-	diff |= TC_DIFF(PARENT,		a->tc_parent != b->tc_parent);
-	diff |= TC_DIFF(IFINDEX,	a->tc_ifindex != b->tc_ifindex);
-	diff |= TC_DIFF(KIND,		strcmp(a->tc_kind, b->tc_kind));
-
-#undef TC_DIFF
-
-	return diff;
-}
-
-void tca_set_ifindex(struct rtnl_tca *t, int ifindex)
-{
-	t->tc_ifindex = ifindex;
-	t->ce_mask |= TCA_ATTR_IFINDEX;
-}
-
-int tca_get_ifindex(struct rtnl_tca *t)
-{
-	return t->tc_ifindex;
-}
-
-void tca_set_handle(struct rtnl_tca *t, uint32_t handle)
-{
-	t->tc_handle = handle;
-	t->ce_mask |= TCA_ATTR_HANDLE;
-}
-
-uint32_t tca_get_handle(struct rtnl_tca *t)
-{
-	if (t->ce_mask & TCA_ATTR_HANDLE)
-		return t->tc_handle;
-	else
-		return 0;
-}
-
-void tca_set_parent(struct rtnl_tca *t, uint32_t parent)
-{
-	t->tc_parent = parent;
-	t->ce_mask |= TCA_ATTR_PARENT;
-}
-
-uint32_t tca_get_parent(struct rtnl_tca *t)
-{
-	if (t->ce_mask & TCA_ATTR_PARENT)
-		return t->tc_parent;
-	else
-		return 0;
-}
-
-void tca_set_kind(struct rtnl_tca *t, const char *kind)
-{
-	strncpy(t->tc_kind, kind, sizeof(t->tc_kind) - 1);
-	t->ce_mask |= TCA_ATTR_KIND;
-}
-
-char *tca_get_kind(struct rtnl_tca *t)
-{
-	if (t->ce_mask & TCA_ATTR_KIND)
-		return t->tc_kind;
-	else
-		return NULL;
-}
-
-uint64_t tca_get_stat(struct rtnl_tca *t, int id)
-{
-	if (id < 0 || id > RTNL_TC_STATS_MAX)
-		return 0;
-
-	return t->tc_stats[id];
-}
-
-int tca_build_msg(struct rtnl_tca *tca, int type, int flags,
-		  struct nl_msg **result)
+int rtnl_tc_msg_build(struct rtnl_tc *tc, int type, int flags,
+		      struct nl_msg **result)
 {
 	struct nl_msg *msg;
+	struct rtnl_tc_ops *ops;
 	struct tcmsg tchdr = {
 		.tcm_family = AF_UNSPEC,
-		.tcm_ifindex = tca->tc_ifindex,
-		.tcm_handle = tca->tc_handle,
-		.tcm_parent = tca->tc_parent,
+		.tcm_ifindex = tc->tc_ifindex,
+		.tcm_handle = tc->tc_handle,
+		.tcm_parent = tc->tc_parent,
 	};
+	int err = -NLE_MSGSIZE;
 
 	msg = nlmsg_alloc_simple(type, flags);
 	if (!msg)
@@ -340,20 +210,355 @@
 	if (nlmsg_append(msg, &tchdr, sizeof(tchdr), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
 
-	if (tca->ce_mask & TCA_ATTR_KIND)
-	    NLA_PUT_STRING(msg, TCA_KIND, tca->tc_kind);
+	if (tc->ce_mask & TCA_ATTR_KIND)
+	    NLA_PUT_STRING(msg, TCA_KIND, tc->tc_kind);
+
+	ops = rtnl_tc_get_ops(tc);
+	if (ops && (ops->to_msg_fill || ops->to_msg_fill_raw)) {
+		struct nlattr *opts;
+		void *data = rtnl_tc_data(tc);
+
+		if (ops->to_msg_fill) {
+			if (!(opts = nla_nest_start(msg, TCA_OPTIONS)))
+				goto nla_put_failure;
+
+			if ((err = ops->to_msg_fill(tc, data, msg)) < 0)
+				goto nla_put_failure;
+
+			nla_nest_end(msg, opts);
+		} else if ((err = ops->to_msg_fill_raw(tc, data, msg)) < 0)
+			goto nla_put_failure;
+	}
 
 	*result = msg;
 	return 0;
 
 nla_put_failure:
 	nlmsg_free(msg);
-	return -NLE_MSGSIZE;
+	return err;
 }
 
+void tca_set_kind(struct rtnl_tc *t, const char *kind)
+{
+	strncpy(t->tc_kind, kind, sizeof(t->tc_kind) - 1);
+	t->ce_mask |= TCA_ATTR_KIND;
+}
+
+
 /** @endcond */
 
 /**
+ * @name Attributes
+ * @{
+ */
+
+/**
+ * Set interface index of traffic control object
+ * @arg tc		traffic control object
+ * @arg ifindex		interface index.
+ *
+ * Sets the interface index of a traffic control object. The interface
+ * index defines the network device which this tc object is attached to.
+ * This function will overwrite any network device assigned with previous
+ * calls to rtnl_tc_set_ifindex() or rtnl_tc_set_link().
+ */
+void rtnl_tc_set_ifindex(struct rtnl_tc *tc, int ifindex)
+{
+	/* Obsolete possible old link reference */
+	rtnl_link_put(tc->tc_link);
+	tc->tc_link = NULL;
+	tc->ce_mask &= ~TCA_ATTR_LINK;
+
+	tc->tc_ifindex = ifindex;
+	tc->ce_mask |= TCA_ATTR_IFINDEX;
+}
+
+/**
+ * Return interface index of traffic control object
+ * @arg tc		traffic control object
+ */
+int rtnl_tc_get_ifindex(struct rtnl_tc *tc)
+{
+	return tc->tc_ifindex;
+}
+
+/**
+ * Set link of traffic control object
+ * @arg tc		traffic control object
+ * @arg link		link object
+ *
+ * Sets the link of a traffic control object. This function serves
+ * the same purpose as rtnl_tc_set_ifindex() but due to the continued
+ * allowed access to the link object it gives it the possibility to
+ * retrieve sane default values for the the MTU and the linktype.
+ * Always prefer this function over rtnl_tc_set_ifindex() if you can
+ * spare to have an additional link object around.
+ */
+void rtnl_tc_set_link(struct rtnl_tc *tc, struct rtnl_link *link)
+{
+	rtnl_link_put(tc->tc_link);
+
+	if (!link)
+		return;
+	if (!link->l_index)
+		BUG();
+
+	nl_object_get(OBJ_CAST(link));
+	tc->tc_link = link;
+	tc->tc_ifindex = link->l_index;
+	tc->ce_mask |= TCA_ATTR_LINK | TCA_ATTR_IFINDEX;
+}
+
+/**
+ * Get link of traffic control object
+ * @arg tc		traffic control object
+ *
+ * Returns the link of a traffic control object. The link is only
+ * returned if it has been set before via rtnl_tc_set_link() or
+ * if a link cache was available while parsing the tc object. This
+ * function may still return NULL even if an ifindex is assigned to
+ * the tc object. It will _not_ look up the link by itself.
+ *
+ * @note The returned link will have its reference counter incremented.
+ *       It is in the responsibility of the caller to return the
+ *       reference.
+ *
+ * @return link object or NULL if not set.
+ */
+struct rtnl_link *rtnl_tc_get_link(struct rtnl_tc *tc)
+{
+	if (tc->tc_link) {
+		nl_object_get(OBJ_CAST(tc->tc_link));
+		return tc->tc_link;
+	}
+
+	return NULL;
+}
+
+/**
+ * Set the Maximum Transmission Unit (MTU) of traffic control object
+ * @arg tc		traffic control object
+ * @arg mtu		largest packet size expected
+ *
+ * Sets the MTU of a traffic control object. Not all traffic control
+ * objects will make use of this but it helps while calculating rate
+ * tables. This value is typically derived directly from the link
+ * the tc object is attached to if the link has been assigned via
+ * rtnl_tc_set_link(). It is usually not necessary to set the MTU
+ * manually, this function is provided to allow overwriting the derived
+ * value.
+ */
+void rtnl_tc_set_mtu(struct rtnl_tc *tc, uint32_t mtu)
+{
+	tc->tc_mtu = mtu;
+	tc->ce_mask |= TCA_ATTR_MTU;
+}
+
+/**
+ * Return the MTU of traffic control object
+ * @arg tc		traffic control object
+ *
+ * Returns the MTU of a traffic control object which has been set via:
+ * -# User specified value set via rtnl_tc_set_mtu()
+ * -# Dervied from link set via rtnl_tc_set_link()
+ * -# Fall back to default: ethernet = 1500
+ */
+uint32_t rtnl_tc_get_mtu(struct rtnl_tc *tc)
+{
+	if (tc->ce_mask & TCA_ATTR_MTU)
+		return tc->tc_mtu;
+	else if (tc->ce_mask & TCA_ATTR_LINK)
+		return tc->tc_link->l_mtu;
+	else
+		return 1500; /* default to ethernet */
+}
+
+/**
+ * Set the Minimum Packet Unit (MPU) of a traffic control object
+ * @arg tc		traffic control object
+ * @arg mpu		minimum packet size expected
+ *
+ * Sets the MPU of a traffic contorl object. It specifies the minimum
+ * packet size to ever hit this traffic control object. Not all traffic
+ * control objects will make use of this but it helps while calculating
+ * rate tables.
+ */
+void rtnl_tc_set_mpu(struct rtnl_tc *tc, uint32_t mpu)
+{
+	tc->tc_mpu = mpu;
+	tc->ce_mask |= TCA_ATTR_MPU;
+}
+
+/**
+ * Return the Minimum Packet Unit (MPU) of a traffic control object
+ * @arg tc		traffic control object
+ *
+ * @return The MPU previously set via rtnl_tc_set_mpu() or 0.
+ */
+uint32_t rtnl_tc_get_mpu(struct rtnl_tc *tc)
+{
+	return tc->tc_mpu;
+}
+
+/**
+ * Set per packet overhead of a traffic control object
+ * @arg tc		traffic control object
+ * @arg overhead	overhead per packet in bytes
+ *
+ * Sets the per packet overhead in bytes occuring on the link not seen
+ * by the kernel. This value can be used to correct size calculations
+ * if the packet size on the wire does not match the packet sizes seen
+ * in the network stack. Not all traffic control objects will make use
+ * this but it helps while calculating accurate packet sizes in the
+ * kernel.
+ */
+void rtnl_tc_set_overhead(struct rtnl_tc *tc, uint32_t overhead)
+{
+	tc->tc_overhead = overhead;
+	tc->ce_mask |= TCA_ATTR_OVERHEAD;
+}
+
+/**
+ * Return per packet overhead of a traffic control object
+ * @arg tc		traffic control object
+ *
+ * @return The overhead previously set by rtnl_tc_set_overhead() or 0.
+ */
+uint32_t rtnl_tc_get_overhead(struct rtnl_tc *tc)
+{
+	return tc->tc_overhead;
+}
+
+/**
+ * Set the linktype of a traffic control object
+ * @arg tc		traffic control object
+ * @arg type		type of link (e.g. ARPHRD_ATM, ARPHRD_ETHER)
+ *
+ * Overwrites the type of link this traffic control object is attached to.
+ * This value is typically derived from the link this tc object is attached
+ * if the link has been assigned via rtnl_tc_set_link(). It is usually not
+ * necessary to set the linktype manually. This function is provided to
+ * allow overwriting the linktype.
+ */
+void rtnl_tc_set_linktype(struct rtnl_tc *tc, uint32_t type)
+{
+	tc->tc_linktype = type;
+	tc->ce_mask |= TCA_ATTR_LINKTYPE;
+}
+
+/**
+ * Return the linktype of a traffic control object
+ * @arg tc		traffic control object
+ *
+ * Returns the linktype of the link the traffic control object is attached to:
+ * -# User specified value via rtnl_tc_set_linktype()
+ * -# Value derived from link set via rtnl_tc_set_link()
+ * -# Default fall-back: ARPHRD_ETHER
+ */
+uint32_t rtnl_tc_get_linktype(struct rtnl_tc *tc)
+{
+	if (tc->ce_mask & TCA_ATTR_LINKTYPE)
+		return tc->tc_linktype;
+	else if (tc->ce_mask & TCA_ATTR_LINK)
+		return tc->tc_link->l_arptype;
+	else
+		return ARPHRD_ETHER; /* default to ethernet */
+}
+
+/**
+ * Set identifier of traffic control object
+ * @arg tc		traffic control object
+ * @arg id		unique identifier
+ */
+void rtnl_tc_set_handle(struct rtnl_tc *tc, uint32_t id)
+{
+	tc->tc_handle = id;
+	tc->ce_mask |= TCA_ATTR_HANDLE;
+}
+
+/**
+ * Return identifier of a traffic control object
+ * @arg tc		traffic control object
+ */
+uint32_t rtnl_tc_get_handle(struct rtnl_tc *tc)
+{
+	return tc->tc_handle;
+}
+
+/**
+ * Set the parent identifier of a traffic control object
+ * @arg tc		traffic control object
+ * @arg parent		identifier of parent traffif control object
+ *
+ */
+void rtnl_tc_set_parent(struct rtnl_tc *tc, uint32_t parent)
+{
+	tc->tc_parent = parent;
+	tc->ce_mask |= TCA_ATTR_PARENT;
+}
+
+/**
+ * Return parent identifier of a traffic control object
+ * @arg tc		traffic control object
+ */
+uint32_t rtnl_tc_get_parent(struct rtnl_tc *tc)
+{
+	return tc->tc_parent;
+}
+
+/**
+ * Define the type of traffic control object
+ * @arg tc		traffic control object
+ * @arg kind		name of the tc object type
+ *
+ * @return 0 on success or a negative error code
+ */
+int rtnl_tc_set_kind(struct rtnl_tc *tc, const char *kind)
+{
+	if (tc->ce_mask & TCA_ATTR_KIND)
+		return -NLE_EXIST;
+
+	strncpy(tc->tc_kind, kind, sizeof(tc->tc_kind) - 1);
+	tc->ce_mask |= TCA_ATTR_KIND;
+
+	/* Force allocation of data */
+	rtnl_tc_data(tc);
+
+	return 0;
+}
+
+/**
+ * Return kind of traffic control object
+ * @arg tc		traffic control object
+ *
+ * @return Kind of traffic control object or NULL if not set.
+ */
+char *rtnl_tc_get_kind(struct rtnl_tc *tc)
+{
+	if (tc->ce_mask & TCA_ATTR_KIND)
+		return tc->tc_kind;
+	else
+		return NULL;
+}
+
+/**
+ * Return value of a statistical counter of a traffic control object
+ * @arg tc		traffic control object
+ * @arg id		identifier of statistical counter
+ *
+ * @return Value of requested statistic counter or 0.
+ */
+uint64_t rtnl_tc_get_stat(struct rtnl_tc *tc, enum rtnl_tc_stat id)
+{
+	if (id < 0 || id > RTNL_TC_STATS_MAX)
+		return 0;
+
+	return tc->tc_stats[id];
+}
+
+/** @} */
+
+/**
  * @name Utilities
  * @{
  */
@@ -428,150 +633,451 @@
  * @{
  */
 
+/*
+ * COPYRIGHT NOTE:
+ * align_to_atm() and adjust_size() derived/coped from iproute2 source.
+ */
+
+/*
+ * The align to ATM cells is used for determining the (ATM) SAR
+ * alignment overhead at the ATM layer. (SAR = Segmentation And
+ * Reassembly).  This is for example needed when scheduling packet on
+ * an ADSL connection.  Note that the extra ATM-AAL overhead is _not_
+ * included in this calculation. This overhead is added in the kernel
+ * before doing the rate table lookup, as this gives better precision
+ * (as the table will always be aligned for 48 bytes).
+ *  --Hawk, d.7/11-2004. <hawk@diku.dk>
+ */
+static unsigned int align_to_atm(unsigned int size)
+{
+	int linksize, cells;
+	cells = size / ATM_CELL_PAYLOAD;
+	if ((size % ATM_CELL_PAYLOAD) > 0)
+		cells++;
+
+	linksize = cells * ATM_CELL_SIZE; /* Use full cell size to add ATM tax */
+	return linksize;
+}
+
+static unsigned int adjust_size(unsigned int size, unsigned int mpu,
+				uint32_t linktype)
+{
+	if (size < mpu)
+		size = mpu;
+
+	switch (linktype) {
+	case ARPHRD_ATM:
+		return align_to_atm(size);
+
+	case ARPHRD_ETHER:
+	default:
+		return size;
+	}
+}
+
 /**
  * Compute a transmission time lookup table
- * @arg dst	 Destination buffer of RTNL_TC_RTABLE_SIZE uint32_t[].
- * @arg mpu	 Minimal size of a packet at all times.
- * @arg overhead Overhead to be added to each packet.
- * @arg cell	 Size of cell, i.e. size of step between entries in bytes.
- * @arg rate	 Rate in bytes per second.
+ * @arg tc		traffic control object
+ * @arg spec		Rate specification
+ * @arg dst		Destination buffer of RTNL_TC_RTABLE_SIZE uint32_t[].
  *
  * Computes a table of RTNL_TC_RTABLE_SIZE entries specyfing the
  * transmission times for various packet sizes, e.g. the transmission
  * time for a packet of size \c pktsize could be looked up:
  * @code
- * txtime = table[pktsize >> log2(cell)];
+ * txtime = table[pktsize >> log2(mtu)];
  * @endcode
  */
-int rtnl_tc_build_rate_table(uint32_t *dst, uint8_t mpu, uint8_t overhead,
-			     int cell, int rate)
+int rtnl_tc_build_rate_table(struct rtnl_tc *tc, struct rtnl_ratespec *spec,
+			     uint32_t *dst)
 {
-	int i, size, cell_log;
+	uint32_t mtu = rtnl_tc_get_mtu(tc);
+	uint32_t linktype = rtnl_tc_get_linktype(tc);
+	uint8_t cell_log = spec->rs_cell_log;
+	unsigned int size, i;
 
-	cell_log = rtnl_tc_calc_cell_log(cell);
-	if (cell_log < 0)
-		return cell_log;
+	spec->rs_mpu = rtnl_tc_get_mpu(tc);
+	spec->rs_overhead = rtnl_tc_get_overhead(tc);
+
+	if (mtu == 0)
+		mtu = 2047;
+
+	if (cell_log == UINT8_MAX) {
+		/*
+		 * cell_log not specified, calculate it. It has to specify the
+		 * minimum number of rshifts required to break the MTU to below
+		 * RTNL_TC_RTABLE_SIZE.
+		 */
+		cell_log = 0;
+		while ((mtu >> cell_log) >= RTNL_TC_RTABLE_SIZE)
+			cell_log++;
+	}
 
 	for (i = 0; i < RTNL_TC_RTABLE_SIZE; i++) {
-		size = (i << cell_log) + overhead;
-		if (size < mpu)
-			size = mpu;
-
-		dst[i] = rtnl_tc_calc_txtime(size, rate);
+		size = adjust_size((i + 1) << cell_log, spec->rs_mpu, linktype);
+		dst[i] = nl_us2ticks(rtnl_tc_calc_txtime(size, spec->rs_rate));
 	}
 
+	spec->rs_cell_align = -1;
+	spec->rs_cell_log = cell_log;
+
 	return 0;
 }
 
 /** @} */
 
 /**
- * @name Traffic Control Handle Translations
- * @{
+ * @name TC implementation of cache functions
  */
 
+void rtnl_tc_free_data(struct nl_object *obj)
+{
+	struct rtnl_tc *tc = TC_CAST(obj);
+	struct rtnl_tc_ops *ops;
+	
+	rtnl_link_put(tc->tc_link);
+	nl_data_free(tc->tc_opts);
+	nl_data_free(tc->tc_xstats);
+
+	if (tc->tc_subdata) {
+		ops = rtnl_tc_get_ops(tc);
+		if (ops && ops->to_free_data)
+			ops->to_free_data(tc, nl_data_get(tc->tc_subdata));
+
+		nl_data_free(tc->tc_subdata);
+	}
+}
+
+int rtnl_tc_clone(struct nl_object *dstobj, struct nl_object *srcobj)
+{
+	struct rtnl_tc *dst = TC_CAST(dstobj);
+	struct rtnl_tc *src = TC_CAST(srcobj);
+	struct rtnl_tc_ops *ops;
+
+	if (src->tc_link) {
+		nl_object_get(OBJ_CAST(src->tc_link));
+		dst->tc_link = src->tc_link;
+	}
+
+	if (src->tc_opts) {
+		dst->tc_opts = nl_data_clone(src->tc_opts);
+		if (!dst->tc_opts)
+			return -NLE_NOMEM;
+	}
+	
+	if (src->tc_xstats) {
+		dst->tc_xstats = nl_data_clone(src->tc_xstats);
+		if (!dst->tc_xstats)
+			return -NLE_NOMEM;
+	}
+
+	if (src->tc_subdata) {
+		if (!(dst->tc_subdata = nl_data_clone(src->tc_subdata))) {
+			return -NLE_NOMEM;
+		}
+	}
+
+	ops = rtnl_tc_get_ops(src);
+	if (ops && ops->to_clone) {
+		void *a = rtnl_tc_data(dst), *b = rtnl_tc_data(src);
+
+		if (!a)
+			return 0;
+		else if (!b)
+			return -NLE_NOMEM;
+
+		return ops->to_clone(a, b);
+	}
+
+	return 0;
+}
+
+static int tc_dump(struct rtnl_tc *tc, enum nl_dump_type type,
+		   struct nl_dump_params *p)
+{
+	struct rtnl_tc_type_ops *type_ops;
+	struct rtnl_tc_ops *ops;
+	void *data = rtnl_tc_data(tc);
+
+	type_ops = tc_type_ops[tc->tc_type];
+	if (type_ops && type_ops->tt_dump[type])
+		type_ops->tt_dump[type](tc, p);
+
+	ops = rtnl_tc_get_ops(tc);
+	if (ops && ops->to_dump[type]) {
+		ops->to_dump[type](tc, data, p);
+		return 1;
+	}
+
+	return 0;
+}
+
+void rtnl_tc_dump_line(struct nl_object *obj, struct nl_dump_params *p)
+{
+	struct rtnl_tc_type_ops *type_ops;
+	struct rtnl_tc *tc = TC_CAST(obj);
+	struct nl_cache *link_cache;
+	char buf[32];
+
+	nl_new_line(p);
+
+	type_ops = tc_type_ops[tc->tc_type];
+	if (type_ops && type_ops->tt_dump_prefix)
+		nl_dump(p, "%s ", type_ops->tt_dump_prefix);
+
+	nl_dump(p, "%s ", tc->tc_kind);
+
+	if ((link_cache = nl_cache_mngt_require_safe("route/link"))) {
+		nl_dump(p, "dev %s ",
+			rtnl_link_i2name(link_cache, tc->tc_ifindex,
+					 buf, sizeof(buf)));
+	} else
+		nl_dump(p, "dev %u ", tc->tc_ifindex);
+	
+	nl_dump(p, "id %s ",
+		rtnl_tc_handle2str(tc->tc_handle, buf, sizeof(buf)));
+	
+	nl_dump(p, "parent %s",
+		rtnl_tc_handle2str(tc->tc_parent, buf, sizeof(buf)));
+
+	tc_dump(tc, NL_DUMP_LINE, p);
+	nl_dump(p, "\n");
+
+	if (link_cache)
+		nl_cache_put(link_cache);
+}
+
+void rtnl_tc_dump_details(struct nl_object *obj, struct nl_dump_params *p)
+{
+	struct rtnl_tc *tc = TC_CAST(obj);
+
+	rtnl_tc_dump_line(OBJ_CAST(tc), p);
+
+	nl_dump_line(p, "  ");
+
+	if (tc->ce_mask & TCA_ATTR_MTU)
+		nl_dump(p, " mtu %u", tc->tc_mtu);
+
+	if (tc->ce_mask & TCA_ATTR_MPU)
+		nl_dump(p, " mpu %u", tc->tc_mpu);
+
+	if (tc->ce_mask & TCA_ATTR_OVERHEAD)
+		nl_dump(p, " overhead %u", tc->tc_overhead);
+
+	if (!tc_dump(tc, NL_DUMP_DETAILS, p))
+		nl_dump(p, "no options");
+	nl_dump(p, "\n");
+}
+
+void rtnl_tc_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
+{
+	struct rtnl_tc *tc = TC_CAST(obj);
+	char *unit, fmt[64];
+	float res;
+
+	rtnl_tc_dump_details(OBJ_CAST(tc), p);
+
+	strcpy(fmt, "        %7.2f %s %10u %10u %10u %10u %10u\n");
+
+	nl_dump_line(p, 
+		"    Stats:    bytes    packets      drops overlimits" \
+		"       qlen    backlog\n");
+
+	res = nl_cancel_down_bytes(tc->tc_stats[RTNL_TC_BYTES], &unit);
+	if (*unit == 'B')
+		fmt[11] = '9';
+
+	nl_dump_line(p, fmt, res, unit,
+		tc->tc_stats[RTNL_TC_PACKETS],
+		tc->tc_stats[RTNL_TC_DROPS],
+		tc->tc_stats[RTNL_TC_OVERLIMITS],
+		tc->tc_stats[RTNL_TC_QLEN],
+		tc->tc_stats[RTNL_TC_BACKLOG]);
+
+	res = nl_cancel_down_bytes(tc->tc_stats[RTNL_TC_RATE_BPS], &unit);
+
+	strcpy(fmt, "        %7.2f %s/s%9u pps");
+
+	if (*unit == 'B')
+		fmt[11] = '9';
+
+	nl_dump_line(p, fmt, res, unit, tc->tc_stats[RTNL_TC_RATE_PPS]);
+
+	tc_dump(tc, NL_DUMP_LINE, p);
+	nl_dump(p, "\n");
+}
+
+int rtnl_tc_compare(struct nl_object *aobj, struct nl_object *bobj,
+		    uint32_t attrs, int flags)
+{
+	struct rtnl_tc *a = TC_CAST(aobj);
+	struct rtnl_tc *b = TC_CAST(bobj);
+	int diff = 0;
+
+#define TC_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, TCA_ATTR_##ATTR, a, b, EXPR)
+
+	diff |= TC_DIFF(HANDLE,		a->tc_handle != b->tc_handle);
+	diff |= TC_DIFF(PARENT,		a->tc_parent != b->tc_parent);
+	diff |= TC_DIFF(IFINDEX,	a->tc_ifindex != b->tc_ifindex);
+	diff |= TC_DIFF(KIND,		strcmp(a->tc_kind, b->tc_kind));
+
+#undef TC_DIFF
+
+	return diff;
+}
+
+/** @} */
+
 /**
- * Convert a traffic control handle to a character string (Reentrant).
- * @arg handle		traffic control handle
- * @arg buf		destination buffer
- * @arg len		buffer length
- *
- * Converts a tarffic control handle to a character string in the
- * form of \c MAJ:MIN and stores it in the specified destination buffer.
- *
- * @return The destination buffer or the type encoded in hexidecimal
- *         form if no match was found.
+ * @name Modules API
  */
-char * rtnl_tc_handle2str(uint32_t handle, char *buf, size_t len)
-{
-	if (TC_H_ROOT == handle)
-		snprintf(buf, len, "root");
-	else if (TC_H_UNSPEC == handle)
-		snprintf(buf, len, "none");
-	else if (0 == TC_H_MAJ(handle))
-		snprintf(buf, len, ":%02x", TC_H_MIN(handle));
-	else if (0 == TC_H_MIN(handle))
-		snprintf(buf, len, "%02x:", TC_H_MAJ(handle) >> 16);
-	else
-		snprintf(buf, len, "%02x:%02x",
-			TC_H_MAJ(handle) >> 16, TC_H_MIN(handle));
 
-	return buf;
+struct rtnl_tc_ops *rtnl_tc_lookup_ops(enum rtnl_tc_type type, const char *kind)
+{
+	struct rtnl_tc_ops *ops;
+
+	nl_list_for_each_entry(ops, &tc_ops_list[type], to_list)
+		if (!strcmp(kind, ops->to_kind))
+			return ops;
+
+	return NULL;
+}
+
+struct rtnl_tc_ops *rtnl_tc_get_ops(struct rtnl_tc *tc)
+{
+	if (!tc->tc_ops)
+		tc->tc_ops = rtnl_tc_lookup_ops(tc->tc_type, tc->tc_kind);
+
+	return tc->tc_ops;
 }
 
 /**
- * Convert a charactering strint to a traffic control handle
- * @arg name		traffic control handle as character string
- * @arg res		destination buffer
- *
- * Converts the provided character string specifying a traffic
- * control handle to the corresponding numeric value.
- *
- * The handle must be provided in one of the following formats:
- *  - root
- *  - none
- *  - XXXX:
- *  - :YYYY
- *  - XXXX:YYYY
- *  - XXXXYYYY
- *
- * @return 0 on success or a negative error code
+ * Register a traffic control module
+ * @arg ops		traffic control module operations
  */
-int rtnl_tc_str2handle(const char *name, uint32_t *res)
+int rtnl_tc_register(struct rtnl_tc_ops *ops)
 {
-	char *colon, *end;
-	uint32_t h;
+	static int init = 0;
 
-	if (!strcasecmp(name, "root")) {
-		*res = TC_H_ROOT;
-		return 0;
+	/*
+	 * Initialiation hack, make sure list is initialized when
+	 * the first tc module registers. Putting this in a
+	 * separate __init would required correct ordering of init
+	 * functions
+	 */
+	if (!init) {
+		int i;
+
+		for (i = 0; i < __RTNL_TC_TYPE_MAX; i++)
+			nl_init_list_head(&tc_ops_list[i]);
+
+		init = 1;
 	}
 
-	if (!strcasecmp(name, "none")) {
-		*res = TC_H_UNSPEC;
-		return 0;
-	}
+	if (!ops->to_kind || ops->to_type > RTNL_TC_TYPE_MAX)
+		BUG();
 
-	h = strtoul(name, &colon, 16);
+	if (rtnl_tc_lookup_ops(ops->to_type, ops->to_kind))
+		return -NLE_EXIST;
 
-	if (colon == name) {
-		/* :YYYY */
-		h = 0;
-		if (':' != *colon)
-			return -NLE_INVAL;
-	}
-
-	if (':' == *colon) {
-		/* check if we would lose bits */
-		if (TC_H_MAJ(h))
-			return -NLE_RANGE;
-		h <<= 16;
-
-		if ('\0' == colon[1]) {
-			/* XXXX: */
-			*res = h;
-		} else {
-			/* XXXX:YYYY */
-			uint32_t l = strtoul(colon+1, &end, 16);
-
-			/* check if we overlap with major part */
-			if (TC_H_MAJ(l))
-				return -NLE_RANGE;
-
-			if ('\0' != *end)
-				return -NLE_INVAL;
-
-			*res = (h | l);
-		}
-	} else if ('\0' == *colon) {
-		/* XXXXYYYY */
-		*res = h;
-	} else
-		return -NLE_INVAL;
+	nl_list_add_tail(&ops->to_list, &tc_ops_list[ops->to_type]);
 
 	return 0;
 }
 
+/**
+ * Unregister a traffic control module
+ * @arg ops		traffic control module operations
+ */
+void rtnl_tc_unregister(struct rtnl_tc_ops *ops)
+{
+	nl_list_del(&ops->to_list);
+}
+
+/**
+ * Return pointer to private data of traffic control object
+ * @arg tc		traffic control object
+ *
+ * Allocates the private traffic control object data section
+ * as necessary and returns it.
+ *
+ * @return Pointer to private tc data or NULL if allocation failed.
+ */
+void *rtnl_tc_data(struct rtnl_tc *tc)
+{
+	if (!tc->tc_subdata) {
+		size_t size;
+
+		if (!tc->tc_ops) {
+			if (!tc->tc_kind)
+				BUG();
+
+			if (!rtnl_tc_get_ops(tc))
+				return NULL;
+		}
+
+		if (!(size = tc->tc_ops->to_size))
+			BUG();
+
+		if (!(tc->tc_subdata = nl_data_alloc(NULL, size)))
+			return NULL;
+	}
+
+	return nl_data_get(tc->tc_subdata);
+}
+
+/**
+ * Check traffic control object type and return private data section 
+ * @arg tc		traffic control object
+ * @arg ops		expected traffic control object operations
+ *
+ * Checks whether the traffic control object matches the type
+ * specified with the traffic control object operations. If the
+ * type matches, the private tc object data is returned. If type
+ * mismatches, APPBUG() will print a application bug warning.
+ *
+ * @see rtnl_tc_data()
+ *
+ * @return Pointer to private tc data or NULL if type mismatches.
+ */
+void *rtnl_tc_data_check(struct rtnl_tc *tc, struct rtnl_tc_ops *ops)
+{
+	if (tc->tc_ops != ops) {
+		char buf[64];
+
+		snprintf(buf, sizeof(buf),
+			 "tc object %p used in %s context but is of type %s",
+			 tc, ops->to_kind, tc->tc_ops->to_kind);
+		APPBUG(buf);
+
+		return NULL;
+	}
+
+	return rtnl_tc_data(tc);
+}
+
+struct nl_af_group tc_groups[] = {
+	{ AF_UNSPEC,	RTNLGRP_TC },
+	{ END_OF_GROUP_LIST },
+};
+
+
+void rtnl_tc_type_register(struct rtnl_tc_type_ops *ops)
+{
+	if (ops->tt_type > RTNL_TC_TYPE_MAX)
+		BUG();
+
+	tc_type_ops[ops->tt_type] = ops;
+}
+
+void rtnl_tc_type_unregister(struct rtnl_tc_type_ops *ops)
+{
+	if (ops->tt_type > RTNL_TC_TYPE_MAX)
+		BUG();
+
+	tc_type_ops[ops->tt_type] = NULL;
+}
+
 /** @} */
 
 /** @} */
diff --git a/lib/socket.c b/lib/socket.c
index 8083bbb..5f61b38 100644
--- a/lib/socket.c
+++ b/lib/socket.c
@@ -6,16 +6,31 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
- * @ingroup core
+ * @ingroup core_types
  * @defgroup socket Socket
+ *
+ * Representation of a netlink socket
+ *
+ * Related sections in the development guide:
+ * - @core_doc{core_sockets, Netlink Sockets}
+ *
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/socket.h>
+ * ~~~~
  */
 
-#include <netlink-local.h>
+#include "defs.h"
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/socket.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <netlink/handlers.h>
@@ -43,17 +58,43 @@
 }
 
 static uint32_t used_ports_map[32];
+static NL_RW_LOCK(port_map_lock);
 
 static uint32_t generate_local_port(void)
 {
-	int i, n;
+	int i, j, n, m;
+	static uint16_t idx_state = 0;
 	uint32_t pid = getpid() & 0x3FFFFF;
 
-	for (i = 0; i < 32; i++) {
+	nl_write_lock(&port_map_lock);
+
+	if (idx_state == 0) {
+		uint32_t t = time(NULL);
+
+		/* from time to time (on average each 2^15 calls), the idx_state will
+		 * be zero again. No problem, just "seed" anew with time(). */
+		idx_state = t ^ (t >> 16) ^ 0x3047;
+	} else
+		idx_state = idx_state + 20011; /* add prime number */
+
+	i = idx_state >> 5;
+	n = idx_state;
+	for (j = 0; j < 32; j++) {
+		/* walk the index somewhat randomized, with always leaving the block
+		 * #0 as last. The reason is that libnl-1 will start at block #0,
+		 * so just leave the first 32 ports preferably for libnl-1 owned sockets
+		 * (this is relevant only if the applications ends up using both versions
+		 * of the library and doesn't hurt otherwise). */
+		if (j == 31)
+			i = 0;
+		else
+			i = (((i-1) + 7) % 31) + 1;
+
 		if (used_ports_map[i] == 0xFFFFFFFF)
 			continue;
 
-		for (n = 0; n < 32; n++) {
+		for (m = 0; m < 32; m++) {
+			n = (n + 13) % 32;
 			if (1UL & (used_ports_map[i] >> n))
 				continue;
 
@@ -62,26 +103,76 @@
 
 			/* PID_MAX_LIMIT is currently at 2^22, leaving 10 bit
 			 * to, i.e. 1024 unique ports per application. */
-			return pid + (n << 22);
 
+			nl_write_unlock(&port_map_lock);
+
+			return pid + (((uint32_t)n) << 22);
 		}
 	}
 
+	nl_write_unlock(&port_map_lock);
+
 	/* Out of sockets in our own PID namespace, what to do? FIXME */
-	return UINT_MAX;
+	NL_DBG(1, "Warning: Ran out of unique local port namespace\n");
+	return UINT32_MAX;
 }
 
 static void release_local_port(uint32_t port)
 {
 	int nr;
+	uint32_t mask;
 
-	if (port == UINT_MAX)
+	if (port == UINT32_MAX)
 		return;
-	
+
+	BUG_ON(port == 0);
+
 	nr = port >> 22;
-	used_ports_map[nr / 32] &= ~(1 << nr % 32);
+	mask = 1UL << (nr % 32);
+	nr /= 32;
+
+	nl_write_lock(&port_map_lock);
+	BUG_ON((used_ports_map[nr] & mask) != mask);
+	used_ports_map[nr] &= ~mask;
+	nl_write_unlock(&port_map_lock);
 }
 
+/** \cond skip */
+void _nl_socket_used_ports_release_all(const uint32_t *used_ports)
+{
+	int i;
+
+	for (i = 0; i < 32; i++) {
+		if (used_ports[i] != 0) {
+			nl_write_lock(&port_map_lock);
+			for (; i < 32; i++) {
+				BUG_ON((used_ports_map[i] & used_ports[i]) != used_ports[i]);
+				used_ports_map[i] &= ~(used_ports[i]);
+			}
+			nl_write_unlock(&port_map_lock);
+			return;
+		}
+	}
+}
+
+void _nl_socket_used_ports_set(uint32_t *used_ports, uint32_t port)
+{
+	int nr;
+	int32_t mask;
+
+	nr = port >> 22;
+	mask = 1UL << (nr % 32);
+	nr /= 32;
+
+	/*
+	BUG_ON(port == UINT32_MAX || port == 0 || (getpid() & 0x3FFFFF) != (port & 0x3FFFFF));
+	BUG_ON(used_ports[nr] & mask);
+	*/
+
+	used_ports[nr] |= mask;
+}
+/** \endcond */
+
 /**
  * @name Allocation
  * @{
@@ -96,15 +187,13 @@
 		return NULL;
 
 	sk->s_fd = -1;
-	sk->s_cb = cb;
+	sk->s_cb = nl_cb_get(cb);
 	sk->s_local.nl_family = AF_NETLINK;
 	sk->s_peer.nl_family = AF_NETLINK;
 	sk->s_seq_expect = sk->s_seq_next = time(0);
-	sk->s_local.nl_pid = generate_local_port();
-	if (sk->s_local.nl_pid == UINT_MAX) {
-		nl_socket_free(sk);
-		return NULL;
-	}
+
+	/* the port is 0 (unspecified), meaning NL_OWN_PORT */
+	sk->s_flags = NL_OWN_PORT;
 
 	return sk;
 }
@@ -117,12 +206,18 @@
 struct nl_sock *nl_socket_alloc(void)
 {
 	struct nl_cb *cb;
-	
+        struct nl_sock *sk;
+
 	cb = nl_cb_alloc(default_cb);
 	if (!cb)
 		return NULL;
 
-	return __alloc_socket(cb);
+        /* will increment cb reference count on success */
+	sk = __alloc_socket(cb);
+
+        nl_cb_put(cb);
+
+        return sk;
 }
 
 /**
@@ -139,7 +234,7 @@
 	if (cb == NULL)
 		BUG();
 
-	return __alloc_socket(nl_cb_get(cb));
+	return __alloc_socket(cb);
 }
 
 /**
@@ -234,13 +329,45 @@
 
 /** @} */
 
+/** \cond skip */
+int _nl_socket_is_local_port_unspecified(struct nl_sock *sk)
+{
+	return (sk->s_local.nl_pid == 0);
+}
+
+uint32_t _nl_socket_generate_local_port_no_release(struct nl_sock *sk)
+{
+	uint32_t port;
+
+	/* reset the port to generate_local_port(), but do not release
+	 * the previously generated port. */
+
+	port = generate_local_port();
+	sk->s_flags &= ~NL_OWN_PORT;
+	sk->s_local.nl_pid = port;
+	return port;
+}
+/** \endcond */
+
 /**
  * @name Source Idenficiation
  * @{
  */
 
-uint32_t nl_socket_get_local_port(struct nl_sock *sk)
+uint32_t nl_socket_get_local_port(const struct nl_sock *sk)
 {
+	if (sk->s_local.nl_pid == 0) {
+		/* modify the const argument sk. This is justified, because
+		 * nobody ever saw the local_port from externally. So, we
+		 * initilize it on first use.
+		 *
+		 * Note that this also means that you cannot call this function
+		 * from multiple threads without synchronization. But nl_sock
+		 * is not automatically threadsafe anyway, so the user is not
+		 * allowed to do that.
+		 */
+		return _nl_socket_generate_local_port_no_release((struct nl_sock *) sk);
+	}
 	return sk->s_local.nl_pid;
 }
 
@@ -249,20 +376,18 @@
  * @arg sk		Netlink socket.
  * @arg port		Local port identifier
  *
- * Assigns a local port identifier to the socket. If port is 0
- * a unique port identifier will be generated automatically.
+ * Assigns a local port identifier to the socket.
+ *
+ * If port is 0, the port is reset to 'unspecified' as it is after newly
+ * calling nl_socket_alloc().
+ * Unspecified means, that the port will be generated automatically later
+ * on first use (either on nl_socket_get_local_port() or nl_connect()).
  */
 void nl_socket_set_local_port(struct nl_sock *sk, uint32_t port)
 {
-	if (port == 0) {
-		port = generate_local_port(); 
-		sk->s_flags &= ~NL_OWN_PORT;
-	} else  {
-		if (!(sk->s_flags & NL_OWN_PORT))
-			release_local_port(sk->s_local.nl_pid);
-		sk->s_flags |= NL_OWN_PORT;
-	}
-
+	if (!(sk->s_flags & NL_OWN_PORT))
+		release_local_port(sk->s_local.nl_pid);
+	sk->s_flags |= NL_OWN_PORT;
 	sk->s_local.nl_pid = port;
 }
 
@@ -300,13 +425,17 @@
 	va_start(ap, group);
 
 	while (group != 0) {
-		if (group < 0)
+		if (group < 0) {
+			va_end(ap);
 			return -NLE_INVAL;
+		}
 
 		err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
 						 &group, sizeof(group));
-		if (err < 0)
+		if (err < 0) {
+			va_end(ap);
 			return -nl_syserr2nlerr(errno);
+		}
 
 		group = va_arg(ap, int);
 	}
@@ -344,13 +473,17 @@
 	va_start(ap, group);
 
 	while (group != 0) {
-		if (group < 0)
+		if (group < 0) {
+			va_end(ap);
 			return -NLE_INVAL;
+		}
 
 		err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP,
 						 &group, sizeof(group));
-		if (err < 0)
+		if (err < 0) {
+			va_end(ap);
 			return -nl_syserr2nlerr(errno);
+		}
 
 		group = va_arg(ap, int);
 	}
@@ -388,7 +521,7 @@
  * @{
  */
 
-uint32_t nl_socket_get_peer_port(struct nl_sock *sk)
+uint32_t nl_socket_get_peer_port(const struct nl_sock *sk)
 {
 	return sk->s_peer.nl_pid;
 }
@@ -398,6 +531,18 @@
 	sk->s_peer.nl_pid = port;
 }
 
+uint32_t nl_socket_get_peer_groups(const struct nl_sock *sk)
+{
+	return sk->s_peer.nl_groups;
+}
+
+void nl_socket_set_peer_groups(struct nl_sock *sk, uint32_t groups)
+{
+	sk->s_peer.nl_groups = groups;
+}
+
+
+
 /** @} */
 
 /**
@@ -405,7 +550,16 @@
  * @{
  */
 
-int nl_socket_get_fd(struct nl_sock *sk)
+/**
+ * Return the file descriptor of the backing socket
+ * @arg sk		Netlink socket
+ *
+ * Only valid after calling nl_connect() to create and bind the respective
+ * socket.
+ *
+ * @return File descriptor or -1 if not available.
+ */
+int nl_socket_get_fd(const struct nl_sock *sk)
 {
 	return sk->s_fd;
 }
@@ -416,7 +570,7 @@
  *
  * @return 0 on success or a negative error code.
  */
-int nl_socket_set_nonblocking(struct nl_sock *sk)
+int nl_socket_set_nonblocking(const struct nl_sock *sk)
 {
 	if (sk->s_fd == -1)
 		return -NLE_BAD_SOCK;
@@ -452,24 +606,27 @@
  * @{
  */
 
-struct nl_cb *nl_socket_get_cb(struct nl_sock *sk)
+struct nl_cb *nl_socket_get_cb(const struct nl_sock *sk)
 {
 	return nl_cb_get(sk->s_cb);
 }
 
 void nl_socket_set_cb(struct nl_sock *sk, struct nl_cb *cb)
 {
+        if (cb == NULL)
+                BUG();
+
 	nl_cb_put(sk->s_cb);
 	sk->s_cb = nl_cb_get(cb);
 }
 
 /**
- * Modify the callback handler associated to the socket
+ * Modify the callback handler associated with the socket
  * @arg sk		Netlink socket.
  * @arg type		which type callback to set
  * @arg kind		kind of callback
  * @arg func		callback function
- * @arg arg		argument to be passwd to callback function
+ * @arg arg		argument to be passed to callback function
  *
  * @see nl_cb_set
  */
@@ -480,6 +637,21 @@
 	return nl_cb_set(sk->s_cb, type, kind, func, arg);
 }
 
+/**
+ * Modify the error callback handler associated with the socket
+ * @arg sk		Netlink socket.
+ * @arg kind		kind of callback
+ * @arg func		callback function
+ * @arg arg		argument to be passed to callback function
+ *
+ * @see nl_cb_err
+ */
+int nl_socket_modify_err_cb(struct nl_sock *sk, enum nl_cb_kind kind,
+			    nl_recvmsg_err_cb_t func, void *arg)
+{
+	return nl_cb_err(sk->s_cb, kind, func, arg);
+}
+
 /** @} */
 
 /**
@@ -529,6 +701,36 @@
 }
 
 /**
+ * Set default message buffer size of netlink socket.
+ * @arg sk		Netlink socket.
+ * @arg bufsize		Default message buffer size in bytes.
+ *
+ * Sets the default message buffer size to the specified length in bytes.
+ * The default message buffer size limits the maximum message size the
+ * socket will be able to receive. It is generally recommneded to specify
+ * a buffer size no less than the size of a memory page.
+ *
+ * @return 0 on success or a negative error code.
+ */
+int nl_socket_set_msg_buf_size(struct nl_sock *sk, size_t bufsize)
+{
+	sk->s_bufsize = bufsize;
+
+	return 0;
+}
+
+/**
+ * Get default message buffer size of netlink socket.
+ * @arg sk		Netlink socket.
+ *
+ * @return Size of default message buffer.
+ */
+size_t nl_socket_get_msg_buf_size(struct nl_sock *sk)
+{
+	return sk->s_bufsize;
+}
+
+/**
  * Enable/disable credential passing on netlink socket.
  * @arg sk		Netlink socket.
  * @arg state		New state (0 - disabled, 1 - enabled)
diff --git a/lib/utils.c b/lib/utils.c
index e1fdae1..7b44243 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -6,25 +6,50 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup core
  * @defgroup utils Utilities
+ *
+ * Collection of helper functions
+ *
  * @{
+ *
+ * Header
+ * ------
+ * ~~~~{.c}
+ * #include <netlink/utils.h>
+ * ~~~~
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/netlink.h>
 #include <netlink/utils.h>
 #include <linux/socket.h>
+#include <stdlib.h> /* exit() */
 
 /**
- * Debug level
+ * Global variable indicating the desired level of debugging output.
+ *
+ * Level | Messages Printed
+ * ----- | ---------------------------------------------------------
+ *     0 | Debugging output disabled
+ *     1 | Warnings, important events and notifications
+ *     2 | More or less important debugging messages
+ *     3 | Repetitive events causing a flood of debugging messages
+ *     4 | Even less important messages
+ *
+ * If available, the variable will be initialized to the value of the
+ * environment variable `NLDBG`. The default value is 0 (disabled).
+ *
+ * For more information, see section @core_doc{_debugging, Debugging}.
  */
 int nl_debug = 0;
 
+/** @cond SKIP */
+#ifdef NL_DEBUG
 struct nl_dump_params nl_debug_dp = {
 	.dp_type = NL_DUMP_DETAILS,
 };
@@ -41,6 +66,7 @@
 
 	nl_debug_dp.dp_fd = stderr;
 }
+#endif
 
 int __nl_read_num_str_file(const char *path, int (*cb)(long, const char *))
 {
@@ -60,33 +86,42 @@
 			continue;
 
 		num = strtol(buf, &end, 0);
-		if (end == buf)
+		if (end == buf) {
+			fclose(fd);
 			return -NLE_INVAL;
+		}
 
-		if (num == LONG_MIN || num == LONG_MAX)
+		if (num == LONG_MIN || num == LONG_MAX) {
+			fclose(fd);
 			return -NLE_RANGE;
+		}
 
 		while (*end == ' ' || *end == '\t')
 			end++;
 
 		goodlen = strcspn(end, "#\r\n\t ");
-		if (goodlen == 0)
+		if (goodlen == 0) {
+			fclose(fd);
 			return -NLE_INVAL;
+		}
 
 		end[goodlen] = '\0';
 
 		err = cb(num, end);
-		if (err < 0)
+		if (err < 0) {
+			fclose(fd);
 			return err;
+		}
 	}
 
 	fclose(fd);
 
 	return 0;
 }
+/** @endcond */
 
 /**
- * @name Unit Pretty-Printing
+ * @name Pretty Printing of Numbers
  * @{
  */
 
@@ -97,6 +132,7 @@
  *
  * Cancels down a byte counter until it reaches a reasonable
  * unit. The chosen unit is assigned to \a unit.
+ * This function assume 1024 bytes in one kilobyte
  * 
  * @return The cancelled down byte counter in the new unit.
  */
@@ -125,30 +161,57 @@
  * @arg	l		bit counter
  * @arg unit		destination unit pointer
  *
- * Cancels downa bit counter until it reaches a reasonable
+ * Cancels down bit counter until it reaches a reasonable
  * unit. The chosen unit is assigned to \a unit.
+ * This function assume 1000 bits in one kilobit
  *
  * @return The cancelled down bit counter in the new unit.
  */
 double nl_cancel_down_bits(unsigned long long l, char **unit)
 {
-	if (l >= 1099511627776ULL) {
+	if (l >= 1000000000000ULL) {
 		*unit = "Tbit";
-		return ((double) l) / 1099511627776ULL;
-	} else if (l >= 1073741824) {
-		*unit = "Gbit";
-		return ((double) l) / 1073741824;
-	} else if (l >= 1048576) {
-		*unit = "Mbit";
-		return ((double) l) / 1048576;
-	} else if (l >= 1024) {
-		*unit = "Kbit";
-		return ((double) l) / 1024;
-	} else {
-		*unit = "bit";
-		return (double) l;
+		return ((double) l) / 1000000000000ULL;
 	}
-		
+
+	if (l >= 1000000000) {
+		*unit = "Gbit";
+		return ((double) l) / 1000000000;
+	}
+
+	if (l >= 1000000) {
+		*unit = "Mbit";
+		return ((double) l) / 1000000;
+	}
+
+	if (l >= 1000) {
+		*unit = "Kbit";
+		return ((double) l) / 1000;
+	}
+
+	*unit = "bit";
+	return (double) l;
+}
+
+int nl_rate2str(unsigned long long rate, int type, char *buf, size_t len)
+{
+	char *unit;
+	double frac;
+
+	switch (type) {
+	case NL_BYTE_RATE:
+		frac = nl_cancel_down_bytes(rate, &unit);
+		break;
+	
+	case NL_BIT_RATE:
+		frac = nl_cancel_down_bits(rate, &unit);
+		break;
+	
+	default:
+		BUG();
+	}
+
+	return snprintf(buf, len, "%.2f%s/s", frac, unit);
 }
 
 /**
@@ -193,6 +256,9 @@
  *  - b,kb/k,m/mb,gb/g for bytes
  *  - bit,kbit/mbit/gbit
  *
+ * This function assume 1000 bits in one kilobit and
+ * 1024 bytes in one kilobyte
+ *
  * @return The number of bytes or -1 if the string is unparseable
  */
 long nl_size2int(const char *str)
@@ -208,13 +274,13 @@
 		else if (!strcasecmp(p, "gb") || !strcasecmp(p, "g"))
 			l *= 1024*1024*1024;
 		else if (!strcasecmp(p, "gbit"))
-			l *= 1024*1024*1024/8;
+			l *= 1000000000L/8;
 		else if (!strcasecmp(p, "mb") || !strcasecmp(p, "m"))
 			l *= 1024*1024;
 		else if (!strcasecmp(p, "mbit"))
-			l *= 1024*1024/8;
+			l *= 1000000/8;
 		else if (!strcasecmp(p, "kbit"))
-			l *= 1024/8;
+			l *= 1000/8;
 		else if (!strcasecmp(p, "bit"))
 			l /= 8;
 		else if (strcasecmp(p, "b") != 0)
@@ -224,6 +290,61 @@
 	return l;
 }
 
+static const struct {
+	double limit;
+	const char *unit;
+} size_units[] = {
+	{ 1024. * 1024. * 1024. * 1024. * 1024., "EiB" },
+	{ 1024. * 1024. * 1024. * 1024., "TiB" },
+	{ 1024. * 1024. * 1024., "GiB" },
+	{ 1024. * 1024., "MiB" },
+	{ 1024., "KiB" },
+	{ 0., "B" },
+};
+
+/**
+ * Convert a size toa character string
+ * @arg size		Size in number of bytes
+ * @arg buf		Buffer to write character string to
+ * @arg len		Size of buf
+ *
+ * This function converts a value in bytes to a human readable representation
+ * of it. The function uses IEC prefixes:
+ *
+ * @code
+ * 1024 bytes => 1 KiB
+ * 1048576 bytes => 1 MiB
+ * @endcode
+ *
+ * The highest prefix is used which ensures a result of >= 1.0, the result
+ * is provided as floating point number with a maximum precision of 2 digits:
+ * @code
+ * 965176 bytes => 942.55 KiB
+ * @endcode
+ *
+ * @return pointer to buf
+ */
+char *nl_size2str(const size_t size, char *buf, const size_t len)
+{
+	size_t i;
+
+	if (size == 0) {
+		snprintf(buf, len, "0B");
+		return buf;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(size_units); i++) {
+		if (size >= size_units[i].limit) {
+			snprintf(buf, len, "%.2g%s",
+				(double) size / size_units[i].limit,
+				size_units[i].unit);
+			return buf;
+		}
+	}
+
+	BUG();
+}
+
 /**
  * Convert a character string to a probability
  * @arg str		probability encoded as character string
@@ -264,12 +385,13 @@
  * @{
  */
 
-#ifdef USER_HZ
-static uint32_t user_hz = USER_HZ;
-#else
-static uint32_t user_hz = 100;
+#ifndef USER_HZ
+#define USER_HZ 100
 #endif
 
+static uint32_t user_hz = USER_HZ;
+static uint32_t psched_hz = USER_HZ;
+
 static double ticks_per_usec = 1.0f;
 
 /* Retrieves the configured HZ and ticks/us value in the kernel.
@@ -281,14 +403,25 @@
  * Supports the environment variables:
  *   PROC_NET_PSCHED  - may point to psched file in /proc
  *   PROC_ROOT        - may point to /proc fs */ 
-static void __init get_psched_settings(void)
+static void get_psched_settings(void)
 {
 	char name[FILENAME_MAX];
 	FILE *fd;
 	int got_hz = 0;
+	static volatile int initialized = 0;
+	const char *ev;
+	NL_LOCK(mutex);
 
-	if (getenv("HZ")) {
-		long hz = strtol(getenv("HZ"), NULL, 0);
+	if (initialized == 1)
+		return;
+
+	nl_lock(&mutex);
+
+	if (initialized == 1)
+		return;
+
+	if ((ev = getenv("HZ"))) {
+		long hz = strtol(ev, NULL, 0);
 
 		if (LONG_MIN != hz && LONG_MAX != hz) {
 			user_hz = hz;
@@ -299,39 +432,63 @@
 	if (!got_hz)
 		user_hz = sysconf(_SC_CLK_TCK);
 
-	if (getenv("TICKS_PER_USEC")) {
-		double t = strtod(getenv("TICKS_PER_USEC"), NULL);
+	psched_hz = user_hz;
+
+	if ((ev = getenv("TICKS_PER_USEC"))) {
+		double t = strtod(ev, NULL);
 		ticks_per_usec = t;
 	}
 	else {
-		if (getenv("PROC_NET_PSCHED"))
-			snprintf(name, sizeof(name), "%s", getenv("PROC_NET_PSCHED"));
-		else if (getenv("PROC_ROOT"))
-			snprintf(name, sizeof(name), "%s/net/psched",
-				 getenv("PROC_ROOT"));
+		if ((ev = getenv("PROC_NET_PSCHED")))
+			snprintf(name, sizeof(name), "%s", ev);
+		else if ((ev = getenv("PROC_ROOT")))
+			snprintf(name, sizeof(name), "%s/net/psched", ev);
 		else
 			strncpy(name, "/proc/net/psched", sizeof(name) - 1);
 		
 		if ((fd = fopen(name, "r"))) {
-			uint32_t tick, us;
-			/* the file contains 4 hexadecimals, but we just use
-			   the first two of them */
-			fscanf(fd, "%08x %08x", &tick, &us);
-			ticks_per_usec = (double)tick/(double)us;
+			unsigned int ns_per_usec, ns_per_tick, nom, denom;
+
+			if (fscanf(fd, "%08x %08x %08x %08x",
+			       &ns_per_usec, &ns_per_tick, &nom, &denom) != 4) {
+                            NL_DBG(1, "Fatal error: can not read psched settings from \"%s\". " \
+                                    "Try to set TICKS_PER_USEC, PROC_NET_PSCHED or PROC_ROOT " \
+                                    "environment variables\n", name);
+                            exit(1);
+                        }
+
+			ticks_per_usec = (double) ns_per_usec / 
+					 (double) ns_per_tick;
+
+			if (nom == 1000000)
+				psched_hz = denom;
+
 			fclose(fd);
 		}
 	}
+	initialized = 1;
+
+	nl_unlock(&mutex);
 }
 
 
 /**
  * Return the value of HZ
  */
-int nl_get_hz(void)
+int nl_get_user_hz(void)
 {
+	get_psched_settings();
 	return user_hz;
 }
 
+/**
+ * Return the value of packet scheduler HZ
+ */
+int nl_get_psched_hz(void)
+{
+	get_psched_settings();
+	return psched_hz;
+}
 
 /**
  * Convert micro seconds to ticks
@@ -340,6 +497,7 @@
  */
 uint32_t nl_us2ticks(uint32_t us)
 {
+	get_psched_settings();
 	return us * ticks_per_usec;
 }
 
@@ -351,6 +509,7 @@
  */
 uint32_t nl_ticks2us(uint32_t ticks)
 {
+	get_psched_settings();
 	return ticks / ticks_per_usec;
 }
 
@@ -404,10 +563,17 @@
  */
 char * nl_msec2str(uint64_t msec, char *buf, size_t len)
 {
-	int i, split[5];
-	char *units[] = {"d", "h", "m", "s", "msec"};
+	uint64_t split[5];
+	size_t i;
+	static const char *units[5] = {"d", "h", "m", "s", "msec"};
+	char * const buf_orig = buf;
 
-#define _SPLIT(idx, unit) if ((split[idx] = msec / unit) > 0) msec %= unit
+	if (msec == 0) {
+		snprintf(buf, len, "0msec");
+		return buf_orig;
+	}
+
+#define _SPLIT(idx, unit) if ((split[idx] = msec / unit)) msec %= unit
 	_SPLIT(0, 86400000);	/* days */
 	_SPLIT(1, 3600000);	/* hours */
 	_SPLIT(2, 60000);	/* minutes */
@@ -415,18 +581,17 @@
 #undef  _SPLIT
 	split[4] = msec;
 
-	memset(buf, 0, len);
-
-	for (i = 0; i < ARRAY_SIZE(split); i++) {
-		if (split[i] > 0) {
-			char t[64];
-			snprintf(t, sizeof(t), "%s%d%s",
-				 strlen(buf) ? " " : "", split[i], units[i]);
-			strncat(buf, t, len - strlen(buf) - 1);
-		}
+	for (i = 0; i < ARRAY_SIZE(split) && len; i++) {
+		int l;
+		if (split[i] == 0)
+			continue;
+		l = snprintf(buf, len, "%s%" PRIu64 "%s",
+			(buf==buf_orig) ? "" : " ", split[i], units[i]);
+		buf += l;
+		len -= l;
 	}
 
-	return buf;
+	return buf_orig;
 }
 
 /** @} */
@@ -436,7 +601,7 @@
  * @{
  */
 
-static struct trans_tbl nlfamilies[] = {
+static const struct trans_tbl nlfamilies[] = {
 	__ADD(NETLINK_ROUTE,route)
 	__ADD(NETLINK_USERSOCK,usersock)
 	__ADD(NETLINK_FIREWALL,firewall)
@@ -477,7 +642,7 @@
  * @{
  */
 
-static struct trans_tbl llprotos[] = {
+static const struct trans_tbl llprotos[] = {
 	{0, "generic"},
 	__ADD(ARPHRD_ETHER,ether)
 	__ADD(ARPHRD_EETHER,eether)
@@ -506,6 +671,7 @@
 #ifdef ARPHRD_HWX25
 	__ADD(ARPHRD_HWX25,hwx25)
 #endif
+	__ADD(ARPHRD_CAN,can)
 	__ADD(ARPHRD_PPP,ppp)
 	__ADD(ARPHRD_HDLC,hdlc)
 	__ADD(ARPHRD_LAPB,lapb)
@@ -545,12 +711,19 @@
 	__ADD(ARPHRD_FCFABRIC+12,fcfb_12)
 	__ADD(ARPHRD_IEEE802_TR,tr)
 	__ADD(ARPHRD_IEEE80211,ieee802.11)
+	__ADD(ARPHRD_PHONET,phonet)
+#ifdef ARPHRD_CAIF
+	__ADD(ARPHRD_CAIF, caif)
+#endif
 #ifdef ARPHRD_IEEE80211_PRISM
 	__ADD(ARPHRD_IEEE80211_PRISM, ieee802.11_prism)
 #endif
 #ifdef ARPHRD_VOID
 	__ADD(ARPHRD_VOID,void)
 #endif
+#ifdef ARPHRD_NONE
+	__ADD(ARPHRD_NONE,nohdr)
+#endif
 };
 
 char * nl_llproto2str(int llproto, char *buf, size_t len)
@@ -571,7 +744,7 @@
  * @{
  */
 
-static struct trans_tbl ether_protos[] = {
+static const struct trans_tbl ether_protos[] = {
 	__ADD(ETH_P_LOOP,loop)
 	__ADD(ETH_P_PUP,pup)
 	__ADD(ETH_P_PUPAT,pupat)
@@ -589,6 +762,7 @@
 	__ADD(ETH_P_DIAG,diag)
 	__ADD(ETH_P_CUST,cust)
 	__ADD(ETH_P_SCA,sca)
+	__ADD(ETH_P_TEB,teb)
 	__ADD(ETH_P_RARP,rarp)
 	__ADD(ETH_P_ATALK,atalk)
 	__ADD(ETH_P_AARP,aarp)
@@ -597,6 +771,8 @@
 #endif
 	__ADD(ETH_P_IPX,ipx)
 	__ADD(ETH_P_IPV6,ipv6)
+	__ADD(ETH_P_PAUSE,pause)
+	__ADD(ETH_P_SLOW,slow)
 #ifdef ETH_P_WCCP
 	__ADD(ETH_P_WCCP,wccp)
 #endif
@@ -605,7 +781,15 @@
 	__ADD(ETH_P_MPLS_UC,mpls_uc)
 	__ADD(ETH_P_MPLS_MC,mpls_mc)
 	__ADD(ETH_P_ATMMPOA,atmmpoa)
+	__ADD(ETH_P_LINK_CTL,link_ctl)
 	__ADD(ETH_P_ATMFATE,atmfate)
+	__ADD(ETH_P_PAE,pae)
+	__ADD(ETH_P_AOE,aoe)
+	__ADD(ETH_P_TIPC,tipc)
+	__ADD(ETH_P_1588,ieee1588)
+	__ADD(ETH_P_FCOE,fcoe)
+	__ADD(ETH_P_FIP,fip)
+	__ADD(ETH_P_EDSA,edsa)
 	__ADD(ETH_P_EDP2,edp2)
 	__ADD(ETH_P_802_3,802.3)
 	__ADD(ETH_P_AX25,ax25)
@@ -616,6 +800,7 @@
 	__ADD(ETH_P_WAN_PPP,wan_ppp)
 	__ADD(ETH_P_PPP_MP,ppp_mp)
 	__ADD(ETH_P_LOCALTALK,localtalk)
+	__ADD(ETH_P_CAN,can)
 	__ADD(ETH_P_PPPTALK,ppptalk)
 	__ADD(ETH_P_TR_802_2,tr_802.2)
 	__ADD(ETH_P_MOBITEX,mobitex)
@@ -623,6 +808,12 @@
 	__ADD(ETH_P_IRDA,irda)
 	__ADD(ETH_P_ECONET,econet)
 	__ADD(ETH_P_HDLC,hdlc)
+	__ADD(ETH_P_ARCNET,arcnet)
+	__ADD(ETH_P_DSA,dsa)
+	__ADD(ETH_P_TRAILER,trailer)
+	__ADD(ETH_P_PHONET,phonet)
+	__ADD(ETH_P_IEEE802154,ieee802154)
+	__ADD(ETH_P_CAIF,caif)
 };
 
 char *nl_ether_proto2str(int eproto, char *buf, size_t len)
@@ -701,7 +892,7 @@
 			else if (params->dp_buf)
 				strncat(params->dp_buf, " ",
 					params->dp_buflen -
-					sizeof(params->dp_buf) - 1);
+					strlen(params->dp_buf) - 1);
 		}
 	}
 
@@ -716,13 +907,15 @@
 		vfprintf(parms->dp_fd, fmt, args);
 	else if (parms->dp_buf || parms->dp_cb) {
 		char *buf = NULL;
-		vasprintf(&buf, fmt, args);
-		if (parms->dp_cb)
-			parms->dp_cb(parms, buf);
-		else
-			strncat(parms->dp_buf, buf,
-			        parms->dp_buflen - strlen(parms->dp_buf) - 1);
-		free(buf);
+		if (vasprintf(&buf, fmt, args) >= 0) {
+			if (parms->dp_cb)
+				parms->dp_cb(parms, buf);
+			else
+				strncat(parms->dp_buf, buf,
+					parms->dp_buflen -
+					strlen(parms->dp_buf) - 1);
+			free(buf);
+		}
 	}
 }
 
@@ -785,12 +978,14 @@
 		free(tl->a);
 		free(tl);
 	}
+
+	nl_init_list_head(head);
 }
 
-char *__type2str(int type, char *buf, size_t len, struct trans_tbl *tbl,
-		 size_t tbl_len)
+char *__type2str(int type, char *buf, size_t len,
+		 const struct trans_tbl *tbl, size_t tbl_len)
 {
-	int i;
+	size_t i;
 	for (i = 0; i < tbl_len; i++) {
 		if (tbl[i].i == type) {
 			snprintf(buf, len, "%s", tbl[i].a);
@@ -819,13 +1014,13 @@
 }
 
 char *__flags2str(int flags, char *buf, size_t len,
-		  struct trans_tbl *tbl, size_t tbl_len)
+		  const struct trans_tbl *tbl, size_t tbl_len)
 {
-	int i;
+	size_t i;
 	int tmp = flags;
 
 	memset(buf, 0, len);
-	
+
 	for (i = 0; i < tbl_len; i++) {
 		if (tbl[i].i & tmp) {
 			tmp &= ~tbl[i].i;
@@ -838,11 +1033,11 @@
 	return buf;
 }
 
-int __str2type(const char *buf, struct trans_tbl *tbl, size_t tbl_len)
+int __str2type(const char *buf, const struct trans_tbl *tbl, size_t tbl_len)
 {
 	unsigned long l;
 	char *end;
-	int i;
+	size_t i;
 
 	if (*buf == '\0')
 		return -NLE_INVAL;
@@ -879,9 +1074,11 @@
 	return (int) l;
 }
 
-int __str2flags(const char *buf, struct trans_tbl *tbl, size_t tbl_len)
+int __str2flags(const char *buf, const struct trans_tbl *tbl, size_t tbl_len)
 {
-	int i, flags = 0, len;
+	int flags = 0;
+	size_t i;
+	size_t len; /* ptrdiff_t ? */
 	char *p = (char *) buf, *t;
 
 	for (;;) {
@@ -891,7 +1088,8 @@
 		t = strchr(p, ',');
 		len = t ? t - p : strlen(p);
 		for (i = 0; i < tbl_len; i++)
-			if (!strncasecmp(tbl[i].a, p, len))
+			if (len == strlen(tbl[i].a) &&
+			    !strncasecmp(tbl[i].a, p, len))
 				flags |= tbl[i].i;
 
 		if (!t)
@@ -930,6 +1128,58 @@
 		obj->ce_ops->oo_dump[type](obj, params);
 }
 
+/**
+ * Check for library capabilities
+ *
+ * @arg	capability	capability identifier
+ *
+ * Check whether the loaded libnl library supports a certain capability.
+ * This is useful so that applications can workaround known issues of
+ * libnl that are fixed in newer library versions, without
+ * having a hard dependency on the new version. It is also useful, for
+ * capabilities that cannot easily be detected using autoconf tests.
+ * The capabilities are integer constants with name NL_CAPABILITY_*.
+ *
+ * As this function is intended to detect capabilities at runtime,
+ * you might not want to depend during compile time on the NL_CAPABILITY_*
+ * names. Instead you can use their numeric values which are guaranteed not to
+ * change meaning.
+ *
+ * @return non zero if libnl supports a certain capability, 0 otherwise.
+ **/
+int nl_has_capability (int capability)
+{
+	static const uint8_t caps[ ( NL_CAPABILITY_MAX + 7 ) / 8  ] = {
+#define _NL_ASSERT(expr) ( 0 * sizeof(struct { unsigned int x: ( (!!(expr)) ? 1 : -1 ); }) )
+#define _NL_SETV(i, r, v) \
+		( _NL_ASSERT( (v) == 0 || (i) * 8 + (r) == (v) - 1 ) + \
+		  ( (v) == 0 ? 0 : (1 << (r)) ) )
+#define _NL_SET(i, v0, v1, v2, v3, v4, v5, v6, v7) \
+		[(i)] = ( \
+			_NL_SETV((i), 0, (v0)) | _NL_SETV((i), 4, (v4)) | \
+			_NL_SETV((i), 1, (v1)) | _NL_SETV((i), 5, (v5)) | \
+			_NL_SETV((i), 2, (v2)) | _NL_SETV((i), 6, (v6)) | \
+			_NL_SETV((i), 3, (v3)) | _NL_SETV((i), 7, (v7)) )
+		_NL_SET(0,
+			NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE,
+			NL_CAPABILITY_ROUTE_LINK_VETH_GET_PEER_OWN_REFERENCE,
+			NL_CAPABILITY_ROUTE_LINK_CLS_ADD_ACT_OWN_REFERENCE,
+			NL_CAPABILITY_NL_CONNECT_RETRY_GENERATE_PORT_ON_ADDRINUSE,
+			0,
+			0,
+			0,
+			0),
+#undef _NL_SET
+#undef _NL_SETV
+#undef _NL_ASSERT
+	};
+
+	if (capability <= 0 || capability > NL_CAPABILITY_MAX)
+		return 0;
+	capability--;
+	return (caps[capability / 8] & (1 << (capability % 8))) != 0;
+}
+
 /** @endcond */
 
 /** @} */
diff --git a/lib/version.c b/lib/version.c
new file mode 100644
index 0000000..0dcafa0
--- /dev/null
+++ b/lib/version.c
@@ -0,0 +1,36 @@
+/*
+ * lib/version.c	Run-time version information
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup core
+ * @defgroup utils Utilities
+ *
+ * Run-time version information
+ *
+ * @{
+ */
+
+
+/**
+ * @name Run-time version information
+ * @{
+ */
+
+#include <netlink/version.h>
+
+const int      nl_ver_num = LIBNL_VER_NUM;
+const int      nl_ver_maj = LIBNL_VER_MAJ;
+const int      nl_ver_min = LIBNL_VER_MIN;
+const int      nl_ver_mic = LIBNL_VER_MIC;
+
+/** @} */
+
+/** @} */
diff --git a/libnl-2.0.pc.in b/libnl-3.0.pc.in
similarity index 68%
rename from libnl-2.0.pc.in
rename to libnl-3.0.pc.in
index e44f0fb..b87e3dc 100644
--- a/libnl-2.0.pc.in
+++ b/libnl-3.0.pc.in
@@ -6,5 +6,5 @@
 Name: libnl
 Description: Convenience library for netlink sockets
 Version: @PACKAGE_VERSION@
-Libs: -L${libdir} -lnl
-Cflags: -I${includedir}
+Libs: -L${libdir} -lnl-@MAJ_VERSION@
+Cflags: -I${includedir}/libnl@MAJ_VERSION@
diff --git a/libnl-cli-3.0.pc.in b/libnl-cli-3.0.pc.in
new file mode 100644
index 0000000..d3638ba
--- /dev/null
+++ b/libnl-cli-3.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libnl-cli
+Description: Command Line Interface library for netlink sockets
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lnl-cli-@MAJ_VERSION@
+Cflags: -I${includedir}
+Requires: libnl-3.0 libnl-genl-3.0 libnl-nf-3.0 libnl-route-3.0
diff --git a/libnl-genl-3.0.pc.in b/libnl-genl-3.0.pc.in
new file mode 100644
index 0000000..d6b69b8
--- /dev/null
+++ b/libnl-genl-3.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libnl-genl
+Description: Generic Netlink Library
+Version: @PACKAGE_VERSION@
+Requires: libnl-3.0
+Libs: -L${libdir} -lnl-genl-@MAJ_VERSION@
+Cflags: -I${includedir}/libnl@MAJ_VERSION@
diff --git a/libnl-idiag-3.0.pc.in b/libnl-idiag-3.0.pc.in
new file mode 100644
index 0000000..9ce5100
--- /dev/null
+++ b/libnl-idiag-3.0.pc.in
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libnl-idiag
+Description: Netlink Inet Diag Family Library
+Version: @PACKAGE_VERSION@
+Requires: libnl-3.0
+Libs: -L${libdir} -lnl-idiag-@MAJ_VERSION@
+Cflags: -I${includedir}/libnl@MAJ_VERSION@
+
diff --git a/libnl-nf-3.0.pc.in b/libnl-nf-3.0.pc.in
new file mode 100644
index 0000000..d82e1a6
--- /dev/null
+++ b/libnl-nf-3.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libnl-nf
+Description: Netfilter Netlink Library
+Version: @PACKAGE_VERSION@
+Requires: libnl-route-3.0
+Libs: -L${libdir} -lnl-nf-@MAJ_VERSION@
+Cflags: -I${includedir}/libnl@MAJ_VERSION@
diff --git a/libnl-route-3.0.pc.in b/libnl-route-3.0.pc.in
new file mode 100644
index 0000000..372a4f4
--- /dev/null
+++ b/libnl-route-3.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libnl-route
+Description: Netlink Routing Family Library
+Version: @PACKAGE_VERSION@
+Requires: libnl-3.0
+Libs: -L${libdir} -lnl-route-@MAJ_VERSION@
+Cflags: -I${includedir}/libnl@MAJ_VERSION@
diff --git a/libnl.sym.in b/libnl.sym.in
new file mode 100644
index 0000000..df8888c
--- /dev/null
+++ b/libnl.sym.in
@@ -0,0 +1,9 @@
+libnl_@MAJ_VERSION@ {
+global:
+	*;
+local:
+	_nl_socket_generate_local_port_no_release;
+	_nl_socket_is_local_port_unspecified;
+	_nl_socket_used_ports_release_all;
+	_nl_socket_used_ports_set;
+};
diff --git a/m4/ax_pkg_swig.m4 b/m4/ax_pkg_swig.m4
new file mode 100644
index 0000000..e112f3d
--- /dev/null
+++ b/m4/ax_pkg_swig.m4
@@ -0,0 +1,135 @@
+# ===========================================================================
+#        http://www.gnu.org/software/autoconf-archive/ax_pkg_swig.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_PKG_SWIG([major.minor.micro], [action-if-found], [action-if-not-found])
+#
+# DESCRIPTION
+#
+#   This macro searches for a SWIG installation on your system. If found,
+#   then SWIG is AC_SUBST'd; if not found, then $SWIG is empty.  If SWIG is
+#   found, then SWIG_LIB is set to the SWIG library path, and AC_SUBST'd.
+#
+#   You can use the optional first argument to check if the version of the
+#   available SWIG is greater than or equal to the value of the argument. It
+#   should have the format: N[.N[.N]] (N is a number between 0 and 999. Only
+#   the first N is mandatory.) If the version argument is given (e.g.
+#   1.3.17), AX_PKG_SWIG checks that the swig package is this version number
+#   or higher.
+#
+#   As usual, action-if-found is executed if SWIG is found, otherwise
+#   action-if-not-found is executed.
+#
+#   In configure.in, use as:
+#
+#     AX_PKG_SWIG(1.3.17, [], [ AC_MSG_ERROR([SWIG is required to build..]) ])
+#     AX_SWIG_ENABLE_CXX
+#     AX_SWIG_MULTI_MODULE_SUPPORT
+#     AX_SWIG_PYTHON
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de>
+#   Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca>
+#   Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net>
+#   Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za>
+#   Copyright (c) 2011 Murray Cumming <murrayc@openismus.com>
+#
+#   This program is free software; you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation; either version 2 of the License, or (at your
+#   option) any later version.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+#   Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License along
+#   with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 8
+
+AC_DEFUN([AX_PKG_SWIG],[
+        # Ubuntu has swig 2.0 as /usr/bin/swig2.0
+        AC_PATH_PROGS([SWIG],[swig swig2.0])
+        if test -z "$SWIG" ; then
+                m4_ifval([$3],[$3],[:])
+        elif test -n "$1" ; then
+                AC_MSG_CHECKING([SWIG version])
+                [swig_version=`$SWIG -version 2>&1 | grep 'SWIG Version' | sed 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/g'`]
+                AC_MSG_RESULT([$swig_version])
+                if test -n "$swig_version" ; then
+                        # Calculate the required version number components
+                        [required=$1]
+                        [required_major=`echo $required | sed 's/[^0-9].*//'`]
+                        if test -z "$required_major" ; then
+                                [required_major=0]
+                        fi
+                        [required=`echo $required | sed 's/[0-9]*[^0-9]//'`]
+                        [required_minor=`echo $required | sed 's/[^0-9].*//'`]
+                        if test -z "$required_minor" ; then
+                                [required_minor=0]
+                        fi
+                        [required=`echo $required | sed 's/[0-9]*[^0-9]//'`]
+                        [required_patch=`echo $required | sed 's/[^0-9].*//'`]
+                        if test -z "$required_patch" ; then
+                                [required_patch=0]
+                        fi
+                        # Calculate the available version number components
+                        [available=$swig_version]
+                        [available_major=`echo $available | sed 's/[^0-9].*//'`]
+                        if test -z "$available_major" ; then
+                                [available_major=0]
+                        fi
+                        [available=`echo $available | sed 's/[0-9]*[^0-9]//'`]
+                        [available_minor=`echo $available | sed 's/[^0-9].*//'`]
+                        if test -z "$available_minor" ; then
+                                [available_minor=0]
+                        fi
+                        [available=`echo $available | sed 's/[0-9]*[^0-9]//'`]
+                        [available_patch=`echo $available | sed 's/[^0-9].*//'`]
+                        if test -z "$available_patch" ; then
+                                [available_patch=0]
+                        fi
+                        # Convert the version tuple into a single number for easier comparison.
+                        # Using base 100 should be safe since SWIG internally uses BCD values
+                        # to encode its version number.
+                        required_swig_vernum=`expr $required_major \* 10000 \
+                            \+ $required_minor \* 100 \+ $required_patch`
+                        available_swig_vernum=`expr $available_major \* 10000 \
+                            \+ $available_minor \* 100 \+ $available_patch`
+
+                        if test $available_swig_vernum -lt $required_swig_vernum; then
+                                AC_MSG_WARN([SWIG version >= $1 is required.  You have $swig_version.])
+                                SWIG=''
+                                m4_ifval([$3],[$3],[])
+                        else
+                                AC_MSG_CHECKING([for SWIG library])
+                                SWIG_LIB=`$SWIG -swiglib`
+                                AC_MSG_RESULT([$SWIG_LIB])
+                                m4_ifval([$2],[$2],[])
+                        fi
+                else
+                        AC_MSG_WARN([cannot determine SWIG version])
+                        SWIG=''
+                        m4_ifval([$3],[$3],[])
+                fi
+        fi
+        AC_SUBST([SWIG_LIB])
+])
diff --git a/m4/ax_python.m4 b/m4/ax_python.m4
new file mode 100644
index 0000000..1bc9d8a
--- /dev/null
+++ b/m4/ax_python.m4
@@ -0,0 +1,97 @@
+# ===========================================================================
+#         http://www.gnu.org/software/autoconf-archive/ax_python.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_PYTHON
+#
+# DESCRIPTION
+#
+#   This macro does a complete Python development environment check.
+#
+#   It recurses through several python versions (from 2.1 to 2.6 in this
+#   version), looking for an executable. When it finds an executable, it
+#   looks to find the header files and library.
+#
+#   It sets PYTHON_BIN to the name of the python executable,
+#   PYTHON_INCLUDE_DIR to the directory holding the header files, and
+#   PYTHON_LIB to the name of the Python library.
+#
+#   This macro calls AC_SUBST on PYTHON_BIN (via AC_CHECK_PROG),
+#   PYTHON_INCLUDE_DIR and PYTHON_LIB.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Michael Tindal
+#
+#   This program is free software; you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation; either version 2 of the License, or (at your
+#   option) any later version.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+#   Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License along
+#   with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 9
+
+AC_DEFUN([AX_PYTHON],
+[AC_MSG_CHECKING(for python build information)
+AC_MSG_RESULT([])
+for python in python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python; do
+AC_CHECK_PROGS(PYTHON_BIN, [$python])
+ax_python_bin=$PYTHON_BIN
+if test x$ax_python_bin != x; then
+   AC_CHECK_LIB($ax_python_bin, main, ax_python_lib=$ax_python_bin, ax_python_lib=no)
+   AC_CHECK_HEADER([$ax_python_bin/Python.h],
+   [[ax_python_header=`locate $ax_python_bin/Python.h | sed -e s,/Python.h,,`]],
+   ax_python_header=no)
+   if test $ax_python_lib != no; then
+     if test $ax_python_header != no; then
+       break;
+     fi
+   fi
+fi
+done
+if test x$ax_python_bin = x; then
+   ax_python_bin=no
+fi
+if test x$ax_python_header = x; then
+   ax_python_header=no
+fi
+if test x$ax_python_lib = x; then
+   ax_python_lib=no
+fi
+
+AC_MSG_RESULT([  results of the Python check:])
+AC_MSG_RESULT([    Binary:      $ax_python_bin])
+AC_MSG_RESULT([    Library:     $ax_python_lib])
+AC_MSG_RESULT([    Include Dir: $ax_python_header])
+
+if test x$ax_python_header != xno; then
+  PYTHON_INCLUDE_DIR=$ax_python_header
+  AC_SUBST(PYTHON_INCLUDE_DIR)
+fi
+if test x$ax_python_lib != xno; then
+  PYTHON_LIB=$ax_python_lib
+  AC_SUBST(PYTHON_LIB)
+fi
+])dnl
diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4
new file mode 100644
index 0000000..a62b860
--- /dev/null
+++ b/m4/ax_python_devel.m4
@@ -0,0 +1,325 @@
+# ===========================================================================
+#      http://www.gnu.org/software/autoconf-archive/ax_python_devel.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_PYTHON_DEVEL([version])
+#
+# DESCRIPTION
+#
+#   Note: Defines as a precious variable "PYTHON_VERSION". Don't override it
+#   in your configure.ac.
+#
+#   This macro checks for Python and tries to get the include path to
+#   'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LDFLAGS)
+#   output variables. It also exports $(PYTHON_EXTRA_LIBS) and
+#   $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code.
+#
+#   You can search for some particular version of Python by passing a
+#   parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please
+#   note that you *have* to pass also an operator along with the version to
+#   match, and pay special attention to the single quotes surrounding the
+#   version number. Don't use "PYTHON_VERSION" for this: that environment
+#   variable is declared as precious and thus reserved for the end-user.
+#
+#   This macro should work for all versions of Python >= 2.1.0. As an end
+#   user, you can disable the check for the python version by setting the
+#   PYTHON_NOVERSIONCHECK environment variable to something else than the
+#   empty string.
+#
+#   If you need to use this macro for an older Python version, please
+#   contact the authors. We're always open for feedback.
+#
+# LICENSE
+#
+#   Copyright (c) 2009 Sebastian Huber <sebastian-huber@web.de>
+#   Copyright (c) 2009 Alan W. Irwin <irwin@beluga.phys.uvic.ca>
+#   Copyright (c) 2009 Rafael Laboissiere <rafael@laboissiere.net>
+#   Copyright (c) 2009 Andrew Collier <colliera@ukzn.ac.za>
+#   Copyright (c) 2009 Matteo Settenvini <matteo@member.fsf.org>
+#   Copyright (c) 2009 Horst Knorr <hk_classes@knoda.org>
+#
+#   This program is free software: you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation, either version 3 of the License, or (at your
+#   option) any later version.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+#   Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License along
+#   with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 8
+
+AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL])
+AC_DEFUN([AX_PYTHON_DEVEL],[
+	#
+	# Allow the use of a (user set) custom python version
+	#
+	AC_ARG_VAR([PYTHON_VERSION],[The installed Python
+		version to use, for example '2.3'. This string
+		will be appended to the Python interpreter
+		canonical name.])
+
+	AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]])
+	if test -z "$PYTHON"; then
+	   AC_MSG_ERROR([Cannot find python$PYTHON_VERSION in your system path])
+	   PYTHON_VERSION=""
+	fi
+
+	#
+	# Check for a version of Python >= 2.1.0
+	#
+	AC_MSG_CHECKING([for a version of Python >= '2.1.0'])
+	ac_supports_python_ver=`$PYTHON -c "import sys; \
+		ver = sys.version.split ()[[0]]; \
+		print (ver >= '2.1.0')"`
+	if test "$ac_supports_python_ver" != "True"; then
+		if test -z "$PYTHON_NOVERSIONCHECK"; then
+			AC_MSG_RESULT([no])
+			AC_MSG_FAILURE([
+This version of the AC@&t@_PYTHON_DEVEL macro
+doesn't work properly with versions of Python before
+2.1.0. You may need to re-run configure, setting the
+variables PYTHON_CPPFLAGS, PYTHON_LDFLAGS, PYTHON_SITE_PKG,
+PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand.
+Moreover, to disable this check, set PYTHON_NOVERSIONCHECK
+to something else than an empty string.
+])
+		else
+			AC_MSG_RESULT([skip at user request])
+		fi
+	else
+		AC_MSG_RESULT([yes])
+	fi
+
+	#
+	# if the macro parameter ``version'' is set, honour it
+	#
+	if test -n "$1"; then
+		AC_MSG_CHECKING([for a version of Python $1])
+		ac_supports_python_ver=`$PYTHON -c "import sys; \
+			ver = sys.version.split ()[[0]]; \
+			print (ver $1)"`
+		if test "$ac_supports_python_ver" = "True"; then
+		   AC_MSG_RESULT([yes])
+		else
+			AC_MSG_RESULT([no])
+			AC_MSG_ERROR([this package requires Python $1.
+If you have it installed, but it isn't the default Python
+interpreter in your system path, please pass the PYTHON_VERSION
+variable to configure. See ``configure --help'' for reference.
+])
+			PYTHON_VERSION=""
+		fi
+	fi
+
+	#
+	# Check if you have distutils, else fail
+	#
+	AC_MSG_CHECKING([for the distutils Python package])
+	ac_distutils_result=`$PYTHON -c "import distutils" 2>&1`
+	if test -z "$ac_distutils_result"; then
+		AC_MSG_RESULT([yes])
+	else
+		AC_MSG_RESULT([no])
+		AC_MSG_ERROR([cannot import Python module "distutils".
+Please check your Python installation. The error was:
+$ac_distutils_result])
+		PYTHON_VERSION=""
+	fi
+
+	#
+	# Check for Python include path
+	#
+	AC_MSG_CHECKING([for Python include path])
+	if test -z "$PYTHON_CPPFLAGS"; then
+		python_path=`$PYTHON -c "import distutils.sysconfig; \
+			print (distutils.sysconfig.get_python_inc ());"`
+		if test -n "${python_path}"; then
+			python_path="-I$python_path"
+		fi
+		PYTHON_CPPFLAGS=$python_path
+	fi
+	AC_MSG_RESULT([$PYTHON_CPPFLAGS])
+	AC_SUBST([PYTHON_CPPFLAGS])
+
+	#
+	# Check for Python library path
+	#
+	AC_MSG_CHECKING([for Python library path])
+	if test -z "$PYTHON_LDFLAGS"; then
+		# (makes two attempts to ensure we've got a version number
+		# from the interpreter)
+		ac_python_version=`cat<<EOD | $PYTHON -
+
+# join all versioning strings, on some systems
+# major/minor numbers could be in different list elements
+from distutils.sysconfig import *
+ret = ''
+for e in get_config_vars ('VERSION'):
+	if (e != None):
+		ret += e
+print (ret)
+EOD`
+
+		if test -z "$ac_python_version"; then
+			if test -n "$PYTHON_VERSION"; then
+				ac_python_version=$PYTHON_VERSION
+			else
+				ac_python_version=`$PYTHON -c "import sys; \
+					print (sys.version[[:3]])"`
+			fi
+		fi
+
+		# Make the versioning information available to the compiler
+		AC_DEFINE_UNQUOTED([HAVE_PYTHON], ["$ac_python_version"],
+                                   [If available, contains the Python version number currently in use.])
+
+		# First, the library directory:
+		ac_python_libdir=`cat<<EOD | $PYTHON -
+
+# There should be only one
+import distutils.sysconfig
+for e in distutils.sysconfig.get_config_vars ('LIBDIR'):
+	if e != None:
+		print (e)
+		break
+EOD`
+
+		# Before checking for libpythonX.Y, we need to know
+		# the extension the OS we're on uses for libraries
+		# (we take the first one, if there's more than one fix me!):
+		ac_python_soext=`$PYTHON -c \
+		  "import distutils.sysconfig; \
+		  print (distutils.sysconfig.get_config_vars('SO')[[0]])"`
+
+		# Now, for the library:
+		ac_python_soname=`$PYTHON -c \
+		  "import distutils.sysconfig; \
+		  print (distutils.sysconfig.get_config_vars('LDLIBRARY')[[0]])"`
+
+		# Strip away extension from the end to canonicalize its name:
+		ac_python_library=`echo "$ac_python_soname" | sed "s/${ac_python_soext}$//"`
+
+		# This small piece shamelessly adapted from PostgreSQL python macro;
+		# credits goes to momjian, I think. I'd like to put the right name
+		# in the credits, if someone can point me in the right direction... ?
+		#
+		if test -n "$ac_python_libdir" -a -n "$ac_python_library" \
+			-a x"$ac_python_library" != x"$ac_python_soname"
+		then
+			# use the official shared library
+			ac_python_library=`echo "$ac_python_library" | sed "s/^lib//"`
+			PYTHON_LDFLAGS="-L$ac_python_libdir -l$ac_python_library"
+		else
+			# old way: use libpython from python_configdir
+			ac_python_libdir=`$PYTHON -c \
+			  "from distutils.sysconfig import get_python_lib as f; \
+			  import os; \
+			  print (os.path.join(f(plat_specific=1, standard_lib=1), 'config'));"`
+			PYTHON_LDFLAGS="-L$ac_python_libdir -lpython$ac_python_version"
+		fi
+
+		if test -z "PYTHON_LDFLAGS"; then
+			AC_MSG_ERROR([
+  Cannot determine location of your Python DSO. Please check it was installed with
+  dynamic libraries enabled, or try setting PYTHON_LDFLAGS by hand.
+			])
+		fi
+	fi
+	AC_MSG_RESULT([$PYTHON_LDFLAGS])
+	AC_SUBST([PYTHON_LDFLAGS])
+
+	#
+	# Check for site packages
+	#
+	AC_MSG_CHECKING([for Python site-packages path])
+	if test -z "$PYTHON_SITE_PKG"; then
+		PYTHON_SITE_PKG=`$PYTHON -c "import distutils.sysconfig; \
+			print (distutils.sysconfig.get_python_lib(0,0));"`
+	fi
+	AC_MSG_RESULT([$PYTHON_SITE_PKG])
+	AC_SUBST([PYTHON_SITE_PKG])
+
+	#
+	# libraries which must be linked in when embedding
+	#
+	AC_MSG_CHECKING(python extra libraries)
+	if test -z "$PYTHON_EXTRA_LIBS"; then
+	   PYTHON_EXTRA_LIBS=`$PYTHON -c "import distutils.sysconfig; \
+                conf = distutils.sysconfig.get_config_var; \
+                print (conf('LOCALMODLIBS') + ' ' + conf('LIBS'))"`
+	fi
+	AC_MSG_RESULT([$PYTHON_EXTRA_LIBS])
+	AC_SUBST(PYTHON_EXTRA_LIBS)
+
+	#
+	# linking flags needed when embedding
+	#
+	AC_MSG_CHECKING(python extra linking flags)
+	if test -z "$PYTHON_EXTRA_LDFLAGS"; then
+		PYTHON_EXTRA_LDFLAGS=`$PYTHON -c "import distutils.sysconfig; \
+			conf = distutils.sysconfig.get_config_var; \
+			print (conf('LINKFORSHARED'))"`
+	fi
+	AC_MSG_RESULT([$PYTHON_EXTRA_LDFLAGS])
+	AC_SUBST(PYTHON_EXTRA_LDFLAGS)
+
+	#
+	# final check to see if everything compiles alright
+	#
+	AC_MSG_CHECKING([consistency of all components of python development environment])
+	# save current global flags
+	ac_save_LIBS="$LIBS"
+	ac_save_CPPFLAGS="$CPPFLAGS"
+	LIBS="$ac_save_LIBS $PYTHON_LDFLAGS $PYTHON_EXTRA_LDFLAGS $PYTHON_EXTRA_LIBS"
+	CPPFLAGS="$ac_save_CPPFLAGS $PYTHON_CPPFLAGS"
+	AC_LANG_PUSH([C])
+	AC_LINK_IFELSE([
+		AC_LANG_PROGRAM([[#include <Python.h>]],
+				[[Py_Initialize();]])
+		],[pythonexists=yes],[pythonexists=no])
+	AC_LANG_POP([C])
+	# turn back to default flags
+	CPPFLAGS="$ac_save_CPPFLAGS"
+	LIBS="$ac_save_LIBS"
+
+	AC_MSG_RESULT([$pythonexists])
+
+        if test ! "x$pythonexists" = "xyes"; then
+	   AC_MSG_FAILURE([
+  Could not link test program to Python. Maybe the main Python library has been
+  installed in some non-standard library path. If so, pass it to configure,
+  via the LDFLAGS environment variable.
+  Example: ./configure LDFLAGS="-L/usr/non-standard-path/python/lib"
+  ============================================================================
+   ERROR!
+   You probably have to install the development version of the Python package
+   for your distribution.  The exact name of this package varies among them.
+  ============================================================================
+	   ])
+	  PYTHON_VERSION=""
+	fi
+
+	#
+	# all done!
+	#
+])
diff --git a/m4/ax_swig_python.m4 b/m4/ax_swig_python.m4
new file mode 100644
index 0000000..8fd3df5
--- /dev/null
+++ b/m4/ax_swig_python.m4
@@ -0,0 +1,64 @@
+# ===========================================================================
+#      http://www.gnu.org/software/autoconf-archive/ax_swig_python.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_SWIG_PYTHON([use-shadow-classes = {no, yes}])
+#
+# DESCRIPTION
+#
+#   Checks for Python and provides the $(AX_SWIG_PYTHON_CPPFLAGS), and
+#   $(AX_SWIG_PYTHON_OPT) output variables.
+#
+#   $(AX_SWIG_PYTHON_OPT) contains all necessary SWIG options to generate
+#   code for Python. Shadow classes are enabled unless the value of the
+#   optional first argument is exactly 'no'. If you need multi module
+#   support (provided by the AX_SWIG_MULTI_MODULE_SUPPORT macro) use
+#   $(AX_SWIG_PYTHON_LIBS) to link against the appropriate library. It
+#   contains the SWIG Python runtime library that is needed by the type
+#   check system for example.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Sebastian Huber <sebastian-huber@web.de>
+#   Copyright (c) 2008 Alan W. Irwin <irwin@beluga.phys.uvic.ca>
+#   Copyright (c) 2008 Rafael Laboissiere <rafael@laboissiere.net>
+#   Copyright (c) 2008 Andrew Collier <colliera@ukzn.ac.za>
+#
+#   This program is free software; you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation; either version 2 of the License, or (at your
+#   option) any later version.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+#   Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License along
+#   with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 7
+
+AU_ALIAS([SWIG_PYTHON], [AX_SWIG_PYTHON])
+AC_DEFUN([AX_SWIG_PYTHON],[
+        AC_REQUIRE([AX_PKG_SWIG])
+        AC_REQUIRE([AX_PYTHON_DEVEL])
+        test "x$1" != "xno" || swig_shadow=" -noproxy"
+        AC_SUBST([AX_SWIG_PYTHON_OPT],[-python$swig_shadow])
+        AC_SUBST([AX_SWIG_PYTHON_CPPFLAGS],[$PYTHON_CPPFLAGS])
+])
diff --git a/make.log b/make.log
new file mode 100644
index 0000000..d4379b5
--- /dev/null
+++ b/make.log
@@ -0,0 +1,355 @@
+find: `vendor': No such file or directory
+============================================
+PLATFORM_VERSION_CODENAME=REL
+PLATFORM_VERSION=7.0
+TARGET_PRODUCT=aosp_bullhead
+TARGET_BUILD_VARIANT=eng
+TARGET_BUILD_TYPE=release
+TARGET_BUILD_APPS=
+TARGET_ARCH=arm64
+TARGET_ARCH_VARIANT=armv8-a
+TARGET_CPU_VARIANT=cortex-a53
+TARGET_2ND_ARCH=arm
+TARGET_2ND_ARCH_VARIANT=armv7-a-neon
+TARGET_2ND_CPU_VARIANT=cortex-a53.a57
+HOST_ARCH=x86_64
+HOST_2ND_ARCH=x86
+HOST_OS=linux
+HOST_OS_EXTRA=Linux-3.13.0-95-generic-x86_64-with-Ubuntu-14.04-trusty
+HOST_CROSS_OS=windows
+HOST_CROSS_ARCH=x86
+HOST_CROSS_2ND_ARCH=x86_64
+HOST_BUILD_TYPE=release
+BUILD_ID=NYC
+OUT_DIR=out
+AUX_OS_VARIANT_LIST=
+============================================
+make: Entering directory `/usr/local/google/home/pstew/build/aosp'
+ninja: no work to do.
+ninja: no work to do.
+Running kati to generate build-aosp_bullhead-mmm-external_libnl_Android.mk.ninja...
+No need to regenerate ninja file
+Starting build with ninja
+ninja: Entering directory `.'
+[  1% 1/68] target thumb C: libnl_32 <= external/libnl/lib/cache.c
+[  2% 2/68] target thumb C: libnl_32 <= external/libnl/lib/data.c
+external/libnl/lib/data.c:119:24: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                        memcpy(data->d_data + data->d_size, buf, size);
+                               ~~~~~~~~~~~~ ^
+external/libnl/lib/data.c:121:24: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                        memset(data->d_data + data->d_size, 0, size);
+                               ~~~~~~~~~~~~ ^
+2 warnings generated.
+[  4% 3/68] target thumb C: libnl_32 <= external/libnl/lib/nl.c
+external/libnl/lib/nl.c:694:18: warning: comparison of integers of different signs: '__kernel_size_t' (aka 'unsigned int') and 'ssize_t' (aka 'int') [-Wsign-compare]
+        if (iov.iov_len < n || (msg.msg_flags & MSG_TRUNC)) {
+            ~~~~~~~~~~~ ^ ~
+external/libnl/lib/nl.c:786:29: warning: missing field 'nl_pad' initializer [-Wmissing-field-initializers]
+        struct sockaddr_nl nla = {0};
+                                   ^
+external/libnl/lib/nl.c:917:23: warning: comparison of integers of different signs: '__u32' (aka 'unsigned int') and 'int' [-Wsign-compare]
+                        if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) {
+                            ~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~
+3 warnings generated.
+[  5% 4/68] target thumb C: libnl_32 <= external/libnl/lib/cache_mngr.c
+[  7% 5/68] target thumb C: libnl_32 <= external/libnl/lib/socket.c
+[  8% 6/68] target thumb C: libnl_32 <= external/libnl/lib/addr.c
+external/libnl/lib/addr.c:707:14: warning: comparison of integers of different signs: 'socklen_t' (aka 'int') and 'unsigned int' [-Wsign-compare]
+                if (*salen < sizeof(*sai))
+                    ~~~~~~ ^ ~~~~~~~~~~~~
+external/libnl/lib/addr.c:719:14: warning: comparison of integers of different signs: 'socklen_t' (aka 'int') and 'unsigned int' [-Wsign-compare]
+                if (*salen < sizeof(*sa6))
+                    ~~~~~~ ^ ~~~~~~~~~~~~
+external/libnl/lib/addr.c:990:24: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        if (addr->a_prefixlen != (8 * addr->a_len)) {
+            ~~~~~~~~~~~~~~~~~ ^   ~~~~~~~~~~~~~~~
+3 warnings generated.
+[ 10% 7/68] target thumb C: libnl_32 <= external/libnl/lib/fib_lookup/lookup.c
+external/libnl/lib/fib_lookup/lookup.c:215:30: warning: missing field 'fl_fwmark' initializer [-Wmissing-field-initializers]
+        struct fib_result_nl fr = {0};
+                                    ^
+1 warning generated.
+[ 11% 8/68] target thumb C: libnl_32 <= external/libnl/lib/fib_lookup/request.c
+[ 13% 9/68] target thumb C: libnl_32 <= external/libnl/lib/object.c
+external/libnl/lib/object.c:134:22: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                memcpy((void *)new + doff, (void *)obj + doff, size);
+                       ~~~~~~~~~~~ ^
+external/libnl/lib/object.c:134:42: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                memcpy((void *)new + doff, (void *)obj + doff, size);
+                                           ~~~~~~~~~~~ ^
+2 warnings generated.
+[ 14% 10/68] target thumb C: libnl_32 <= external/libnl/lib/msg.c
+external/libnl/lib/msg.c:168:21: warning: comparison of integers of different signs: 'const __u32' (aka 'const unsigned int') and 'int' [-Wsign-compare]
+        if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
+            ~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~
+external/libnl/lib/msg.c:183:18: warning: comparison of integers of different signs: 'const __u32' (aka 'const unsigned int') and 'int' [-Wsign-compare]
+                nlh->nlmsg_len <= remaining);
+                ~~~~~~~~~~~~~~ ^  ~~~~~~~~~
+external/libnl/lib/msg.c:367:10: warning: comparison of integers of different signs: 'size_t' (aka 'unsigned int') and 'int' [-Wsign-compare]
+        if (max < nlmsg_total_size(0))
+            ~~~ ^ ~~~~~~~~~~~~~~~~~~~
+external/libnl/lib/msg.c:418:6: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+        buf += nlmsg_len;
+        ~~~ ^
+external/libnl/lib/msg.c:422:14: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                memset(buf + len, 0, tlen - len);
+                       ~~~ ^
+external/libnl/lib/msg.c:842:7: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+        data += GENL_HDRLEN;
+        ~~~~ ^
+external/libnl/lib/msg.c:855:9: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                        data += hdrsize;
+                        ~~~~ ^
+external/libnl/lib/msg.c:836:18: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        if (*payloadlen < GENL_HDRLEN)
+            ~~~~~~~~~~~ ^ ~~~~~~~~~~~
+external/libnl/lib/msg.c:897:32: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                        dump_hex(ofd, nla_data(nla) + alen,
+                                      ~~~~~~~~~~~~~ ^
+external/libnl/lib/msg.c:920:4: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
+                        strerror_r(-err->error, buf, sizeof(buf)));
+                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+external/libnl/lib/msg.c:915:21: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        if (nlmsg_len(hdr) >= sizeof(*err)) {
+            ~~~~~~~~~~~~~~ ^  ~~~~~~~~~~~~
+11 warnings generated.
+[ 16% 11/68] target thumb C: libnl_32 <= external/libnl/lib/attr.c
+external/libnl/lib/attr.c:150:19: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        return remaining >= sizeof(*nla) &&
+               ~~~~~~~~~ ^  ~~~~~~~~~~~~
+external/libnl/lib/attr.c:208:19: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        if (nla_len(nla) < minlen)
+            ~~~~~~~~~~~~ ^ ~~~~~~
+external/libnl/lib/attr.c:463:11: warning: comparison of integers of different signs: 'int' and 'size_t' (aka 'unsigned int') [-Wsign-compare]
+        if (tlen > msg->nm_size)
+            ~~~~ ^ ~~~~~~~~~~~~
+external/libnl/lib/attr.c:653:26: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        if (nla && nla_len(nla) >= sizeof(tmp))
+                   ~~~~~~~~~~~~ ^  ~~~~~~~~~~~
+external/libnl/lib/attr.c:815:41: warning: arithmetic on pointers to void is a GNU extension [-Wpointer-arith]
+        len = (void *) nlmsg_tail(msg->nm_nlh) - (void *) start;
+              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~
+external/libnl/lib/attr.c:863:41: warning: arithmetic on pointers to void is a GNU extension [-Wpointer-arith]
+        len = (void *) nlmsg_tail(msg->nm_nlh) - (void *) attr;
+              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~
+6 warnings generated.
+[ 17% 12/68] target thumb C: libnl_32 <= external/libnl/lib/utils.c
+[ 19% 13/68] target thumb C: libnl_32 <= external/libnl/lib/handlers.c
+external/libnl/lib/handlers.c:85:3: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
+                strerror_r(-e->error, buf, sizeof(buf)));
+                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+external/libnl/lib/handlers.c:206:11: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
+        if (kind < 0 || kind > NL_CB_KIND_MAX)
+            ~~~~ ^ ~
+external/libnl/lib/handlers.c:296:11: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
+        if (type < 0 || type > NL_CB_TYPE_MAX)
+            ~~~~ ^ ~
+external/libnl/lib/handlers.c:299:11: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
+        if (kind < 0 || kind > NL_CB_KIND_MAX)
+            ~~~~ ^ ~
+external/libnl/lib/handlers.c:346:11: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
+        if (kind < 0 || kind > NL_CB_KIND_MAX)
+            ~~~~ ^ ~
+5 warnings generated.
+[ 20% 14/68] target thumb C: libnl_32 <= external/libnl/lib/cache_mngt.c
+[ 22% 15/68] target thumb C: libnl_32 <= external/libnl/lib/genl/mngt.c
+external/libnl/lib/genl/mngt.c:250:22: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        if (ops->co_hdrsize < GENL_HDRSIZE(0)) {
+            ~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~
+1 warning generated.
+[ 23% 16/68] target thumb C: libnl_32 <= external/libnl/lib/genl/ctrl.c
+[ 25% 17/68] target thumb C: libnl_32 <= external/libnl/lib/genl/genl.c
+external/libnl/lib/genl/genl.c:125:24: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        if (genlmsg_len(ghdr) < NLMSG_ALIGN(hdrlen))
+            ~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~
+external/libnl/lib/genl/genl.c:261:32: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+        return genlmsg_user_hdr(gnlh) + NLMSG_ALIGN(hdrlen);
+               ~~~~~~~~~~~~~~~~~~~~~~ ^
+external/libnl/lib/genl/genl.c:365:25: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+        return nlmsg_data(nlh) + GENL_HDRLEN;
+               ~~~~~~~~~~~~~~~ ^
+3 warnings generated.
+[ 26% 18/68] target thumb C: libnl_32 <= external/libnl/lib/genl/family.c
+[ 27% 19/68] target thumb C: libnl_32 <= external/libnl/lib/route/rtnl.c
+[ 29% 20/68] target thumb C: libnl_32 <= external/libnl/lib/route/route_utils.c
+[ 30% 21/68] target thumb C: libnl_32 <= external/libnl/lib/error.c
+[ 32% 22/68] target thumb C: libnl_32 <= external/libnl/lib/netfilter/nfnl.c
+[ 33% 23/68] target thumb C: libnl_32 <= external/libnl/lib/version.c
+[ 35% 24/68] target thumb C: libnl_32 <= external/libnl/lib/hash.c
+[ 36% 25/68] target thumb C: libnl_32 <= external/libnl/lib/hashtable.c
+external/libnl/lib/hashtable.c:194:9: warning: invalid application of 'sizeof' to a void type [-Wpointer-arith]
+        return(__nl_hash(k, length, initval));
+               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+external/libnl/include/netlink/hash.h:64:62: note: expanded from macro '__nl_hash'
+#define __nl_hash(p, num, base) nl_hash_any((p), (num)*sizeof(*(p)), (base))
+                                                             ^~~~~~
+1 warning generated.
+[ 38% 26/68] target StaticLib: libnl_32 (out/target/product/bullhead/obj_arm/STATIC_LIBRARIES/libnl_intermediates/libnl.a)
+[ 39% 27/68] target SharedLib: libnl_32 (out/target/product/bullhead/obj_arm/SHARED_LIBRARIES/libnl_intermediates/LINKED/libnl.so)
+[ 41% 28/68] target Pack Relocations: libnl_32 (out/target/product/bullhead/obj_arm/SHARED_LIBRARIES/libnl_intermediates/PACKED/libnl.so)
+[ 42% 29/68] target Symbolic: libnl_32 (out/target/product/bullhead/symbols/system/lib/libnl.so)
+[ 44% 30/68] target Strip (mini debug info): libnl_32 (out/target/product/bullhead/obj_arm/lib/libnl.so)
+[ 45% 31/68] Notice file: external/libnl/NOTICE -- out/target/product/bullhead/obj/NOTICE_FILES/src//system/lib/libnl.so.txt
+[ 47% 32/68] Notice file: external/libnl/NOTICE -- out/target/product/bullhead/obj/NOTICE_FILES/src//system/lib64/libnl.a.txt
+[ 48% 33/68] Notice file: external/libnl/NOTICE -- out/target/product/bullhead/obj/NOTICE_FILES/src//system/lib/libnl.a.txt
+[ 50% 34/68] Install: out/target/product/bullhead/system/lib/libnl.so
+[ 51% 35/68] target  C: libnl <= external/libnl/lib/cache.c
+[ 52% 36/68] target  C: libnl <= external/libnl/lib/data.c
+external/libnl/lib/data.c:119:24: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                        memcpy(data->d_data + data->d_size, buf, size);
+                               ~~~~~~~~~~~~ ^
+external/libnl/lib/data.c:121:24: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                        memset(data->d_data + data->d_size, 0, size);
+                               ~~~~~~~~~~~~ ^
+2 warnings generated.
+[ 54% 37/68] target  C: libnl <= external/libnl/lib/nl.c
+external/libnl/lib/nl.c:694:18: warning: comparison of integers of different signs: '__kernel_size_t' (aka 'unsigned long') and 'ssize_t' (aka 'long') [-Wsign-compare]
+        if (iov.iov_len < n || (msg.msg_flags & MSG_TRUNC)) {
+            ~~~~~~~~~~~ ^ ~
+external/libnl/lib/nl.c:786:29: warning: missing field 'nl_pad' initializer [-Wmissing-field-initializers]
+        struct sockaddr_nl nla = {0};
+                                   ^
+external/libnl/lib/nl.c:917:23: warning: comparison of integers of different signs: '__u32' (aka 'unsigned int') and 'int' [-Wsign-compare]
+                        if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) {
+                            ~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~
+3 warnings generated.
+[ 55% 38/68] target  C: libnl <= external/libnl/lib/addr.c
+external/libnl/lib/addr.c:990:24: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        if (addr->a_prefixlen != (8 * addr->a_len)) {
+            ~~~~~~~~~~~~~~~~~ ^   ~~~~~~~~~~~~~~~
+1 warning generated.
+[ 57% 39/68] target  C: libnl <= external/libnl/lib/cache_mngr.c
+[ 58% 40/68] target  C: libnl <= external/libnl/lib/socket.c
+[ 60% 41/68] target  C: libnl <= external/libnl/lib/fib_lookup/lookup.c
+external/libnl/lib/fib_lookup/lookup.c:215:30: warning: missing field 'fl_fwmark' initializer [-Wmissing-field-initializers]
+        struct fib_result_nl fr = {0};
+                                    ^
+1 warning generated.
+[ 61% 42/68] target  C: libnl <= external/libnl/lib/msg.c
+external/libnl/lib/msg.c:168:21: warning: comparison of integers of different signs: 'const __u32' (aka 'const unsigned int') and 'int' [-Wsign-compare]
+        if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
+            ~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~
+external/libnl/lib/msg.c:183:18: warning: comparison of integers of different signs: 'const __u32' (aka 'const unsigned int') and 'int' [-Wsign-compare]
+                nlh->nlmsg_len <= remaining);
+                ~~~~~~~~~~~~~~ ^  ~~~~~~~~~
+external/libnl/lib/msg.c:367:10: warning: comparison of integers of different signs: 'size_t' (aka 'unsigned long') and 'int' [-Wsign-compare]
+        if (max < nlmsg_total_size(0))
+            ~~~ ^ ~~~~~~~~~~~~~~~~~~~
+external/libnl/lib/msg.c:418:6: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+        buf += nlmsg_len;
+        ~~~ ^
+external/libnl/lib/msg.c:422:14: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                memset(buf + len, 0, tlen - len);
+                       ~~~ ^
+external/libnl/lib/msg.c:842:7: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+        data += GENL_HDRLEN;
+        ~~~~ ^
+external/libnl/lib/msg.c:855:9: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                        data += hdrsize;
+                        ~~~~ ^
+external/libnl/lib/msg.c:836:18: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare]
+        if (*payloadlen < GENL_HDRLEN)
+            ~~~~~~~~~~~ ^ ~~~~~~~~~~~
+external/libnl/lib/msg.c:897:32: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                        dump_hex(ofd, nla_data(nla) + alen,
+                                      ~~~~~~~~~~~~~ ^
+external/libnl/lib/msg.c:920:4: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
+                        strerror_r(-err->error, buf, sizeof(buf)));
+                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+external/libnl/lib/msg.c:915:21: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare]
+        if (nlmsg_len(hdr) >= sizeof(*err)) {
+            ~~~~~~~~~~~~~~ ^  ~~~~~~~~~~~~
+11 warnings generated.
+[ 63% 43/68] target  C: libnl <= external/libnl/lib/fib_lookup/request.c
+[ 64% 44/68] target  C: libnl <= external/libnl/lib/attr.c
+external/libnl/lib/attr.c:150:19: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare]
+        return remaining >= sizeof(*nla) &&
+               ~~~~~~~~~ ^  ~~~~~~~~~~~~
+external/libnl/lib/attr.c:208:19: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        if (nla_len(nla) < minlen)
+            ~~~~~~~~~~~~ ^ ~~~~~~
+external/libnl/lib/attr.c:463:11: warning: comparison of integers of different signs: 'int' and 'size_t' (aka 'unsigned long') [-Wsign-compare]
+        if (tlen > msg->nm_size)
+            ~~~~ ^ ~~~~~~~~~~~~
+external/libnl/lib/attr.c:653:26: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare]
+        if (nla && nla_len(nla) >= sizeof(tmp))
+                   ~~~~~~~~~~~~ ^  ~~~~~~~~~~~
+external/libnl/lib/attr.c:815:41: warning: arithmetic on pointers to void is a GNU extension [-Wpointer-arith]
+        len = (void *) nlmsg_tail(msg->nm_nlh) - (void *) start;
+              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~
+external/libnl/lib/attr.c:863:41: warning: arithmetic on pointers to void is a GNU extension [-Wpointer-arith]
+        len = (void *) nlmsg_tail(msg->nm_nlh) - (void *) attr;
+              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~
+6 warnings generated.
+[ 66% 45/68] target  C: libnl <= external/libnl/lib/object.c
+external/libnl/lib/object.c:134:22: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                memcpy((void *)new + doff, (void *)obj + doff, size);
+                       ~~~~~~~~~~~ ^
+external/libnl/lib/object.c:134:42: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+                memcpy((void *)new + doff, (void *)obj + doff, size);
+                                           ~~~~~~~~~~~ ^
+2 warnings generated.
+[ 67% 46/68] target  C: libnl <= external/libnl/lib/utils.c
+[ 69% 47/68] target  C: libnl <= external/libnl/lib/cache_mngt.c
+[ 70% 48/68] target  C: libnl <= external/libnl/lib/handlers.c
+external/libnl/lib/handlers.c:85:3: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
+                strerror_r(-e->error, buf, sizeof(buf)));
+                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+external/libnl/lib/handlers.c:206:11: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
+        if (kind < 0 || kind > NL_CB_KIND_MAX)
+            ~~~~ ^ ~
+external/libnl/lib/handlers.c:296:11: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
+        if (type < 0 || type > NL_CB_TYPE_MAX)
+            ~~~~ ^ ~
+external/libnl/lib/handlers.c:299:11: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
+        if (kind < 0 || kind > NL_CB_KIND_MAX)
+            ~~~~ ^ ~
+external/libnl/lib/handlers.c:346:11: warning: comparison of unsigned enum expression < 0 is always false [-Wtautological-compare]
+        if (kind < 0 || kind > NL_CB_KIND_MAX)
+            ~~~~ ^ ~
+5 warnings generated.
+[ 72% 49/68] target  C: libnl <= external/libnl/lib/genl/ctrl.c
+[ 73% 50/68] target  C: libnl <= external/libnl/lib/genl/mngt.c
+external/libnl/lib/genl/mngt.c:250:22: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare]
+        if (ops->co_hdrsize < GENL_HDRSIZE(0)) {
+            ~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~
+1 warning generated.
+[ 75% 51/68] target  C: libnl <= external/libnl/lib/genl/family.c
+[ 76% 52/68] target  C: libnl <= external/libnl/lib/genl/genl.c
+external/libnl/lib/genl/genl.c:125:24: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare]
+        if (genlmsg_len(ghdr) < NLMSG_ALIGN(hdrlen))
+            ~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~
+external/libnl/lib/genl/genl.c:261:32: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+        return genlmsg_user_hdr(gnlh) + NLMSG_ALIGN(hdrlen);
+               ~~~~~~~~~~~~~~~~~~~~~~ ^
+external/libnl/lib/genl/genl.c:365:25: warning: arithmetic on a pointer to void is a GNU extension [-Wpointer-arith]
+        return nlmsg_data(nlh) + GENL_HDRLEN;
+               ~~~~~~~~~~~~~~~ ^
+3 warnings generated.
+[ 77% 53/68] target  C: libnl <= external/libnl/lib/route/rtnl.c
+[ 79% 54/68] target  C: libnl <= external/libnl/lib/netfilter/nfnl.c
+[ 80% 55/68] target  C: libnl <= external/libnl/lib/route/route_utils.c
+[ 82% 56/68] target  C: libnl <= external/libnl/lib/error.c
+[ 83% 57/68] target  C: libnl <= external/libnl/lib/hash.c
+[ 85% 58/68] target  C: libnl <= external/libnl/lib/version.c
+[ 86% 59/68] target  C: libnl <= external/libnl/lib/hashtable.c
+external/libnl/lib/hashtable.c:194:9: warning: invalid application of 'sizeof' to a void type [-Wpointer-arith]
+        return(__nl_hash(k, length, initval));
+               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+external/libnl/include/netlink/hash.h:64:62: note: expanded from macro '__nl_hash'
+#define __nl_hash(p, num, base) nl_hash_any((p), (num)*sizeof(*(p)), (base))
+                                                             ^~~~~~
+1 warning generated.
+[ 88% 60/68] target StaticLib: libnl (out/target/product/bullhead/obj/STATIC_LIBRARIES/libnl_intermediates/libnl.a)
+[ 89% 61/68] target SharedLib: libnl (out/target/product/bullhead/obj/SHARED_LIBRARIES/libnl_intermediates/LINKED/libnl.so)
+[ 91% 62/68] target Pack Relocations: libnl (out/target/product/bullhead/obj/SHARED_LIBRARIES/libnl_intermediates/PACKED/libnl.so)
+[ 92% 63/68] target Symbolic: libnl (out/target/product/bullhead/symbols/system/lib64/libnl.so)
+[ 94% 64/68] target Strip (mini debug info): libnl (out/target/product/bullhead/obj/lib/libnl.so)
+[ 95% 65/68] Notice file: external/libnl/NOTICE -- out/target/product/bullhead/obj/NOTICE_FILES/src//system/lib64/libnl.so.txt
+[ 97% 66/68] Install: out/target/product/bullhead/system/lib64/libnl.so
+[ 98% 67/68] build out/target/product/bullhead/obj_arm/lib/libnl.so.toc
+[100% 68/68] build out/target/product/bullhead/obj/lib/libnl.so.toc
+make: Leaving directory `/usr/local/google/home/pstew/build/aosp'
+
+#### make completed successfully (13 seconds) ####
+
diff --git a/man/Makefile.am b/man/Makefile.am
new file mode 100644
index 0000000..af1277b
--- /dev/null
+++ b/man/Makefile.am
@@ -0,0 +1,7 @@
+# -*- Makefile -*-
+
+dist_man8_MANS = \
+	nl-classid-lookup.8 \
+	nl-pktloc-lookup.8 \
+	nl-qdisc-add.8 nl-qdisc-delete.8 nl-qdisc-list.8 \
+	genl-ctrl-list.8
diff --git a/man/genl-ctrl-list.8 b/man/genl-ctrl-list.8
new file mode 100644
index 0000000..6132475
--- /dev/null
+++ b/man/genl-ctrl-list.8
@@ -0,0 +1,104 @@
+.TH genl\-ctrl-list 8 "20 April 2012" "libnl"
+.SH NAME
+genl\-ctrl\-list \- List available kernel-side Generic Netlink families
+.SH SYNOPSIS
+.B genl\-ctrl\-list [-d]
+
+.SH DESCRIPTION
+.PP
+Queries the Generic Netlink controller in kernel and prints a list of all
+registered Generic Netlink families including the version of the interface
+that has been registered.
+
+.SH OPTIONS
+.TP
+.BR \-\^h " or " \-\-help
+Print help text to console and exit.
+.TP
+.BR \-\^v " or " \-\-version
+Print versioning information to console and exit.
+.TP
+.BR \-\^d " or " \-\-details
+Include additional detailed information for each Generic Netlink
+family that is printed.
+
+The information includes:
+
+.RS
+.TP
+.B hdrsize N
+The size of the user specific header.
+
+.TP
+.B maxattr N
+The maximum Netlink attribute identifier expected by the interface.
+
+.TP
+.B op NAME (ID) <FLAGS>
+A list of available operations including their name, numeric identifier
+and the flags indicating the capabilities of the opertion.
+
+Available flags:
+.RS
+.TP
+.I admin-perm
+Requires administrative privileges
+
+.TP
+.I has-doit
+Command can handle request
+
+.TP
+.I has-dump
+Command can handle a dump request
+
+.TP
+.I has-policy
+Command enforces attribute validation policy
+.RE
+
+.TP
+.B grp NAME (ID)
+A list of registered multicast groups including name (if available)
+and identifier.
+.RE
+
+.RS
+.B Example:
+.RS
+0x0010 nlctrl version 2
+.RS 0
+    hdrsize 0 maxattr 7
+.RS 0
+      op GETFAMILY (0x03) <has-doit,has-dump,has-policy>
+.RS 0
+      grp notify (0x10)
+.RE
+
+
+.SH EXAMPLE
+.RS 0
+$ genl-ctrl-list 
+.RS 0
+0x0010 nlctrl version 2
+.RS 0
+0x0011 NLBL_MGMT version 3
+.RS 0
+0x0012 NLBL_CIPSOv4 version 3
+.RS 0
+0x0013 NLBL_UNLBL version 3
+.RS 0
+0x0014 acpi_event version 1
+.RS 0
+0x0015 thermal_event version 1
+.RS 0
+0x0016 VFS_DQUOT version 1
+.RS 0
+0x0017 TASKSTATS version 1
+.RS 0
+0x0018 NET_DM version 2
+
+.SH AUTHOR
+.PP
+Thomas Graf is the original author and current maintainer of libnl and
+libnl tools. Many people have contributed to it since.
diff --git a/man/nl-classid-lookup.8 b/man/nl-classid-lookup.8
new file mode 100644
index 0000000..3cf13de
--- /dev/null
+++ b/man/nl-classid-lookup.8
@@ -0,0 +1,51 @@
+.TH nl\-classid\-lookup 8 "19 October 2010" "libnl"
+.SH NAME
+nl\-classid\-lookup - Lookup classid definitions
+.SH SYNOPSIS
+.B nl\-classid\-lookup
+.RB [ \-hv ]
+.RB [ \-r ]
+.RB [ \-\-raw ]
+.I name
+
+.SH DESCRIPTION
+.PP
+nl\-classid\-lookup searches the classid database for a matching entry. It is used
+to resolve qdisc/class names to classid values and vice versa.
+
+.SH OPTIONS
+.TP
+.BR \-\^h " or " \-\-help
+Print help text to console and exit.
+.TP
+.BR \-\^v " or " \-\-version
+Print versioning information to console and exit.
+.TP
+.BR \-\^r " or " \-\-reverse
+Do a reverse lookup. Lookup a classid and print its name.
+.TP
+.B \-\-raw
+Print the raw classid in hexadecimal format, do not pretty print it.
+
+.SH USAGE
+.PP
+Resolve the qdisc/class name "interactive":
+.PP
+.RS
+# nl\-classid\-lookup interactive
+.RE
+.PP
+Lookup the name of classid 1:2:
+.PP
+.RS
+# nl\-classid\-lookup -r 1:2
+.RE
+
+.SH FILES
+.PP
+/etc/libnl/classid
+
+.SH AUTHOR
+.PP
+Thomas Graf is the original author and current maintainer of libnl and
+libnl tools. Many people have contributed to it since.
diff --git a/man/nl-pktloc-lookup.8 b/man/nl-pktloc-lookup.8
new file mode 100644
index 0000000..7a1daa4
--- /dev/null
+++ b/man/nl-pktloc-lookup.8
@@ -0,0 +1,48 @@
+.TH nl\-pktloc-lookup 8 "27 October 2010" "libnl"
+.SH NAME
+nl\-pktloc\-lookup - Lookup packet location definitions
+.SH SYNOPSIS
+.B nl\-pktloc\-lookup
+.I name
+.br
+.B nl\-pktloc\-lookup \-\-list
+
+.SH DESCRIPTION
+.PP
+nl\-pktloc\-lookup searches the packet location database for a matching
+entry. It is used to resolve packet location aliases to their definition,
+i.e. alignment, layer, offset, and mask.
+
+.SH OPTIONS
+.TP
+.BR \-\^h " or " \-\-help
+Print help text to console and exit.
+.TP
+.BR \-\^v " or " \-\-version
+Print versioning information to console and exit.
+.TP
+.BR \-\^l " or " \-\-list
+List all packet location definitions.
+.TP
+.BR \-\-u32=VALUE
+Prints the packet location definition in a special format that is
+understood by iproute2's u32 selector parser. It will output a
+u32 selector which will compare the provided value with the value
+specified by the packet location.
+
+Please note that due to the limitation of u32, it is not possible
+to use packet locations based on the link layer. nl-pktloc-lookup
+will print an error message in this case.
+
+Example:
+  selector=$(nl-pktloc-lookup --u32 22 tcp.sport)
+  tc filter add [...] u32 match $(selector) flowid 1:2
+
+.SH FILES
+.PP
+/etc/libnl/pktloc
+
+.SH AUTHOR
+.PP
+Thomas Graf is the original author and current maintainer of libnl and
+libnl tools. Many people have contributed to it since.
diff --git a/man/nl-qdisc-add.8 b/man/nl-qdisc-add.8
new file mode 100644
index 0000000..bc3fb17
--- /dev/null
+++ b/man/nl-qdisc-add.8
@@ -0,0 +1,118 @@
+.TH nl\-qdisc 8 "21 October 2010" "libnl"
+.SH NAME
+nl\-qdisc\-{add|list|delete} - Manage queueing disciplines
+.SH SYNOPSIS
+.B nl\-qdisc\-add \-\-dev
+.I dev
+.B \-\-parent
+.I id
+.B [OPTIONS]
+.I qdisc-type
+.B [QDISC]
+.sp
+.B nl\-qdisc\-delete [ \-\-interactive ] [OPTIONS]
+.sp
+.B nl\-qdisc\-list [OPTIONS]
+
+.SH DESCRIPTION
+.PP
+The nl\-qdisc tools allow to manage and configure queueing disciplines
+(qdiscs) in the kernel.
+
+.SH OPTIONS
+.TP
+.BR \-\^h " or " \-\-help
+Print help text to console and exit.
+.TP
+.BR \-\^v " or " \-\-version
+Print versioning information to console and exit.
+.TP
+.BR \-\^q " or " \-\-quiet
+Do not print informal notifications about actions taken to the console.
+By default a short description of each qdisc added/update/deleted will
+be printed to the console. This option disables this behaviour.
+.TP
+.BR \-\^d " or " \-\-dev "=DEV"
+Network device the qdisc is attached to.
+.TP
+.BR \-\^p " or " \-\-parent "=ID"
+Identifier of the parent qdisc/class this qdisc is attached to. The
+identifier can be specified as classid, name or one of the special
+values "root" or "ingress".
+.TP
+.BR \-\^i " or " \-\-id "=ID"
+Identifier of qdisc. It can be specified as classid or name.
+
+.SS nl\-qdisc\-add Options
+.TP
+.B \-\-update
+Update qdisc if it already exists, otherwise attempting to add a qdisc which already
+exists will result in an error. This does not include changing the type of the qdisc,
+use \-\-replace if you wish to do so.
+.TP
+.B \-\-replace
+Replace or update qdisc if it already exists. Same behaviour as \-\-update but will
+completely replace the qdisc if it exists already.
+.TP
+.B \-\-update\-only
+Update an existing qdisc but do not create it if it does not exist.
+.TP
+.B \-\-replace\-only
+Update or replace an existing qdisc but do not create it if it does exist.
+
+.SS nl\-qdisc\-delete Options
+.TP
+.B \-\-interactive
+The interactive mode requires confirmation by the user for each qdisc deleted. It
+will print a prompt for each qdisc matching the provided filter and requires the
+user to answer 'y'es or 'n'o.
+.TP
+.B \-\-yes
+Make the default answer for interactive prompts be 'y'es. This option is also
+required to delete all qdiscs on all network devices.
+.TP
+.BR \-\^k " or " \-\-kind "=TYPE"
+Only delete qdiscs of this type.
+
+.SS nl\-qdisc\-list Options
+.TP
+.B \-\-details
+Show detailed information for each qdisc listed.
+.TP
+.B \-\-stats
+Show statistics information for each qdisc listed. This option will also turn
+on detailed information automatically.
+.TP
+.BR \-\^r " or " \-\-recursive
+List all TC objects recurisvely attached to all qdiscs matching the filter.
+.TP
+.BR \-\^k " or " \-\-kind "=TYPE"
+Only list qdiscs of this type.
+
+.SH USAGE
+.PP
+Add a HTB root qdisc with id "5:":
+.PP
+.RS
+nl\-qdisc\-add \-\-dev eth0 \-\-parent root \-\-id 5: htb
+.RE
+.PP
+List all qdiscs on eth0 and print statistical data:
+.PP
+.RS
+nl\-qdisc\-list \-\-stats \-\-dev eth0
+.RE
+.PP
+Delete the qdisc "5:":
+.RS
+nl\-qdisc\-delete \-\-id 5:
+.RE
+
+.SH "SEE ALSO"
+.PP
+.B nl\-classid\-lookup(8)
+
+.SH AUTHOR
+.PP
+Thomas Graf is the original author and current maintainer of libnl and
+libnl tools. Many people have contributed to it since.
diff --git a/man/nl-qdisc-delete.8 b/man/nl-qdisc-delete.8
new file mode 100644
index 0000000..864a4e0
--- /dev/null
+++ b/man/nl-qdisc-delete.8
@@ -0,0 +1 @@
+.so man8/nl-qdisc-add.8
diff --git a/man/nl-qdisc-list.8 b/man/nl-qdisc-list.8
new file mode 100644
index 0000000..864a4e0
--- /dev/null
+++ b/man/nl-qdisc-list.8
@@ -0,0 +1 @@
+.so man8/nl-qdisc-add.8
diff --git a/python/.gitignore b/python/.gitignore
new file mode 100644
index 0000000..a83b942
--- /dev/null
+++ b/python/.gitignore
@@ -0,0 +1,4 @@
+build
+capi_wrap.c
+capi.py
+setup.py
diff --git a/python/Makefile.am b/python/Makefile.am
new file mode 100644
index 0000000..a1a3426
--- /dev/null
+++ b/python/Makefile.am
@@ -0,0 +1,3 @@
+# -*- Makefile -*-
+
+SUBDIRS = doc examples netlink tests
diff --git a/python/README b/python/README
new file mode 100644
index 0000000..4ccc337
--- /dev/null
+++ b/python/README
@@ -0,0 +1,12 @@
+
+***************************************************************************
+
+NOTE: The python wrapper is experimental and may or may not work.
+
+***************************************************************************
+
+For the brave:
+
+  (requires an installed libnl)
+  - $ python ./setup.py build
+  - $ sudo python ./setup.py install
diff --git a/python/doc/Makefile.am b/python/doc/Makefile.am
new file mode 100644
index 0000000..a2fe6f2
--- /dev/null
+++ b/python/doc/Makefile.am
@@ -0,0 +1,8 @@
+# -*- Makefile -*-
+
+EXTRA_DIST = \
+	conf.py \
+	core.rst \
+	index.rst \
+	route_addr.rst \
+	route.rst
diff --git a/python/doc/conf.py b/python/doc/conf.py
new file mode 100644
index 0000000..74041f0
--- /dev/null
+++ b/python/doc/conf.py
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+#
+# libnl-python documentation build configuration file, created by
+# sphinx-quickstart on Mon May  9 10:58:58 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.todo', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'libnl-python'
+copyright = u'2011, Thomas Graf <tgraf@suug.ch>'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'libnl-pythondoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'libnl-python.tex', u'libnl-python Documentation',
+   u'Thomas Graf \\textless{}tgraf@suug.ch\\textgreater{}', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'libnl-python', u'libnl-python Documentation',
+     [u'Thomas Graf <tgraf@suug.ch>'], 1)
+]
diff --git a/python/doc/core.rst b/python/doc/core.rst
new file mode 100644
index 0000000..012e14c
--- /dev/null
+++ b/python/doc/core.rst
@@ -0,0 +1,215 @@
+*******************
+Netlink Core Module
+*******************
+
+.. py:module:: netlink.core
+
+Examples::
+
+	import netlink.core as netlink
+
+===============
+Object
+===============
+
+.. py:class:: Object
+   
+   Base class for all classes representing a cacheable object
+
+   Example::
+	obj = netlink.Object("route/link", "link")
+
+   .. py:method:: clone
+
+      Clone the object and return a duplicate (used for COW)
+
+   .. py:method:: dump([params=None])
+
+      Call the libnl internal dump mechanism to dump the object
+      according to the parameters specified.
+
+   .. py:method:: apply(attr, val)
+
+      Applies a attribute=value pair and modifies the object accordingly.
+      Example::
+	obj.apply("mtu", 1200)      # Sets attribute mtu to 1200 (link obj)
+
+      :raises: KeyError if attribute is unknown
+      :raises: ImmutableError if attribute is not mutable
+
+   .. py:attribute:: mark
+
+      True if the object is marked, otherwise False.
+
+   .. py:attribute:: shared
+
+      True if the object is used by multiple parties, otherwise False.
+
+   .. py:attribute:: refcnt
+
+      Number of users sharing a reference to the object
+      :rtype: int
+
+   .. py:attribute:: attrs
+
+      List of attributes
+
+      :rtype: list of strings
+
+===============
+Cache
+===============
+
+.. py:class:: Cache
+   
+   Base class for all cache implementations.
+
+   A cache is a collection of cacheable objects which is typically used
+   by netlink protocols which handle any kind of object, e.g. network
+   links, network addresses, neighbours, ...
+
+   .. py:method:: subset(filter)
+
+      Returns a new cache containing the subset which matches the
+      provided filter.
+
+      :raises: ValueError if no filter is specified
+      :rtype: :py:class:`Cache`
+
+   .. py:method:: dump([params=None, filter=None])
+
+      Calls the libnl internal dump mechanism to dump the cache according
+      to the parameters and filter specified.
+
+   .. py:method:: clear()
+
+      Remove and possibly destroy all objects in the cache
+
+   .. py:method:: refill([socket=None]) -> :py:class:`Cache`
+
+      Clears and refills the cache with the content which is provided by
+      the kernel, e.g. for a link cache this would mean refilling the
+      cache with all configured network links.
+
+   .. py:method:: provide()
+      
+      Caches which have been "provided" are made available to other users
+      (of the same application context) which "require" it. F.e. a link
+      cache is generally provided to allow others to translate interface
+      indexes to link names
+
+
+   .. py:method:: unprovide()
+      
+      No longer make the cache available to others. If the cache has been
+      handed out already, that reference will still be valid.
+
+===============
+AbstractAddress
+===============
+
+.. py:class:: AbstractAddress
+   
+   Abstract representation of an address. This class is not to be mistaken
+   with :py:class:`route.Address` which represents a configured network
+   address. This class represents the actual address in a family independent
+   way::
+
+	addr = netlink.AbstractAddress('127.0.0.1/8')
+	print addr               # => '127.0.0.1/8'
+	print addr.prefixlen     # => '8'
+	print addr.family        # => 'inet'
+	print len(addr)          # => '4' (32bit ipv4 address)
+
+	a = netlink.AbstractAddress('10.0.0.1/24')
+	b = netlink.AbstractAddress('10.0.0.2/24')
+	print a == b             # => False
+
+   .. py:attribute:: prefixlen
+
+      Length of prefix in number of bits.
+
+      :rtype: int
+
+   .. py:attribute:: family
+
+      The family type of the address. Setting the address family can be
+      done with a string or a :py:class:`AddressFamily` object.
+
+      :rtype: :py:class:`AddressFamily`
+
+   .. py:attribute:: shared
+
+      True if address is in use by multiple callers, otherwise False
+
+      :rtype: bool
+
+===============
+AddressFamily
+===============
+
+.. py:class:: AddressFamily
+   
+   Address family representation::
+   
+	af = netlink.AddressFamily('inet6')
+	# raises:
+	#   - ValueError if family name is not known
+	#   - TypeError if invalid type is specified for family
+   
+	print af        # => 'inet6' (string representation)
+	print int(af)   # => 10 (numeric representation)
+	print repr(af)  # => AddressFamily('inet6')
+
+===============
+Exceptions
+===============
+
+.. py:exception:: NetlinkError
+
+   Generic exception raised by netlink modules.
+
+.. py:exception:: KernelError
+
+   Raised if an error occured while communicating with the kernel. Contains
+   the error code returning which is automatically included in the error
+   message.
+
+.. py:exception:: ImmutableError
+
+   Raised if an attribute is modified which is marked immutable.
+
+===============
+Socket
+===============
+
+.. py:class:: Socket
+
+   Netlink socket.
+
+   Note: It is not required to manually create and connect netlink sockets
+   when using caches. The caches will automatically lookup or create a
+   socket as needed.
+
+   .. py:attribute:: local_port
+
+      Local port (address) of netlink socket
+
+   .. py:attribute:: peer_port
+
+      Peer port (remote address) of netlink socket. If set, all messages
+      will be sent to that peer.
+
+   .. py:method:: connect(proto)
+
+      Connect the netlink socket using the specified netlink protocol::
+	sock.connect(netlink.NETLINK_ROUTE)
+
+   .. py:method:: disconnect()
+
+      Disconnect the socket
+
+   .. py:method:: set_bufsize(rx, tx)
+
+      Sets the size of the socket buffer
+
diff --git a/python/doc/index.rst b/python/doc/index.rst
new file mode 100644
index 0000000..8de8ae3
--- /dev/null
+++ b/python/doc/index.rst
@@ -0,0 +1,24 @@
+.. libnl-python documentation master file, created by
+   sphinx-quickstart on Mon May  9 10:58:58 2011.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to libnl-python's documentation!
+========================================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+   core
+   route
+   route_addr
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/python/doc/route.rst b/python/doc/route.rst
new file mode 100644
index 0000000..0b8f3f9
--- /dev/null
+++ b/python/doc/route.rst
@@ -0,0 +1,3 @@
+**********************
+Routing
+**********************
diff --git a/python/doc/route_addr.rst b/python/doc/route_addr.rst
new file mode 100644
index 0000000..2cfe139
--- /dev/null
+++ b/python/doc/route_addr.rst
@@ -0,0 +1,47 @@
+=================
+Network Addresses
+=================
+
+The **Address** module provides access to the network address configuration
+of the kernel. It provides an interface to fetch all configured addresses,
+add new addresses and to delete existing addresses.
+
+Fetching the list of network addresses is achieved by creating a new
+address cache::
+
+	import netlink.route.address as Address
+
+        addr_cache = Address.AddressCache()
+        addr_cache.refill()
+
+        for addr in addr_cache:
+                print addr
+
+.. py:module:: netlink.route.addr
+
+
+AddressCache
+------------
+
+.. py:class:: AddressCache
+
+   Represents a cache containing all or a subset of network addresses.
+
+   .. py:method:: lookup(ifindex, local)
+
+      Lookup the address which matches ifindex and local address
+
+      :raises: KeyError if address is not found.
+
+Address
+-------
+
+.. py:class:: Address
+
+   Representation of a configured network address.
+
+   .. py:attribute:: ifindex
+
+      Interface index
+
+      :rtype: int
diff --git a/python/examples/Makefile.am b/python/examples/Makefile.am
new file mode 100644
index 0000000..1cea5e2
--- /dev/null
+++ b/python/examples/Makefile.am
@@ -0,0 +1,6 @@
+# -*- Makefile -*-
+
+EXTRA_DIST = \
+	iface.py \
+	nl80211.py \
+	wiphy.py
diff --git a/python/examples/iface.py b/python/examples/iface.py
new file mode 100644
index 0000000..7021882
--- /dev/null
+++ b/python/examples/iface.py
@@ -0,0 +1,93 @@
+import netlink.capi as nl
+import netlink.genl.capi as genl
+import nl80211
+import sys
+import traceback
+
+class test_class:
+	def __init__(self):
+		self.done = 1;
+
+def msg_handler(m, a):
+	try:
+		e, attr = genl.py_genlmsg_parse(nl.nlmsg_hdr(m), 0,
+						nl80211.NL80211_ATTR_MAX, None)
+		if nl80211.NL80211_ATTR_WIPHY in attr:
+			thiswiphy = nl.nla_get_u32(attr[nl80211.NL80211_ATTR_WIPHY])
+			print("phy#%d" % thiswiphy)
+		if nl80211.NL80211_ATTR_IFNAME in attr:
+			print("\tinterface %s" % nl.nla_get_string(attr[nl80211.NL80211_ATTR_IFNAME]));
+		if nl80211.NL80211_ATTR_IFINDEX in attr:
+			print("\tifindex %d" % nl.nla_get_u32(attr[nl80211.NL80211_ATTR_IFINDEX]))
+		if nl80211.NL80211_ATTR_WDEV in attr:
+			print("\twdev 0x%lx" % nl.nla_get_u64(attr[nl80211.NL80211_ATTR_WDEV]))
+		if nl80211.NL80211_ATTR_MAC in attr:
+			print("\tmac %02x:%02x:%02x:%02x:%02x:%02x" % tuple(nl.nla_data(attr[nl80211.NL80211_ATTR_MAC])))
+		if nl80211.NL80211_ATTR_SSID in attr:
+			print("\tssid ", nl.nla_data(attr[nl80211.NL80211_ATTR_SSID]))
+		if nl80211.NL80211_ATTR_IFTYPE in attr:
+			iftype = nl.nla_get_u32(attr[nl80211.NL80211_ATTR_IFTYPE])
+			print("\ttype %s" % nl80211.nl80211_iftype2str[iftype])
+		if nl80211.NL80211_ATTR_WIPHY_FREQ in attr:
+			freq = nl.nla_get_u32(attr[nl80211.NL80211_ATTR_WIPHY_FREQ])
+
+			sys.stdout.write("\tfreq %d MHz" % freq);
+
+			if nl80211.NL80211_ATTR_CHANNEL_WIDTH in attr:
+				chanw = nl.nla_get_u32(attr[nl80211.NL80211_ATTR_CHANNEL_WIDTH])
+				sys.stdout.write(", width: %s" % nl80211.nl80211_chan_width2str[chanw])
+				if nl80211.NL80211_ATTR_CENTER_FREQ1 in attr:
+					sys.stdout.write(", center1: %d MHz" %
+						nl.nla_get_u32(attr[nl80211.NL80211_ATTR_CENTER_FREQ1]))
+				if nl80211.NL80211_ATTR_CENTER_FREQ2 in attr:
+					sys.stdout.write(", center2: %d MHz" %
+						nl.nla_get_u32(attr[nl80211.NL80211_ATTR_CENTER_FREQ2]))
+			elif nl80211.NL80211_ATTR_WIPHY_CHANNEL_TYPE in attr:
+				channel_type = nl.nla_get_u32(attr[nl80211.NL80211_ATTR_WIPHY_CHANNEL_TYPE])
+				sys.stdout.write(" %s" % nl80211.nl80211_channel_type2str(channel_type));
+
+			sys.stdout.write("\n");
+		return nl.NL_SKIP;
+	except Exception as e:
+		(t,v,tb) = sys.exc_info()
+		print v.message
+		traceback.print_tb(tb)
+
+def error_handler(err, a):
+	a.done = err.error
+	return nl.NL_STOP
+
+def finish_handler(m, a):
+	return nl.NL_SKIP
+
+def ack_handler(m, a):
+	a.done = 0
+	return nl.NL_STOP
+
+try:
+	cbd = test_class()
+	tx_cb = nl.nl_cb_alloc(nl.NL_CB_DEFAULT)
+	rx_cb = nl.nl_cb_clone(tx_cb)
+	s = nl.nl_socket_alloc_cb(tx_cb)
+	nl.py_nl_cb_err(rx_cb, nl.NL_CB_CUSTOM, error_handler, cbd);
+	nl.py_nl_cb_set(rx_cb, nl.NL_CB_FINISH, nl.NL_CB_CUSTOM, finish_handler, cbd);
+	nl.py_nl_cb_set(rx_cb, nl.NL_CB_ACK, nl.NL_CB_CUSTOM, ack_handler, cbd);
+	nl.py_nl_cb_set(rx_cb, nl.NL_CB_VALID, nl.NL_CB_CUSTOM, msg_handler, cbd);
+
+	genl.genl_connect(s)
+	family = genl.genl_ctrl_resolve(s, 'nl80211')
+	m = nl.nlmsg_alloc()
+	genl.genlmsg_put(m, 0, 0, family, 0, 0, nl80211.NL80211_CMD_GET_INTERFACE, 0)
+	nl.nla_put_u32(m, nl80211.NL80211_ATTR_IFINDEX, nl.if_nametoindex('wlan0'))
+
+	err = nl.nl_send_auto_complete(s, m);
+	if err < 0:
+		nl.nlmsg_free(msg)
+
+	while cbd.done > 0 and not err < 0:
+		err = nl.nl_recvmsgs(s, rx_cb)
+
+except Exception as e:
+	(t, v, tb) = sys.exc_info()
+	print v.message
+	traceback.print_tb(tb)
diff --git a/python/examples/nl80211.py b/python/examples/nl80211.py
new file mode 100644
index 0000000..7434bef
--- /dev/null
+++ b/python/examples/nl80211.py
@@ -0,0 +1,1577 @@
+###########################################################
+# file: nl80211.py
+# ---------------------------------------------------------
+# This file is generated using extract.py using pycparser
+###########################################################
+NL80211_GENL_FAMILY = 'nl80211'
+
+NL80211_CMD_UNSPEC = 0
+NL80211_CMD_GET_WIPHY = 1
+NL80211_CMD_SET_WIPHY = 2
+NL80211_CMD_NEW_WIPHY = 3
+NL80211_CMD_DEL_WIPHY = 4
+NL80211_CMD_GET_INTERFACE = 5
+NL80211_CMD_SET_INTERFACE = 6
+NL80211_CMD_NEW_INTERFACE = 7
+NL80211_CMD_DEL_INTERFACE = 8
+NL80211_CMD_GET_KEY = 9
+NL80211_CMD_SET_KEY = 10
+NL80211_CMD_NEW_KEY = 11
+NL80211_CMD_DEL_KEY = 12
+NL80211_CMD_GET_BEACON = 13
+NL80211_CMD_SET_BEACON = 14
+NL80211_CMD_START_AP = 15
+NL80211_CMD_NEW_BEACON = NL80211_CMD_START_AP
+NL80211_CMD_STOP_AP = 16
+NL80211_CMD_DEL_BEACON = NL80211_CMD_STOP_AP
+NL80211_CMD_GET_STATION = 17
+NL80211_CMD_SET_STATION = 18
+NL80211_CMD_NEW_STATION = 19
+NL80211_CMD_DEL_STATION = 20
+NL80211_CMD_GET_MPATH = 21
+NL80211_CMD_SET_MPATH = 22
+NL80211_CMD_NEW_MPATH = 23
+NL80211_CMD_DEL_MPATH = 24
+NL80211_CMD_SET_BSS = 25
+NL80211_CMD_SET_REG = 26
+NL80211_CMD_REQ_SET_REG = 27
+NL80211_CMD_GET_MESH_CONFIG = 28
+NL80211_CMD_SET_MESH_CONFIG = 29
+NL80211_CMD_SET_MGMT_EXTRA_IE = 30
+NL80211_CMD_GET_REG = 31
+NL80211_CMD_GET_SCAN = 32
+NL80211_CMD_TRIGGER_SCAN = 33
+NL80211_CMD_NEW_SCAN_RESULTS = 34
+NL80211_CMD_SCAN_ABORTED = 35
+NL80211_CMD_REG_CHANGE = 36
+NL80211_CMD_AUTHENTICATE = 37
+NL80211_CMD_ASSOCIATE = 38
+NL80211_CMD_DEAUTHENTICATE = 39
+NL80211_CMD_DISASSOCIATE = 40
+NL80211_CMD_MICHAEL_MIC_FAILURE = 41
+NL80211_CMD_REG_BEACON_HINT = 42
+NL80211_CMD_JOIN_IBSS = 43
+NL80211_CMD_LEAVE_IBSS = 44
+NL80211_CMD_TESTMODE = 45
+NL80211_CMD_CONNECT = 46
+NL80211_CMD_ROAM = 47
+NL80211_CMD_DISCONNECT = 48
+NL80211_CMD_SET_WIPHY_NETNS = 49
+NL80211_CMD_GET_SURVEY = 50
+NL80211_CMD_NEW_SURVEY_RESULTS = 51
+NL80211_CMD_SET_PMKSA = 52
+NL80211_CMD_DEL_PMKSA = 53
+NL80211_CMD_FLUSH_PMKSA = 54
+NL80211_CMD_REMAIN_ON_CHANNEL = 55
+NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL = 56
+NL80211_CMD_SET_TX_BITRATE_MASK = 57
+NL80211_CMD_REGISTER_FRAME = 58
+NL80211_CMD_REGISTER_ACTION = NL80211_CMD_REGISTER_FRAME
+NL80211_CMD_FRAME = 59
+NL80211_CMD_ACTION = NL80211_CMD_FRAME
+NL80211_CMD_FRAME_TX_STATUS = 60
+NL80211_CMD_ACTION_TX_STATUS = NL80211_CMD_FRAME_TX_STATUS
+NL80211_CMD_SET_POWER_SAVE = 61
+NL80211_CMD_GET_POWER_SAVE = 62
+NL80211_CMD_SET_CQM = 63
+NL80211_CMD_NOTIFY_CQM = 64
+NL80211_CMD_SET_CHANNEL = 65
+NL80211_CMD_SET_WDS_PEER = 66
+NL80211_CMD_FRAME_WAIT_CANCEL = 67
+NL80211_CMD_JOIN_MESH = 68
+NL80211_CMD_LEAVE_MESH = 69
+NL80211_CMD_UNPROT_DEAUTHENTICATE = 70
+NL80211_CMD_UNPROT_DISASSOCIATE = 71
+NL80211_CMD_NEW_PEER_CANDIDATE = 72
+NL80211_CMD_GET_WOWLAN = 73
+NL80211_CMD_SET_WOWLAN = 74
+NL80211_CMD_START_SCHED_SCAN = 75
+NL80211_CMD_STOP_SCHED_SCAN = 76
+NL80211_CMD_SCHED_SCAN_RESULTS = 77
+NL80211_CMD_SCHED_SCAN_STOPPED = 78
+NL80211_CMD_SET_REKEY_OFFLOAD = 79
+NL80211_CMD_PMKSA_CANDIDATE = 80
+NL80211_CMD_TDLS_OPER = 81
+NL80211_CMD_TDLS_MGMT = 82
+NL80211_CMD_UNEXPECTED_FRAME = 83
+NL80211_CMD_PROBE_CLIENT = 84
+NL80211_CMD_REGISTER_BEACONS = 85
+NL80211_CMD_UNEXPECTED_4ADDR_FRAME = 86
+NL80211_CMD_SET_NOACK_MAP = 87
+NL80211_CMD_CH_SWITCH_NOTIFY = 88
+NL80211_CMD_START_P2P_DEVICE = 89
+NL80211_CMD_STOP_P2P_DEVICE = 90
+NL80211_CMD_CONN_FAILED = 91
+NL80211_CMD_SET_MCAST_RATE = 92
+NL80211_CMD_SET_MAC_ACL = 93
+NL80211_CMD_RADAR_DETECT = 94
+NL80211_CMD_GET_PROTOCOL_FEATURES = 95
+NL80211_CMD_UPDATE_FT_IES = 96
+NL80211_CMD_FT_EVENT = 97
+NL80211_CMD_CRIT_PROTOCOL_START = 98
+NL80211_CMD_CRIT_PROTOCOL_STOP = 99
+__NL80211_CMD_AFTER_LAST = 100
+NL80211_CMD_MAX = __NL80211_CMD_AFTER_LAST - 1
+NL80211_ATTR_UNSPEC = 0
+NL80211_ATTR_WIPHY = 1
+NL80211_ATTR_WIPHY_NAME = 2
+NL80211_ATTR_IFINDEX = 3
+NL80211_ATTR_IFNAME = 4
+NL80211_ATTR_IFTYPE = 5
+NL80211_ATTR_MAC = 6
+NL80211_ATTR_KEY_DATA = 7
+NL80211_ATTR_KEY_IDX = 8
+NL80211_ATTR_KEY_CIPHER = 9
+NL80211_ATTR_KEY_SEQ = 10
+NL80211_ATTR_KEY_DEFAULT = 11
+NL80211_ATTR_BEACON_INTERVAL = 12
+NL80211_ATTR_DTIM_PERIOD = 13
+NL80211_ATTR_BEACON_HEAD = 14
+NL80211_ATTR_BEACON_TAIL = 15
+NL80211_ATTR_STA_AID = 16
+NL80211_ATTR_STA_FLAGS = 17
+NL80211_ATTR_STA_LISTEN_INTERVAL = 18
+NL80211_ATTR_STA_SUPPORTED_RATES = 19
+NL80211_ATTR_STA_VLAN = 20
+NL80211_ATTR_STA_INFO = 21
+NL80211_ATTR_WIPHY_BANDS = 22
+NL80211_ATTR_MNTR_FLAGS = 23
+NL80211_ATTR_MESH_ID = 24
+NL80211_ATTR_STA_PLINK_ACTION = 25
+NL80211_ATTR_MPATH_NEXT_HOP = 26
+NL80211_ATTR_MPATH_INFO = 27
+NL80211_ATTR_BSS_CTS_PROT = 28
+NL80211_ATTR_BSS_SHORT_PREAMBLE = 29
+NL80211_ATTR_BSS_SHORT_SLOT_TIME = 30
+NL80211_ATTR_HT_CAPABILITY = 31
+NL80211_ATTR_SUPPORTED_IFTYPES = 32
+NL80211_ATTR_REG_ALPHA2 = 33
+NL80211_ATTR_REG_RULES = 34
+NL80211_ATTR_MESH_CONFIG = 35
+NL80211_ATTR_BSS_BASIC_RATES = 36
+NL80211_ATTR_WIPHY_TXQ_PARAMS = 37
+NL80211_ATTR_WIPHY_FREQ = 38
+NL80211_ATTR_WIPHY_CHANNEL_TYPE = 39
+NL80211_ATTR_KEY_DEFAULT_MGMT = 40
+NL80211_ATTR_MGMT_SUBTYPE = 41
+NL80211_ATTR_IE = 42
+NL80211_ATTR_MAX_NUM_SCAN_SSIDS = 43
+NL80211_ATTR_SCAN_FREQUENCIES = 44
+NL80211_ATTR_SCAN_SSIDS = 45
+NL80211_ATTR_GENERATION = 46
+NL80211_ATTR_BSS = 47
+NL80211_ATTR_REG_INITIATOR = 48
+NL80211_ATTR_REG_TYPE = 49
+NL80211_ATTR_SUPPORTED_COMMANDS = 50
+NL80211_ATTR_FRAME = 51
+NL80211_ATTR_SSID = 52
+NL80211_ATTR_AUTH_TYPE = 53
+NL80211_ATTR_REASON_CODE = 54
+NL80211_ATTR_KEY_TYPE = 55
+NL80211_ATTR_MAX_SCAN_IE_LEN = 56
+NL80211_ATTR_CIPHER_SUITES = 57
+NL80211_ATTR_FREQ_BEFORE = 58
+NL80211_ATTR_FREQ_AFTER = 59
+NL80211_ATTR_FREQ_FIXED = 60
+NL80211_ATTR_WIPHY_RETRY_SHORT = 61
+NL80211_ATTR_WIPHY_RETRY_LONG = 62
+NL80211_ATTR_WIPHY_FRAG_THRESHOLD = 63
+NL80211_ATTR_WIPHY_RTS_THRESHOLD = 64
+NL80211_ATTR_TIMED_OUT = 65
+NL80211_ATTR_USE_MFP = 66
+NL80211_ATTR_STA_FLAGS2 = 67
+NL80211_ATTR_CONTROL_PORT = 68
+NL80211_ATTR_TESTDATA = 69
+NL80211_ATTR_PRIVACY = 70
+NL80211_ATTR_DISCONNECTED_BY_AP = 71
+NL80211_ATTR_STATUS_CODE = 72
+NL80211_ATTR_CIPHER_SUITES_PAIRWISE = 73
+NL80211_ATTR_CIPHER_SUITE_GROUP = 74
+NL80211_ATTR_WPA_VERSIONS = 75
+NL80211_ATTR_AKM_SUITES = 76
+NL80211_ATTR_REQ_IE = 77
+NL80211_ATTR_RESP_IE = 78
+NL80211_ATTR_PREV_BSSID = 79
+NL80211_ATTR_KEY = 80
+NL80211_ATTR_KEYS = 81
+NL80211_ATTR_PID = 82
+NL80211_ATTR_4ADDR = 83
+NL80211_ATTR_SURVEY_INFO = 84
+NL80211_ATTR_PMKID = 85
+NL80211_ATTR_MAX_NUM_PMKIDS = 86
+NL80211_ATTR_DURATION = 87
+NL80211_ATTR_COOKIE = 88
+NL80211_ATTR_WIPHY_COVERAGE_CLASS = 89
+NL80211_ATTR_TX_RATES = 90
+NL80211_ATTR_FRAME_MATCH = 91
+NL80211_ATTR_ACK = 92
+NL80211_ATTR_PS_STATE = 93
+NL80211_ATTR_CQM = 94
+NL80211_ATTR_LOCAL_STATE_CHANGE = 95
+NL80211_ATTR_AP_ISOLATE = 96
+NL80211_ATTR_WIPHY_TX_POWER_SETTING = 97
+NL80211_ATTR_WIPHY_TX_POWER_LEVEL = 98
+NL80211_ATTR_TX_FRAME_TYPES = 99
+NL80211_ATTR_RX_FRAME_TYPES = 100
+NL80211_ATTR_FRAME_TYPE = 101
+NL80211_ATTR_CONTROL_PORT_ETHERTYPE = 102
+NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT = 103
+NL80211_ATTR_SUPPORT_IBSS_RSN = 104
+NL80211_ATTR_WIPHY_ANTENNA_TX = 105
+NL80211_ATTR_WIPHY_ANTENNA_RX = 106
+NL80211_ATTR_MCAST_RATE = 107
+NL80211_ATTR_OFFCHANNEL_TX_OK = 108
+NL80211_ATTR_BSS_HT_OPMODE = 109
+NL80211_ATTR_KEY_DEFAULT_TYPES = 110
+NL80211_ATTR_MAX_REMAIN_ON_CHANNEL_DURATION = 111
+NL80211_ATTR_MESH_SETUP = 112
+NL80211_ATTR_WIPHY_ANTENNA_AVAIL_TX = 113
+NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX = 114
+NL80211_ATTR_SUPPORT_MESH_AUTH = 115
+NL80211_ATTR_STA_PLINK_STATE = 116
+NL80211_ATTR_WOWLAN_TRIGGERS = 117
+NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED = 118
+NL80211_ATTR_SCHED_SCAN_INTERVAL = 119
+NL80211_ATTR_INTERFACE_COMBINATIONS = 120
+NL80211_ATTR_SOFTWARE_IFTYPES = 121
+NL80211_ATTR_REKEY_DATA = 122
+NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS = 123
+NL80211_ATTR_MAX_SCHED_SCAN_IE_LEN = 124
+NL80211_ATTR_SCAN_SUPP_RATES = 125
+NL80211_ATTR_HIDDEN_SSID = 126
+NL80211_ATTR_IE_PROBE_RESP = 127
+NL80211_ATTR_IE_ASSOC_RESP = 128
+NL80211_ATTR_STA_WME = 129
+NL80211_ATTR_SUPPORT_AP_UAPSD = 130
+NL80211_ATTR_ROAM_SUPPORT = 131
+NL80211_ATTR_SCHED_SCAN_MATCH = 132
+NL80211_ATTR_MAX_MATCH_SETS = 133
+NL80211_ATTR_PMKSA_CANDIDATE = 134
+NL80211_ATTR_TX_NO_CCK_RATE = 135
+NL80211_ATTR_TDLS_ACTION = 136
+NL80211_ATTR_TDLS_DIALOG_TOKEN = 137
+NL80211_ATTR_TDLS_OPERATION = 138
+NL80211_ATTR_TDLS_SUPPORT = 139
+NL80211_ATTR_TDLS_EXTERNAL_SETUP = 140
+NL80211_ATTR_DEVICE_AP_SME = 141
+NL80211_ATTR_DONT_WAIT_FOR_ACK = 142
+NL80211_ATTR_FEATURE_FLAGS = 143
+NL80211_ATTR_PROBE_RESP_OFFLOAD = 144
+NL80211_ATTR_PROBE_RESP = 145
+NL80211_ATTR_DFS_REGION = 146
+NL80211_ATTR_DISABLE_HT = 147
+NL80211_ATTR_HT_CAPABILITY_MASK = 148
+NL80211_ATTR_NOACK_MAP = 149
+NL80211_ATTR_INACTIVITY_TIMEOUT = 150
+NL80211_ATTR_RX_SIGNAL_DBM = 151
+NL80211_ATTR_BG_SCAN_PERIOD = 152
+NL80211_ATTR_WDEV = 153
+NL80211_ATTR_USER_REG_HINT_TYPE = 154
+NL80211_ATTR_CONN_FAILED_REASON = 155
+NL80211_ATTR_SAE_DATA = 156
+NL80211_ATTR_VHT_CAPABILITY = 157
+NL80211_ATTR_SCAN_FLAGS = 158
+NL80211_ATTR_CHANNEL_WIDTH = 159
+NL80211_ATTR_CENTER_FREQ1 = 160
+NL80211_ATTR_CENTER_FREQ2 = 161
+NL80211_ATTR_P2P_CTWINDOW = 162
+NL80211_ATTR_P2P_OPPPS = 163
+NL80211_ATTR_LOCAL_MESH_POWER_MODE = 164
+NL80211_ATTR_ACL_POLICY = 165
+NL80211_ATTR_MAC_ADDRS = 166
+NL80211_ATTR_MAC_ACL_MAX = 167
+NL80211_ATTR_RADAR_EVENT = 168
+NL80211_ATTR_EXT_CAPA = 169
+NL80211_ATTR_EXT_CAPA_MASK = 170
+NL80211_ATTR_STA_CAPABILITY = 171
+NL80211_ATTR_STA_EXT_CAPABILITY = 172
+NL80211_ATTR_PROTOCOL_FEATURES = 173
+NL80211_ATTR_SPLIT_WIPHY_DUMP = 174
+NL80211_ATTR_DISABLE_VHT = 175
+NL80211_ATTR_VHT_CAPABILITY_MASK = 176
+NL80211_ATTR_MDID = 177
+NL80211_ATTR_IE_RIC = 178
+NL80211_ATTR_CRIT_PROT_ID = 179
+NL80211_ATTR_MAX_CRIT_PROT_DURATION = 180
+NL80211_ATTR_PEER_AID = 181
+__NL80211_ATTR_AFTER_LAST = 182
+NL80211_ATTR_MAX = __NL80211_ATTR_AFTER_LAST - 1
+NL80211_IFTYPE_UNSPECIFIED = 0
+NL80211_IFTYPE_ADHOC = 1
+NL80211_IFTYPE_STATION = 2
+NL80211_IFTYPE_AP = 3
+NL80211_IFTYPE_AP_VLAN = 4
+NL80211_IFTYPE_WDS = 5
+NL80211_IFTYPE_MONITOR = 6
+NL80211_IFTYPE_MESH_POINT = 7
+NL80211_IFTYPE_P2P_CLIENT = 8
+NL80211_IFTYPE_P2P_GO = 9
+NL80211_IFTYPE_P2P_DEVICE = 10
+NUM_NL80211_IFTYPES = 11
+NL80211_IFTYPE_MAX = NUM_NL80211_IFTYPES - 1
+__NL80211_STA_FLAG_INVALID = 0
+NL80211_STA_FLAG_AUTHORIZED = 1
+NL80211_STA_FLAG_SHORT_PREAMBLE = 2
+NL80211_STA_FLAG_WME = 3
+NL80211_STA_FLAG_MFP = 4
+NL80211_STA_FLAG_AUTHENTICATED = 5
+NL80211_STA_FLAG_TDLS_PEER = 6
+NL80211_STA_FLAG_ASSOCIATED = 7
+__NL80211_STA_FLAG_AFTER_LAST = 8
+NL80211_STA_FLAG_MAX = __NL80211_STA_FLAG_AFTER_LAST - 1
+__NL80211_RATE_INFO_INVALID = 0
+NL80211_RATE_INFO_BITRATE = 1
+NL80211_RATE_INFO_MCS = 2
+NL80211_RATE_INFO_40_MHZ_WIDTH = 3
+NL80211_RATE_INFO_SHORT_GI = 4
+NL80211_RATE_INFO_BITRATE32 = 5
+NL80211_RATE_INFO_VHT_MCS = 6
+NL80211_RATE_INFO_VHT_NSS = 7
+NL80211_RATE_INFO_80_MHZ_WIDTH = 8
+NL80211_RATE_INFO_80P80_MHZ_WIDTH = 9
+NL80211_RATE_INFO_160_MHZ_WIDTH = 10
+__NL80211_RATE_INFO_AFTER_LAST = 11
+NL80211_RATE_INFO_MAX = __NL80211_RATE_INFO_AFTER_LAST - 1
+__NL80211_STA_BSS_PARAM_INVALID = 0
+NL80211_STA_BSS_PARAM_CTS_PROT = 1
+NL80211_STA_BSS_PARAM_SHORT_PREAMBLE = 2
+NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME = 3
+NL80211_STA_BSS_PARAM_DTIM_PERIOD = 4
+NL80211_STA_BSS_PARAM_BEACON_INTERVAL = 5
+__NL80211_STA_BSS_PARAM_AFTER_LAST = 6
+NL80211_STA_BSS_PARAM_MAX = __NL80211_STA_BSS_PARAM_AFTER_LAST - 1
+__NL80211_STA_INFO_INVALID = 0
+NL80211_STA_INFO_INACTIVE_TIME = 1
+NL80211_STA_INFO_RX_BYTES = 2
+NL80211_STA_INFO_TX_BYTES = 3
+NL80211_STA_INFO_LLID = 4
+NL80211_STA_INFO_PLID = 5
+NL80211_STA_INFO_PLINK_STATE = 6
+NL80211_STA_INFO_SIGNAL = 7
+NL80211_STA_INFO_TX_BITRATE = 8
+NL80211_STA_INFO_RX_PACKETS = 9
+NL80211_STA_INFO_TX_PACKETS = 10
+NL80211_STA_INFO_TX_RETRIES = 11
+NL80211_STA_INFO_TX_FAILED = 12
+NL80211_STA_INFO_SIGNAL_AVG = 13
+NL80211_STA_INFO_RX_BITRATE = 14
+NL80211_STA_INFO_BSS_PARAM = 15
+NL80211_STA_INFO_CONNECTED_TIME = 16
+NL80211_STA_INFO_STA_FLAGS = 17
+NL80211_STA_INFO_BEACON_LOSS = 18
+NL80211_STA_INFO_T_OFFSET = 19
+NL80211_STA_INFO_LOCAL_PM = 20
+NL80211_STA_INFO_PEER_PM = 21
+NL80211_STA_INFO_NONPEER_PM = 22
+NL80211_STA_INFO_RX_BYTES64 = 23
+NL80211_STA_INFO_TX_BYTES64 = 24
+NL80211_STA_INFO_CHAIN_SIGNAL = 25
+NL80211_STA_INFO_CHAIN_SIGNAL_AVG = 26
+__NL80211_STA_INFO_AFTER_LAST = 27
+NL80211_STA_INFO_MAX = __NL80211_STA_INFO_AFTER_LAST - 1
+NL80211_MPATH_FLAG_ACTIVE = 1 << 0
+NL80211_MPATH_FLAG_RESOLVING = 1 << 1
+NL80211_MPATH_FLAG_SN_VALID = 1 << 2
+NL80211_MPATH_FLAG_FIXED = 1 << 3
+NL80211_MPATH_FLAG_RESOLVED = 1 << 4
+__NL80211_MPATH_INFO_INVALID = 0
+NL80211_MPATH_INFO_FRAME_QLEN = 1
+NL80211_MPATH_INFO_SN = 2
+NL80211_MPATH_INFO_METRIC = 3
+NL80211_MPATH_INFO_EXPTIME = 4
+NL80211_MPATH_INFO_FLAGS = 5
+NL80211_MPATH_INFO_DISCOVERY_TIMEOUT = 6
+NL80211_MPATH_INFO_DISCOVERY_RETRIES = 7
+__NL80211_MPATH_INFO_AFTER_LAST = 8
+NL80211_MPATH_INFO_MAX = __NL80211_MPATH_INFO_AFTER_LAST - 1
+__NL80211_BAND_ATTR_INVALID = 0
+NL80211_BAND_ATTR_FREQS = 1
+NL80211_BAND_ATTR_RATES = 2
+NL80211_BAND_ATTR_HT_MCS_SET = 3
+NL80211_BAND_ATTR_HT_CAPA = 4
+NL80211_BAND_ATTR_HT_AMPDU_FACTOR = 5
+NL80211_BAND_ATTR_HT_AMPDU_DENSITY = 6
+NL80211_BAND_ATTR_VHT_MCS_SET = 7
+NL80211_BAND_ATTR_VHT_CAPA = 8
+__NL80211_BAND_ATTR_AFTER_LAST = 9
+NL80211_BAND_ATTR_MAX = __NL80211_BAND_ATTR_AFTER_LAST - 1
+__NL80211_FREQUENCY_ATTR_INVALID = 0
+NL80211_FREQUENCY_ATTR_FREQ = 1
+NL80211_FREQUENCY_ATTR_DISABLED = 2
+NL80211_FREQUENCY_ATTR_PASSIVE_SCAN = 3
+NL80211_FREQUENCY_ATTR_NO_IBSS = 4
+NL80211_FREQUENCY_ATTR_RADAR = 5
+NL80211_FREQUENCY_ATTR_MAX_TX_POWER = 6
+NL80211_FREQUENCY_ATTR_DFS_STATE = 7
+NL80211_FREQUENCY_ATTR_DFS_TIME = 8
+NL80211_FREQUENCY_ATTR_NO_HT40_MINUS = 9
+NL80211_FREQUENCY_ATTR_NO_HT40_PLUS = 10
+NL80211_FREQUENCY_ATTR_NO_80MHZ = 11
+NL80211_FREQUENCY_ATTR_NO_160MHZ = 12
+__NL80211_FREQUENCY_ATTR_AFTER_LAST = 13
+NL80211_FREQUENCY_ATTR_MAX = __NL80211_FREQUENCY_ATTR_AFTER_LAST - 1
+__NL80211_BITRATE_ATTR_INVALID = 0
+NL80211_BITRATE_ATTR_RATE = 1
+NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE = 2
+__NL80211_BITRATE_ATTR_AFTER_LAST = 3
+NL80211_BITRATE_ATTR_MAX = __NL80211_BITRATE_ATTR_AFTER_LAST - 1
+NL80211_REGDOM_SET_BY_CORE = 0
+NL80211_REGDOM_SET_BY_USER = 1
+NL80211_REGDOM_SET_BY_DRIVER = 2
+NL80211_REGDOM_SET_BY_COUNTRY_IE = 3
+NL80211_REGDOM_TYPE_COUNTRY = 0
+NL80211_REGDOM_TYPE_WORLD = 1
+NL80211_REGDOM_TYPE_CUSTOM_WORLD = 2
+NL80211_REGDOM_TYPE_INTERSECTION = 3
+__NL80211_REG_RULE_ATTR_INVALID = 0
+NL80211_ATTR_REG_RULE_FLAGS = 1
+NL80211_ATTR_FREQ_RANGE_START = 2
+NL80211_ATTR_FREQ_RANGE_END = 3
+NL80211_ATTR_FREQ_RANGE_MAX_BW = 4
+NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN = 5
+NL80211_ATTR_POWER_RULE_MAX_EIRP = 6
+__NL80211_REG_RULE_ATTR_AFTER_LAST = 7
+NL80211_REG_RULE_ATTR_MAX = __NL80211_REG_RULE_ATTR_AFTER_LAST - 1
+__NL80211_SCHED_SCAN_MATCH_ATTR_INVALID = 0
+NL80211_SCHED_SCAN_MATCH_ATTR_SSID = 1
+NL80211_SCHED_SCAN_MATCH_ATTR_RSSI = 2
+__NL80211_SCHED_SCAN_MATCH_ATTR_AFTER_LAST = 3
+NL80211_SCHED_SCAN_MATCH_ATTR_MAX = __NL80211_SCHED_SCAN_MATCH_ATTR_AFTER_LAST - 1
+NL80211_RRF_NO_OFDM = 1 << 0
+NL80211_RRF_NO_CCK = 1 << 1
+NL80211_RRF_NO_INDOOR = 1 << 2
+NL80211_RRF_NO_OUTDOOR = 1 << 3
+NL80211_RRF_DFS = 1 << 4
+NL80211_RRF_PTP_ONLY = 1 << 5
+NL80211_RRF_PTMP_ONLY = 1 << 6
+NL80211_RRF_PASSIVE_SCAN = 1 << 7
+NL80211_RRF_NO_IBSS = 1 << 8
+NL80211_DFS_UNSET = 0
+NL80211_DFS_FCC = 1
+NL80211_DFS_ETSI = 2
+NL80211_DFS_JP = 3
+NL80211_USER_REG_HINT_USER = 0
+NL80211_USER_REG_HINT_CELL_BASE = 1
+__NL80211_SURVEY_INFO_INVALID = 0
+NL80211_SURVEY_INFO_FREQUENCY = 1
+NL80211_SURVEY_INFO_NOISE = 2
+NL80211_SURVEY_INFO_IN_USE = 3
+NL80211_SURVEY_INFO_CHANNEL_TIME = 4
+NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY = 5
+NL80211_SURVEY_INFO_CHANNEL_TIME_EXT_BUSY = 6
+NL80211_SURVEY_INFO_CHANNEL_TIME_RX = 7
+NL80211_SURVEY_INFO_CHANNEL_TIME_TX = 8
+__NL80211_SURVEY_INFO_AFTER_LAST = 9
+NL80211_SURVEY_INFO_MAX = __NL80211_SURVEY_INFO_AFTER_LAST - 1
+__NL80211_MNTR_FLAG_INVALID = 0
+NL80211_MNTR_FLAG_FCSFAIL = 1
+NL80211_MNTR_FLAG_PLCPFAIL = 2
+NL80211_MNTR_FLAG_CONTROL = 3
+NL80211_MNTR_FLAG_OTHER_BSS = 4
+NL80211_MNTR_FLAG_COOK_FRAMES = 5
+NL80211_MNTR_FLAG_ACTIVE = 6
+__NL80211_MNTR_FLAG_AFTER_LAST = 7
+NL80211_MNTR_FLAG_MAX = __NL80211_MNTR_FLAG_AFTER_LAST - 1
+NL80211_MESH_POWER_UNKNOWN = 0
+NL80211_MESH_POWER_ACTIVE = 1
+NL80211_MESH_POWER_LIGHT_SLEEP = 2
+NL80211_MESH_POWER_DEEP_SLEEP = 3
+__NL80211_MESH_POWER_AFTER_LAST = 4
+NL80211_MESH_POWER_MAX = __NL80211_MESH_POWER_AFTER_LAST - 1
+__NL80211_MESHCONF_INVALID = 0
+NL80211_MESHCONF_RETRY_TIMEOUT = 1
+NL80211_MESHCONF_CONFIRM_TIMEOUT = 2
+NL80211_MESHCONF_HOLDING_TIMEOUT = 3
+NL80211_MESHCONF_MAX_PEER_LINKS = 4
+NL80211_MESHCONF_MAX_RETRIES = 5
+NL80211_MESHCONF_TTL = 6
+NL80211_MESHCONF_AUTO_OPEN_PLINKS = 7
+NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES = 8
+NL80211_MESHCONF_PATH_REFRESH_TIME = 9
+NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT = 10
+NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT = 11
+NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL = 12
+NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME = 13
+NL80211_MESHCONF_HWMP_ROOTMODE = 14
+NL80211_MESHCONF_ELEMENT_TTL = 15
+NL80211_MESHCONF_HWMP_RANN_INTERVAL = 16
+NL80211_MESHCONF_GATE_ANNOUNCEMENTS = 17
+NL80211_MESHCONF_HWMP_PERR_MIN_INTERVAL = 18
+NL80211_MESHCONF_FORWARDING = 19
+NL80211_MESHCONF_RSSI_THRESHOLD = 20
+NL80211_MESHCONF_SYNC_OFFSET_MAX_NEIGHBOR = 21
+NL80211_MESHCONF_HT_OPMODE = 22
+NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT = 23
+NL80211_MESHCONF_HWMP_ROOT_INTERVAL = 24
+NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL = 25
+NL80211_MESHCONF_POWER_MODE = 26
+NL80211_MESHCONF_AWAKE_WINDOW = 27
+NL80211_MESHCONF_PLINK_TIMEOUT = 28
+__NL80211_MESHCONF_ATTR_AFTER_LAST = 29
+NL80211_MESHCONF_ATTR_MAX = __NL80211_MESHCONF_ATTR_AFTER_LAST - 1
+__NL80211_MESH_SETUP_INVALID = 0
+NL80211_MESH_SETUP_ENABLE_VENDOR_PATH_SEL = 1
+NL80211_MESH_SETUP_ENABLE_VENDOR_METRIC = 2
+NL80211_MESH_SETUP_IE = 3
+NL80211_MESH_SETUP_USERSPACE_AUTH = 4
+NL80211_MESH_SETUP_USERSPACE_AMPE = 5
+NL80211_MESH_SETUP_ENABLE_VENDOR_SYNC = 6
+NL80211_MESH_SETUP_USERSPACE_MPM = 7
+NL80211_MESH_SETUP_AUTH_PROTOCOL = 8
+__NL80211_MESH_SETUP_ATTR_AFTER_LAST = 9
+NL80211_MESH_SETUP_ATTR_MAX = __NL80211_MESH_SETUP_ATTR_AFTER_LAST - 1
+__NL80211_TXQ_ATTR_INVALID = 0
+NL80211_TXQ_ATTR_AC = 1
+NL80211_TXQ_ATTR_TXOP = 2
+NL80211_TXQ_ATTR_CWMIN = 3
+NL80211_TXQ_ATTR_CWMAX = 4
+NL80211_TXQ_ATTR_AIFS = 5
+__NL80211_TXQ_ATTR_AFTER_LAST = 6
+NL80211_TXQ_ATTR_MAX = __NL80211_TXQ_ATTR_AFTER_LAST - 1
+NL80211_AC_VO = 0
+NL80211_AC_VI = 1
+NL80211_AC_BE = 2
+NL80211_AC_BK = 3
+NL80211_NUM_ACS = 4
+NL80211_CHAN_NO_HT = 0
+NL80211_CHAN_HT20 = 1
+NL80211_CHAN_HT40MINUS = 2
+NL80211_CHAN_HT40PLUS = 3
+NL80211_CHAN_WIDTH_20_NOHT = 0
+NL80211_CHAN_WIDTH_20 = 1
+NL80211_CHAN_WIDTH_40 = 2
+NL80211_CHAN_WIDTH_80 = 3
+NL80211_CHAN_WIDTH_80P80 = 4
+NL80211_CHAN_WIDTH_160 = 5
+__NL80211_BSS_INVALID = 0
+NL80211_BSS_BSSID = 1
+NL80211_BSS_FREQUENCY = 2
+NL80211_BSS_TSF = 3
+NL80211_BSS_BEACON_INTERVAL = 4
+NL80211_BSS_CAPABILITY = 5
+NL80211_BSS_INFORMATION_ELEMENTS = 6
+NL80211_BSS_SIGNAL_MBM = 7
+NL80211_BSS_SIGNAL_UNSPEC = 8
+NL80211_BSS_STATUS = 9
+NL80211_BSS_SEEN_MS_AGO = 10
+NL80211_BSS_BEACON_IES = 11
+__NL80211_BSS_AFTER_LAST = 12
+NL80211_BSS_MAX = __NL80211_BSS_AFTER_LAST - 1
+NL80211_BSS_STATUS_AUTHENTICATED = 0
+NL80211_BSS_STATUS_ASSOCIATED = 1
+NL80211_BSS_STATUS_IBSS_JOINED = 2
+NL80211_AUTHTYPE_OPEN_SYSTEM = 0
+NL80211_AUTHTYPE_SHARED_KEY = 1
+NL80211_AUTHTYPE_FT = 2
+NL80211_AUTHTYPE_NETWORK_EAP = 3
+NL80211_AUTHTYPE_SAE = 4
+__NL80211_AUTHTYPE_NUM = 5
+NL80211_AUTHTYPE_MAX = __NL80211_AUTHTYPE_NUM - 1
+NL80211_AUTHTYPE_AUTOMATIC = 6
+NL80211_KEYTYPE_GROUP = 0
+NL80211_KEYTYPE_PAIRWISE = 1
+NL80211_KEYTYPE_PEERKEY = 2
+NUM_NL80211_KEYTYPES = 3
+NL80211_MFP_NO = 0
+NL80211_MFP_REQUIRED = 1
+NL80211_WPA_VERSION_1 = 1 << 0
+NL80211_WPA_VERSION_2 = 1 << 1
+__NL80211_KEY_DEFAULT_TYPE_INVALID = 0
+NL80211_KEY_DEFAULT_TYPE_UNICAST = 1
+NL80211_KEY_DEFAULT_TYPE_MULTICAST = 2
+NUM_NL80211_KEY_DEFAULT_TYPES = 3
+__NL80211_KEY_INVALID = 0
+NL80211_KEY_DATA = 1
+NL80211_KEY_IDX = 2
+NL80211_KEY_CIPHER = 3
+NL80211_KEY_SEQ = 4
+NL80211_KEY_DEFAULT = 5
+NL80211_KEY_DEFAULT_MGMT = 6
+NL80211_KEY_TYPE = 7
+NL80211_KEY_DEFAULT_TYPES = 8
+__NL80211_KEY_AFTER_LAST = 9
+NL80211_KEY_MAX = __NL80211_KEY_AFTER_LAST - 1
+__NL80211_TXRATE_INVALID = 0
+NL80211_TXRATE_LEGACY = 1
+NL80211_TXRATE_MCS = 2
+__NL80211_TXRATE_AFTER_LAST = 3
+NL80211_TXRATE_MAX = __NL80211_TXRATE_AFTER_LAST - 1
+NL80211_BAND_2GHZ = 0
+NL80211_BAND_5GHZ = 1
+NL80211_BAND_60GHZ = 2
+NL80211_PS_DISABLED = 0
+NL80211_PS_ENABLED = 1
+__NL80211_ATTR_CQM_INVALID = 0
+NL80211_ATTR_CQM_RSSI_THOLD = 1
+NL80211_ATTR_CQM_RSSI_HYST = 2
+NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT = 3
+NL80211_ATTR_CQM_PKT_LOSS_EVENT = 4
+NL80211_ATTR_CQM_TXE_RATE = 5
+NL80211_ATTR_CQM_TXE_PKTS = 6
+NL80211_ATTR_CQM_TXE_INTVL = 7
+__NL80211_ATTR_CQM_AFTER_LAST = 8
+NL80211_ATTR_CQM_MAX = __NL80211_ATTR_CQM_AFTER_LAST - 1
+NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW = 0
+NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH = 1
+NL80211_CQM_RSSI_BEACON_LOSS_EVENT = 2
+NL80211_TX_POWER_AUTOMATIC = 0
+NL80211_TX_POWER_LIMITED = 1
+NL80211_TX_POWER_FIXED = 2
+__NL80211_WOWLAN_PKTPAT_INVALID = 0
+NL80211_WOWLAN_PKTPAT_MASK = 1
+NL80211_WOWLAN_PKTPAT_PATTERN = 2
+NL80211_WOWLAN_PKTPAT_OFFSET = 3
+NUM_NL80211_WOWLAN_PKTPAT = 4
+MAX_NL80211_WOWLAN_PKTPAT = NUM_NL80211_WOWLAN_PKTPAT - 1
+__NL80211_WOWLAN_TRIG_INVALID = 0
+NL80211_WOWLAN_TRIG_ANY = 1
+NL80211_WOWLAN_TRIG_DISCONNECT = 2
+NL80211_WOWLAN_TRIG_MAGIC_PKT = 3
+NL80211_WOWLAN_TRIG_PKT_PATTERN = 4
+NL80211_WOWLAN_TRIG_GTK_REKEY_SUPPORTED = 5
+NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE = 6
+NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST = 7
+NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE = 8
+NL80211_WOWLAN_TRIG_RFKILL_RELEASE = 9
+NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211 = 10
+NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211_LEN = 11
+NL80211_WOWLAN_TRIG_WAKEUP_PKT_8023 = 12
+NL80211_WOWLAN_TRIG_WAKEUP_PKT_8023_LEN = 13
+NL80211_WOWLAN_TRIG_TCP_CONNECTION = 14
+NL80211_WOWLAN_TRIG_WAKEUP_TCP_MATCH = 15
+NL80211_WOWLAN_TRIG_WAKEUP_TCP_CONNLOST = 16
+NL80211_WOWLAN_TRIG_WAKEUP_TCP_NOMORETOKENS = 17
+NUM_NL80211_WOWLAN_TRIG = 18
+MAX_NL80211_WOWLAN_TRIG = NUM_NL80211_WOWLAN_TRIG - 1
+__NL80211_WOWLAN_TCP_INVALID = 0
+NL80211_WOWLAN_TCP_SRC_IPV4 = 1
+NL80211_WOWLAN_TCP_DST_IPV4 = 2
+NL80211_WOWLAN_TCP_DST_MAC = 3
+NL80211_WOWLAN_TCP_SRC_PORT = 4
+NL80211_WOWLAN_TCP_DST_PORT = 5
+NL80211_WOWLAN_TCP_DATA_PAYLOAD = 6
+NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ = 7
+NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN = 8
+NL80211_WOWLAN_TCP_DATA_INTERVAL = 9
+NL80211_WOWLAN_TCP_WAKE_PAYLOAD = 10
+NL80211_WOWLAN_TCP_WAKE_MASK = 11
+NUM_NL80211_WOWLAN_TCP = 12
+MAX_NL80211_WOWLAN_TCP = NUM_NL80211_WOWLAN_TCP - 1
+NL80211_IFACE_LIMIT_UNSPEC = 0
+NL80211_IFACE_LIMIT_MAX = 1
+NL80211_IFACE_LIMIT_TYPES = 2
+NUM_NL80211_IFACE_LIMIT = 3
+MAX_NL80211_IFACE_LIMIT = NUM_NL80211_IFACE_LIMIT - 1
+NL80211_IFACE_COMB_UNSPEC = 0
+NL80211_IFACE_COMB_LIMITS = 1
+NL80211_IFACE_COMB_MAXNUM = 2
+NL80211_IFACE_COMB_STA_AP_BI_MATCH = 3
+NL80211_IFACE_COMB_NUM_CHANNELS = 4
+NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS = 5
+NUM_NL80211_IFACE_COMB = 6
+MAX_NL80211_IFACE_COMB = NUM_NL80211_IFACE_COMB - 1
+NL80211_PLINK_LISTEN = 0
+NL80211_PLINK_OPN_SNT = 1
+NL80211_PLINK_OPN_RCVD = 2
+NL80211_PLINK_CNF_RCVD = 3
+NL80211_PLINK_ESTAB = 4
+NL80211_PLINK_HOLDING = 5
+NL80211_PLINK_BLOCKED = 6
+NUM_NL80211_PLINK_STATES = 7
+MAX_NL80211_PLINK_STATES = NUM_NL80211_PLINK_STATES - 1
+NL80211_PLINK_ACTION_NO_ACTION = 0
+NL80211_PLINK_ACTION_OPEN = 1
+NL80211_PLINK_ACTION_BLOCK = 2
+NUM_NL80211_PLINK_ACTIONS = 3
+__NL80211_REKEY_DATA_INVALID = 0
+NL80211_REKEY_DATA_KEK = 1
+NL80211_REKEY_DATA_KCK = 2
+NL80211_REKEY_DATA_REPLAY_CTR = 3
+NUM_NL80211_REKEY_DATA = 4
+MAX_NL80211_REKEY_DATA = NUM_NL80211_REKEY_DATA - 1
+NL80211_HIDDEN_SSID_NOT_IN_USE = 0
+NL80211_HIDDEN_SSID_ZERO_LEN = 1
+NL80211_HIDDEN_SSID_ZERO_CONTENTS = 2
+__NL80211_STA_WME_INVALID = 0
+NL80211_STA_WME_UAPSD_QUEUES = 1
+NL80211_STA_WME_MAX_SP = 2
+__NL80211_STA_WME_AFTER_LAST = 3
+NL80211_STA_WME_MAX = __NL80211_STA_WME_AFTER_LAST - 1
+__NL80211_PMKSA_CANDIDATE_INVALID = 0
+NL80211_PMKSA_CANDIDATE_INDEX = 1
+NL80211_PMKSA_CANDIDATE_BSSID = 2
+NL80211_PMKSA_CANDIDATE_PREAUTH = 3
+NUM_NL80211_PMKSA_CANDIDATE = 4
+MAX_NL80211_PMKSA_CANDIDATE = NUM_NL80211_PMKSA_CANDIDATE - 1
+NL80211_TDLS_DISCOVERY_REQ = 0
+NL80211_TDLS_SETUP = 1
+NL80211_TDLS_TEARDOWN = 2
+NL80211_TDLS_ENABLE_LINK = 3
+NL80211_TDLS_DISABLE_LINK = 4
+NL80211_FEATURE_SK_TX_STATUS = 1 << 0
+NL80211_FEATURE_HT_IBSS = 1 << 1
+NL80211_FEATURE_INACTIVITY_TIMER = 1 << 2
+NL80211_FEATURE_CELL_BASE_REG_HINTS = 1 << 3
+NL80211_FEATURE_P2P_DEVICE_NEEDS_CHANNEL = 1 << 4
+NL80211_FEATURE_SAE = 1 << 5
+NL80211_FEATURE_LOW_PRIORITY_SCAN = 1 << 6
+NL80211_FEATURE_SCAN_FLUSH = 1 << 7
+NL80211_FEATURE_AP_SCAN = 1 << 8
+NL80211_FEATURE_VIF_TXPOWER = 1 << 9
+NL80211_FEATURE_NEED_OBSS_SCAN = 1 << 10
+NL80211_FEATURE_P2P_GO_CTWIN = 1 << 11
+NL80211_FEATURE_P2P_GO_OPPPS = 1 << 12
+NL80211_FEATURE_ADVERTISE_CHAN_LIMITS = 1 << 14
+NL80211_FEATURE_FULL_AP_CLIENT_STATE = 1 << 15
+NL80211_FEATURE_USERSPACE_MPM = 1 << 16
+NL80211_FEATURE_ACTIVE_MONITOR = 1 << 17
+NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS = 1 << 0
+NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 = 1 << 1
+NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P = 1 << 2
+NL80211_PROBE_RESP_OFFLOAD_SUPPORT_80211U = 1 << 3
+NL80211_CONN_FAIL_MAX_CLIENTS = 0
+NL80211_CONN_FAIL_BLOCKED_CLIENT = 1
+NL80211_SCAN_FLAG_LOW_PRIORITY = 1 << 0
+NL80211_SCAN_FLAG_FLUSH = 1 << 1
+NL80211_SCAN_FLAG_AP = 1 << 2
+NL80211_ACL_POLICY_ACCEPT_UNLESS_LISTED = 0
+NL80211_ACL_POLICY_DENY_UNLESS_LISTED = 1
+NL80211_RADAR_DETECTED = 0
+NL80211_RADAR_CAC_FINISHED = 1
+NL80211_RADAR_CAC_ABORTED = 2
+NL80211_RADAR_NOP_FINISHED = 3
+NL80211_DFS_USABLE = 0
+NL80211_DFS_UNAVAILABLE = 1
+NL80211_DFS_AVAILABLE = 2
+NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP = 1 << 0
+NL80211_CRIT_PROTO_UNSPEC = 0
+NL80211_CRIT_PROTO_DHCP = 1
+NL80211_CRIT_PROTO_EAPOL = 2
+NL80211_CRIT_PROTO_APIPA = 3
+NUM_NL80211_CRIT_PROTO = 4
+nl80211_commands2str = {
+	NL80211_CMD_UNSPEC: "NL80211_CMD_UNSPEC",
+	NL80211_CMD_GET_WIPHY: "NL80211_CMD_GET_WIPHY",
+	NL80211_CMD_SET_WIPHY: "NL80211_CMD_SET_WIPHY",
+	NL80211_CMD_NEW_WIPHY: "NL80211_CMD_NEW_WIPHY",
+	NL80211_CMD_DEL_WIPHY: "NL80211_CMD_DEL_WIPHY",
+	NL80211_CMD_GET_INTERFACE: "NL80211_CMD_GET_INTERFACE",
+	NL80211_CMD_SET_INTERFACE: "NL80211_CMD_SET_INTERFACE",
+	NL80211_CMD_NEW_INTERFACE: "NL80211_CMD_NEW_INTERFACE",
+	NL80211_CMD_DEL_INTERFACE: "NL80211_CMD_DEL_INTERFACE",
+	NL80211_CMD_GET_KEY: "NL80211_CMD_GET_KEY",
+	NL80211_CMD_SET_KEY: "NL80211_CMD_SET_KEY",
+	NL80211_CMD_NEW_KEY: "NL80211_CMD_NEW_KEY",
+	NL80211_CMD_DEL_KEY: "NL80211_CMD_DEL_KEY",
+	NL80211_CMD_GET_BEACON: "NL80211_CMD_GET_BEACON",
+	NL80211_CMD_SET_BEACON: "NL80211_CMD_SET_BEACON",
+	NL80211_CMD_START_AP: "NL80211_CMD_START_AP",
+	NL80211_CMD_STOP_AP: "NL80211_CMD_STOP_AP",
+	NL80211_CMD_GET_STATION: "NL80211_CMD_GET_STATION",
+	NL80211_CMD_SET_STATION: "NL80211_CMD_SET_STATION",
+	NL80211_CMD_NEW_STATION: "NL80211_CMD_NEW_STATION",
+	NL80211_CMD_DEL_STATION: "NL80211_CMD_DEL_STATION",
+	NL80211_CMD_GET_MPATH: "NL80211_CMD_GET_MPATH",
+	NL80211_CMD_SET_MPATH: "NL80211_CMD_SET_MPATH",
+	NL80211_CMD_NEW_MPATH: "NL80211_CMD_NEW_MPATH",
+	NL80211_CMD_DEL_MPATH: "NL80211_CMD_DEL_MPATH",
+	NL80211_CMD_SET_BSS: "NL80211_CMD_SET_BSS",
+	NL80211_CMD_SET_REG: "NL80211_CMD_SET_REG",
+	NL80211_CMD_REQ_SET_REG: "NL80211_CMD_REQ_SET_REG",
+	NL80211_CMD_GET_MESH_CONFIG: "NL80211_CMD_GET_MESH_CONFIG",
+	NL80211_CMD_SET_MESH_CONFIG: "NL80211_CMD_SET_MESH_CONFIG",
+	NL80211_CMD_SET_MGMT_EXTRA_IE: "NL80211_CMD_SET_MGMT_EXTRA_IE",
+	NL80211_CMD_GET_REG: "NL80211_CMD_GET_REG",
+	NL80211_CMD_GET_SCAN: "NL80211_CMD_GET_SCAN",
+	NL80211_CMD_TRIGGER_SCAN: "NL80211_CMD_TRIGGER_SCAN",
+	NL80211_CMD_NEW_SCAN_RESULTS: "NL80211_CMD_NEW_SCAN_RESULTS",
+	NL80211_CMD_SCAN_ABORTED: "NL80211_CMD_SCAN_ABORTED",
+	NL80211_CMD_REG_CHANGE: "NL80211_CMD_REG_CHANGE",
+	NL80211_CMD_AUTHENTICATE: "NL80211_CMD_AUTHENTICATE",
+	NL80211_CMD_ASSOCIATE: "NL80211_CMD_ASSOCIATE",
+	NL80211_CMD_DEAUTHENTICATE: "NL80211_CMD_DEAUTHENTICATE",
+	NL80211_CMD_DISASSOCIATE: "NL80211_CMD_DISASSOCIATE",
+	NL80211_CMD_MICHAEL_MIC_FAILURE: "NL80211_CMD_MICHAEL_MIC_FAILURE",
+	NL80211_CMD_REG_BEACON_HINT: "NL80211_CMD_REG_BEACON_HINT",
+	NL80211_CMD_JOIN_IBSS: "NL80211_CMD_JOIN_IBSS",
+	NL80211_CMD_LEAVE_IBSS: "NL80211_CMD_LEAVE_IBSS",
+	NL80211_CMD_TESTMODE: "NL80211_CMD_TESTMODE",
+	NL80211_CMD_CONNECT: "NL80211_CMD_CONNECT",
+	NL80211_CMD_ROAM: "NL80211_CMD_ROAM",
+	NL80211_CMD_DISCONNECT: "NL80211_CMD_DISCONNECT",
+	NL80211_CMD_SET_WIPHY_NETNS: "NL80211_CMD_SET_WIPHY_NETNS",
+	NL80211_CMD_GET_SURVEY: "NL80211_CMD_GET_SURVEY",
+	NL80211_CMD_NEW_SURVEY_RESULTS: "NL80211_CMD_NEW_SURVEY_RESULTS",
+	NL80211_CMD_SET_PMKSA: "NL80211_CMD_SET_PMKSA",
+	NL80211_CMD_DEL_PMKSA: "NL80211_CMD_DEL_PMKSA",
+	NL80211_CMD_FLUSH_PMKSA: "NL80211_CMD_FLUSH_PMKSA",
+	NL80211_CMD_REMAIN_ON_CHANNEL: "NL80211_CMD_REMAIN_ON_CHANNEL",
+	NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL: "NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL",
+	NL80211_CMD_SET_TX_BITRATE_MASK: "NL80211_CMD_SET_TX_BITRATE_MASK",
+	NL80211_CMD_REGISTER_FRAME: "NL80211_CMD_REGISTER_FRAME",
+	NL80211_CMD_FRAME: "NL80211_CMD_FRAME",
+	NL80211_CMD_FRAME_TX_STATUS: "NL80211_CMD_FRAME_TX_STATUS",
+	NL80211_CMD_SET_POWER_SAVE: "NL80211_CMD_SET_POWER_SAVE",
+	NL80211_CMD_GET_POWER_SAVE: "NL80211_CMD_GET_POWER_SAVE",
+	NL80211_CMD_SET_CQM: "NL80211_CMD_SET_CQM",
+	NL80211_CMD_NOTIFY_CQM: "NL80211_CMD_NOTIFY_CQM",
+	NL80211_CMD_SET_CHANNEL: "NL80211_CMD_SET_CHANNEL",
+	NL80211_CMD_SET_WDS_PEER: "NL80211_CMD_SET_WDS_PEER",
+	NL80211_CMD_FRAME_WAIT_CANCEL: "NL80211_CMD_FRAME_WAIT_CANCEL",
+	NL80211_CMD_JOIN_MESH: "NL80211_CMD_JOIN_MESH",
+	NL80211_CMD_LEAVE_MESH: "NL80211_CMD_LEAVE_MESH",
+	NL80211_CMD_UNPROT_DEAUTHENTICATE: "NL80211_CMD_UNPROT_DEAUTHENTICATE",
+	NL80211_CMD_UNPROT_DISASSOCIATE: "NL80211_CMD_UNPROT_DISASSOCIATE",
+	NL80211_CMD_NEW_PEER_CANDIDATE: "NL80211_CMD_NEW_PEER_CANDIDATE",
+	NL80211_CMD_GET_WOWLAN: "NL80211_CMD_GET_WOWLAN",
+	NL80211_CMD_SET_WOWLAN: "NL80211_CMD_SET_WOWLAN",
+	NL80211_CMD_START_SCHED_SCAN: "NL80211_CMD_START_SCHED_SCAN",
+	NL80211_CMD_STOP_SCHED_SCAN: "NL80211_CMD_STOP_SCHED_SCAN",
+	NL80211_CMD_SCHED_SCAN_RESULTS: "NL80211_CMD_SCHED_SCAN_RESULTS",
+	NL80211_CMD_SCHED_SCAN_STOPPED: "NL80211_CMD_SCHED_SCAN_STOPPED",
+	NL80211_CMD_SET_REKEY_OFFLOAD: "NL80211_CMD_SET_REKEY_OFFLOAD",
+	NL80211_CMD_PMKSA_CANDIDATE: "NL80211_CMD_PMKSA_CANDIDATE",
+	NL80211_CMD_TDLS_OPER: "NL80211_CMD_TDLS_OPER",
+	NL80211_CMD_TDLS_MGMT: "NL80211_CMD_TDLS_MGMT",
+	NL80211_CMD_UNEXPECTED_FRAME: "NL80211_CMD_UNEXPECTED_FRAME",
+	NL80211_CMD_PROBE_CLIENT: "NL80211_CMD_PROBE_CLIENT",
+	NL80211_CMD_REGISTER_BEACONS: "NL80211_CMD_REGISTER_BEACONS",
+	NL80211_CMD_UNEXPECTED_4ADDR_FRAME: "NL80211_CMD_UNEXPECTED_4ADDR_FRAME",
+	NL80211_CMD_SET_NOACK_MAP: "NL80211_CMD_SET_NOACK_MAP",
+	NL80211_CMD_CH_SWITCH_NOTIFY: "NL80211_CMD_CH_SWITCH_NOTIFY",
+	NL80211_CMD_START_P2P_DEVICE: "NL80211_CMD_START_P2P_DEVICE",
+	NL80211_CMD_STOP_P2P_DEVICE: "NL80211_CMD_STOP_P2P_DEVICE",
+	NL80211_CMD_CONN_FAILED: "NL80211_CMD_CONN_FAILED",
+	NL80211_CMD_SET_MCAST_RATE: "NL80211_CMD_SET_MCAST_RATE",
+	NL80211_CMD_SET_MAC_ACL: "NL80211_CMD_SET_MAC_ACL",
+	NL80211_CMD_RADAR_DETECT: "NL80211_CMD_RADAR_DETECT",
+	NL80211_CMD_GET_PROTOCOL_FEATURES: "NL80211_CMD_GET_PROTOCOL_FEATURES",
+	NL80211_CMD_UPDATE_FT_IES: "NL80211_CMD_UPDATE_FT_IES",
+	NL80211_CMD_FT_EVENT: "NL80211_CMD_FT_EVENT",
+	NL80211_CMD_CRIT_PROTOCOL_START: "NL80211_CMD_CRIT_PROTOCOL_START",
+	NL80211_CMD_CRIT_PROTOCOL_STOP: "NL80211_CMD_CRIT_PROTOCOL_STOP",
+	__NL80211_CMD_AFTER_LAST: "__NL80211_CMD_AFTER_LAST",
+}
+nl80211_attrs2str = {
+	NL80211_ATTR_UNSPEC: "NL80211_ATTR_UNSPEC",
+	NL80211_ATTR_WIPHY: "NL80211_ATTR_WIPHY",
+	NL80211_ATTR_WIPHY_NAME: "NL80211_ATTR_WIPHY_NAME",
+	NL80211_ATTR_IFINDEX: "NL80211_ATTR_IFINDEX",
+	NL80211_ATTR_IFNAME: "NL80211_ATTR_IFNAME",
+	NL80211_ATTR_IFTYPE: "NL80211_ATTR_IFTYPE",
+	NL80211_ATTR_MAC: "NL80211_ATTR_MAC",
+	NL80211_ATTR_KEY_DATA: "NL80211_ATTR_KEY_DATA",
+	NL80211_ATTR_KEY_IDX: "NL80211_ATTR_KEY_IDX",
+	NL80211_ATTR_KEY_CIPHER: "NL80211_ATTR_KEY_CIPHER",
+	NL80211_ATTR_KEY_SEQ: "NL80211_ATTR_KEY_SEQ",
+	NL80211_ATTR_KEY_DEFAULT: "NL80211_ATTR_KEY_DEFAULT",
+	NL80211_ATTR_BEACON_INTERVAL: "NL80211_ATTR_BEACON_INTERVAL",
+	NL80211_ATTR_DTIM_PERIOD: "NL80211_ATTR_DTIM_PERIOD",
+	NL80211_ATTR_BEACON_HEAD: "NL80211_ATTR_BEACON_HEAD",
+	NL80211_ATTR_BEACON_TAIL: "NL80211_ATTR_BEACON_TAIL",
+	NL80211_ATTR_STA_AID: "NL80211_ATTR_STA_AID",
+	NL80211_ATTR_STA_FLAGS: "NL80211_ATTR_STA_FLAGS",
+	NL80211_ATTR_STA_LISTEN_INTERVAL: "NL80211_ATTR_STA_LISTEN_INTERVAL",
+	NL80211_ATTR_STA_SUPPORTED_RATES: "NL80211_ATTR_STA_SUPPORTED_RATES",
+	NL80211_ATTR_STA_VLAN: "NL80211_ATTR_STA_VLAN",
+	NL80211_ATTR_STA_INFO: "NL80211_ATTR_STA_INFO",
+	NL80211_ATTR_WIPHY_BANDS: "NL80211_ATTR_WIPHY_BANDS",
+	NL80211_ATTR_MNTR_FLAGS: "NL80211_ATTR_MNTR_FLAGS",
+	NL80211_ATTR_MESH_ID: "NL80211_ATTR_MESH_ID",
+	NL80211_ATTR_STA_PLINK_ACTION: "NL80211_ATTR_STA_PLINK_ACTION",
+	NL80211_ATTR_MPATH_NEXT_HOP: "NL80211_ATTR_MPATH_NEXT_HOP",
+	NL80211_ATTR_MPATH_INFO: "NL80211_ATTR_MPATH_INFO",
+	NL80211_ATTR_BSS_CTS_PROT: "NL80211_ATTR_BSS_CTS_PROT",
+	NL80211_ATTR_BSS_SHORT_PREAMBLE: "NL80211_ATTR_BSS_SHORT_PREAMBLE",
+	NL80211_ATTR_BSS_SHORT_SLOT_TIME: "NL80211_ATTR_BSS_SHORT_SLOT_TIME",
+	NL80211_ATTR_HT_CAPABILITY: "NL80211_ATTR_HT_CAPABILITY",
+	NL80211_ATTR_SUPPORTED_IFTYPES: "NL80211_ATTR_SUPPORTED_IFTYPES",
+	NL80211_ATTR_REG_ALPHA2: "NL80211_ATTR_REG_ALPHA2",
+	NL80211_ATTR_REG_RULES: "NL80211_ATTR_REG_RULES",
+	NL80211_ATTR_MESH_CONFIG: "NL80211_ATTR_MESH_CONFIG",
+	NL80211_ATTR_BSS_BASIC_RATES: "NL80211_ATTR_BSS_BASIC_RATES",
+	NL80211_ATTR_WIPHY_TXQ_PARAMS: "NL80211_ATTR_WIPHY_TXQ_PARAMS",
+	NL80211_ATTR_WIPHY_FREQ: "NL80211_ATTR_WIPHY_FREQ",
+	NL80211_ATTR_WIPHY_CHANNEL_TYPE: "NL80211_ATTR_WIPHY_CHANNEL_TYPE",
+	NL80211_ATTR_KEY_DEFAULT_MGMT: "NL80211_ATTR_KEY_DEFAULT_MGMT",
+	NL80211_ATTR_MGMT_SUBTYPE: "NL80211_ATTR_MGMT_SUBTYPE",
+	NL80211_ATTR_IE: "NL80211_ATTR_IE",
+	NL80211_ATTR_MAX_NUM_SCAN_SSIDS: "NL80211_ATTR_MAX_NUM_SCAN_SSIDS",
+	NL80211_ATTR_SCAN_FREQUENCIES: "NL80211_ATTR_SCAN_FREQUENCIES",
+	NL80211_ATTR_SCAN_SSIDS: "NL80211_ATTR_SCAN_SSIDS",
+	NL80211_ATTR_GENERATION: "NL80211_ATTR_GENERATION",
+	NL80211_ATTR_BSS: "NL80211_ATTR_BSS",
+	NL80211_ATTR_REG_INITIATOR: "NL80211_ATTR_REG_INITIATOR",
+	NL80211_ATTR_REG_TYPE: "NL80211_ATTR_REG_TYPE",
+	NL80211_ATTR_SUPPORTED_COMMANDS: "NL80211_ATTR_SUPPORTED_COMMANDS",
+	NL80211_ATTR_FRAME: "NL80211_ATTR_FRAME",
+	NL80211_ATTR_SSID: "NL80211_ATTR_SSID",
+	NL80211_ATTR_AUTH_TYPE: "NL80211_ATTR_AUTH_TYPE",
+	NL80211_ATTR_REASON_CODE: "NL80211_ATTR_REASON_CODE",
+	NL80211_ATTR_KEY_TYPE: "NL80211_ATTR_KEY_TYPE",
+	NL80211_ATTR_MAX_SCAN_IE_LEN: "NL80211_ATTR_MAX_SCAN_IE_LEN",
+	NL80211_ATTR_CIPHER_SUITES: "NL80211_ATTR_CIPHER_SUITES",
+	NL80211_ATTR_FREQ_BEFORE: "NL80211_ATTR_FREQ_BEFORE",
+	NL80211_ATTR_FREQ_AFTER: "NL80211_ATTR_FREQ_AFTER",
+	NL80211_ATTR_FREQ_FIXED: "NL80211_ATTR_FREQ_FIXED",
+	NL80211_ATTR_WIPHY_RETRY_SHORT: "NL80211_ATTR_WIPHY_RETRY_SHORT",
+	NL80211_ATTR_WIPHY_RETRY_LONG: "NL80211_ATTR_WIPHY_RETRY_LONG",
+	NL80211_ATTR_WIPHY_FRAG_THRESHOLD: "NL80211_ATTR_WIPHY_FRAG_THRESHOLD",
+	NL80211_ATTR_WIPHY_RTS_THRESHOLD: "NL80211_ATTR_WIPHY_RTS_THRESHOLD",
+	NL80211_ATTR_TIMED_OUT: "NL80211_ATTR_TIMED_OUT",
+	NL80211_ATTR_USE_MFP: "NL80211_ATTR_USE_MFP",
+	NL80211_ATTR_STA_FLAGS2: "NL80211_ATTR_STA_FLAGS2",
+	NL80211_ATTR_CONTROL_PORT: "NL80211_ATTR_CONTROL_PORT",
+	NL80211_ATTR_TESTDATA: "NL80211_ATTR_TESTDATA",
+	NL80211_ATTR_PRIVACY: "NL80211_ATTR_PRIVACY",
+	NL80211_ATTR_DISCONNECTED_BY_AP: "NL80211_ATTR_DISCONNECTED_BY_AP",
+	NL80211_ATTR_STATUS_CODE: "NL80211_ATTR_STATUS_CODE",
+	NL80211_ATTR_CIPHER_SUITES_PAIRWISE: "NL80211_ATTR_CIPHER_SUITES_PAIRWISE",
+	NL80211_ATTR_CIPHER_SUITE_GROUP: "NL80211_ATTR_CIPHER_SUITE_GROUP",
+	NL80211_ATTR_WPA_VERSIONS: "NL80211_ATTR_WPA_VERSIONS",
+	NL80211_ATTR_AKM_SUITES: "NL80211_ATTR_AKM_SUITES",
+	NL80211_ATTR_REQ_IE: "NL80211_ATTR_REQ_IE",
+	NL80211_ATTR_RESP_IE: "NL80211_ATTR_RESP_IE",
+	NL80211_ATTR_PREV_BSSID: "NL80211_ATTR_PREV_BSSID",
+	NL80211_ATTR_KEY: "NL80211_ATTR_KEY",
+	NL80211_ATTR_KEYS: "NL80211_ATTR_KEYS",
+	NL80211_ATTR_PID: "NL80211_ATTR_PID",
+	NL80211_ATTR_4ADDR: "NL80211_ATTR_4ADDR",
+	NL80211_ATTR_SURVEY_INFO: "NL80211_ATTR_SURVEY_INFO",
+	NL80211_ATTR_PMKID: "NL80211_ATTR_PMKID",
+	NL80211_ATTR_MAX_NUM_PMKIDS: "NL80211_ATTR_MAX_NUM_PMKIDS",
+	NL80211_ATTR_DURATION: "NL80211_ATTR_DURATION",
+	NL80211_ATTR_COOKIE: "NL80211_ATTR_COOKIE",
+	NL80211_ATTR_WIPHY_COVERAGE_CLASS: "NL80211_ATTR_WIPHY_COVERAGE_CLASS",
+	NL80211_ATTR_TX_RATES: "NL80211_ATTR_TX_RATES",
+	NL80211_ATTR_FRAME_MATCH: "NL80211_ATTR_FRAME_MATCH",
+	NL80211_ATTR_ACK: "NL80211_ATTR_ACK",
+	NL80211_ATTR_PS_STATE: "NL80211_ATTR_PS_STATE",
+	NL80211_ATTR_CQM: "NL80211_ATTR_CQM",
+	NL80211_ATTR_LOCAL_STATE_CHANGE: "NL80211_ATTR_LOCAL_STATE_CHANGE",
+	NL80211_ATTR_AP_ISOLATE: "NL80211_ATTR_AP_ISOLATE",
+	NL80211_ATTR_WIPHY_TX_POWER_SETTING: "NL80211_ATTR_WIPHY_TX_POWER_SETTING",
+	NL80211_ATTR_WIPHY_TX_POWER_LEVEL: "NL80211_ATTR_WIPHY_TX_POWER_LEVEL",
+	NL80211_ATTR_TX_FRAME_TYPES: "NL80211_ATTR_TX_FRAME_TYPES",
+	NL80211_ATTR_RX_FRAME_TYPES: "NL80211_ATTR_RX_FRAME_TYPES",
+	NL80211_ATTR_FRAME_TYPE: "NL80211_ATTR_FRAME_TYPE",
+	NL80211_ATTR_CONTROL_PORT_ETHERTYPE: "NL80211_ATTR_CONTROL_PORT_ETHERTYPE",
+	NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT: "NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT",
+	NL80211_ATTR_SUPPORT_IBSS_RSN: "NL80211_ATTR_SUPPORT_IBSS_RSN",
+	NL80211_ATTR_WIPHY_ANTENNA_TX: "NL80211_ATTR_WIPHY_ANTENNA_TX",
+	NL80211_ATTR_WIPHY_ANTENNA_RX: "NL80211_ATTR_WIPHY_ANTENNA_RX",
+	NL80211_ATTR_MCAST_RATE: "NL80211_ATTR_MCAST_RATE",
+	NL80211_ATTR_OFFCHANNEL_TX_OK: "NL80211_ATTR_OFFCHANNEL_TX_OK",
+	NL80211_ATTR_BSS_HT_OPMODE: "NL80211_ATTR_BSS_HT_OPMODE",
+	NL80211_ATTR_KEY_DEFAULT_TYPES: "NL80211_ATTR_KEY_DEFAULT_TYPES",
+	NL80211_ATTR_MAX_REMAIN_ON_CHANNEL_DURATION: "NL80211_ATTR_MAX_REMAIN_ON_CHANNEL_DURATION",
+	NL80211_ATTR_MESH_SETUP: "NL80211_ATTR_MESH_SETUP",
+	NL80211_ATTR_WIPHY_ANTENNA_AVAIL_TX: "NL80211_ATTR_WIPHY_ANTENNA_AVAIL_TX",
+	NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX: "NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX",
+	NL80211_ATTR_SUPPORT_MESH_AUTH: "NL80211_ATTR_SUPPORT_MESH_AUTH",
+	NL80211_ATTR_STA_PLINK_STATE: "NL80211_ATTR_STA_PLINK_STATE",
+	NL80211_ATTR_WOWLAN_TRIGGERS: "NL80211_ATTR_WOWLAN_TRIGGERS",
+	NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED: "NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED",
+	NL80211_ATTR_SCHED_SCAN_INTERVAL: "NL80211_ATTR_SCHED_SCAN_INTERVAL",
+	NL80211_ATTR_INTERFACE_COMBINATIONS: "NL80211_ATTR_INTERFACE_COMBINATIONS",
+	NL80211_ATTR_SOFTWARE_IFTYPES: "NL80211_ATTR_SOFTWARE_IFTYPES",
+	NL80211_ATTR_REKEY_DATA: "NL80211_ATTR_REKEY_DATA",
+	NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS: "NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS",
+	NL80211_ATTR_MAX_SCHED_SCAN_IE_LEN: "NL80211_ATTR_MAX_SCHED_SCAN_IE_LEN",
+	NL80211_ATTR_SCAN_SUPP_RATES: "NL80211_ATTR_SCAN_SUPP_RATES",
+	NL80211_ATTR_HIDDEN_SSID: "NL80211_ATTR_HIDDEN_SSID",
+	NL80211_ATTR_IE_PROBE_RESP: "NL80211_ATTR_IE_PROBE_RESP",
+	NL80211_ATTR_IE_ASSOC_RESP: "NL80211_ATTR_IE_ASSOC_RESP",
+	NL80211_ATTR_STA_WME: "NL80211_ATTR_STA_WME",
+	NL80211_ATTR_SUPPORT_AP_UAPSD: "NL80211_ATTR_SUPPORT_AP_UAPSD",
+	NL80211_ATTR_ROAM_SUPPORT: "NL80211_ATTR_ROAM_SUPPORT",
+	NL80211_ATTR_SCHED_SCAN_MATCH: "NL80211_ATTR_SCHED_SCAN_MATCH",
+	NL80211_ATTR_MAX_MATCH_SETS: "NL80211_ATTR_MAX_MATCH_SETS",
+	NL80211_ATTR_PMKSA_CANDIDATE: "NL80211_ATTR_PMKSA_CANDIDATE",
+	NL80211_ATTR_TX_NO_CCK_RATE: "NL80211_ATTR_TX_NO_CCK_RATE",
+	NL80211_ATTR_TDLS_ACTION: "NL80211_ATTR_TDLS_ACTION",
+	NL80211_ATTR_TDLS_DIALOG_TOKEN: "NL80211_ATTR_TDLS_DIALOG_TOKEN",
+	NL80211_ATTR_TDLS_OPERATION: "NL80211_ATTR_TDLS_OPERATION",
+	NL80211_ATTR_TDLS_SUPPORT: "NL80211_ATTR_TDLS_SUPPORT",
+	NL80211_ATTR_TDLS_EXTERNAL_SETUP: "NL80211_ATTR_TDLS_EXTERNAL_SETUP",
+	NL80211_ATTR_DEVICE_AP_SME: "NL80211_ATTR_DEVICE_AP_SME",
+	NL80211_ATTR_DONT_WAIT_FOR_ACK: "NL80211_ATTR_DONT_WAIT_FOR_ACK",
+	NL80211_ATTR_FEATURE_FLAGS: "NL80211_ATTR_FEATURE_FLAGS",
+	NL80211_ATTR_PROBE_RESP_OFFLOAD: "NL80211_ATTR_PROBE_RESP_OFFLOAD",
+	NL80211_ATTR_PROBE_RESP: "NL80211_ATTR_PROBE_RESP",
+	NL80211_ATTR_DFS_REGION: "NL80211_ATTR_DFS_REGION",
+	NL80211_ATTR_DISABLE_HT: "NL80211_ATTR_DISABLE_HT",
+	NL80211_ATTR_HT_CAPABILITY_MASK: "NL80211_ATTR_HT_CAPABILITY_MASK",
+	NL80211_ATTR_NOACK_MAP: "NL80211_ATTR_NOACK_MAP",
+	NL80211_ATTR_INACTIVITY_TIMEOUT: "NL80211_ATTR_INACTIVITY_TIMEOUT",
+	NL80211_ATTR_RX_SIGNAL_DBM: "NL80211_ATTR_RX_SIGNAL_DBM",
+	NL80211_ATTR_BG_SCAN_PERIOD: "NL80211_ATTR_BG_SCAN_PERIOD",
+	NL80211_ATTR_WDEV: "NL80211_ATTR_WDEV",
+	NL80211_ATTR_USER_REG_HINT_TYPE: "NL80211_ATTR_USER_REG_HINT_TYPE",
+	NL80211_ATTR_CONN_FAILED_REASON: "NL80211_ATTR_CONN_FAILED_REASON",
+	NL80211_ATTR_SAE_DATA: "NL80211_ATTR_SAE_DATA",
+	NL80211_ATTR_VHT_CAPABILITY: "NL80211_ATTR_VHT_CAPABILITY",
+	NL80211_ATTR_SCAN_FLAGS: "NL80211_ATTR_SCAN_FLAGS",
+	NL80211_ATTR_CHANNEL_WIDTH: "NL80211_ATTR_CHANNEL_WIDTH",
+	NL80211_ATTR_CENTER_FREQ1: "NL80211_ATTR_CENTER_FREQ1",
+	NL80211_ATTR_CENTER_FREQ2: "NL80211_ATTR_CENTER_FREQ2",
+	NL80211_ATTR_P2P_CTWINDOW: "NL80211_ATTR_P2P_CTWINDOW",
+	NL80211_ATTR_P2P_OPPPS: "NL80211_ATTR_P2P_OPPPS",
+	NL80211_ATTR_LOCAL_MESH_POWER_MODE: "NL80211_ATTR_LOCAL_MESH_POWER_MODE",
+	NL80211_ATTR_ACL_POLICY: "NL80211_ATTR_ACL_POLICY",
+	NL80211_ATTR_MAC_ADDRS: "NL80211_ATTR_MAC_ADDRS",
+	NL80211_ATTR_MAC_ACL_MAX: "NL80211_ATTR_MAC_ACL_MAX",
+	NL80211_ATTR_RADAR_EVENT: "NL80211_ATTR_RADAR_EVENT",
+	NL80211_ATTR_EXT_CAPA: "NL80211_ATTR_EXT_CAPA",
+	NL80211_ATTR_EXT_CAPA_MASK: "NL80211_ATTR_EXT_CAPA_MASK",
+	NL80211_ATTR_STA_CAPABILITY: "NL80211_ATTR_STA_CAPABILITY",
+	NL80211_ATTR_STA_EXT_CAPABILITY: "NL80211_ATTR_STA_EXT_CAPABILITY",
+	NL80211_ATTR_PROTOCOL_FEATURES: "NL80211_ATTR_PROTOCOL_FEATURES",
+	NL80211_ATTR_SPLIT_WIPHY_DUMP: "NL80211_ATTR_SPLIT_WIPHY_DUMP",
+	NL80211_ATTR_DISABLE_VHT: "NL80211_ATTR_DISABLE_VHT",
+	NL80211_ATTR_VHT_CAPABILITY_MASK: "NL80211_ATTR_VHT_CAPABILITY_MASK",
+	NL80211_ATTR_MDID: "NL80211_ATTR_MDID",
+	NL80211_ATTR_IE_RIC: "NL80211_ATTR_IE_RIC",
+	NL80211_ATTR_CRIT_PROT_ID: "NL80211_ATTR_CRIT_PROT_ID",
+	NL80211_ATTR_MAX_CRIT_PROT_DURATION: "NL80211_ATTR_MAX_CRIT_PROT_DURATION",
+	NL80211_ATTR_PEER_AID: "NL80211_ATTR_PEER_AID",
+	__NL80211_ATTR_AFTER_LAST: "__NL80211_ATTR_AFTER_LAST",
+}
+nl80211_iftype2str = {
+	NL80211_IFTYPE_UNSPECIFIED: "NL80211_IFTYPE_UNSPECIFIED",
+	NL80211_IFTYPE_ADHOC: "NL80211_IFTYPE_ADHOC",
+	NL80211_IFTYPE_STATION: "NL80211_IFTYPE_STATION",
+	NL80211_IFTYPE_AP: "NL80211_IFTYPE_AP",
+	NL80211_IFTYPE_AP_VLAN: "NL80211_IFTYPE_AP_VLAN",
+	NL80211_IFTYPE_WDS: "NL80211_IFTYPE_WDS",
+	NL80211_IFTYPE_MONITOR: "NL80211_IFTYPE_MONITOR",
+	NL80211_IFTYPE_MESH_POINT: "NL80211_IFTYPE_MESH_POINT",
+	NL80211_IFTYPE_P2P_CLIENT: "NL80211_IFTYPE_P2P_CLIENT",
+	NL80211_IFTYPE_P2P_GO: "NL80211_IFTYPE_P2P_GO",
+	NL80211_IFTYPE_P2P_DEVICE: "NL80211_IFTYPE_P2P_DEVICE",
+	NUM_NL80211_IFTYPES: "NUM_NL80211_IFTYPES",
+}
+nl80211_sta_flags2str = {
+	__NL80211_STA_FLAG_INVALID: "__NL80211_STA_FLAG_INVALID",
+	NL80211_STA_FLAG_AUTHORIZED: "NL80211_STA_FLAG_AUTHORIZED",
+	NL80211_STA_FLAG_SHORT_PREAMBLE: "NL80211_STA_FLAG_SHORT_PREAMBLE",
+	NL80211_STA_FLAG_WME: "NL80211_STA_FLAG_WME",
+	NL80211_STA_FLAG_MFP: "NL80211_STA_FLAG_MFP",
+	NL80211_STA_FLAG_AUTHENTICATED: "NL80211_STA_FLAG_AUTHENTICATED",
+	NL80211_STA_FLAG_TDLS_PEER: "NL80211_STA_FLAG_TDLS_PEER",
+	NL80211_STA_FLAG_ASSOCIATED: "NL80211_STA_FLAG_ASSOCIATED",
+	__NL80211_STA_FLAG_AFTER_LAST: "__NL80211_STA_FLAG_AFTER_LAST",
+}
+nl80211_rate_info2str = {
+	__NL80211_RATE_INFO_INVALID: "__NL80211_RATE_INFO_INVALID",
+	NL80211_RATE_INFO_BITRATE: "NL80211_RATE_INFO_BITRATE",
+	NL80211_RATE_INFO_MCS: "NL80211_RATE_INFO_MCS",
+	NL80211_RATE_INFO_40_MHZ_WIDTH: "NL80211_RATE_INFO_40_MHZ_WIDTH",
+	NL80211_RATE_INFO_SHORT_GI: "NL80211_RATE_INFO_SHORT_GI",
+	NL80211_RATE_INFO_BITRATE32: "NL80211_RATE_INFO_BITRATE32",
+	NL80211_RATE_INFO_VHT_MCS: "NL80211_RATE_INFO_VHT_MCS",
+	NL80211_RATE_INFO_VHT_NSS: "NL80211_RATE_INFO_VHT_NSS",
+	NL80211_RATE_INFO_80_MHZ_WIDTH: "NL80211_RATE_INFO_80_MHZ_WIDTH",
+	NL80211_RATE_INFO_80P80_MHZ_WIDTH: "NL80211_RATE_INFO_80P80_MHZ_WIDTH",
+	NL80211_RATE_INFO_160_MHZ_WIDTH: "NL80211_RATE_INFO_160_MHZ_WIDTH",
+	__NL80211_RATE_INFO_AFTER_LAST: "__NL80211_RATE_INFO_AFTER_LAST",
+}
+nl80211_sta_bss_param2str = {
+	__NL80211_STA_BSS_PARAM_INVALID: "__NL80211_STA_BSS_PARAM_INVALID",
+	NL80211_STA_BSS_PARAM_CTS_PROT: "NL80211_STA_BSS_PARAM_CTS_PROT",
+	NL80211_STA_BSS_PARAM_SHORT_PREAMBLE: "NL80211_STA_BSS_PARAM_SHORT_PREAMBLE",
+	NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME: "NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME",
+	NL80211_STA_BSS_PARAM_DTIM_PERIOD: "NL80211_STA_BSS_PARAM_DTIM_PERIOD",
+	NL80211_STA_BSS_PARAM_BEACON_INTERVAL: "NL80211_STA_BSS_PARAM_BEACON_INTERVAL",
+	__NL80211_STA_BSS_PARAM_AFTER_LAST: "__NL80211_STA_BSS_PARAM_AFTER_LAST",
+}
+nl80211_sta_info2str = {
+	__NL80211_STA_INFO_INVALID: "__NL80211_STA_INFO_INVALID",
+	NL80211_STA_INFO_INACTIVE_TIME: "NL80211_STA_INFO_INACTIVE_TIME",
+	NL80211_STA_INFO_RX_BYTES: "NL80211_STA_INFO_RX_BYTES",
+	NL80211_STA_INFO_TX_BYTES: "NL80211_STA_INFO_TX_BYTES",
+	NL80211_STA_INFO_LLID: "NL80211_STA_INFO_LLID",
+	NL80211_STA_INFO_PLID: "NL80211_STA_INFO_PLID",
+	NL80211_STA_INFO_PLINK_STATE: "NL80211_STA_INFO_PLINK_STATE",
+	NL80211_STA_INFO_SIGNAL: "NL80211_STA_INFO_SIGNAL",
+	NL80211_STA_INFO_TX_BITRATE: "NL80211_STA_INFO_TX_BITRATE",
+	NL80211_STA_INFO_RX_PACKETS: "NL80211_STA_INFO_RX_PACKETS",
+	NL80211_STA_INFO_TX_PACKETS: "NL80211_STA_INFO_TX_PACKETS",
+	NL80211_STA_INFO_TX_RETRIES: "NL80211_STA_INFO_TX_RETRIES",
+	NL80211_STA_INFO_TX_FAILED: "NL80211_STA_INFO_TX_FAILED",
+	NL80211_STA_INFO_SIGNAL_AVG: "NL80211_STA_INFO_SIGNAL_AVG",
+	NL80211_STA_INFO_RX_BITRATE: "NL80211_STA_INFO_RX_BITRATE",
+	NL80211_STA_INFO_BSS_PARAM: "NL80211_STA_INFO_BSS_PARAM",
+	NL80211_STA_INFO_CONNECTED_TIME: "NL80211_STA_INFO_CONNECTED_TIME",
+	NL80211_STA_INFO_STA_FLAGS: "NL80211_STA_INFO_STA_FLAGS",
+	NL80211_STA_INFO_BEACON_LOSS: "NL80211_STA_INFO_BEACON_LOSS",
+	NL80211_STA_INFO_T_OFFSET: "NL80211_STA_INFO_T_OFFSET",
+	NL80211_STA_INFO_LOCAL_PM: "NL80211_STA_INFO_LOCAL_PM",
+	NL80211_STA_INFO_PEER_PM: "NL80211_STA_INFO_PEER_PM",
+	NL80211_STA_INFO_NONPEER_PM: "NL80211_STA_INFO_NONPEER_PM",
+	NL80211_STA_INFO_RX_BYTES64: "NL80211_STA_INFO_RX_BYTES64",
+	NL80211_STA_INFO_TX_BYTES64: "NL80211_STA_INFO_TX_BYTES64",
+	NL80211_STA_INFO_CHAIN_SIGNAL: "NL80211_STA_INFO_CHAIN_SIGNAL",
+	NL80211_STA_INFO_CHAIN_SIGNAL_AVG: "NL80211_STA_INFO_CHAIN_SIGNAL_AVG",
+	__NL80211_STA_INFO_AFTER_LAST: "__NL80211_STA_INFO_AFTER_LAST",
+}
+nl80211_mpath_flags2str = {
+	NL80211_MPATH_FLAG_ACTIVE: "NL80211_MPATH_FLAG_ACTIVE",
+	NL80211_MPATH_FLAG_RESOLVING: "NL80211_MPATH_FLAG_RESOLVING",
+	NL80211_MPATH_FLAG_SN_VALID: "NL80211_MPATH_FLAG_SN_VALID",
+	NL80211_MPATH_FLAG_FIXED: "NL80211_MPATH_FLAG_FIXED",
+	NL80211_MPATH_FLAG_RESOLVED: "NL80211_MPATH_FLAG_RESOLVED",
+}
+nl80211_mpath_info2str = {
+	__NL80211_MPATH_INFO_INVALID: "__NL80211_MPATH_INFO_INVALID",
+	NL80211_MPATH_INFO_FRAME_QLEN: "NL80211_MPATH_INFO_FRAME_QLEN",
+	NL80211_MPATH_INFO_SN: "NL80211_MPATH_INFO_SN",
+	NL80211_MPATH_INFO_METRIC: "NL80211_MPATH_INFO_METRIC",
+	NL80211_MPATH_INFO_EXPTIME: "NL80211_MPATH_INFO_EXPTIME",
+	NL80211_MPATH_INFO_FLAGS: "NL80211_MPATH_INFO_FLAGS",
+	NL80211_MPATH_INFO_DISCOVERY_TIMEOUT: "NL80211_MPATH_INFO_DISCOVERY_TIMEOUT",
+	NL80211_MPATH_INFO_DISCOVERY_RETRIES: "NL80211_MPATH_INFO_DISCOVERY_RETRIES",
+	__NL80211_MPATH_INFO_AFTER_LAST: "__NL80211_MPATH_INFO_AFTER_LAST",
+}
+nl80211_band_attr2str = {
+	__NL80211_BAND_ATTR_INVALID: "__NL80211_BAND_ATTR_INVALID",
+	NL80211_BAND_ATTR_FREQS: "NL80211_BAND_ATTR_FREQS",
+	NL80211_BAND_ATTR_RATES: "NL80211_BAND_ATTR_RATES",
+	NL80211_BAND_ATTR_HT_MCS_SET: "NL80211_BAND_ATTR_HT_MCS_SET",
+	NL80211_BAND_ATTR_HT_CAPA: "NL80211_BAND_ATTR_HT_CAPA",
+	NL80211_BAND_ATTR_HT_AMPDU_FACTOR: "NL80211_BAND_ATTR_HT_AMPDU_FACTOR",
+	NL80211_BAND_ATTR_HT_AMPDU_DENSITY: "NL80211_BAND_ATTR_HT_AMPDU_DENSITY",
+	NL80211_BAND_ATTR_VHT_MCS_SET: "NL80211_BAND_ATTR_VHT_MCS_SET",
+	NL80211_BAND_ATTR_VHT_CAPA: "NL80211_BAND_ATTR_VHT_CAPA",
+	__NL80211_BAND_ATTR_AFTER_LAST: "__NL80211_BAND_ATTR_AFTER_LAST",
+}
+nl80211_frequency_attr2str = {
+	__NL80211_FREQUENCY_ATTR_INVALID: "__NL80211_FREQUENCY_ATTR_INVALID",
+	NL80211_FREQUENCY_ATTR_FREQ: "NL80211_FREQUENCY_ATTR_FREQ",
+	NL80211_FREQUENCY_ATTR_DISABLED: "NL80211_FREQUENCY_ATTR_DISABLED",
+	NL80211_FREQUENCY_ATTR_PASSIVE_SCAN: "NL80211_FREQUENCY_ATTR_PASSIVE_SCAN",
+	NL80211_FREQUENCY_ATTR_NO_IBSS: "NL80211_FREQUENCY_ATTR_NO_IBSS",
+	NL80211_FREQUENCY_ATTR_RADAR: "NL80211_FREQUENCY_ATTR_RADAR",
+	NL80211_FREQUENCY_ATTR_MAX_TX_POWER: "NL80211_FREQUENCY_ATTR_MAX_TX_POWER",
+	NL80211_FREQUENCY_ATTR_DFS_STATE: "NL80211_FREQUENCY_ATTR_DFS_STATE",
+	NL80211_FREQUENCY_ATTR_DFS_TIME: "NL80211_FREQUENCY_ATTR_DFS_TIME",
+	NL80211_FREQUENCY_ATTR_NO_HT40_MINUS: "NL80211_FREQUENCY_ATTR_NO_HT40_MINUS",
+	NL80211_FREQUENCY_ATTR_NO_HT40_PLUS: "NL80211_FREQUENCY_ATTR_NO_HT40_PLUS",
+	NL80211_FREQUENCY_ATTR_NO_80MHZ: "NL80211_FREQUENCY_ATTR_NO_80MHZ",
+	NL80211_FREQUENCY_ATTR_NO_160MHZ: "NL80211_FREQUENCY_ATTR_NO_160MHZ",
+	__NL80211_FREQUENCY_ATTR_AFTER_LAST: "__NL80211_FREQUENCY_ATTR_AFTER_LAST",
+}
+nl80211_bitrate_attr2str = {
+	__NL80211_BITRATE_ATTR_INVALID: "__NL80211_BITRATE_ATTR_INVALID",
+	NL80211_BITRATE_ATTR_RATE: "NL80211_BITRATE_ATTR_RATE",
+	NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE: "NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE",
+	__NL80211_BITRATE_ATTR_AFTER_LAST: "__NL80211_BITRATE_ATTR_AFTER_LAST",
+}
+nl80211_reg_initiator2str = {
+	NL80211_REGDOM_SET_BY_CORE: "NL80211_REGDOM_SET_BY_CORE",
+	NL80211_REGDOM_SET_BY_USER: "NL80211_REGDOM_SET_BY_USER",
+	NL80211_REGDOM_SET_BY_DRIVER: "NL80211_REGDOM_SET_BY_DRIVER",
+	NL80211_REGDOM_SET_BY_COUNTRY_IE: "NL80211_REGDOM_SET_BY_COUNTRY_IE",
+}
+nl80211_reg_type2str = {
+	NL80211_REGDOM_TYPE_COUNTRY: "NL80211_REGDOM_TYPE_COUNTRY",
+	NL80211_REGDOM_TYPE_WORLD: "NL80211_REGDOM_TYPE_WORLD",
+	NL80211_REGDOM_TYPE_CUSTOM_WORLD: "NL80211_REGDOM_TYPE_CUSTOM_WORLD",
+	NL80211_REGDOM_TYPE_INTERSECTION: "NL80211_REGDOM_TYPE_INTERSECTION",
+}
+nl80211_reg_rule_attr2str = {
+	__NL80211_REG_RULE_ATTR_INVALID: "__NL80211_REG_RULE_ATTR_INVALID",
+	NL80211_ATTR_REG_RULE_FLAGS: "NL80211_ATTR_REG_RULE_FLAGS",
+	NL80211_ATTR_FREQ_RANGE_START: "NL80211_ATTR_FREQ_RANGE_START",
+	NL80211_ATTR_FREQ_RANGE_END: "NL80211_ATTR_FREQ_RANGE_END",
+	NL80211_ATTR_FREQ_RANGE_MAX_BW: "NL80211_ATTR_FREQ_RANGE_MAX_BW",
+	NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN: "NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN",
+	NL80211_ATTR_POWER_RULE_MAX_EIRP: "NL80211_ATTR_POWER_RULE_MAX_EIRP",
+	__NL80211_REG_RULE_ATTR_AFTER_LAST: "__NL80211_REG_RULE_ATTR_AFTER_LAST",
+}
+nl80211_sched_scan_match_attr2str = {
+	__NL80211_SCHED_SCAN_MATCH_ATTR_INVALID: "__NL80211_SCHED_SCAN_MATCH_ATTR_INVALID",
+	NL80211_SCHED_SCAN_MATCH_ATTR_SSID: "NL80211_SCHED_SCAN_MATCH_ATTR_SSID",
+	NL80211_SCHED_SCAN_MATCH_ATTR_RSSI: "NL80211_SCHED_SCAN_MATCH_ATTR_RSSI",
+	__NL80211_SCHED_SCAN_MATCH_ATTR_AFTER_LAST: "__NL80211_SCHED_SCAN_MATCH_ATTR_AFTER_LAST",
+}
+nl80211_reg_rule_flags2str = {
+	NL80211_RRF_NO_OFDM: "NL80211_RRF_NO_OFDM",
+	NL80211_RRF_NO_CCK: "NL80211_RRF_NO_CCK",
+	NL80211_RRF_NO_INDOOR: "NL80211_RRF_NO_INDOOR",
+	NL80211_RRF_NO_OUTDOOR: "NL80211_RRF_NO_OUTDOOR",
+	NL80211_RRF_DFS: "NL80211_RRF_DFS",
+	NL80211_RRF_PTP_ONLY: "NL80211_RRF_PTP_ONLY",
+	NL80211_RRF_PTMP_ONLY: "NL80211_RRF_PTMP_ONLY",
+	NL80211_RRF_PASSIVE_SCAN: "NL80211_RRF_PASSIVE_SCAN",
+	NL80211_RRF_NO_IBSS: "NL80211_RRF_NO_IBSS",
+}
+nl80211_dfs_regions2str = {
+}
+nl80211_user_reg_hint_type2str = {
+}
+nl80211_survey_info2str = {
+	__NL80211_SURVEY_INFO_INVALID: "__NL80211_SURVEY_INFO_INVALID",
+	NL80211_SURVEY_INFO_FREQUENCY: "NL80211_SURVEY_INFO_FREQUENCY",
+	NL80211_SURVEY_INFO_NOISE: "NL80211_SURVEY_INFO_NOISE",
+	NL80211_SURVEY_INFO_IN_USE: "NL80211_SURVEY_INFO_IN_USE",
+	NL80211_SURVEY_INFO_CHANNEL_TIME: "NL80211_SURVEY_INFO_CHANNEL_TIME",
+	NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY: "NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY",
+	NL80211_SURVEY_INFO_CHANNEL_TIME_EXT_BUSY: "NL80211_SURVEY_INFO_CHANNEL_TIME_EXT_BUSY",
+	NL80211_SURVEY_INFO_CHANNEL_TIME_RX: "NL80211_SURVEY_INFO_CHANNEL_TIME_RX",
+	NL80211_SURVEY_INFO_CHANNEL_TIME_TX: "NL80211_SURVEY_INFO_CHANNEL_TIME_TX",
+	__NL80211_SURVEY_INFO_AFTER_LAST: "__NL80211_SURVEY_INFO_AFTER_LAST",
+}
+nl80211_mntr_flags2str = {
+	__NL80211_MNTR_FLAG_INVALID: "__NL80211_MNTR_FLAG_INVALID",
+	NL80211_MNTR_FLAG_FCSFAIL: "NL80211_MNTR_FLAG_FCSFAIL",
+	NL80211_MNTR_FLAG_PLCPFAIL: "NL80211_MNTR_FLAG_PLCPFAIL",
+	NL80211_MNTR_FLAG_CONTROL: "NL80211_MNTR_FLAG_CONTROL",
+	NL80211_MNTR_FLAG_OTHER_BSS: "NL80211_MNTR_FLAG_OTHER_BSS",
+	NL80211_MNTR_FLAG_COOK_FRAMES: "NL80211_MNTR_FLAG_COOK_FRAMES",
+	NL80211_MNTR_FLAG_ACTIVE: "NL80211_MNTR_FLAG_ACTIVE",
+	__NL80211_MNTR_FLAG_AFTER_LAST: "__NL80211_MNTR_FLAG_AFTER_LAST",
+}
+nl80211_mesh_power_mode2str = {
+	NL80211_MESH_POWER_UNKNOWN: "NL80211_MESH_POWER_UNKNOWN",
+	NL80211_MESH_POWER_ACTIVE: "NL80211_MESH_POWER_ACTIVE",
+	NL80211_MESH_POWER_LIGHT_SLEEP: "NL80211_MESH_POWER_LIGHT_SLEEP",
+	NL80211_MESH_POWER_DEEP_SLEEP: "NL80211_MESH_POWER_DEEP_SLEEP",
+	__NL80211_MESH_POWER_AFTER_LAST: "__NL80211_MESH_POWER_AFTER_LAST",
+}
+nl80211_meshconf_params2str = {
+	__NL80211_MESHCONF_INVALID: "__NL80211_MESHCONF_INVALID",
+	NL80211_MESHCONF_RETRY_TIMEOUT: "NL80211_MESHCONF_RETRY_TIMEOUT",
+	NL80211_MESHCONF_CONFIRM_TIMEOUT: "NL80211_MESHCONF_CONFIRM_TIMEOUT",
+	NL80211_MESHCONF_HOLDING_TIMEOUT: "NL80211_MESHCONF_HOLDING_TIMEOUT",
+	NL80211_MESHCONF_MAX_PEER_LINKS: "NL80211_MESHCONF_MAX_PEER_LINKS",
+	NL80211_MESHCONF_MAX_RETRIES: "NL80211_MESHCONF_MAX_RETRIES",
+	NL80211_MESHCONF_TTL: "NL80211_MESHCONF_TTL",
+	NL80211_MESHCONF_AUTO_OPEN_PLINKS: "NL80211_MESHCONF_AUTO_OPEN_PLINKS",
+	NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES: "NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES",
+	NL80211_MESHCONF_PATH_REFRESH_TIME: "NL80211_MESHCONF_PATH_REFRESH_TIME",
+	NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT: "NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT",
+	NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT: "NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT",
+	NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL: "NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL",
+	NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME: "NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME",
+	NL80211_MESHCONF_HWMP_ROOTMODE: "NL80211_MESHCONF_HWMP_ROOTMODE",
+	NL80211_MESHCONF_ELEMENT_TTL: "NL80211_MESHCONF_ELEMENT_TTL",
+	NL80211_MESHCONF_HWMP_RANN_INTERVAL: "NL80211_MESHCONF_HWMP_RANN_INTERVAL",
+	NL80211_MESHCONF_GATE_ANNOUNCEMENTS: "NL80211_MESHCONF_GATE_ANNOUNCEMENTS",
+	NL80211_MESHCONF_HWMP_PERR_MIN_INTERVAL: "NL80211_MESHCONF_HWMP_PERR_MIN_INTERVAL",
+	NL80211_MESHCONF_FORWARDING: "NL80211_MESHCONF_FORWARDING",
+	NL80211_MESHCONF_RSSI_THRESHOLD: "NL80211_MESHCONF_RSSI_THRESHOLD",
+	NL80211_MESHCONF_SYNC_OFFSET_MAX_NEIGHBOR: "NL80211_MESHCONF_SYNC_OFFSET_MAX_NEIGHBOR",
+	NL80211_MESHCONF_HT_OPMODE: "NL80211_MESHCONF_HT_OPMODE",
+	NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT: "NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT",
+	NL80211_MESHCONF_HWMP_ROOT_INTERVAL: "NL80211_MESHCONF_HWMP_ROOT_INTERVAL",
+	NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL: "NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL",
+	NL80211_MESHCONF_POWER_MODE: "NL80211_MESHCONF_POWER_MODE",
+	NL80211_MESHCONF_AWAKE_WINDOW: "NL80211_MESHCONF_AWAKE_WINDOW",
+	NL80211_MESHCONF_PLINK_TIMEOUT: "NL80211_MESHCONF_PLINK_TIMEOUT",
+	__NL80211_MESHCONF_ATTR_AFTER_LAST: "__NL80211_MESHCONF_ATTR_AFTER_LAST",
+}
+nl80211_mesh_setup_params2str = {
+	__NL80211_MESH_SETUP_INVALID: "__NL80211_MESH_SETUP_INVALID",
+	NL80211_MESH_SETUP_ENABLE_VENDOR_PATH_SEL: "NL80211_MESH_SETUP_ENABLE_VENDOR_PATH_SEL",
+	NL80211_MESH_SETUP_ENABLE_VENDOR_METRIC: "NL80211_MESH_SETUP_ENABLE_VENDOR_METRIC",
+	NL80211_MESH_SETUP_IE: "NL80211_MESH_SETUP_IE",
+	NL80211_MESH_SETUP_USERSPACE_AUTH: "NL80211_MESH_SETUP_USERSPACE_AUTH",
+	NL80211_MESH_SETUP_USERSPACE_AMPE: "NL80211_MESH_SETUP_USERSPACE_AMPE",
+	NL80211_MESH_SETUP_ENABLE_VENDOR_SYNC: "NL80211_MESH_SETUP_ENABLE_VENDOR_SYNC",
+	NL80211_MESH_SETUP_USERSPACE_MPM: "NL80211_MESH_SETUP_USERSPACE_MPM",
+	NL80211_MESH_SETUP_AUTH_PROTOCOL: "NL80211_MESH_SETUP_AUTH_PROTOCOL",
+	__NL80211_MESH_SETUP_ATTR_AFTER_LAST: "__NL80211_MESH_SETUP_ATTR_AFTER_LAST",
+}
+nl80211_txq_attr2str = {
+	__NL80211_TXQ_ATTR_INVALID: "__NL80211_TXQ_ATTR_INVALID",
+	NL80211_TXQ_ATTR_AC: "NL80211_TXQ_ATTR_AC",
+	NL80211_TXQ_ATTR_TXOP: "NL80211_TXQ_ATTR_TXOP",
+	NL80211_TXQ_ATTR_CWMIN: "NL80211_TXQ_ATTR_CWMIN",
+	NL80211_TXQ_ATTR_CWMAX: "NL80211_TXQ_ATTR_CWMAX",
+	NL80211_TXQ_ATTR_AIFS: "NL80211_TXQ_ATTR_AIFS",
+	__NL80211_TXQ_ATTR_AFTER_LAST: "__NL80211_TXQ_ATTR_AFTER_LAST",
+}
+nl80211_ac2str = {
+	NL80211_AC_VO: "NL80211_AC_VO",
+	NL80211_AC_VI: "NL80211_AC_VI",
+	NL80211_AC_BE: "NL80211_AC_BE",
+	NL80211_AC_BK: "NL80211_AC_BK",
+	NL80211_NUM_ACS: "NL80211_NUM_ACS",
+}
+nl80211_channel_type2str = {
+	NL80211_CHAN_NO_HT: "NL80211_CHAN_NO_HT",
+	NL80211_CHAN_HT20: "NL80211_CHAN_HT20",
+	NL80211_CHAN_HT40MINUS: "NL80211_CHAN_HT40MINUS",
+	NL80211_CHAN_HT40PLUS: "NL80211_CHAN_HT40PLUS",
+}
+nl80211_chan_width2str = {
+	NL80211_CHAN_WIDTH_20_NOHT: "NL80211_CHAN_WIDTH_20_NOHT",
+	NL80211_CHAN_WIDTH_20: "NL80211_CHAN_WIDTH_20",
+	NL80211_CHAN_WIDTH_40: "NL80211_CHAN_WIDTH_40",
+	NL80211_CHAN_WIDTH_80: "NL80211_CHAN_WIDTH_80",
+	NL80211_CHAN_WIDTH_80P80: "NL80211_CHAN_WIDTH_80P80",
+	NL80211_CHAN_WIDTH_160: "NL80211_CHAN_WIDTH_160",
+}
+nl80211_bss2str = {
+	__NL80211_BSS_INVALID: "__NL80211_BSS_INVALID",
+	NL80211_BSS_BSSID: "NL80211_BSS_BSSID",
+	NL80211_BSS_FREQUENCY: "NL80211_BSS_FREQUENCY",
+	NL80211_BSS_TSF: "NL80211_BSS_TSF",
+	NL80211_BSS_BEACON_INTERVAL: "NL80211_BSS_BEACON_INTERVAL",
+	NL80211_BSS_CAPABILITY: "NL80211_BSS_CAPABILITY",
+	NL80211_BSS_INFORMATION_ELEMENTS: "NL80211_BSS_INFORMATION_ELEMENTS",
+	NL80211_BSS_SIGNAL_MBM: "NL80211_BSS_SIGNAL_MBM",
+	NL80211_BSS_SIGNAL_UNSPEC: "NL80211_BSS_SIGNAL_UNSPEC",
+	NL80211_BSS_STATUS: "NL80211_BSS_STATUS",
+	NL80211_BSS_SEEN_MS_AGO: "NL80211_BSS_SEEN_MS_AGO",
+	NL80211_BSS_BEACON_IES: "NL80211_BSS_BEACON_IES",
+	__NL80211_BSS_AFTER_LAST: "__NL80211_BSS_AFTER_LAST",
+}
+nl80211_bss_status2str = {
+	NL80211_BSS_STATUS_AUTHENTICATED: "NL80211_BSS_STATUS_AUTHENTICATED",
+	NL80211_BSS_STATUS_ASSOCIATED: "NL80211_BSS_STATUS_ASSOCIATED",
+	NL80211_BSS_STATUS_IBSS_JOINED: "NL80211_BSS_STATUS_IBSS_JOINED",
+}
+nl80211_auth_type2str = {
+	NL80211_AUTHTYPE_OPEN_SYSTEM: "NL80211_AUTHTYPE_OPEN_SYSTEM",
+	NL80211_AUTHTYPE_SHARED_KEY: "NL80211_AUTHTYPE_SHARED_KEY",
+	NL80211_AUTHTYPE_FT: "NL80211_AUTHTYPE_FT",
+	NL80211_AUTHTYPE_NETWORK_EAP: "NL80211_AUTHTYPE_NETWORK_EAP",
+	NL80211_AUTHTYPE_SAE: "NL80211_AUTHTYPE_SAE",
+	__NL80211_AUTHTYPE_NUM: "__NL80211_AUTHTYPE_NUM",
+	NL80211_AUTHTYPE_AUTOMATIC: "NL80211_AUTHTYPE_AUTOMATIC",
+}
+nl80211_key_type2str = {
+	NL80211_KEYTYPE_GROUP: "NL80211_KEYTYPE_GROUP",
+	NL80211_KEYTYPE_PAIRWISE: "NL80211_KEYTYPE_PAIRWISE",
+	NL80211_KEYTYPE_PEERKEY: "NL80211_KEYTYPE_PEERKEY",
+	NUM_NL80211_KEYTYPES: "NUM_NL80211_KEYTYPES",
+}
+nl80211_mfp2str = {
+	NL80211_MFP_NO: "NL80211_MFP_NO",
+	NL80211_MFP_REQUIRED: "NL80211_MFP_REQUIRED",
+}
+nl80211_wpa_versions2str = {
+	NL80211_WPA_VERSION_1: "NL80211_WPA_VERSION_1",
+	NL80211_WPA_VERSION_2: "NL80211_WPA_VERSION_2",
+}
+nl80211_key_default_types2str = {
+	__NL80211_KEY_DEFAULT_TYPE_INVALID: "__NL80211_KEY_DEFAULT_TYPE_INVALID",
+	NL80211_KEY_DEFAULT_TYPE_UNICAST: "NL80211_KEY_DEFAULT_TYPE_UNICAST",
+	NL80211_KEY_DEFAULT_TYPE_MULTICAST: "NL80211_KEY_DEFAULT_TYPE_MULTICAST",
+	NUM_NL80211_KEY_DEFAULT_TYPES: "NUM_NL80211_KEY_DEFAULT_TYPES",
+}
+nl80211_key_attributes2str = {
+	__NL80211_KEY_INVALID: "__NL80211_KEY_INVALID",
+	NL80211_KEY_DATA: "NL80211_KEY_DATA",
+	NL80211_KEY_IDX: "NL80211_KEY_IDX",
+	NL80211_KEY_CIPHER: "NL80211_KEY_CIPHER",
+	NL80211_KEY_SEQ: "NL80211_KEY_SEQ",
+	NL80211_KEY_DEFAULT: "NL80211_KEY_DEFAULT",
+	NL80211_KEY_DEFAULT_MGMT: "NL80211_KEY_DEFAULT_MGMT",
+	NL80211_KEY_TYPE: "NL80211_KEY_TYPE",
+	NL80211_KEY_DEFAULT_TYPES: "NL80211_KEY_DEFAULT_TYPES",
+	__NL80211_KEY_AFTER_LAST: "__NL80211_KEY_AFTER_LAST",
+}
+nl80211_tx_rate_attributes2str = {
+	__NL80211_TXRATE_INVALID: "__NL80211_TXRATE_INVALID",
+	NL80211_TXRATE_LEGACY: "NL80211_TXRATE_LEGACY",
+	NL80211_TXRATE_MCS: "NL80211_TXRATE_MCS",
+	__NL80211_TXRATE_AFTER_LAST: "__NL80211_TXRATE_AFTER_LAST",
+}
+nl80211_band2str = {
+	NL80211_BAND_2GHZ: "NL80211_BAND_2GHZ",
+	NL80211_BAND_5GHZ: "NL80211_BAND_5GHZ",
+	NL80211_BAND_60GHZ: "NL80211_BAND_60GHZ",
+}
+nl80211_ps_state2str = {
+	NL80211_PS_DISABLED: "NL80211_PS_DISABLED",
+	NL80211_PS_ENABLED: "NL80211_PS_ENABLED",
+}
+nl80211_attr_cqm2str = {
+	__NL80211_ATTR_CQM_INVALID: "__NL80211_ATTR_CQM_INVALID",
+	NL80211_ATTR_CQM_RSSI_THOLD: "NL80211_ATTR_CQM_RSSI_THOLD",
+	NL80211_ATTR_CQM_RSSI_HYST: "NL80211_ATTR_CQM_RSSI_HYST",
+	NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT: "NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT",
+	NL80211_ATTR_CQM_PKT_LOSS_EVENT: "NL80211_ATTR_CQM_PKT_LOSS_EVENT",
+	NL80211_ATTR_CQM_TXE_RATE: "NL80211_ATTR_CQM_TXE_RATE",
+	NL80211_ATTR_CQM_TXE_PKTS: "NL80211_ATTR_CQM_TXE_PKTS",
+	NL80211_ATTR_CQM_TXE_INTVL: "NL80211_ATTR_CQM_TXE_INTVL",
+	__NL80211_ATTR_CQM_AFTER_LAST: "__NL80211_ATTR_CQM_AFTER_LAST",
+}
+nl80211_cqm_rssi_threshold_event2str = {
+	NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW: "NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW",
+	NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH: "NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH",
+	NL80211_CQM_RSSI_BEACON_LOSS_EVENT: "NL80211_CQM_RSSI_BEACON_LOSS_EVENT",
+}
+nl80211_tx_power_setting2str = {
+	NL80211_TX_POWER_AUTOMATIC: "NL80211_TX_POWER_AUTOMATIC",
+	NL80211_TX_POWER_LIMITED: "NL80211_TX_POWER_LIMITED",
+	NL80211_TX_POWER_FIXED: "NL80211_TX_POWER_FIXED",
+}
+nl80211_wowlan_packet_pattern_attr2str = {
+	__NL80211_WOWLAN_PKTPAT_INVALID: "__NL80211_WOWLAN_PKTPAT_INVALID",
+	NL80211_WOWLAN_PKTPAT_MASK: "NL80211_WOWLAN_PKTPAT_MASK",
+	NL80211_WOWLAN_PKTPAT_PATTERN: "NL80211_WOWLAN_PKTPAT_PATTERN",
+	NL80211_WOWLAN_PKTPAT_OFFSET: "NL80211_WOWLAN_PKTPAT_OFFSET",
+	NUM_NL80211_WOWLAN_PKTPAT: "NUM_NL80211_WOWLAN_PKTPAT",
+}
+nl80211_wowlan_triggers2str = {
+	__NL80211_WOWLAN_TRIG_INVALID: "__NL80211_WOWLAN_TRIG_INVALID",
+	NL80211_WOWLAN_TRIG_ANY: "NL80211_WOWLAN_TRIG_ANY",
+	NL80211_WOWLAN_TRIG_DISCONNECT: "NL80211_WOWLAN_TRIG_DISCONNECT",
+	NL80211_WOWLAN_TRIG_MAGIC_PKT: "NL80211_WOWLAN_TRIG_MAGIC_PKT",
+	NL80211_WOWLAN_TRIG_PKT_PATTERN: "NL80211_WOWLAN_TRIG_PKT_PATTERN",
+	NL80211_WOWLAN_TRIG_GTK_REKEY_SUPPORTED: "NL80211_WOWLAN_TRIG_GTK_REKEY_SUPPORTED",
+	NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE: "NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE",
+	NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST: "NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST",
+	NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE: "NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE",
+	NL80211_WOWLAN_TRIG_RFKILL_RELEASE: "NL80211_WOWLAN_TRIG_RFKILL_RELEASE",
+	NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211: "NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211",
+	NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211_LEN: "NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211_LEN",
+	NL80211_WOWLAN_TRIG_WAKEUP_PKT_8023: "NL80211_WOWLAN_TRIG_WAKEUP_PKT_8023",
+	NL80211_WOWLAN_TRIG_WAKEUP_PKT_8023_LEN: "NL80211_WOWLAN_TRIG_WAKEUP_PKT_8023_LEN",
+	NL80211_WOWLAN_TRIG_TCP_CONNECTION: "NL80211_WOWLAN_TRIG_TCP_CONNECTION",
+	NL80211_WOWLAN_TRIG_WAKEUP_TCP_MATCH: "NL80211_WOWLAN_TRIG_WAKEUP_TCP_MATCH",
+	NL80211_WOWLAN_TRIG_WAKEUP_TCP_CONNLOST: "NL80211_WOWLAN_TRIG_WAKEUP_TCP_CONNLOST",
+	NL80211_WOWLAN_TRIG_WAKEUP_TCP_NOMORETOKENS: "NL80211_WOWLAN_TRIG_WAKEUP_TCP_NOMORETOKENS",
+	NUM_NL80211_WOWLAN_TRIG: "NUM_NL80211_WOWLAN_TRIG",
+}
+nl80211_wowlan_tcp_attrs2str = {
+	__NL80211_WOWLAN_TCP_INVALID: "__NL80211_WOWLAN_TCP_INVALID",
+	NL80211_WOWLAN_TCP_SRC_IPV4: "NL80211_WOWLAN_TCP_SRC_IPV4",
+	NL80211_WOWLAN_TCP_DST_IPV4: "NL80211_WOWLAN_TCP_DST_IPV4",
+	NL80211_WOWLAN_TCP_DST_MAC: "NL80211_WOWLAN_TCP_DST_MAC",
+	NL80211_WOWLAN_TCP_SRC_PORT: "NL80211_WOWLAN_TCP_SRC_PORT",
+	NL80211_WOWLAN_TCP_DST_PORT: "NL80211_WOWLAN_TCP_DST_PORT",
+	NL80211_WOWLAN_TCP_DATA_PAYLOAD: "NL80211_WOWLAN_TCP_DATA_PAYLOAD",
+	NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ: "NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ",
+	NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN: "NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN",
+	NL80211_WOWLAN_TCP_DATA_INTERVAL: "NL80211_WOWLAN_TCP_DATA_INTERVAL",
+	NL80211_WOWLAN_TCP_WAKE_PAYLOAD: "NL80211_WOWLAN_TCP_WAKE_PAYLOAD",
+	NL80211_WOWLAN_TCP_WAKE_MASK: "NL80211_WOWLAN_TCP_WAKE_MASK",
+	NUM_NL80211_WOWLAN_TCP: "NUM_NL80211_WOWLAN_TCP",
+}
+nl80211_iface_limit_attrs2str = {
+	NL80211_IFACE_LIMIT_UNSPEC: "NL80211_IFACE_LIMIT_UNSPEC",
+	NL80211_IFACE_LIMIT_MAX: "NL80211_IFACE_LIMIT_MAX",
+	NL80211_IFACE_LIMIT_TYPES: "NL80211_IFACE_LIMIT_TYPES",
+	NUM_NL80211_IFACE_LIMIT: "NUM_NL80211_IFACE_LIMIT",
+}
+nl80211_if_combination_attrs2str = {
+	NL80211_IFACE_COMB_UNSPEC: "NL80211_IFACE_COMB_UNSPEC",
+	NL80211_IFACE_COMB_LIMITS: "NL80211_IFACE_COMB_LIMITS",
+	NL80211_IFACE_COMB_MAXNUM: "NL80211_IFACE_COMB_MAXNUM",
+	NL80211_IFACE_COMB_STA_AP_BI_MATCH: "NL80211_IFACE_COMB_STA_AP_BI_MATCH",
+	NL80211_IFACE_COMB_NUM_CHANNELS: "NL80211_IFACE_COMB_NUM_CHANNELS",
+	NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS: "NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS",
+	NUM_NL80211_IFACE_COMB: "NUM_NL80211_IFACE_COMB",
+}
+nl80211_plink_state2str = {
+	NL80211_PLINK_LISTEN: "NL80211_PLINK_LISTEN",
+	NL80211_PLINK_OPN_SNT: "NL80211_PLINK_OPN_SNT",
+	NL80211_PLINK_OPN_RCVD: "NL80211_PLINK_OPN_RCVD",
+	NL80211_PLINK_CNF_RCVD: "NL80211_PLINK_CNF_RCVD",
+	NL80211_PLINK_ESTAB: "NL80211_PLINK_ESTAB",
+	NL80211_PLINK_HOLDING: "NL80211_PLINK_HOLDING",
+	NL80211_PLINK_BLOCKED: "NL80211_PLINK_BLOCKED",
+	NUM_NL80211_PLINK_STATES: "NUM_NL80211_PLINK_STATES",
+}
+plink_actions2str = {
+	NL80211_PLINK_ACTION_NO_ACTION: "NL80211_PLINK_ACTION_NO_ACTION",
+	NL80211_PLINK_ACTION_OPEN: "NL80211_PLINK_ACTION_OPEN",
+	NL80211_PLINK_ACTION_BLOCK: "NL80211_PLINK_ACTION_BLOCK",
+	NUM_NL80211_PLINK_ACTIONS: "NUM_NL80211_PLINK_ACTIONS",
+}
+nl80211_rekey_data2str = {
+	__NL80211_REKEY_DATA_INVALID: "__NL80211_REKEY_DATA_INVALID",
+	NL80211_REKEY_DATA_KEK: "NL80211_REKEY_DATA_KEK",
+	NL80211_REKEY_DATA_KCK: "NL80211_REKEY_DATA_KCK",
+	NL80211_REKEY_DATA_REPLAY_CTR: "NL80211_REKEY_DATA_REPLAY_CTR",
+	NUM_NL80211_REKEY_DATA: "NUM_NL80211_REKEY_DATA",
+}
+nl80211_hidden_ssid2str = {
+	NL80211_HIDDEN_SSID_NOT_IN_USE: "NL80211_HIDDEN_SSID_NOT_IN_USE",
+	NL80211_HIDDEN_SSID_ZERO_LEN: "NL80211_HIDDEN_SSID_ZERO_LEN",
+	NL80211_HIDDEN_SSID_ZERO_CONTENTS: "NL80211_HIDDEN_SSID_ZERO_CONTENTS",
+}
+nl80211_sta_wme_attr2str = {
+	__NL80211_STA_WME_INVALID: "__NL80211_STA_WME_INVALID",
+	NL80211_STA_WME_UAPSD_QUEUES: "NL80211_STA_WME_UAPSD_QUEUES",
+	NL80211_STA_WME_MAX_SP: "NL80211_STA_WME_MAX_SP",
+	__NL80211_STA_WME_AFTER_LAST: "__NL80211_STA_WME_AFTER_LAST",
+}
+nl80211_pmksa_candidate_attr2str = {
+	__NL80211_PMKSA_CANDIDATE_INVALID: "__NL80211_PMKSA_CANDIDATE_INVALID",
+	NL80211_PMKSA_CANDIDATE_INDEX: "NL80211_PMKSA_CANDIDATE_INDEX",
+	NL80211_PMKSA_CANDIDATE_BSSID: "NL80211_PMKSA_CANDIDATE_BSSID",
+	NL80211_PMKSA_CANDIDATE_PREAUTH: "NL80211_PMKSA_CANDIDATE_PREAUTH",
+	NUM_NL80211_PMKSA_CANDIDATE: "NUM_NL80211_PMKSA_CANDIDATE",
+}
+nl80211_tdls_operation2str = {
+	NL80211_TDLS_DISCOVERY_REQ: "NL80211_TDLS_DISCOVERY_REQ",
+	NL80211_TDLS_SETUP: "NL80211_TDLS_SETUP",
+	NL80211_TDLS_TEARDOWN: "NL80211_TDLS_TEARDOWN",
+	NL80211_TDLS_ENABLE_LINK: "NL80211_TDLS_ENABLE_LINK",
+	NL80211_TDLS_DISABLE_LINK: "NL80211_TDLS_DISABLE_LINK",
+}
+nl80211_feature_flags2str = {
+	NL80211_FEATURE_SK_TX_STATUS: "NL80211_FEATURE_SK_TX_STATUS",
+	NL80211_FEATURE_HT_IBSS: "NL80211_FEATURE_HT_IBSS",
+	NL80211_FEATURE_INACTIVITY_TIMER: "NL80211_FEATURE_INACTIVITY_TIMER",
+	NL80211_FEATURE_CELL_BASE_REG_HINTS: "NL80211_FEATURE_CELL_BASE_REG_HINTS",
+	NL80211_FEATURE_P2P_DEVICE_NEEDS_CHANNEL: "NL80211_FEATURE_P2P_DEVICE_NEEDS_CHANNEL",
+	NL80211_FEATURE_SAE: "NL80211_FEATURE_SAE",
+	NL80211_FEATURE_LOW_PRIORITY_SCAN: "NL80211_FEATURE_LOW_PRIORITY_SCAN",
+	NL80211_FEATURE_SCAN_FLUSH: "NL80211_FEATURE_SCAN_FLUSH",
+	NL80211_FEATURE_AP_SCAN: "NL80211_FEATURE_AP_SCAN",
+	NL80211_FEATURE_VIF_TXPOWER: "NL80211_FEATURE_VIF_TXPOWER",
+	NL80211_FEATURE_NEED_OBSS_SCAN: "NL80211_FEATURE_NEED_OBSS_SCAN",
+	NL80211_FEATURE_P2P_GO_CTWIN: "NL80211_FEATURE_P2P_GO_CTWIN",
+	NL80211_FEATURE_P2P_GO_OPPPS: "NL80211_FEATURE_P2P_GO_OPPPS",
+	NL80211_FEATURE_ADVERTISE_CHAN_LIMITS: "NL80211_FEATURE_ADVERTISE_CHAN_LIMITS",
+	NL80211_FEATURE_FULL_AP_CLIENT_STATE: "NL80211_FEATURE_FULL_AP_CLIENT_STATE",
+	NL80211_FEATURE_USERSPACE_MPM: "NL80211_FEATURE_USERSPACE_MPM",
+	NL80211_FEATURE_ACTIVE_MONITOR: "NL80211_FEATURE_ACTIVE_MONITOR",
+}
+nl80211_probe_resp_offload_support_attr2str = {
+	NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS: "NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS",
+	NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2: "NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2",
+	NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P: "NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P",
+	NL80211_PROBE_RESP_OFFLOAD_SUPPORT_80211U: "NL80211_PROBE_RESP_OFFLOAD_SUPPORT_80211U",
+}
+nl80211_connect_failed_reason2str = {
+	NL80211_CONN_FAIL_MAX_CLIENTS: "NL80211_CONN_FAIL_MAX_CLIENTS",
+	NL80211_CONN_FAIL_BLOCKED_CLIENT: "NL80211_CONN_FAIL_BLOCKED_CLIENT",
+}
+nl80211_scan_flags2str = {
+	NL80211_SCAN_FLAG_LOW_PRIORITY: "NL80211_SCAN_FLAG_LOW_PRIORITY",
+	NL80211_SCAN_FLAG_FLUSH: "NL80211_SCAN_FLAG_FLUSH",
+	NL80211_SCAN_FLAG_AP: "NL80211_SCAN_FLAG_AP",
+}
+nl80211_acl_policy2str = {
+	NL80211_ACL_POLICY_ACCEPT_UNLESS_LISTED: "NL80211_ACL_POLICY_ACCEPT_UNLESS_LISTED",
+	NL80211_ACL_POLICY_DENY_UNLESS_LISTED: "NL80211_ACL_POLICY_DENY_UNLESS_LISTED",
+}
+nl80211_radar_event2str = {
+	NL80211_RADAR_DETECTED: "NL80211_RADAR_DETECTED",
+	NL80211_RADAR_CAC_FINISHED: "NL80211_RADAR_CAC_FINISHED",
+	NL80211_RADAR_CAC_ABORTED: "NL80211_RADAR_CAC_ABORTED",
+	NL80211_RADAR_NOP_FINISHED: "NL80211_RADAR_NOP_FINISHED",
+}
+nl80211_dfs_state2str = {
+	NL80211_DFS_USABLE: "NL80211_DFS_USABLE",
+	NL80211_DFS_UNAVAILABLE: "NL80211_DFS_UNAVAILABLE",
+	NL80211_DFS_AVAILABLE: "NL80211_DFS_AVAILABLE",
+}
+nl80211_protocol_features2str = {
+	NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP: "NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP",
+}
+nl80211_crit_proto_id2str = {
+	NL80211_CRIT_PROTO_UNSPEC: "NL80211_CRIT_PROTO_UNSPEC",
+	NL80211_CRIT_PROTO_DHCP: "NL80211_CRIT_PROTO_DHCP",
+	NL80211_CRIT_PROTO_EAPOL: "NL80211_CRIT_PROTO_EAPOL",
+	NL80211_CRIT_PROTO_APIPA: "NL80211_CRIT_PROTO_APIPA",
+	NUM_NL80211_CRIT_PROTO: "NUM_NL80211_CRIT_PROTO",
+}
diff --git a/python/examples/wiphy.py b/python/examples/wiphy.py
new file mode 100644
index 0000000..73e2d4d
--- /dev/null
+++ b/python/examples/wiphy.py
@@ -0,0 +1,141 @@
+import netlink.capi as nl
+import netlink.genl.capi as genl
+import nl80211
+import sys
+import traceback
+
+class test_class:
+	def __init__(self):
+		self.done = 1;
+
+def freq_to_ch(freq):
+	if freq == 2484:
+		return 14;
+
+	if freq < 2484:
+		return (freq - 2407) / 5;
+
+	# FIXME: dot11ChannelStartingFactor (802.11-2007 17.3.8.3.2)
+	if freq < 45000:
+		return freq/5 - 1000;
+
+	if freq >= 58320 and freq <= 64800:
+		return (freq - 56160) / 2160;
+
+	return 0;
+
+def handle_freq(attr, pol):
+	e, fattr = nl.py_nla_parse_nested(nl80211.NL80211_FREQUENCY_ATTR_MAX, attr, pol)
+	if nl80211.NL80211_FREQUENCY_ATTR_FREQ in fattr:
+		freq = nl.nla_get_u32(fattr[nl80211.NL80211_FREQUENCY_ATTR_FREQ])
+		sys.stdout.write("\t\tfreq %d MHz [%d]" % (freq, freq_to_ch(freq)))
+	if nl80211.NL80211_FREQUENCY_ATTR_MAX_TX_POWER in fattr and not (nl80211.NL80211_FREQUENCY_ATTR_DISABLED in fattr):
+		sys.stdout.write(" (%.1f dBm)" % (0.01 * nl.nla_get_u32(fattr[nl80211.NL80211_FREQUENCY_ATTR_MAX_TX_POWER])))
+	if nl80211.NL80211_FREQUENCY_ATTR_DISABLED in fattr:
+		sys.stdout.write(" (disabled)")
+	sys.stdout.write("\n")
+
+def handle_band(attr, fpol):
+	e, battr = nl.py_nla_parse_nested(nl80211.NL80211_BAND_ATTR_MAX, attr, None)
+	print("\tband %d:" % nl.nla_type(attr))
+	if nl80211.NL80211_BAND_ATTR_FREQS in battr:
+		for fattr in nl.nla_get_nested(battr[nl80211.NL80211_BAND_ATTR_FREQS]):
+			handle_freq(fattr, fpol)
+
+def cipher_name(suite):
+	suite_val = '%02x%02x%02x%02x' % tuple(reversed(suite))
+	if suite_val == '000fac01':
+		return "WEP40 (00-0f-ac:1)"
+	elif suite_val == '000fac05':
+		return "WEP104 (00-0f-ac:5)"
+	elif suite_val == '000fac02':
+		return "TKIP (00-0f-ac:2)"
+	elif suite_val == '000fac04':
+		return "CCMP (00-0f-ac:4)"
+	elif suite_val == '000fac06':
+		return "CMAC (00-0f-ac:6)"
+	elif suite_val == '000fac08':
+		return "GCMP (00-0f-ac:8)"
+	elif suite_val == '00147201':
+		return "WPI-SMS4 (00-14-72:1)"
+	else:
+		return suite_val
+
+def msg_handler(m, a):
+	try:
+		e, attr = genl.py_genlmsg_parse(nl.nlmsg_hdr(m), 0,
+						nl80211.NL80211_ATTR_MAX, None)
+		if nl80211.NL80211_ATTR_WIPHY_NAME in attr:
+			print('wiphy %s' % nl.nla_get_string(attr[nl80211.NL80211_ATTR_WIPHY_NAME]))
+		if nl80211.NL80211_ATTR_WIPHY_BANDS in attr:
+			fpol = nl.nla_policy_array(nl80211.NL80211_FREQUENCY_ATTR_MAX + 1)
+			fpol[nl80211.NL80211_FREQUENCY_ATTR_FREQ].type = nl.NLA_U32
+			fpol[nl80211.NL80211_FREQUENCY_ATTR_DISABLED].type = nl.NLA_FLAG
+			fpol[nl80211.NL80211_FREQUENCY_ATTR_PASSIVE_SCAN].type = nl.NLA_FLAG
+			fpol[nl80211.NL80211_FREQUENCY_ATTR_NO_IBSS].type = nl.NLA_FLAG
+			fpol[nl80211.NL80211_FREQUENCY_ATTR_RADAR].type = nl.NLA_FLAG
+			fpol[nl80211.NL80211_FREQUENCY_ATTR_MAX_TX_POWER].type = nl.NLA_U32
+
+			nattrs = nl.nla_get_nested(attr[nl80211.NL80211_ATTR_WIPHY_BANDS])
+			for nattr in nattrs:
+				handle_band(nattr, fpol)
+		if nl80211.NL80211_ATTR_CIPHER_SUITES in attr:
+			ciphers = nl.nla_data(attr[nl80211.NL80211_ATTR_CIPHER_SUITES])
+			num = len(ciphers) / 4
+			if num > 0:
+				print("\tSupported Ciphers:");
+				for i in range(0, num, 4):
+					print("\t\t* %s" % cipher_name(ciphers[i:i+4]))
+		if nl80211.NL80211_ATTR_SUPPORTED_IFTYPES in attr:
+			print("\tSupported interface modes:")
+			ifattr = nl.nla_get_nested(attr[nl80211.NL80211_ATTR_SUPPORTED_IFTYPES])
+			for nl_mode in ifattr:
+				print("\t\t* %s" % nl80211.nl80211_iftype2str[nl.nla_type(nl_mode)])
+		if nl80211.NL80211_ATTR_SOFTWARE_IFTYPES in attr:
+			print("\tsoftware interface modes (can always be added):")
+			ifattr = nl.nla_get_nested(attr[nl80211.NL80211_ATTR_SOFTWARE_IFTYPES])
+			for nl_mode in ifattr:
+				print("\t\t* %s" % nl80211.nl80211_iftype2str[nl.nla_type(nl_mode)])
+		return nl.NL_SKIP
+	except Exception as e:
+		(t,v,tb) = sys.exc_info()
+		print v.message
+		traceback.print_tb(tb)
+
+def error_handler(err, a):
+	a.done = err.error
+	return nl.NL_STOP
+
+def finish_handler(m, a):
+	return nl.NL_SKIP
+
+def ack_handler(m, a):
+	a.done = 0
+	return nl.NL_STOP
+
+try:
+	cbd = test_class()
+	tx_cb = nl.nl_cb_alloc(nl.NL_CB_DEFAULT)
+	rx_cb = nl.nl_cb_clone(tx_cb)
+	s = nl.nl_socket_alloc_cb(tx_cb)
+	nl.py_nl_cb_err(rx_cb, nl.NL_CB_CUSTOM, error_handler, cbd);
+	nl.py_nl_cb_set(rx_cb, nl.NL_CB_FINISH, nl.NL_CB_CUSTOM, finish_handler, cbd);
+	nl.py_nl_cb_set(rx_cb, nl.NL_CB_ACK, nl.NL_CB_CUSTOM, ack_handler, cbd);
+	nl.py_nl_cb_set(rx_cb, nl.NL_CB_VALID, nl.NL_CB_CUSTOM, msg_handler, cbd);
+
+	genl.genl_connect(s)
+	family = genl.genl_ctrl_resolve(s, 'nl80211')
+	m = nl.nlmsg_alloc()
+	genl.genlmsg_put(m, 0, 0, family, 0, 0, nl80211.NL80211_CMD_GET_WIPHY, 0)
+	nl.nla_put_u32(m, nl80211.NL80211_ATTR_WIPHY, 7)
+
+	err = nl.nl_send_auto_complete(s, m);
+	if err < 0:
+		nl.nlmsg_free(msg)
+
+	while cbd.done > 0 and not err < 0:
+		err = nl.nl_recvmsgs(s, rx_cb)
+except Exception as e:
+	(t, v, tb) = sys.exc_info()
+	print v.message
+	traceback.print_tb(tb)
diff --git a/python/netlink/Makefile.am b/python/netlink/Makefile.am
new file mode 100644
index 0000000..1f6eaf8
--- /dev/null
+++ b/python/netlink/Makefile.am
@@ -0,0 +1,11 @@
+# -*- Makefile -*-
+
+SUBDIRS = route genl
+
+EXTRA_DIST = \
+	capi.i \
+	fixes.h \
+	__init__.py \
+	core.py \
+	util.py \
+	utils.h
diff --git a/python/netlink/__init__.py b/python/netlink/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/netlink/__init__.py
diff --git a/python/netlink/capi.i b/python/netlink/capi.i
new file mode 100644
index 0000000..e5d8a53
--- /dev/null
+++ b/python/netlink/capi.i
@@ -0,0 +1,964 @@
+%module capi
+%{
+#include <netlink/netlink.h>
+#include <netlink/types.h>
+#include <netlink/socket.h>
+#include <netlink/msg.h>
+#include <netlink/object.h>
+#include <netlink/cache.h>
+#include <netlink/attr.h>
+#include <net/if.h>
+
+#define DEBUG
+#include "utils.h"
+%}
+
+%include <stdint.i>
+%include <cstring.i>
+%include <cpointer.i>
+
+%inline %{
+        struct nl_dump_params *alloc_dump_params(void)
+        {
+                struct nl_dump_params *dp;
+                if (!(dp = calloc(1, sizeof(*dp))))
+                        return NULL;
+                dp->dp_fd = stdout;
+                return dp;
+        }
+
+        void free_dump_params(struct nl_dump_params *dp)
+        {
+                free(dp);
+        }
+%};
+
+/* <netlink/types.h> */
+
+enum nl_dump_type {
+	NL_DUMP_LINE,		/**< Dump object briefly on one line */
+	NL_DUMP_DETAILS,	/**< Dump all attributes but no statistics */
+	NL_DUMP_STATS,		/**< Dump all attributes including statistics */
+	__NL_DUMP_MAX,
+};
+
+struct nl_dump_params
+{
+	/**
+	 * Specifies the type of dump that is requested.
+	 */
+	enum nl_dump_type	dp_type;
+
+	/**
+	 * Specifies the number of whitespaces to be put in front
+	 * of every new line (indentation).
+	 */
+	int			dp_prefix;
+
+	/**
+	 * Causes the cache index to be printed for each element.
+	 */
+	int			dp_print_index;
+
+	/**
+	 * Causes each element to be prefixed with the message type.
+	 */
+	int			dp_dump_msgtype;
+
+	/**
+	 * A callback invoked for output
+	 *
+	 * Passed arguments are:
+	 *  - dumping parameters
+	 *  - string to append to the output
+	 */
+	void			(*dp_cb)(struct nl_dump_params *, char *);
+
+	/**
+	 * A callback invoked for every new line, can be used to
+	 * customize the indentation.
+	 *
+	 * Passed arguments are:
+	 *  - dumping parameters
+	 *  - line number starting from 0
+	 */
+	void			(*dp_nl_cb)(struct nl_dump_params *, int);
+
+	/**
+	 * User data pointer, can be used to pass data to callbacks.
+	 */
+	void			*dp_data;
+
+	/**
+	 * File descriptor the dumping output should go to
+	 */
+	FILE *			dp_fd;
+
+	/**
+	 * Alternatively the output may be redirected into a buffer
+	 */
+	char *			dp_buf;
+
+	/**
+	 * Length of the buffer dp_buf
+	 */
+	size_t			dp_buflen;
+
+	/**
+	 * PRIVATE
+	 * Set if a dump was performed prior to the actual dump handler.
+	 */
+	int			dp_pre_dump;
+
+	/**
+	 * PRIVATE
+	 * Owned by the current caller
+	 */
+	int			dp_ivar;
+
+	unsigned int		dp_line;
+};
+
+/* <net/if.h> */
+extern unsigned int if_nametoindex(const char *ifname);
+
+/* <netlink/errno.h> */
+extern const char *nl_geterror(int);
+
+/* <netlink/utils.h> */
+
+extern double nl_cancel_down_bytes(unsigned long long, char **);
+extern double nl_cancel_down_bits(unsigned long long, char **);
+%cstring_output_maxsize(char *buf, size_t len)
+extern int nl_rate2str(unsigned long long rate, int type, char *buf, size_t len);
+extern double nl_cancel_down_us(uint32_t, char **);
+
+extern long nl_size2int(const char *);
+%cstring_output_maxsize(char *buf, const size_t len)
+extern char *nl_size2str(const size_t, char *buf, const size_t len);
+extern long nl_prob2int(const char *);
+
+extern int nl_get_user_hz(void);
+extern uint32_t nl_us2ticks(uint32_t);
+extern uint32_t nl_ticks2us(uint32_t);
+extern int nl_str2msec(const char *, uint64_t *);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *nl_msec2str(uint64_t, char *buf, size_t len);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *nl_llproto2str(int, char *buf, size_t len);
+extern int nl_str2llproto(const char *);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *nl_ether_proto2str(int, char *buf, size_t len);
+extern int nl_str2ether_proto(const char *);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *nl_ip_proto2str(int, char *buf, size_t len);
+extern int nl_str2ip_proto(const char *);
+
+extern void nl_new_line(struct nl_dump_params *);
+extern void nl_dump(struct nl_dump_params *, const char *, ...);
+extern void nl_dump_line(struct nl_dump_params *, const char *, ...);
+
+/* <netlink/netlink.h> */
+extern struct nl_dump_params *alloc_dump_params(void);
+extern void free_dump_params(struct nl_dump_params *);
+
+extern int nl_connect(struct nl_sock *, int);
+extern void nl_close(struct nl_sock *);
+
+/* <netlink/socket.h> */
+extern struct nl_sock *nl_socket_alloc(void);
+extern struct nl_sock *nl_socket_alloc_cb(struct nl_cb *);
+extern void nl_socket_free(struct nl_sock *);
+
+extern uint32_t nl_socket_get_local_port(const struct nl_sock *);
+extern void nl_socket_set_local_port(struct nl_sock *, uint32_t);
+
+extern uint32_t nl_socket_get_peer_port(const struct nl_sock *);
+extern void nl_socket_set_peer_port(struct nl_sock *, uint32_t);
+
+extern uint32_t nl_socket_get_peer_groups(const struct nl_sock *sk);
+extern void  nl_socket_set_peer_groups(struct nl_sock *sk, uint32_t groups);
+
+extern int nl_socket_set_buffer_size(struct nl_sock *, int, int);
+extern void nl_socket_set_cb(struct nl_sock *, struct nl_cb *);
+
+extern int nl_send_auto_complete(struct nl_sock *, struct nl_msg *);
+extern int nl_recvmsgs(struct nl_sock *, struct nl_cb *);
+
+/* <netlink/msg.h> */
+extern int			nlmsg_size(int);
+extern int			nlmsg_total_size(int);
+extern int			nlmsg_padlen(int);
+
+extern void *			nlmsg_data(const struct nlmsghdr *);
+extern int			nlmsg_datalen(const struct nlmsghdr *);
+extern void *			nlmsg_tail(const struct nlmsghdr *);
+
+/* attribute access */
+extern struct nlattr *	  nlmsg_attrdata(const struct nlmsghdr *, int);
+extern int		  nlmsg_attrlen(const struct nlmsghdr *, int);
+
+/* message parsing */
+extern int		  nlmsg_valid_hdr(const struct nlmsghdr *, int);
+extern int		  nlmsg_ok(const struct nlmsghdr *, int);
+extern struct nlmsghdr *  nlmsg_next(struct nlmsghdr *, int *);
+extern int		  nlmsg_parse(struct nlmsghdr *, int, struct nlattr **,
+				      int, struct nla_policy *);
+extern struct nlattr *	  nlmsg_find_attr(struct nlmsghdr *, int, int);
+extern int		  nlmsg_validate(struct nlmsghdr *, int, int,
+					 struct nla_policy *);
+
+extern struct nl_msg *	  nlmsg_alloc(void);
+extern struct nl_msg *	  nlmsg_alloc_size(size_t);
+extern struct nl_msg *	  nlmsg_alloc_simple(int, int);
+extern void		  nlmsg_set_default_size(size_t);
+extern struct nl_msg *	  nlmsg_inherit(struct nlmsghdr *);
+extern struct nl_msg *	  nlmsg_convert(struct nlmsghdr *);
+extern void *		  nlmsg_reserve(struct nl_msg *, size_t, int);
+extern int		  nlmsg_append(struct nl_msg *, void *, size_t, int);
+extern int		  nlmsg_expand(struct nl_msg *, size_t);
+
+extern struct nlmsghdr *  nlmsg_put(struct nl_msg *, uint32_t, uint32_t,
+				    int, int, int);
+extern struct nlmsghdr *  nlmsg_hdr(struct nl_msg *);
+extern void		  nlmsg_get(struct nl_msg *);
+extern void		  nlmsg_free(struct nl_msg *);
+
+/* attribute modification */
+extern void		  nlmsg_set_proto(struct nl_msg *, int);
+extern int		  nlmsg_get_proto(struct nl_msg *);
+extern size_t		  nlmsg_get_max_size(struct nl_msg *);
+extern void		  nlmsg_set_src(struct nl_msg *, struct sockaddr_nl *);
+extern struct sockaddr_nl *nlmsg_get_src(struct nl_msg *);
+extern void		  nlmsg_set_dst(struct nl_msg *, struct sockaddr_nl *);
+extern struct sockaddr_nl *nlmsg_get_dst(struct nl_msg *);
+extern void		  nlmsg_set_creds(struct nl_msg *, struct ucred *);
+extern struct ucred *	  nlmsg_get_creds(struct nl_msg *);
+
+extern char *		  nl_nlmsgtype2str(int, char *, size_t);
+extern int		  nl_str2nlmsgtype(const char *);
+
+extern char *		  nl_nlmsg_flags2str(int, char *, size_t);
+
+extern int		  nl_msg_parse(struct nl_msg *,
+				       void (*cb)(struct nl_object *, void *),
+				       void *);
+
+extern void		nl_msg_dump(struct nl_msg *, FILE *);
+
+%inline %{
+	struct nl_object *cast_obj(void *obj)
+        {
+                return (struct nl_object *) obj;
+        }
+
+        struct nl_object *object_alloc_name(const char *name)
+        {
+                struct nl_object *obj;
+
+                if (nl_object_alloc_name(name, &obj) < 0)
+                        return NULL;
+
+                return obj;
+        }
+%};
+
+extern struct nl_object *nl_object_alloc(struct nl_object_ops *);
+extern void nl_object_free(struct nl_object *);
+extern struct nl_object *nl_object_clone(struct nl_object *);
+extern void nl_object_get(struct nl_object *);
+extern void nl_object_put(struct nl_object *);
+extern int nl_object_shared(struct nl_object *);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern void nl_object_dump_buf(struct nl_object *, char *buf, size_t len);
+
+extern void nl_object_dump(struct nl_object *, struct nl_dump_params *);
+
+extern int nl_object_identical(struct nl_object *, struct nl_object *);
+extern uint32_t nl_object_diff(struct nl_object *, struct nl_object *);
+extern int nl_object_match_filter(struct nl_object *, struct nl_object *);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *nl_object_attrs2str(struct nl_object *, uint32_t, char *buf, size_t len);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *nl_object_attr_list(struct nl_object *, char *buf, size_t len);
+
+extern void nl_object_mark(struct nl_object *);
+extern void nl_object_unmark(struct nl_object *);
+extern int nl_object_is_marked(struct nl_object *);
+
+extern int nl_object_get_refcnt(struct nl_object *);
+
+/* <netlink/cache.h> */
+
+typedef void (*change_func_t)(struct nl_cache *, struct nl_object *, int, void *);
+
+%inline %{
+        struct nl_cache *alloc_cache_name(const char *name)
+        {
+                struct nl_cache *c;
+                if (nl_cache_alloc_name(name, &c) < 0)
+                        return NULL;
+                return c;
+        }
+
+        struct nl_cache_mngr *alloc_cache_mngr(struct nl_sock *sock,
+                                               int protocol, int flags)
+        {
+                struct nl_cache_mngr *mngr;
+
+                if (nl_cache_mngr_alloc(sock, protocol, flags, &mngr) < 0)
+                        return NULL;
+
+                return mngr;
+        }
+
+        struct nl_cache *cache_mngr_add(struct nl_cache_mngr *mngr,
+                                        const char *name, change_func_t func,
+                                        void *arg)
+        {
+                struct nl_cache *cache;
+
+                if (nl_cache_mngr_add(mngr, name, func, arg, &cache) < 0)
+                        return NULL;
+
+                return cache;
+        }
+%}
+
+/* Access Functions */
+extern int			nl_cache_nitems(struct nl_cache *);
+extern int			nl_cache_nitems_filter(struct nl_cache *,
+						       struct nl_object *);
+extern struct nl_cache_ops *	nl_cache_get_ops(struct nl_cache *);
+extern struct nl_object *	nl_cache_get_first(struct nl_cache *);
+extern struct nl_object *	nl_cache_get_last(struct nl_cache *);
+extern struct nl_object *	nl_cache_get_next(struct nl_object *);
+extern struct nl_object *	nl_cache_get_prev(struct nl_object *);
+
+extern struct nl_cache *	nl_cache_alloc(struct nl_cache_ops *);
+extern struct nl_cache *	nl_cache_subset(struct nl_cache *,
+						struct nl_object *);
+extern void			nl_cache_clear(struct nl_cache *);
+extern void			nl_cache_free(struct nl_cache *);
+
+/* Cache modification */
+extern int			nl_cache_add(struct nl_cache *,
+					     struct nl_object *);
+extern int			nl_cache_parse_and_add(struct nl_cache *,
+						       struct nl_msg *);
+extern void			nl_cache_remove(struct nl_object *);
+extern int			nl_cache_refill(struct nl_sock *,
+						struct nl_cache *);
+extern int			nl_cache_pickup(struct nl_sock *,
+						struct nl_cache *);
+extern int			nl_cache_resync(struct nl_sock *,
+						struct nl_cache *,
+						change_func_t,
+						void *);
+extern int			nl_cache_include(struct nl_cache *,
+						 struct nl_object *,
+						 change_func_t,
+						 void *);
+extern void			nl_cache_set_arg1(struct nl_cache *, int);
+extern void			nl_cache_set_arg2(struct nl_cache *, int);
+
+/* General */
+extern int			nl_cache_is_empty(struct nl_cache *);
+extern struct nl_object *	nl_cache_search(struct nl_cache *,
+						struct nl_object *);
+extern void			nl_cache_mark_all(struct nl_cache *);
+
+/* Dumping */
+extern void			nl_cache_dump(struct nl_cache *,
+					      struct nl_dump_params *);
+extern void			nl_cache_dump_filter(struct nl_cache *,
+						     struct nl_dump_params *,
+						     struct nl_object *);
+
+/* Iterators */
+extern void			nl_cache_foreach(struct nl_cache *,
+						 void (*cb)(struct nl_object *,
+							    void *),
+						 void *arg);
+extern void			nl_cache_foreach_filter(struct nl_cache *,
+							struct nl_object *,
+							void (*cb)(struct
+								   nl_object *,
+								   void *),
+							void *arg);
+
+/* --- cache management --- */
+
+/* Cache type management */
+extern struct nl_cache_ops *	nl_cache_ops_lookup(const char *);
+extern struct nl_cache_ops *	nl_cache_ops_associate(int, int);
+extern struct nl_msgtype *	nl_msgtype_lookup(struct nl_cache_ops *, int);
+extern void			nl_cache_ops_foreach(void (*cb)(struct nl_cache_ops *, void *), void *);
+extern int			nl_cache_mngt_register(struct nl_cache_ops *);
+extern int			nl_cache_mngt_unregister(struct nl_cache_ops *);
+
+/* Global cache provisioning/requiring */
+extern void			nl_cache_mngt_provide(struct nl_cache *);
+extern void			nl_cache_mngt_unprovide(struct nl_cache *);
+extern struct nl_cache *	nl_cache_mngt_require(const char *);
+
+struct nl_cache_mngr;
+
+#define NL_AUTO_PROVIDE		1
+
+extern int			nl_cache_mngr_get_fd(struct nl_cache_mngr *);
+extern int			nl_cache_mngr_poll(struct nl_cache_mngr *,
+						   int);
+extern int			nl_cache_mngr_data_ready(struct nl_cache_mngr *);
+extern void			nl_cache_mngr_free(struct nl_cache_mngr *);
+
+/* <netlink/addr.h> */
+%inline %{
+        struct nl_addr *addr_parse(const char *addr, int guess)
+        {
+                struct nl_addr *result;
+
+                if (nl_addr_parse(addr, guess, &result) < 0)
+                        return NULL;
+
+                return result;
+        }
+%};
+
+extern struct nl_addr *nl_addr_alloc(size_t);
+extern struct nl_addr *nl_addr_alloc_attr(struct nlattr *, int);
+extern struct nl_addr *nl_addr_build(int, void *, size_t);
+extern struct nl_addr *nl_addr_clone(struct nl_addr *);
+
+extern struct nl_addr *nl_addr_get(struct nl_addr *);
+extern void nl_addr_put(struct nl_addr *);
+extern int nl_addr_shared(struct nl_addr *);
+
+extern int nl_addr_cmp(struct nl_addr *, struct nl_addr *);
+extern int nl_addr_cmp_prefix(struct nl_addr *, struct nl_addr *);
+extern int nl_addr_iszero(struct nl_addr *);
+extern int nl_addr_valid(char *, int);
+extern int nl_addr_guess_family(struct nl_addr *);
+extern int nl_addr_fill_sockaddr(struct nl_addr *, struct sockaddr *, socklen_t *);
+extern int nl_addr_info(struct nl_addr *, struct addrinfo **);
+extern int nl_addr_resolve(struct nl_addr *addr, char *host, size_t hostlen);
+
+extern void nl_addr_set_family(struct nl_addr *, int);
+extern int nl_addr_get_family(struct nl_addr *);
+extern int nl_addr_set_binary_addr(struct nl_addr *, void *, size_t);
+
+extern void *nl_addr_get_binary_addr(struct nl_addr *);
+extern unsigned int nl_addr_get_len(struct nl_addr *);
+extern void nl_addr_set_prefixlen(struct nl_addr *, int);
+extern unsigned int nl_addr_get_prefixlen(struct nl_addr *);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *nl_af2str(int, char *buf, size_t len);
+extern int nl_str2af(const char *);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *nl_addr2str(struct nl_addr *, char *buf, size_t len);
+
+/* Message Handlers <netlink/handlers.h> */
+/**
+ * Callback actions
+ * @ingroup cb
+ */
+enum nl_cb_action {
+	/** Proceed with wathever would come next */
+	NL_OK,
+	/** Skip this message */
+	NL_SKIP,
+	/** Stop parsing altogether and discard remaining messages */
+	NL_STOP,
+};
+
+/**
+ * Callback kinds
+ * @ingroup cb
+ */
+enum nl_cb_kind {
+	/** Default handlers (quiet) */
+	NL_CB_DEFAULT,
+	/** Verbose default handlers (error messages printed) */
+	NL_CB_VERBOSE,
+	/** Debug handlers for debugging */
+	NL_CB_DEBUG,
+	/** Customized handler specified by the user */
+	NL_CB_CUSTOM,
+	__NL_CB_KIND_MAX,
+};
+
+#define NL_CB_KIND_MAX (__NL_CB_KIND_MAX - 1)
+
+/**
+ * Callback types
+ * @ingroup cb
+ */
+enum nl_cb_type {
+	/** Message is valid */
+	NL_CB_VALID,
+	/** Last message in a series of multi part messages received */
+	NL_CB_FINISH,
+	/** Report received that data was lost */
+	NL_CB_OVERRUN,
+	/** Message wants to be skipped */
+	NL_CB_SKIPPED,
+	/** Message is an acknowledge */
+	NL_CB_ACK,
+	/** Called for every message received */
+	NL_CB_MSG_IN,
+	/** Called for every message sent out except for nl_sendto() */
+	NL_CB_MSG_OUT,
+	/** Message is malformed and invalid */
+	NL_CB_INVALID,
+	/** Called instead of internal sequence number checking */
+	NL_CB_SEQ_CHECK,
+	/** Sending of an acknowledge message has been requested */
+	NL_CB_SEND_ACK,
+	/** Flag NLM_F_DUMP_INTR is set in message */
+	NL_CB_DUMP_INTR,
+	__NL_CB_TYPE_MAX,
+};
+
+#define NL_CB_TYPE_MAX (__NL_CB_TYPE_MAX - 1)
+
+extern struct nl_cb *nl_cb_alloc(enum nl_cb_kind);
+extern struct nl_cb *nl_cb_clone(struct nl_cb *);
+
+struct nlmsgerr {
+	int error;
+};
+
+%{
+
+struct pynl_callback {
+	PyObject *cbf;
+	PyObject *cba;
+};
+
+struct pynl_cbinfo {
+	struct nl_cb *cb;
+	struct pynl_callback cbtype[NL_CB_TYPE_MAX+1];
+	struct pynl_callback cberr;
+	struct list_head list;
+};
+
+LIST_HEAD(callback_list);
+
+static struct pynl_cbinfo *pynl_find_cbinfo(struct nl_cb *cb, int unlink)
+{
+	struct list_head *pos, *prev;
+	struct pynl_cbinfo *info;
+
+	list_for_each_safe(pos, prev, &callback_list) {
+		info = container_of(pos, struct pynl_cbinfo, list);
+		if (info->cb == cb) {
+			if (unlink)
+				list_del(pos, prev);
+			pynl_dbg("cb=%p: found=%p\n", cb, info);
+			return info;
+		}
+	}
+	pynl_dbg("cb=%p: not found\n", cb);
+	return NULL;
+}
+
+static struct pynl_cbinfo *pynl_get_cbinfo(struct nl_cb *cb, int unlink)
+{
+	struct pynl_cbinfo *info;
+
+	info = pynl_find_cbinfo(cb, unlink);
+
+	if (info || unlink) {
+		/* found or no need to allocate a new one */
+		pynl_dbg("cb=%p: done\n", cb);
+		return info;
+	}
+
+	info = calloc(1, sizeof(*info));
+	info->cb = cb;
+	list_add(&info->list, &callback_list);
+	pynl_dbg("cb=%p: added %p\n", cb, info);
+	return info;
+}
+
+static int nl_recv_msg_handler(struct nl_msg *msg, void *arg)
+{
+	struct pynl_callback *cbd = arg;
+	PyObject *msgobj;
+	PyObject *cbparobj;
+	PyObject *resobj;
+	PyObject *funcobj;
+	int result;
+
+	if (!cbd) {
+		result = NL_STOP;
+		goto done;
+	}
+	msgobj = SWIG_NewPointerObj(SWIG_as_voidptr(msg),
+				    SWIGTYPE_p_nl_msg, 0 |  0 );
+	/* add selfobj if callback is a method */
+	if (cbd->cbf && PyMethod_Check(cbd->cbf)) {
+		PyObject *selfobj = PyMethod_Self(cbd->cbf);
+		cbparobj = Py_BuildValue("(OOO)", selfobj ? selfobj : cbd->cba,
+					 msgobj, cbd->cba);
+		funcobj = PyMethod_Function(cbd->cbf);
+		pynl_dbg("callback %sbounded instance method %p\n",
+			 selfobj ? "" : "un", funcobj);
+	} else {
+		cbparobj = Py_BuildValue("(OO)", msgobj, cbd->cba);
+		funcobj = cbd->cbf;
+		pynl_dbg("callback function %p\n", funcobj);
+	}
+	resobj = PyObject_CallObject(funcobj, cbparobj);
+	Py_DECREF(cbparobj);
+	if (resobj && PyInt_Check(resobj))
+		result = (int)PyInt_AsLong(resobj);
+	else
+		result = NL_STOP;
+	Py_XDECREF(resobj);
+done:
+	pynl_dbg("result=%d\n", result);
+	return result;
+}
+
+static int nl_recv_err_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
+			       void *arg)
+{
+	struct pynl_callback *cbd = arg;
+	PyObject *errobj;
+	PyObject *cbparobj;
+	PyObject *resobj;
+	PyObject *funcobj;
+	int result;
+
+	if (!cbd)
+		return NL_STOP;
+	errobj = SWIG_NewPointerObj(SWIG_as_voidptr(err),
+				    SWIGTYPE_p_nlmsgerr, 0 |  0 );
+	/* add selfobj if callback is a method */
+	if (cbd->cbf && PyMethod_Check(cbd->cbf)) {
+		PyObject *selfobj = PyMethod_Self(cbd->cbf);
+		cbparobj = Py_BuildValue("(OOO)", selfobj ? selfobj : cbd->cba,
+					 errobj, cbd->cba);
+		funcobj = PyMethod_Function(cbd->cbf);
+	} else {
+		cbparobj = Py_BuildValue("(OO)", errobj, cbd->cba);
+		funcobj = cbd->cbf;
+	}
+	resobj = PyObject_CallObject(funcobj, cbparobj);
+	Py_DECREF(cbparobj);
+	if (resobj && PyInt_Check(resobj))
+		result = (int)PyInt_AsLong(resobj);
+	else
+		result = NL_STOP;
+	Py_XDECREF(resobj);
+	pynl_dbg("error: err=%d ret=%d\n", err->error, result);
+	return result;
+}
+
+%}
+%inline %{
+struct nl_cb *py_nl_cb_clone(struct nl_cb *cb)
+{
+	struct pynl_cbinfo *info, *clone_info;
+	struct nl_cb *clone;
+	int i;
+
+	clone = nl_cb_clone(cb);
+	info = pynl_find_cbinfo(cb, 0);
+	if (info) {
+		clone_info = pynl_get_cbinfo(clone, 0);
+		/* increase refcnt to callback parameters and copy them */
+		for (i = 0; info && i <= NL_CB_TYPE_MAX; i++) {
+			Py_XINCREF(info->cbtype[i].cbf);
+			Py_XINCREF(info->cbtype[i].cba);
+			clone_info->cbtype[i].cbf = info->cbtype[i].cbf;
+			clone_info->cbtype[i].cba = info->cbtype[i].cba;
+		}
+		Py_XINCREF(info->cberr.cbf);
+		Py_XINCREF(info->cberr.cba);
+		clone_info->cberr.cbf = info->cberr.cbf;
+		clone_info->cberr.cba = info->cberr.cba;
+	}
+	return clone;
+}
+
+void py_nl_cb_put(struct nl_cb *cb)
+{
+	struct pynl_cbinfo *info;
+	int i;
+
+	/* obtain callback info (and unlink) */
+	info = pynl_get_cbinfo(cb, 1);
+	pynl_dbg("cb=%p, info=%p\n", cb, info);
+	/* decrease refcnt for callback type handlers */
+	for (i = 0; info && i <= NL_CB_TYPE_MAX; i++) {
+		Py_XDECREF(info->cbtype[i].cbf);
+		Py_XDECREF(info->cbtype[i].cba);
+	}
+	/* decrease refcnt for error handler and free callback info */
+	if (info) {
+		Py_XDECREF(info->cberr.cbf);
+		Py_XDECREF(info->cberr.cba);
+		free(info);
+	}
+	nl_cb_put(cb);
+}
+
+int py_nl_cb_set(struct nl_cb *cb, enum nl_cb_type t, enum nl_cb_kind k,
+		PyObject *func, PyObject *a)
+{
+	struct pynl_cbinfo *info;
+
+	/* obtain callback info */
+	info = pynl_get_cbinfo(cb, 0);
+
+	/* clear existing handlers (if any) */
+	Py_XDECREF(info->cbtype[t].cbf);
+	Py_XDECREF(info->cbtype[t].cba);
+	info->cbtype[t].cbf = NULL;
+	info->cbtype[t].cba = NULL;
+	pynl_dbg("cb=%p, info=%p, type=%d, kind=%d\n", cb, info, t, k);
+	/* handle custom callback */
+	if (k == NL_CB_CUSTOM) {
+		Py_XINCREF(func);
+		Py_XINCREF(a);
+		info->cbtype[t].cbf = func;
+		info->cbtype[t].cba = a;
+		return nl_cb_set(cb, t, k,
+				 nl_recv_msg_handler, &info->cbtype[t]);
+	}
+	return nl_cb_set(cb, t, k,  NULL, NULL);
+}
+
+int py_nl_cb_set_all(struct nl_cb *cb, enum nl_cb_kind k,
+		    PyObject *func , PyObject *a)
+{
+	struct pynl_cbinfo *info;
+	int t;
+
+	info = pynl_get_cbinfo(cb, 0);
+	pynl_dbg("cb=%p, info=%p, kind=%d\n", cb, info, k);
+	for (t = 0; t <= NL_CB_TYPE_MAX; t++) {
+		/* (possibly) free existing handler */
+		Py_XDECREF(info->cbtype[t].cbf);
+		Py_XDECREF(info->cbtype[t].cba);
+		info->cbtype[t].cbf = NULL;
+		info->cbtype[t].cba = NULL;
+		if (k == NL_CB_CUSTOM) {
+			Py_XINCREF(func);
+			Py_XINCREF(a);
+			info->cbtype[t].cbf = func;
+			info->cbtype[t].cba = a;
+		}
+	}
+	if (k == NL_CB_CUSTOM)
+		/* callback argument is same for all so using idx 0 here */
+		return nl_cb_set_all(cb, k, nl_recv_msg_handler,
+				     &info->cbtype[0]);
+	else
+		return nl_cb_set_all(cb, k, NULL, NULL);
+}
+
+int py_nl_cb_err(struct nl_cb *cb, enum nl_cb_kind k,
+		PyObject *func, PyObject *a)
+{
+	struct pynl_cbinfo *info;
+
+	/* obtain callback info */
+	info = pynl_get_cbinfo(cb, 0);
+	pynl_dbg("cb=%p, info=%p, kind=%d\n", cb, info, k);
+	/* clear existing handlers (if any) */
+	Py_XDECREF(info->cberr.cbf);
+	Py_XDECREF(info->cberr.cba);
+	info->cberr.cbf = NULL;
+	info->cberr.cba = NULL;
+
+	/* handle custom callback */
+	if (k == NL_CB_CUSTOM) {
+		Py_XINCREF(func);
+		Py_XINCREF(a);
+		info->cberr.cbf = func;
+		info->cberr.cba = a;
+		return nl_cb_err(cb, k,
+				 nl_recv_err_handler, &info->cberr);
+	}
+	return nl_cb_err(cb, k,  NULL, NULL);
+}
+%}
+
+/* Attributes <netlink/attr.h> */
+/*
+ * This typemap is a bit tricky as it uses arg1, which is knowledge about
+ * the SWIGged wrapper output.
+ */
+%typemap(out) void * {
+	$result = PyByteArray_FromStringAndSize($1, nla_len(arg1));
+}
+extern void *nla_data(struct nlattr *);
+%typemap(out) void *;
+extern int		nla_type(const struct nlattr *);
+
+/* Integer attribute */
+extern uint8_t		nla_get_u8(struct nlattr *);
+extern int		nla_put_u8(struct nl_msg *, int, uint8_t);
+extern uint16_t		nla_get_u16(struct nlattr *);
+extern int		nla_put_u16(struct nl_msg *, int, uint16_t);
+extern uint32_t		nla_get_u32(struct nlattr *);
+extern int		nla_put_u32(struct nl_msg *, int, uint32_t);
+extern uint64_t		nla_get_u64(struct nlattr *);
+extern int		nla_put_u64(struct nl_msg *, int, uint64_t);
+
+/* String attribute */
+extern char *		nla_get_string(struct nlattr *);
+extern char *		nla_strdup(struct nlattr *);
+extern int		nla_put_string(struct nl_msg *, int, const char *);
+
+/* Flag attribute */
+extern int		nla_get_flag(struct nlattr *);
+extern int		nla_put_flag(struct nl_msg *, int);
+
+/* Msec attribute */
+extern unsigned long	nla_get_msecs(struct nlattr *);
+extern int		nla_put_msecs(struct nl_msg *, int, unsigned long);
+
+/* Attribute nesting */
+extern int		nla_put_nested(struct nl_msg *, int, struct nl_msg *);
+extern struct nlattr *	nla_nest_start(struct nl_msg *, int);
+extern int		nla_nest_end(struct nl_msg *, struct nlattr *);
+%inline %{
+PyObject *py_nla_parse_nested(int max, struct nlattr *nest_attr, PyObject *p)
+{
+	struct nlattr *tb_msg[max + 1];
+	struct nla_policy *policy = NULL;
+	void *pol;
+	PyObject *attrs = Py_None;
+	PyObject *k;
+	PyObject *v;
+	PyObject *resobj;
+	int err;
+	int i;
+
+	if (p != Py_None) {
+		PyObject *pobj;
+
+		if (!PyList_Check(p)) {
+			fprintf(stderr, "expected list object\n");
+			err = -1;
+			goto fail;
+		}
+		pobj = PyList_GetItem(p, 0);
+		err = SWIG_ConvertPtr(pobj, &pol, SWIGTYPE_p_nla_policy, 0 |  0 );
+		if (!SWIG_IsOK(err))
+			goto fail;
+		policy = pol;
+	}
+	err = nla_parse_nested(tb_msg, max, nest_attr, policy);
+	if (err < 0) {
+		fprintf(stderr, "Failed to parse response message\n");
+	} else {
+		attrs = PyDict_New();
+		for (i = 0; i <= max; i++)
+			if (tb_msg[i]) {
+				k = PyInt_FromLong((long)i);
+				v = SWIG_NewPointerObj(SWIG_as_voidptr(tb_msg[i]), SWIGTYPE_p_nlattr, 0 |  0 );
+				PyDict_SetItem(attrs, k, v);
+			}
+	}
+fail:
+	if (attrs == Py_None)
+		Py_INCREF(attrs);
+	resobj = Py_BuildValue("(iO)", err, attrs);
+	return resobj;
+}
+
+/*
+ * nla_get_nested() - get list of nested attributes.
+ *
+ * nla_for_each_<nested|attr>() is a macro construct that needs another approach
+ * for Python. Create and return list of nested attributes.
+ */
+PyObject *nla_get_nested(struct nlattr *nest_attr)
+{
+	PyObject *listobj;
+	PyObject *nestattrobj;
+	struct nlattr *pos;
+	int rem;
+
+	listobj = PyList_New(0);
+	nla_for_each_nested(pos, nest_attr, rem) {
+		nestattrobj = SWIG_NewPointerObj(SWIG_as_voidptr(pos),
+						 SWIGTYPE_p_nlattr, 0 |  0 );
+		PyList_Append(listobj, nestattrobj);
+	}
+	return listobj;
+}
+%}
+
+ /**
+  * @ingroup attr
+  * Basic attribute data types
+  *
+  * See \ref attr_datatypes for more details.
+  */
+enum {
+	NLA_UNSPEC,	/**< Unspecified type, binary data chunk */
+	NLA_U8,		/**< 8 bit integer */
+	NLA_U16,	/**< 16 bit integer */
+	NLA_U32,	/**< 32 bit integer */
+	NLA_U64,	/**< 64 bit integer */
+	NLA_STRING,	/**< NUL terminated character string */
+	NLA_FLAG,	/**< Flag */
+	NLA_MSECS,	/**< Micro seconds (64bit) */
+	NLA_NESTED,	/**< Nested attributes */
+	__NLA_TYPE_MAX,
+};
+
+#define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1)
+
+/** @} */
+
+/**
+ * @ingroup attr
+ * Attribute validation policy.
+ *
+ * See \ref attr_datatypes for more details.
+ */
+struct nla_policy {
+	/** Type of attribute or NLA_UNSPEC */
+	uint16_t	type;
+
+	/** Minimal length of payload required */
+	uint16_t	minlen;
+
+	/** Maximal length of payload allowed */
+	uint16_t	maxlen;
+};
+
+%inline %{
+PyObject *nla_policy_array(int n_items)
+{
+	struct nla_policy *policies;
+	PyObject *listobj;
+	PyObject *polobj;
+	int i;
+
+	policies = calloc(n_items, sizeof(*policies));
+	listobj = PyList_New(n_items);
+	for (i = 0; i < n_items; i++) {
+		polobj = SWIG_NewPointerObj(SWIG_as_voidptr(&policies[i]),
+					    SWIGTYPE_p_nla_policy, 0 |  0 );
+		PyList_SetItem(listobj, i, polobj);
+	}
+	return listobj;
+}
+%}
diff --git a/python/netlink/core.py b/python/netlink/core.py
new file mode 100644
index 0000000..e5864cf
--- /dev/null
+++ b/python/netlink/core.py
@@ -0,0 +1,793 @@
+#
+# Netlink interface based on libnl
+#
+# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+#
+
+"""netlink library based on libnl
+
+This module provides an interface to netlink sockets
+
+The module contains the following public classes:
+ - Socket -- The netlink socket
+ - Message -- The netlink message
+ - Callback -- The netlink callback handler
+ - Object -- Abstract object (based on struct nl_obect in libnl) used as
+         base class for all object types which can be put into a Cache
+ - Cache -- A collection of objects which are derived from the base
+        class Object. Used for netlink protocols which maintain a list
+        or tree of objects.
+ - DumpParams --
+
+The following exceptions are defined:
+ - NetlinkError -- Base exception for all general purpose exceptions raised.
+ - KernelError -- Raised when the kernel returns an error as response to a
+          request.
+
+All other classes or functions in this module are considered implementation
+details.
+"""
+from __future__ import absolute_import
+
+
+
+from . import capi
+import sys
+import socket
+
+__all__ = [
+    'Socket',
+    'Message',
+    'Callback',
+    'DumpParams',
+    'Object',
+    'Cache',
+    'KernelError',
+    'NetlinkError',
+]
+
+__version__ = '0.1'
+
+# netlink protocols
+NETLINK_ROUTE = 0
+# NETLINK_UNUSED = 1
+NETLINK_USERSOCK = 2
+NETLINK_FIREWALL = 3
+NETLINK_INET_DIAG = 4
+NETLINK_NFLOG = 5
+NETLINK_XFRM = 6
+NETLINK_SELINUX = 7
+NETLINK_ISCSI = 8
+NETLINK_AUDIT = 9
+NETLINK_FIB_LOOKUP = 10
+NETLINK_CONNECTOR = 11
+NETLINK_NETFILTER = 12
+NETLINK_IP6_FW = 13
+NETLINK_DNRTMSG = 14
+NETLINK_KOBJECT_UEVENT = 15
+NETLINK_GENERIC = 16
+NETLINK_SCSITRANSPORT = 18
+NETLINK_ECRYPTFS = 19
+
+NL_DONTPAD = 0
+NL_AUTO_PORT = 0
+NL_AUTO_SEQ = 0
+
+NL_DUMP_LINE = 0
+NL_DUMP_DETAILS = 1
+NL_DUMP_STATS = 2
+
+NLM_F_REQUEST = 1
+NLM_F_MULTI = 2
+NLM_F_ACK = 4
+NLM_F_ECHO = 8
+
+NLM_F_ROOT = 0x100
+NLM_F_MATCH = 0x200
+NLM_F_ATOMIC = 0x400
+NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH
+
+NLM_F_REPLACE = 0x100
+NLM_F_EXCL = 0x200
+NLM_F_CREATE = 0x400
+NLM_F_APPEND = 0x800
+
+class NetlinkError(Exception):
+    def __init__(self, error):
+        self._error = error
+        self._msg = capi.nl_geterror(error)
+
+    def __str__(self):
+        return self._msg
+
+class KernelError(NetlinkError):
+    def __str__(self):
+        return 'Kernel returned: {0}'.format(self._msg)
+
+class ImmutableError(NetlinkError):
+    def __init__(self, msg):
+        self._msg = msg
+
+    def __str__(self):
+        return 'Immutable attribute: {0}'.format(self._msg)
+
+class Message(object):
+    """Netlink message"""
+
+    def __init__(self, size=0):
+        if size == 0:
+            self._msg = capi.nlmsg_alloc()
+        else:
+            self._msg = capi.nlmsg_alloc_size(size)
+
+        if self._msg is None:
+            raise Exception('Message allocation returned NULL')
+
+    def __del__(self):
+        capi.nlmsg_free(self._msg)
+
+    def __len__(self):
+        return capi.nlmsg_len(nlmsg_hdr(self._msg))
+
+    @property
+    def protocol(self):
+        return capi.nlmsg_get_proto(self._msg)
+
+    @protocol.setter
+    def protocol(self, value):
+        capi.nlmsg_set_proto(self._msg, value)
+
+    @property
+    def maxSize(self):
+        return capi.nlmsg_get_max_size(self._msg)
+
+    @property
+    def hdr(self):
+        return capi.nlmsg_hdr(self._msg)
+
+    @property
+    def data(self):
+        return capi.nlmsg_data(self._msg)
+
+    @property
+    def attrs(self):
+        return capi.nlmsg_attrdata(self._msg)
+
+    def send(self, sock):
+        sock.send(self)
+
+class Callback(object):
+    """Netlink callback"""
+
+    def __init__(self, kind=capi.NL_CB_DEFAULT):
+        if isinstance(kind, Callback):
+            self._cb = capi.py_nl_cb_clone(kind._cb)
+        else:
+            self._cb = capi.nl_cb_alloc(kind)
+
+    def __del__(self):
+        capi.py_nl_cb_put(self._cb)
+
+    def set_type(self, t, k, handler, obj):
+        return capi.py_nl_cb_set(self._cb, t, k, handler, obj)
+
+    def set_all(self, k, handler, obj):
+        return capi.py_nl_cb_set_all(self._cb, k, handler, obj)
+
+    def set_err(self, k, handler, obj):
+        return capi.py_nl_cb_err(self._cb, k, handler, obj)
+
+    def clone(self):
+        return Callback(self)
+
+class Socket(object):
+    """Netlink socket"""
+
+    def __init__(self, cb=None):
+        if isinstance(cb, Callback):
+            self._sock = capi.nl_socket_alloc_cb(cb._cb)
+        elif cb == None:
+            self._sock = capi.nl_socket_alloc()
+        else:
+            raise Exception('\'cb\' parameter has wrong type')
+
+        if self._sock is None:
+            raise Exception('NULL pointer returned while allocating socket')
+
+    def __del__(self):
+        capi.nl_socket_free(self._sock)
+
+    def __str__(self):
+        return 'nlsock<{0}>'.format(self.local_port)
+
+    @property
+    def local_port(self):
+        return capi.nl_socket_get_local_port(self._sock)
+
+    @local_port.setter
+    def local_port(self, value):
+        capi.nl_socket_set_local_port(self._sock, int(value))
+
+    @property
+    def peer_port(self):
+        return capi.nl_socket_get_peer_port(self._sock)
+
+    @peer_port.setter
+    def peer_port(self, value):
+        capi.nl_socket_set_peer_port(self._sock, int(value))
+
+    @property
+    def peer_groups(self):
+        return capi.nl_socket_get_peer_groups(self._sock)
+
+    @peer_groups.setter
+    def peer_groups(self, value):
+        capi.nl_socket_set_peer_groups(self._sock, value)
+
+    def set_bufsize(self, rx, tx):
+        capi.nl_socket_set_buffer_size(self._sock, rx, tx)
+
+    def connect(self, proto):
+        capi.nl_connect(self._sock, proto)
+        return self
+
+    def disconnect(self):
+        capi.nl_close(self._sock)
+
+    def sendto(self, buf):
+        ret = capi.nl_sendto(self._sock, buf, len(buf))
+        if ret < 0:
+            raise Exception('Failed to send')
+        else:
+            return ret
+
+    def send_auto_complete(self, msg):
+        if not isinstance(msg, Message):
+            raise Exception('must provide Message instance')
+        ret = capi.nl_send_auto_complete(self._sock, msg._msg)
+        if ret < 0:
+            raise Exception('send_auto_complete failed: ret=%d' % ret)
+        return ret
+
+    def recvmsgs(self, recv_cb):
+        if not isinstance(recv_cb, Callback):
+            raise Exception('must provide Callback instance')
+        ret = capi.nl_recvmsgs(self._sock, recv_cb._cb)
+        if ret < 0:
+            raise Exception('recvmsg failed: ret=%d' % ret)
+
+_sockets = {}
+
+def lookup_socket(protocol):
+    try:
+        sock = _sockets[protocol]
+    except KeyError:
+        sock = Socket()
+        sock.connect(protocol)
+        _sockets[protocol] = sock
+
+    return sock
+
+class DumpParams(object):
+    """Dumping parameters"""
+
+    def __init__(self, type_=NL_DUMP_LINE):
+        self._dp = capi.alloc_dump_params()
+        if not self._dp:
+            raise Exception('Unable to allocate struct nl_dump_params')
+
+        self._dp.dp_type = type_
+
+    def __del__(self):
+        capi.free_dump_params(self._dp)
+
+    @property
+    def type(self):
+        return self._dp.dp_type
+
+    @type.setter
+    def type(self, value):
+        self._dp.dp_type = value
+
+    @property
+    def prefix(self):
+        return self._dp.dp_prefix
+
+    @prefix.setter
+    def prefix(self, value):
+        self._dp.dp_prefix = value
+
+# underscore this to make sure it is deleted first upon module deletion
+_defaultDumpParams = DumpParams(NL_DUMP_LINE)
+
+class Object(object):
+    """Cacheable object (base class)"""
+
+    def __init__(self, obj_name, name, obj=None):
+        self._obj_name = obj_name
+        self._name = name
+        self._modules = []
+
+        if not obj:
+            obj = capi.object_alloc_name(self._obj_name)
+
+        self._nl_object = obj
+
+        # Create a clone which stores the original state to notice
+        # modifications
+        clone_obj = capi.nl_object_clone(self._nl_object)
+        self._orig = self._obj2type(clone_obj)
+
+    def __del__(self):
+        if not self._nl_object:
+            raise ValueError()
+
+        capi.nl_object_put(self._nl_object)
+
+    def __str__(self):
+        if hasattr(self, 'format'):
+            return self.format()
+        else:
+            return capi.nl_object_dump_buf(self._nl_object, 4096).rstrip()
+
+    def _new_instance(self):
+        raise NotImplementedError()
+
+    def clone(self):
+        """Clone object"""
+        return self._new_instance(capi.nl_object_clone(self._nl_object))
+
+    def _module_lookup(self, path, constructor=None):
+        """Lookup object specific module and load it
+
+        Object implementations consisting of multiple types may
+        offload some type specific code to separate modules which
+        are loadable on demand, e.g. a VLAN link or a specific
+        queueing discipline implementation.
+
+        Loads the module `path` and calls the constructor if
+        supplied or `module`.init()
+
+        The constructor/init function typically assigns a new
+        object covering the type specific implementation aspects
+        to the new object, e.g. link.vlan = VLANLink()
+        """
+        try:
+            __import__(path)
+        except ImportError:
+            return
+
+        module = sys.modules[path]
+
+        if constructor:
+            ret = getattr(module, constructor)(self)
+        else:
+            ret = module.init(self)
+
+        if ret:
+            self._modules.append(ret)
+
+    def _module_brief(self):
+        ret = ''
+
+        for module in self._modules:
+            if hasattr(module, 'brief'):
+                ret += module.brief()
+
+        return ret
+
+    def dump(self, params=None):
+        """Dump object as human readable text"""
+        if params is None:
+            params = _defaultDumpParams
+
+        capi.nl_object_dump(self._nl_object, params._dp)
+
+
+    @property
+    def mark(self):
+        return bool(capi.nl_object_is_marked(self._nl_object))
+
+    @mark.setter
+    def mark(self, value):
+        if value:
+            capi.nl_object_mark(self._nl_object)
+        else:
+            capi.nl_object_unmark(self._nl_object)
+
+    @property
+    def shared(self):
+        return capi.nl_object_shared(self._nl_object) != 0
+
+    @property
+    def attrs(self):
+        attr_list = capi.nl_object_attr_list(self._nl_object, 1024)
+        return attr_list[0].split()
+
+    @property
+    def refcnt(self):
+        return capi.nl_object_get_refcnt(self._nl_object)
+
+    # this method resolves multiple levels of sub types to allow
+    # accessing properties of subclass/subtypes (e.g. link.vlan.id)
+    def _resolve(self, attr):
+        obj = self
+        l = attr.split('.')
+        while len(l) > 1:
+            obj = getattr(obj, l.pop(0))
+        return (obj, l.pop(0))
+
+    def _setattr(self, attr, val):
+        obj, attr = self._resolve(attr)
+        return setattr(obj, attr, val)
+
+    def _hasattr(self, attr):
+        obj, attr = self._resolve(attr)
+        return hasattr(obj, attr)
+
+class ObjIterator(object):
+    def __init__(self, cache, obj):
+        self._cache = cache
+        self._nl_object = None
+
+        if not obj:
+            self._end = 1
+        else:
+            capi.nl_object_get(obj)
+            self._nl_object = obj
+            self._first = 1
+            self._end = 0
+
+    def __del__(self):
+        if self._nl_object:
+            capi.nl_object_put(self._nl_object)
+
+    def __iter__(self):
+        return self
+
+    def get_next(self):
+        return capi.nl_cache_get_next(self._nl_object)
+
+    def next(self):
+        return self.__next__()
+
+    def __next__(self):
+        if self._end:
+            raise StopIteration()
+
+        if self._first:
+            ret = self._nl_object
+            self._first = 0
+        else:
+            ret = self.get_next()
+            if not ret:
+                self._end = 1
+                raise StopIteration()
+
+        # return ref of previous element and acquire ref of current
+        # element to have object stay around until we fetched the
+        # next ptr
+        capi.nl_object_put(self._nl_object)
+        capi.nl_object_get(ret)
+        self._nl_object = ret
+
+        # reference used inside object
+        capi.nl_object_get(ret)
+        return self._cache._new_object(ret)
+
+
+class ReverseObjIterator(ObjIterator):
+    def get_next(self):
+        return capi.nl_cache_get_prev(self._nl_object)
+
+class Cache(object):
+    """Collection of netlink objects"""
+    def __init__(self):
+        if self.__class__ is Cache:
+            raise NotImplementedError()
+        self.arg1 = None
+        self.arg2 = None
+
+    def __del__(self):
+        capi.nl_cache_free(self._nl_cache)
+
+    def __len__(self):
+        return capi.nl_cache_nitems(self._nl_cache)
+
+    def __iter__(self):
+        obj = capi.nl_cache_get_first(self._nl_cache)
+        return ObjIterator(self, obj)
+
+    def __reversed__(self):
+        obj = capi.nl_cache_get_last(self._nl_cache)
+        return ReverseObjIterator(self, obj)
+
+    def __contains__(self, item):
+        obj = capi.nl_cache_search(self._nl_cache, item._nl_object)
+        if obj is None:
+            return False
+        else:
+            capi.nl_object_put(obj)
+            return True
+
+    # called by sub classes to allocate type specific caches by name
+    @staticmethod
+    def _alloc_cache_name(name):
+        return capi.alloc_cache_name(name)
+
+    # implemented by sub classes, must return new instasnce of cacheable
+    # object
+    @staticmethod
+    def _new_object(obj):
+        raise NotImplementedError()
+
+    # implemented by sub classes, must return instance of sub class
+    def _new_cache(self, cache):
+        raise NotImplementedError()
+
+    def subset(self, filter_):
+        """Return new cache containing subset of cache
+
+        Cretes a new cache containing all objects which match the
+        specified filter.
+        """
+        if not filter_:
+            raise ValueError()
+
+        c = capi.nl_cache_subset(self._nl_cache, filter_._nl_object)
+        return self._new_cache(cache=c)
+
+    def dump(self, params=None, filter_=None):
+        """Dump (print) cache as human readable text"""
+        if not params:
+            params = _defaultDumpParams
+
+        if filter_:
+            filter_ = filter_._nl_object
+
+        capi.nl_cache_dump_filter(self._nl_cache, params._dp, filter_)
+
+    def clear(self):
+        """Remove all cache entries"""
+        capi.nl_cache_clear(self._nl_cache)
+
+    # Called by sub classes to set first cache argument
+    def _set_arg1(self, arg):
+        self.arg1 = arg
+        capi.nl_cache_set_arg1(self._nl_cache, arg)
+
+    # Called by sub classes to set second cache argument
+    def _set_arg2(self, arg):
+        self.arg2 = arg
+        capi.nl_cache_set_arg2(self._nl_cache, arg)
+
+    def refill(self, socket=None):
+        """Clear cache and refill it"""
+        if socket is None:
+            socket = lookup_socket(self._protocol)
+
+        capi.nl_cache_refill(socket._sock, self._nl_cache)
+        return self
+
+    def resync(self, socket=None, cb=None, args=None):
+        """Synchronize cache with content in kernel"""
+        if socket is None:
+            socket = lookup_socket(self._protocol)
+
+        capi.nl_cache_resync(socket._sock, self._nl_cache, cb, args)
+
+    def provide(self):
+        """Provide this cache to others
+
+        Caches which have been "provided" are made available
+        to other users (of the same application context) which
+        "require" it. F.e. a link cache is generally provided
+        to allow others to translate interface indexes to
+        link names
+        """
+
+        capi.nl_cache_mngt_provide(self._nl_cache)
+
+    def unprovide(self):
+        """Unprovide this cache
+
+        No longer make the cache available to others. If the cache
+        has been handed out already, that reference will still
+        be valid.
+        """
+        capi.nl_cache_mngt_unprovide(self._nl_cache)
+
+# Cache Manager (Work in Progress)
+NL_AUTO_PROVIDE = 1
+class CacheManager(object):
+    def __init__(self, protocol, flags=None):
+
+        self._sock = Socket()
+        self._sock.connect(protocol)
+
+        if not flags:
+            flags = NL_AUTO_PROVIDE
+
+        self._mngr = capi.cache_mngr_alloc(self._sock._sock, protocol, flags)
+
+    def __del__(self):
+        if self._sock:
+            self._sock.disconnect()
+
+        if self._mngr:
+            capi.nl_cache_mngr_free(self._mngr)
+
+    def add(self, name):
+        capi.cache_mngr_add(self._mngr, name, None, None)
+
+class AddressFamily(object):
+    """Address family representation
+
+    af = AddressFamily('inet6')
+    # raises:
+    #   - ValueError if family name is not known
+    #   - TypeError if invalid type is specified for family
+
+    print af        # => 'inet6' (string representation)
+    print int(af)   # => 10 (numeric representation)
+    print repr(af)  # => AddressFamily('inet6')
+    """
+    def __init__(self, family=socket.AF_UNSPEC):
+        if isinstance(family, str):
+            family = capi.nl_str2af(family)
+            if family < 0:
+                raise ValueError('Unknown family name')
+        elif not isinstance(family, int):
+            raise TypeError()
+
+        self._family = family
+
+    def __str__(self):
+        return capi.nl_af2str(self._family, 32)[0]
+
+    def __int__(self):
+        return self._family
+
+    def __repr__(self):
+        return 'AddressFamily({0!r})'.format(str(self))
+
+
+class AbstractAddress(object):
+    """Abstract address object
+
+    addr = AbstractAddress('127.0.0.1/8')
+    print addr               # => '127.0.0.1/8'
+    print addr.prefixlen     # => '8'
+    print addr.family        # => 'inet'
+    print len(addr)          # => '4' (32bit ipv4 address)
+
+    a = AbstractAddress('10.0.0.1/24')
+    b = AbstractAddress('10.0.0.2/24')
+    print a == b             # => False
+
+
+    """
+    def __init__(self, addr):
+        self._nl_addr = None
+
+        if isinstance(addr, str):
+            # returns None on success I guess
+            # TO CORRECT 
+            addr = capi.addr_parse(addr, socket.AF_UNSPEC)
+            if addr is None:
+                raise ValueError('Invalid address format')
+        elif addr:
+            capi.nl_addr_get(addr)
+
+        self._nl_addr = addr
+
+    def __del__(self):
+        if self._nl_addr:
+            capi.nl_addr_put(self._nl_addr)
+
+    def __cmp__(self, other):
+        if isinstance(other, str):
+            other = AbstractAddress(other)
+
+        diff = self.prefixlen - other.prefixlen
+        if diff == 0:
+            diff = capi.nl_addr_cmp(self._nl_addr, other._nl_addr)
+
+        return diff
+
+    def contains(self, item):
+        diff = int(self.family) - int(item.family)
+        if diff:
+            return False
+
+        if item.prefixlen < self.prefixlen:
+            return False
+
+        diff = capi.nl_addr_cmp_prefix(self._nl_addr, item._nl_addr)
+        return diff == 0
+
+    def __nonzero__(self):
+        if self._nl_addr:
+            return not capi.nl_addr_iszero(self._nl_addr)
+        else:
+            return False
+
+    def __len__(self):
+        if self._nl_addr:
+            return capi.nl_addr_get_len(self._nl_addr)
+        else:
+            return 0
+
+    def __str__(self):
+        if self._nl_addr:
+            return capi.nl_addr2str(self._nl_addr, 64)[0]
+        else:
+            return 'none'
+
+    @property
+    def shared(self):
+        """True if address is shared (multiple users)"""
+        if self._nl_addr:
+            return capi.nl_addr_shared(self._nl_addr) != 0
+        else:
+            return False
+
+    @property
+    def prefixlen(self):
+        """Length of prefix (number of bits)"""
+        if self._nl_addr:
+            return capi.nl_addr_get_prefixlen(self._nl_addr)
+        else:
+            return 0
+
+    @prefixlen.setter
+    def prefixlen(self, value):
+        if not self._nl_addr:
+            raise TypeError()
+
+        capi.nl_addr_set_prefixlen(self._nl_addr, int(value))
+
+    @property
+    def family(self):
+        """Address family"""
+        f = 0
+        if self._nl_addr:
+            f = capi.nl_addr_get_family(self._nl_addr)
+
+        return AddressFamily(f)
+
+    @family.setter
+    def family(self, value):
+        if not self._nl_addr:
+            raise TypeError()
+
+        if not isinstance(value, AddressFamily):
+            value = AddressFamily(value)
+
+        capi.nl_addr_set_family(self._nl_addr, int(value))
+
+
+# keyword:
+#   type = { int | str }
+#   immutable = { True | False }
+#   fmt = func (formatting function)
+#   title = string
+
+def nlattr(**kwds):
+    """netlink object attribute decorator
+
+    decorator used to mark mutable and immutable properties
+    of netlink objects. All properties marked as such are
+    regarded to be accessable.
+
+    @property
+    @netlink.nlattr(type=int)
+    def my_attr(self):
+        return self._my_attr
+
+    """
+
+    def wrap_fn(func):
+        func.formatinfo = kwds
+        return func
+    return wrap_fn
diff --git a/python/netlink/fixes.h b/python/netlink/fixes.h
new file mode 100644
index 0000000..9a6118b
--- /dev/null
+++ b/python/netlink/fixes.h
@@ -0,0 +1 @@
+#include <stdint.h>
diff --git a/python/netlink/genl/Makefile.am b/python/netlink/genl/Makefile.am
new file mode 100644
index 0000000..9e30904
--- /dev/null
+++ b/python/netlink/genl/Makefile.am
@@ -0,0 +1,5 @@
+# -*- Makefile -*-
+
+EXTRA_DIST = \
+	capi.i \
+	__init__.py
diff --git a/python/netlink/genl/__init__.py b/python/netlink/genl/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/netlink/genl/__init__.py
diff --git a/python/netlink/genl/capi.i b/python/netlink/genl/capi.i
new file mode 100644
index 0000000..069e617
--- /dev/null
+++ b/python/netlink/genl/capi.i
@@ -0,0 +1,108 @@
+%module capi
+%{
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/family.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/mngt.h>
+%}
+
+%include <stdint.i>
+%include <cstring.i>
+
+/* #include <netlink/genl/ctrl.h> */
+extern int genl_ctrl_alloc_cache(struct nl_sock *, struct nl_cache **o_cache);
+extern struct genl_family *genl_ctrl_search(struct nl_cache *, int);
+extern struct genl_family *genl_ctrl_search_by_name(struct nl_cache *,
+						    const char *);
+extern int genl_ctrl_resolve(struct nl_sock *, const char *);
+extern int genl_ctrl_resolve_grp(struct nl_sock *sk, const char *family,
+				 const char *grp);
+
+/* #include <netlink/genl/family.h> */
+extern struct genl_family *genl_family_alloc(void);
+extern void genl_family_put(struct genl_family *);
+
+extern unsigned int genl_family_get_id(struct genl_family *);
+extern void genl_family_set_id(struct genl_family *, unsigned int);
+extern char *genl_family_get_name(struct genl_family *);
+extern void genl_family_set_name(struct genl_family *, const char *name);
+extern uint8_t genl_family_get_version(struct genl_family *);
+extern void genl_family_set_version(struct genl_family *, uint8_t);
+extern uint32_t genl_family_get_hdrsize(struct genl_family *);
+extern void genl_family_set_hdrsize(struct genl_family *, uint32_t);
+extern uint32_t genl_family_get_maxattr(struct genl_family *);
+extern void genl_family_set_maxattr(struct genl_family *, uint32_t);
+
+extern int genl_family_add_op(struct genl_family *, int, int);
+extern int genl_family_add_grp(struct genl_family *, uint32_t , const char *);
+
+/* #include <netlink/genl/genl.h> */
+extern int genl_connect(struct nl_sock *);
+
+extern void *genlmsg_put(struct nl_msg *, uint32_t, uint32_t,
+			 int, int, int, uint8_t, uint8_t);
+
+struct nlattr {
+};
+
+struct nla_policy {
+	/** Type of attribute or NLA_UNSPEC */
+	uint16_t	type;
+
+	/** Minimal length of payload required */
+	uint16_t	minlen;
+
+	/** Maximal length of payload allowed */
+	uint16_t	maxlen;
+};
+
+%inline %{
+PyObject *py_genlmsg_parse(struct nlmsghdr *nlh, int uhl, int max,
+			   PyObject *p)
+{
+	struct nlattr *tb_msg[max + 1];
+	struct nla_policy *policy = NULL;
+	void *pol;
+	PyObject *attrs = Py_None;
+	PyObject *k;
+	PyObject *v;
+	PyObject *resobj;
+	int err;
+	int i;
+
+	if (p != Py_None) {
+		PyObject *pobj;
+
+		if (!PyList_Check(p)) {
+			fprintf(stderr, "expected list object\n");
+			err = -1;
+			goto fail;
+		}
+		pobj = PyList_GetItem(p, 0);
+		err = SWIG_ConvertPtr(pobj, &pol, SWIGTYPE_p_nla_policy, 0 |  0 );
+		if (!SWIG_IsOK(err))
+			goto fail;
+		policy = pol;
+	}
+	err = genlmsg_parse(nlh, uhl, tb_msg, max, policy);
+	if (err < 0) {
+		fprintf(stderr, "Failed to parse response message\n");
+	} else {
+		attrs = PyDict_New();
+		for (i = 0; i <= max; i++)
+			if (tb_msg[i]) {
+				k = PyInt_FromLong((long)i);
+				v = SWIG_NewPointerObj(SWIG_as_voidptr(tb_msg[i]), SWIGTYPE_p_nlattr, 0 |  0 );
+				PyDict_SetItem(attrs, k, v);
+			}
+	}
+fail:
+	if (attrs == Py_None)
+		Py_INCREF(Py_None);
+	resobj = Py_BuildValue("(iO)", err, attrs);
+	return resobj;
+}
+
+%}
+/* #include <netlink/genl/mngt.h> */
+/* nothing yet */
diff --git a/python/netlink/route/Makefile.am b/python/netlink/route/Makefile.am
new file mode 100644
index 0000000..ef714f4
--- /dev/null
+++ b/python/netlink/route/Makefile.am
@@ -0,0 +1,14 @@
+# -*- Makefile -*-
+
+EXTRA_DIST = \
+	capi.i \
+	__init__.py \
+	address.py \
+	link.py \
+	tc.py \
+	links/__init__.py \
+	links/dummy.py \
+	links/inet.py \
+	links/vlan.py \
+	qdisc/__init__.py \
+	qdisc/htb.py
diff --git a/python/netlink/route/__init__.py b/python/netlink/route/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/netlink/route/__init__.py
diff --git a/python/netlink/route/address.py b/python/netlink/route/address.py
new file mode 100644
index 0000000..cab2a97
--- /dev/null
+++ b/python/netlink/route/address.py
@@ -0,0 +1,367 @@
+#
+# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+#
+
+"""Module providing access to network addresses
+"""
+
+from __future__ import absolute_import
+
+
+__version__ = '1.0'
+__all__ = [
+    'AddressCache',
+    'Address']
+
+import datetime
+from .. import core as netlink
+from .  import capi as capi
+from .  import link as Link
+from .. import util as util
+
+class AddressCache(netlink.Cache):
+    """Cache containing network addresses"""
+
+    def __init__(self, cache=None):
+        if not cache:
+            cache = self._alloc_cache_name('route/addr')
+
+        self._protocol = netlink.NETLINK_ROUTE
+        self._nl_cache = cache
+
+    def __getitem__(self, key):
+        # Using ifindex=0 here implies that the local address itself
+        # is unique, otherwise the first occurence is returned.
+        return self.lookup(0, key)
+
+    def lookup(self, ifindex, local):
+        if type(local) is str:
+            local = netlink.AbstractAddress(local)
+
+        addr = capi.rtnl_addr_get(self._nl_cache, ifindex,
+                      local._nl_addr)
+        if addr is None:
+            raise KeyError()
+
+        return Address._from_capi(addr)
+
+    @staticmethod
+    def _new_object(obj):
+        return Address(obj)
+
+    @staticmethod
+    def _new_cache(cache):
+        return AddressCache(cache=cache)
+
+class Address(netlink.Object):
+    """Network address"""
+
+    def __init__(self, obj=None):
+        netlink.Object.__init__(self, 'route/addr', 'address', obj)
+        self._rtnl_addr = self._obj2type(self._nl_object)
+
+    @classmethod
+    def _from_capi(cls, obj):
+        return cls(capi.addr2obj(obj))
+
+    @staticmethod
+    def _obj2type(obj):
+        return capi.obj2addr(obj)
+
+    def __cmp__(self, other):
+        # sort by:
+        #    1. network link
+        #    2. address family
+        #    3. local address (including prefixlen)
+        diff = self.ifindex - other.ifindex
+
+        if diff == 0:
+            diff = self.family - other.family
+            if diff == 0:
+                diff = capi.nl_addr_cmp(self.local, other.local)
+
+        return diff
+
+    @staticmethod
+    def _new_instance(obj):
+        return Address(obj)
+
+    @property
+    @netlink.nlattr(type=int, immutable=True, fmt=util.num)
+    def ifindex(self):
+        """interface index"""
+        return capi.rtnl_addr_get_ifindex(self._rtnl_addr)
+
+    @ifindex.setter
+    def ifindex(self, value):
+        link = Link.resolve(value)
+        if not link:
+            raise ValueError()
+
+        self.link = link
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.string)
+    def link(self):
+        link = capi.rtnl_addr_get_link(self._rtnl_addr)
+        if not link:
+            return None
+
+        return Link.Link.from_capi(link)
+
+    @link.setter
+    def link(self, value):
+        if type(value) is str:
+            try:
+                value = Link.resolve(value)
+            except KeyError:
+                raise ValueError()
+
+        capi.rtnl_addr_set_link(self._rtnl_addr, value._rtnl_link)
+
+        # ifindex is immutable but we assume that if _orig does not
+        # have an ifindex specified, it was meant to be given here
+        if capi.rtnl_addr_get_ifindex(self._orig) == 0:
+            capi.rtnl_addr_set_ifindex(self._orig, value.ifindex)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.string)
+    def label(self):
+        """address label"""
+        return capi.rtnl_addr_get_label(self._rtnl_addr)
+
+    @label.setter
+    def label(self, value):
+        capi.rtnl_addr_set_label(self._rtnl_addr, value)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.string)
+    def flags(self):
+        """Flags
+
+        Setting this property will *Not* reset flags to value you supply in
+
+        Examples:
+        addr.flags = '+xxx' # add xxx flag
+        addr.flags = 'xxx'  # exactly the same
+        addr.flags = '-xxx' # remove xxx flag
+        addr.flags = [ '+xxx', '-yyy' ] # list operation
+        """
+        flags = capi.rtnl_addr_get_flags(self._rtnl_addr)
+        return capi.rtnl_addr_flags2str(flags, 256)[0].split(',')
+
+    def _set_flag(self, flag):
+        if flag.startswith('-'):
+            i = capi.rtnl_addr_str2flags(flag[1:])
+            capi.rtnl_addr_unset_flags(self._rtnl_addr, i)
+        elif flag.startswith('+'):
+            i = capi.rtnl_addr_str2flags(flag[1:])
+            capi.rtnl_addr_set_flags(self._rtnl_addr, i)
+        else:
+            i = capi.rtnl_addr_str2flags(flag)
+            capi.rtnl_addr_set_flags(self._rtnl_addr, i)
+
+    @flags.setter
+    def flags(self, value):
+        if type(value) is list:
+            for flag in value:
+                self._set_flag(flag)
+        else:
+            self._set_flag(value)
+
+    @property
+    @netlink.nlattr(type=int, immutable=True, fmt=util.num)
+    def family(self):
+        """Address family"""
+        fam = capi.rtnl_addr_get_family(self._rtnl_addr)
+        return netlink.AddressFamily(fam)
+
+    @family.setter
+    def family(self, value):
+        if not isinstance(value, netlink.AddressFamily):
+            value = netlink.AddressFamily(value)
+
+        capi.rtnl_addr_set_family(self._rtnl_addr, int(value))
+
+    @property
+    @netlink.nlattr(type=int, fmt=util.num)
+    def scope(self):
+        """Address scope"""
+        scope = capi.rtnl_addr_get_scope(self._rtnl_addr)
+        return capi.rtnl_scope2str(scope, 32)[0]
+
+    @scope.setter
+    def scope(self, value):
+        if type(value) is str:
+            value = capi.rtnl_str2scope(value)
+        capi.rtnl_addr_set_scope(self._rtnl_addr, value)
+
+    @property
+    @netlink.nlattr(type=str, immutable=True, fmt=util.addr)
+    def local(self):
+        """Local address"""
+        a = capi.rtnl_addr_get_local(self._rtnl_addr)
+        return netlink.AbstractAddress(a)
+
+    @local.setter
+    def local(self, value):
+        a = netlink.AbstractAddress(value)
+        capi.rtnl_addr_set_local(self._rtnl_addr, a._nl_addr)
+
+        # local is immutable but we assume that if _orig does not
+        # have a local address specified, it was meant to be given here
+        if capi.rtnl_addr_get_local(self._orig) is None:
+            capi.rtnl_addr_set_local(self._orig, a._nl_addr)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.addr)
+    def peer(self):
+        """Peer address"""
+        a = capi.rtnl_addr_get_peer(self._rtnl_addr)
+        return netlink.AbstractAddress(a)
+
+    @peer.setter
+    def peer(self, value):
+        a = netlink.AbstractAddress(value)
+        capi.rtnl_addr_set_peer(self._rtnl_addr, a._nl_addr)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.addr)
+    def broadcast(self):
+        """Broadcast address"""
+        a = capi.rtnl_addr_get_broadcast(self._rtnl_addr)
+        return netlink.AbstractAddress(a)
+
+    @broadcast.setter
+    def broadcast(self, value):
+        a = netlink.AbstractAddress(value)
+        capi.rtnl_addr_set_broadcast(self._rtnl_addr, a._nl_addr)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.addr)
+    def multicast(self):
+        """multicast address"""
+        a = capi.rtnl_addr_get_multicast(self._rtnl_addr)
+        return netlink.AbstractAddress(a)
+
+    @multicast.setter
+    def multicast(self, value):
+        try:
+            a = netlink.AbstractAddress(value)
+        except ValueError as err:
+            raise AttributeError('multicast', err)
+
+        capi.rtnl_addr_set_multicast(self._rtnl_addr, a._nl_addr)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.addr)
+    def anycast(self):
+        """anycast address"""
+        a = capi.rtnl_addr_get_anycast(self._rtnl_addr)
+        return netlink.AbstractAddress(a)
+
+    @anycast.setter
+    def anycast(self, value):
+        a = netlink.AbstractAddress(value)
+        capi.rtnl_addr_set_anycast(self._rtnl_addr, a._nl_addr)
+
+    @property
+    @netlink.nlattr(type=int, immutable=True, fmt=util.num)
+    def valid_lifetime(self):
+        """Valid lifetime"""
+        msecs = capi.rtnl_addr_get_valid_lifetime(self._rtnl_addr)
+        if msecs == 0xFFFFFFFF:
+            return None
+        else:
+            return datetime.timedelta(seconds=msecs)
+
+    @valid_lifetime.setter
+    def valid_lifetime(self, value):
+        capi.rtnl_addr_set_valid_lifetime(self._rtnl_addr, int(value))
+
+    @property
+    @netlink.nlattr(type=int, immutable=True, fmt=util.num)
+    def preferred_lifetime(self):
+        """Preferred lifetime"""
+        msecs = capi.rtnl_addr_get_preferred_lifetime(self._rtnl_addr)
+        if msecs == 0xFFFFFFFF:
+            return None
+        else:
+            return datetime.timedelta(seconds=msecs)
+
+    @preferred_lifetime.setter
+    def preferred_lifetime(self, value):
+        capi.rtnl_addr_set_preferred_lifetime(self._rtnl_addr, int(value))
+
+    @property
+    @netlink.nlattr(type=int, immutable=True, fmt=util.num)
+    def create_time(self):
+        """Creation time"""
+        hsec = capi.rtnl_addr_get_create_time(self._rtnl_addr)
+        return datetime.timedelta(milliseconds=10*hsec)
+
+    @property
+    @netlink.nlattr(type=int, immutable=True, fmt=util.num)
+    def last_update(self):
+        """Last update"""
+        hsec = capi.rtnl_addr_get_last_update_time(self._rtnl_addr)
+        return datetime.timedelta(milliseconds=10*hsec)
+
+    def add(self, socket=None, flags=None):
+        if not socket:
+            socket = netlink.lookup_socket(netlink.NETLINK_ROUTE)
+
+        if not flags:
+            flags = netlink.NLM_F_CREATE
+
+        ret = capi.rtnl_addr_add(socket._sock, self._rtnl_addr, flags)
+        if ret < 0:
+            raise netlink.KernelError(ret)
+
+    def delete(self, socket, flags=0):
+        """Attempt to delete this address in the kernel"""
+        ret = capi.rtnl_addr_delete(socket._sock, self._rtnl_addr, flags)
+        if ret < 0:
+            raise netlink.KernelError(ret)
+
+    ###################################################################
+    # private properties
+    #
+    # Used for formatting output. USE AT OWN RISK
+    @property
+    def _flags(self):
+        return ','.join(self.flags)
+
+    def format(self, details=False, stats=False, nodev=False, indent=''):
+        """Return address as formatted text"""
+        fmt = util.MyFormatter(self, indent)
+
+        buf = fmt.format('{a|local!b}')
+
+        if not nodev:
+            buf += fmt.format(' {a|ifindex}')
+
+        buf += fmt.format(' {a|scope}')
+
+        if self.label:
+            buf += fmt.format(' "{a|label}"')
+
+        buf += fmt.format(' <{a|_flags}>')
+
+        if details:
+            buf += fmt.nl('\t{t|broadcast} {t|multicast}') \
+                 + fmt.nl('\t{t|peer} {t|anycast}')
+
+            if self.valid_lifetime:
+                buf += fmt.nl('\t{s|valid-lifetime!k} '\
+                       '{a|valid_lifetime}')
+
+            if self.preferred_lifetime:
+                buf += fmt.nl('\t{s|preferred-lifetime!k} '\
+                       '{a|preferred_lifetime}')
+
+        if stats and (self.create_time or self.last_update):
+            buf += self.nl('\t{s|created!k} {a|create_time}'\
+                   ' {s|last-updated!k} {a|last_update}')
+
+        return buf
diff --git a/python/netlink/route/capi.i b/python/netlink/route/capi.i
new file mode 100644
index 0000000..2d72bd7
--- /dev/null
+++ b/python/netlink/route/capi.i
@@ -0,0 +1,507 @@
+%module capi
+%{
+#include <netlink/route/rtnl.h>
+#include <netlink/route/link.h>
+#include <netlink/route/link/vlan.h>
+#include <netlink/route/link/macvlan.h>
+#include <netlink/route/link/vxlan.h>
+#include <netlink/route/link/bridge.h>
+#include <netlink/route/link/inet.h>
+
+#include <netlink/route/tc.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/class.h>
+#include <netlink/route/classifier.h>
+
+#include <netlink/route/qdisc/htb.h>
+
+#include <netlink/route/addr.h>
+%}
+
+%include <stdint.i>
+%include <cstring.i>
+
+%inline %{
+        struct nl_object *link2obj(struct rtnl_link *link)
+        {
+                return OBJ_CAST(link);
+        }
+
+        struct rtnl_link *obj2link(struct nl_object *obj)
+        {
+                return (struct rtnl_link *) obj;
+        }
+
+        struct rtnl_link *get_from_kernel(struct nl_sock *sk, int ifindex, const char *name)
+        {
+                struct rtnl_link *link;
+                if (rtnl_link_get_kernel(sk, ifindex, name, &link) < 0)
+                        return NULL;
+                return link;
+        }
+
+        uint32_t inet_get_conf(struct rtnl_link *link, const unsigned int id)
+        {
+                uint32_t result;
+
+                if (rtnl_link_inet_get_conf(link, id, &result) < 0)
+                        return 0;
+
+                return result;
+        }
+%};
+
+extern struct nl_object *link2obj(struct rtnl_link *);
+extern struct rtnl_link *obj2link(struct nl_object *);
+
+/* <netlink/route/rtnl.h> */
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *		rtnl_scope2str(int, char *buf, size_t len);
+extern int		rtnl_str2scope(const char *);
+
+/* <netlink/route/link.h> */
+
+extern struct rtnl_link *rtnl_link_alloc(void);
+
+extern struct rtnl_link *rtnl_link_get(struct nl_cache *, int);
+extern struct rtnl_link *rtnl_link_get_by_name(struct nl_cache *, const char *);
+
+extern int rtnl_link_build_add_request(struct rtnl_link *, int, struct nl_msg **);
+extern int rtnl_link_add(struct nl_sock *, struct rtnl_link *, int);
+extern int rtnl_link_build_change_request(struct rtnl_link *, struct rtnl_link *, int, struct nl_msg **);
+extern int rtnl_link_change(struct nl_sock *, struct rtnl_link *, struct rtnl_link *, int);
+
+extern int rtnl_link_build_delete_request(const struct rtnl_link *, struct nl_msg **);
+extern int rtnl_link_delete(struct nl_sock *, const struct rtnl_link *);
+extern int rtnl_link_build_get_request(int, const char *, struct nl_msg **);
+
+extern char *rtnl_link_stat2str(int, char *, size_t);
+extern int rtnl_link_str2stat(const char *);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *rtnl_link_flags2str(int, char *buf, size_t len);
+extern int rtnl_link_str2flags(const char *);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *rtnl_link_operstate2str(uint8_t, char *buf, size_t len);
+extern int rtnl_link_str2operstate(const char *);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *rtnl_link_mode2str(uint8_t, char *buf, size_t len);
+extern int rtnl_link_str2mode(const char *);
+
+extern void rtnl_link_set_qdisc(struct rtnl_link *, const char *);
+extern char *rtnl_link_get_qdisc(struct rtnl_link *);
+
+extern void rtnl_link_set_name(struct rtnl_link *, const char *);
+extern char *rtnl_link_get_name(struct rtnl_link *);
+
+extern void rtnl_link_set_flags(struct rtnl_link *, unsigned int);
+extern void rtnl_link_unset_flags(struct rtnl_link *, unsigned int);
+extern unsigned int rtnl_link_get_flags(struct rtnl_link *);
+
+extern void rtnl_link_set_mtu(struct rtnl_link *, unsigned int);
+extern unsigned int rtnl_link_get_mtu(struct rtnl_link *);
+
+extern void rtnl_link_set_txqlen(struct rtnl_link *, unsigned int);
+extern unsigned int rtnl_link_get_txqlen(struct rtnl_link *);
+
+extern void rtnl_link_set_ifindex(struct rtnl_link *, int);
+extern int rtnl_link_get_ifindex(struct rtnl_link *);
+
+extern void rtnl_link_set_family(struct rtnl_link *, int);
+extern int rtnl_link_get_family(struct rtnl_link *);
+
+extern void rtnl_link_set_arptype(struct rtnl_link *, unsigned int);
+extern unsigned int rtnl_link_get_arptype(struct rtnl_link *);
+
+extern void rtnl_link_set_addr(struct rtnl_link *, struct nl_addr *);
+extern struct nl_addr *rtnl_link_get_addr(struct rtnl_link *);
+
+extern void rtnl_link_set_broadcast(struct rtnl_link *, struct nl_addr *);
+extern struct nl_addr *rtnl_link_get_broadcast(struct rtnl_link *);
+
+extern void rtnl_link_set_link(struct rtnl_link *, int);
+extern int rtnl_link_get_link(struct rtnl_link *);
+
+extern void rtnl_link_set_master(struct rtnl_link *, int);
+extern int rtnl_link_get_master(struct rtnl_link *);
+
+extern void rtnl_link_set_operstate(struct rtnl_link *, uint8_t);
+extern uint8_t rtnl_link_get_operstate(struct rtnl_link *);
+
+extern void rtnl_link_set_linkmode(struct rtnl_link *, uint8_t);
+extern uint8_t rtnl_link_get_linkmode(struct rtnl_link *);
+
+extern const char *rtnl_link_get_ifalias(struct rtnl_link *);
+extern void rtnl_link_set_ifalias(struct rtnl_link *, const char *);
+
+extern int rtnl_link_get_num_vf(struct rtnl_link *, uint32_t *);
+
+extern uint64_t rtnl_link_get_stat(struct rtnl_link *, int);
+extern int rtnl_link_set_stat(struct rtnl_link *, const unsigned int, const uint64_t);
+
+extern int rtnl_link_set_type(struct rtnl_link *, const char *);
+extern char *rtnl_link_get_type(struct rtnl_link *);
+
+extern int rtnl_link_enslave(struct nl_sock * sock, struct rtnl_link * master, struct rtnl_link * slave);
+extern int rtnl_link_release(struct nl_sock * sock, struct rtnl_link * slave);
+
+/* <netlink/route/link/vlan.h> */
+
+struct vlan_map
+{
+	uint32_t		vm_from;
+	uint32_t		vm_to;
+};
+
+#define VLAN_PRIO_MAX 7
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *rtnl_link_vlan_flags2str(int, char *buf, size_t len);
+extern int rtnl_link_vlan_str2flags(const char *);
+
+extern int rtnl_link_vlan_set_id(struct rtnl_link *, int);
+extern int rtnl_link_vlan_get_id(struct rtnl_link *);
+
+extern int rtnl_link_vlan_set_flags(struct rtnl_link *, unsigned int);
+extern int rtnl_link_vlan_unset_flags(struct rtnl_link *, unsigned int);
+extern unsigned int rtnl_link_vlan_get_flags(struct rtnl_link *);
+
+extern int rtnl_link_vlan_set_ingress_map(struct rtnl_link *, int, uint32_t);
+extern uint32_t *rtnl_link_vlan_get_ingress_map(struct rtnl_link *);
+
+extern int rtnl_link_vlan_set_egress_map(struct rtnl_link *, uint32_t, int);
+extern struct vlan_map *rtnl_link_vlan_get_egress_map(struct rtnl_link *, int *);
+
+/* <netlink/route/link/macvlan.h> */
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern struct rtnl_link *rtnl_link_macvlan_alloc(void);
+extern int		rtnl_link_is_macvlan(struct rtnl_link *);
+extern char *		rtnl_link_macvlan_mode2str(int, char *, size_t);
+extern int		rtnl_link_macvlan_str2mode(const char *);
+extern char *		rtnl_link_macvlan_flags2str(int, char *, size_t);
+extern int		rtnl_link_macvlan_str2flags(const char *);
+extern int		rtnl_link_macvlan_set_mode(struct rtnl_link *, uint32_t);
+extern uint32_t		rtnl_link_macvlan_get_mode(struct rtnl_link *);
+extern int		rtnl_link_macvlan_set_flags(struct rtnl_link *, uint16_t);
+extern int		rtnl_link_macvlan_unset_flags(struct rtnl_link *, uint16_t);
+extern uint16_t		rtnl_link_macvlan_get_flags(struct rtnl_link *);
+
+/* <netlink/route/link/vxlan.h> */
+
+#define VXLAN_ID_MAX 16777215
+
+extern struct rtnl_link *rtnl_link_vxlan_alloc(void);
+
+extern int rtnl_link_is_vxlan(struct rtnl_link *);
+
+extern int rtnl_link_vxlan_set_id(struct rtnl_link *, uint32_t);
+extern int rtnl_link_vxlan_get_id(struct rtnl_link *, uint32_t *);
+
+extern int rtnl_link_vxlan_set_group(struct rtnl_link *, struct nl_addr *);
+extern int rtnl_link_vxlan_get_group(struct rtnl_link *, struct nl_addr **);
+
+extern int rtnl_link_vxlan_set_link(struct rtnl_link *, uint32_t);
+extern int rtnl_link_vxlan_get_link(struct rtnl_link *, uint32_t *);
+
+extern int rtnl_link_vxlan_set_local(struct rtnl_link *, struct nl_addr *);
+extern int rtnl_link_vxlan_get_local(struct rtnl_link *, struct nl_addr **);
+
+extern int rtnl_link_vxlan_set_ttl(struct rtnl_link *, uint8_t);
+extern int rtnl_link_vxlan_get_ttl(struct rtnl_link *);
+
+extern int rtnl_link_vxlan_set_tos(struct rtnl_link *, uint8_t);
+extern int rtnl_link_vxlan_get_tos(struct rtnl_link *);
+
+extern int rtnl_link_vxlan_set_learning(struct rtnl_link *, uint8_t);
+extern int rtnl_link_vxlan_get_learning(struct rtnl_link *);
+extern int rtnl_link_vxlan_enable_learning(struct rtnl_link *);
+extern int rtnl_link_vxlan_disable_learning(struct rtnl_link *);
+
+extern int rtnl_link_vxlan_set_ageing(struct rtnl_link *, uint32_t);
+extern int rtnl_link_vxlan_get_ageing(struct rtnl_link *, uint32_t *);
+
+extern int rtnl_link_vxlan_set_limit(struct rtnl_link *, uint32_t);
+extern int rtnl_link_vxlan_get_limit(struct rtnl_link *, uint32_t *);
+
+extern int rtnl_link_vxlan_set_port_range(struct rtnl_link *,
+										  struct ifla_vxlan_port_range *);
+extern int rtnl_link_vxlan_get_port_range(struct rtnl_link *,
+										  struct ifla_vxlan_port_range *);
+
+extern int rtnl_link_vxlan_set_proxy(struct rtnl_link *, uint8_t);
+extern int rtnl_link_vxlan_get_proxy(struct rtnl_link *);
+extern int rtnl_link_vxlan_enable_proxy(struct rtnl_link *);
+extern int rtnl_link_vxlan_disable_proxy(struct rtnl_link *);
+
+extern int rtnl_link_vxlan_set_rsc(struct rtnl_link *, uint8_t);
+extern int rtnl_link_vxlan_get_rsc(struct rtnl_link *);
+extern int rtnl_link_vxlan_enable_rsc(struct rtnl_link *);
+extern int rtnl_link_vxlan_disable_rsc(struct rtnl_link *);
+
+extern int rtnl_link_vxlan_set_l2miss(struct rtnl_link *, uint8_t);
+extern int rtnl_link_vxlan_get_l2miss(struct rtnl_link *);
+extern int rtnl_link_vxlan_enable_l2miss(struct rtnl_link *);
+extern int rtnl_link_vxlan_disable_l2miss(struct rtnl_link *);
+
+extern int rtnl_link_vxlan_set_l3miss(struct rtnl_link *, uint8_t);
+extern int rtnl_link_vxlan_get_l3miss(struct rtnl_link *);
+extern int rtnl_link_vxlan_enable_l3miss(struct rtnl_link *);
+extern int rtnl_link_vxlan_disable_l3miss(struct rtnl_link *);
+
+/* <netlink/route/link/bridge.h> */
+
+enum rtnl_link_bridge_flags {
+	RTNL_BRIDGE_HAIRPIN_MODE	= 0x0001,
+	RTNL_BRIDGE_BPDU_GUARD		= 0x0002,
+	RTNL_BRIDGE_ROOT_BLOCK		= 0x0004,
+	RTNL_BRIDGE_FAST_LEAVE		= 0x0008,
+};
+
+extern int	rtnl_link_is_bridge(struct rtnl_link *);
+extern int	rtnl_link_bridge_has_ext_info(struct rtnl_link *);
+
+extern int	rtnl_link_bridge_set_port_state(struct rtnl_link *, uint8_t );
+extern int	rtnl_link_bridge_get_port_state(struct rtnl_link *);
+
+extern int	rtnl_link_bridge_set_priority(struct rtnl_link *, uint16_t);
+extern int	rtnl_link_bridge_get_priority(struct rtnl_link *);
+
+extern int	rtnl_link_bridge_set_cost(struct rtnl_link *, uint32_t);
+extern int	rtnl_link_bridge_get_cost(struct rtnl_link *, uint32_t *);
+
+extern int	rtnl_link_bridge_unset_flags(struct rtnl_link *, unsigned int);
+extern int	rtnl_link_bridge_set_flags(struct rtnl_link *, unsigned int);
+extern int	rtnl_link_bridge_get_flags(struct rtnl_link *);
+
+extern char * rtnl_link_bridge_flags2str(int, char *, size_t);
+extern int	rtnl_link_bridge_str2flags(const char *);
+
+/* <netlink/route/link/inet.h> */
+%cstring_output_maxsize(char *buf, size_t len)
+extern const char *rtnl_link_inet_devconf2str(int, char *buf, size_t len);
+extern unsigned int rtnl_link_inet_str2devconf(const char *);
+
+extern int rtnl_link_inet_set_conf(struct rtnl_link *, const unsigned int, uint32_t);
+
+/* <netlink/route/tc.h> */
+
+%inline %{
+        uint32_t tc_str2handle(const char *name)
+        {
+                uint32_t result;
+
+                if (rtnl_tc_str2handle(name, &result) < 0)
+                        return 0;
+
+                return result;
+        }
+%};
+
+extern void		rtnl_tc_set_ifindex(struct rtnl_tc *, int);
+extern int		rtnl_tc_get_ifindex(struct rtnl_tc *);
+extern void		rtnl_tc_set_link(struct rtnl_tc *, struct rtnl_link *);
+extern struct rtnl_link *rtnl_tc_get_link(struct rtnl_tc *);
+extern void		rtnl_tc_set_mtu(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_mtu(struct rtnl_tc *);
+extern void		rtnl_tc_set_mpu(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_mpu(struct rtnl_tc *);
+extern void		rtnl_tc_set_overhead(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_overhead(struct rtnl_tc *);
+extern void		rtnl_tc_set_linktype(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_linktype(struct rtnl_tc *);
+extern void		rtnl_tc_set_handle(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_handle(struct rtnl_tc *);
+extern void		rtnl_tc_set_parent(struct rtnl_tc *, uint32_t);
+extern uint32_t		rtnl_tc_get_parent(struct rtnl_tc *);
+extern int		rtnl_tc_set_kind(struct rtnl_tc *, const char *);
+extern char *		rtnl_tc_get_kind(struct rtnl_tc *);
+extern uint64_t		rtnl_tc_get_stat(struct rtnl_tc *, enum rtnl_tc_stat);
+
+extern int		rtnl_tc_calc_txtime(int, int);
+extern int		rtnl_tc_calc_bufsize(int, int);
+extern int		rtnl_tc_calc_cell_log(int);
+
+extern int		rtnl_tc_read_classid_file(void);
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *		rtnl_tc_handle2str(uint32_t, char *buf, size_t len);
+extern int		rtnl_classid_generate(const char *, uint32_t *, uint32_t);
+
+/* <netlink/route/qdisc.h> */
+
+%inline %{
+        struct nl_object *qdisc2obj(struct rtnl_qdisc *qdisc)
+        {
+                return OBJ_CAST(qdisc);
+        }
+
+        struct rtnl_qdisc *obj2qdisc(struct nl_object *obj)
+        {
+                return (struct rtnl_qdisc *) obj;
+        }
+
+        struct nl_object *class2obj(struct rtnl_class *cl)
+        {
+                return OBJ_CAST(cl);
+        }
+
+        struct rtnl_class *obj2class(struct nl_object *obj)
+        {
+                return (struct rtnl_class *) obj;
+        }
+
+        struct nl_object *cls2obj(struct rtnl_cls *cls)
+        {
+                return OBJ_CAST(cls);
+        }
+
+        struct rtnl_cls *obj2cls(struct nl_object *obj)
+        {
+                return (struct rtnl_cls *) obj;
+        }
+
+        struct rtnl_tc *obj2tc(struct nl_object *obj)
+        {
+                return TC_CAST(obj);
+        }
+%};
+extern struct rtnl_qdisc *
+		rtnl_qdisc_alloc(void);
+
+extern struct rtnl_qdisc *
+		rtnl_qdisc_get(struct nl_cache *, int, uint32_t);
+
+extern struct rtnl_qdisc *
+		rtnl_qdisc_get_by_parent(struct nl_cache *, int, uint32_t);
+
+extern int	rtnl_qdisc_build_add_request(struct rtnl_qdisc *, int,
+					     struct nl_msg **);
+extern int	rtnl_qdisc_add(struct nl_sock *, struct rtnl_qdisc *, int);
+
+extern int	rtnl_qdisc_build_update_request(struct rtnl_qdisc *,
+						struct rtnl_qdisc *,
+						int, struct nl_msg **);
+
+extern int	rtnl_qdisc_update(struct nl_sock *, struct rtnl_qdisc *,
+				  struct rtnl_qdisc *, int);
+
+extern int	rtnl_qdisc_build_delete_request(struct rtnl_qdisc *,
+						struct nl_msg **);
+extern int	rtnl_qdisc_delete(struct nl_sock *, struct rtnl_qdisc *);
+
+/* <netlink/route/classifier.h> */
+
+extern struct rtnl_cls *rtnl_cls_alloc(void);
+extern void		rtnl_cls_put(struct rtnl_cls *);
+
+extern int		rtnl_cls_add(struct nl_sock *, struct rtnl_cls *, int);
+
+extern int		rtnl_cls_delete(struct nl_sock *, struct rtnl_cls *,
+					int);
+
+extern void		rtnl_cls_set_prio(struct rtnl_cls *, uint16_t);
+extern uint16_t		rtnl_cls_get_prio(struct rtnl_cls *);
+
+extern void		rtnl_cls_set_protocol(struct rtnl_cls *, uint16_t);
+extern uint16_t		rtnl_cls_get_protocol(struct rtnl_cls *);
+
+/* <netlink/route/qdisc/htb.h> */
+
+extern uint32_t	rtnl_htb_get_rate2quantum(struct rtnl_qdisc *);
+extern int	rtnl_htb_set_rate2quantum(struct rtnl_qdisc *, uint32_t);
+extern uint32_t	rtnl_htb_get_defcls(struct rtnl_qdisc *);
+extern int	rtnl_htb_set_defcls(struct rtnl_qdisc *, uint32_t);
+
+extern uint32_t	rtnl_htb_get_prio(struct rtnl_class *);
+extern int	rtnl_htb_set_prio(struct rtnl_class *, uint32_t);
+extern uint32_t	rtnl_htb_get_rate(struct rtnl_class *);
+extern int	rtnl_htb_set_rate(struct rtnl_class *, uint32_t);
+extern uint32_t	rtnl_htb_get_ceil(struct rtnl_class *);
+extern int	rtnl_htb_set_ceil(struct rtnl_class *, uint32_t);
+extern uint32_t	rtnl_htb_get_rbuffer(struct rtnl_class *);
+extern int	rtnl_htb_set_rbuffer(struct rtnl_class *, uint32_t);
+extern uint32_t	rtnl_htb_get_cbuffer(struct rtnl_class *);
+extern int	rtnl_htb_set_cbuffer(struct rtnl_class *, uint32_t);
+extern uint32_t	rtnl_htb_get_quantum(struct rtnl_class *);
+extern int	rtnl_htb_set_quantum(struct rtnl_class *, uint32_t);
+extern int	rtnl_htb_get_level(struct rtnl_class *);
+
+/* <netlink/route/addr.h> */
+
+%inline %{
+        struct nl_object *addr2obj(struct rtnl_addr *addr)
+        {
+                return OBJ_CAST(addr);
+        }
+
+        struct rtnl_addr *obj2addr(struct nl_object *obj)
+        {
+                return (struct rtnl_addr *) obj;
+        }
+%};
+
+extern struct rtnl_addr *rtnl_addr_alloc(void);
+
+extern struct rtnl_addr *
+		rtnl_addr_get(struct nl_cache *, int, struct nl_addr *);
+
+extern int	rtnl_addr_build_add_request(struct rtnl_addr *, int,
+					    struct nl_msg **);
+extern int	rtnl_addr_add(struct nl_sock *, struct rtnl_addr *, int);
+
+extern int	rtnl_addr_build_delete_request(struct rtnl_addr *, int,
+					       struct nl_msg **);
+extern int	rtnl_addr_delete(struct nl_sock *,
+				 struct rtnl_addr *, int);
+
+%cstring_output_maxsize(char *buf, size_t len)
+extern char *	rtnl_addr_flags2str(int, char *buf, size_t len);
+extern int	rtnl_addr_str2flags(const char *);
+
+extern int	rtnl_addr_set_label(struct rtnl_addr *, const char *);
+extern char *	rtnl_addr_get_label(struct rtnl_addr *);
+
+extern void	rtnl_addr_set_ifindex(struct rtnl_addr *, int);
+extern int	rtnl_addr_get_ifindex(struct rtnl_addr *);
+
+extern void	rtnl_addr_set_link(struct rtnl_addr *, struct rtnl_link *);
+extern struct rtnl_link *
+		rtnl_addr_get_link(struct rtnl_addr *);
+extern void	rtnl_addr_set_family(struct rtnl_addr *, int);
+extern int	rtnl_addr_get_family(struct rtnl_addr *);
+
+extern void	rtnl_addr_set_prefixlen(struct rtnl_addr *, int);
+extern int	rtnl_addr_get_prefixlen(struct rtnl_addr *);
+
+extern void	rtnl_addr_set_scope(struct rtnl_addr *, int);
+extern int	rtnl_addr_get_scope(struct rtnl_addr *);
+
+extern void	rtnl_addr_set_flags(struct rtnl_addr *, unsigned int);
+extern void	rtnl_addr_unset_flags(struct rtnl_addr *, unsigned int);
+extern unsigned int rtnl_addr_get_flags(struct rtnl_addr *);
+
+extern int	rtnl_addr_set_local(struct rtnl_addr *,
+					    struct nl_addr *);
+extern struct nl_addr *rtnl_addr_get_local(struct rtnl_addr *);
+
+extern int	rtnl_addr_set_peer(struct rtnl_addr *, struct nl_addr *);
+extern struct nl_addr *rtnl_addr_get_peer(struct rtnl_addr *);
+
+extern int	rtnl_addr_set_broadcast(struct rtnl_addr *, struct nl_addr *);
+extern struct nl_addr *rtnl_addr_get_broadcast(struct rtnl_addr *);
+
+extern int	rtnl_addr_set_multicast(struct rtnl_addr *, struct nl_addr *);
+extern struct nl_addr *rtnl_addr_get_multicast(struct rtnl_addr *);
+
+extern int	rtnl_addr_set_anycast(struct rtnl_addr *, struct nl_addr *);
+extern struct nl_addr *rtnl_addr_get_anycast(struct rtnl_addr *);
+
+extern uint32_t rtnl_addr_get_valid_lifetime(struct rtnl_addr *);
+extern void	rtnl_addr_set_valid_lifetime(struct rtnl_addr *, uint32_t);
+extern uint32_t rtnl_addr_get_preferred_lifetime(struct rtnl_addr *);
+extern void	rtnl_addr_set_preferred_lifetime(struct rtnl_addr *, uint32_t);
+extern uint32_t rtnl_addr_get_create_time(struct rtnl_addr *);
+extern uint32_t rtnl_addr_get_last_update_time(struct rtnl_addr *);
diff --git a/python/netlink/route/link.py b/python/netlink/route/link.py
new file mode 100644
index 0000000..5ec14b2
--- /dev/null
+++ b/python/netlink/route/link.py
@@ -0,0 +1,551 @@
+#
+# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+#
+
+"""Module providing access to network links
+
+This module provides an interface to view configured network links,
+modify them and to add and delete virtual network links.
+
+The following is a basic example:
+    import netlink.core as netlink
+    import netlink.route.link as link
+
+    sock = netlink.Socket()
+    sock.connect(netlink.NETLINK_ROUTE)
+
+    cache = link.LinkCache()	# create new empty link cache
+    cache.refill(sock)		# fill cache with all configured links
+    eth0 = cache['eth0']		# lookup link "eth0"
+    print eth0			# print basic configuration
+
+The module contains the following public classes:
+
+  - Link -- Represents a network link. Instances can be created directly
+        via the constructor (empty link objects) or via the refill()
+        method of a LinkCache.
+  - LinkCache -- Derived from netlink.Cache, holds any number of
+         network links (Link instances). Main purpose is to keep
+         a local list of all network links configured in the
+         kernel.
+
+The following public functions exist:
+  - get_from_kernel(socket, name)
+
+"""
+
+from __future__ import absolute_import
+
+__version__ = '0.1'
+__all__ = [
+    'LinkCache',
+    'Link',
+    'get_from_kernel',
+]
+
+import socket
+from .. import core as netlink
+from .. import capi as core_capi
+from .  import capi as capi
+from .links  import inet as inet
+from .. import util as util
+
+# Link statistics definitions
+RX_PACKETS = 0
+TX_PACKETS = 1
+RX_BYTES = 2
+TX_BYTES = 3
+RX_ERRORS = 4
+TX_ERRORS = 5
+RX_DROPPED = 6
+TX_DROPPED = 7
+RX_COMPRESSED = 8
+TX_COMPRESSED = 9
+RX_FIFO_ERR = 10
+TX_FIFO_ERR = 11
+RX_LEN_ERR = 12
+RX_OVER_ERR = 13
+RX_CRC_ERR = 14
+RX_FRAME_ERR = 15
+RX_MISSED_ERR = 16
+TX_ABORT_ERR = 17
+TX_CARRIER_ERR = 18
+TX_HBEAT_ERR = 19
+TX_WIN_ERR = 20
+COLLISIONS = 21
+MULTICAST = 22
+IP6_INPKTS = 23
+IP6_INHDRERRORS = 24
+IP6_INTOOBIGERRORS = 25
+IP6_INNOROUTES = 26
+IP6_INADDRERRORS = 27
+IP6_INUNKNOWNPROTOS = 28
+IP6_INTRUNCATEDPKTS = 29
+IP6_INDISCARDS = 30
+IP6_INDELIVERS = 31
+IP6_OUTFORWDATAGRAMS = 32
+IP6_OUTPKTS = 33
+IP6_OUTDISCARDS = 34
+IP6_OUTNOROUTES = 35
+IP6_REASMTIMEOUT = 36
+IP6_REASMREQDS = 37
+IP6_REASMOKS = 38
+IP6_REASMFAILS = 39
+IP6_FRAGOKS = 40
+IP6_FRAGFAILS = 41
+IP6_FRAGCREATES = 42
+IP6_INMCASTPKTS = 43
+IP6_OUTMCASTPKTS = 44
+IP6_INBCASTPKTS = 45
+IP6_OUTBCASTPKTS = 46
+IP6_INOCTETS = 47
+IP6_OUTOCTETS = 48
+IP6_INMCASTOCTETS = 49
+IP6_OUTMCASTOCTETS = 50
+IP6_INBCASTOCTETS = 51
+IP6_OUTBCASTOCTETS = 52
+ICMP6_INMSGS = 53
+ICMP6_INERRORS = 54
+ICMP6_OUTMSGS = 55
+ICMP6_OUTERRORS = 56
+
+class LinkCache(netlink.Cache):
+    """Cache of network links"""
+
+    def __init__(self, family=socket.AF_UNSPEC, cache=None):
+        if not cache:
+            cache = self._alloc_cache_name('route/link')
+
+        self._info_module = None
+        self._protocol = netlink.NETLINK_ROUTE
+        self._nl_cache = cache
+        self._set_arg1(family)
+
+    def __getitem__(self, key):
+        if type(key) is int:
+            link = capi.rtnl_link_get(self._nl_cache, key)
+        else:
+            link = capi.rtnl_link_get_by_name(self._nl_cache, key)
+
+        if link is None:
+            raise KeyError()
+        else:
+            return Link.from_capi(link)
+
+    @staticmethod
+    def _new_object(obj):
+        return Link(obj)
+
+    def _new_cache(self, cache):
+        return LinkCache(family=self.arg1, cache=cache)
+
+class Link(netlink.Object):
+    """Network link"""
+
+    def __init__(self, obj=None):
+        netlink.Object.__init__(self, 'route/link', 'link', obj)
+        self._rtnl_link = self._obj2type(self._nl_object)
+
+        if self.type:
+            self._module_lookup('netlink.route.links.' + self.type)
+
+        self.inet = inet.InetLink(self)
+        self.af = {'inet' : self.inet }
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, tb):
+        if exc_type is None:
+            self.change()
+        else:
+            return false
+
+    @classmethod
+    def from_capi(cls, obj):
+        return cls(capi.link2obj(obj))
+
+    @staticmethod
+    def _obj2type(obj):
+        return capi.obj2link(obj)
+
+    def __cmp__(self, other):
+        return self.ifindex - other.ifindex
+
+    @staticmethod
+    def _new_instance(obj):
+        if not obj:
+            raise ValueError()
+
+        return Link(obj)
+
+    @property
+    @netlink.nlattr(type=int, immutable=True, fmt=util.num)
+    def ifindex(self):
+        """interface index"""
+        return capi.rtnl_link_get_ifindex(self._rtnl_link)
+
+    @ifindex.setter
+    def ifindex(self, value):
+        capi.rtnl_link_set_ifindex(self._rtnl_link, int(value))
+
+        # ifindex is immutable but we assume that if _orig does not
+        # have an ifindex specified, it was meant to be given here
+        if capi.rtnl_link_get_ifindex(self._orig) == 0:
+            capi.rtnl_link_set_ifindex(self._orig, int(value))
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.bold)
+    def name(self):
+        """Name of link"""
+        return capi.rtnl_link_get_name(self._rtnl_link)
+
+    @name.setter
+    def name(self, value):
+        capi.rtnl_link_set_name(self._rtnl_link, value)
+
+        # name is the secondary identifier, if _orig does not have
+        # the name specified yet, assume it was meant to be specified
+        # here. ifindex will always take priority, therefore if ifindex
+        # is specified as well, this will be ignored automatically.
+        if capi.rtnl_link_get_name(self._orig) is None:
+            capi.rtnl_link_set_name(self._orig, value)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.string)
+    def flags(self):
+        """Flags
+        Setting this property will *Not* reset flags to value you supply in
+        Examples:
+        link.flags = '+xxx' # add xxx flag
+        link.flags = 'xxx'  # exactly the same
+        link.flags = '-xxx' # remove xxx flag
+        link.flags = [ '+xxx', '-yyy' ] # list operation
+        """
+        flags = capi.rtnl_link_get_flags(self._rtnl_link)
+        return capi.rtnl_link_flags2str(flags, 256)[0].split(',')
+
+    def _set_flag(self, flag):
+        if flag.startswith('-'):
+            i = capi.rtnl_link_str2flags(flag[1:])
+            capi.rtnl_link_unset_flags(self._rtnl_link, i)
+        elif flag.startswith('+'):
+            i = capi.rtnl_link_str2flags(flag[1:])
+            capi.rtnl_link_set_flags(self._rtnl_link, i)
+        else:
+            i = capi.rtnl_link_str2flags(flag)
+            capi.rtnl_link_set_flags(self._rtnl_link, i)
+
+    @flags.setter
+    def flags(self, value):
+        if not (type(value) is str):
+            for flag in value:
+                self._set_flag(flag)
+        else:
+            self._set_flag(value)
+
+    @property
+    @netlink.nlattr(type=int, fmt=util.num)
+    def mtu(self):
+        """Maximum Transmission Unit"""
+        return capi.rtnl_link_get_mtu(self._rtnl_link)
+
+    @mtu.setter
+    def mtu(self, value):
+        capi.rtnl_link_set_mtu(self._rtnl_link, int(value))
+
+    @property
+    @netlink.nlattr(type=int, immutable=True, fmt=util.num)
+    def family(self):
+        """Address family"""
+        return capi.rtnl_link_get_family(self._rtnl_link)
+
+    @family.setter
+    def family(self, value):
+        capi.rtnl_link_set_family(self._rtnl_link, value)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.addr)
+    def address(self):
+        """Hardware address (MAC address)"""
+        a = capi.rtnl_link_get_addr(self._rtnl_link)
+        return netlink.AbstractAddress(a)
+
+    @address.setter
+    def address(self, value):
+        capi.rtnl_link_set_addr(self._rtnl_link, value._addr)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.addr)
+    def broadcast(self):
+        """Hardware broadcast address"""
+        a = capi.rtnl_link_get_broadcast(self._rtnl_link)
+        return netlink.AbstractAddress(a)
+
+    @broadcast.setter
+    def broadcast(self, value):
+        capi.rtnl_link_set_broadcast(self._rtnl_link, value._addr)
+
+    @property
+    @netlink.nlattr(type=str, immutable=True, fmt=util.string)
+    def qdisc(self):
+        """Name of qdisc (cannot be changed)"""
+        return capi.rtnl_link_get_qdisc(self._rtnl_link)
+
+    @qdisc.setter
+    def qdisc(self, value):
+        capi.rtnl_link_set_qdisc(self._rtnl_link, value)
+
+    @property
+    @netlink.nlattr(type=int, fmt=util.num)
+    def txqlen(self):
+        """Length of transmit queue"""
+        return capi.rtnl_link_get_txqlen(self._rtnl_link)
+
+    @txqlen.setter
+    def txqlen(self, value):
+        capi.rtnl_link_set_txqlen(self._rtnl_link, int(value))
+
+    @property
+    @netlink.nlattr(type=str, immutable=True, fmt=util.string)
+    def arptype(self):
+        """Type of link (cannot be changed)"""
+        type_ = capi.rtnl_link_get_arptype(self._rtnl_link)
+        return core_capi.nl_llproto2str(type_, 64)[0]
+
+    @arptype.setter
+    def arptype(self, value):
+        i = core_capi.nl_str2llproto(value)
+        capi.rtnl_link_set_arptype(self._rtnl_link, i)
+
+    @property
+    @netlink.nlattr(type=str, immutable=True, fmt=util.string, title='state')
+    def operstate(self):
+        """Operational status"""
+        operstate = capi.rtnl_link_get_operstate(self._rtnl_link)
+        return capi.rtnl_link_operstate2str(operstate, 32)[0]
+
+    @operstate.setter
+    def operstate(self, value):
+        i = capi.rtnl_link_str2operstate(value)
+        capi.rtnl_link_set_operstate(self._rtnl_link, i)
+
+    @property
+    @netlink.nlattr(type=str, immutable=True, fmt=util.string)
+    def mode(self):
+        """Link mode"""
+        mode = capi.rtnl_link_get_linkmode(self._rtnl_link)
+        return capi.rtnl_link_mode2str(mode, 32)[0]
+
+    @mode.setter
+    def mode(self, value):
+        i = capi.rtnl_link_str2mode(value)
+        capi.rtnl_link_set_linkmode(self._rtnl_link, i)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.string)
+    def alias(self):
+        """Interface alias (SNMP)"""
+        return capi.rtnl_link_get_ifalias(self._rtnl_link)
+
+    @alias.setter
+    def alias(self, value):
+        capi.rtnl_link_set_ifalias(self._rtnl_link, value)
+
+    @property
+    @netlink.nlattr(type=str, fmt=util.string)
+    def type(self):
+        """Link type"""
+        return capi.rtnl_link_get_type(self._rtnl_link)
+
+    @type.setter
+    def type(self, value):
+        if capi.rtnl_link_set_type(self._rtnl_link, value) < 0:
+            raise NameError('unknown info type')
+
+        self._module_lookup('netlink.route.links.' + value)
+
+    def get_stat(self, stat):
+        """Retrieve statistical information"""
+        if type(stat) is str:
+            stat = capi.rtnl_link_str2stat(stat)
+            if stat < 0:
+                raise NameError('unknown name of statistic')
+
+        return capi.rtnl_link_get_stat(self._rtnl_link, stat)
+
+    def enslave(self, slave, sock=None):
+        if not sock:
+            sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
+
+        return capi.rtnl_link_enslave(sock._sock, self._rtnl_link, slave._rtnl_link)
+
+    def release(self, slave, sock=None):
+        if not sock:
+            sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
+
+        return capi.rtnl_link_release(sock._sock, self._rtnl_link, slave._rtnl_link)
+
+    def add(self, sock=None, flags=None):
+        if not sock:
+            sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
+
+        if not flags:
+            flags = netlink.NLM_F_CREATE
+
+        ret = capi.rtnl_link_add(sock._sock, self._rtnl_link, flags)
+        if ret < 0:
+            raise netlink.KernelError(ret)
+
+    def change(self, sock=None, flags=0):
+        """Commit changes made to the link object"""
+        if sock is None:
+            sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
+
+        if not self._orig:
+            raise netlink.NetlinkError('Original link not available')
+        ret = capi.rtnl_link_change(sock._sock, self._orig, self._rtnl_link, flags)
+        if ret < 0:
+            raise netlink.KernelError(ret)
+
+    def delete(self, sock=None):
+        """Attempt to delete this link in the kernel"""
+        if sock is None:
+            sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
+
+        ret = capi.rtnl_link_delete(sock._sock, self._rtnl_link)
+        if ret < 0:
+            raise netlink.KernelError(ret)
+
+    ###################################################################
+    # private properties
+    #
+    # Used for formatting output. USE AT OWN RISK
+    @property
+    def _state(self):
+        if 'up' in self.flags:
+            buf = util.good('up')
+            if 'lowerup' not in self.flags:
+                buf += ' ' + util.bad('no-carrier')
+        else:
+            buf = util.bad('down')
+        return buf
+
+    @property
+    def _brief(self):
+        return self._module_brief() + self._foreach_af('brief')
+
+    @property
+    def _flags(self):
+        ignore = [
+            'up',
+            'running',
+            'lowerup',
+        ]
+        return ','.join([flag for flag in self.flags if flag not in ignore])
+
+    def _foreach_af(self, name, args=None):
+        buf = ''
+        for af in self.af:
+            try:
+                func = getattr(self.af[af], name)
+                s = str(func(args))
+                if len(s) > 0:
+                    buf += ' ' + s
+            except AttributeError:
+                pass
+        return buf
+
+    def format(self, details=False, stats=False, indent=''):
+        """Return link as formatted text"""
+        fmt = util.MyFormatter(self, indent)
+
+        buf = fmt.format('{a|ifindex} {a|name} {a|arptype} {a|address} '\
+                 '{a|_state} <{a|_flags}> {a|_brief}')
+
+        if details:
+            buf += fmt.nl('\t{t|mtu} {t|txqlen} {t|weight} '\
+                      '{t|qdisc} {t|operstate}')
+            buf += fmt.nl('\t{t|broadcast} {t|alias}')
+
+            buf += self._foreach_af('details', fmt)
+
+        if stats:
+            l = [['Packets', RX_PACKETS, TX_PACKETS],
+                 ['Bytes', RX_BYTES, TX_BYTES],
+                 ['Errors', RX_ERRORS, TX_ERRORS],
+                 ['Dropped', RX_DROPPED, TX_DROPPED],
+                 ['Compressed', RX_COMPRESSED, TX_COMPRESSED],
+                 ['FIFO Errors', RX_FIFO_ERR, TX_FIFO_ERR],
+                 ['Length Errors', RX_LEN_ERR, None],
+                 ['Over Errors', RX_OVER_ERR, None],
+                 ['CRC Errors', RX_CRC_ERR, None],
+                 ['Frame Errors', RX_FRAME_ERR, None],
+                 ['Missed Errors', RX_MISSED_ERR, None],
+                 ['Abort Errors', None, TX_ABORT_ERR],
+                 ['Carrier Errors', None, TX_CARRIER_ERR],
+                 ['Heartbeat Errors', None, TX_HBEAT_ERR],
+                 ['Window Errors', None, TX_WIN_ERR],
+                 ['Collisions', None, COLLISIONS],
+                 ['Multicast', None, MULTICAST],
+                 ['', None, None],
+                 ['Ipv6:', None, None],
+                 ['Packets', IP6_INPKTS, IP6_OUTPKTS],
+                 ['Bytes', IP6_INOCTETS, IP6_OUTOCTETS],
+                 ['Discards', IP6_INDISCARDS, IP6_OUTDISCARDS],
+                 ['Multicast Packets', IP6_INMCASTPKTS, IP6_OUTMCASTPKTS],
+                 ['Multicast Bytes', IP6_INMCASTOCTETS, IP6_OUTMCASTOCTETS],
+                 ['Broadcast Packets', IP6_INBCASTPKTS, IP6_OUTBCASTPKTS],
+                 ['Broadcast Bytes', IP6_INBCASTOCTETS, IP6_OUTBCASTOCTETS],
+                 ['Delivers', IP6_INDELIVERS, None],
+                 ['Forwarded', None, IP6_OUTFORWDATAGRAMS],
+                 ['No Routes', IP6_INNOROUTES, IP6_OUTNOROUTES],
+                 ['Header Errors', IP6_INHDRERRORS, None],
+                 ['Too Big Errors', IP6_INTOOBIGERRORS, None],
+                 ['Address Errors', IP6_INADDRERRORS, None],
+                 ['Unknown Protocol', IP6_INUNKNOWNPROTOS, None],
+                 ['Truncated Packets', IP6_INTRUNCATEDPKTS, None],
+                 ['Reasm Timeouts', IP6_REASMTIMEOUT, None],
+                 ['Reasm Requests', IP6_REASMREQDS, None],
+                 ['Reasm Failures', IP6_REASMFAILS, None],
+                 ['Reasm OK', IP6_REASMOKS, None],
+                 ['Frag Created', None, IP6_FRAGCREATES],
+                 ['Frag Failures', None, IP6_FRAGFAILS],
+                 ['Frag OK', None, IP6_FRAGOKS],
+                 ['', None, None],
+                 ['ICMPv6:', None, None],
+                 ['Messages', ICMP6_INMSGS, ICMP6_OUTMSGS],
+                 ['Errors', ICMP6_INERRORS, ICMP6_OUTERRORS]]
+
+            buf += '\n\t%s%s%s%s\n' % (33 * ' ', util.title('RX'),
+                           15 * ' ', util.title('TX'))
+
+            for row in l:
+                row[0] = util.kw(row[0])
+                row[1] = self.get_stat(row[1]) if row[1] else ''
+                row[2] = self.get_stat(row[2]) if row[2] else ''
+                buf += '\t{0[0]:27} {0[1]:>16} {0[2]:>16}\n'.format(row)
+
+            buf += self._foreach_af('stats')
+
+        return buf
+
+def get(name, sock=None):
+    """Lookup Link object directly from kernel"""
+    if not name:
+        raise ValueError()
+
+    if not sock:
+        sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
+
+    link = capi.get_from_kernel(sock._sock, 0, name)
+    if not link:
+        return None
+
+    return Link.from_capi(link)
+
+_link_cache = LinkCache()
+
+def resolve(name):
+    _link_cache.refill()
+    return _link_cache[name]
diff --git a/python/netlink/route/links/__init__.py b/python/netlink/route/links/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/netlink/route/links/__init__.py
diff --git a/python/netlink/route/links/bridge.py b/python/netlink/route/links/bridge.py
new file mode 100644
index 0000000..549b092
--- /dev/null
+++ b/python/netlink/route/links/bridge.py
@@ -0,0 +1,119 @@
+#
+# Copyright (c) 2013 Nicolas PLANEL <nicolas.planel@enovance.com>
+#
+
+"""BRIDGE network link
+
+"""
+
+from __future__ import absolute_import
+
+from ... import core as netlink
+from ..  import capi as capi
+
+class BRIDGELink(object):
+    def __init__(self, link):
+        self._link = link
+        self._has_ext_info = capi.rtnl_link_bridge_has_ext_info(self._link)
+        self._port_state_values = ['disabled','listening','learning','forwarding','blocking']
+
+    def bridge_assert_ext_info(self):
+        if self._has_ext_info == False:
+            print """
+            Please update your kernel to be able to call this method.
+            Your current kernel bridge version is too old to support this extention.
+            """
+            raise RuntimeWarning()
+
+    def port_state2str(self, state):
+        return self._port_state_values[state]
+
+    def str2port_state(self, str):
+        for value, port in enumerate(self._port_state_values):
+            if str.lower() == port:
+                return value
+        raise ValueError()
+
+    @property
+    @netlink.nlattr(type=int)
+    def port_state(self):
+        """bridge state :
+        %s
+        """ % (self.port_state)
+        return capi.rtnl_link_bridge_get_state(self._link)
+
+    @port_state.setter
+    def port_state(self, state):
+        capi.rtnl_link_bridge_set_state(self._link, int(state))
+
+    @property
+    @netlink.nlattr(type=int)
+    def priority(self):
+        """bridge prio
+        """
+        bridge_assert_ext_info()
+        return capi.rtnl_link_bridge_get_prio(self._link)
+
+    @priority.setter
+    def priority(self, prio):
+        bridge_assert_ext_info()
+        if prio < 0 or prio >= 2**16:
+            raise ValueError()
+        capi.rtnl_link_bridge_set_prio(self._link, int(prio))
+
+    @property
+    @netlink.nlattr(type=int)
+    def cost(self):
+        """bridge prio
+        """
+        bridge_assert_ext_info()
+        return capi.rtnl_link_bridge_get_cost(self._link)
+
+    @cost.setter
+    def cost(self, cost):
+        bridge_assert_ext_info()
+        if cost < 0 or cost >= 2**32:
+            raise ValueError()
+        capi.rtnl_link_bridge_set_cost(self._link, int(cost))
+
+    @property
+    @netlink.nlattr(type=str)
+    def flags(self):
+        """ BRIDGE flags
+        Setting this property will *Not* reset flags to value you supply in
+        Examples:
+        link.flags = '+xxx' # add xxx flag
+        link.flags = 'xxx'  # exactly the same
+        link.flags = '-xxx' # remove xxx flag
+        link.flags = [ '+xxx', '-yyy' ] # list operation
+        """
+        self.bridge_assert_ext_info()
+        flags = capi.rtnl_link_bridge_get_flags(self._link)
+        return capi.rtnl_link_bridge_flags2str(flags, 256)[0].split(',')
+
+    def _set_flag(self, flag):
+        if flag.startswith('-'):
+            i = capi.rtnl_link_bridge_str2flags(flag[1:])
+            capi.rtnl_link_bridge_unset_flags(self._link, i)
+        elif flag.startswith('+'):
+            i = capi.rtnl_link_bridge_str2flags(flag[1:])
+            capi.rtnl_link_bridge_set_flags(self._link, i)
+        else:
+            i = capi.rtnl_link_bridge_str2flags(flag)
+            capi.rtnl_link_bridge_set_flags(self._link, i)
+
+    @flags.setter
+    def flags(self, value):
+        self.bridge_assert_ext_info()
+        if type(value) is list:
+            for flag in value:
+                self._set_flag(flag)
+        else:
+            self._set_flag(value)
+
+    def brief(self):
+        return 'bridge-has-ext-info {0}'.format(self._has_ext_info)
+
+def init(link):
+    link.bridge = BRIDGELink(link._rtnl_link)
+    return link.bridge
diff --git a/python/netlink/route/links/dummy.py b/python/netlink/route/links/dummy.py
new file mode 100644
index 0000000..e9491cc
--- /dev/null
+++ b/python/netlink/route/links/dummy.py
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+#
+
+"""Dummy
+
+"""
+from __future__ import absolute_import
+
+__version__ = '1.0'
+__all__ = [
+    'init',
+]
+
+
+class DummyLink(object):
+    def __init__(self, link):
+        self._rtnl_link = link
+
+    @staticmethod
+    def brief():
+        return 'dummy'
+
+def init(link):
+    link.dummy = DummyLink(link._rtnl_link)
+    return link.dummy
diff --git a/python/netlink/route/links/inet.py b/python/netlink/route/links/inet.py
new file mode 100644
index 0000000..f5f45cb
--- /dev/null
+++ b/python/netlink/route/links/inet.py
@@ -0,0 +1,158 @@
+#
+# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+#
+
+"""IPv4
+
+"""
+
+from __future__ import absolute_import
+
+__all__ = [
+    '',
+]
+
+from ... import core as netlink
+from ..  import capi as capi
+from ... import util as util
+DEVCONF_FORWARDING = 1
+DEVCONF_MC_FORWARDING = 2
+DEVCONF_PROXY_ARP = 3
+DEVCONF_ACCEPT_REDIRECTS = 4
+DEVCONF_SECURE_REDIRECTS = 5
+DEVCONF_SEND_REDIRECTS = 6
+DEVCONF_SHARED_MEDIA = 7
+DEVCONF_RP_FILTER = 8
+DEVCONF_ACCEPT_SOURCE_ROUTE = 9
+DEVCONF_BOOTP_RELAY = 10
+DEVCONF_LOG_MARTIANS = 11
+DEVCONF_TAG = 12
+DEVCONF_ARPFILTER = 13
+DEVCONF_MEDIUM_ID = 14
+DEVCONF_NOXFRM = 15
+DEVCONF_NOPOLICY = 16
+DEVCONF_FORCE_IGMP_VERSION = 17
+DEVCONF_ARP_ANNOUNCE = 18
+DEVCONF_ARP_IGNORE = 19
+DEVCONF_PROMOTE_SECONDARIES = 20
+DEVCONF_ARP_ACCEPT = 21
+DEVCONF_ARP_NOTIFY = 22
+DEVCONF_ACCEPT_LOCAL = 23
+DEVCONF_SRC_VMARK = 24
+DEVCONF_PROXY_ARP_PVLAN = 25
+DEVCONF_MAX = DEVCONF_PROXY_ARP_PVLAN
+
+def _resolve(id):
+    if type(id) is str:
+        id = capi.rtnl_link_inet_str2devconf(id)[0]
+        if id < 0:
+            raise NameError('unknown configuration id')
+    return id
+
+class InetLink(object):
+    def __init__(self, link):
+        self._link = link
+
+    def details(self, fmt):
+        buf = fmt.nl('\n\t{0}\n\t'.format(util.title('Configuration:')))
+
+        for i in range(DEVCONF_FORWARDING, DEVCONF_MAX+1):
+            if i & 1 and i > 1:
+                buf += fmt.nl('\t')
+            txt = util.kw(capi.rtnl_link_inet_devconf2str(i, 32)[0])
+            buf += fmt.format('{0:28s} {1:12}  ', txt,
+                      self.get_conf(i))
+
+
+        return buf
+
+    def get_conf(self, id):
+        return capi.inet_get_conf(self._link._rtnl_link, _resolve(id))
+
+    def set_conf(self, id, value):
+        return capi.rtnl_link_inet_set_conf(self._link._rtnl_link,
+                        _resolve(id), int(value))
+
+    @property
+    @netlink.nlattr(type=bool, fmt=util.boolean)
+    def forwarding(self):
+        return bool(self.get_conf(DEVCONF_FORWARDING))
+
+    @forwarding.setter
+    def forwarding(self, value):
+        self.set_conf(DEVCONF_FORWARDING, int(value))
+
+
+    @property
+    @netlink.nlattr(type=bool, fmt=util.boolean)
+    def mc_forwarding(self):
+        return bool(self.get_conf(DEVCONF_MC_FORWARDING))
+
+    @mc_forwarding.setter
+    def mc_forwarding(self, value):
+        self.set_conf(DEVCONF_MC_FORWARDING, int(value))
+
+
+    @property
+    @netlink.nlattr(type=bool, fmt=util.boolean)
+    def proxy_arp(self):
+        return bool(self.get_conf(DEVCONF_PROXY_ARP))
+
+    @proxy_arp.setter
+    def proxy_arp(self, value):
+        self.set_conf(DEVCONF_PROXY_ARP, int(value))
+
+    @property
+    @netlink.nlattr(type=bool, fmt=util.boolean)
+    def accept_redirects(self):
+        return bool(self.get_conf(DEVCONF_ACCEPT_REDIRECTS))
+
+    @accept_redirects.setter
+    def accept_redirects(self, value):
+        self.set_conf(DEVCONF_ACCEPT_REDIRECTS, int(value))
+
+    @property
+    @netlink.nlattr(type=bool, fmt=util.boolean)
+    def secure_redirects(self):
+        return bool(self.get_conf(DEVCONF_SECURE_REDIRECTS))
+
+    @secure_redirects.setter
+    def secure_redirects(self, value):
+        self.set_conf(DEVCONF_SECURE_REDIRECTS, int(value))
+
+    @property
+    @netlink.nlattr(type=bool, fmt=util.boolean)
+    def send_redirects(self):
+        return bool(self.get_conf(DEVCONF_SEND_REDIRECTS))
+
+    @send_redirects.setter
+    def send_redirects(self, value):
+        self.set_conf(DEVCONF_SEND_REDIRECTS, int(value))
+
+    @property
+    @netlink.nlattr(type=bool, fmt=util.boolean)
+    def shared_media(self):
+        return bool(self.get_conf(DEVCONF_SHARED_MEDIA))
+
+    @shared_media.setter
+    def shared_media(self, value):
+        self.set_conf(DEVCONF_SHARED_MEDIA, int(value))
+
+#	IPV4_DEVCONF_RP_FILTER,
+#	IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE,
+#	IPV4_DEVCONF_BOOTP_RELAY,
+#	IPV4_DEVCONF_LOG_MARTIANS,
+#	IPV4_DEVCONF_TAG,
+#	IPV4_DEVCONF_ARPFILTER,
+#	IPV4_DEVCONF_MEDIUM_ID,
+#	IPV4_DEVCONF_NOXFRM,
+#	IPV4_DEVCONF_NOPOLICY,
+#	IPV4_DEVCONF_FORCE_IGMP_VERSION,
+#	IPV4_DEVCONF_ARP_ANNOUNCE,
+#	IPV4_DEVCONF_ARP_IGNORE,
+#	IPV4_DEVCONF_PROMOTE_SECONDARIES,
+#	IPV4_DEVCONF_ARP_ACCEPT,
+#	IPV4_DEVCONF_ARP_NOTIFY,
+#	IPV4_DEVCONF_ACCEPT_LOCAL,
+#	IPV4_DEVCONF_SRC_VMARK,
+#	IPV4_DEVCONF_PROXY_ARP_PVLAN,
diff --git a/python/netlink/route/links/vlan.py b/python/netlink/route/links/vlan.py
new file mode 100644
index 0000000..0ba3781
--- /dev/null
+++ b/python/netlink/route/links/vlan.py
@@ -0,0 +1,71 @@
+#
+# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+#
+
+"""VLAN network link
+
+"""
+
+from __future__ import absolute_import
+
+
+from ... import core as netlink
+from ..  import capi as capi
+class VLANLink(object):
+    def __init__(self, link):
+        self._link = link
+
+    @property
+    @netlink.nlattr(type=int)
+    def id(self):
+        """vlan identifier"""
+        return capi.rtnl_link_vlan_get_id(self._link)
+
+    @id.setter
+    def id(self, value):
+        capi.rtnl_link_vlan_set_id(self._link, int(value))
+
+    @property
+    @netlink.nlattr(type=str)
+    def flags(self):
+        """ VLAN flags
+        Setting this property will *Not* reset flags to value you supply in
+        Examples:
+        link.flags = '+xxx' # add xxx flag
+        link.flags = 'xxx'  # exactly the same
+        link.flags = '-xxx' # remove xxx flag
+        link.flags = [ '+xxx', '-yyy' ] # list operation
+        """
+        flags = capi.rtnl_link_vlan_get_flags(self._link)
+        return capi.rtnl_link_vlan_flags2str(flags, 256)[0].split(',')
+
+    def _set_flag(self, flag):
+        if flag.startswith('-'):
+            i = capi.rtnl_link_vlan_str2flags(flag[1:])
+            capi.rtnl_link_vlan_unset_flags(self._link, i)
+        elif flag.startswith('+'):
+            i = capi.rtnl_link_vlan_str2flags(flag[1:])
+            capi.rtnl_link_vlan_set_flags(self._link, i)
+        else:
+            i = capi.rtnl_link_vlan_str2flags(flag)
+            capi.rtnl_link_vlan_set_flags(self._link, i)
+
+    @flags.setter
+    def flags(self, value):
+        if type(value) is list:
+            for flag in value:
+                self._set_flag(flag)
+        else:
+            self._set_flag(value)
+
+    ###################################################################
+    # TODO:
+    #   - ingress map
+    #   - egress map
+
+    def brief(self):
+        return 'vlan-id {0}'.format(self.id)
+
+def init(link):
+    link.vlan = VLANLink(link._rtnl_link)
+    return link.vlan
diff --git a/python/netlink/route/qdisc/__init__.py b/python/netlink/route/qdisc/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/netlink/route/qdisc/__init__.py
diff --git a/python/netlink/route/qdisc/htb.py b/python/netlink/route/qdisc/htb.py
new file mode 100644
index 0000000..d051c8d
--- /dev/null
+++ b/python/netlink/route/qdisc/htb.py
@@ -0,0 +1,145 @@
+#
+# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+#
+
+"""HTB qdisc
+
+"""
+
+from __future__ import absolute_import
+
+from ... import core as netlink
+from ... import util as util
+from ..  import capi as capi
+from ..  import tc as tc
+
+class HTBQdisc(object):
+    def __init__(self, qdisc):
+        self._qdisc = qdisc
+
+    @property
+    @netlink.nlattr(type=int)
+    def default_class(self):
+        return tc.Handle(capi.rtnl_htb_get_defcls(self._qdisc._rtnl_qdisc))
+
+    @default_class.setter
+    def default_class(self, value):
+        capi.rtnl_htb_set_defcls(self._qdisc._rtnl_qdisc, int(value))
+
+    @property
+    @netlink.nlattr('r2q', type=int)
+    def r2q(self):
+        return capi.rtnl_htb_get_rate2quantum(self._qdisc._rtnl_qdisc)
+
+    @r2q.setter
+    def r2q(self, value):
+        capi.rtnl_htb_get_rate2quantum(self._qdisc._rtnl_qdisc,
+                           int(value))
+
+    def brief(self):
+        fmt = util.MyFormatter(self)
+
+        ret = ' {s|default-class!k} {a|default_class}'
+
+        if self.r2q:
+            ret += ' {s|r2q!k} {a|r2q}'
+
+        return fmt.format(ret)
+
+class HTBClass(object):
+    def __init__(self, cl):
+        self._class = cl
+
+    @property
+    @netlink.nlattr(type=str)
+    def rate(self):
+        rate = capi.rtnl_htb_get_rate(self._class._rtnl_class)
+        return util.Rate(rate)
+
+    @rate.setter
+    def rate(self, value):
+        capi.rtnl_htb_set_rate(self._class._rtnl_class, int(value))
+
+    @property
+    @netlink.nlattr(type=str)
+    def ceil(self):
+        ceil = capi.rtnl_htb_get_ceil(self._class._rtnl_class)
+        return util.Rate(ceil)
+
+    @ceil.setter
+    def ceil(self, value):
+        capi.rtnl_htb_set_ceil(self._class._rtnl_class, int(value))
+
+    @property
+    @netlink.nlattr(type=str)
+    def burst(self):
+        burst = capi.rtnl_htb_get_rbuffer(self._class._rtnl_class)
+        return util.Size(burst)
+
+    @burst.setter
+    def burst(self, value):
+        capi.rtnl_htb_set_rbuffer(self._class._rtnl_class, int(value))
+
+    @property
+    @netlink.nlattr(type=str)
+    def ceil_burst(self):
+        burst = capi.rtnl_htb_get_cbuffer(self._class._rtnl_class)
+        return util.Size(burst)
+
+    @ceil_burst.setter
+    def ceil_burst(self, value):
+        capi.rtnl_htb_set_cbuffer(self._class._rtnl_class, int(value))
+
+    @property
+    @netlink.nlattr(type=int)
+    def prio(self):
+        return capi.rtnl_htb_get_prio(self._class._rtnl_class)
+
+    @prio.setter
+    def prio(self, value):
+        capi.rtnl_htb_set_prio(self._class._rtnl_class, int(value))
+
+    @property
+    @netlink.nlattr(type=int)
+    def quantum(self):
+        return capi.rtnl_htb_get_quantum(self._class._rtnl_class)
+
+    @quantum.setter
+    def quantum(self, value):
+        capi.rtnl_htb_set_quantum(self._class._rtnl_class, int(value))
+
+    @property
+    @netlink.nlattr(type=int)
+    def level(self):
+        return capi.rtnl_htb_get_level(self._class._rtnl_class)
+
+    @level.setter
+    def level(self, value):
+        capi.rtnl_htb_set_level(self._class._rtnl_class, int(value))
+
+    def brief(self):
+        fmt = util.MyFormatter(self)
+
+        ret = ' {t|prio} {t|rate}'
+
+        if self.rate != self.ceil:
+            ret += ' {s|borrow-up-to!k} {a|ceil}'
+
+        ret += ' {t|burst}'
+
+        return fmt.format(ret)
+
+    def details(self):
+        fmt = util.MyFormatter(self)
+
+        return fmt.nl('\t{t|level} {t|quantum}')
+
+def init_qdisc(qdisc):
+    qdisc.htb = HTBQdisc(qdisc)
+    return qdisc.htb
+
+def init_class(cl):
+    cl.htb = HTBClass(cl)
+    return cl.htb
+
+#extern void rtnl_htb_set_quantum(struct rtnl_class *, uint32_t quantum);
diff --git a/python/netlink/route/tc.py b/python/netlink/route/tc.py
new file mode 100644
index 0000000..0689b71
--- /dev/null
+++ b/python/netlink/route/tc.py
@@ -0,0 +1,595 @@
+#
+# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+#
+from __future__ import absolute_import
+
+__all__ = [
+    'TcCache',
+    'Tc',
+    'QdiscCache',
+    'Qdisc',
+    'TcClassCache',
+    'TcClass',
+]
+
+from .. import core as netlink
+from .  import capi as capi
+from .. import util as util
+from .  import link as Link
+
+TC_PACKETS = 0
+TC_BYTES = 1
+TC_RATE_BPS = 2
+TC_RATE_PPS = 3
+TC_QLEN = 4
+TC_BACKLOG = 5
+TC_DROPS = 6
+TC_REQUEUES = 7
+TC_OVERLIMITS = 9
+
+TC_H_ROOT = 0xFFFFFFFF
+TC_H_INGRESS = 0xFFFFFFF1
+
+STAT_PACKETS = 0
+STAT_BYTES = 1
+STAT_RATE_BPS = 2
+STAT_RATE_PPS = 3
+STAT_QLEN = 4
+STAT_BACKLOG = 5
+STAT_DROPS = 6
+STAT_REQUEUES = 7
+STAT_OVERLIMITS = 8
+STAT_MAX = STAT_OVERLIMITS
+
+
+class Handle(object):
+    """ Traffic control handle
+
+    Representation of a traffic control handle which uniquely identifies
+    each traffic control object in its link namespace.
+
+    handle = tc.Handle('10:20')
+    handle = tc.handle('root')
+    print int(handle)
+    print str(handle)
+    """
+    def __init__(self, val=None):
+        if type(val) is str:
+            val = capi.tc_str2handle(val)
+        elif not val:
+            val = 0
+
+        self._val = int(val)
+
+    def __cmp__(self, other):
+        if other is None:
+            other = 0
+
+        if isinstance(other, Handle):
+            return int(self) - int(other)
+        elif isinstance(other, int):
+            return int(self) - other
+        else:
+            raise TypeError()
+
+    def __int__(self):
+        return self._val
+
+    def __str__(self):
+        return capi.rtnl_tc_handle2str(self._val, 64)[0]
+
+    def isroot(self):
+        return self._val == TC_H_ROOT or self._val == TC_H_INGRESS
+
+class TcCache(netlink.Cache):
+    """Cache of traffic control object"""
+
+    def __getitem__(self, key):
+        raise NotImplementedError()
+
+class Tc(netlink.Object):
+    def __cmp__(self, other):
+        diff = self.ifindex - other.ifindex
+        if diff == 0:
+            diff = int(self.handle) - int(other.handle)
+        return diff
+
+    def _tc_module_lookup(self):
+        self._module_lookup(self._module_path + self.kind,
+                    'init_' + self._name)
+
+    @property
+    def root(self):
+        """True if tc object is a root object"""
+        return self.parent.isroot()
+
+    @property
+    def ifindex(self):
+        """interface index"""
+        return capi.rtnl_tc_get_ifindex(self._rtnl_tc)
+
+    @ifindex.setter
+    def ifindex(self, value):
+        capi.rtnl_tc_set_ifindex(self._rtnl_tc, int(value))
+
+    @property
+    def link(self):
+        link = capi.rtnl_tc_get_link(self._rtnl_tc)
+        if not link:
+            return None
+
+        return Link.Link.from_capi(link)
+
+    @link.setter
+    def link(self, value):
+        capi.rtnl_tc_set_link(self._rtnl_tc, value._link)
+
+    @property
+    def mtu(self):
+        return capi.rtnl_tc_get_mtu(self._rtnl_tc)
+
+    @mtu.setter
+    def mtu(self, value):
+        capi.rtnl_tc_set_mtu(self._rtnl_tc, int(value))
+
+    @property
+    def mpu(self):
+        return capi.rtnl_tc_get_mpu(self._rtnl_tc)
+
+    @mpu.setter
+    def mpu(self, value):
+        capi.rtnl_tc_set_mpu(self._rtnl_tc, int(value))
+
+    @property
+    def overhead(self):
+        return capi.rtnl_tc_get_overhead(self._rtnl_tc)
+
+    @overhead.setter
+    def overhead(self, value):
+        capi.rtnl_tc_set_overhead(self._rtnl_tc, int(value))
+
+    @property
+    def linktype(self):
+        return capi.rtnl_tc_get_linktype(self._rtnl_tc)
+
+    @linktype.setter
+    def linktype(self, value):
+        capi.rtnl_tc_set_linktype(self._rtnl_tc, int(value))
+
+    @property
+    @netlink.nlattr(fmt=util.handle)
+    def handle(self):
+        return Handle(capi.rtnl_tc_get_handle(self._rtnl_tc))
+
+    @handle.setter
+    def handle(self, value):
+        capi.rtnl_tc_set_handle(self._rtnl_tc, int(value))
+
+    @property
+    @netlink.nlattr(fmt=util.handle)
+    def parent(self):
+        return Handle(capi.rtnl_tc_get_parent(self._rtnl_tc))
+
+    @parent.setter
+    def parent(self, value):
+        capi.rtnl_tc_set_parent(self._rtnl_tc, int(value))
+
+    @property
+    @netlink.nlattr(fmt=util.bold)
+    def kind(self):
+        return capi.rtnl_tc_get_kind(self._rtnl_tc)
+
+    @kind.setter
+    def kind(self, value):
+        capi.rtnl_tc_set_kind(self._rtnl_tc, value)
+        self._tc_module_lookup()
+
+    def get_stat(self, id):
+        return capi.rtnl_tc_get_stat(self._rtnl_tc, id)
+
+    @property
+    def _dev(self):
+        buf = util.kw('dev') + ' '
+
+        if self.link:
+            return buf + util.string(self.link.name)
+        else:
+            return buf + util.num(self.ifindex)
+
+    def brief(self, title, nodev=False, noparent=False):
+        ret = title + ' {a|kind} {a|handle}'
+
+        if not nodev:
+            ret += ' {a|_dev}'
+
+        if not noparent:
+            ret += ' {t|parent}'
+
+        return ret + self._module_brief()
+
+    @staticmethod
+    def details():
+        return '{t|mtu} {t|mpu} {t|overhead} {t|linktype}'
+
+    @property
+    def packets(self):
+        return self.get_stat(STAT_PACKETS)
+
+    @property
+    def bytes(self):
+        return self.get_stat(STAT_BYTES)
+
+    @property
+    def qlen(self):
+        return self.get_stat(STAT_QLEN)
+
+    @staticmethod
+    def stats(fmt):
+        return fmt.nl('{t|packets} {t|bytes} {t|qlen}')
+
+class QdiscCache(netlink.Cache):
+    """Cache of qdiscs"""
+
+    def __init__(self, cache=None):
+        if not cache:
+            cache = self._alloc_cache_name('route/qdisc')
+
+        self._protocol = netlink.NETLINK_ROUTE
+        self._nl_cache = cache
+
+#	def __getitem__(self, key):
+#        	if type(key) is int:
+#                        link = capi.rtnl_link_get(self._this, key)
+#                elif type(key) is str:
+#                        link = capi.rtnl_link_get_by_name(self._this, key)
+#
+#		if qdisc is None:
+#                        raise KeyError()
+#		else:
+#                        return Qdisc._from_capi(capi.qdisc2obj(qdisc))
+
+    @staticmethod
+    def _new_object(obj):
+        return Qdisc(obj)
+
+    @staticmethod
+    def _new_cache(cache):
+        return QdiscCache(cache=cache)
+
+class Qdisc(Tc):
+    """Queueing discipline"""
+
+    def __init__(self, obj=None):
+        netlink.Object.__init__(self, 'route/qdisc', 'qdisc', obj)
+        self._module_path = 'netlink.route.qdisc.'
+        self._rtnl_qdisc = self._obj2type(self._nl_object)
+        self._rtnl_tc = capi.obj2tc(self._nl_object)
+
+        if self.kind:
+            self._tc_module_lookup()
+
+    @classmethod
+    def from_capi(cls, obj):
+        return cls(capi.qdisc2obj(obj))
+
+    @staticmethod
+    def _obj2type(obj):
+        return capi.obj2qdisc(obj)
+
+    @staticmethod
+    def _new_instance(obj):
+        if not obj:
+            raise ValueError()
+
+        return Qdisc(obj)
+
+    @property
+    def childs(self):
+        ret = []
+
+        if int(self.handle):
+            ret += get_cls(self.ifindex, parent=self.handle)
+
+            if self.root:
+                ret += get_class(self.ifindex, parent=TC_H_ROOT)
+
+            ret += get_class(self.ifindex, parent=self.handle)
+
+        return ret
+
+#	def add(self, socket, flags=None):
+#        	if not flags:
+#                        flags = netlink.NLM_F_CREATE
+#
+#		ret = capi.rtnl_link_add(socket._sock, self._link, flags)
+#		if ret < 0:
+#			raise netlink.KernelError(ret)
+#
+#	def change(self, socket, flags=0):
+#		"""Commit changes made to the link object"""
+#		if not self._orig:
+#			raise NetlinkError('Original link not available')
+#        	ret = capi.rtnl_link_change(socket._sock, self._orig, self._link, flags)
+#                if ret < 0:
+#                        raise netlink.KernelError(ret)
+#
+#	def delete(self, socket):
+#		"""Attempt to delete this link in the kernel"""
+#        	ret = capi.rtnl_link_delete(socket._sock, self._link)
+#                if ret < 0:
+#                        raise netlink.KernelError(ret)
+
+    def format(self, details=False, stats=False, nodev=False,
+           noparent=False, indent=''):
+        """Return qdisc as formatted text"""
+        fmt = util.MyFormatter(self, indent)
+
+        buf = fmt.format(self.brief('qdisc', nodev, noparent))
+
+        if details:
+            buf += fmt.nl('\t' + self.details())
+
+        if stats:
+            buf += self.stats(fmt)
+
+#		if stats:
+#			l = [['Packets', RX_PACKETS, TX_PACKETS],
+#			     ['Bytes', RX_BYTES, TX_BYTES],
+#			     ['Errors', RX_ERRORS, TX_ERRORS],
+#			     ['Dropped', RX_DROPPED, TX_DROPPED],
+#			     ['Compressed', RX_COMPRESSED, TX_COMPRESSED],
+#			     ['FIFO Errors', RX_FIFO_ERR, TX_FIFO_ERR],
+#			     ['Length Errors', RX_LEN_ERR, None],
+#			     ['Over Errors', RX_OVER_ERR, None],
+#			     ['CRC Errors', RX_CRC_ERR, None],
+#			     ['Frame Errors', RX_FRAME_ERR, None],
+#			     ['Missed Errors', RX_MISSED_ERR, None],
+#			     ['Abort Errors', None, TX_ABORT_ERR],
+#			     ['Carrier Errors', None, TX_CARRIER_ERR],
+#			     ['Heartbeat Errors', None, TX_HBEAT_ERR],
+#			     ['Window Errors', None, TX_WIN_ERR],
+#			     ['Collisions', None, COLLISIONS],
+#			     ['Multicast', None, MULTICAST],
+#			     ['', None, None],
+#			     ['Ipv6:', None, None],
+#			     ['Packets', IP6_INPKTS, IP6_OUTPKTS],
+#			     ['Bytes', IP6_INOCTETS, IP6_OUTOCTETS],
+#			     ['Discards', IP6_INDISCARDS, IP6_OUTDISCARDS],
+#			     ['Multicast Packets', IP6_INMCASTPKTS, IP6_OUTMCASTPKTS],
+#			     ['Multicast Bytes', IP6_INMCASTOCTETS, IP6_OUTMCASTOCTETS],
+#			     ['Broadcast Packets', IP6_INBCASTPKTS, IP6_OUTBCASTPKTS],
+#			     ['Broadcast Bytes', IP6_INBCASTOCTETS, IP6_OUTBCASTOCTETS],
+#			     ['Delivers', IP6_INDELIVERS, None],
+#			     ['Forwarded', None, IP6_OUTFORWDATAGRAMS],
+#			     ['No Routes', IP6_INNOROUTES, IP6_OUTNOROUTES],
+#			     ['Header Errors', IP6_INHDRERRORS, None],
+#			     ['Too Big Errors', IP6_INTOOBIGERRORS, None],
+#			     ['Address Errors', IP6_INADDRERRORS, None],
+#			     ['Unknown Protocol', IP6_INUNKNOWNPROTOS, None],
+#			     ['Truncated Packets', IP6_INTRUNCATEDPKTS, None],
+#			     ['Reasm Timeouts', IP6_REASMTIMEOUT, None],
+#			     ['Reasm Requests', IP6_REASMREQDS, None],
+#			     ['Reasm Failures', IP6_REASMFAILS, None],
+#			     ['Reasm OK', IP6_REASMOKS, None],
+#			     ['Frag Created', None, IP6_FRAGCREATES],
+#			     ['Frag Failures', None, IP6_FRAGFAILS],
+#			     ['Frag OK', None, IP6_FRAGOKS],
+#			     ['', None, None],
+#			     ['ICMPv6:', None, None],
+#			     ['Messages', ICMP6_INMSGS, ICMP6_OUTMSGS],
+#			     ['Errors', ICMP6_INERRORS, ICMP6_OUTERRORS]]
+#
+#			buf += '\n\t%s%s%s%s\n' % (33 * ' ', util.title('RX'),
+#                        			   15 * ' ', util.title('TX'))
+#
+#			for row in l:
+#				row[0] = util.kw(row[0])
+#                                row[1] = self.get_stat(row[1]) if row[1] else ''
+#                                row[2] = self.get_stat(row[2]) if row[2] else ''
+#				buf += '\t{0:27} {1:>16} {2:>16}\n'.format(*row)
+
+        return buf
+
+class TcClassCache(netlink.Cache):
+    """Cache of traffic classes"""
+
+    def __init__(self, ifindex, cache=None):
+        if not cache:
+            cache = self._alloc_cache_name('route/class')
+
+        self._protocol = netlink.NETLINK_ROUTE
+        self._nl_cache = cache
+        self._set_arg1(ifindex)
+
+    @staticmethod
+    def _new_object(obj):
+        return TcClass(obj)
+
+    def _new_cache(self, cache):
+        return TcClassCache(self.arg1, cache=cache)
+
+class TcClass(Tc):
+    """Traffic Class"""
+
+    def __init__(self, obj=None):
+        netlink.Object.__init__(self, 'route/class', 'class', obj)
+        self._module_path = 'netlink.route.qdisc.'
+        self._rtnl_class = self._obj2type(self._nl_object)
+        self._rtnl_tc = capi.obj2tc(self._nl_object)
+
+        if self.kind:
+            self._tc_module_lookup()
+
+    @classmethod
+    def from_capi(cls, obj):
+        return cls(capi.class2obj(obj))
+
+    @staticmethod
+    def _obj2type(obj):
+        return capi.obj2class(obj)
+
+    @staticmethod
+    def _new_instance(obj):
+        if not obj:
+            raise ValueError()
+
+        return TcClass(obj)
+
+    @property
+    def childs(self):
+        ret = []
+
+        # classes can have classifiers, child classes and leaf
+        # qdiscs
+        ret += get_cls(self.ifindex, parent=self.handle)
+        ret += get_class(self.ifindex, parent=self.handle)
+        ret += get_qdisc(self.ifindex, parent=self.handle)
+
+        return ret
+
+    def format(self, details=False, _stats=False, nodev=False,
+           noparent=False, indent=''):
+        """Return class as formatted text"""
+        fmt = util.MyFormatter(self, indent)
+
+        buf = fmt.format(self.brief('class', nodev, noparent))
+
+        if details:
+            buf += fmt.nl('\t' + self.details())
+
+        return buf
+
+class ClassifierCache(netlink.Cache):
+    """Cache of traffic classifiers objects"""
+
+    def __init__(self, ifindex, parent, cache=None):
+        if not cache:
+            cache = self._alloc_cache_name('route/cls')
+
+        self._protocol = netlink.NETLINK_ROUTE
+        self._nl_cache = cache
+        self._set_arg1(ifindex)
+        self._set_arg2(int(parent))
+
+    @staticmethod
+    def _new_object(obj):
+        return Classifier(obj)
+
+    def _new_cache(self, cache):
+        return ClassifierCache(self.arg1, self.arg2, cache=cache)
+
+class Classifier(Tc):
+    """Classifier"""
+
+    def __init__(self, obj=None):
+        netlink.Object.__init__(self, 'route/cls', 'cls', obj)
+        self._module_path = 'netlink.route.cls.'
+        self._rtnl_cls = self._obj2type(self._nl_object)
+        self._rtnl_tc = capi.obj2tc(self._nl_object)
+
+    @classmethod
+    def from_capi(cls, obj):
+        return cls(capi.cls2obj(obj))
+
+    @staticmethod
+    def _obj2type(obj):
+        return capi.obj2cls(obj)
+
+    @staticmethod
+    def _new_instance(obj):
+        if not obj:
+            raise ValueError()
+
+        return Classifier(obj)
+
+    @property
+    def priority(self):
+        return capi.rtnl_cls_get_prio(self._rtnl_cls)
+
+    @priority.setter
+    def priority(self, value):
+        capi.rtnl_cls_set_prio(self._rtnl_cls, int(value))
+
+    @property
+    def protocol(self):
+        return capi.rtnl_cls_get_protocol(self._rtnl_cls)
+
+    @protocol.setter
+    def protocol(self, value):
+        capi.rtnl_cls_set_protocol(self._rtnl_cls, int(value))
+
+    @property
+    def childs(self):
+        return []
+
+    def format(self, details=False, _stats=False, nodev=False,
+           noparent=False, indent=''):
+        """Return class as formatted text"""
+        fmt = util.MyFormatter(self, indent)
+
+        buf = fmt.format(self.brief('classifier', nodev, noparent))
+        buf += fmt.format(' {t|priority} {t|protocol}')
+
+        if details:
+            buf += fmt.nl('\t' + self.details())
+
+        return buf
+
+_qdisc_cache = QdiscCache()
+
+def get_qdisc(ifindex, handle=None, parent=None):
+    l = []
+
+    _qdisc_cache.refill()
+
+    for qdisc in _qdisc_cache:
+        if qdisc.ifindex != ifindex:
+            continue
+        if (handle is not None) and (qdisc.handle != handle):
+            continue
+        if (parent is not None) and (qdisc.parent != parent):
+            continue
+        l.append(qdisc)
+
+    return l
+
+_class_cache = {}
+
+def get_class(ifindex, parent, handle=None):
+    l = []
+
+    try:
+        cache = _class_cache[ifindex]
+    except KeyError:
+        cache = TcClassCache(ifindex)
+        _class_cache[ifindex] = cache
+
+    cache.refill()
+
+    for cl in cache:
+        if (parent is not None) and (cl.parent != parent):
+            continue
+        if (handle is not None) and (cl.handle != handle):
+            continue
+        l.append(cl)
+
+    return l
+
+_cls_cache = {}
+
+def get_cls(ifindex, parent, handle=None):
+
+    chain = _cls_cache.get(ifindex, dict())
+
+    try:
+        cache = chain[parent]
+    except KeyError:
+        cache = ClassifierCache(ifindex, parent)
+        chain[parent] = cache
+
+    cache.refill()
+
+    if handle is None:
+        return [ cls for cls in cache ]
+
+    return [ cls for cls in cache if cls.handle == handle ]
diff --git a/python/netlink/util.py b/python/netlink/util.py
new file mode 100644
index 0000000..22ed5cf
--- /dev/null
+++ b/python/netlink/util.py
@@ -0,0 +1,175 @@
+#
+# Utilities
+#
+# Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+#
+
+"""utility module for netlink
+
+"""
+
+from __future__ import absolute_import
+
+from . import core as netlink
+from . import capi as capi
+from string import Formatter
+import types
+
+__version__ = '1.0'
+
+#rename into colored_output
+def _color(t, c):
+    return '{esc}[{color}m{text}{esc}[0m'.format(esc=b'\x1b'.decode(), color=c, text=t)
+
+def black(t):
+    return _color(t, 30)
+
+def red(t):
+    return _color(t, 31)
+
+def green(t):
+    return _color(t, 32)
+
+def yellow(t):
+    return _color(t, 33)
+
+def blue(t):
+    return _color(t, 34)
+
+def magenta(t):
+    return _color(t, 35)
+
+def cyan(t):
+    return _color(t, 36)
+
+def white(t):
+    return _color(t, 37)
+
+def bold(t):
+    return _color(t, 1)
+
+def kw(t):
+    return yellow(t)
+
+def num(t):
+    return str(t)
+
+def string(t):
+    return t
+
+def addr(t):
+    return str(t)
+
+def bad(t):
+    return red(t)
+
+def good(t):
+    return green(t)
+
+def title(t):
+    return t
+
+def boolean(t):
+    return str(t)
+
+def handle(t):
+    return str(t)
+
+class MyFormatter(Formatter):
+    def __init__(self, obj, indent=''):
+        self._obj = obj
+        self._indent = indent
+
+    def _nlattr(self, key):
+        value = getattr(self._obj.__class__, key)
+        if not isinstance(value, property):
+            raise ValueError('Invalid formatting string {0}'.format(key))
+
+        d = getattr(value.fget, 'formatinfo', dict())
+
+        # value = value.fget() is exactly the same
+        value = getattr(self._obj, key)
+
+        if 'fmt' in d:
+            value = d['fmt'](value)
+
+        title_ = d.get('title', None)
+
+        return title_, str(value)
+
+    def get_value(self, key, args, kwds):
+        # Let default get_value() handle ints
+        if not isinstance(key, str):
+            return Formatter.get_value(self, key, args, kwds)
+
+        # HACK, we allow defining strings via fields to allow
+        # conversions
+        if key[:2] == 's|':
+            return key[2:]
+
+        if key[:2] == 't|':
+            # title mode ("TITLE ATTR")
+            include_title = True
+        elif key[:2] == 'a|':
+            # plain attribute mode ("ATTR")
+            include_title = False
+        else:
+            # No special field, have default get_value() get it
+            return Formatter.get_value(self, key, args, kwds)
+
+        key = key[2:]
+        (title_, value) = self._nlattr(key)
+
+        if include_title:
+            if not title_:
+                title_ = key    # fall back to key as title
+            value = '{0} {1}'.format(kw(title_), value)
+
+        return value
+
+    def convert_field(self, value, conversion):
+        if conversion == 'r':
+            return repr(value)
+        elif conversion == 's':
+            return str(value)
+        elif conversion == 'k':
+            return kw(value)
+        elif conversion == 'b':
+            return bold(value)
+        elif conversion is None:
+            return value
+
+        raise ValueError('Unknown converion specifier {0!s}'.format(conversion))
+
+    def nl(self, format_string=''):
+        return '\n' + self._indent + self.format(format_string)
+
+NL_BYTE_RATE = 0
+NL_BIT_RATE = 1
+
+class Rate(object):
+    def __init__(self, rate, mode=NL_BYTE_RATE):
+        self._rate = rate
+        self._mode = mode
+
+    def __str__(self):
+        return capi.nl_rate2str(self._rate, self._mode, 32)[1]
+
+    def __int__(self):
+        return self._rate
+
+    def __cmp__(self, other):
+        return int(self) - int(other)
+
+class Size(object):
+    def __init__(self, size):
+        self._size = size
+
+    def __str__(self):
+        return capi.nl_size2str(self._size, 32)[0]
+
+    def __int__(self):
+        return self._size
+
+    def __cmp__(self, other):
+        return int(self) - int(other)
diff --git a/python/netlink/utils.h b/python/netlink/utils.h
new file mode 100644
index 0000000..7836c30
--- /dev/null
+++ b/python/netlink/utils.h
@@ -0,0 +1,41 @@
+struct list_head {
+	struct list_head *next;
+};
+
+#define LIST_HEAD(name) \
+	struct list_head name = { &(name) }
+
+static inline int list_empty(const struct list_head *head)
+{
+	return head->next == head;
+}
+
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	new->next = head->next;
+	head->next = new;
+}
+
+static inline void list_del(struct list_head *entry, struct list_head *prev)
+{
+	prev->next = entry->next;
+	entry->next = entry;
+}
+
+#define list_for_each_safe(pos, n, head) \
+	for (n = (head), pos = (head)->next; pos != (head); \
+	     n = pos, pos = n->next)
+
+#undef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+
+#define container_of(ptr, type, member) ({			\
+	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
+	(type *)( (char *)__mptr - offsetof(type,member) );})
+
+#ifdef DEBUG
+#define pynl_dbg(fmt, ...) \
+	fprintf(stderr, "%s: " fmt, __func__, __VA_ARGS__)
+#else
+#define pynl_dbg(fmt, ...)
+#endif
diff --git a/python/setup.py.in b/python/setup.py.in
new file mode 100644
index 0000000..346c770
--- /dev/null
+++ b/python/setup.py.in
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+from distutils.core import setup, Extension
+
+opts = ['-O', '-nodefaultctor']
+include = ['@top_builddir@/include', '@top_srcdir@/include']
+library_dirs = ['@top_builddir@/lib/.libs']
+
+netlink_capi = Extension('netlink/_capi',
+                         sources = ['@srcdir@/netlink/capi.i'],
+			 include_dirs = include,
+			 swig_opts = opts,
+			 library_dirs = library_dirs,
+			 libraries = ['nl-3'],
+			)
+
+route_capi = Extension('netlink/route/_capi',
+                         sources = ['@srcdir@/netlink/route/capi.i'],
+			 include_dirs = include,
+			 swig_opts = opts,
+			 library_dirs = library_dirs,
+			 libraries = ['nl-3', 'nl-route-3'],
+			)
+
+genl_capi = Extension('netlink/genl/_capi',
+			 sources = ['@srcdir@/netlink/genl/capi.i'],
+			 include_dirs = include,
+			 swig_opts = opts,
+			 library_dirs = library_dirs,
+			 libraries = ['nl-3', 'nl-genl-3'],
+			)
+
+setup(name = 'netlink',
+      version = '1.0',
+      description = 'Python wrapper for netlink protocols',
+      author = 'Thomas Graf',
+      author_email = 'tgraf@suug.ch',
+      ext_modules = [netlink_capi, route_capi, genl_capi],
+      package_dir = {'': '@srcdir@'},
+      packages = ['netlink', 'netlink.genl', 'netlink.route',
+		  'netlink.route.links', 'netlink.route.qdisc'],
+     )
diff --git a/python/tests/Makefile.am b/python/tests/Makefile.am
new file mode 100644
index 0000000..15f77fa
--- /dev/null
+++ b/python/tests/Makefile.am
@@ -0,0 +1,5 @@
+
+# -*- Makefile -*-
+
+EXTRA_DIST = \
+	test-create-bridge.py
diff --git a/python/tests/test-create-bridge.py b/python/tests/test-create-bridge.py
new file mode 100644
index 0000000..216b249
--- /dev/null
+++ b/python/tests/test-create-bridge.py
@@ -0,0 +1,28 @@
+import netlink.core as netlink
+import netlink.route.capi as capi
+import netlink.route.link as link
+
+sock = netlink.lookup_socket(netlink.NETLINK_ROUTE)
+
+cache = link.LinkCache()
+cache.refill(sock)
+
+testtap1 = cache['testtap1']
+print testtap1
+
+lbr = link.Link()
+lbr.type = 'bridge'
+lbr.name = 'testbridge'
+print lbr
+lbr.add()
+
+cache.refill(sock)
+lbr = cache['testbridge']
+print lbr
+
+lbr.enslave(testtap1)
+cache.refill(sock)
+testtap1 = cache['testtap1']
+
+print capi.rtnl_link_is_bridge(lbr._rtnl_link)
+print capi.rtnl_link_get_master(testtap1._rtnl_link)
diff --git a/src/.gitignore b/src/.gitignore
index 60233c8..5de9f29 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -1,5 +1,9 @@
 genl-ctrl-list
+/nf-ct-add
 nf-ct-list
+nf-exp-list
+nf-exp-add
+nf-exp-delete
 nf-log
 nf-monitor
 nl-addr-add
@@ -21,6 +25,12 @@
 nl-qdisc-add
 nl-qdisc-delete
 nl-qdisc-list
+nl-class-add
+nl-class-delete
+nl-class-list
+nl-cls-add
+nl-cls-delete
+nl-cls-list
 nl-route-add
 nl-route-delete
 nl-route-list
@@ -29,3 +39,8 @@
 nl-tctree-list
 nl-util-addr
 nf-queue
+nl-classid-lookup
+nl-pktloc-lookup
+nl-link-enslave
+nl-link-release
+idiag-socket-details
diff --git a/src/Makefile.am b/src/Makefile.am
index dda32a7..b40b8a0 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,17 +2,33 @@
 
 SUBDIRS = lib
 
-AM_CPPFLAGS  = -Wall -I${top_srcdir}/include -I${top_builddir}/include -D_GNU_SOURCE
-AM_LDFLAGS = -L${top_builddir}/lib -L${top_builddir}/src/lib -lnl-cli
+AM_CPPFLAGS  = -I${top_srcdir}/include/linux-private -I${top_srcdir}/include -I${top_builddir}/include -D_GNU_SOURCE -DSYSCONFDIR=\"$(sysconfdir)/libnl\"
+AM_CFLAGS = -Wall
+
+LDADD = \
+	${top_builddir}/src/lib/libnl-cli-3.la \
+	${top_builddir}/lib/libnl-3.la \
+	${top_builddir}/lib/libnl-nf-3.la \
+	${top_builddir}/lib/libnl-genl-3.la \
+	${top_builddir}/lib/libnl-route-3.la \
+	${top_builddir}/lib/libnl-idiag-3.la
+
+sbin_PROGRAMS = \
+	genl-ctrl-list \
+	nl-qdisc-add nl-qdisc-list nl-qdisc-delete \
+	nl-class-add nl-class-list nl-class-delete \
+	nl-cls-add nl-cls-list nl-cls-delete \
+	nl-classid-lookup \
+	nl-pktloc-lookup \
+	nl-link-list
 
 noinst_PROGRAMS = \
-	genl-ctrl-list \
-	nf-ct-list nf-log nf-queue nf-monitor \
+	nf-ct-list nf-ct-add nf-log nf-queue nf-monitor \
+	nf-exp-list nf-exp-add nf-exp-delete \
 	nl-addr-add nl-addr-delete nl-addr-list \
-	nl-link-list nl-link-set nl-link-stats \
+	nl-link-set nl-link-stats \
 	nl-link-ifindex2name nl-link-name2ifindex \
 	nl-neigh-add nl-neigh-delete nl-neigh-list \
-	nl-qdisc-delete nl-qdisc-list \
 	nl-rule-list \
 	nl-neightbl-list \
 	nl-monitor \
@@ -21,81 +37,70 @@
 	nl-fib-lookup \
 	nl-list-caches nl-list-sockets \
 	nl-util-addr \
-	nl-pktloc-lookup
+	nl-link-enslave \
+	nl-link-release \
+	idiag-socket-details
 
 genl_ctrl_list_SOURCES = genl-ctrl-list.c 
-genl_ctrl_list_LDADD = -lnl-genl -lnl-route
 
 nf_ct_list_SOURCES = nf-ct-list.c 
-nf_ct_list_LDADD = -lnl-nf
+nf_ct_add_SOURCES = nf-ct-add.c
 nf_log_SOURCES = nf-log.c
-nf_log_LDADD = -lnl-nf
 nf_queue_SOURCES = nf-queue.c 
-nf_queue_LDADD = -lnl-nf
 nf_monitor_SOURCES = nf-monitor.c
-nf_monitor_LDADD = -lnl-nf
+
+nf_exp_list_SOURCES = nf-exp-list.c 
+nf_exp_add_SOURCES = nf-exp-add.c 
+nf_exp_delete_SOURCES = nf-exp-delete.c 
 
 nl_addr_add_SOURCES = nl-addr-add.c
-nl_addr_add_LDADD = -lnl-route
 nl_addr_delete_SOURCES = nl-addr-delete.c
-nl_addr_delete_LDADD = -lnl-route
 nl_addr_list_SOURCES = nl-addr-list.c
-nl_addr_list_LDADD = -lnl-route
 
 nl_link_list_SOURCES = nl-link-list.c
-nl_link_list_LDADD = -lnl-route
 nl_link_set_SOURCES = nl-link-set.c
-nl_link_set_LDADD = -lnl-route
 nl_link_stats_SOURCES = nl-link-stats.c
-nl_link_stats_LDADD = -lnl-route
 nl_link_ifindex2name_SOURCES = nl-link-ifindex2name.c
-nl_link_ifindex2name_LDADD = -lnl-route
 nl_link_name2ifindex_SOURCES = nl-link-name2ifindex.c
-nl_link_name2ifindex_LDADD = -lnl-route
 
 nl_monitor_SOURCES = nl-monitor.c
-nl_monitor_LDADD = -lnl-route
 
 nl_neigh_add_SOURCES = nl-neigh-add.c
-nl_neigh_add_LDADD = -lnl-route
 nl_neigh_delete_SOURCES = nl-neigh-delete.c
-nl_neigh_delete_LDADD = -lnl-route
 nl_neigh_list_SOURCES = nl-neigh-list.c
-nl_neigh_list_LDADD = -lnl-route
 
 nl_neightbl_list_SOURCES = nl-neightbl-list.c
-nl_neightbl_list_LDADD = -lnl-route
 
+nl_qdisc_add_SOURCES = nl-qdisc-add.c
 nl_qdisc_delete_SOURCES = nl-qdisc-delete.c
-nl_qdisc_delete_LDADD = -lnl-route
 nl_qdisc_list_SOURCES = nl-qdisc-list.c
-nl_qdisc_list_LDADD = -lnl-route
+
+nl_class_add_SOURCES = nl-class-add.c
+nl_class_delete_SOURCES = nl-class-delete.c
+nl_class_list_SOURCES = nl-class-list.c
+
+nl_cls_add_SOURCES = nl-cls-add.c
+nl_cls_list_SOURCES = nl-cls-list.c
+nl_cls_delete_SOURCES = nl-cls-delete.c
 
 nl_route_add_SOURCES = nl-route-add.c
-nl_route_add_LDADD = -lnl-route
 nl_route_delete_SOURCES = nl-route-delete.c
-nl_route_delete_LDADD = -lnl-route
 nl_route_get_SOURCES = nl-route-get.c
-nl_route_get_LDADD = -lnl-route
 nl_route_list_SOURCES = nl-route-list.c
-nl_route_list_LDADD = -lnl-route
 
 nl_rule_list_SOURCES = nl-rule-list.c
-nl_rule_list_LDADD = -lnl-route
 
 nl_tctree_list_SOURCES = nl-tctree-list.c
-nl_tctree_list_LDADD = -lnl-route
 
 nl_fib_lookup_SOURCES = nl-fib-lookup.c
-nl_fib_lookup_LDADD = -lnl-route
 
 nl_list_caches_SOURCES = nl-list-caches.c
-nl_list_caches_LDADD = -lnl-route
 nl_list_sockets_SOURCES = nl-list-sockets.c
-nl_list_sockets_LDADD = -lnl-route
 
 nl_util_addr_SOURCES = nl-util-addr.c
-nl_util_addr_LDADD = -lnl-route
 
 nl_pktloc_lookup_SOURCES = nl-pktloc-lookup.c
-nl_pktloc_lookup_LDADD = -lnl-route
+
+nl_classid_lookup_SOURCES = nl-classid-lookup.c
+
+idiag_socket_details_SOURCES = idiag-socket-details.c
diff --git a/src/cls/basic.c b/src/cls/basic.c
deleted file mode 100644
index df1c112..0000000
--- a/src/cls/basic.c
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * src/cls/basic.c	Basic Classifier
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU General Public License as
- *	published by the Free Software Foundation version 2 of the License.
- *
- * Copyright (c) 2008 Thomas Graf <tgraf@suug.ch>
- */
-
-#include "utils.h"
-#include <netlink/route/cls/basic.h>
-#include <netlink/route/cls/ematch.h>
-
-static void print_usage(void)
-{
-	printf(
-"Usage: ... basic [OPTIONS]...\n"
-"\n"
-"Options\n"
-" -h, --help                Show this help.\n"
-" -e, --ematch=MATCH        Extended match (See --ematch help).\n"
-" -c, --classid=HANDLE      Target class to classify matching packets to.\n"
-	);
-	exit(0);
-}
-
-static void basic_parse_argv(struct rtnl_cls *cls, int argc, char **argv)
-{
-	uint32_t classid;
-
-	for (;;) {
-		int c, optidx = 0, err;
-		static struct option long_opts[] = {
-			{ "help", 0, 0, 'h' },
-			{ "ematch", 1, 0, 'e' },
-			{ "classid", 1, 0, 'c' },
-			{ 0, 0, 0, 0 }
-		};
-	
-		c = getopt_long(argc, argv, "he:c:", long_opts, &optidx);
-		if (c == -1)
-			break;
-
-		switch (c) {
-		case '?':
-			exit(NLE_INVAL);
-
-		case 'h':
-			print_usage();
-
-		case 'e':
-#if 0
-			if ((err = parse_ematch_syntax(optarg, &tree)) < 0)
-				fatal(err, "Error while parsing ematch: %s",
-				      nl_geterror(err));
-
-			if ((err = rtnl_basic_set_ematch(cls, tree)) < 0)
-				fatal(err, "Unable to set ematch: %s",
-					nl_geterror(err));
-#endif
-			break;
-
-		case 'c':
-			if ((err = rtnl_tc_str2handle(optarg, &classid)) < 0)
-				fatal(err, "Invalid classid \"%s\": %s",
-				      optarg, nl_geterror(err));
-				
-			if ((err = rtnl_basic_set_classid(cls, classid)) < 0)
-				fatal(err, "Unable to set classid: %s",
-				      nl_geterror(err));
-			break;
-		}
- 	}
-}
-
-static struct cls_module basic_module = {
-	.name		= "basic",
-	.parse_argv	= basic_parse_argv,
-};
-
-static void __attribute__ ((constructor)) basic_init(void)
-{
-	register_cls_module(&basic_module);
-}
-
-static void __attribute__ ((destructor)) basic_exit(void)
-{
-	unregister_cls_module(&basic_module);
-}
diff --git a/src/cls/cgroup.c b/src/cls/cgroup.c
deleted file mode 100644
index ad0392f..0000000
--- a/src/cls/cgroup.c
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * src/cls/cgroup.c	Control Groups Classifier
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU General Public License as
- *	published by the Free Software Foundation version 2 of the License.
- *
- * Copyright (c) 2009 Thomas Graf <tgraf@suug.ch>
- */
-
-#include "utils.h"
-#include <netlink/route/cls/cgroup.h>
-#include <netlink/route/cls/ematch.h>
-
-static void print_usage(void)
-{
-	printf(
-"Usage: ... cgroup [OPTIONS]...\n"
-"\n"
-"Options\n"
-" -h, --help                Show this help.\n"
-" -e, --ematch=MATCH        Extended match (See --ematch help).\n"
-" -c, --classid=HANDLE      Target class to classify matching packets to.\n"
-	);
-	exit(0);
-}
-
-static void basic_parse_argv(struct rtnl_cls *cls, int argc, char **argv)
-{
-	for (;;) {
-		int c, optidx = 0;
-		static struct option long_opts[] = {
-			{ "help", 0, 0, 'h' },
-			{ "ematch", 1, 0, 'e' },
-			{ "classid", 1, 0, 'c' },
-			{ 0, 0, 0, 0 }
-		};
-	
-		c = getopt_long(argc, argv, "he:c:", long_opts, &optidx);
-		if (c == -1)
-			break;
-
-		switch (c) {
-		case '?':
-			exit(NLE_INVAL);
-
-		case 'h':
-			print_usage();
-
-#if 0
-		case 'e':
-			if ((err = parse_ematch_syntax(optarg, &tree)) < 0)
-				fatal(err, "Error while parsing ematch: %s",
-				      nl_geterror(err));
-
-			if ((err = rtnl_basic_set_ematch(cls, tree)) < 0)
-				fatal(err, "Unable to set ematch: %s",
-					nl_geterror(err));
-			break;
-#endif
-		}
- 	}
-}
-
-static struct cls_module cgroup_module = {
-	.name		= "cgroup",
-	.parse_argv	= basic_parse_argv,
-};
-
-static void __init cgroup_init(void)
-{
-	register_cls_module(&cgroup_module);
-}
-
-static void __exit cgroup_exit(void)
-{
-	unregister_cls_module(&cgroup_module);
-}
diff --git a/src/cls/utils.c b/src/cls/utils.c
deleted file mode 100644
index ef6603b..0000000
--- a/src/cls/utils.c
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * src/cls-utils.c     Classifier Helpers
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU General Public License as
- *	published by the Free Software Foundation version 2 of the License.
- *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
- */
-
-#include "utils.h"
-
-struct rtnl_cls *nlt_alloc_cls(void)
-{
-	struct rtnl_cls *cls;
-
-	cls = rtnl_cls_alloc();
-	if (!cls)
-		fatal(ENOMEM, "Unable to allocate classifier object");
-
-	return cls;
-}
-
-void parse_dev(struct rtnl_cls *cls, struct nl_cache *link_cache, char *arg)
-{
-	int ival;
-
-	if (!(ival = rtnl_link_name2i(link_cache, arg)))
-		fatal(ENOENT, "Link \"%s\" does not exist", arg);
-
-	rtnl_cls_set_ifindex(cls, ival);
-}
-  
-void parse_prio(struct rtnl_cls *cls, char *arg)
-{
-	uint32_t prio = parse_u32(arg);
-	rtnl_cls_set_prio(cls, prio);
-}
-
-void parse_parent(struct rtnl_cls *cls, char *arg)
-{
-	uint32_t parent;
-	int err;
-
-	if ((err = rtnl_tc_str2handle(arg, &parent)) < 0)
-		fatal(err, "Unable to parse handle \"%s\": %s",
-		      arg, nl_geterror(err));
-
-	rtnl_cls_set_parent(cls, parent);
-}
-
-void parse_handle(struct rtnl_cls *cls, char *arg)
-{
-	uint32_t handle;
-	int err;
-
-	if ((err = rtnl_tc_str2handle(arg, &handle)) < 0)
-		fatal(err, "Unable to parse handle \"%s\": %s",
-		      arg, nl_geterror(err));
-
-	rtnl_cls_set_handle(cls, handle);
-}
-
-void parse_proto(struct rtnl_cls *cls, char *arg)
-{
-	int proto = nl_str2ether_proto(arg);
-	if (proto < 0)
-		fatal(proto, "Unable to parse protocol \"%s\": %s",
-		      arg, nl_geterror(proto));
-	rtnl_cls_set_protocol(cls, proto);
-}
-
-static NL_LIST_HEAD(cls_modules);
-
-struct cls_module *lookup_cls_mod(struct rtnl_cls_ops *ops)
-{
-	struct cls_module *mod;
-
-	nl_list_for_each_entry(mod, &cls_modules, list) {
-		if (mod->ops == ops)
-			return mod;
-	}
-
-	return NULL;
-}
-
-void register_cls_module(struct cls_module *mod)
-{
-	struct rtnl_cls_ops *ops;
-
-	if (!(ops = __rtnl_cls_lookup_ops(mod->name)))
-		fatal(ENOENT, "Could not locate classifier module \"%s\"",
-			mod->name);
-
-	if (lookup_cls_mod(ops) != NULL)
-		fatal(EEXIST, "Duplicate classifier module registration.");
-
-	mod->ops = ops;
-	nl_list_add_tail(&mod->list, &cls_modules);
-}
-
-void unregister_cls_module(struct cls_module *mod)
-{
-	nl_list_del(&mod->list);
-}
diff --git a/src/cls/utils.h b/src/cls/utils.h
deleted file mode 100644
index 1a8ee9b..0000000
--- a/src/cls/utils.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * src/cls-utils.h     Classifier Helpers
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU General Public License as
- *	published by the Free Software Foundation version 2 of the License.
- *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
- */
-
-#ifndef __CLS_UTILS_H_
-#define __CLS_UTILS_H_
-
-#include "../utils.h"
-#include <netlink/route/classifier-modules.h>
-#include <netlink/route/cls/ematch.h>
-
-struct cls_module
-{
-	const char *		name;
-	struct rtnl_cls_ops *	ops;
-	void		      (*parse_argv)(struct rtnl_cls *, int, char **);
-	struct nl_list_head	list;
-};
-
-extern struct cls_module *lookup_cls_mod(struct rtnl_cls_ops *);
-extern void register_cls_module(struct cls_module *);
-extern void unregister_cls_module(struct cls_module *);
-
-struct ematch_module
-{
-	int kind;
-	struct rtnl_ematch_ops *ops;
-	void (*parse_argv)(struct rtnl_ematch *, int, char **);
-	struct nl_list_head list;
-};
-
-extern struct ematch_module *lookup_ematch_mod(struct rtnl_ematch_ops *);
-extern void register_ematch_module(struct ematch_module *);
-extern void unregister_ematch_module(struct ematch_module *);
-
-extern struct rtnl_cls *nlt_alloc_cls(void);
-extern void parse_dev(struct rtnl_cls *, struct nl_cache *, char *);
-extern void parse_prio(struct rtnl_cls *, char *);
-extern void parse_parent(struct rtnl_cls *, char *);
-extern void parse_handle(struct rtnl_cls *, char *);
-extern void parse_proto(struct rtnl_cls *, char *);
-
-extern int parse_ematch_syntax(const char *, struct rtnl_ematch_tree **);
-
-#endif
diff --git a/src/disabled-nl-qdisc-add.c b/src/disabled-nl-qdisc-add.c
deleted file mode 100644
index a1fab4e..0000000
--- a/src/disabled-nl-qdisc-add.c
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * src/nl-qdisc-dump.c     Dump qdisc attributes
- *
- *	This library is free software; you can redistribute it and/or
- *	modify it under the terms of the GNU Lesser General Public
- *	License as published by the Free Software Foundation version 2.1
- *	of the License.
- *
- * Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
- */
-
-#include "utils.h"
-#include <netlink/route/sch/fifo.h>
-#include <netlink/route/sch/prio.h>
-
-static void print_usage(void)
-{
-	printf(
-"Usage: nl-qdisc-add <ifindex> <handle> <parent> <kind>\n");
-	exit(1);
-}
-
-static int parse_blackhole_opts(struct rtnl_qdisc *qdisc, char *argv[],
-				int argc)
-{
-	return 0;
-}
-
-static int parse_pfifo_opts(struct rtnl_qdisc *qdisc, char *argv[], int argc)
-{
-	int err, limit;
-
-	if (argc > 0) {
-		if (argc != 2 || strcasecmp(argv[0], "limit")) {
-			fprintf(stderr, "Usage: ... pfifo limit <limit>\n");
-			return -1;
-		}
-
-		limit = strtoul(argv[1], NULL, 0);
-		err = rtnl_qdisc_fifo_set_limit(qdisc, limit);
-		if (err < 0) {
-			fprintf(stderr, "%s\n", nl_geterror());
-			return -1;
-		}
-	}
-
-	return 0;
-}
-
-static int parse_bfifo_opts(struct rtnl_qdisc *qdisc, char *argv[], int argc)
-{
-	int err, limit;
-
-	if (argc > 0) {
-		if (argc != 2 || strcasecmp(argv[0], "limit")) {
-			fprintf(stderr, "Usage: ... bfifo limit <limit>\n");
-			return -1;
-		}
-
-		limit = nl_size2int(argv[1]);
-		if (limit < 0) {
-			fprintf(stderr, "Invalid value for limit.\n");
-			return -1;
-		}
-
-		err = rtnl_qdisc_fifo_set_limit(qdisc, limit);
-		if (err < 0) {
-			fprintf(stderr, "%s\n", nl_geterror());
-			return -1;
-		}
-	}
-
-	return 0;
-}
-
-static int parse_prio_opts(struct rtnl_qdisc *qdisc, char *argv[], int argc)
-{
-	int i, err, bands;
-	uint8_t map[] = QDISC_PRIO_DEFAULT_PRIOMAP;
-
-	if (argc > 0) {
-		if (argc < 2 || strcasecmp(argv[0], "bands"))
-			goto usage;
-
-		bands = strtoul(argv[1], NULL, 0);
-		err = rtnl_qdisc_prio_set_bands(qdisc, bands);
-		if (err < 0) {
-			fprintf(stderr, "%s\n", nl_geterror());
-			return -1;
-		}
-	}
-
-	if (argc > 2) {
-		if (argc < 5 || strcasecmp(argv[2], "map"))
-			goto usage;
-
-		for (i = 3; i < (argc & ~1U); i += 2) {
-			int prio, band;
-
-			prio = rtnl_str2prio(argv[i]);
-			if (prio < 0 || prio > sizeof(map)/sizeof(map[0])) {
-				fprintf(stderr, "Invalid priority \"%s\"\n",
-					argv[i]);
-				return -1;
-			}
-
-			band = strtoul(argv[i+1], NULL, 0);
-
-			map[prio] = band;
-		}
-	}
-
-	err = rtnl_qdisc_prio_set_priomap(qdisc, map, sizeof(map));
-	if (err < 0) {
-		fprintf(stderr, "%s\n", nl_geterror());
-		return -1;
-	}
-
-	return 0;
-usage:
-	fprintf(stderr, "Usage: ... prio bands <nbands> map MAP\n"
-			"MAP := <prio> <band>\n");
-	return -1;
-}
-
-int main(int argc, char *argv[])
-{
-	struct nl_sock *nlh;
-	struct rtnl_qdisc *qdisc;
-	uint32_t handle, parent;
-	int err = 1;
-
-	if (nltool_init(argc, argv) < 0)
-		return -1;
-
-	if (argc < 5 || !strcmp(argv[1], "-h"))
-		print_usage();
-
-	nlh = nltool_alloc_handle();
-	if (!nlh)
-		goto errout;
-
-	qdisc = rtnl_qdisc_alloc();
-	if (!qdisc)
-		goto errout_free_handle;
-
-	rtnl_qdisc_set_ifindex(qdisc, strtoul(argv[1], NULL, 0));
-
-	if (rtnl_tc_str2handle(argv[2], &handle) < 0) {
-		fprintf(stderr, "%s\n", nl_geterror());
-		goto errout_free_qdisc;
-	}
-
-	if (rtnl_tc_str2handle(argv[3], &parent) < 0) {
-		fprintf(stderr, "%s\n", nl_geterror());
-		goto errout_free_qdisc;
-	}
-
-	rtnl_qdisc_set_handle(qdisc, handle);
-	rtnl_qdisc_set_parent(qdisc, parent);
-	rtnl_qdisc_set_kind(qdisc, argv[4]);
-
-	if (!strcasecmp(argv[4], "blackhole"))
-		err = parse_blackhole_opts(qdisc, &argv[5], argc-5);
-	else if (!strcasecmp(argv[4], "pfifo"))
-		err = parse_pfifo_opts(qdisc, &argv[5], argc-5);
-	else if (!strcasecmp(argv[4], "bfifo"))
-		err = parse_bfifo_opts(qdisc, &argv[5], argc-5);
-	else if (!strcasecmp(argv[4], "prio"))
-		err = parse_prio_opts(qdisc, &argv[5], argc-5);
-	else {
-		fprintf(stderr, "Unknown qdisc \"%s\"\n", argv[4]);
-		goto errout_free_qdisc;
-	}
-
-	if (err < 0)
-		goto errout_free_qdisc;
-
-	if (nltool_connect(nlh, NETLINK_ROUTE) < 0)
-		goto errout_free_qdisc;
-
-	if (rtnl_qdisc_add(nlh, qdisc, NLM_F_REPLACE) < 0) {
-		fprintf(stderr, "Unable to add Qdisc: %s\n", nl_geterror());
-		goto errout_close;
-	}
-
-	err = 0;
-errout_close:
-	nl_close(nlh);
-errout_free_qdisc:
-	rtnl_qdisc_put(qdisc);
-errout_free_handle:
-	nl_handle_destroy(nlh);
-errout:
-	return err;
-}
diff --git a/src/genl-ctrl-list.c b/src/genl-ctrl-list.c
index e25eb2a..0895bcc 100644
--- a/src/genl-ctrl-list.c
+++ b/src/genl-ctrl-list.c
@@ -1,12 +1,12 @@
 /*
- * src/genl-ctrl-list.c	List Generic Netlink Controller
+ * src/genl-ctrl-list.c	List Generic Netlink Families
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2012 Thomas Graf <tgraf@suug.ch>
  */
 
 #include <netlink/cli/utils.h>
@@ -20,10 +20,10 @@
 static void print_usage(void)
 {
 	printf(
-	"Usage: genl-ctrl-list [OPTION]...\n"
+	"Usage: genl-ctrl-list [--details]\n"
 	"\n"
 	"Options\n"
-	" -f, --format=TYPE     Output format { brief | details | stats }\n"
+	" -d, --details         Include detailed information in the list\n"
 	" -h, --help            Show this help\n"
 	" -v, --version         Show versioning information\n"
 	);
@@ -46,18 +46,20 @@
 	for (;;) {
 		int c, optidx = 0;
 		static struct option long_opts[] = {
+			{ "details", 0, 0, 'd' },
 			{ "format", 1, 0, 'f' },
 			{ "help", 0, 0, 'h' },
 			{ "version", 0, 0, 'v' },
 			{ 0, 0, 0, 0 }
 		};
 	
-		c = getopt_long(argc, argv, "f:hv", long_opts, &optidx);
+		c = getopt_long(argc, argv, "df:hv", long_opts, &optidx);
 		if (c == -1)
 			break;
 
 		switch (c) {
 		case 'f': params.dp_type = nl_cli_parse_dumptype(optarg); break;
+		case 'd': params.dp_type = NL_DUMP_DETAILS; break;
 		case 'h': print_usage(); break;
 		case 'v': nl_cli_print_version(); break;
 		}
diff --git a/src/idiag-socket-details.c b/src/idiag-socket-details.c
new file mode 100644
index 0000000..9568676
--- /dev/null
+++ b/src/idiag-socket-details.c
@@ -0,0 +1,90 @@
+/*
+ * src/idiag-socket-details.c     List socket details
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License as
+ *	published by the Free Software Foundation version 2 of the License.
+ *
+ * Copyright (c) 2013 Sassano Systems LLC <joe@sassanosystems.com>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/idiag/idiagnl.h>
+#include <netlink/idiag/msg.h>
+#include <linux/netlink.h>
+
+static void print_usage(void)
+{
+	printf(
+"Usage: idiag-socket-details [OPTION]\n"
+"\n"
+"Options\n"
+"     --summary		    Show socket detail summary.\n"
+"     --details             Show socket details on multiple lines.\n"
+"     --stats               Show full socket statistics.\n"
+" -h, --help                Show this help.\n"
+" -v, --version             Show versioning information.\n"
+	);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	struct nl_sock *sock;
+	struct nl_cache *idiag_cache;
+	struct nl_dump_params params = {
+		.dp_type = NL_DUMP_LINE,
+		.dp_nl_cb = NULL,
+		.dp_fd = stdout,
+	};
+	int err = 0;
+
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_INET_DIAG);
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_SUMMARY = 257,
+			ARG_DETAILS = 258,
+			ARG_STATS = 259,
+			ARG_FAMILY,
+		};
+		static struct option long_opts[] = {
+			{ "details", 0, 0, ARG_DETAILS },
+			{ "summary", 0, 0, ARG_SUMMARY },
+			{ "stats", 0, 0, ARG_STATS},
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ 0, 0, 0, 0 }
+		};
+
+		c = getopt_long(argc, argv, "hv", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case '?': exit(NLE_INVAL);
+		case ARG_SUMMARY: params.dp_type = NL_DUMP_LINE; break;
+		case ARG_DETAILS: params.dp_type = NL_DUMP_DETAILS; break;
+		case ARG_STATS:   params.dp_type = NL_DUMP_STATS; break;
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		}
+	}
+
+	if ((err = idiagnl_msg_alloc_cache(sock, AF_INET, IDIAG_SS_ALL,
+					&idiag_cache))) {
+		nl_cli_fatal(err, "Unable to allocate idiag msg cache: %s",
+				nl_geterror(err));
+	}
+
+	nl_cache_mngt_provide(idiag_cache);
+
+	nl_cache_dump_filter(idiag_cache, &params, NULL);
+
+	nl_cache_mngt_unprovide(idiag_cache);
+	nl_cache_free(idiag_cache);
+	nl_socket_free(sock);
+
+	return 0;
+}
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 80c217c..2c259be 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,7 +1,12 @@
 # -*- Makefile -*-
 
-AM_CPPFLAGS  = -Wall -I${top_srcdir}/include -I${top_builddir}/include -D_GNU_SOURCE -DPKGLIBDIR=\"$(pkglibdir)\" -DSYSCONFDIR=\"$(sysconfdir)\" -rdynamic
-AM_LDFLAGS = -L${top_builddir}/lib -ldl
+AM_CPPFLAGS  = -I${top_srcdir}/include/linux-private -I${top_srcdir}/include -I${top_builddir}/include -D_GNU_SOURCE -DPKGLIBDIR=\"$(pkglibdir)\" -DSYSCONFDIR=\"$(sysconfdir)\" -rdynamic
+AM_CFLAGS = -Wall
+AM_LDFLAGS = \
+	-version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE)
+NL_LIBADD = \
+	-L${top_builddir}/lib \
+	-ldl
 
 #nobase_pkglib_LTLIBRARIES = cls/basic.la cls/ematch/cmp.la
 #cls_basic_la_LDFLAGS = -module -version-info 2:0:0
@@ -26,16 +31,15 @@
 #	cls/pktloc_syntax.c cls/pktloc_syntax.h
 
 lib_LTLIBRARIES = \
-	libnl-cli.la
+	libnl-cli-3.la
 
-libnl_cli_la_LDFLAGS = -version-info 2:0:0
+libnl_cli_3_la_LIBADD  = ${top_builddir}/lib/libnl-3.la \
+		       ${top_builddir}/lib/libnl-route-3.la \
+		       ${top_builddir}/lib/libnl-nf-3.la \
+		       ${top_builddir}/lib/libnl-genl-3.la ${NL_LIBADD}
 
-libnl_cli_la_LIBADD  = ${top_builddir}/lib/libnl.la \
-		       ${top_builddir}/lib/libnl-route.la \
-		       ${top_builddir}/lib/libnl-nf.la \
-		       ${top_builddir}/lib/libnl-genl.la
-
-libnl_cli_la_SOURCES = \
-	utils.c addr.c ct.c link.c neigh.c qdisc.c rule.c route.c
+libnl_cli_3_la_SOURCES = \
+	utils.c addr.c ct.c link.c neigh.c rule.c route.c \
+	tc.c qdisc.c class.c cls.c exp.c
 #	cls/ematch_syntax.c cls/ematch_grammar.c cls/ematch.c
 #	cls/pktloc_syntax.c cls/pktloc_grammar.c cls/utils.c
diff --git a/src/lib/class.c b/src/lib/class.c
new file mode 100644
index 0000000..96f60cd
--- /dev/null
+++ b/src/lib/class.c
@@ -0,0 +1,45 @@
+/*
+ * src/lib/class.c     CLI Class Helpers
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup cli
+ * @defgroup cli_class Traffic Classes
+ * @{
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/class.h>
+
+struct rtnl_class *nl_cli_class_alloc(void)
+{
+	struct rtnl_class *class;
+
+	if (!(class = rtnl_class_alloc()))
+		nl_cli_fatal(ENOMEM, "Unable to allocate class object");
+
+	return class;
+}
+
+struct nl_cache *nl_cli_class_alloc_cache(struct nl_sock *sock, int ifindex)
+{
+	struct nl_cache *cache;
+	int err;
+
+	if ((err = rtnl_class_alloc_cache(sock, ifindex, &cache)) < 0)
+		nl_cli_fatal(err, "Unable to allocate class cache: %s",
+			     nl_geterror(err));
+
+	nl_cache_mngt_provide(cache);
+
+	return cache;
+}
+
+/** @} */
diff --git a/src/lib/cls.c b/src/lib/cls.c
new file mode 100644
index 0000000..86d775d
--- /dev/null
+++ b/src/lib/cls.c
@@ -0,0 +1,71 @@
+/*
+ * src/lib/cls.c     	CLI Classifier Helpers
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup cli
+ * @defgroup cli_cls Classifiers
+ * @{
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/cls.h>
+#include <netlink/route/cls/ematch.h>
+
+struct rtnl_cls *nl_cli_cls_alloc(void)
+{
+	struct rtnl_cls *cls;
+
+	if (!(cls = rtnl_cls_alloc()))
+		nl_cli_fatal(ENOMEM, "Unable to allocate classifier object");
+
+	return cls;
+}
+
+struct nl_cache *nl_cli_cls_alloc_cache(struct nl_sock *sock, int ifindex,
+					uint32_t parent)
+{
+	struct nl_cache *cache;
+	int err;
+
+	if ((err = rtnl_cls_alloc_cache(sock, ifindex, parent, &cache)) < 0)
+		nl_cli_fatal(err, "Unable to allocate classifier cache: %s",
+			     nl_geterror(err));
+
+	return cache;
+}
+
+void nl_cli_cls_parse_proto(struct rtnl_cls *cls, char *arg)
+{
+	int proto;
+
+	if ((proto = nl_str2ether_proto(arg)) < 0)
+		nl_cli_fatal(proto, "Unknown protocol \"%s\".", arg);
+
+	rtnl_cls_set_protocol(cls, proto);
+}
+
+struct rtnl_ematch_tree *nl_cli_cls_parse_ematch(struct rtnl_cls *cls, char *arg)
+{
+	struct rtnl_ematch_tree *tree;
+	char *errstr = NULL;
+	int err;
+
+	if ((err = rtnl_ematch_parse_expr(arg, &errstr, &tree)) < 0)
+		nl_cli_fatal(err, "Unable to parse ematch expression: %s",
+				  errstr);
+	
+	if (errstr)
+		free(errstr);
+
+	return tree;
+}
+
+/** @} */
diff --git a/src/lib/ct.c b/src/lib/ct.c
index 5bab08f..c903878 100644
--- a/src/lib/ct.c
+++ b/src/lib/ct.c
@@ -137,6 +137,12 @@
 	nfnl_ct_set_status(ct, status);
 }
 
+void nl_cli_ct_parse_zone(struct nfnl_ct *ct, char *arg)
+{
+	uint32_t zone = nl_cli_parse_u32(arg);
+	nfnl_ct_set_zone(ct, zone);
+}
+
 #if 0
 		} else if (arg_match("origicmpid")) {
 			if (argc > ++idx)
diff --git a/src/lib/exp.c b/src/lib/exp.c
new file mode 100644
index 0000000..a7a74f5
--- /dev/null
+++ b/src/lib/exp.c
@@ -0,0 +1,165 @@
+/*
+ * src/lib/exp.c		CLI Expectation Helpers
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2012 Rich Fought <rich.fought@watchguard.com>
+ */
+
+/**
+ * @ingroup cli
+ * @defgroup cli_exp Expectation Tracking
+ *
+ * @{
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/exp.h>
+
+struct nfnl_exp *nl_cli_exp_alloc(void)
+{
+	struct nfnl_exp *exp;
+
+	exp = nfnl_exp_alloc();
+	if (!exp)
+		nl_cli_fatal(ENOMEM, "Unable to allocate expectation object");
+
+	return exp;
+}
+
+struct nl_cache *nl_cli_exp_alloc_cache(struct nl_sock *sk)
+{
+	return nl_cli_alloc_cache(sk, "expectation", nfnl_exp_alloc_cache);
+}
+
+void nl_cli_exp_parse_family(struct nfnl_exp *exp, char *arg)
+{
+	int family;
+
+	if ((family = nl_str2af(arg)) == AF_UNSPEC)
+		nl_cli_fatal(EINVAL,
+			     "Unable to nl_cli_exp_parse family \"%s\": %s",
+			     arg, nl_geterror(NLE_INVAL));
+
+	nfnl_exp_set_family(exp, family);
+}
+
+void nl_cli_exp_parse_timeout(struct nfnl_exp *exp, char *arg)
+{
+	uint32_t timeout = nl_cli_parse_u32(arg);
+	nfnl_exp_set_timeout(exp, timeout);
+}
+
+void nl_cli_exp_parse_id(struct nfnl_exp *exp, char *arg)
+{
+	uint32_t id = nl_cli_parse_u32(arg);
+	nfnl_exp_set_id(exp, id);
+}
+
+void nl_cli_exp_parse_helper_name(struct nfnl_exp *exp, char *arg)
+{
+	nfnl_exp_set_helper_name(exp, arg);
+}
+
+void nl_cli_exp_parse_zone(struct nfnl_exp *exp, char *arg)
+{
+	uint32_t zone = nl_cli_parse_u32(arg);
+	nfnl_exp_set_zone(exp, zone);
+}
+
+void nl_cli_exp_parse_flags(struct nfnl_exp *exp, char *arg)
+{
+	uint32_t flags = nl_cli_parse_u32(arg);
+	nfnl_exp_set_flags(exp, flags);
+}
+
+void nl_cli_exp_parse_class(struct nfnl_exp *exp, char *arg)
+{
+	uint32_t class = nl_cli_parse_u32(arg);
+	nfnl_exp_set_class(exp, class);
+}
+
+void nl_cli_exp_parse_nat_dir(struct nfnl_exp *exp, char *arg)
+{
+	uint32_t nat_dir = nl_cli_parse_u32(arg);
+	nfnl_exp_set_nat_dir(exp, nat_dir);
+}
+
+void nl_cli_exp_parse_fn(struct nfnl_exp *exp, char *arg)
+{
+	nfnl_exp_set_fn(exp, arg);
+}
+
+void nl_cli_exp_parse_src(struct nfnl_exp *exp, int tuple, char *arg)
+{
+	int err;
+	struct nl_addr *a = nl_cli_addr_parse(arg, nfnl_exp_get_family(exp));
+	if ((err = nfnl_exp_set_src(exp, tuple, a)) < 0)
+		nl_cli_fatal(err, "Unable to set source address: %s",
+			     nl_geterror(err));
+}
+
+void nl_cli_exp_parse_dst(struct nfnl_exp *exp, int tuple, char *arg)
+{
+	int err;
+	struct nl_addr *a = nl_cli_addr_parse(arg, nfnl_exp_get_family(exp));
+	if ((err = nfnl_exp_set_dst(exp, tuple, a)) < 0)
+		nl_cli_fatal(err, "Unable to set destination address: %s",
+			     nl_geterror(err));
+}
+
+void nl_cli_exp_parse_l4protonum(struct nfnl_exp *exp, int tuple, char *arg)
+{
+	int l4protonum;
+
+	if ((l4protonum = nl_str2ip_proto(arg)) < 0)
+		nl_cli_fatal(l4protonum,
+			"Unable to nl_cli_exp_parse protocol \"%s\": %s",
+			arg, nl_geterror(l4protonum));
+
+	nfnl_exp_set_l4protonum(exp, tuple, l4protonum);
+}
+
+void nl_cli_exp_parse_src_port(struct nfnl_exp *exp, int tuple, char *arg)
+{
+	uint32_t sport = nl_cli_parse_u32(arg);
+	uint16_t dport = nfnl_exp_get_dst_port(exp, tuple);
+	nfnl_exp_set_ports(exp, tuple, sport, dport);
+}
+
+void nl_cli_exp_parse_dst_port(struct nfnl_exp *exp, int tuple, char *arg)
+{
+	uint32_t dport = nl_cli_parse_u32(arg);
+	uint16_t sport = nfnl_exp_get_src_port(exp, tuple);
+	nfnl_exp_set_ports(exp, tuple, sport, dport);
+}
+
+void nl_cli_exp_parse_icmp_id(struct nfnl_exp *exp, int tuple, char *arg)
+{
+	uint32_t id = nl_cli_parse_u32(arg);
+	uint8_t type = nfnl_exp_get_icmp_type(exp, tuple);
+	uint8_t code = nfnl_exp_get_icmp_code(exp, tuple);
+	nfnl_exp_set_icmp(exp, tuple, id, type, code);
+}
+
+void nl_cli_exp_parse_icmp_type(struct nfnl_exp *exp, int tuple, char *arg)
+{
+	uint32_t type = nl_cli_parse_u32(arg);
+	uint16_t id = nfnl_exp_get_icmp_id(exp, tuple);
+	uint8_t code = nfnl_exp_get_icmp_code(exp, tuple);
+	nfnl_exp_set_icmp(exp, tuple, id, type, code);
+}
+
+void nl_cli_exp_parse_icmp_code(struct nfnl_exp *exp, int tuple, char *arg)
+{
+	uint32_t code = nl_cli_parse_u32(arg);
+	uint16_t id = nfnl_exp_get_icmp_id(exp, tuple);
+	uint8_t type = nfnl_exp_get_icmp_type(exp, tuple);
+	nfnl_exp_set_icmp(exp, tuple, id, type, code);
+}
+
+/** @} */
diff --git a/src/lib/link.c b/src/lib/link.c
index c192569..5bce824 100644
--- a/src/lib/link.c
+++ b/src/lib/link.c
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
@@ -18,6 +18,7 @@
 
 #include <netlink/cli/utils.h>
 #include <netlink/cli/link.h>
+#include <linux/if.h>
 
 struct rtnl_link *nl_cli_link_alloc(void)
 {
@@ -30,11 +31,30 @@
 	return link;
 }
 
+struct nl_cache *nl_cli_link_alloc_cache_family(struct nl_sock *sock, int family)
+{
+	struct nl_cache *cache;
+	int err;
+
+	if ((err = rtnl_link_alloc_cache(sock, family, &cache)) < 0)
+		nl_cli_fatal(err, "Unable to allocate link cache: %s",
+			     nl_geterror(err));
+
+	nl_cache_mngt_provide(cache);
+
+	return cache;
+}
+
+struct nl_cache *nl_cli_link_alloc_cache(struct nl_sock *sock)
+{
+	return nl_cli_link_alloc_cache_family(sock, AF_UNSPEC);
+}
+
 void nl_cli_link_parse_family(struct rtnl_link *link, char *arg)
 {
 	int family;
 
-	if ((family = nl_str2af(arg)) == AF_UNSPEC)
+	if ((family = nl_str2af(arg)) < 0)
 		nl_cli_fatal(EINVAL,
 			     "Unable to translate address family \"%s\"", arg);
 
@@ -66,8 +86,16 @@
 
 void nl_cli_link_parse_weight(struct rtnl_link *link, char *arg)
 {
-	uint32_t weight = nl_cli_parse_u32(arg);
-	rtnl_link_set_weight(link, weight);
+}
+
+void nl_cli_link_parse_ifalias(struct rtnl_link *link, char *arg)
+{
+	if (strlen(arg) > IFALIASZ)
+		nl_cli_fatal(ERANGE,
+			"Link ifalias too big, must not exceed %u in length.",
+			IFALIASZ);
+
+	rtnl_link_set_ifalias(link, arg);
 }
 
 /** @} */
diff --git a/src/lib/neigh.c b/src/lib/neigh.c
index a814bd8..4518e46 100644
--- a/src/lib/neigh.c
+++ b/src/lib/neigh.c
@@ -25,7 +25,7 @@
 
 	neigh = rtnl_neigh_alloc();
 	if (!neigh)
-		nl_cli_fatal(ENOMEM, "Unable to allocate neighbout object");
+		nl_cli_fatal(ENOMEM, "Unable to allocate neighbour object");
 
 	return neigh;
 }
diff --git a/src/lib/qdisc.c b/src/lib/qdisc.c
index bc7ff92..ccf7d26 100644
--- a/src/lib/qdisc.c
+++ b/src/lib/qdisc.c
@@ -6,67 +6,27 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2011 Thomas Graf <tgraf@suug.ch>
  */
 
 /**
  * @ingroup cli
  * @defgroup cli_qdisc Queueing Disciplines
- *
  * @{
  */
 
 #include <netlink/cli/utils.h>
 #include <netlink/cli/qdisc.h>
+#include <netlink/route/class.h>
 
 struct rtnl_qdisc *nl_cli_qdisc_alloc(void)
 {
 	struct rtnl_qdisc *qdisc;
 
-	qdisc = rtnl_qdisc_alloc();
-	if (!qdisc)
+	if (!(qdisc = rtnl_qdisc_alloc()))
 		nl_cli_fatal(ENOMEM, "Unable to allocate qdisc object");
 
 	return qdisc;
 }
 
-void nl_cli_qdisc_parse_dev(struct rtnl_qdisc *qdisc, struct nl_cache *link_cache, char *arg)
-{
-	int ival;
-
-	if (!(ival = rtnl_link_name2i(link_cache, arg)))
-		nl_cli_fatal(ENOENT, "Link \"%s\" does not exist", arg);
-
-	rtnl_qdisc_set_ifindex(qdisc, ival);
-}
-
-void nl_cli_qdisc_parse_parent(struct rtnl_qdisc *qdisc, char *arg)
-{
-	uint32_t parent;
-	int err;
-
-	if ((err = rtnl_tc_str2handle(arg, &parent)) < 0)
-		nl_cli_fatal(err, "Unable to parse handle \"%s\": %s",
-		      arg, nl_geterror(err));
-
-	rtnl_qdisc_set_parent(qdisc, parent);
-}
-
-void nl_cli_qdisc_parse_handle(struct rtnl_qdisc *qdisc, char *arg)
-{
-	uint32_t handle;
-	int err;
-
-	if ((err = rtnl_tc_str2handle(arg, &handle)) < 0)
-		nl_cli_fatal(err, "Unable to parse handle \"%s\": %s",
-		      arg, nl_geterror(err));
-
-	rtnl_qdisc_set_handle(qdisc, handle);
-}
-
-void nl_cli_qdisc_parse_kind(struct rtnl_qdisc *qdisc, char *arg)
-{
-	rtnl_qdisc_set_kind(qdisc, arg);
-}
-
 /** @} */
diff --git a/src/lib/route.c b/src/lib/route.c
index 05cb2ad..cd3e897 100644
--- a/src/lib/route.c
+++ b/src/lib/route.c
@@ -198,14 +198,18 @@
 {
 	unsigned long lval;
 	char *endptr;
+	int table;
 
 	lval = strtoul(arg, &endptr, 0);
 	if (endptr == arg) {
-		if ((lval = rtnl_route_str2table(arg)) < 0)
+		if ((table = rtnl_route_str2table(arg)) < 0)
 			nl_cli_fatal(EINVAL, "Unknown table name \"%s\"", arg);
 	}
+	else {
+		table = lval;
+	}
 
-	rtnl_route_set_table(route, lval);
+	rtnl_route_set_table(route, table);
 }
 
 void nl_cli_route_parse_prio(struct rtnl_route *route, char *arg)
@@ -233,16 +237,20 @@
 {
 	unsigned long lval;
 	char *endptr;
+	int proto;
 
 	lval = strtoul(arg, &endptr, 0);
 	if (endptr == arg) {
-		if ((lval = rtnl_route_str2proto(arg)) < 0)
+		if ((proto = rtnl_route_str2proto(arg)) < 0)
 			nl_cli_fatal(EINVAL,
 				     "Unknown routing protocol name \"%s\"",
 				     arg);
 	}
+	else {
+		proto = lval;
+	}
 
-	rtnl_route_set_protocol(route, lval);
+	rtnl_route_set_protocol(route, proto);
 }
 
 void nl_cli_route_parse_type(struct rtnl_route *route, char *arg)
diff --git a/src/lib/tc.c b/src/lib/tc.c
new file mode 100644
index 0000000..dde729f
--- /dev/null
+++ b/src/lib/tc.c
@@ -0,0 +1,165 @@
+/*
+ * src/lib/tc.c     CLI Traffic Control Helpers
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink-private/route/tc-api.h>
+
+/**
+ * @ingroup cli
+ * @defgroup cli_tc Traffic Control
+ * @{
+ */
+void nl_cli_tc_parse_dev(struct rtnl_tc *tc, struct nl_cache *link_cache, char *name)
+{
+	struct rtnl_link *link;
+	
+	link = rtnl_link_get_by_name(link_cache, name);
+	if (!link)
+		nl_cli_fatal(ENOENT, "Link \"%s\" does not exist.", name);
+
+	rtnl_tc_set_link(tc, link);
+	rtnl_link_put(link);
+}
+
+void nl_cli_tc_parse_parent(struct rtnl_tc *tc, char *arg)
+{
+	uint32_t parent;
+	int err;
+
+	if ((err = rtnl_tc_str2handle(arg, &parent)) < 0)
+		nl_cli_fatal(err, "Unable to parse handle \"%s\": %s",
+		      arg, nl_geterror(err));
+
+	rtnl_tc_set_parent(tc, parent);
+}
+
+void nl_cli_tc_parse_handle(struct rtnl_tc *tc, char *arg, int create)
+{
+	uint32_t handle, parent;
+	int err;
+
+	parent = rtnl_tc_get_parent(tc);
+
+	if ((err = rtnl_tc_str2handle(arg, &handle)) < 0) {
+		if (err == -NLE_OBJ_NOTFOUND && create)
+			err = rtnl_classid_generate(arg, &handle, parent);
+
+		if (err < 0)
+			nl_cli_fatal(err, "Unable to parse handle \"%s\": %s",
+				     arg, nl_geterror(err));
+	}
+
+	rtnl_tc_set_handle(tc, handle);
+}
+
+void nl_cli_tc_parse_mtu(struct rtnl_tc *tc, char *arg)
+{
+	rtnl_tc_set_mtu(tc, nl_cli_parse_u32(arg));
+}
+
+void nl_cli_tc_parse_mpu(struct rtnl_tc *tc, char *arg)
+{
+	rtnl_tc_set_mpu(tc, nl_cli_parse_u32(arg));
+}
+
+void nl_cli_tc_parse_overhead(struct rtnl_tc *tc, char *arg)
+{
+	rtnl_tc_set_overhead(tc, nl_cli_parse_u32(arg));
+}
+
+void nl_cli_tc_parse_kind(struct rtnl_tc *tc, char *arg)
+{
+	rtnl_tc_set_kind(tc, arg);
+}
+
+void nl_cli_tc_parse_linktype(struct rtnl_tc *tc, char *arg)
+{
+	int type;
+
+	if ((type = nl_str2llproto(arg)) < 0)
+		nl_cli_fatal(type, "Unable to parse linktype \"%s\": %s",
+			arg, nl_geterror(type));
+
+	rtnl_tc_set_linktype(tc, type);
+}
+
+static NL_LIST_HEAD(tc_modules);
+
+static struct nl_cli_tc_module *__nl_cli_tc_lookup(struct rtnl_tc_ops *ops)
+{
+	struct nl_cli_tc_module *tm;
+
+	nl_list_for_each_entry(tm, &tc_modules, tm_list)
+		if (tm->tm_ops == ops)
+			return tm;
+
+	return NULL;
+}
+
+struct nl_cli_tc_module *nl_cli_tc_lookup(struct rtnl_tc_ops *ops)
+{
+	struct nl_cli_tc_module *tm;
+
+	if ((tm = __nl_cli_tc_lookup(ops)))
+		return tm;
+
+	switch (ops->to_type) {
+	case RTNL_TC_TYPE_QDISC:
+	case RTNL_TC_TYPE_CLASS:
+		nl_cli_load_module("cli/qdisc", ops->to_kind);
+		break;
+
+	case RTNL_TC_TYPE_CLS:
+		nl_cli_load_module("cli/cls", ops->to_kind);
+		break;
+
+	default:
+		nl_cli_fatal(EINVAL, "BUG: unhandled TC object type %d",
+				ops->to_type);
+	}
+
+	if (!(tm = __nl_cli_tc_lookup(ops)))  {
+		nl_cli_fatal(EINVAL, "Application bug: The shared library for "
+			"the tc object \"%s\" was successfully loaded but it "
+			"seems that module did not register itself",
+			ops->to_kind);
+	}
+
+	return tm;
+}
+
+void nl_cli_tc_register(struct nl_cli_tc_module *tm)
+{
+	struct rtnl_tc_ops *ops;
+
+	if (!(ops = rtnl_tc_lookup_ops(tm->tm_type, tm->tm_name))) {
+		nl_cli_fatal(ENOENT, "Unable to register CLI TC module "
+		"\"%s\": No matching libnl TC module found.", tm->tm_name);
+	}
+
+	if (__nl_cli_tc_lookup(ops)) {
+		nl_cli_fatal(EEXIST, "Unable to register CLI TC module "
+		"\"%s\": Module already registered.", tm->tm_name);
+	}
+
+	tm->tm_ops = ops;
+
+	nl_list_add_tail(&tm->tm_list, &tc_modules);
+}
+
+void nl_cli_tc_unregister(struct nl_cli_tc_module *tm)
+{
+	nl_list_del(&tm->tm_list);
+}
+
+
+/** @} */
diff --git a/src/lib/utils.c b/src/lib/utils.c
index 02a7be1..e5eacde 100644
--- a/src/lib/utils.c
+++ b/src/lib/utils.c
@@ -13,10 +13,25 @@
  * @defgroup cli Command Line Interface API
  *
  * @{
+ *
+ * These modules provide an interface for text based applications. The
+ * functions provided are wrappers for their libnl equivalent with
+ * added error handling. The functions check for allocation failures,
+ * invalid input, and unknown types and will print error messages
+ * accordingly via nl_cli_fatal().
  */
 
 #include <netlink/cli/utils.h>
 
+/**
+ * Parse a text based 32 bit unsigned integer argument
+ * @arg arg		Integer in text form.
+ *
+ * Tries to convert the number provided in arg to a uint32_t. Will call
+ * nl_cli_fatal() if the conversion fails.
+ *
+ * @return 32bit unsigned integer.
+ */
 uint32_t nl_cli_parse_u32(const char *arg)
 {
 	unsigned long lval;
@@ -34,7 +49,7 @@
 {
 	printf("libnl tools version %s\n", LIBNL_VERSION);
 	printf(
-	"Copyright (C) 2003-2009 Thomas Graf <tgraf@redhat.com>\n"
+	"Copyright (C) 2003-2010 Thomas Graf <tgraf@redhat.com>\n"
 	"\n"
 	"This program comes with ABSOLUTELY NO WARRANTY. This is free \n"
 	"software, and you are welcome to redistribute it under certain\n"
@@ -44,9 +59,18 @@
 	exit(0);
 }
 
+/**
+ * Print error message and quit application
+ * @arg err		Error code.
+ * @arg fmt		Error message.
+ *
+ * Prints the formatted error message to stderr and quits the application
+ * using the provided error code.
+ */
 void nl_cli_fatal(int err, const char *fmt, ...)
 {
 	va_list ap;
+	char buf[256];
 
 	fprintf(stderr, "Error: ");
 
@@ -56,7 +80,7 @@
 		va_end(ap);
 		fprintf(stderr, "\n");
 	} else
-		fprintf(stderr, "%s\n", strerror(err));
+		fprintf(stderr, "%s\n", strerror_r(err, buf, sizeof(buf)));
 
 	exit(abs(err));
 }
@@ -102,8 +126,6 @@
 		return NL_DUMP_DETAILS;
 	else if (!strcasecmp(str, "stats"))
 		return NL_DUMP_STATS;
-	else if (!strcasecmp(str, "env"))
-		return NL_DUMP_ENV;
 	else
 		nl_cli_fatal(EINVAL, "Invalid dump type \"%s\".\n", str);
 
@@ -113,20 +135,34 @@
 int nl_cli_confirm(struct nl_object *obj, struct nl_dump_params *params,
 		   int default_yes)
 {
-	int answer;
-
 	nl_object_dump(obj, params);
-	printf("Delete? (%c/%c) ",
-		default_yes ? 'Y' : 'y',
-		default_yes ? 'n' : 'N');
 
-	do {
-		answer = tolower(getchar());
-		if (answer == '\n')
+	for (;;) {
+		char buf[32] = { 0 };
+		int answer;
+
+		printf("Delete? (%c/%c) ",
+			default_yes ? 'Y' : 'y',
+			default_yes ? 'n' : 'N');
+
+		if (!fgets(buf, sizeof(buf), stdin)) {
+			fprintf(stderr, "Error while reading\n.");
+			continue;
+		}
+
+		switch ((answer = tolower(buf[0]))) {
+		case '\n':
 			answer = default_yes ? 'y' : 'n';
-	} while (answer != 'y' && answer != 'n');
+		case 'y':
+		case 'n':
+			return answer == 'y';
+		}
 
-	return answer == 'y';
+		fprintf(stderr, "Invalid input, try again.\n");
+	}
+
+	return 0;
+
 }
 
 struct nl_cache *nl_cli_alloc_cache(struct nl_sock *sock, const char *name,
@@ -144,4 +180,17 @@
 	return cache;
 }
 
+void nl_cli_load_module(const char *prefix, const char *name)
+{
+	char path[FILENAME_MAX+1];
+	void *handle;
+
+	snprintf(path, sizeof(path), "%s/%s/%s.so",
+		 PKGLIBDIR, prefix, name);
+
+	if (!(handle = dlopen(path, RTLD_NOW)))
+		nl_cli_fatal(ENOENT, "Unable to load module \"%s\": %s\n",
+			path, dlerror());
+}
+
 /** @} */
diff --git a/src/nf-ct-add.c b/src/nf-ct-add.c
new file mode 100644
index 0000000..8ad4c53
--- /dev/null
+++ b/src/nf-ct-add.c
@@ -0,0 +1,142 @@
+/*
+ * src/nf-ct-list.c     List Conntrack Entries
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2007 Philip Craig <philipc@snapgear.com>
+ * Copyright (c) 2007 Secure Computing Corporation
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/ct.h>
+
+static int quiet = 0;
+
+static void print_usage(void)
+{
+	printf(
+	"Usage: nf-ct-add [OPTION]... [CONNTRACK ENTRY]\n"
+	"\n"
+	"Options\n"
+	" -q, --quiet           Do not print informal notifications.\n"
+	" -h, --help            Show this help\n"
+	" -v, --version         Show versioning information\n"
+	"\n"
+	"Conntrack Selection\n"
+	" -p, --proto=PROTOCOL    Protocol\n"
+	"     --orig-src=ADDR     Original source address\n"
+	"     --orig-sport=PORT   Original source port\n"
+	"     --orig-dst=ADDR     Original destination address\n"
+	"     --orig-dport=PORT   Original destination port\n"
+	"     --reply-src=ADDR    Reply source address\n"
+	"     --reply-sport=PORT  Reply source port\n"
+	"     --reply-dst=ADDR    Reply destination address\n"
+	"     --reply-dport=PORT  Reply destination port\n"
+	" -F, --family=FAMILY     Address family\n"
+	"     --mark=NUM          Mark value\n"
+	"     --timeout=NUM       Timeout value\n"
+	"     --status            Bitset representing status of connection.\n"
+	"     --zone=NUM          Zone value\n"
+	);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	struct nl_sock *sock;
+	struct nfnl_ct *ct;
+	struct nl_dump_params params = {
+		.dp_type = NL_DUMP_LINE,
+		.dp_fd = stdout,
+	};
+	int err, nlflags = NLM_F_CREATE;
+
+	ct = nl_cli_ct_alloc();
+
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_ORIG_SRC = 257,
+			ARG_ORIG_SPORT = 258,
+			ARG_ORIG_DST,
+			ARG_ORIG_DPORT,
+			ARG_REPLY_SRC,
+			ARG_REPLY_SPORT,
+			ARG_REPLY_DST,
+			ARG_REPLY_DPORT,
+			ARG_MARK,
+			ARG_TIMEOUT,
+			ARG_STATUS,
+			ARG_ZONE,
+		};
+		static struct option long_opts[] = {
+			{ "quiet", 0, 0, 'q' },
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ "proto", 1, 0, 'p' },
+			{ "orig-src", 1, 0, ARG_ORIG_SRC },
+			{ "orig-sport", 1, 0, ARG_ORIG_SPORT },
+			{ "orig-dst", 1, 0, ARG_ORIG_DST },
+			{ "orig-dport", 1, 0, ARG_ORIG_DPORT },
+			{ "reply-src", 1, 0, ARG_REPLY_SRC },
+			{ "reply-sport", 1, 0, ARG_REPLY_SPORT },
+			{ "reply-dst", 1, 0, ARG_REPLY_DST },
+			{ "reply-dport", 1, 0, ARG_REPLY_DPORT },
+			{ "family", 1, 0, 'F' },
+			{ "mark", 1, 0, ARG_MARK },
+			{ "timeout", 1, 0, ARG_TIMEOUT },
+			{ "status", 1, 0, ARG_STATUS },
+			{ "zone", 1, 0, ARG_ZONE },
+			{ 0, 0, 0, 0 }
+		};
+
+		c = getopt_long(argc, argv, "46q:hv:p:F:", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case '?': exit(NLE_INVAL);
+		case 'q': quiet = 1; break;
+		case '4': nfnl_ct_set_family(ct, AF_INET); break;
+		case '6': nfnl_ct_set_family(ct, AF_INET6); break;
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'p': nl_cli_ct_parse_protocol(ct, optarg); break;
+		case ARG_ORIG_SRC: nl_cli_ct_parse_src(ct, 0, optarg); break;
+		case ARG_ORIG_SPORT: nl_cli_ct_parse_src_port(ct, 0, optarg); break;
+		case ARG_ORIG_DST: nl_cli_ct_parse_dst(ct, 0, optarg); break;
+		case ARG_ORIG_DPORT: nl_cli_ct_parse_dst_port(ct, 0, optarg); break;
+		case ARG_REPLY_SRC: nl_cli_ct_parse_src(ct, 1, optarg); break;
+		case ARG_REPLY_SPORT: nl_cli_ct_parse_src_port(ct, 1, optarg); break;
+		case ARG_REPLY_DST: nl_cli_ct_parse_dst(ct, 1, optarg); break;
+		case ARG_REPLY_DPORT: nl_cli_ct_parse_dst_port(ct, 1, optarg); break;
+		case 'F': nl_cli_ct_parse_family(ct, optarg); break;
+		case ARG_MARK: nl_cli_ct_parse_mark(ct, optarg); break;
+		case ARG_TIMEOUT: nl_cli_ct_parse_timeout(ct, optarg); break;
+		case ARG_STATUS: nl_cli_ct_parse_status(ct, optarg); break;
+		case ARG_ZONE: nl_cli_ct_parse_zone(ct, optarg); break;
+		}
+	}
+
+	if (!quiet) {
+		printf("Adding ");
+		nl_object_dump(OBJ_CAST(ct), &params);
+	}
+
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_NETFILTER);
+
+	if ((err = nfnl_ct_add(sock, ct, nlflags)) < 0)
+		nl_cli_fatal(err, "Unable to add conntrack: %s", nl_geterror(err));
+
+	if (!quiet) {
+		printf("Added ");
+		nl_object_dump(OBJ_CAST(ct), &params);
+	}
+
+	return 0;
+}
diff --git a/src/nf-exp-add.c b/src/nf-exp-add.c
new file mode 100644
index 0000000..4b7f9d9
--- /dev/null
+++ b/src/nf-exp-add.c
@@ -0,0 +1,187 @@
+/*
+ * src/nf-exp-add.c     Create an expectation
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2007 Philip Craig <philipc@snapgear.com>
+ * Copyright (c) 2007 Secure Computing Corporation
+ * Copyright (c) 2012 Rich Fought <rich.fought@watchguard.com>
+ *
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/exp.h>
+
+static int quiet = 0;
+
+static void print_usage(void)
+{
+	printf(
+	"Usage: nf-exp-list [OPTION]... [CONNTRACK ENTRY]\n"
+	"\n"
+	"Options\n"
+	"     --replace             Replace the address if it exists.\n"
+	" -q, --quiet               Do not print informal notifications.\n"
+	" -h, --help            Show this help\n"
+	" -v, --version         Show versioning information\n"
+	"\n"
+	"Expectation Selection\n"
+	" -i, --id=NUM                Identifier\n"
+	"     --expect-proto=PROTOCOL Expectation protocol\n"
+	"     --expect-src=ADDR       Expectation source address\n"
+	"     --expect-sport=PORT     Expectation source port\n"
+	"     --expect-dst=ADDR       Expectation destination address\n"
+	"     --expect-dport=PORT     Expectation destination port\n"
+	"     --master-proto=PROTOCOL Master conntrack protocol\n"
+	"     --master-src=ADDR       Master conntrack source address\n"
+	"     --master-sport=PORT     Master conntrack source port\n"
+	"     --master-dst=ADDR       Master conntrack destination address\n"
+	"     --master-dport=PORT     Master conntrack destination port\n"
+	"     --mask-proto=PROTOCOL   Mask protocol\n"
+	"     --mask-src=ADDR         Mask source address\n"
+	"     --mask-sport=PORT       Mask source port\n"
+	"     --mask-dst=ADDR         Mask destination address\n"
+	"     --mask-dport=PORT       Mask destination port\n"
+	" -F, --family=FAMILY         Address family\n"
+	"     --timeout=NUM           Timeout value\n"
+	"     --helper=STRING         Helper Name\n"
+	"     --flags                 Flags (Kernel 2.6.37)\n"
+	);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	struct nl_sock *sock;
+	struct nfnl_exp *exp;
+	struct nl_dump_params params = {
+		.dp_type = NL_DUMP_LINE,
+		.dp_fd = stdout,
+	};
+	int err, nlflags = NLM_F_CREATE;
+
+ 	exp = nl_cli_exp_alloc();
+ 
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_MARK = 270,
+			ARG_TCP_STATE = 271,
+			ARG_EXPECT_PROTO,
+			ARG_EXPECT_SRC,
+			ARG_EXPECT_SPORT,
+			ARG_EXPECT_DST,
+			ARG_EXPECT_DPORT,
+			ARG_MASTER_PROTO,
+			ARG_MASTER_SRC,
+			ARG_MASTER_SPORT,
+			ARG_MASTER_DST,
+			ARG_MASTER_DPORT,
+			ARG_MASK_PROTO,
+			ARG_MASK_SRC,
+			ARG_MASK_SPORT,
+			ARG_MASK_DST,
+			ARG_MASK_DPORT,
+            ARG_NAT_PROTO,
+            ARG_NAT_SRC,
+            ARG_NAT_SPORT,
+            ARG_NAT_DST,
+            ARG_NAT_DPORT,
+            ARG_NAT_DIR,
+			ARG_TIMEOUT,
+			ARG_HELPER_NAME,
+			ARG_REPLACE,
+			ARG_FLAGS,
+		};
+		static struct option long_opts[] = {
+			{ "replace", 1, 0, ARG_REPLACE },
+			{ "quiet", 0, 0, 'q' },
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ "id", 1, 0, 'i' },
+			{ "expect-proto", 1, 0, ARG_EXPECT_PROTO },
+			{ "expect-src", 1, 0, ARG_EXPECT_SRC },
+			{ "expect-sport", 1, 0, ARG_EXPECT_SPORT },
+			{ "expect-dst", 1, 0, ARG_EXPECT_DST },
+			{ "expect-dport", 1, 0, ARG_EXPECT_DPORT },
+			{ "master-proto", 1, 0, ARG_MASTER_PROTO },
+			{ "master-src", 1, 0, ARG_MASTER_SRC },
+			{ "master-sport", 1, 0, ARG_MASTER_SPORT },
+			{ "master-dst", 1, 0, ARG_MASTER_DST },
+			{ "master-dport", 1, 0, ARG_MASTER_DPORT },
+			{ "mask-proto", 1, 0, ARG_MASK_PROTO },
+			{ "mask-src", 1, 0, ARG_MASK_SRC },
+			{ "mask-sport", 1, 0, ARG_MASK_SPORT },
+			{ "mask-dst", 1, 0, ARG_MASK_DST },
+			{ "mask-dport", 1, 0, ARG_MASK_DPORT },
+            { "nat-proto", 1, 0, ARG_NAT_PROTO },
+            { "nat-src", 1, 0, ARG_NAT_SRC },
+            { "nat-sport", 1, 0, ARG_NAT_SPORT },
+            { "nat-dst", 1, 0, ARG_NAT_DST },
+            { "nat-dport", 1, 0, ARG_NAT_DPORT },
+            { "nat-dir", 1, 0, ARG_NAT_DIR },
+			{ "family", 1, 0, 'F' },
+			{ "timeout", 1, 0, ARG_TIMEOUT },
+			{ "helper", 1, 0, ARG_HELPER_NAME },
+			{ "flags", 1, 0, ARG_FLAGS},
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "46f:hvi:p:F:", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case '?': exit(NLE_INVAL);
+		case ARG_REPLACE: nlflags |= NLM_F_REPLACE; break;
+		case 'q': quiet = 1; break;
+		case '4': nfnl_exp_set_family(exp, AF_INET); break;
+		case '6': nfnl_exp_set_family(exp, AF_INET6); break;
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'i': nl_cli_exp_parse_id(exp, optarg); break;
+		case ARG_EXPECT_PROTO: nl_cli_exp_parse_l4protonum(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_SRC: nl_cli_exp_parse_src(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_SPORT: nl_cli_exp_parse_src_port(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_DST: nl_cli_exp_parse_dst(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_DPORT: nl_cli_exp_parse_dst_port(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_MASTER_PROTO: nl_cli_exp_parse_l4protonum(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_SRC: nl_cli_exp_parse_src(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_SPORT: nl_cli_exp_parse_src_port(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_DST: nl_cli_exp_parse_dst(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_DPORT: nl_cli_exp_parse_dst_port(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASK_PROTO: nl_cli_exp_parse_l4protonum(exp, NFNL_EXP_TUPLE_MASK, optarg); break;
+		case ARG_MASK_SRC: nl_cli_exp_parse_src(exp, NFNL_EXP_TUPLE_MASK, optarg); break;
+		case ARG_MASK_SPORT: nl_cli_exp_parse_src_port(exp, NFNL_EXP_TUPLE_MASK, optarg); break;
+		case ARG_MASK_DST: nl_cli_exp_parse_dst(exp, NFNL_EXP_TUPLE_MASK, optarg); break;
+		case ARG_MASK_DPORT: nl_cli_exp_parse_dst_port(exp, NFNL_EXP_TUPLE_MASK, optarg); break;
+        case ARG_NAT_PROTO: nl_cli_exp_parse_l4protonum(exp, NFNL_EXP_TUPLE_NAT, optarg); break;
+        case ARG_NAT_SRC: nl_cli_exp_parse_src(exp, NFNL_EXP_TUPLE_NAT, optarg); break;
+        case ARG_NAT_SPORT: nl_cli_exp_parse_src_port(exp, NFNL_EXP_TUPLE_NAT, optarg); break;
+        case ARG_NAT_DST: nl_cli_exp_parse_dst(exp, NFNL_EXP_TUPLE_NAT, optarg); break;
+        case ARG_NAT_DPORT: nl_cli_exp_parse_dst_port(exp, NFNL_EXP_TUPLE_NAT, optarg); break;
+        case ARG_NAT_DIR: nl_cli_exp_parse_nat_dir(exp, optarg); break;
+		case 'F': nl_cli_exp_parse_family(exp, optarg); break;
+		case ARG_TIMEOUT: nl_cli_exp_parse_timeout(exp, optarg); break;
+		case ARG_HELPER_NAME: nl_cli_exp_parse_helper_name(exp, optarg); break;
+		case ARG_FLAGS: nl_cli_exp_parse_flags(exp, optarg); break;
+		}
+ 	}
+
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_NETFILTER);
+
+	if ((err = nfnl_exp_add(sock, exp, nlflags)) < 0)
+		nl_cli_fatal(err, "Unable to add expectation: %s", nl_geterror(err));
+
+	if (!quiet) {
+		printf("Added ");
+		nl_object_dump(OBJ_CAST(exp), &params);
+	}
+
+	return 0;
+}
diff --git a/src/nf-exp-delete.c b/src/nf-exp-delete.c
new file mode 100644
index 0000000..2ec45ae
--- /dev/null
+++ b/src/nf-exp-delete.c
@@ -0,0 +1,165 @@
+/*
+ * src/nf-exp-delete.c     Delete an expectation
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2007 Philip Craig <philipc@snapgear.com>
+ * Copyright (c) 2007 Secure Computing Corporation
+ * Copyright (c) 2012 Rich Fought <rich.fought@watchguard.com>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/exp.h>
+
+static int quiet = 0;
+
+static void print_usage(void)
+{
+	printf(
+	"Usage: nf-exp-list [OPTION]... [CONNTRACK ENTRY]\n"
+	"\n"
+	"Options\n"
+	"     --replace             Replace the address if it exists.\n"
+	" -q, --quiet               Do not print informal notifications.\n"
+	" -h, --help                Show this help\n"
+	" -v, --version             Show versioning information\n"
+	"\n"
+	"Expectation Selection\n"
+	" -i, --id=NUM                Identifier\n"
+	"     --expect-proto=PROTOCOL Expectation protocol\n"
+	"     --expect-src=ADDR       Expectation source address\n"
+	"     --expect-sport=PORT     Expectation source port\n"
+	"     --expect-dst=ADDR       Expectation destination address\n"
+	"     --expect-dport=PORT     Expectation destination port\n"
+	"     --master-proto=PROTOCOL Master conntrack protocol\n"
+	"     --master-src=ADDR       Master conntrack source address\n"
+	"     --master-sport=PORT     Master conntrack source port\n"
+	"     --master-dst=ADDR       Master conntrack destination address\n"
+	"     --master-dport=PORT     Master conntrack destination port\n"
+	"     --mask-proto=PROTOCOL   Mask protocol\n"
+	"     --mask-src=ADDR         Mask source address\n"
+	"     --mask-sport=PORT       Mask source port\n"
+	"     --mask-dst=ADDR         Mask destination address\n"
+	"     --mask-dport=PORT       Mask destination port\n"
+	" -F, --family=FAMILY         Address family\n"
+	"     --timeout=NUM           Timeout value\n"
+	"     --helper=STRING         Helper Name\n"
+	"     --flags                 Flags\n"
+	);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	struct nl_sock *sock;
+	struct nfnl_exp *exp;
+	struct nl_dump_params params = {
+		.dp_type = NL_DUMP_LINE,
+		.dp_fd = stdout,
+	};
+	int err, nlflags = 0;
+
+ 	exp = nl_cli_exp_alloc();
+ 
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_MARK = 270,
+			ARG_TCP_STATE = 271,
+			ARG_EXPECT_PROTO,
+			ARG_EXPECT_SRC,
+			ARG_EXPECT_SPORT,
+			ARG_EXPECT_DST,
+			ARG_EXPECT_DPORT,
+			ARG_MASTER_PROTO,
+			ARG_MASTER_SRC,
+			ARG_MASTER_SPORT,
+			ARG_MASTER_DST,
+			ARG_MASTER_DPORT,
+ 			ARG_MASK_PROTO,
+ 			ARG_MASK_SRC,
+			ARG_MASK_SPORT,
+			ARG_MASK_DST,
+			ARG_MASK_DPORT,
+			ARG_TIMEOUT,
+ 			ARG_HELPER_NAME,
+			ARG_FLAGS,
+		};
+		static struct option long_opts[] = {
+ 			{ "quiet", 0, 0, 'q' },
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ "id", 1, 0, 'i' },
+			{ "expect-proto", 1, 0, ARG_EXPECT_PROTO },
+			{ "expect-src", 1, 0, ARG_EXPECT_SRC },
+			{ "expect-sport", 1, 0, ARG_EXPECT_SPORT },
+			{ "expect-dst", 1, 0, ARG_EXPECT_DST },
+			{ "expect-dport", 1, 0, ARG_EXPECT_DPORT },
+			{ "master-proto", 1, 0, ARG_MASTER_PROTO },
+			{ "master-src", 1, 0, ARG_MASTER_SRC },
+			{ "master-sport", 1, 0, ARG_MASTER_SPORT },
+			{ "master-dst", 1, 0, ARG_MASTER_DST },
+			{ "master-dport", 1, 0, ARG_MASTER_DPORT },
+			{ "mask-proto", 1, 0, ARG_MASK_PROTO },
+ 			{ "mask-src", 1, 0, ARG_MASK_SRC },
+ 			{ "mask-sport", 1, 0, ARG_MASK_SPORT },
+ 			{ "mask-dst", 1, 0, ARG_MASK_DST },
+ 			{ "mask-dport", 1, 0, ARG_MASK_DPORT },
+			{ "family", 1, 0, 'F' },
+			{ "timeout", 1, 0, ARG_TIMEOUT },
+			{ "helper", 1, 0, ARG_HELPER_NAME },
+ 			{ "flags", 1, 0, ARG_FLAGS},
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "46f:hvi:p:F:", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case '?': exit(NLE_INVAL);
+		case 'q': quiet = 1; break;
+		case '4': nfnl_exp_set_family(exp, AF_INET); break;
+		case '6': nfnl_exp_set_family(exp, AF_INET6); break;
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'i': nl_cli_exp_parse_id(exp, optarg); break;
+		case ARG_EXPECT_PROTO: nl_cli_exp_parse_l4protonum(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_SRC: nl_cli_exp_parse_src(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_SPORT: nl_cli_exp_parse_src_port(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_DST: nl_cli_exp_parse_dst(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_DPORT: nl_cli_exp_parse_dst_port(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_MASTER_PROTO: nl_cli_exp_parse_l4protonum(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_SRC: nl_cli_exp_parse_src(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_SPORT: nl_cli_exp_parse_src_port(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_DST: nl_cli_exp_parse_dst(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_DPORT: nl_cli_exp_parse_dst_port(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASK_PROTO: nl_cli_exp_parse_l4protonum(exp, NFNL_EXP_TUPLE_MASK, optarg); break;
+		case ARG_MASK_SRC: nl_cli_exp_parse_src(exp, NFNL_EXP_TUPLE_MASK, optarg); break;
+		case ARG_MASK_SPORT: nl_cli_exp_parse_src_port(exp, NFNL_EXP_TUPLE_MASK, optarg); break;
+		case ARG_MASK_DST: nl_cli_exp_parse_dst(exp, NFNL_EXP_TUPLE_MASK, optarg); break;
+ 		case ARG_MASK_DPORT: nl_cli_exp_parse_dst_port(exp, NFNL_EXP_TUPLE_MASK, optarg); break;
+		case 'F': nl_cli_exp_parse_family(exp, optarg); break;
+		case ARG_TIMEOUT: nl_cli_exp_parse_timeout(exp, optarg); break;
+		case ARG_HELPER_NAME: nl_cli_exp_parse_helper_name(exp, optarg); break;
+		case ARG_FLAGS: nl_cli_exp_parse_flags(exp, optarg); break;
+		}
+ 	}
+
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_NETFILTER);
+
+	if ((err = nfnl_exp_del(sock, exp, nlflags)) < 0)
+		nl_cli_fatal(err, "Unable to delete expectation: %s", nl_geterror(err));
+
+	if (!quiet) {
+		printf("Deleted ");
+		nl_object_dump(OBJ_CAST(exp), &params);
+	}
+
+	return 0;
+}
diff --git a/src/nf-exp-list.c b/src/nf-exp-list.c
new file mode 100644
index 0000000..1c6ec69
--- /dev/null
+++ b/src/nf-exp-list.c
@@ -0,0 +1,137 @@
+/*
+ * src/nf-exp-list.c     List Expectation Entries
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2007 Philip Craig <philipc@snapgear.com>
+ * Copyright (c) 2007 Secure Computing Corporation
+ * Copyright (c) 2012 Rich Fought <rich.fought@watchguard.com>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/exp.h>
+
+static void print_usage(void)
+{
+	printf(
+	"Usage: nf-exp-list [OPTION]... [EXPECTATION ENTRY]\n"
+	"\n"
+	"Options\n"
+	" -f, --format=TYPE     Output format { brief | details }\n"
+	" -h, --help            Show this help\n"
+	" -v, --version         Show versioning information\n"
+	"\n"
+	"Expectation Selection\n"
+	" -i, --id=NUM                Identifier\n"
+	"     --expect-proto=PROTOCOL Expectation protocol\n"
+	"     --expect-src=ADDR       Expectation source address\n"
+	"     --expect-sport=PORT     Expectation source port\n"
+	"     --expect-dst=ADDR       Expectation destination address\n"
+	"     --expect-dport=PORT     Expectation destination port\n"
+	"     --master-proto=PROTOCOL Master conntrack protocol\n"
+	"     --master-src=ADDR       Master conntrack source address\n"
+	"     --master-sport=PORT     Master conntrack source port\n"
+	"     --master-dst=ADDR       Master conntrack destination address\n"
+	"     --master-dport=PORT     Master conntrack destination port\n"
+	" -F, --family=FAMILY         Address family\n"
+	"     --timeout=NUM           Timeout value\n"
+	"     --helper=STRING         Helper Name\n"
+	//"     --flags                 Flags\n"
+	);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	struct nl_sock *sock;
+	struct nl_cache *exp_cache;
+	struct nfnl_exp *exp;
+	struct nl_dump_params params = {
+		.dp_type = NL_DUMP_LINE,
+		.dp_fd = stdout,
+	};
+ 
+ 	exp = nl_cli_exp_alloc();
+ 
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_MARK = 270,
+			ARG_TCP_STATE = 271,
+			ARG_EXPECT_PROTO,
+			ARG_EXPECT_SRC,
+			ARG_EXPECT_SPORT,
+			ARG_EXPECT_DST,
+			ARG_EXPECT_DPORT,
+			ARG_MASTER_PROTO,
+			ARG_MASTER_SRC,
+			ARG_MASTER_SPORT,
+			ARG_MASTER_DST,
+			ARG_MASTER_DPORT,
+			ARG_TIMEOUT,
+ 			ARG_HELPER_NAME,
+			ARG_FLAGS,
+		};
+		static struct option long_opts[] = {
+			{ "format", 1, 0, 'f' },
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ "id", 1, 0, 'i' },
+			{ "expect-proto", 1, 0, ARG_EXPECT_PROTO },
+			{ "expect-src", 1, 0, ARG_EXPECT_SRC },
+			{ "expect-sport", 1, 0, ARG_EXPECT_SPORT },
+			{ "expect-dst", 1, 0, ARG_EXPECT_DST },
+			{ "expect-dport", 1, 0, ARG_EXPECT_DPORT },
+			{ "master-proto", 1, 0, ARG_MASTER_PROTO },
+			{ "master-src", 1, 0, ARG_MASTER_SRC },
+			{ "master-sport", 1, 0, ARG_MASTER_SPORT },
+			{ "master-dst", 1, 0, ARG_MASTER_DST },
+			{ "master-dport", 1, 0, ARG_MASTER_DPORT },
+			{ "family", 1, 0, 'F' },
+			{ "timeout", 1, 0, ARG_TIMEOUT },
+			{ "helper", 1, 0, ARG_HELPER_NAME },
+			{ "flags", 1, 0, ARG_FLAGS},
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "46f:hvi:p:F:", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case '?': exit(NLE_INVAL);
+		case '4': nfnl_exp_set_family(exp, AF_INET); break;
+		case '6': nfnl_exp_set_family(exp, AF_INET6); break;
+		case 'f': params.dp_type = nl_cli_parse_dumptype(optarg); break;
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'i': nl_cli_exp_parse_id(exp, optarg); break;
+		case ARG_EXPECT_PROTO: nl_cli_exp_parse_l4protonum(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_SRC: nl_cli_exp_parse_src(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_SPORT: nl_cli_exp_parse_src_port(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_DST: nl_cli_exp_parse_dst(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_EXPECT_DPORT: nl_cli_exp_parse_dst_port(exp, NFNL_EXP_TUPLE_EXPECT, optarg); break;
+		case ARG_MASTER_PROTO: nl_cli_exp_parse_l4protonum(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_SRC: nl_cli_exp_parse_src(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_SPORT: nl_cli_exp_parse_src_port(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_DST: nl_cli_exp_parse_dst(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case ARG_MASTER_DPORT: nl_cli_exp_parse_dst_port(exp, NFNL_EXP_TUPLE_MASTER, optarg); break;
+		case 'F': nl_cli_exp_parse_family(exp, optarg); break;
+		case ARG_TIMEOUT: nl_cli_exp_parse_timeout(exp, optarg); break;
+		case ARG_HELPER_NAME: nl_cli_exp_parse_helper_name(exp, optarg); break;
+		case ARG_FLAGS: nl_cli_exp_parse_flags(exp, optarg); break;
+		}
+ 	}
+
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_NETFILTER);
+	exp_cache = nl_cli_exp_alloc_cache(sock);
+
+	nl_cache_dump_filter(exp_cache, &params, OBJ_CAST(exp));
+
+	return 0;
+}
diff --git a/src/nf-log.c b/src/nf-log.c
index 26bae6d..913ba16 100644
--- a/src/nf-log.c
+++ b/src/nf-log.c
@@ -96,7 +96,7 @@
 
 	copy_range = 0xFFFF;
 	if (argc > 4)
-		copy_mode = atoi(argv[4]);
+		copy_range = atoi(argv[4]);
 	nfnl_log_set_copy_range(log, copy_range);
 
 	if ((err = nfnl_log_create(nf_sock, log)) < 0)
diff --git a/src/nf-queue.c b/src/nf-queue.c
index 3fb3c11..922d9c8 100644
--- a/src/nf-queue.c
+++ b/src/nf-queue.c
@@ -13,6 +13,7 @@
 
 #include <netlink/cli/utils.h>
 #include <netlink/cli/link.h>
+#include <netinet/in.h>
 #include <linux/netfilter.h>
 #include <linux/netfilter/nfnetlink_queue.h>
 #include <netlink/netfilter/nfnl.h>
@@ -41,31 +42,7 @@
 		.dp_fd = stdout,
 		.dp_dump_msgtype = 1,
 	};
-	uint32_t packet_id = nfnl_queue_msg_get_packetid(msg);
-	static uint32_t next_packet_id = 0;
-	struct nfnl_queue_msg *lost_msg = NULL;
-	uint8_t family;
-	uint16_t group;
 
-	if (packet_id > next_packet_id) {
-		printf("Warning: %d Out of order packets.  Queue or socket overload \n", packet_id - next_packet_id);
-		group = nfnl_queue_msg_get_group(msg);
-		family = nfnl_queue_msg_get_family(msg);
-		lost_msg = nfnl_queue_msg_alloc();
-
-		do {
-			nfnl_queue_msg_set_group(lost_msg, group);
-			nfnl_queue_msg_set_family(lost_msg, family);
-			nfnl_queue_msg_set_packetid(lost_msg, next_packet_id);
-			nfnl_queue_msg_set_verdict(lost_msg, NF_ACCEPT);
-			nfnl_queue_msg_send_verdict(nf_sock, lost_msg);
-			next_packet_id++;
-		} while (packet_id > next_packet_id);
-
-		nfnl_queue_msg_put(lost_msg);
-	}
-
-	next_packet_id = packet_id + 1;
 	nfnl_queue_msg_set_verdict(msg, NF_ACCEPT);
 	nl_object_dump(obj, &dp);
 	nfnl_queue_msg_send_verdict(nf_sock, msg);
diff --git a/src/nl-addr-list.c b/src/nl-addr-list.c
index 9b045a5..20995a8 100644
--- a/src/nl-addr-list.c
+++ b/src/nl-addr-list.c
@@ -41,7 +41,7 @@
 
 static char *prefix;
 
-void print_prefix(struct nl_dump_params *p, int line)
+static void print_prefix(struct nl_dump_params *p, int line)
 {
 	if (prefix)
 		nl_dump(p, "%s", prefix);
@@ -65,7 +65,7 @@
 		     nl_addr2str(rtnl_addr_get_local(addr), buf, sizeof(buf)));
 
 	nl_dump_line(p, "%s_IFINDEX=%u\n", pfx, rtnl_addr_get_ifindex(addr));
-	link_cache = nl_cache_mngt_require("route/link");
+	link_cache = nl_cache_mngt_require_safe("route/link");
 	if (link_cache)
 		nl_dump_line(p, "%s_IFNAME=%s\n", pfx,
 			     rtnl_link_i2name(link_cache,
@@ -94,6 +94,9 @@
 	nl_dump_line(p, "%s_CACHEINFO_VALID=%u\n", pfx,
 		     rtnl_addr_get_valid_lifetime(addr));
 
+	if (link_cache)
+		nl_cache_put(link_cache);
+
 #if 0
 	if (addr->ce_mask & ADDR_ATTR_CACHEINFO) {
 		struct rtnl_addr_cacheinfo *ci = &addr->a_cacheinfo;
diff --git a/src/nl-class-add.c b/src/nl-class-add.c
new file mode 100644
index 0000000..b9a17dc
--- /dev/null
+++ b/src/nl-class-add.c
@@ -0,0 +1,155 @@
+/*
+ * src/nl-class-add.c     Add/Update/Replace Traffic Class
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/cli/qdisc.h>
+#include <netlink/cli/class.h>
+#include <netlink/cli/link.h>
+
+#include <netlink-private/route/tc-api.h>
+
+static int quiet = 0;
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-class-add [OPTIONS]... class [CONFIGURATION]...\n"
+"\n"
+"OPTIONS\n"
+" -q, --quiet               Do not print informal notifications.\n"
+" -h, --help                Show this help text.\n"
+" -v, --version             Show versioning information.\n"
+"     --update              Update class if it exists.\n"
+"     --update-only         Only update class, never create it.\n"
+" -d, --dev=DEV             Network device the class should be attached to.\n"
+" -i, --id=ID               ID of new class (default: auto-generated)\n"
+" -p, --parent=ID           ID of parent { root | ingress | class-ID }\n"
+"     --mtu=SIZE            Overwrite MTU (default: MTU of network device)\n"
+"     --mpu=SIZE            Minimum packet size on the link (default: 0).\n"
+"     --overhead=SIZE       Overhead in bytes per packet (default: 0).\n"
+"     --linktype=TYPE       Overwrite linktype (default: type of network device)\n"
+"\n"
+"CONFIGURATION\n"
+" -h, --help                Show help text of class specific options.\n"
+"\n"
+"EXAMPLE\n"
+"   $ nl-class-add --dev=eth1 --parent=root htb --rate=100mbit\n"
+"\n"
+	);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	struct nl_sock *sock;
+	struct rtnl_class *class;
+	struct rtnl_tc *tc;
+	struct nl_cache *link_cache;
+	struct nl_dump_params dp = {
+		.dp_type = NL_DUMP_DETAILS,
+		.dp_fd = stdout,
+	};
+	struct nl_cli_tc_module *tm;
+	struct rtnl_tc_ops *ops;
+	int err, flags = NLM_F_CREATE | NLM_F_EXCL;
+	char *kind, *id = NULL;
+ 
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_ROUTE);
+
+	link_cache = nl_cli_link_alloc_cache(sock);
+
+ 	class = nl_cli_class_alloc();
+	tc = (struct rtnl_tc *) class;
+ 
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_UPDATE = 257,
+			ARG_UPDATE_ONLY = 258,
+			ARG_MTU,
+			ARG_MPU,
+			ARG_OVERHEAD,
+			ARG_LINKTYPE,
+		};
+		static struct option long_opts[] = {
+			{ "quiet", 0, 0, 'q' },
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ "dev", 1, 0, 'd' },
+			{ "parent", 1, 0, 'p' },
+			{ "id", 1, 0, 'i' },
+			{ "update", 0, 0, ARG_UPDATE },
+			{ "update-only", 0, 0, ARG_UPDATE_ONLY },
+			{ "mtu", 1, 0, ARG_MTU },
+			{ "mpu", 1, 0, ARG_MPU },
+			{ "overhead", 1, 0, ARG_OVERHEAD },
+			{ "linktype", 1, 0, ARG_LINKTYPE },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "+qhvd:p:i:",
+				long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'q': quiet = 1; break;
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'd': nl_cli_tc_parse_dev(tc, link_cache, optarg); break;
+		case 'p': nl_cli_tc_parse_parent(tc, optarg); break;
+		case 'i': id = strdup(optarg); break;
+		case ARG_UPDATE: flags = NLM_F_CREATE; break;
+		case ARG_UPDATE_ONLY: flags = 0; break;
+		case ARG_MTU: nl_cli_tc_parse_mtu(tc, optarg); break;
+		case ARG_MPU: nl_cli_tc_parse_mpu(tc, optarg); break;
+		case ARG_OVERHEAD: nl_cli_tc_parse_overhead(tc, optarg); break;
+		case ARG_LINKTYPE: nl_cli_tc_parse_linktype(tc, optarg); break;
+		}
+ 	}
+
+	if (optind >= argc)
+		print_usage();
+
+	if (!rtnl_tc_get_ifindex(tc))
+		nl_cli_fatal(EINVAL, "You must specify a network device (--dev=XXX)");
+
+	if (!rtnl_tc_get_parent(tc))
+		nl_cli_fatal(EINVAL, "You must specify a parent (--parent=XXX)");
+
+	if (id) {
+		nl_cli_tc_parse_handle(tc, id, 1);
+		free(id);
+	}
+
+	kind = argv[optind++];
+	rtnl_tc_set_kind(tc, kind);
+
+	if (!(ops = rtnl_tc_get_ops(tc)))
+		nl_cli_fatal(ENOENT, "Unknown class \"%s\"", kind);
+
+	if (!(tm = nl_cli_tc_lookup(ops)))
+		nl_cli_fatal(ENOTSUP, "class type \"%s\" not supported.", kind);
+
+	tm->tm_parse_argv(tc, argc, argv);
+
+	if (!quiet) {
+		printf("Adding ");
+		nl_object_dump(OBJ_CAST(class), &dp);
+ 	}
+
+	if ((err = rtnl_class_add(sock, class, flags)) < 0)
+		nl_cli_fatal(EINVAL, "Unable to add class: %s", nl_geterror(err));
+
+	return 0;
+}
diff --git a/src/nl-class-delete.c b/src/nl-class-delete.c
new file mode 100644
index 0000000..37657a4
--- /dev/null
+++ b/src/nl-class-delete.c
@@ -0,0 +1,128 @@
+/*
+ * src/nl-class-delete.c     Delete Traffic Classes
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/class.h>
+#include <netlink/cli/link.h>
+
+static int quiet = 0, default_yes = 0, deleted = 0, interactive = 0;
+static struct nl_sock *sock;
+
+static void print_usage(void)
+{
+	printf(
+	"Usage: nl-class-delete [OPTION]... [class]\n"
+	"\n"
+	"OPTIONS\n"
+	"     --interactive     Run interactively.\n"
+	"     --yes             Set default answer to yes.\n"
+	" -q, --quiet           Do not print informal notifications.\n"
+	" -h, --help            Show this help text and exit.\n"
+	" -v, --version         Show versioning information and exit.\n"
+	"\n"
+	" -d, --dev=DEV         Device the class is attached to.\n"
+	" -p, --parent=ID       Identifier of parent qdisc/class.\n"
+	" -i, --id=ID           Identifier\n"
+	" -k, --kind=NAME       Kind of class (e.g. pfifo_fast)\n"
+	"\n"
+	"EXAMPLE\n"
+	"    # Delete all classes on eth0 attached to parent 1:\n"
+	"    $ nl-class-delete --dev eth0 --parent 1:\n"
+	"\n"
+	);
+
+	exit(0);
+}
+
+static void delete_cb(struct nl_object *obj, void *arg)
+{
+	struct rtnl_class *class = nl_object_priv(obj);
+	struct nl_dump_params params = {
+		.dp_type = NL_DUMP_LINE,
+		.dp_fd = stdout,
+	};
+	int err;
+
+	if (interactive && !nl_cli_confirm(obj, &params, default_yes))
+		return;
+
+	if ((err = rtnl_class_delete(sock, class)) < 0)
+		nl_cli_fatal(err, "Unable to delete class: %s\n", nl_geterror(err));
+
+	if (!quiet) {
+		printf("Deleted ");
+		nl_object_dump(obj, &params);
+	}
+
+	deleted++;
+}
+
+int main(int argc, char *argv[])
+{
+	struct rtnl_class *class;
+	struct rtnl_tc *tc;
+	struct nl_cache *link_cache, *class_cache;
+ 
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_ROUTE);
+	link_cache = nl_cli_link_alloc_cache(sock);
+ 	class = nl_cli_class_alloc();
+	tc = (struct rtnl_tc *) class;
+ 
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_YES = 257,
+			ARG_INTERACTIVE = 258,
+		};
+		static struct option long_opts[] = {
+			{ "interactive", 0, 0, ARG_INTERACTIVE },
+			{ "yes", 0, 0, ARG_YES },
+			{ "quiet", 0, 0, 'q' },
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ "dev", 1, 0, 'd' },
+			{ "parent", 1, 0, 'p' },
+			{ "id", 1, 0, 'i' },
+			{ "kind", 1, 0, 'k' },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "qhvd:p:i:k:", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case '?': nl_cli_fatal(EINVAL, "Invalid options");
+		case ARG_INTERACTIVE: interactive = 1; break;
+		case ARG_YES: default_yes = 1; break;
+		case 'q': quiet = 1; break;
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'd': nl_cli_tc_parse_dev(tc, link_cache, optarg); break;
+		case 'p': nl_cli_tc_parse_parent(tc, optarg); break;
+		case 'i': nl_cli_tc_parse_handle(tc, optarg, 0); break;
+		case 'k': nl_cli_tc_parse_kind(tc, optarg); break;
+		}
+ 	}
+
+	if (!rtnl_tc_get_ifindex(tc))
+		nl_cli_fatal(EINVAL, "You must specify a network device (--dev=XXX)");
+
+	class_cache = nl_cli_class_alloc_cache(sock, rtnl_tc_get_ifindex(tc));
+
+	nl_cache_foreach_filter(class_cache, OBJ_CAST(class), delete_cb, NULL);
+
+	if (!quiet)
+		printf("Deleted %d classs\n", deleted);
+
+	return 0;
+}
diff --git a/src/nl-class-list.c b/src/nl-class-list.c
new file mode 100644
index 0000000..c2423fb
--- /dev/null
+++ b/src/nl-class-list.c
@@ -0,0 +1,117 @@
+/*
+ * src/nl-class-list.c     List Traffic Classes
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/cli/class.h>
+#include <netlink/cli/link.h>
+
+static struct nl_sock *sock;
+
+static struct nl_dump_params params = {
+	.dp_type = NL_DUMP_LINE,
+};
+
+static void print_usage(void)
+{
+	printf(
+	"Usage: nl-class-list [OPTION]...\n"
+	"\n"
+	"OPTIONS\n"
+	"     --details         Show details\n"
+	"     --stats           Show statistics\n"
+	" -h, --help            Show this help\n"
+	" -v, --version         Show versioning information\n"
+	"\n"
+	" -d, --dev=DEV         Device the class is attached to. (default: all)\n"
+	" -p, --parent=ID       Identifier of parent class.\n"
+	" -i, --id=ID           Identifier.\n"
+	" -k, --kind=NAME       Kind of class (e.g. pfifo_fast)\n"
+	"\n"
+	"EXAMPLE\n"
+	"    # Display statistics of all classes on eth0\n"
+	"    $ nl-class-list --stats --dev=eth0\n"
+	"\n"
+	);
+	exit(0);
+}
+
+static void __dump_class(int ifindex, struct rtnl_class *filter)
+{
+	struct nl_cache *cache;
+
+	cache = nl_cli_class_alloc_cache(sock, ifindex);
+	nl_cache_dump_filter(cache, &params, OBJ_CAST(filter));
+}
+
+static void dump_class(struct nl_object *obj, void *arg)
+{
+	struct rtnl_link *link = nl_object_priv(obj);
+
+	__dump_class(rtnl_link_get_ifindex(link), arg);
+}
+
+int main(int argc, char *argv[])
+{
+	struct rtnl_class *class;
+	struct rtnl_tc *tc;
+	struct nl_cache *link_cache;
+	int ifindex;
+ 
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_ROUTE);
+	link_cache = nl_cli_link_alloc_cache(sock);
+ 	class = nl_cli_class_alloc();
+	tc = (struct rtnl_tc *) class;
+
+	params.dp_fd = stdout;
+ 
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_DETAILS = 257,
+			ARG_STATS = 258,
+		};
+		static struct option long_opts[] = {
+			{ "details", 0, 0, ARG_DETAILS },
+			{ "stats", 0, 0, ARG_STATS },
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ "dev", 1, 0, 'd' },
+			{ "parent", 1, 0, 'p' },
+			{ "id", 1, 0, 'i' },
+			{ "kind", 1, 0, 'k' },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "hvd:p:i:k:", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case ARG_DETAILS: params.dp_type = NL_DUMP_DETAILS; break;
+		case ARG_STATS: params.dp_type = NL_DUMP_STATS; break;
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'd': nl_cli_tc_parse_dev(tc, link_cache, optarg); break;
+		case 'p': nl_cli_tc_parse_parent(tc, optarg); break;
+		case 'i': nl_cli_tc_parse_handle(tc, optarg, 0); break;
+		case 'k': nl_cli_tc_parse_kind(tc, optarg); break;
+		}
+ 	}
+
+	if ((ifindex = rtnl_tc_get_ifindex(tc)))
+		__dump_class(ifindex, class);
+	 else
+		nl_cache_foreach(link_cache, dump_class, class);
+
+	return 0;
+}
diff --git a/src/nl-classid-lookup.c b/src/nl-classid-lookup.c
new file mode 100644
index 0000000..1d45d0b
--- /dev/null
+++ b/src/nl-classid-lookup.c
@@ -0,0 +1,87 @@
+/*
+ * src/nl-classid-lookup.c     Lookup classid
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-classid-lookup [OPTIONS]... NAME\n"
+"\n"
+"OPTIONS\n"
+" -h, --help                Show this help text.\n"
+" -v, --version             Show versioning information.\n"
+" -r, --reverse             Do a reverse lookup, i.e. classid to name.\n"
+"     --raw                 Print the raw classid, not pretty printed.\n"
+"\n"
+"EXAMPLE\n"
+"   $ nl-classid-lookup low_latency\n"
+"   $ nl-classid-lookup -r 1:12\n"
+"\n"
+	);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	uint32_t classid;
+	char *name;
+	int err, reverse = 0, raw = 0;
+
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_RAW = 257,
+		};
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ "reverse", 0, 0, 'r' },
+			{ "raw", 0, 0, ARG_RAW },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "hvr", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'r': reverse = 1; break;
+		case ARG_RAW: raw = 1; break;
+		}
+ 	}
+
+	if (optind >= argc)
+		print_usage();
+
+	name = argv[optind++];
+
+	/*
+	 * We use rtnl_tc_str2handle() even while doing a reverse lookup. This
+	 * allows for name -> name lookups. This is intentional, it does not
+	 * do any harm and avoids duplicating a lot of code.
+	 */
+	if ((err = rtnl_tc_str2handle(name, &classid)) < 0)
+		nl_cli_fatal(err, "Unable to lookup classid \"%s\": %s",
+			     name, nl_geterror(err));
+
+	if (reverse) {
+		char buf[64];
+		printf("%s\n", rtnl_tc_handle2str(classid, buf, sizeof(buf)));
+	} else if (raw)
+		printf("%#x\n", classid);
+	else
+		printf("%x:%x\n", TC_H_MAJ(classid) >> 16, TC_H_MIN(classid));
+
+	return 0;
+}
diff --git a/src/nl-cls-add.c b/src/nl-cls-add.c
index 997f02f..6acb320 100644
--- a/src/nl-cls-add.c
+++ b/src/nl-cls-add.c
@@ -5,29 +5,45 @@
  *	modify it under the terms of the GNU General Public License as
  *	published by the Free Software Foundation version 2 of the License.
  *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
  */
 
-#include "cls/utils.h"
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/cli/cls.h>
+#include <netlink/cli/link.h>
+
+#include <netlink-private/route/tc-api.h>
 
 static int quiet = 0;
 
 static void print_usage(void)
 {
 	printf(
-"Usage: nl-cls-add [OPTION]... [CLASSIFIER] TYPE [TYPE OPTIONS]...\n"
+"Usage: nl-cls-add [OPTIONS]... classifier [CONFIGURATION]...\n"
 "\n"
-"Options\n"
+"OPTIONS\n"
 " -q, --quiet               Do not print informal notifications.\n"
-" -h, --help                Show this help.\n"
+" -h, --help                Show this help text.\n"
 " -v, --version             Show versioning information.\n"
+"     --update              Update classifier if it exists.\n"
+"     --update-only         Only update classifier, never create it.\n"
+" -d, --dev=DEV             Network device the classifier should be attached to.\n"
+" -i, --id=ID               ID of new classifier (default: auto-generated)\n"
+" -p, --parent=ID           ID of parent { root | ingress | class-ID }\n"
+"     --protocol=PROTO      Protocol to match (default: all)\n"
+"     --prio=PRIO           Priority (default: 0)\n"
+"     --mtu=SIZE            Overwrite MTU (default: MTU of network device)\n"
+"     --mpu=SIZE            Minimum packet size on the link (default: 0).\n"
+"     --overhead=SIZE       Overhead in bytes per packet (default: 0).\n"
+"     --linktype=TYPE       Overwrite linktype (default: type of network device)\n"
 "\n"
-"Classifier Options\n"
-" -d, --dev=DEV             Device the classifier should be assigned to.\n"
-" -p, --parent=HANDLE       Parent QDisc\n"
-"     --proto=PROTO         Protocol (default=IPv4)\n"
-"     --prio=NUM            Priority (0..256)\n"
-"     --id=HANDLE           Unique identifier\n"
+"CONFIGURATION\n"
+" -h, --help                Show help text of classifier specific options.\n"
+"\n"
+"EXAMPLE\n"
+"   $ nl-cls-add --dev=eth1 --parent=q_root basic --target c_www\n"
+"\n"
 	);
 	exit(0);
 }
@@ -36,27 +52,36 @@
 {
 	struct nl_sock *sock;
 	struct rtnl_cls *cls;
+	struct rtnl_tc *tc;
 	struct nl_cache *link_cache;
-	struct rtnl_cls_ops *ops;
-	struct cls_module *mod;
 	struct nl_dump_params dp = {
 		.dp_type = NL_DUMP_DETAILS,
 		.dp_fd = stdout,
 	};
-	char *kind;
-	int err, nlflags = NLM_F_CREATE;
+	struct nl_cli_tc_module *tm;
+	struct rtnl_tc_ops *ops;
+	int err, flags = NLM_F_CREATE | NLM_F_EXCL;
+	char *kind, *id = NULL;
  
-	sock = nlt_alloc_socket();
-	nlt_connect(sock, NETLINK_ROUTE);
-	link_cache = nlt_alloc_link_cache(sock);
-	cls = nlt_alloc_cls();
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_ROUTE);
 
+	link_cache = nl_cli_link_alloc_cache(sock);
+
+ 	cls = nl_cli_cls_alloc();
+	tc = (struct rtnl_tc *) cls;
+ 
 	for (;;) {
 		int c, optidx = 0;
 		enum {
-			ARG_PROTO = 257,
-			ARG_PRIO = 258,
-			ARG_ID,
+			ARG_UPDATE = 257,
+			ARG_UPDATE_ONLY = 258,
+			ARG_MTU,
+			ARG_MPU,
+			ARG_OVERHEAD,
+			ARG_LINKTYPE,
+			ARG_PROTO,
+			ARG_PRIO,
 		};
 		static struct option long_opts[] = {
 			{ "quiet", 0, 0, 'q' },
@@ -64,54 +89,75 @@
 			{ "version", 0, 0, 'v' },
 			{ "dev", 1, 0, 'd' },
 			{ "parent", 1, 0, 'p' },
+			{ "id", 1, 0, 'i' },
 			{ "proto", 1, 0, ARG_PROTO },
 			{ "prio", 1, 0, ARG_PRIO },
-			{ "id", 1, 0, ARG_ID },
+			{ "update", 0, 0, ARG_UPDATE },
+			{ "update-only", 0, 0, ARG_UPDATE_ONLY },
+			{ "mtu", 1, 0, ARG_MTU },
+			{ "mpu", 1, 0, ARG_MPU },
+			{ "overhead", 1, 0, ARG_OVERHEAD },
+			{ "linktype", 1, 0, ARG_LINKTYPE },
 			{ 0, 0, 0, 0 }
 		};
 	
-		c = getopt_long(argc, argv, "+qhva:d:", long_opts, &optidx);
+		c = getopt_long(argc, argv, "+qhvd:p:i:",
+				long_opts, &optidx);
 		if (c == -1)
 			break;
 
 		switch (c) {
-		case '?': exit(NLE_INVAL);
 		case 'q': quiet = 1; break;
 		case 'h': print_usage(); break;
-		case 'v': nlt_print_version(); break;
-		case 'd': parse_dev(cls, link_cache, optarg); break;
-		case 'p': parse_parent(cls, optarg); break;
-		case ARG_PRIO: parse_prio(cls, optarg); break;
-		case ARG_ID: parse_handle(cls, optarg); break;
-		case ARG_PROTO: parse_proto(cls, optarg); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'd': nl_cli_tc_parse_dev(tc, link_cache, optarg); break;
+		case 'p': nl_cli_tc_parse_parent(tc, optarg); break;
+		case 'i': id = strdup(optarg); break;
+		case ARG_UPDATE: flags = NLM_F_CREATE; break;
+		case ARG_UPDATE_ONLY: flags = 0; break;
+		case ARG_MTU: nl_cli_tc_parse_mtu(tc, optarg); break;
+		case ARG_MPU: nl_cli_tc_parse_mpu(tc, optarg); break;
+		case ARG_OVERHEAD: nl_cli_tc_parse_overhead(tc, optarg); break;
+		case ARG_LINKTYPE: nl_cli_tc_parse_linktype(tc, optarg); break;
+		case ARG_PROTO: nl_cli_cls_parse_proto(cls, optarg); break;
+		case ARG_PRIO:
+			rtnl_cls_set_prio(cls, nl_cli_parse_u32(optarg));
+			break;
 		}
  	}
 
-	if (optind >= argc) {
+	if (optind >= argc)
 		print_usage();
-		fatal(EINVAL, "Missing classifier type");
+
+	if (!rtnl_tc_get_ifindex(tc))
+		nl_cli_fatal(EINVAL, "You must specify a network device (--dev=XXX)");
+
+	if (!rtnl_tc_get_parent(tc))
+		nl_cli_fatal(EINVAL, "You must specify a parent (--parent=XXX)");
+
+	if (id) {
+		nl_cli_tc_parse_handle(tc, id, 1);
+		free(id);
 	}
 
 	kind = argv[optind++];
-	if ((err = rtnl_cls_set_kind(cls, kind)) < 0)
-		fatal(ENOENT, "Unknown classifier type \"%s\".", kind);
-	
-	ops = rtnl_cls_get_ops(cls);
-	if (!(mod = lookup_cls_mod(ops)))
-		fatal(ENOTSUP, "Classifier type \"%s\" not supported.", kind);
+	rtnl_tc_set_kind(tc, kind);
 
-	mod->parse_argv(cls, argc, argv);
+	if (!(ops = rtnl_tc_get_ops(tc)))
+		nl_cli_fatal(ENOENT, "Unknown classifier \"%s\".", kind);
 
-	printf("Adding ");
-	nl_object_dump(OBJ_CAST(cls), &dp);
+	if (!(tm = nl_cli_tc_lookup(ops)))
+		nl_cli_fatal(ENOTSUP, "Classifier type \"%s\" not supported.", kind);
 
-	if ((err = rtnl_cls_add(sock, cls, nlflags)) < 0)
-		fatal(err, "Unable to add classifier: %s", nl_geterror(err));
+	tm->tm_parse_argv(tc, argc, argv);
 
 	if (!quiet) {
-		printf("Added ");
+		printf("Adding ");
 		nl_object_dump(OBJ_CAST(cls), &dp);
  	}
 
+	if ((err = rtnl_cls_add(sock, cls, flags)) < 0)
+		nl_cli_fatal(EINVAL, "Unable to add classifier: %s", nl_geterror(err));
+
 	return 0;
 }
diff --git a/src/nl-cls-delete.c b/src/nl-cls-delete.c
index cfdc170..2b3db1f 100644
--- a/src/nl-cls-delete.c
+++ b/src/nl-cls-delete.c
@@ -6,52 +6,59 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2010 Thomas Graf <tgraf@suug.ch>
  */
 
-#include "cls/utils.h"
+#include <netlink/cli/utils.h>
+#include <netlink/cli/cls.h>
+#include <netlink/cli/link.h>
 
-static int interactive = 0, default_yes = 0, quiet = 0;
-static int deleted = 0;
+static int quiet = 0, default_yes = 0, deleted = 0, interactive = 0;
 static struct nl_sock *sock;
 
 static void print_usage(void)
 {
 	printf(
-	"Usage: nl-cls-list [OPTION]... [CLASSIFIER]\n"
-	"\n"
-	"Options\n"
-	" -i, --interactive     Run interactively\n"
-	"     --yes             Set default answer to yes\n"
-	" -q, --quiet		Do not print informal notifications\n"
-	" -h, --help            Show this help\n"
-	" -v, --version		Show versioning information\n"
-	"\n"
-	"Classifier Options\n"
-	" -d, --dev=DEV         Device the classifier should be assigned to.\n"
-	" -p, --parent=HANDLE   Parent qdisc/class\n"
-	"     --proto=PROTO     Protocol\n"
-	"     --prio=NUM        Priority (0..256)\n"
-	"     --id=HANDLE       Unique identifier\n"
+"Usage: nl-cls-delete [OPTION]... [class]\n"
+"\n"
+"OPTIONS\n"
+"     --interactive         Run interactively.\n"
+"     --yes                 Set default answer to yes.\n"
+" -q, --quiet               Do not print informal notifications.\n"
+" -h, --help                Show this help text and exit.\n"
+" -v, --version             Show versioning information and exit.\n"
+"\n"
+" -d, --dev=DEV             Device the classifer is attached to.\n"
+" -p, --parent=ID           Identifier of parent qdisc/class.\n"
+" -i, --id=ID               Identifier\n"
+" -k, --kind=NAME           Kind of classifier (e.g. basic, u32, fw)\n"
+"     --protocol=PROTO      Protocol to match (default: all)\n"
+"     --prio=PRIO           Priority (default: 0)\n"
+"\n"
+"EXAMPLE\n"
+"    # Delete all classifiers on eth0 attached to parent q_root:\n"
+"    $ nl-cls-delete --dev eth0 --parent q_root:\n"
+"\n"
 	);
+
 	exit(0);
 }
 
 static void delete_cb(struct nl_object *obj, void *arg)
 {
-	struct rtnl_cls *cls = (struct rtnl_cls *) obj;
+	struct rtnl_cls *cls = nl_object_priv(obj);
 	struct nl_dump_params params = {
 		.dp_type = NL_DUMP_LINE,
 		.dp_fd = stdout,
 	};
 	int err;
 
-	if (interactive && !nlt_confirm(obj, &params, default_yes))
+	if (interactive && !nl_cli_confirm(obj, &params, default_yes))
 		return;
 
 	if ((err = rtnl_cls_delete(sock, cls, 0)) < 0)
-		fatal(err, "Unable to delete classifier: %s",
-		      nl_geterror(err));
+		nl_cli_fatal(err, "Unable to delete classifier: %s\n",
+				nl_geterror(err));
 
 	if (!quiet) {
 		printf("Deleted ");
@@ -61,73 +68,88 @@
 	deleted++;
 }
 
+static void __delete_link(int ifindex, struct rtnl_cls *filter)
+{
+	struct nl_cache *cache;
+	uint32_t parent = rtnl_tc_get_parent((struct rtnl_tc *) filter);
+
+	cache = nl_cli_cls_alloc_cache(sock, ifindex, parent);
+	nl_cache_foreach_filter(cache, OBJ_CAST(filter), delete_cb, NULL);
+	nl_cache_free(cache);
+}
+
+static void delete_link(struct nl_object *obj, void *arg)
+{
+	struct rtnl_link *link = nl_object_priv(obj);
+
+	__delete_link(rtnl_link_get_ifindex(link), arg);
+}
+
 int main(int argc, char *argv[])
 {
-	struct nl_cache *link_cache, *cls_cache;
 	struct rtnl_cls *cls;
-	int nf = 0, err;
-
-	sock = nlt_alloc_socket();
-	nlt_connect(sock, NETLINK_ROUTE);
-	link_cache = nlt_alloc_link_cache(sock);
-	cls = nlt_alloc_cls();
-
+	struct rtnl_tc *tc;
+	struct nl_cache *link_cache;
+	int ifindex;
+ 
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_ROUTE);
+	link_cache = nl_cli_link_alloc_cache(sock);
+ 	cls = nl_cli_cls_alloc();
+	tc = (struct rtnl_tc *) cls;
+ 
 	for (;;) {
 		int c, optidx = 0;
 		enum {
-			ARG_PRIO = 257,
-			ARG_PROTO = 258,
-			ARG_ID,
-			ARG_YES,
+			ARG_YES = 257,
+			ARG_INTERACTIVE = 258,
+			ARG_PROTO,
+			ARG_PRIO,
 		};
 		static struct option long_opts[] = {
-			{ "interactive", 0, 0, 'i' },
+			{ "interactive", 0, 0, ARG_INTERACTIVE },
 			{ "yes", 0, 0, ARG_YES },
 			{ "quiet", 0, 0, 'q' },
 			{ "help", 0, 0, 'h' },
 			{ "version", 0, 0, 'v' },
 			{ "dev", 1, 0, 'd' },
 			{ "parent", 1, 0, 'p' },
+			{ "id", 1, 0, 'i' },
+			{ "kind", 1, 0, 'k' },
 			{ "proto", 1, 0, ARG_PROTO },
 			{ "prio", 1, 0, ARG_PRIO },
-			{ "id", 1, 0, ARG_ID },
 			{ 0, 0, 0, 0 }
 		};
 	
-		c = getopt_long(argc, argv, "iqhvd:p:", long_opts, &optidx);
+		c = getopt_long(argc, argv, "qhvd:p:i:k:", long_opts, &optidx);
 		if (c == -1)
 			break;
 
 		switch (c) {
-		case 'i': interactive = 1; break;
+		case '?': nl_cli_fatal(EINVAL, "Invalid options");
+		case ARG_INTERACTIVE: interactive = 1; break;
 		case ARG_YES: default_yes = 1; break;
 		case 'q': quiet = 1; break;
 		case 'h': print_usage(); break;
-		case 'v': nlt_print_version(); break;
-		case 'd': nf++; parse_dev(cls, link_cache, optarg); break;
-		case 'p': nf++; parse_parent(cls, optarg); break;
-		case ARG_PRIO: nf++; parse_prio(cls, optarg); break;
-		case ARG_ID: nf++; parse_handle(cls, optarg); break;
-		case ARG_PROTO: nf++; parse_proto(cls, optarg); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'd': nl_cli_tc_parse_dev(tc, link_cache, optarg); break;
+		case 'p': nl_cli_tc_parse_parent(tc, optarg); break;
+		case 'i': nl_cli_tc_parse_handle(tc, optarg, 0); break;
+		case 'k': nl_cli_tc_parse_kind(tc, optarg); break;
+		case ARG_PROTO: nl_cli_cls_parse_proto(cls, optarg); break;
+		case ARG_PRIO:
+			rtnl_cls_set_prio(cls, nl_cli_parse_u32(optarg));
+			break;
 		}
-	}
+ 	}
 
-	if (nf == 0 && !interactive && !default_yes) {
-		fprintf(stderr, "You attempted to delete all classifiers in "
-			"non-interactive mode, aborting.\n");
-		exit(0);
-	}
-
-	err = rtnl_cls_alloc_cache(sock, rtnl_cls_get_ifindex(cls),
-				   rtnl_cls_get_parent(cls), &cls_cache);
-	if (err < 0)
-		fatal(err, "Unable to allocate classifier cache: %s",
-		      nl_geterror(err));
-
-	nl_cache_foreach_filter(cls_cache, OBJ_CAST(cls), delete_cb, NULL);
+	if ((ifindex = rtnl_tc_get_ifindex(tc)))
+		__delete_link(ifindex, cls);
+	 else
+		nl_cache_foreach(link_cache, delete_link, cls);
 
 	if (!quiet)
-		printf("Deleted %d classifiers\n", deleted);
+		printf("Deleted %d classs\n", deleted);
 
 	return 0;
 }
diff --git a/src/nl-cls-list.c b/src/nl-cls-list.c
index 9121d52..5072585 100644
--- a/src/nl-cls-list.c
+++ b/src/nl-cls-list.c
@@ -6,13 +6,16 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2008 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2008-2010 Thomas Graf <tgraf@suug.ch>
  */
 
-#include "cls/utils.h"
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/cli/cls.h>
+#include <netlink/cli/link.h>
 
 static struct nl_sock *sock;
-static struct rtnl_cls *cls;
+
 static struct nl_dump_params params = {
 	.dp_type = NL_DUMP_LINE,
 };
@@ -20,94 +23,107 @@
 static void print_usage(void)
 {
 	printf(
-	"Usage: nl-cls-list [OPTION]... [CLASSIFIER]\n"
-	"\n"
-	"Options\n"
-	" -f, --format=TYPE     Output format { brief | details | stats }\n"
-	" -h, --help            Show this help text.\n"
-	" -v, --version         Show versioning information.\n"
-	"\n"
-	"Classifier Options\n"
-	" -d, --dev=DEV         Device the classifier should be assigned to.\n"
-	" -p, --parent=HANDLE   Parent qdisc/class\n"
-	"     --proto=PROTO     Protocol\n"
-	"     --prio=NUM        Priority\n"
-	"     --id=NUM          Identifier\n"
+"Usage: nl-cls-list [OPTION]...\n"
+"\n"
+"OPTIONS\n"
+"     --details             Show details\n"
+"     --stats               Show statistics\n"
+" -h, --help                Show this help\n"
+" -v, --version             Show versioning information\n"
+"\n"
+" -d, --dev=DEV             Device the classifier is attached to. (default: all)\n"
+" -p, --parent=ID           Identifier of parent class.\n"
+" -i, --id=ID               Identifier.\n"
+" -k, --kind=NAME           Kind of classifier (e.g. basic, u32, fw)\n"
+"     --protocol=PROTO      Protocol to match (default: all)\n"
+"     --prio=PRIO           Priority (default: 0)\n"
+"\n"
+"EXAMPLE\n"
+"    # Display statistics of all classes on eth0\n"
+"    $ nl-cls-list --stats --dev=eth0\n"
+"\n"
 	);
 	exit(0);
 }
 
-static void print_cls(struct nl_object *obj, void *arg)
+static void __dump_link(int ifindex, struct rtnl_cls *filter)
 {
-	struct nl_cache *cls_cache;
-	int err, ifindex;
+	struct nl_cache *cache;
+	uint32_t parent = rtnl_tc_get_parent((struct rtnl_tc *) filter);
 
-	if (obj)
-		ifindex = rtnl_link_get_ifindex((struct rtnl_link *) obj);
-	else
-		ifindex = rtnl_cls_get_ifindex(cls);
+	cache = nl_cli_cls_alloc_cache(sock, ifindex, parent);
+	nl_cache_dump_filter(cache, &params, OBJ_CAST(filter));
+	nl_cache_free(cache);
+}
 
-	err = rtnl_cls_alloc_cache(sock, ifindex, rtnl_cls_get_parent(cls),
-				   &cls_cache);
-	if (err < 0)
-		fatal(err, "Unable to allocate classifier cache: %s",
-		      nl_geterror(err));
+static void dump_link(struct nl_object *obj, void *arg)
+{
+	struct rtnl_link *link = nl_object_priv(obj);
 
-	nl_cache_dump_filter(cls_cache, &params, OBJ_CAST(cls));
-	nl_cache_free(cls_cache);
+	__dump_link(rtnl_link_get_ifindex(link), arg);
 }
 
 int main(int argc, char *argv[])
 {
+	struct rtnl_cls *cls;
+	struct rtnl_tc *tc;
 	struct nl_cache *link_cache;
-	int dev = 0;
+	int ifindex;
+ 
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_ROUTE);
+	link_cache = nl_cli_link_alloc_cache(sock);
+ 	cls = nl_cli_cls_alloc();
+	tc = (struct rtnl_tc *) cls;
 
 	params.dp_fd = stdout;
-	sock = nlt_alloc_socket();
-	nlt_connect(sock, NETLINK_ROUTE);
-	link_cache = nlt_alloc_link_cache(sock);
-	cls = nlt_alloc_cls();
-
+ 
 	for (;;) {
 		int c, optidx = 0;
 		enum {
-			ARG_PROTO = 257,
-			ARG_PRIO = 258,
-			ARG_ID,
+			ARG_DETAILS = 257,
+			ARG_STATS = 258,
+			ARG_PROTO,
+			ARG_PRIO,
 		};
 		static struct option long_opts[] = {
-			{ "format", 1, 0, 'f' },
+			{ "details", 0, 0, ARG_DETAILS },
+			{ "stats", 0, 0, ARG_STATS },
 			{ "help", 0, 0, 'h' },
 			{ "version", 0, 0, 'v' },
 			{ "dev", 1, 0, 'd' },
 			{ "parent", 1, 0, 'p' },
+			{ "id", 1, 0, 'i' },
+			{ "kind", 1, 0, 'k' },
 			{ "proto", 1, 0, ARG_PROTO },
 			{ "prio", 1, 0, ARG_PRIO },
-			{ "id", 1, 0, ARG_ID },
 			{ 0, 0, 0, 0 }
 		};
 	
-		c = getopt_long(argc, argv, "+f:qhva:d:", long_opts, &optidx);
+		c = getopt_long(argc, argv, "hvd:p:i:k:", long_opts, &optidx);
 		if (c == -1)
 			break;
 
 		switch (c) {
-		case '?': exit(NLE_INVAL);
-		case 'f': params.dp_type = nlt_parse_dumptype(optarg); break;
+		case ARG_DETAILS: params.dp_type = NL_DUMP_DETAILS; break;
+		case ARG_STATS: params.dp_type = NL_DUMP_STATS; break;
 		case 'h': print_usage(); break;
-		case 'v': nlt_print_version(); break;
-		case 'd': dev = 1; parse_dev(cls, link_cache, optarg); break;
-		case 'p': parse_parent(cls, optarg); break;
-		case ARG_PRIO: parse_prio(cls, optarg); break;
-		case ARG_ID: parse_handle(cls, optarg); break;
-		case ARG_PROTO: parse_proto(cls, optarg); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'd': nl_cli_tc_parse_dev(tc, link_cache, optarg); break;
+		case 'p': nl_cli_tc_parse_parent(tc, optarg); break;
+		case 'i': nl_cli_tc_parse_handle(tc, optarg, 0); break;
+		case 'k': nl_cli_tc_parse_kind(tc, optarg); break;
+		case ARG_PROTO: nl_cli_cls_parse_proto(cls, optarg); break;
+		case ARG_PRIO:
+			rtnl_cls_set_prio(cls, nl_cli_parse_u32(optarg));
+			break;
 		}
  	}
 
-	if (!dev)
-		nl_cache_foreach(link_cache, print_cls, NULL);
-	else
-		print_cls(NULL, NULL);
+	if ((ifindex = rtnl_tc_get_ifindex(tc)))
+		__dump_link(ifindex, cls);
+	 else
+		nl_cache_foreach(link_cache, dump_link, cls);
 
 	return 0;
 }
diff --git a/src/nl-link-enslave.c b/src/nl-link-enslave.c
new file mode 100644
index 0000000..2b5d47d
--- /dev/null
+++ b/src/nl-link-enslave.c
@@ -0,0 +1,50 @@
+/*
+ * src/nl-link-enslave.c     Enslave a link
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/link.h>
+#include <netlink/route/link/bonding.h>
+
+int main(int argc, char *argv[])
+{
+	struct nl_sock *sock;
+	struct nl_cache *link_cache;
+	struct rtnl_link *master, *slave;
+	int err;
+
+	if (argc < 3) {
+		fprintf(stderr, "Usage: nl-link-enslave master slave\n");
+		return 1;
+	}
+
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_ROUTE);
+	link_cache = nl_cli_link_alloc_cache(sock);
+
+	if (!(master = rtnl_link_get_by_name(link_cache, argv[1]))) {
+		fprintf(stderr, "Unknown link: %s\n", argv[1]);
+		return 1;
+	}
+
+	if (!(slave = rtnl_link_get_by_name(link_cache, argv[2]))) {
+		fprintf(stderr, "Unknown link: %s\n", argv[2]);
+		return 1;
+	}
+
+	if ((err = rtnl_link_bond_enslave(sock, master, slave)) < 0) {
+		fprintf(stderr, "Unable to enslave %s to %s: %s\n",
+			argv[2], argv[1], nl_geterror(err));
+		return 1;
+	}
+
+	return 0;
+}
+
diff --git a/src/nl-link-list.c b/src/nl-link-list.c
index 5e1e3f6..b5c98b4 100644
--- a/src/nl-link-list.c
+++ b/src/nl-link-list.c
@@ -6,43 +6,29 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2010 Thomas Graf <tgraf@suug.ch>
  */
 
-#if 0
-static void print_usage(void)
-{
-	printf(
-	"Usage: nl-link-dump <mode> [<filter>]\n"
-	"  mode := { brief | detailed | stats | xml }\n"
-	"  filter := [dev DEV] [mtu MTU] [txqlen TXQLEN] [weight WEIGHT] [link LINK]\n"
-	"            [master MASTER] [qdisc QDISC] [addr ADDR] [broadcast BRD]\n"
-	"            [{ up | down }] [{ arp | noarp }] [{ promisc | nopromisc }]\n"
-	"            [{ dynamic | nodynamic }] [{ multicast | nomulticast }]\n"
-	"            [{ trailers | notrailers }] [{ allmulticast | noallmulticast }]\n");
-	exit(1);
-}
-#endif
-
 #include <netlink/cli/utils.h>
 #include <netlink/cli/link.h>
 
 static void print_usage(void)
 {
 	printf(
-	"Usage: nl-link-list [OPTION]... [Link]\n"
-	"\n"
-	"Options\n"
-	" -f, --format=TYPE     Output format { brief | details | stats }\n"
-	" -h, --help            Show this help\n"
-	" -v, --version         Show versioning information\n"
-	"\n"
-	"Link Options\n"
-	" -n, --name=NAME	link name\n"
-	" -i, --index           interface index\n"
-	"     --mtu=NUM         MTU value\n"
-	"     --txqlen=NUM      TX queue length\n"
-	"     --weight=NUM      weight\n"
+"Usage: nl-link-list [OPTIONS]... \n"
+"\n"
+"OPTIONS\n"
+"     --details             Show detailed information of each link\n"
+"     --stats               Show statistics, implies --details\n"
+" -h, --help                Show this help text.\n"
+" -v, --version             Show versioning information.\n"
+"\n"
+" -n, --name=NAME	    Name of link\n"
+" -i, --index               Interface index (unique identifier)\n"
+"     --family=NAME         Link address family\n"
+"     --mtu=NUM             MTU value\n"
+"     --txqlen=NUM          TX queue length\n"
+"     --weight=NUM          Weight\n"
 	);
 	exit(0);
 }
@@ -59,7 +45,6 @@
 
 	sock = nl_cli_alloc_socket();
 	nl_cli_connect(sock, NETLINK_ROUTE);
-	link_cache = nl_cli_link_alloc_cache(sock);
 	link = nl_cli_link_alloc();
 
 	for (;;) {
@@ -69,9 +54,12 @@
 			ARG_MTU = 258,
 			ARG_TXQLEN,
 			ARG_WEIGHT,
+			ARG_DETAILS,
+			ARG_STATS,
 		};
 		static struct option long_opts[] = {
-			{ "format", 1, 0, 'f' },
+			{ "details", 0, 0, ARG_DETAILS },
+			{ "stats", 0, 0, ARG_STATS },
 			{ "help", 0, 0, 'h' },
 			{ "version", 0, 0, 'v' },
 			{ "name", 1, 0, 'n' },
@@ -83,12 +71,13 @@
 			{ 0, 0, 0, 0 }
 		};
 
-		c = getopt_long(argc, argv, "f:hvn:i:", long_opts, &optidx);
+		c = getopt_long(argc, argv, "hvn:i:", long_opts, &optidx);
 		if (c == -1)
 			break;
 
 		switch (c) {
-		case 'f': params.dp_type = nl_cli_parse_dumptype(optarg); break;
+		case ARG_DETAILS: params.dp_type = NL_DUMP_DETAILS; break;
+		case ARG_STATS: params.dp_type = NL_DUMP_STATS; break;
 		case 'h': print_usage(); break;
 		case 'v': nl_cli_print_version(); break;
 		case 'n': nl_cli_link_parse_name(link, optarg); break;
@@ -100,6 +89,9 @@
 		}
 	}
 
+	link_cache = nl_cli_link_alloc_cache_family(sock,
+				rtnl_link_get_family(link));
+
 	nl_cache_dump_filter(link_cache, &params, OBJ_CAST(link));
 
 	return 0;
diff --git a/src/nl-link-name2ifindex.c b/src/nl-link-name2ifindex.c
index b04af04..b8ae4bc 100644
--- a/src/nl-link-name2ifindex.c
+++ b/src/nl-link-name2ifindex.c
@@ -14,7 +14,7 @@
 
 static void print_usage(void)
 {
-	printf("Usage: nl-link-ifindex2name <ifindex>\n");
+	printf("Usage: nl-link-name2ifindex <name>\n");
 	exit(0);
 }
 
diff --git a/src/nl-link-release.c b/src/nl-link-release.c
new file mode 100644
index 0000000..4c9f15a
--- /dev/null
+++ b/src/nl-link-release.c
@@ -0,0 +1,45 @@
+/*
+ * src/nl-link-release.c     release a link
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/link.h>
+#include <netlink/route/link/bonding.h>
+
+int main(int argc, char *argv[])
+{
+	struct nl_sock *sock;
+	struct nl_cache *link_cache;
+	struct rtnl_link *slave;
+	int err;
+
+	if (argc < 2) {
+		fprintf(stderr, "Usage: nl-link-release slave\n");
+		return 1;
+	}
+
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_ROUTE);
+	link_cache = nl_cli_link_alloc_cache(sock);
+
+	if (!(slave = rtnl_link_get_by_name(link_cache, argv[1]))) {
+		fprintf(stderr, "Unknown link: %s\n", argv[1]);
+		return 1;
+	}
+
+	if ((err = rtnl_link_bond_release(sock, slave)) < 0) {
+		fprintf(stderr, "Unable to release slave %s: %s\n",
+			argv[1], nl_geterror(err));
+		return 1;
+	}
+
+	return 0;
+}
+
diff --git a/src/nl-link-set.c b/src/nl-link-set.c
index 94c94e7..bbb60f9 100644
--- a/src/nl-link-set.c
+++ b/src/nl-link-set.c
@@ -6,7 +6,7 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 #include <netlink/cli/utils.h>
@@ -41,6 +41,8 @@
 	"     --mtu=NUM         MTU value\n"
 	"     --txqlen=NUM      TX queue length\n"
 	"     --weight=NUM      weight\n"
+	"     --ifalias=NAME    alias name (SNMP IfAlias)\n"
+	"     --state=up/down   set interface up/down\n"
 	);
 	exit(0);
 }
@@ -55,7 +57,7 @@
 	};
 	int err;
 
-	if ((err = rtnl_link_change(sock, link, change, 0) < 0))
+	if ((err = rtnl_link_change(sock, link, change, 0)) < 0)
 		nl_cli_fatal(err, "Unable to change link: %s",
 			     nl_geterror(err));
 
@@ -84,6 +86,8 @@
 			ARG_MTU = 258,
 			ARG_TXQLEN,
 			ARG_WEIGHT,
+			ARG_IFALIAS,
+			ARG_STATE,
 		};
 		static struct option long_opts[] = {
 			{ "quiet", 0, 0, 'q' },
@@ -95,6 +99,8 @@
 			{ "mtu", 1, 0, ARG_MTU },
 			{ "txqlen", 1, 0, ARG_TXQLEN },
 			{ "weight", 1, 0, ARG_WEIGHT },
+			{ "ifalias", 1, 0, ARG_IFALIAS },
+			{ "state", 1, 0, ARG_STATE },
 			{ 0, 0, 0, 0 }
 		};
 
@@ -109,9 +115,16 @@
 		case 'n': ok++; nl_cli_link_parse_name(link, optarg); break;
 		case 'i': ok++; nl_cli_link_parse_ifindex(link, optarg); break;
 		case ARG_RENAME: nl_cli_link_parse_name(change, optarg); break;
-		case ARG_MTU: nl_cli_link_parse_mtu(link, optarg); break;
-		case ARG_TXQLEN: nl_cli_link_parse_txqlen(link, optarg); break;
-		case ARG_WEIGHT: nl_cli_link_parse_weight(link, optarg); break;
+		case ARG_MTU: nl_cli_link_parse_mtu(change, optarg); break;
+		case ARG_TXQLEN: nl_cli_link_parse_txqlen(change, optarg); break;
+		case ARG_WEIGHT: nl_cli_link_parse_weight(change, optarg); break;
+		case ARG_IFALIAS: nl_cli_link_parse_ifalias(change, optarg); break;
+		case ARG_STATE:
+			if(!strcmp(optarg, "up"))
+				rtnl_link_set_flags(change, IFF_UP);
+			else if(!strcmp(optarg, "down"))
+				rtnl_link_unset_flags(change, IFF_UP);
+			break;
 		}
 	}
 
diff --git a/src/nl-list-caches.c b/src/nl-list-caches.c
index 7e4ffc1..853d8a4 100644
--- a/src/nl-list-caches.c
+++ b/src/nl-list-caches.c
@@ -9,7 +9,7 @@
  * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
  */
 
-#include <netlink-local.h>
+#include <netlink-private/netlink.h>
 #include <netlink/cli/utils.h>
 
 static void print_usage(void)
@@ -48,7 +48,6 @@
 			"brief",
 			"detailed",
 			"stats",
-			"env",
 		};
 		int i;
 
@@ -82,9 +81,9 @@
 
 		printf("    genl:\n" \
 		       "        name: %s\n" \
-		       "        family: %d\n" \
+		       "        user-hdr: %d\n" \
 		       "        id: %d\n",
-		       genl_ops->o_name, genl_ops->o_family, genl_ops->o_id);
+		       genl_ops->o_name, genl_ops->o_hdrsize, genl_ops->o_id);
 
 		if (genl_ops->o_ncmds) {
 			int i;
diff --git a/src/nl-list-sockets.c b/src/nl-list-sockets.c
index 868006e..c2fa1cd 100644
--- a/src/nl-list-sockets.c
+++ b/src/nl-list-sockets.c
@@ -30,7 +30,7 @@
 	while (fgets(buf, sizeof(buf), fd)) {
 		unsigned long sk, cb;
 		int ret, proto, pid, rmem, wmem, refcnt;
-		uint32_t groups;
+		unsigned int groups;
 		
 		ret = sscanf(buf, "%lx %d %d %08x %d %d %lx %d\n",
 			     &sk, &proto, &pid, &groups, &rmem, &wmem,
diff --git a/src/nl-neigh-delete.c b/src/nl-neigh-delete.c
index 887bd84..c418608 100644
--- a/src/nl-neigh-delete.c
+++ b/src/nl-neigh-delete.c
@@ -14,7 +14,7 @@
 #include <netlink/cli/link.h>
 
 static int quiet = 0, default_yes = 0, deleted = 0, interactive = 0;
-struct nl_sock *sock;
+static struct nl_sock *sock;
 
 static void print_usage(void)
 {
diff --git a/src/nl-pktloc-lookup.c b/src/nl-pktloc-lookup.c
index 09b04b2..17c867b 100644
--- a/src/nl-pktloc-lookup.c
+++ b/src/nl-pktloc-lookup.c
@@ -14,24 +14,141 @@
 
 static void print_usage(void)
 {
-	printf("Usage: nl-pktloc-lookup <name>\n");
+printf(
+"Usage: nl-pktloc-lookup [OPTIONS] <name>\n"
+"\n"
+"OPTIONS\n"
+" -h, --help                Show this help text.\n"
+" -v, --version             Show versioning information.\n"
+" -l, --list                List all packet location definitions.\n"
+"     --u32=VALUE	    Print in iproute2's u32 selector style\n"
+"\n"
+"\n"
+"EXAMPLE\n"
+"   $ nl-pktloc-lookup ip.dst\n"
+"   $ nl-pktloc-lookup --list\n"
+"\n"
+);
 	exit(0);
 }
 
+static const char *align_txt[] = {
+	[TCF_EM_ALIGN_U8] = "u8",
+	[TCF_EM_ALIGN_U16] = "u16",
+	[TCF_EM_ALIGN_U32] = "u32"
+};
+
+static uint32_t align_mask[] = {
+	[TCF_EM_ALIGN_U8] = 0xff,
+	[TCF_EM_ALIGN_U16] = 0xffff,
+	[TCF_EM_ALIGN_U32] = 0xffffffff,
+};
+
+static const char *layer_txt[] = {
+	[TCF_LAYER_LINK] = "eth",
+	[TCF_LAYER_NETWORK] = "ip",
+	[TCF_LAYER_TRANSPORT] = "tcp"
+};
+
+static void dump_u32_style(struct rtnl_pktloc *loc, uint32_t value)
+{
+	if (loc->align > 4)
+		nl_cli_fatal(EINVAL, "u32 only supports alignments u8|u16|u32.");
+
+	if (loc->layer == TCF_LAYER_LINK)
+		nl_cli_fatal(EINVAL, "u32 does not support link "
+				"layer locations.");
+
+	if (loc->shift > 0)
+		nl_cli_fatal(EINVAL, "u32 does not support shifting.");
+
+	printf("%s %x %x at %s%u\n",
+		align_txt[loc->align],
+		value, loc->mask ? loc->mask : align_mask[loc->align],
+		loc->layer == TCF_LAYER_TRANSPORT ? "nexthdr+" : "",
+		loc->offset);
+}
+
+static char *get_align_txt(struct rtnl_pktloc *loc)
+{
+	static char buf[16];
+
+	if (loc->align <= 4)
+		strcpy(buf, align_txt[loc->align]);
+	else
+		snprintf(buf, sizeof(buf), "%u", loc->align);
+
+	return buf;
+}
+
+static void dump_loc(struct rtnl_pktloc *loc)
+{
+	printf("%s = %s at %s+%u & %#x >> %u\n",
+		loc->name, get_align_txt(loc), layer_txt[loc->layer],
+		loc->offset, loc->mask, loc->shift);
+}
+
+static void list_cb(struct rtnl_pktloc *loc, void *arg)
+{
+	printf("%-26s %-5s %3s+%-4u %#-10x %-8u %u\n",
+		loc->name, get_align_txt(loc), layer_txt[loc->layer],
+		loc->offset, loc->mask, loc->shift, loc->refcnt);
+}
+
+static void do_list(void)
+{
+	printf(
+"name                      align  offset  mask     shift    refcnt\n");
+	printf("---------------------------------------------------------\n");
+
+	rtnl_pktloc_foreach(&list_cb, NULL);
+}
+
 int main(int argc, char *argv[])
 {
 	struct rtnl_pktloc *loc;
-	int err;
+	int err, ustyle = 0;
+	uint32_t uvalue = 0;
 
-	if (argc < 2)
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_U32 = 257,
+		};
+		static struct option long_opts[] = {
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ "list", 0, 0, 'l' },
+			{ "u32", 1, 0, ARG_U32 },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "hvl", long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'l': do_list(); exit(0);
+		case ARG_U32:
+			ustyle = 1;
+			uvalue = nl_cli_parse_u32(optarg);
+			break;
+		}
+ 	}
+
+	if (optind >= argc)
 		print_usage();
 
-	if ((err = rtnl_pktloc_lookup(argv[1], &loc)) < 0)
+	if ((err = rtnl_pktloc_lookup(argv[optind++], &loc)) < 0)
 		nl_cli_fatal(err, "Unable to lookup packet location: %s",
 			nl_geterror(err));
 
-	printf("%s: %u %u+%u 0x%x %u\n", loc->name, loc->align,
-		loc->layer, loc->offset, loc->mask, loc->flags);
+	if (ustyle)
+		dump_u32_style(loc, uvalue);
+	else
+		dump_loc(loc);
 
 	return 0;
 }
diff --git a/src/nl-qdisc-add.c b/src/nl-qdisc-add.c
new file mode 100644
index 0000000..c2a7c9f
--- /dev/null
+++ b/src/nl-qdisc-add.c
@@ -0,0 +1,146 @@
+/*
+ * src/nl-qdisc-add.c     Add Queueing Discipline
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2010 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/cli/qdisc.h>
+#include <netlink/cli/link.h>
+
+#include <netlink-private/route/tc-api.h>
+
+static int quiet = 0;
+
+static void print_usage(void)
+{
+	printf(
+"Usage: nl-qdisc-add [OPTIONS]... QDISC [CONFIGURATION]...\n"
+"\n"
+"OPTIONS\n"
+" -q, --quiet               Do not print informal notifications.\n"
+" -h, --help                Show this help text.\n"
+" -v, --version             Show versioning information.\n"
+"     --update              Update qdisc if it exists.\n"
+"     --replace             Replace or update qdisc if it exists.\n"
+"     --update-only         Only update qdisc, never create it.\n"
+"     --replace-only        Only replace or update qdisc, never create it.\n"
+" -d, --dev=DEV             Network device the qdisc should be attached to.\n"
+" -i, --id=ID               ID of new qdisc (default: auto-generated)r\n"
+" -p, --parent=ID           ID of parent { root | ingress | QDISC-ID }\n"
+"\n"
+"CONFIGURATION\n"
+" -h, --help                Show help text of qdisc specific options.\n"
+"\n"
+"EXAMPLE\n"
+"   $ nl-qdisc-add --dev=eth1 --parent=root htb --rate=100mbit\n"
+"\n"
+	);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	struct nl_sock *sock;
+	struct rtnl_qdisc *qdisc;
+	struct rtnl_tc *tc;
+	struct nl_cache *link_cache;
+	struct nl_dump_params dp = {
+		.dp_type = NL_DUMP_DETAILS,
+		.dp_fd = stdout,
+	};
+	struct nl_cli_tc_module *tm;
+	struct rtnl_tc_ops *ops;
+	int err, flags = NLM_F_CREATE | NLM_F_EXCL;
+	char *kind, *id = NULL;
+ 
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_ROUTE);
+
+	link_cache = nl_cli_link_alloc_cache(sock);
+
+ 	qdisc = nl_cli_qdisc_alloc();
+	tc = (struct rtnl_tc *) qdisc;
+ 
+	for (;;) {
+		int c, optidx = 0;
+		enum {
+			ARG_REPLACE = 257,
+			ARG_UPDATE = 258,
+			ARG_REPLACE_ONLY,
+			ARG_UPDATE_ONLY,
+		};
+		static struct option long_opts[] = {
+			{ "quiet", 0, 0, 'q' },
+			{ "help", 0, 0, 'h' },
+			{ "version", 0, 0, 'v' },
+			{ "dev", 1, 0, 'd' },
+			{ "parent", 1, 0, 'p' },
+			{ "id", 1, 0, 'i' },
+			{ "replace", 0, 0, ARG_REPLACE },
+			{ "update", 0, 0, ARG_UPDATE },
+			{ "replace-only", 0, 0, ARG_REPLACE_ONLY },
+			{ "update-only", 0, 0, ARG_UPDATE_ONLY },
+			{ 0, 0, 0, 0 }
+		};
+	
+		c = getopt_long(argc, argv, "+qhvd:p:i:",
+				long_opts, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'q': quiet = 1; break;
+		case 'h': print_usage(); break;
+		case 'v': nl_cli_print_version(); break;
+		case 'd': nl_cli_tc_parse_dev(tc, link_cache, optarg); break;
+		case 'p': nl_cli_tc_parse_parent(tc, optarg); break;
+		case 'i': id = strdup(optarg); break;
+		case ARG_UPDATE: flags = NLM_F_CREATE; break;
+		case ARG_REPLACE: flags = NLM_F_CREATE | NLM_F_REPLACE; break;
+		case ARG_UPDATE_ONLY: flags = 0; break;
+		case ARG_REPLACE_ONLY: flags = NLM_F_REPLACE; break;
+		}
+ 	}
+
+	if (optind >= argc)
+		print_usage();
+
+	if (!rtnl_tc_get_ifindex(tc))
+		nl_cli_fatal(EINVAL, "You must specify a network device (--dev=XXX)");
+
+	if (!rtnl_tc_get_parent(tc))
+		nl_cli_fatal(EINVAL, "You must specify a parent");
+
+	if (id) {
+		nl_cli_tc_parse_handle(tc, id, 1);
+		free(id);
+	}
+
+	kind = argv[optind++];
+	rtnl_tc_set_kind(tc, kind);
+
+	if (!(ops = rtnl_tc_get_ops(tc)))
+		nl_cli_fatal(ENOENT, "Unknown qdisc \"%s\"", kind);
+
+	if (!(tm = nl_cli_tc_lookup(ops)))
+		nl_cli_fatal(ENOTSUP, "Qdisc type \"%s\" not supported.", kind);
+
+	tm->tm_parse_argv(tc, argc, argv);
+
+	if (!quiet) {
+		printf("Adding ");
+		nl_object_dump(OBJ_CAST(qdisc), &dp);
+ 	}
+
+	if ((err = rtnl_qdisc_add(sock, qdisc, flags)) < 0)
+		nl_cli_fatal(EINVAL, "Unable to add qdisc: %s", nl_geterror(err));
+
+	return 0;
+}
diff --git a/src/nl-qdisc-delete.c b/src/nl-qdisc-delete.c
index 5cb7aa3..2f945bb 100644
--- a/src/nl-qdisc-delete.c
+++ b/src/nl-qdisc-delete.c
@@ -6,32 +6,32 @@
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 #include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
 #include <netlink/cli/qdisc.h>
 #include <netlink/cli/link.h>
 
 static int quiet = 0, default_yes = 0, deleted = 0, interactive = 0;
-struct nl_sock *sock;
+static struct nl_sock *sock;
 
 static void print_usage(void)
 {
 	printf(
 	"Usage: nl-qdisc-delete [OPTION]... [QDISC]\n"
 	"\n"
-	"Options\n"
-	" -i, --interactive     Run interactively\n"
-	"     --yes             Set default answer to yes\n"
-	" -q, --quiet           Do not print informal notifications\n"
-	" -h, --help            Show this help\n"
-	" -v, --version         Show versioning information\n"
+	"OPTIONS\n"
+	"     --interactive     Run interactively.\n"
+	"     --yes             Set default answer to yes.\n"
+	" -q, --quiet           Do not print informal notifications.\n"
+	" -h, --help            Show this help text and exit.\n"
+	" -v, --version         Show versioning information and exit.\n"
 	"\n"
-	"QDisc Options\n"
-	" -d, --dev=DEV         Device the qdisc is attached to\n"
-	" -p, --parent=HANDLE   Identifier of parent qdisc\n"
-	" -H, --handle=HANDLE   Identifier\n"
+	" -d, --dev=DEV         Device the qdisc is attached to.\n"
+	" -p, --parent=ID       Identifier of parent qdisc/class.\n"
+	" -i, --id=ID           Identifier\n"
 	" -k, --kind=NAME       Kind of qdisc (e.g. pfifo_fast)\n"
 	);
 
@@ -47,6 +47,10 @@
 	};
 	int err;
 
+	/* Ignore default qdiscs, unable to delete */
+	if (rtnl_tc_get_handle((struct rtnl_tc *) qdisc) == 0)
+		return;
+
 	if (interactive && !nl_cli_confirm(obj, &params, default_yes))
 		return;
 
@@ -64,49 +68,73 @@
 int main(int argc, char *argv[])
 {
 	struct rtnl_qdisc *qdisc;
+	struct rtnl_tc *tc;
 	struct nl_cache *link_cache, *qdisc_cache;
+	int nfilter = 0;
  
 	sock = nl_cli_alloc_socket();
 	nl_cli_connect(sock, NETLINK_ROUTE);
 	link_cache = nl_cli_link_alloc_cache(sock);
 	qdisc_cache = nl_cli_qdisc_alloc_cache(sock);
  	qdisc = nl_cli_qdisc_alloc();
+	tc = (struct rtnl_tc *) qdisc;
  
 	for (;;) {
 		int c, optidx = 0;
 		enum {
 			ARG_YES = 257,
+			ARG_INTERACTIVE = 258,
 		};
 		static struct option long_opts[] = {
-			{ "interactive", 0, 0, 'i' },
+			{ "interactive", 0, 0, ARG_INTERACTIVE },
 			{ "yes", 0, 0, ARG_YES },
 			{ "quiet", 0, 0, 'q' },
 			{ "help", 0, 0, 'h' },
 			{ "version", 0, 0, 'v' },
 			{ "dev", 1, 0, 'd' },
 			{ "parent", 1, 0, 'p' },
-			{ "handle", 1, 0, 'H' },
+			{ "id", 1, 0, 'i' },
 			{ "kind", 1, 0, 'k' },
 			{ 0, 0, 0, 0 }
 		};
 	
-		c = getopt_long(argc, argv, "iqhvd:p:H:k:", long_opts, &optidx);
+		c = getopt_long(argc, argv, "qhvd:p:i:k:", long_opts, &optidx);
 		if (c == -1)
 			break;
 
 		switch (c) {
-		case 'i': interactive = 1; break;
+		case '?': nl_cli_fatal(EINVAL, "Invalid options");
+		case ARG_INTERACTIVE: interactive = 1; break;
 		case ARG_YES: default_yes = 1; break;
 		case 'q': quiet = 1; break;
 		case 'h': print_usage(); break;
 		case 'v': nl_cli_print_version(); break;
-		case 'd': nl_cli_qdisc_parse_dev(qdisc, link_cache, optarg); break;
-		case 'p': nl_cli_qdisc_parse_parent(qdisc, optarg); break;
-		case 'H': nl_cli_qdisc_parse_handle(qdisc, optarg); break;
-		case 'k': nl_cli_qdisc_parse_kind(qdisc, optarg); break;
+		case 'd':
+			nfilter++;
+			nl_cli_tc_parse_dev(tc, link_cache, optarg);
+			break;
+		case 'p':
+			nfilter++;
+			nl_cli_tc_parse_parent(tc, optarg);
+			break;
+		case 'i':
+			nfilter++;
+			nl_cli_tc_parse_handle(tc, optarg, 0);
+			break;
+		case 'k':
+			nfilter++;
+			nl_cli_tc_parse_kind(tc, optarg);
+			break;
 		}
  	}
 
+	if (nfilter == 0 && !interactive && !default_yes) {
+		nl_cli_fatal(EINVAL,
+			"You are attempting to delete all qdiscs on all devices.\n"
+			"If you want to proceed, run nl-qdisc-delete --yes.\n"
+			"Aborting...");
+	}
+
 	nl_cache_foreach_filter(qdisc_cache, OBJ_CAST(qdisc), delete_cb, NULL);
 
 	if (!quiet)
diff --git a/src/nl-qdisc-list.c b/src/nl-qdisc-list.c
index ec6e25f..5b0a3f0 100644
--- a/src/nl-qdisc-list.c
+++ b/src/nl-qdisc-list.c
@@ -1,90 +1,183 @@
 /*
- * src/nl-qdisc-list.c     List Qdiscs
+ * src/nl-qdisc-list.c     List Queueing Disciplines
  *
  *	This library is free software; you can redistribute it and/or
  *	modify it under the terms of the GNU Lesser General Public
  *	License as published by the Free Software Foundation version 2.1
  *	of the License.
  *
- * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2003-2010 Thomas Graf <tgraf@suug.ch>
  */
 
 #include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
 #include <netlink/cli/qdisc.h>
+#include <netlink/cli/class.h>
+#include <netlink/cli/cls.h>
 #include <netlink/cli/link.h>
 
-static int quiet = 0;
+#define NUM_INDENT 4
+
+static struct nl_sock *sock;
+static int recursive = 0;
+static struct nl_dump_params params = {
+	.dp_type = NL_DUMP_LINE,
+};
 
 static void print_usage(void)
 {
 	printf(
 	"Usage: nl-qdisc-list [OPTION]... [QDISC]\n"
 	"\n"
-	"Options\n"
-	" -f, --format=TYPE     Output format { brief | details | stats }\n"
+	"OPTIONS\n"
+	"     --details         Show details\n"
+	"     --stats           Show statistics\n"
+	" -r, --recursive       Show recursive tree\n"
 	" -h, --help            Show this help\n"
 	" -v, --version         Show versioning information\n"
 	"\n"
-	"QDisc Options\n"
-	" -d, --dev=DEV         Device the qdisc is attached to\n"
-	" -p, --parent=HANDLE   Identifier of parent qdisc\n"
-	" -H, --handle=HANDLE   Identifier\n"
+	" -d, --dev=DEV         Device the qdisc is attached to. (default: all)\n"
+	" -p, --parent=ID       Identifier of parent qdisc.\n"
+	" -i, --id=ID           Identifier.\n"
 	" -k, --kind=NAME       Kind of qdisc (e.g. pfifo_fast)\n"
+	"\n"
+	"EXAMPLE\n"
+	"    # Display statistics of all qdiscs attached to eth0\n"
+	"    $ nl-qdisc-list --details --dev=eth0\n"
+	"\n"
 	);
 	exit(0);
 }
 
+static void list_classes(int ifindex, uint32_t parent);
+static void list_qdiscs(int ifindex, uint32_t parent);
+
+static void list_class(struct nl_object *obj, void *arg)
+{
+	struct rtnl_tc *tc = nl_object_priv(obj);
+	nl_object_dump(obj, &params);
+
+	list_classes(rtnl_tc_get_ifindex(tc), rtnl_tc_get_handle(tc));
+	list_qdiscs(rtnl_tc_get_ifindex(tc), rtnl_tc_get_handle(tc));
+}
+
+static void list_classes(int ifindex, uint32_t parent)
+{
+	struct nl_cache *class_cache;
+	struct rtnl_class *filter = nl_cli_class_alloc();
+
+	class_cache = nl_cli_class_alloc_cache(sock, ifindex);
+
+	rtnl_tc_set_parent((struct rtnl_tc *) filter, parent);
+	params.dp_prefix += NUM_INDENT;
+	nl_cache_foreach_filter(class_cache, OBJ_CAST(filter), list_class, NULL);
+	params.dp_prefix -= NUM_INDENT;
+
+	rtnl_class_put(filter);
+	nl_cache_free(class_cache);
+}
+
+static void list_cls(int ifindex, uint32_t parent)
+{
+	struct nl_cache *cls_cache;
+
+	cls_cache = nl_cli_cls_alloc_cache(sock, ifindex, parent);
+
+	params.dp_prefix += NUM_INDENT;
+	nl_cache_dump(cls_cache, &params);
+	params.dp_prefix -= NUM_INDENT;
+
+	nl_cache_free(cls_cache);
+}
+
+static void list_qdisc(struct nl_object *obj, void *arg)
+{
+	struct rtnl_qdisc *qdisc = nl_object_priv(obj);
+	struct rtnl_tc *tc = (struct rtnl_tc *) qdisc;
+
+	nl_object_dump(obj, &params);
+
+	list_cls(rtnl_tc_get_ifindex(tc), rtnl_tc_get_handle(tc));
+
+	if (rtnl_tc_get_parent(tc) == TC_H_ROOT) {
+		list_cls(rtnl_tc_get_ifindex(tc), TC_H_ROOT);
+		list_classes(rtnl_tc_get_ifindex(tc), TC_H_ROOT);
+	}
+
+	list_classes(rtnl_tc_get_ifindex(tc), rtnl_tc_get_handle(tc));
+}
+
+static void list_qdiscs(int ifindex, uint32_t parent)
+{
+	struct nl_cache *qdisc_cache;
+	struct rtnl_qdisc *filter = nl_cli_qdisc_alloc();
+
+	qdisc_cache = nl_cli_qdisc_alloc_cache(sock);
+
+	rtnl_tc_set_ifindex((struct rtnl_tc *) filter, ifindex);
+	rtnl_tc_set_parent((struct rtnl_tc *) filter, parent);
+	params.dp_prefix += NUM_INDENT;
+	nl_cache_foreach_filter(qdisc_cache, OBJ_CAST(filter), list_qdisc, NULL);
+	params.dp_prefix -= NUM_INDENT;
+
+	rtnl_qdisc_put(filter);
+	nl_cache_free(qdisc_cache);
+}
+
 int main(int argc, char *argv[])
 {
-	struct nl_sock *sock;
 	struct rtnl_qdisc *qdisc;
+	struct rtnl_tc *tc;
 	struct nl_cache *link_cache, *qdisc_cache;
-	struct nl_dump_params params = {
-		.dp_type = NL_DUMP_LINE,
-		.dp_fd = stdout,
-	};
  
+	params.dp_fd = stdout;
 	sock = nl_cli_alloc_socket();
 	nl_cli_connect(sock, NETLINK_ROUTE);
 	link_cache = nl_cli_link_alloc_cache(sock);
 	qdisc_cache = nl_cli_qdisc_alloc_cache(sock);
  	qdisc = nl_cli_qdisc_alloc();
+	tc = (struct rtnl_tc *) qdisc;
  
 	for (;;) {
 		int c, optidx = 0;
 		enum {
-			ARG_YES = 257,
+			ARG_DETAILS = 257,
+			ARG_STATS = 258,
 		};
 		static struct option long_opts[] = {
-			{ "format", 1, 0, 'f' },
-			{ "quiet", 0, 0, 'q' },
+			{ "details", 0, 0, ARG_DETAILS },
+			{ "stats", 0, 0, ARG_STATS },
+			{ "recursive", 0, 0, 'r' },
 			{ "help", 0, 0, 'h' },
 			{ "version", 0, 0, 'v' },
 			{ "dev", 1, 0, 'd' },
 			{ "parent", 1, 0, 'p' },
-			{ "handle", 1, 0, 'H' },
+			{ "id", 1, 0, 'i' },
 			{ "kind", 1, 0, 'k' },
 			{ 0, 0, 0, 0 }
 		};
 	
-		c = getopt_long(argc, argv, "f:qhvd:p:H:k:",
-				long_opts, &optidx);
+		c = getopt_long(argc, argv, "rhvd:p:i:k:", long_opts, &optidx);
 		if (c == -1)
 			break;
 
 		switch (c) {
-		case 'f': params.dp_type = nl_cli_parse_dumptype(optarg); break;
-		case 'q': quiet = 1; break;
+		case ARG_DETAILS: params.dp_type = NL_DUMP_DETAILS; break;
+		case ARG_STATS: params.dp_type = NL_DUMP_STATS; break;
+		case 'r': recursive = 1; break;
 		case 'h': print_usage(); break;
 		case 'v': nl_cli_print_version(); break;
-		case 'd': nl_cli_qdisc_parse_dev(qdisc, link_cache, optarg); break;
-		case 'p': nl_cli_qdisc_parse_parent(qdisc, optarg); break;
-		case 'H': nl_cli_qdisc_parse_handle(qdisc, optarg); break;
-		case 'k': nl_cli_qdisc_parse_kind(qdisc, optarg); break;
+		case 'd': nl_cli_tc_parse_dev(tc, link_cache, optarg); break;
+		case 'p': nl_cli_tc_parse_parent(tc, optarg); break;
+		case 'i': nl_cli_tc_parse_handle(tc, optarg, 0); break;
+		case 'k': nl_cli_tc_parse_kind(tc, optarg); break;
 		}
  	}
 
-	nl_cache_dump_filter(qdisc_cache, &params, OBJ_CAST(qdisc));
+	if (recursive)
+		nl_cache_foreach_filter(qdisc_cache, OBJ_CAST(qdisc), list_qdisc, NULL);
+	else
+		nl_cache_dump_filter(qdisc_cache, &params, OBJ_CAST(qdisc));
 
 	return 0;
 }
diff --git a/src/nl-route-add.c b/src/nl-route-add.c
index 2f187df..d4aa767 100644
--- a/src/nl-route-add.c
+++ b/src/nl-route-add.c
@@ -120,7 +120,7 @@
 		}
 	}
 
-	if ((err = rtnl_route_add(sock, route, 0)) < 0)
+	if ((err = rtnl_route_add(sock, route, NLM_F_EXCL)) < 0)
 		nl_cli_fatal(err, "Unable to add route: %s", nl_geterror(err));
 
 	if (!quiet) {
diff --git a/src/nl-route-get.c b/src/nl-route-get.c
index c2f07d4..9d9a448 100644
--- a/src/nl-route-get.c
+++ b/src/nl-route-get.c
@@ -65,8 +65,12 @@
 		};
 
 		m = nlmsg_alloc_simple(RTM_GETROUTE, 0);
-		nlmsg_append(m, &rmsg, sizeof(rmsg), NLMSG_ALIGNTO);
-		nla_put_addr(m, RTA_DST, dst);
+		if (!m)
+			nl_cli_fatal(ENOMEM, "out of memory");
+		if (nlmsg_append(m, &rmsg, sizeof(rmsg), NLMSG_ALIGNTO) < 0)
+			nl_cli_fatal(ENOMEM, "out of memory");
+		if (nla_put_addr(m, RTA_DST, dst) < 0)
+			nl_cli_fatal(ENOMEM, "out of memory");
 
 		err = nl_send_auto_complete(sock, m);
 		nlmsg_free(m);
diff --git a/src/nl-tctree-list.c b/src/nl-tctree-list.c
index a074a51..d90cb28 100644
--- a/src/nl-tctree-list.c
+++ b/src/nl-tctree-list.c
@@ -12,6 +12,7 @@
 #include <netlink/cli/utils.h>
 #include <netlink/cli/link.h>
 #include <netlink/cli/qdisc.h>
+#include <netlink/cli/class.h>
 #include <linux/pkt_sched.h>
 
 static struct nl_sock *sock;
@@ -22,6 +23,7 @@
 
 static int ifindex;
 static void print_qdisc(struct nl_object *, void *);
+static void print_tc_childs(struct rtnl_tc *, void *);
 
 static void print_usage(void)
 {
@@ -41,7 +43,7 @@
 	struct rtnl_qdisc *leaf;
 	struct rtnl_class *class = (struct rtnl_class *) obj;
 	struct nl_cache *cls_cache;
-	uint32_t parent = rtnl_class_get_handle(class);
+	uint32_t parent = rtnl_tc_get_handle((struct rtnl_tc *) class);
 
 	params.dp_prefix = (int)(long) arg;
 	nl_object_dump(obj, &params);
@@ -50,7 +52,7 @@
 	if (leaf)
 		print_qdisc((struct nl_object *) leaf, arg + 2);
 
-	rtnl_class_foreach_child(class, class_cache, &print_class, arg + 2);
+	print_tc_childs(TC_CAST(class), arg + 2);
 
 	if (rtnl_cls_alloc_cache(sock, ifindex, parent, &cls_cache) < 0)
 		return;
@@ -60,16 +62,30 @@
 	nl_cache_free(cls_cache);
 }
 
+static void print_tc_childs(struct rtnl_tc *tc, void *arg)
+{
+	struct rtnl_class *filter;
+
+	filter = nl_cli_class_alloc();
+
+	rtnl_tc_set_parent(TC_CAST(filter), rtnl_tc_get_handle(tc));
+	rtnl_tc_set_ifindex(TC_CAST(filter), rtnl_tc_get_ifindex(tc));
+
+	nl_cache_foreach_filter(class_cache, OBJ_CAST(filter), &print_class, arg);
+
+	rtnl_class_put(filter);
+}
+
 static void print_qdisc(struct nl_object *obj, void *arg)
 {
 	struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) obj;
 	struct nl_cache *cls_cache;
-	uint32_t parent = rtnl_qdisc_get_handle(qdisc);
+	uint32_t parent = rtnl_tc_get_handle((struct rtnl_tc *) qdisc);
 
 	params.dp_prefix = (int)(long) arg;
 	nl_object_dump(obj, &params);
 
-	rtnl_qdisc_foreach_child(qdisc, class_cache, &print_class, arg + 2);
+	print_tc_childs(TC_CAST(qdisc), arg + 2);
 
 	if (rtnl_cls_alloc_cache(sock, ifindex, parent, &cls_cache) < 0)
 		return;
diff --git a/tests/.gitignore b/tests/.gitignore
index 19184ee..6b77cac 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,4 +1,23 @@
-test-cache-mngr
-test-genl
-test-nf-cache-mngr
-test-socket-creation
+/check-all
+/check-all.log
+/check-all.trs
+/test-*.log
+/test-*.trs
+/test-cache-mngr
+/test-complex-HTB-with-hash-filters
+/test-create-bond
+/test-create-bridge
+/test-create-ip6tnl
+/test-create-ipgre
+/test-create-ipip
+/test-create-ipvti
+/test-create-sit
+/test-create-veth
+/test-create-vlan
+/test-create-vxlan
+/test-delete-link
+/test-genl
+/test-nf-cache-mngr
+/test-socket-creation
+/test-suite.log
+/test-u32-filter-with-actions
diff --git a/tests/Makefile b/tests/Makefile
deleted file mode 100644
index 8494eea..0000000
--- a/tests/Makefile
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# src/Makefile
-#
-# 	This library is free software; you can redistribute it and/or
-#	modify it under the terms of the GNU Lesser General Public
-#	License as published by the Free Software Foundation version 2.1
-#	of the License.
-#
-# Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
-#
-
-ifeq ($(shell [ ! -r ../Makefile.opts ] && echo 1),)
-    include ../Makefile.opts
-endif
-
-LDFLAGS	+= -L../lib -lnl ../src/utils.o
-CIN 	:= $(wildcard test-*.c)
-TOOLS	:= $(CIN:%.c=%)
-
-all: $(TOOLS)
-
-$(TOOLS): ../src/utils.o
-
-test-%: test-%.c
-	@echo "  LD $@"; \
-	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -lnl-genl -lnl-route
-
-clean:
-	@echo "  CLEAN src"; \
-	rm -f $(TOOLS)
-
-distclean: clean
-
-install:
-	@true
-
-include ../Makefile.rules
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..c388d0b
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,67 @@
+# -*- Makefile -*-
+
+EXTRA_DIST = \
+	util.h
+
+if ENABLE_UNIT_TESTS
+
+AM_CPPFLAGS  = -Wall -I${top_srcdir}/include/linux-private -I${top_srcdir}/include -I${top_builddir}/include -D_GNU_SOURCE -DSYSCONFDIR=\"$(sysconfdir)/libnl\"
+
+LDADD = \
+	${top_builddir}/lib/libnl-3.la \
+	${top_builddir}/lib/libnl-nf-3.la \
+	${top_builddir}/lib/libnl-genl-3.la \
+	${top_builddir}/lib/libnl-route-3.la \
+	@CHECK_LIBS@
+
+AM_CFLAGS = @CHECK_CFLAGS@
+
+UNIT_TESTS = check-all
+
+check_PROGRAMS = \
+	test-create-bond \
+	test-create-vlan \
+	test-create-vxlan \
+	test-create-veth \
+	test-create-bridge \
+	test-create-ip6tnl \
+	test-create-ipgre \
+	test-create-ipip \
+	test-create-ipvti \
+	test-create-sit \
+	test-delete-link \
+	test-socket-creation \
+	test-complex-HTB-with-hash-filters \
+	test-u32-filter-with-actions \
+	${UNIT_TESTS}
+
+TESTS = \
+	${UNIT_TESTS}
+
+if ENABLE_CLI
+LDADD += ${top_builddir}/src/lib/libnl-cli-3.la
+check_PROGRAMS += \
+	test-cache-mngr \
+	test-genl \
+	test-nf-cache-mngr
+endif
+
+test_cache_mngr_SOURCES = test-cache-mngr.c
+test_create_bond_SOURCES = test-create-bond.c
+test_create_vlan_SOURCES = test-create-vlan.c
+test_create_vxlan_SOURCES = test-create-vxlan.c
+test_create_veth_SOURCES = test-create-veth.c
+test_create_bridge_SOURCES = test-create-bridge.c
+test_delete_link_SOURCES = test-delete-link.c
+test_genl_SOURCES = test-genl.c
+test_nf_cache_mngr_SOURCES = test-nf-cache-mngr.c
+test_socket_creation_SOURCES = test-socket-creation.c
+test_complex_HTB_with_hash_filters_SOURCES = test-complex-HTB-with-hash-filters.c
+test_u32_filter_with_actions_SOURCES = test-u32-filter-with-actions.c
+
+# Unit tests
+check_all_SOURCES = \
+	check-all.c \
+	check-addr.c \
+	check-attr.c
+endif
diff --git a/tests/check-addr.c b/tests/check-addr.c
new file mode 100644
index 0000000..39f3ede
--- /dev/null
+++ b/tests/check-addr.c
@@ -0,0 +1,212 @@
+/*
+ * tests/check-addr.c		nl_addr unit tests
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <check.h>
+#include <netlink/addr.h>
+
+START_TEST(addr_alloc)
+{
+	struct nl_addr *addr;
+
+	addr = nl_addr_alloc(16);
+	fail_if(addr == NULL,
+		"Allocation should not return NULL");
+
+	fail_if(nl_addr_iszero(addr) == 0,
+		"New empty address should be all zeros");
+
+	fail_if(nl_addr_get_family(addr) != AF_UNSPEC,
+		"New empty address should have family AF_UNSPEC");
+
+	fail_if(nl_addr_get_prefixlen(addr) != 0,
+		"New empty address should have prefix length 0");
+
+	fail_if(nl_addr_shared(addr),
+		"New empty address should not be shared");
+
+	fail_if(nl_addr_get(addr) != addr,
+		"nl_addr_get() should return pointer to address");
+
+	fail_if(nl_addr_shared(addr) == 0,
+		"Address should be shared after call to nl_addr_get()");
+
+	nl_addr_put(addr);
+
+	fail_if(nl_addr_shared(addr),
+		"Address should not be shared after call to nl_addr_put()");
+
+	fail_if(nl_addr_fill_sockaddr(addr, NULL, 0) == 0,
+		"Socket address filling should fail for empty address");
+
+	nl_addr_put(addr);
+}
+END_TEST
+
+START_TEST(addr_binary_addr)
+{
+	struct nl_addr *addr, *addr2;
+	char baddr[4] = { 0x1, 0x2, 0x3, 0x4 };
+	char baddr2[6] = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 };
+
+	addr = nl_addr_alloc(4);
+	fail_if(addr == NULL,
+		"Allocation should not return NULL");
+
+	fail_if(nl_addr_set_binary_addr(addr, baddr, 4) < 0,
+		"Valid binary address should be settable");
+
+	fail_if(nl_addr_get_prefixlen(addr) != 0,
+		"Prefix length should be unchanged after nl_addr_set_binary_addr()");
+
+	fail_if(nl_addr_get_len(addr) != 4,
+		"Address length should be 4");
+
+	fail_if(nl_addr_set_binary_addr(addr, baddr2, 6) == 0,
+		"Should not be able to set binary address exceeding maximum length");
+
+	fail_if(nl_addr_get_len(addr) != 4,
+		"Address length should still be 4");
+
+	fail_if(nl_addr_guess_family(addr) != AF_INET,
+		"Binary address of length 4 should be guessed as AF_INET");
+
+	fail_if(memcmp(baddr, nl_addr_get_binary_addr(addr), 4) != 0,
+		"Binary address mismatches");
+
+	addr2 = nl_addr_build(AF_UNSPEC, baddr, 4);
+	fail_if(addr2 == NULL,
+		"Building of address should not fail");
+
+	nl_addr_set_prefixlen(addr, 32);
+	fail_if(nl_addr_get_prefixlen(addr) != 32,
+		"Prefix length should be successful changed after nl_addr_set_prefixlen()");
+
+	fail_if(nl_addr_cmp(addr, addr2),
+		"Addresses built from same binary address should match");
+
+	nl_addr_put(addr);
+	nl_addr_put(addr2);
+}
+END_TEST
+
+START_TEST(addr_parse4)
+{
+	struct nl_addr *addr4, *clone;
+	struct sockaddr_in sin;
+	socklen_t len = sizeof(sin);
+	char *addr_str = "10.0.0.1/16";
+	char buf[128];
+
+	fail_if(nl_addr_parse(addr_str, AF_INET6, &addr4) == 0,
+		"Should not be able to parse IPv4 address in IPv6 mode");
+
+	fail_if(nl_addr_parse(addr_str, AF_UNSPEC, &addr4) != 0,
+		"Should be able to parse \"%s\"", addr_str);
+
+	fail_if(nl_addr_get_family(addr4) != AF_INET,
+		"Address family should be AF_INET");
+
+	fail_if(nl_addr_get_prefixlen(addr4) != 16,
+		"Prefix length should be 16");
+
+	fail_if(nl_addr_iszero(addr4),
+		"Address should not be all zeroes");
+
+	clone = nl_addr_clone(addr4);
+	fail_if(clone == NULL,
+		"Cloned address should not be NULL");
+
+	fail_if(nl_addr_cmp(addr4, clone) != 0,
+		"Cloned address should not mismatch original");
+
+	fail_if(nl_addr_fill_sockaddr(addr4, (struct sockaddr *) &sin, &len) != 0,
+		"Should be able to fill socketaddr");
+
+	fail_if(strcmp(nl_addr2str(addr4, buf, sizeof(buf)), addr_str),
+		"Address translated back to string does not match original");
+
+	nl_addr_put(addr4);
+	nl_addr_put(clone);
+}
+END_TEST
+
+START_TEST(addr_parse6)
+{
+	struct nl_addr *addr6, *clone;
+	struct sockaddr_in6 sin;
+	socklen_t len = sizeof(sin);
+	char *addr_str = "2001:1:2::3/64";
+	char buf[128];
+
+	fail_if(nl_addr_parse(addr_str, AF_INET, &addr6) == 0,
+		"Should not be able to parse IPv6 address in IPv4 mode");
+
+	fail_if(nl_addr_parse(addr_str, AF_UNSPEC, &addr6) != 0,
+		"Should be able to parse \"%s\"", addr_str);
+
+	fail_if(nl_addr_get_family(addr6) != AF_INET6,
+		"Address family should be AF_INET6");
+
+	fail_if(nl_addr_get_prefixlen(addr6) != 64,
+		"Prefix length should be 64");
+
+	fail_if(nl_addr_iszero(addr6),
+		"Address should not be all zeroes");
+
+	clone = nl_addr_clone(addr6);
+	fail_if(clone == NULL,
+		"Cloned address should not be NULL");
+
+	fail_if(nl_addr_cmp(addr6, clone) != 0,
+		"Cloned address should not mismatch original");
+
+	fail_if(nl_addr_fill_sockaddr(addr6, (struct sockaddr *) &sin, &len) != 0,
+		"Should be able to fill socketaddr");
+
+	fail_if(strcmp(nl_addr2str(addr6, buf, sizeof(buf)), addr_str),
+		"Address translated back to string does not match original");
+
+	nl_addr_put(addr6);
+	nl_addr_put(clone);
+}
+END_TEST
+
+START_TEST(addr_info)
+{
+	struct nl_addr *addr;
+	char *addr_str = "127.0.0.1";
+	struct addrinfo *result;
+
+	fail_if(nl_addr_parse(addr_str, AF_UNSPEC, &addr) != 0,
+		"Parsing of valid address should not fail");
+
+	fail_if(nl_addr_info(addr, &result) != 0,
+		"getaddrinfo() on loopback address should work");
+
+	freeaddrinfo(result);
+	nl_addr_put(addr);
+}
+END_TEST
+
+Suite *make_nl_addr_suite(void)
+{
+	Suite *suite = suite_create("Abstract addresses");
+
+	TCase *tc_addr = tcase_create("Core");
+	tcase_add_test(tc_addr, addr_alloc);
+	tcase_add_test(tc_addr, addr_binary_addr);
+	tcase_add_test(tc_addr, addr_parse4);
+	tcase_add_test(tc_addr, addr_parse6);
+	tcase_add_test(tc_addr, addr_info);
+	suite_add_tcase(suite, tc_addr);
+
+	return suite;
+}
diff --git a/tests/check-all.c b/tests/check-all.c
new file mode 100644
index 0000000..e431802
--- /dev/null
+++ b/tests/check-all.c
@@ -0,0 +1,44 @@
+/*
+ * tests/check-all.c		overall unit test program
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <check.h>
+
+extern Suite *make_nl_addr_suite(void);
+extern Suite *make_nl_attr_suite(void);
+
+static Suite *main_suite(void)
+{
+	Suite *suite = suite_create("main");
+
+	return suite;
+}
+
+int main(int argc, char *argv[])
+{
+	SRunner *runner;
+	int nfailed;
+	
+	runner = srunner_create(main_suite());
+
+	/* Add testsuites below */
+
+	srunner_add_suite(runner, make_nl_addr_suite());
+	srunner_add_suite(runner, make_nl_attr_suite());
+
+	/* Do not add testsuites below this line */
+
+	srunner_run_all(runner, CK_ENV);
+
+	nfailed = srunner_ntests_failed(runner);
+	srunner_free(runner);
+
+	return nfailed != 0;
+}
diff --git a/tests/check-attr.c b/tests/check-attr.c
new file mode 100644
index 0000000..d862230
--- /dev/null
+++ b/tests/check-attr.c
@@ -0,0 +1,88 @@
+/*
+ * tests/check-attr.c		nla_attr unit tests
+ *
+ *	This library is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU Lesser General Public
+ *	License as published by the Free Software Foundation version 2.1
+ *	of the License.
+ *
+ * Copyright (c) 2013 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include "util.h"
+#include <netlink/attr.h>
+#include <netlink/msg.h>
+
+START_TEST(attr_size)
+{
+	fail_if(nla_attr_size(0) != NLA_HDRLEN,
+		"Length of empty attribute should match header size");
+	fail_if(nla_attr_size(1) != NLA_HDRLEN + 1,
+	        "Length of 1 bytes payload should be NLA_HDRLEN + 1");
+	fail_if(nla_attr_size(2) != NLA_HDRLEN + 2,
+	        "Length of 2 bytes payload should be NLA_HDRLEN + 2");
+	fail_if(nla_attr_size(3) != NLA_HDRLEN + 3,
+	        "Length of 3 bytes payload should be NLA_HDRLEN + 3");
+	fail_if(nla_attr_size(4) != NLA_HDRLEN + 4,
+	        "Length of 4 bytes payload should be NLA_HDRLEN + 4");
+
+	fail_if(nla_total_size(1) != NLA_HDRLEN + 4,
+		"Total size of 1 bytes payload should result in 8 bytes");
+	fail_if(nla_total_size(2) != NLA_HDRLEN + 4,
+		"Total size of 2 bytes payload should result in 8 bytes");
+	fail_if(nla_total_size(3) != NLA_HDRLEN + 4,
+		"Total size of 3 bytes payload should result in 8 bytes");
+	fail_if(nla_total_size(4) != NLA_HDRLEN + 4,
+		"Total size of 4 bytes payload should result in 8 bytes");
+
+	fail_if(nla_padlen(1) != 3,
+		"2 bytes of payload should result in 3 padding bytes");
+	fail_if(nla_padlen(2) != 2,
+		"2 bytes of payload should result in 2 padding bytes");
+	fail_if(nla_padlen(3) != 1,
+		"3 bytes of payload should result in 1 padding bytes");
+	fail_if(nla_padlen(4) != 0,
+		"4 bytes of payload should result in 0 padding bytes");
+	fail_if(nla_padlen(5) != 3,
+		"5 bytes of payload should result in 3 padding bytes");
+}
+END_TEST
+
+START_TEST(msg_construct)
+{
+	struct nl_msg *msg;
+	struct nlmsghdr *nlh;
+	struct nlattr *a;
+	int i, rem;
+
+	msg = nlmsg_alloc();
+	fail_if(!msg, "Unable to allocate netlink message");
+
+	for (i = 1; i < 256; i++) {
+		fail_if(nla_put_u32(msg, i, i+1) != 0,
+			"Unable to add attribute %d", i);
+	}
+
+	nlh = nlmsg_hdr(msg);
+	i = 1;
+	nlmsg_for_each_attr(a, nlh, 0, rem) {
+		fail_if(nla_type(a) != i, "Expected attribute %d", i);
+		i++;
+		fail_if(nla_get_u32(a) != i, "Expected attribute value %d", i);
+	}
+
+	nlmsg_free(msg);
+}
+END_TEST
+
+Suite *make_nl_attr_suite(void)
+{
+	Suite *suite = suite_create("Netlink attributes");
+
+	TCase *nl_attr = tcase_create("Core");
+	tcase_add_test(nl_attr, attr_size);
+	tcase_add_test(nl_attr, msg_construct);
+	suite_add_tcase(suite, nl_attr);
+
+	return suite;
+}
diff --git a/tests/test-cache-mngr.c b/tests/test-cache-mngr.c
index 777bce8..8999e58 100644
--- a/tests/test-cache-mngr.c
+++ b/tests/test-cache-mngr.c
@@ -1,16 +1,20 @@
-#include "../src/utils.h"
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+#include <netlink/cli/utils.h>
 #include <signal.h>
 
+#include <netlink-private/cache-api.h>
+
 static int quit = 0;
 
-static void change_cb(struct nl_cache *cache, struct nl_object *obj,
-		      int action)
-{
-	struct nl_dump_params dp = {
-		.dp_type = NL_DUMP_LINE,
-		.dp_fd = stdout,
-	};
+static struct nl_dump_params dp = {
+	.dp_type = NL_DUMP_LINE,
+};
 
+
+static void change_cb(struct nl_cache *cache, struct nl_object *obj,
+		      int action, void *data)
+{
 	if (action == NL_ACT_NEW)
 		printf("NEW ");
 	else if (action == NL_ACT_DEL)
@@ -29,43 +33,34 @@
 int main(int argc, char *argv[])
 {
 	struct nl_cache_mngr *mngr;
-	struct nl_cache *lc, *nc, *ac, *rc;
-	struct nl_sock *sock;
-	int err;
+	struct nl_cache *cache;
+	int err, i;
+
+	dp.dp_fd = stdout;
 
 	signal(SIGINT, sigint);
 
-	sock = nlt_alloc_socket();
-	err = nl_cache_mngr_alloc(sock, NETLINK_ROUTE, NL_AUTO_PROVIDE, &mngr);
+	err = nl_cache_mngr_alloc(NULL, NETLINK_ROUTE, NL_AUTO_PROVIDE, &mngr);
 	if (err < 0)
-		fatal(err, "Unable to allocate cache manager: %s",
-		      nl_geterror(err));
+		nl_cli_fatal(err, "Unable to allocate cache manager: %s",
+			     nl_geterror(err));
 
-	if ((err = nl_cache_mngr_add(mngr, "route/link", &change_cb, &lc)) < 0)
-		fatal(err, "Unable to add cache route/link: %s",
-		      nl_geterror(err));
-
-	if ((err = nl_cache_mngr_add(mngr, "route/neigh", &change_cb, &nc)) < 0)
-		fatal(err, "Unable to add cache route/neigh: %s",
-		      nl_geterror(err));
-
-	if ((err = nl_cache_mngr_add(mngr, "route/addr", &change_cb, &ac)) < 0)
-		fatal(err, "Unable to add cache route/addr: %s",
-		      nl_geterror(err));
-
-	if ((err = nl_cache_mngr_add(mngr, "route/route", &change_cb, &rc)) < 0)
-		fatal(err, "Unable to add cache route/route: %s",
-		      nl_geterror(err));
+	for (i = 1; i < argc; i++) {
+		err = nl_cache_mngr_add(mngr, argv[i], &change_cb, NULL, &cache);
+		if (err < 0)
+			nl_cli_fatal(err, "Unable to add cache %s: %s",
+				     argv[i], nl_geterror(err));
+	}
 
 	while (!quit) {
-		int err = nl_cache_mngr_poll(mngr, 5000);
+		int err = nl_cache_mngr_poll(mngr, 1000);
 		if (err < 0 && err != -NLE_INTR)
-			fatal(err, "Polling failed: %s", nl_geterror(err));
+			nl_cli_fatal(err, "Polling failed: %s", nl_geterror(err));
 
+		nl_cache_mngr_info(mngr, &dp);
 	}
 
 	nl_cache_mngr_free(mngr);
-	nl_socket_free(sock);
 
 	return 0;
 }
diff --git a/tests/test-complex-HTB-with-hash-filters.c b/tests/test-complex-HTB-with-hash-filters.c
new file mode 100644
index 0000000..48cf5e3
--- /dev/null
+++ b/tests/test-complex-HTB-with-hash-filters.c
@@ -0,0 +1,761 @@
+/*
+ * test/test-complex-HTB-with-hash-filters.c     Add HTB qdisc, HTB classes and creates some hash filters
+ *
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation version 2.1
+ *      of the License.
+ *
+ * Copyright (c) 2011 Adrian Ban <adrian.ban@mantech.ro>
+ */
+
+#include <netlink/route/link.h>
+#include <netlink/route/tc.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/htb.h>
+#include <netlink/route/qdisc/sfq.h>
+#include <netlink/route/cls/u32.h>
+#include <netlink/route/classifier.h>
+#include <netlink/route/class.h>
+#include <linux/if_ether.h>
+
+#include <netlink/attr.h>
+//#include "include/rtnl_u32.h"
+
+#include <stdio.h>
+#include <string.h>
+//#include "include/rtnl_u32_addon.h"
+
+#define 	TC_HANDLE(maj, min)   (TC_H_MAJ((maj) << 16) | TC_H_MIN(min))
+
+/* some functions are copied from iproute-tc tool */
+int get_u32(__u32 *val, const char *arg, int base)
+{
+	unsigned long res;
+	char *ptr;
+
+	if (!arg || !*arg)
+		return -1;
+	res = strtoul(arg, &ptr, base);
+	if (!ptr || ptr == arg || *ptr || res > 0xFFFFFFFFUL)
+		return -1;
+	*val = res;
+	return 0;
+}
+
+int get_u32_handle(__u32 *handle, const char *str)
+{
+	__u32 htid=0, hash=0, nodeid=0;
+	char *tmp = strchr(str, ':');
+        
+	if (tmp == NULL) {
+		if (memcmp("0x", str, 2) == 0)
+			return get_u32(handle, str, 16);
+		return -1;
+	}
+	htid = strtoul(str, &tmp, 16);
+	if (tmp == str && *str != ':' && *str != 0)
+		return -1;
+	if (htid>=0x1000)
+		return -1;
+	if (*tmp) {
+		str = tmp+1;
+		hash = strtoul(str, &tmp, 16);
+		if (tmp == str && *str != ':' && *str != 0)
+			return -1;
+		if (hash>=0x100)
+			return -1;
+		if (*tmp) {
+			str = tmp+1;
+			nodeid = strtoul(str, &tmp, 16);
+			if (tmp == str && *str != 0)
+				return -1;
+			if (nodeid>=0x1000)
+				return -1;
+		}
+	}
+	*handle = (htid<<20)|(hash<<12)|nodeid;
+	return 0;
+}
+
+uint32_t get_u32_parse_handle(const char *cHandle)
+{
+	uint32_t handle=0;
+
+	if(get_u32_handle(&handle, cHandle)) {
+		printf ("Illegal \"ht\"\n");
+		return -1;
+	}
+
+	if (handle && TC_U32_NODE(handle)) {
+		printf("\"link\" must be a hash table.\n");
+		return -1;
+	}
+	return handle;
+}
+
+int get_tc_classid(__u32 *h, const char *str)
+{
+	__u32 maj, min;
+	char *p;
+
+	maj = TC_H_ROOT;
+	if (strcmp(str, "root") == 0)
+		goto ok;
+	maj = TC_H_UNSPEC;
+	if (strcmp(str, "none") == 0)
+		goto ok;
+	maj = strtoul(str, &p, 16);
+	if (p == str) {
+		maj = 0;
+		if (*p != ':')
+			return -1;
+	}
+	if (*p == ':') {
+		if (maj >= (1<<16))
+			return -1;
+		maj <<= 16;
+		str = p+1;
+		min = strtoul(str, &p, 16);
+		if (*p != 0)
+			return -1;
+		if (min >= (1<<16))
+			return -1;
+		maj |= min;
+	} else if (*p != 0)
+		return -1;
+
+ok:
+	*h = maj;
+	return 0;
+}
+
+/* 
+ * Function that adds a new filter and attach it to a hash table
+ *
+ */
+int u32_add_filter_on_ht(struct nl_sock *sock, struct rtnl_link *rtnlLink, uint32_t prio, 
+		uint32_t keyval, uint32_t keymask, int keyoff, int keyoffmask,
+		uint32_t htid, uint32_t classid
+)
+{
+    struct rtnl_cls *cls;
+    int err;
+
+    //printf("Key Val  : 0x%x\n", keyval);
+    //printf("Key Mask : 0x%x\n", keymask);
+
+    cls=rtnl_cls_alloc();
+    if (!(cls)) {
+        printf("Can not allocate classifier\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+    
+    rtnl_tc_set_link(TC_CAST(cls), rtnlLink);
+
+    if ((err = rtnl_tc_set_kind(TC_CAST(cls), "u32"))) {
+        printf("Can not set classifier as u32\n");
+        return 1;
+    }
+
+    rtnl_cls_set_prio(cls, prio);
+    rtnl_cls_set_protocol(cls, ETH_P_IP);
+
+    rtnl_tc_set_parent(TC_CAST(cls), TC_HANDLE(1, 0));
+
+    rtnl_u32_set_hashtable(cls, htid);
+
+    rtnl_u32_add_key_uint32(cls, keyval, keymask, keyoff, keyoffmask); /* 10.0.0.0/8 */
+
+    rtnl_u32_set_classid(cls, classid);
+    
+    rtnl_u32_set_cls_terminal(cls);
+
+    if ((err = rtnl_cls_add(sock, cls, NLM_F_CREATE))) {
+        printf("Can not add classifier: %s\n", nl_geterror(err));
+        return -1;
+    }
+    rtnl_cls_put(cls);
+    return 0;
+
+}
+
+/* 
+ * Function that adds a new filter and attach it to a hash table 
+ * and set next hash table link with hash mask
+ *
+ */
+int u32_add_filter_on_ht_with_hashmask(struct nl_sock *sock, struct rtnl_link *rtnlLink, uint32_t prio, 
+	    uint32_t keyval, uint32_t keymask, int keyoff, int keyoffmask,
+	    uint32_t htid, uint32_t htlink, uint32_t hmask, uint32_t hoffset
+)
+{
+    struct rtnl_cls *cls;
+    int err;
+
+    //printf("Key Val  : 0x%x\n", keyval);
+    //printf("Key Mask : 0x%x\n", keymask);
+
+    cls=rtnl_cls_alloc();
+    if (!(cls)) {
+        printf("Can not allocate classifier\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+    
+    rtnl_tc_set_link(TC_CAST(cls), rtnlLink);
+
+    if ((err = rtnl_tc_set_kind(TC_CAST(cls), "u32"))) {
+        printf("Can not set classifier as u32\n");
+        return 1;
+    }
+
+    rtnl_cls_set_prio(cls, prio);
+    rtnl_cls_set_protocol(cls, ETH_P_IP);
+
+    rtnl_tc_set_parent(TC_CAST(cls), TC_HANDLE(1, 0));
+    
+    if (htid)
+	rtnl_u32_set_hashtable(cls, htid);
+
+    rtnl_u32_add_key_uint32(cls, keyval, keymask, keyoff, keyoffmask);
+
+    rtnl_u32_set_hashmask(cls, hmask, hoffset);
+
+    rtnl_u32_set_link(cls, htlink);
+
+
+    if ((err = rtnl_cls_add(sock, cls, NLM_F_CREATE))) {
+        printf("Can not add classifier: %s\n", nl_geterror(err));
+        return -1;
+    }
+    rtnl_cls_put(cls);
+    return 0;
+}
+
+/* 
+ * function that creates a new hash table 
+ */
+int u32_add_ht(struct nl_sock *sock, struct rtnl_link *rtnlLink, uint32_t prio, uint32_t htid, uint32_t divisor)
+{
+
+    int err;
+    struct rtnl_cls *cls;
+
+    cls=rtnl_cls_alloc();
+    if (!(cls)) {
+        printf("Can not allocate classifier\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+    
+    rtnl_tc_set_link(TC_CAST(cls), rtnlLink);
+
+    if ((err = rtnl_tc_set_kind(TC_CAST(cls), "u32"))) {
+        printf("Can not set classifier as u32\n");
+        return 1;
+    }
+
+    rtnl_cls_set_prio(cls, prio);
+    rtnl_cls_set_protocol(cls, ETH_P_IP);
+    rtnl_tc_set_parent(TC_CAST(cls), TC_HANDLE(1, 0));
+
+    rtnl_u32_set_handle(cls, htid, 0x0, 0x0);
+    //printf("htid: 0x%X\n", htid);
+    rtnl_u32_set_divisor(cls, divisor);
+
+    if ((err = rtnl_cls_add(sock, cls, NLM_F_CREATE))) {
+        printf("Can not add classifier: %s\n", nl_geterror(err));
+        return -1;
+    }
+    rtnl_cls_put(cls);
+    return 0;
+}
+
+/*
+ * function that adds a new HTB qdisc and set the default class for unclassified traffic
+ */
+int qdisc_add_HTB(struct nl_sock *sock, struct rtnl_link *rtnlLink, uint32_t defaultClass)
+{
+    
+    struct rtnl_qdisc *qdisc;
+    int err;
+    
+    /* Allocation of a qdisc object */
+    if (!(qdisc = rtnl_qdisc_alloc())) {
+        printf("Can not allocate Qdisc\n");
+	return -1;
+    }
+
+    //rtnl_tc_set_ifindex(TC_CAST(qdisc), master_index);
+    rtnl_tc_set_link(TC_CAST(qdisc), rtnlLink);
+    rtnl_tc_set_parent(TC_CAST(qdisc), TC_H_ROOT);
+
+    //delete the qdisc
+    //printf("Delete current qdisc\n");
+    rtnl_qdisc_delete(sock, qdisc);
+    //rtnl_qdisc_put(qdisc);
+
+    //add a HTB qdisc
+    //printf("Add a new HTB qdisc\n");
+    rtnl_tc_set_handle(TC_CAST(qdisc), TC_HANDLE(1,0));
+
+    if ((err = rtnl_tc_set_kind(TC_CAST(qdisc), "htb"))) {
+        printf("Can not allocate HTB\n");
+	return -1;
+    }
+
+    /* Set default class for unclassified traffic */
+    //printf("Set default class for unclassified traffic\n");
+    rtnl_htb_set_defcls(qdisc, TC_HANDLE(1, defaultClass));
+    rtnl_htb_set_rate2quantum(qdisc, 1);
+
+    /* Submit request to kernel and wait for response */
+    if ((err = rtnl_qdisc_add(sock, qdisc, NLM_F_CREATE))) {
+        printf("Can not allocate HTB Qdisc\n");
+	return -1;
+    }
+
+    /* Return the qdisc object to free memory resources */
+    rtnl_qdisc_put(qdisc);
+
+    return 0;
+}
+
+/*
+ * function that adds a new HTB class and set its parameters
+ */
+int class_add_HTB(struct nl_sock *sock, struct rtnl_link *rtnlLink, 
+		    uint32_t parentMaj, uint32_t parentMin,
+		    uint32_t childMaj,  uint32_t childMin, 
+		    uint64_t rate, uint64_t ceil,
+		    uint32_t burst, uint32_t cburst, 
+		    uint32_t prio
+)
+{
+    int err;
+    struct rtnl_class *class;
+    //struct rtnl_class *class = (struct rtnl_class *) tc;
+
+    //create a HTB class 
+    //class = (struct rtnl_class *)rtnl_class_alloc();
+    if (!(class = rtnl_class_alloc())) {
+        printf("Can not allocate class object\n");
+        return 1;
+    }
+    //
+    rtnl_tc_set_link(TC_CAST(class), rtnlLink);
+    //add a HTB qdisc
+    //printf("Add a new HTB class with 0x%X:0x%X on parent 0x%X:0x%X\n", childMaj, childMin, parentMaj, parentMin);
+    rtnl_tc_set_parent(TC_CAST(class), TC_HANDLE(parentMaj, parentMin));
+    rtnl_tc_set_handle(TC_CAST(class), TC_HANDLE(childMaj, childMin));
+
+    if ((err = rtnl_tc_set_kind(TC_CAST(class), "htb"))) {
+        printf("Can not set HTB to class\n");
+        return 1;
+    }
+
+    //printf("set HTB class prio to %u\n", prio);
+    rtnl_htb_set_prio((struct rtnl_class *)class, prio);
+
+    if (rate) {
+	//rate=rate/8;
+	rtnl_htb_set_rate(class, rate);
+    }
+    if (ceil) {
+	//ceil=ceil/8;
+	rtnl_htb_set_ceil(class, ceil);
+    }
+    
+    if (burst) {
+	//printf ("Class HTB: set rate burst: %u\n", burst);
+        rtnl_htb_set_rbuffer(class, burst);
+    }
+    if (cburst) {
+	//printf ("Class HTB: set rate cburst: %u\n", cburst);
+        rtnl_htb_set_cbuffer(class, cburst);
+    }
+    /* Submit request to kernel and wait for response */
+    if ((err = rtnl_class_add(sock, class, NLM_F_CREATE))) {
+        printf("Can not allocate HTB Qdisc\n");
+        return 1;
+    }
+    rtnl_class_put(class);
+    return 0;
+}
+
+/*
+ * function that adds a HTB root class and set its parameters
+ */
+int class_add_HTB_root(struct nl_sock *sock, struct rtnl_link *rtnlLink, 
+			uint64_t rate, uint64_t ceil,
+			uint32_t burst, uint32_t cburst
+)
+{
+    int err;
+    struct rtnl_class *class;
+
+    //create a HTB class 
+    class = (struct rtnl_class *)rtnl_class_alloc();
+    //class = rtnl_class_alloc();
+    if (!class) {
+        printf("Can not allocate class object\n");
+        return 1;
+    }
+    //
+    rtnl_tc_set_link(TC_CAST(class), rtnlLink);
+    rtnl_tc_set_parent(TC_CAST(class), TC_H_ROOT);
+    //add a HTB class
+    //printf("Add a new HTB ROOT class\n");
+    rtnl_tc_set_handle(TC_CAST(class), 1);
+
+    if ((err = rtnl_tc_set_kind(TC_CAST(class), "htb"))) {
+        printf("Can not set HTB to class\n");
+        return 1;
+    }
+
+    if (rate) {
+	//rate=rate/8;
+	rtnl_htb_set_rate(class, rate);
+    }
+    if (ceil) {
+	//ceil=ceil/8;
+	rtnl_htb_set_ceil(class, ceil);
+    }
+    
+    if (burst) {
+        rtnl_htb_set_rbuffer(class, burst);
+    }
+    if (cburst) {
+        rtnl_htb_set_cbuffer(class, cburst);
+    }
+    
+    /* Submit request to kernel and wait for response */
+    if ((err = rtnl_class_add(sock, class, NLM_F_CREATE))) {
+        printf("Can not allocate HTB Qdisc\n");
+        return 1;
+    }
+    rtnl_class_put(class);
+    return 0;
+}
+
+/*
+ * function that adds a new SFQ qdisc as a leaf for a HTB class
+ */
+int qdisc_add_SFQ_leaf(struct nl_sock *sock, struct rtnl_link *rtnlLink,
+			uint32_t parentMaj, uint32_t parentMin, 
+			int quantum, int limit, int perturb
+)
+{
+    int err;
+    struct rtnl_qdisc *qdisc;
+
+    if (!(qdisc = rtnl_qdisc_alloc())) {
+        printf("Can not allocate qdisc object\n");
+        return 1;
+    }
+    rtnl_tc_set_link(TC_CAST(qdisc), rtnlLink);
+    rtnl_tc_set_parent(TC_CAST(qdisc), TC_HANDLE(parentMaj, parentMin));
+
+    rtnl_tc_set_handle(TC_CAST(qdisc), TC_HANDLE(parentMin,0));
+
+    if ((err = rtnl_tc_set_kind(TC_CAST(qdisc), "sfq"))) {
+        printf("Can not set SQF class\n");
+        return 1;
+    }
+
+    if(quantum) {
+        rtnl_sfq_set_quantum(qdisc, quantum);
+    } else {
+        rtnl_sfq_set_quantum(qdisc, 16000); // tc default value
+    }
+    if(limit) {
+        rtnl_sfq_set_limit(qdisc, limit); // default is 127
+    }
+    if(perturb) {
+        rtnl_sfq_set_perturb(qdisc, perturb); // default never perturb the hash
+    }
+
+    /* Submit request to kernel and wait for response */
+    if ((err = rtnl_qdisc_add(sock, qdisc, NLM_F_CREATE))) {
+        printf("Can not allocate SFQ qdisc\n");
+	return -1;
+    }
+
+    /* Return the qdisc object to free memory resources */
+    rtnl_qdisc_put(qdisc);
+    return 0;
+}
+
+
+
+
+int main() {
+    
+    struct nl_sock *sock;
+    struct rtnl_link *link;
+
+    //struct rtnl_qdisc *qdisc;
+    //struct rtnl_class *class;
+    //struct rtnl_cls   *cls;
+
+    uint32_t ht, htlink, htid, direction, classid;
+    //uint32_t hash, hashmask, nodeid, divisor, handle;
+    //struct rtnl_u32 *f_u32;
+    char chashlink[16]="";
+
+    //uint64_t drops, qlen;
+
+    //int master_index;
+    int err;
+    
+    //uint64_t rate=0, ceil=0;
+
+    struct nl_cache *link_cache;
+    
+    if (!(sock = nl_socket_alloc())) {
+        printf("Unable to allocate netlink socket\n");
+        exit(1);
+    }
+
+    if ((err = nl_connect(sock, NETLINK_ROUTE)) < 0 ) {
+        printf("Nu s-a putut conecta la NETLINK!\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+
+    
+    if ((err = rtnl_link_alloc_cache(sock, AF_UNSPEC, &link_cache)) < 0) {
+        printf("Unable to allocate link cache: %s\n",
+                             nl_geterror(err));
+        nl_socket_free(sock);
+        exit(1);
+    }
+    
+    /* lookup interface index of eth0 */
+    if (!(link = rtnl_link_get_by_name(link_cache, "imq0"))) {
+        /* error */
+        printf("Interface not found\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+    
+    err=qdisc_add_HTB(sock, link, 0xffff);
+    //drops = rtnl_tc_get_stat(TC_CAST(qdisc), RTNL_TC_DROPS);
+    
+    //printf("Add ROOT HTB class\n");
+    err=class_add_HTB_root(sock, link, 12500000, 12500000, 25000, 25000);
+    err=class_add_HTB(sock, link, 1, 0, 1, 0xffff, 1250000, 12500000, 25000, 25000, 5);
+    err=qdisc_add_SFQ_leaf(sock, link, 1, 0xffff, 16000, 0, 10);
+    err=class_add_HTB(sock, link, 1, 1, 1, 0x5, 2000000, 2000000, 25000, 25000, 5);
+    err=qdisc_add_SFQ_leaf(sock, link, 1, 0x5, 16000, 0, 10);
+    err=class_add_HTB(sock, link, 1, 1, 1, 0x6, 1000000, 1000000, 25000, 25000, 5);
+    err=qdisc_add_SFQ_leaf(sock, link, 1, 0x6, 16000, 0, 10);
+    //err=class_add_HTB(sock, link, 1, 0, 1, 0x7, 1024000, 100000000, 5);
+    //err=class_add_HTB(sock, link, 1, 0, 1, 0x8, 2048000, 100000000, 5);
+    //err=class_add_HTB(sock, link, 1, 0, 1, 0x9, 4096000, 100000000, 5);
+    //err=class_add_HTB(sock, link, 1, 0, 1, 0xa, 8192000, 100000000, 5);
+
+    //printf("Add main hash table\n");
+
+    /* create u32 first hash filter table
+     *
+     */
+    /* formula calcul handle:
+    *         uint32_t handle = (htid << 20) | (hash << 12) | nodeid;
+    */
+
+    /*
+     * Upper limit of number of hash tables: 4096 (0xFFF)
+     * Number of hashes in a table: 256 values (0xFF)
+     *
+     */
+
+    /* using 256 values for hash table 
+     * each entry in hash table match a byte from IP address specified later by a hash key
+     */
+
+    uint32_t i;
+    for (i = 1; i <= 0xf; i++) 
+	u32_add_ht(sock, link, 1, i, 256);
+
+    /* 
+     * attach a u32 filter to the first hash 
+     * that redirects all traffic and make a hash key
+     * from the fist byte of the IP address
+     *
+     */
+
+    //divisor=0x0;	// unused here
+    //handle = 0x0;	// unused here
+    //hash = 0x0;		// unused here
+    //htid = 0x0;		// unused here
+    //nodeid = 0x0;	// unused here
+
+    // direction = 12 -> source IP
+    // direction = 16 -> destination IP
+    direction = 16;
+
+    /*
+     * which hash table will use
+     * in our case is hash table no 1 defined previous
+     *
+     * There are 2 posibilities to set the the hash table:
+     * 1. Using function get_u32_handle and sent a string in
+     *  format 10: where 10 is number of the hash table
+     * 2. Create your own value in format: 0xa00000
+     *
+     */
+    strcpy(chashlink, "1:");
+    //printf("Hash Link: %s\n", chashlink);
+    //chashlink=malloc(sizeof(char) *
+    htlink = 0x0;		// is used by get_u32_handle to return the correct value of hash table (link)
+    
+    if(get_u32_handle(&htlink, chashlink)) {
+        printf ("Illegal \"link\"");
+        nl_socket_free(sock);
+        exit(1);
+    }
+    //printf ("hash link : 0x%X\n", htlink);
+    //printf ("hash link test : %u\n", (htlink && TC_U32_NODE(htlink)));
+
+    if (htlink && TC_U32_NODE(htlink)) {
+	printf("\"link\" must be a hash table.\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+    /* the hash mask will hit the hash table (link) no 1: in our case
+     */
+
+    /* set the hash key mask */
+    //hashmask = 0xFF000000UL;	// the mask that is used to match the hash in specific table, in our case for example 1:a with mean the first byte which is 10 in hash table 1
+
+    /* Here we add a hash filter which match the first byte (see the hashmask value)
+     * of the source IP (offset 12 in the packet header)
+     * You can use also offset 16 to match the destination IP
+     */
+
+    /*
+     * Also we need a filter to match our rule
+     * This mean that we will put a 0.0.0.0/0 filter in our first rule
+     * that match the offset 12 (source IP)
+     * Also you can put offset 16 to match the destination IP
+     */
+
+    u32_add_filter_on_ht_with_hashmask(sock, link, 1, 
+	    0x0, 0x0, direction, 0,
+	    0, htlink, 0xff000000, direction);
+
+    /*
+     * For each first byte that we need to match we will create a new hash table
+     * For example: you have those clases: 10.0.0.0/24 and 172.16.0.0/23
+     * For byte 10 and byte 172 will create a separate hash table that will match the second
+     * byte from each class.
+     *
+     */
+
+    
+    // Create a new hash table with prio 1, id 2 and 256 entries
+//    u32_CreateNewHashTable(sock, link, 1, 2, 256);
+    // Create a new hash table with prio 1, id 3 and 256 entries
+//    u32_CreateNewHashTable(sock, link, 1, 3, 256);
+//    u32_CreateNewHashTable(sock, link, 1, 4, 256);
+//    u32_CreateNewHashTable(sock, link, 1, 5, 256);
+
+    /*
+     * Now we will create other filter under (ATENTION) our first hash table (link) 1:
+     * Previous rule redirects the trafic according the hash mask to hash table (link) no 1:
+     * Here we will match the hash tables from 1:0 to 1:ff. Under each hash table we will attach 
+     * other rules that matches next byte from IP source/destination IP and we will repeat the 
+     * previous steps.
+     *
+     */
+    
+
+    // /8 check
+
+    // 10.0.0.0/8
+    ht=get_u32_parse_handle("1:a:");
+    htid = (ht&0xFFFFF000);
+    htlink=get_u32_parse_handle("2:");
+
+    u32_add_filter_on_ht_with_hashmask(sock, link, 1, 
+	    0x0a000000, 0xff000000, direction, 0,
+	    htid, htlink, 0x00ff0000, direction);
+
+    // 172.0.0.0/8
+    ht=get_u32_parse_handle("1:ac:");
+    htid = (ht&0xFFFFF000);
+    htlink=get_u32_parse_handle("3:");
+
+    u32_add_filter_on_ht_with_hashmask(sock, link, 1, 
+	    0xac000000, 0xff000000, direction, 0,
+	    htid, htlink, 0x00ff0000, direction);
+
+
+    // /16 check
+    // 10.0.0.0/16
+    ht=get_u32_parse_handle("2:0:");
+    htid = (ht&0xFFFFF000);
+    htlink=get_u32_parse_handle("4:");
+
+    u32_add_filter_on_ht_with_hashmask(sock, link, 1, 
+	    0x0a000000, 0xffff0000, direction, 0,
+	    htid, htlink, 0x0000ff00, direction);
+
+    // 172.17.0.0/16
+    ht=get_u32_parse_handle("3:11:");
+    htid = (ht&0xFFFFF000);
+    htlink=get_u32_parse_handle("5:");
+
+    u32_add_filter_on_ht_with_hashmask(sock, link, 1, 
+	    0xac110000, 0xffff0000, direction, 0,
+	    htid, htlink, 0x0000ff00, direction);
+
+    // /24 check
+    // 10.0.9.0/24
+    ht=get_u32_parse_handle("4:9:");
+    htid = (ht&0xFFFFF000);
+    htlink=get_u32_parse_handle("6:");
+
+    u32_add_filter_on_ht_with_hashmask(sock, link, 1, 
+	    0x0a000900, 0xffffff00, direction, 0,
+	    htid, htlink, 0x000000ff, direction);
+    
+    // 172.17.2.0/16
+    ht=get_u32_parse_handle("5:2:");
+    htid = (ht&0xFFFFF000);
+    htlink=get_u32_parse_handle("7:");
+
+    u32_add_filter_on_ht_with_hashmask(sock, link, 1, 
+	    0xac110200, 0xffffff00, direction, 0,
+	    htid, htlink, 0x000000ff, direction);
+
+
+    // final filters
+    // 10.0.9.20
+    ht=get_u32_parse_handle("6:14:");
+    htid = (ht&0xFFFFF000);
+
+    err = get_tc_classid(&classid, "1:5");
+
+    u32_add_filter_on_ht(sock, link, 1, 
+	    0x0a000914, 0xffffffff, direction, 0,
+	    htid, classid);
+
+    // 172.17.2.120
+    ht=get_u32_parse_handle("7:78:");
+    htid = (ht&0xFFFFF000);
+
+    err = get_tc_classid(&classid, "1:6");
+
+    u32_add_filter_on_ht(sock, link, 1, 
+	    0xac110278, 0xffffffff, direction, 0,
+	    htid, classid);
+
+    
+
+    nl_socket_free(sock);
+    return 0;
+}
diff --git a/tests/test-create-bond.c b/tests/test-create-bond.c
new file mode 100644
index 0000000..11bc5b0
--- /dev/null
+++ b/tests/test-create-bond.c
@@ -0,0 +1,29 @@
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+#include <netlink/route/link/bonding.h>
+
+int main(int argc, char *argv[])
+{
+	struct rtnl_link *link;
+	struct nl_sock *sk;
+	int err;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	link = rtnl_link_bond_alloc();
+	rtnl_link_set_name(link, "my_bond");
+
+	if ((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0) {
+		nl_perror(err, "Unable to add link");
+		return err;
+	}
+
+	rtnl_link_put(link);
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-create-bridge.c b/tests/test-create-bridge.c
new file mode 100644
index 0000000..7202cd7
--- /dev/null
+++ b/tests/test-create-bridge.c
@@ -0,0 +1,80 @@
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+#include <netlink/route/link/bridge.h>
+
+#define TEST_BRIDGE_NAME "testbridge"
+#define TEST_INTERFACE_NAME "testtap1"
+
+int create_bridge(struct nl_sock *sk, struct nl_cache *link_cache, const char *name) {
+	struct rtnl_link *link;
+	int err;
+
+	link = rtnl_link_alloc();
+	if ((err = rtnl_link_set_type(link, "bridge")) < 0) {
+		rtnl_link_put(link);
+		return err;
+	}
+	rtnl_link_set_name(link, name);
+
+	if ((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0) {
+		return err;
+	}
+	rtnl_link_put(link);
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	struct rtnl_link *link;
+	struct nl_cache *link_cache;
+	struct nl_sock *sk;
+	int err;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	if ((err = rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache)) < 0) {
+		nl_perror(err, "Unable to allocate cache");
+		return err;
+	}
+
+	if ((err = create_bridge(sk, link_cache, TEST_BRIDGE_NAME)) < 0) {
+		nl_perror(err, "Unable to allocate testbridge");
+		return err;
+	}
+
+	nl_cache_refill(sk, link_cache);
+
+	link = rtnl_link_get_by_name(link_cache, TEST_BRIDGE_NAME);
+	struct rtnl_link *ltap = rtnl_link_get_by_name(link_cache, TEST_INTERFACE_NAME);
+	if (!ltap) {
+		fprintf(stderr, "You should create a tap interface before lunch this test (# tunctl -t %s)\n", TEST_INTERFACE_NAME);
+		return -1;
+	}
+
+	if ((err = rtnl_link_enslave(sk, link, ltap)) < 0) {
+		nl_perror(err, "Unable to enslave interface to his bridge\n");
+		return err;
+	}
+
+	if(rtnl_link_is_bridge(link) == 0) {
+		fprintf(stderr, "Link is not a bridge\n");
+		return -2;
+	}
+	if(rtnl_link_get_master(ltap) <= 0) {
+		fprintf(stderr, "Interface is not attached to a bridge\n");
+		return -3;
+	}
+
+	rtnl_link_put(ltap);
+	rtnl_link_put(link);
+
+	nl_cache_free(link_cache);
+	nl_socket_free(sk);
+
+	return 0;
+}
diff --git a/tests/test-create-ip6tnl.c b/tests/test-create-ip6tnl.c
new file mode 100644
index 0000000..b36ab3d
--- /dev/null
+++ b/tests/test-create-ip6tnl.c
@@ -0,0 +1,55 @@
+#include <netlink/route/link/ip6tnl.h>
+#include <netlink-private/netlink.h>
+
+int main(int argc, char *argv[])
+{
+	struct nl_cache *link_cache;
+	struct rtnl_link *link;
+        struct in6_addr addr;
+	struct nl_sock *sk;
+	int err, if_index;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	err = rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache);
+	if ( err < 0) {
+		nl_perror(err, "Unable to allocate cache");
+		return err;
+	}
+
+	if_index = rtnl_link_name2i(link_cache, "ens33");
+	if (!if_index) {
+		fprintf(stderr, "Unable to lookup ens33");
+		return -1;
+	}
+
+	link = rtnl_link_ip6_tnl_alloc();
+	if(!link) {
+		nl_perror(err, "Unable to allocate link");
+		return -1;
+
+	}
+	rtnl_link_set_name(link, "ip6tnl-tun");
+	rtnl_link_ip6_tnl_set_link(link, if_index);
+
+	inet_pton(AF_INET6, "2607:f0d0:1002:51::4", &addr);
+	rtnl_link_ip6_tnl_set_local(link, &addr);
+
+	inet_pton(AF_INET6, "2607:f0d0:1002:52::5", &addr);
+	rtnl_link_ip6_tnl_set_remote(link, &addr);
+
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	if (err < 0) {
+		nl_perror(err, "Unable to add link");
+		return err;
+	}
+
+	rtnl_link_put(link);
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-create-ipgre.c b/tests/test-create-ipgre.c
new file mode 100644
index 0000000..66ea6da
--- /dev/null
+++ b/tests/test-create-ipgre.c
@@ -0,0 +1,56 @@
+#include <netlink/route/link/ipgre.h>
+#include <netlink-private/netlink.h>
+
+int main(int argc, char *argv[])
+{
+	struct nl_cache *link_cache;
+	struct rtnl_link *link;
+	struct in_addr addr;
+	struct nl_sock *sk;
+	int err, if_index;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	err = rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache);
+	if ( err < 0) {
+		nl_perror(err, "Unable to allocate cache");
+		return err;
+	}
+
+	if_index = rtnl_link_name2i(link_cache, "eno16777736");
+	if (!if_index) {
+		fprintf(stderr, "Unable to lookup eno16777736");
+		return -1;
+	}
+
+	link = rtnl_link_ipgre_alloc();
+	if(!link) {
+		nl_perror(err, "Unable to allocate link");
+		return -1;
+
+	}
+	rtnl_link_set_name(link, "ipgre-tun");
+	rtnl_link_ipgre_set_link(link, if_index);
+
+	inet_pton(AF_INET, "192.168.254.12", &addr.s_addr);
+	rtnl_link_ipgre_set_local(link, addr.s_addr);
+
+	inet_pton(AF_INET, "192.168.254.13", &addr.s_addr);
+	rtnl_link_ipgre_set_remote(link, addr.s_addr);
+
+	rtnl_link_ipgre_set_ttl(link, 64);
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	if (err < 0) {
+		nl_perror(err, "Unable to add link");
+		return err;
+	}
+
+	rtnl_link_put(link);
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-create-ipip.c b/tests/test-create-ipip.c
new file mode 100644
index 0000000..44b9b2c
--- /dev/null
+++ b/tests/test-create-ipip.c
@@ -0,0 +1,56 @@
+#include <netlink/route/link/ipip.h>
+#include <netlink-private/netlink.h>
+
+int main(int argc, char *argv[])
+{
+	struct nl_cache *link_cache;
+	struct rtnl_link *link;
+	struct in_addr addr;
+	struct nl_sock *sk;
+	int err, if_index;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	err = rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache);
+	if ( err < 0) {
+		nl_perror(err, "Unable to allocate cache");
+		return err;
+	}
+
+	if_index = rtnl_link_name2i(link_cache, "eno16777736");
+	if (!if_index) {
+		fprintf(stderr, "Unable to lookup eno16777736");
+		return -1;
+	}
+
+	link = rtnl_link_ipip_alloc();
+	if(!link) {
+		nl_perror(err, "Unable to allocate link");
+		return -1;
+	}
+
+	rtnl_link_set_name(link, "ipip-tun");
+	rtnl_link_ipip_set_link(link, if_index);
+
+	inet_pton(AF_INET, "192.168.254.12", &addr.s_addr);
+	rtnl_link_ipip_set_local(link, addr.s_addr);
+
+	inet_pton(AF_INET, "192.168.254.13", &addr.s_addr);
+	rtnl_link_ipip_set_remote(link, addr.s_addr);
+
+	rtnl_link_ipip_set_ttl(link, 64);
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	if (err < 0) {
+		nl_perror(err, "Unable to add link");
+		return err;
+	}
+
+	rtnl_link_put(link);
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-create-ipvti.c b/tests/test-create-ipvti.c
new file mode 100644
index 0000000..6cb92d7
--- /dev/null
+++ b/tests/test-create-ipvti.c
@@ -0,0 +1,55 @@
+#include <netlink/route/link/ipvti.h>
+#include <netlink-private/netlink.h>
+
+int main(int argc, char *argv[])
+{
+	struct nl_cache *link_cache;
+	struct rtnl_link *link;
+	struct in_addr addr;
+	struct nl_sock *sk;
+	int err, if_index;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	err = rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache);
+	if ( err < 0) {
+		nl_perror(err, "Unable to allocate cache");
+		return err;
+	}
+
+	if_index = rtnl_link_name2i(link_cache, "ens33");
+	if (!if_index) {
+		fprintf(stderr, "Unable to lookup ens33");
+		return -1;
+	}
+
+	link = rtnl_link_ipvti_alloc();
+	if(!link) {
+		nl_perror(err, "Unable to allocate link");
+		return -1;
+
+	}
+	rtnl_link_set_name(link, "ipvti-tun");
+	rtnl_link_ipvti_set_link(link, if_index);
+
+	inet_pton(AF_INET, "192.168.254.12", &addr.s_addr);
+	rtnl_link_ipvti_set_local(link, addr.s_addr);
+
+	inet_pton(AF_INET, "192.168.254.13", &addr.s_addr);
+	rtnl_link_ipvti_set_remote(link, addr.s_addr);
+
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	if (err < 0) {
+		nl_perror(err, "Unable to add link");
+		return err;
+	}
+
+	rtnl_link_put(link);
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-create-macvlan.c b/tests/test-create-macvlan.c
new file mode 100644
index 0000000..6477923
--- /dev/null
+++ b/tests/test-create-macvlan.c
@@ -0,0 +1,48 @@
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+#include <netlink/route/link/macvlan.h>
+
+int main(int argc, char *argv[])
+{
+	struct rtnl_link *link;
+	struct nl_cache *link_cache;
+	struct nl_sock *sk;
+        struct nl_addr* addr;
+	int err, master_index;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	if ((err = rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache)) < 0) {
+		nl_perror(err, "Unable to allocate cache");
+		return err;
+	}
+
+	if (!(master_index = rtnl_link_name2i(link_cache, "eth0"))) {
+		fprintf(stderr, "Unable to lookup eth0");
+		return -1;
+	}
+
+	link = rtnl_link_macvlan_alloc();
+
+	rtnl_link_set_link(link, master_index);
+
+        addr = nl_addr_build(AF_LLC, ether_aton("00:11:22:33:44:55"), ETH_ALEN);
+        rtnl_link_set_addr(link, addr);
+        nl_addr_put(addr);
+
+	rtnl_link_macvlan_set_mode(link, rtnl_link_macvlan_str2mode("bridge"));
+
+	if ((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0) {
+		nl_perror(err, "Unable to add link");
+		return err;
+	}
+
+	rtnl_link_put(link);
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-create-sit.c b/tests/test-create-sit.c
new file mode 100644
index 0000000..d33e496
--- /dev/null
+++ b/tests/test-create-sit.c
@@ -0,0 +1,56 @@
+#include <netlink/route/link/sit.h>
+#include <netlink-private/netlink.h>
+
+int main(int argc, char *argv[])
+{
+	struct nl_cache *link_cache;
+	struct rtnl_link *link;
+	struct in_addr addr;
+	struct nl_sock *sk;
+	int err, if_index;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	err = rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache);
+	if ( err < 0) {
+		nl_perror(err, "Unable to allocate cache");
+		return err;
+	}
+
+	if_index = rtnl_link_name2i(link_cache, "eno16777736");
+	if (!if_index) {
+		fprintf(stderr, "Unable to lookup eno16777736");
+		return -1;
+	}
+
+	link = rtnl_link_sit_alloc();
+	if(!link) {
+		nl_perror(err, "Unable to allocate link");
+		return -1;
+
+	}
+	rtnl_link_set_name(link, "sit-tun");
+	rtnl_link_sit_set_link(link, if_index);
+
+	inet_pton(AF_INET, "192.168.254.12", &addr.s_addr);
+	rtnl_link_sit_set_local(link, addr.s_addr);
+
+	inet_pton(AF_INET, "192.168.254.13", &addr.s_addr);
+	rtnl_link_sit_set_remote(link, addr.s_addr);
+
+	rtnl_link_sit_set_ttl(link, 64);
+	err = rtnl_link_add(sk, link, NLM_F_CREATE);
+	if (err < 0) {
+		nl_perror(err, "Unable to add link");
+		return err;
+	}
+
+	rtnl_link_put(link);
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-create-veth.c b/tests/test-create-veth.c
new file mode 100644
index 0000000..db5ab8b
--- /dev/null
+++ b/tests/test-create-veth.c
@@ -0,0 +1,42 @@
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+#include <netlink/route/link/veth.h>
+
+int main(int argc, char *argv[])
+{
+	struct rtnl_link *link;
+	struct nl_sock *sk;
+	int err;
+	struct rtnl_link *peer;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+#if 0
+	rtnl_link_veth_add(sk, "veth2", "veth3", getpid());
+#else
+	link = rtnl_link_veth_alloc();
+	if (!link) {
+		nl_perror(err, "Unable to alloc link");
+		return err;
+	}
+		
+	rtnl_link_set_name(link, "veth8");
+	peer = rtnl_link_veth_get_peer(link);
+	rtnl_link_set_name(peer, "veth9");
+
+	if ((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0) {
+		nl_perror(err, "Unable to add link");
+		return err;
+	}
+	printf("peer is %s\n", rtnl_link_get_name(peer));
+	rtnl_link_put(peer);
+	rtnl_link_put(link);
+#endif
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-create-vlan.c b/tests/test-create-vlan.c
new file mode 100644
index 0000000..64e478f
--- /dev/null
+++ b/tests/test-create-vlan.c
@@ -0,0 +1,43 @@
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+#include <netlink/route/link/vlan.h>
+
+int main(int argc, char *argv[])
+{
+	struct rtnl_link *link;
+	struct nl_cache *link_cache;
+	struct nl_sock *sk;
+	int err, master_index;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	if ((err = rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache)) < 0) {
+		nl_perror(err, "Unable to allocate cache");
+		return err;
+	}
+
+	if (!(master_index = rtnl_link_name2i(link_cache, "eth0"))) {
+		fprintf(stderr, "Unable to lookup eth0");
+		return -1;
+	}
+
+	link = rtnl_link_vlan_alloc();
+
+	rtnl_link_set_link(link, master_index);
+
+	rtnl_link_vlan_set_id(link, 10);
+
+	if ((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0) {
+		nl_perror(err, "Unable to add link");
+		return err;
+	}
+
+	rtnl_link_put(link);
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-create-vxlan.c b/tests/test-create-vxlan.c
new file mode 100644
index 0000000..98a5103
--- /dev/null
+++ b/tests/test-create-vxlan.c
@@ -0,0 +1,47 @@
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+#include <netlink/route/link/vxlan.h>
+
+int main(int argc, char *argv[])
+{
+	struct rtnl_link *link;
+	struct nl_addr *addr;
+	struct nl_sock *sk;
+	int err;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	link = rtnl_link_vxlan_alloc();
+
+	rtnl_link_set_name(link, "vxlan128");
+
+	if ((err = rtnl_link_vxlan_set_id(link, 128)) < 0) {
+		nl_perror(err, "Unable to set VXLAN network identifier");
+		return err;
+	}
+
+	if ((err = nl_addr_parse("239.0.0.1", AF_INET, &addr)) < 0) {
+		nl_perror(err, "Unable to parse IP address");
+		return err;
+	}
+
+	if ((err = rtnl_link_vxlan_set_group(link, addr)) < 0) {
+		nl_perror(err, "Unable to set multicast IP address");
+		return err;
+	}
+	nl_addr_put(addr);
+
+	if ((err = rtnl_link_add(sk, link, NLM_F_CREATE)) < 0) {
+		nl_perror(err, "Unable to add link");
+		return err;
+	}
+
+	rtnl_link_put(link);
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-delete-link.c b/tests/test-delete-link.c
new file mode 100644
index 0000000..9cf1034
--- /dev/null
+++ b/tests/test-delete-link.c
@@ -0,0 +1,28 @@
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+int main(int argc, char *argv[])
+{
+	struct rtnl_link *link;
+	struct nl_sock *sk;
+	int err;
+
+	sk = nl_socket_alloc();
+	if ((err = nl_connect(sk, NETLINK_ROUTE)) < 0) {
+		nl_perror(err, "Unable to connect socket");
+		return err;
+	}
+
+	link = rtnl_link_alloc();
+	rtnl_link_set_name(link, "my_bond");
+
+	if ((err = rtnl_link_delete(sk, link)) < 0) {
+		nl_perror(err, "Unable to delete link");
+		return err;
+	}
+
+	rtnl_link_put(link);
+	nl_close(sk);
+
+	return 0;
+}
diff --git a/tests/test-genl.c b/tests/test-genl.c
index 8bf60c5..74aea10 100644
--- a/tests/test-genl.c
+++ b/tests/test-genl.c
@@ -1,4 +1,72 @@
-#include "../src/utils.h"
+#include <netlink/cli/utils.h>
+#include <linux/taskstats.h>
+
+static struct nla_policy attr_policy[TASKSTATS_TYPE_MAX+1] = {
+	[TASKSTATS_TYPE_PID]	= { .type = NLA_U32 },
+	[TASKSTATS_TYPE_TGID]	= { .type = NLA_U32 },
+	[TASKSTATS_TYPE_STATS]	= { .minlen = sizeof(struct taskstats) },
+	[TASKSTATS_TYPE_AGGR_PID] = { .type = NLA_NESTED },
+	[TASKSTATS_TYPE_AGGR_TGID] = { .type = NLA_NESTED },
+};
+
+
+static int parse_cmd_new(struct nl_cache_ops *unused, struct genl_cmd *cmd,
+			 struct genl_info *info, void *arg)
+{
+	struct nlattr *attrs[TASKSTATS_TYPE_MAX+1];
+	struct nlattr *nested;
+	int err;
+
+	if (info->attrs[TASKSTATS_TYPE_AGGR_PID])
+		nested = info->attrs[TASKSTATS_TYPE_AGGR_PID];
+	else if (info->attrs[TASKSTATS_TYPE_AGGR_TGID])
+		nested = info->attrs[TASKSTATS_TYPE_AGGR_TGID];
+	else {
+		fprintf(stderr, "Invalid taskstats message: Unable to find "
+				"nested attribute/\n");
+		return NL_SKIP;
+	}
+
+	err = nla_parse_nested(attrs, TASKSTATS_TYPE_MAX, nested, attr_policy);
+	if (err < 0) {
+		nl_perror(err, "Error while parsing generic netlink message");
+		return err;
+	}
+
+
+	if (attrs[TASKSTATS_TYPE_STATS]) {
+		struct taskstats *stats = nla_data(attrs[TASKSTATS_TYPE_STATS]);
+
+		printf("%s pid %u uid %u gid %u parent %u\n",
+		       stats->ac_comm, stats->ac_pid, stats->ac_uid,
+		       stats->ac_gid, stats->ac_ppid);
+	}
+
+	return 0;
+}
+
+static int parse_cb(struct nl_msg *msg, void *arg)
+{
+	return genl_handle_msg(msg, NULL);
+}
+
+static struct genl_cmd cmds[] = {
+	{
+		.c_id		= TASKSTATS_CMD_NEW,
+		.c_name		= "taskstats_new()",
+		.c_maxattr	= TASKSTATS_TYPE_MAX,
+		.c_attr_policy	= attr_policy,
+		.c_msg_parser	= &parse_cmd_new,
+	},
+};
+
+#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
+
+static struct genl_ops ops = {
+	.o_name = TASKSTATS_GENL_NAME,
+	.o_cmds = cmds,
+	.o_ncmds = ARRAY_SIZE(cmds),
+};
 
 int main(int argc, char *argv[])
 {
@@ -7,28 +75,42 @@
 	void *hdr;
 	int err;
 
-	sock = nlt_alloc_socket();
-	nlt_connect(sock, NETLINK_GENERIC);
+	sock = nl_cli_alloc_socket();
+	nl_cli_connect(sock, NETLINK_GENERIC);
+
+	if ((err = genl_register_family(&ops)) < 0)
+		nl_cli_fatal(err, "Unable to register Generic Netlink family");
+
+	if ((err = genl_ops_resolve(sock, &ops)) < 0)
+		nl_cli_fatal(err, "Unable to resolve family name");
+
+	if (genl_ctrl_resolve(sock, "nlctrl") != GENL_ID_CTRL)
+		nl_cli_fatal(NLE_INVAL, "Resolving of \"nlctrl\" failed");
 
 	msg = nlmsg_alloc();
 	if (msg == NULL)
-		fatal(NLE_NOMEM, "Unable to allocate netlink message");
+		nl_cli_fatal(NLE_NOMEM, "Unable to allocate netlink message");
 
-	hdr = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, GENL_ID_CTRL,
-			  0, 0, CTRL_CMD_GETFAMILY, 1);
+	hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, ops.o_id,
+			  0, 0, TASKSTATS_CMD_GET, TASKSTATS_GENL_VERSION);
 	if (hdr == NULL)
-		fatal(ENOMEM, "Unable to write genl header");
+		nl_cli_fatal(ENOMEM, "Unable to write genl header");
 
-	if ((err = nla_put_u32(msg, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL)) < 0)
-		fatal(err, "Unable to add attribute: %s", nl_geterror(err));
+	if ((err = nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, 1)) < 0)
+		nl_cli_fatal(err, "Unable to add attribute: %s", nl_geterror(err));
 
 	if ((err = nl_send_auto_complete(sock, msg)) < 0)
-		fatal(err, "Unable to send message: %s", nl_geterror(err));
-
-	if ((err = nl_recvmsgs_default(sock)) < 0)
-		fatal(err, "Unable to receive message: %s", nl_geterror(err));
+		nl_cli_fatal(err, "Unable to send message: %s", nl_geterror(err));
 
 	nlmsg_free(msg);
+
+	if ((err = nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM,
+			parse_cb, NULL)) < 0)
+		nl_cli_fatal(err, "Unable to modify valid message callback");
+
+	if ((err = nl_recvmsgs_default(sock)) < 0)
+		nl_cli_fatal(err, "Unable to receive message: %s", nl_geterror(err));
+
 	nl_close(sock);
 	nl_socket_free(sock);
 
diff --git a/tests/test-nf-cache-mngr.c b/tests/test-nf-cache-mngr.c
index 05485bf..b4f3022 100644
--- a/tests/test-nf-cache-mngr.c
+++ b/tests/test-nf-cache-mngr.c
@@ -1,13 +1,13 @@
-#include "../src/utils.h"
+#include <netlink/cli/utils.h>
 
 static void change_cb(struct nl_cache *cache, struct nl_object *obj,
-		      int action)
+		      int action, void *data)
 {
 	struct nfnl_ct *ct = (struct nfnl_ct *) obj;
 	static struct nl_addr *hack = NULL;
 
 	if (!hack)
-		hack = nl_addr_parse("194.88.212.233", AF_INET);
+		nl_addr_parse("194.88.212.233", AF_INET, &hack);
 
 	if (!nl_addr_cmp(hack, nfnl_ct_get_src(ct, 1)) ||
 	    !nl_addr_cmp(hack, nfnl_ct_get_dst(ct, 1))) {
@@ -26,25 +26,26 @@
 	struct nl_cache_mngr *mngr;
 	struct nl_sock *sock;
 	struct nl_cache *ct;
+	int err;
 
-	sock = nlt_socket_alloc();
+	sock = nl_cli_alloc_socket();
 
-	mngr = nl_cache_mngr_alloc(sock, NETLINK_NETFILTER, NL_AUTO_PROVIDE);
-	if (!mngr) {
-		nl_perror("nl_cache_mngr_alloc");
+	err = nl_cache_mngr_alloc(sock, NETLINK_NETFILTER, NL_AUTO_PROVIDE, &mngr);
+	if (err < 0) {
+		nl_perror(err, "nl_cache_mngr_alloc");
 		return -1;
 	}
 
-	ct = nl_cache_mngr_add(mngr, "netfilter/ct", &change_cb);
-	if (ct == NULL) {
-		nl_perror("nl_cache_mngr_add(netfilter/ct)");
+	err = nl_cache_mngr_add(mngr, "netfilter/ct", &change_cb, NULL, &ct);
+	if (err < 0) {
+		nl_perror(err, "nl_cache_mngr_add(netfilter/ct)");
 		return -1;
 	}
 
 	for (;;) {
 		int err = nl_cache_mngr_poll(mngr, 5000);
 		if (err < 0) {
-			nl_perror("nl_cache_mngr_poll()");
+			nl_perror(err, "nl_cache_mngr_poll()");
 			return -1;
 		}
 
diff --git a/tests/test-socket-creation.c b/tests/test-socket-creation.c
index a170ccd..83f3ad4 100644
--- a/tests/test-socket-creation.c
+++ b/tests/test-socket-creation.c
@@ -1,23 +1,24 @@
-#include "../src/utils.h"
+#include <netlink/netlink.h>
+#include <errno.h>
 
 int main(int argc, char *argv[])
 {
 	struct nl_sock *h[1025];
 	int i;
 
-	h[0] = nl_handle_alloc();
+	h[0] = nl_socket_alloc();
 	printf("Created handle with port 0x%x\n",
 			nl_socket_get_local_port(h[0]));
-	nl_handle_destroy(h[0]);
-	h[0] = nl_handle_alloc();
+	nl_socket_free(h[0]);
+	h[0] = nl_socket_alloc();
 	printf("Created handle with port 0x%x\n",
 			nl_socket_get_local_port(h[0]));
-	nl_handle_destroy(h[0]);
+	nl_socket_free(h[0]);
 
 	for (i = 0; i < 1025; i++) {
-		h[i] = nl_handle_alloc();
+		h[i] = nl_socket_alloc();
 		if (h[i] == NULL)
-			nl_perror("Unable to allocate socket");
+			nl_perror(ENOMEM, "Unable to allocate socket");
 		else
 			printf("Created handle with port 0x%x\n",
 				nl_socket_get_local_port(h[i]));
diff --git a/tests/test-u32-filter-with-actions.c b/tests/test-u32-filter-with-actions.c
new file mode 100644
index 0000000..55f913a
--- /dev/null
+++ b/tests/test-u32-filter-with-actions.c
@@ -0,0 +1,400 @@
+/*
+ * test/tests-u32-with-actions.c     Add ingress qdisc, create some hash filters, and add redirect action
+ *
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation version 2.1
+ *      of the License.
+ *
+ * Stolen from tests/test-complex-HTB-with-hash-filters.c
+ *
+ * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
+ */
+
+#include <netlink/route/link.h>
+#include <netlink/route/tc.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/cls/u32.h>
+#include <netlink/route/classifier.h>
+#include <netlink/route/action.h>
+#include <netlink/route/act/mirred.h>
+#include <netlink/route/class.h>
+#include <linux/if_ether.h>
+
+#include <netlink/attr.h>
+#include <stdio.h>
+#include <string.h>
+
+#define 	TC_HANDLE(maj, min)   (TC_H_MAJ((maj) << 16) | TC_H_MIN(min))
+
+/* some functions are copied from iproute-tc tool */
+static int get_u32(__u32 *val, const char *arg, int base)
+{
+	unsigned long res;
+	char *ptr;
+
+	if (!arg || !*arg)
+		return -1;
+	res = strtoul(arg, &ptr, base);
+	if (!ptr || ptr == arg || *ptr || res > 0xFFFFFFFFUL)
+		return -1;
+	*val = res;
+	return 0;
+}
+
+static int get_u32_handle(__u32 *handle, const char *str)
+{
+	__u32 htid=0, hash=0, nodeid=0;
+	char *tmp = strchr(str, ':');
+        
+	if (tmp == NULL) {
+		if (memcmp("0x", str, 2) == 0)
+			return get_u32(handle, str, 16);
+		return -1;
+	}
+	htid = strtoul(str, &tmp, 16);
+	if (tmp == str && *str != ':' && *str != 0)
+		return -1;
+	if (htid>=0x1000)
+		return -1;
+	if (*tmp) {
+		str = tmp+1;
+		hash = strtoul(str, &tmp, 16);
+		if (tmp == str && *str != ':' && *str != 0)
+			return -1;
+		if (hash>=0x100)
+			return -1;
+		if (*tmp) {
+			str = tmp+1;
+			nodeid = strtoul(str, &tmp, 16);
+			if (tmp == str && *str != 0)
+				return -1;
+			if (nodeid>=0x1000)
+				return -1;
+		}
+	}
+	*handle = (htid<<20)|(hash<<12)|nodeid;
+	return 0;
+}
+
+static uint32_t get_u32_parse_handle(const char *cHandle)
+{
+	uint32_t handle=0;
+
+	if(get_u32_handle(&handle, cHandle)) {
+		printf ("Illegal \"ht\"\n");
+		return -1;
+	}
+
+	if (handle && TC_U32_NODE(handle)) {
+		printf("\"link\" must be a hash table.\n");
+		return -1;
+	}
+	return handle;
+}
+
+/* 
+ * Function that adds a new filter and attach it to a hash table 
+ * and set next hash table link with hash mask
+ *
+ */
+static
+int u32_add_filter_on_ht_with_hashmask(struct nl_sock *sock, struct rtnl_link *rtnlLink, uint32_t prio, 
+	    uint32_t keyval, uint32_t keymask, int keyoff, int keyoffmask,
+	    uint32_t htid, uint32_t htlink, uint32_t hmask, uint32_t hoffset, struct rtnl_act *act)
+{
+    struct rtnl_cls *cls;
+    int err;
+
+    cls=rtnl_cls_alloc();
+    if (!(cls)) {
+        printf("Can not allocate classifier\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+    
+    rtnl_tc_set_link(TC_CAST(cls), rtnlLink);
+
+    if ((err = rtnl_tc_set_kind(TC_CAST(cls), "u32"))) {
+        printf("Can not set classifier as u32\n");
+        return 1;
+    }
+
+    rtnl_cls_set_prio(cls, prio);
+    rtnl_cls_set_protocol(cls, ETH_P_IP);
+
+    rtnl_tc_set_parent(TC_CAST(cls), TC_HANDLE(0xffff, 0));
+    
+    if (htid)
+	rtnl_u32_set_hashtable(cls, htid);
+
+    rtnl_u32_add_key_uint32(cls, keyval, keymask, keyoff, keyoffmask);
+
+    rtnl_u32_set_hashmask(cls, hmask, hoffset);
+
+    rtnl_u32_set_link(cls, htlink);
+
+    rtnl_u32_add_action(cls, act);
+
+
+    if ((err = rtnl_cls_add(sock, cls, NLM_F_CREATE))) {
+        printf("Can not add classifier: %s\n", nl_geterror(err));
+        return -1;
+    }
+    rtnl_cls_put(cls);
+    return 0;
+}
+
+/* 
+ * function that creates a new hash table 
+ */
+static
+int u32_add_ht(struct nl_sock *sock, struct rtnl_link *rtnlLink, uint32_t prio, uint32_t htid, uint32_t divisor)
+{
+
+    int err;
+    struct rtnl_cls *cls;
+
+    cls=rtnl_cls_alloc();
+    if (!(cls)) {
+        printf("Can not allocate classifier\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+    
+    rtnl_tc_set_link(TC_CAST(cls), rtnlLink);
+
+    if ((err = rtnl_tc_set_kind(TC_CAST(cls), "u32"))) {
+        printf("Can not set classifier as u32\n");
+        return 1;
+    }
+
+    rtnl_cls_set_prio(cls, prio);
+    rtnl_cls_set_protocol(cls, ETH_P_IP);
+    rtnl_tc_set_parent(TC_CAST(cls), TC_HANDLE(0xffff, 0));
+
+    rtnl_u32_set_handle(cls, htid, 0x0, 0x0);
+    //printf("htid: 0x%X\n", htid);
+    rtnl_u32_set_divisor(cls, divisor);
+
+    if ((err = rtnl_cls_add(sock, cls, NLM_F_CREATE))) {
+        printf("Can not add classifier: %s\n", nl_geterror(err));
+        return -1;
+    }
+    rtnl_cls_put(cls);
+    return 0;
+}
+
+/*
+ * function that adds a new ingress qdisc and set the default class for unclassified traffic
+ */
+static
+int qdisc_add_ingress(struct nl_sock *sock, struct rtnl_link *rtnlLink)
+{
+    
+    struct rtnl_qdisc *qdisc;
+    int err;
+    
+    /* Allocation of a qdisc object */
+    if (!(qdisc = rtnl_qdisc_alloc())) {
+        printf("Can not allocate Qdisc\n");
+	return -1;
+    }
+
+    //rtnl_tc_set_ifindex(TC_CAST(qdisc), master_index);
+    rtnl_tc_set_link(TC_CAST(qdisc), rtnlLink);
+    rtnl_tc_set_parent(TC_CAST(qdisc), TC_H_ROOT);
+
+    //printf("Delete current qdisc\n");
+    rtnl_qdisc_delete(sock, qdisc);
+    //rtnl_qdisc_put(qdisc);
+
+    rtnl_tc_set_handle(TC_CAST(qdisc), TC_HANDLE(0xffff, 0));
+
+    if ((err = rtnl_tc_set_kind(TC_CAST(qdisc), "ingress"))) {
+        printf("Can not allocate ingress\n");
+	return -1;
+    }
+
+    /* Submit request to kernel and wait for response */
+    if ((err = rtnl_qdisc_add(sock, qdisc, NLM_F_CREATE))) {
+        printf("Can not allocate ingress Qdisc\n");
+	return -1;
+    }
+
+    /* Return the qdisc object to free memory resources */
+    rtnl_qdisc_put(qdisc);
+
+    return 0;
+}
+
+int main(void)
+{
+    struct nl_sock *sock;
+    struct rtnl_link *link;
+    uint32_t ht, htlink, htid, direction;
+    char chashlink[16]="";
+    int err;
+    struct nl_cache *link_cache;
+    struct rtnl_act *act;
+
+    if (!(sock = nl_socket_alloc())) {
+        printf("Unable to allocate netlink socket\n");
+        exit(1);
+    }
+
+    if ((err = nl_connect(sock, NETLINK_ROUTE)) < 0 ) {
+        printf("Nu s-a putut conecta la NETLINK!\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+
+    if ((err = rtnl_link_alloc_cache(sock, AF_UNSPEC, &link_cache)) < 0) {
+        printf("Unable to allocate link cache: %s\n",
+                             nl_geterror(err));
+        nl_socket_free(sock);
+        exit(1);
+    }
+    
+    /* lookup interface index of eth0 */
+    if (!(link = rtnl_link_get_by_name(link_cache, "eth0"))) {
+        /* error */
+        printf("Interface not found\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+    
+    err=qdisc_add_ingress(sock, link);
+    //printf("Add main hash table\n");
+
+    /* create u32 first hash filter table
+     *
+     */
+    /* formula calcul handle:
+    *         uint32_t handle = (htid << 20) | (hash << 12) | nodeid;
+    */
+
+    /*
+     * Upper limit of number of hash tables: 4096 (0xFFF)
+     * Number of hashes in a table: 256 values (0xFF)
+     *
+     */
+
+    /* using 256 values for hash table 
+     * each entry in hash table match a byte from IP address specified later by a hash key
+     */
+
+    uint32_t i;
+    for (i = 1; i <= 0xf; i++) 
+	u32_add_ht(sock, link, 1, i, 256);
+
+    /* 
+     * attach a u32 filter to the first hash 
+     * that redirects all traffic and make a hash key
+     * from the fist byte of the IP address
+     *
+     */
+
+    //divisor=0x0;	// unused here
+    //handle = 0x0;	// unused here
+    //hash = 0x0;		// unused here
+    //htid = 0x0;		// unused here
+    //nodeid = 0x0;	// unused here
+
+    // direction = 12 -> source IP
+    // direction = 16 -> destination IP
+    direction = 16;
+
+    /*
+     * which hash table will use
+     * in our case is hash table no 1 defined previous
+     *
+     * There are 2 posibilities to set the the hash table:
+     * 1. Using function get_u32_handle and sent a string in
+     *  format 10: where 10 is number of the hash table
+     * 2. Create your own value in format: 0xa00000
+     *
+     */
+    strcpy(chashlink, "1:");
+    //printf("Hash Link: %s\n", chashlink);
+    //chashlink=malloc(sizeof(char) *
+    htlink = 0x0;		// is used by get_u32_handle to return the correct value of hash table (link)
+    
+    if(get_u32_handle(&htlink, chashlink)) {
+        printf ("Illegal \"link\"");
+        nl_socket_free(sock);
+        exit(1);
+    }
+    //printf ("hash link : 0x%X\n", htlink);
+    //printf ("hash link test : %u\n", (htlink && TC_U32_NODE(htlink)));
+
+    if (htlink && TC_U32_NODE(htlink)) {
+	printf("\"link\" must be a hash table.\n");
+        nl_socket_free(sock);
+        exit(1);
+    }
+
+    /* the hash mask will hit the hash table (link) no 1: in our case
+     */
+
+    /* set the hash key mask */
+    //hashmask = 0xFF000000UL;	// the mask that is used to match the hash in specific table, in our case for example 1:a with mean the first byte which is 10 in hash table 1
+
+    /* Here we add a hash filter which match the first byte (see the hashmask value)
+     * of the source IP (offset 12 in the packet header)
+     * You can use also offset 16 to match the destination IP
+     */
+
+    /*
+     * Also we need a filter to match our rule
+     * This mean that we will put a 0.0.0.0/0 filter in our first rule
+     * that match the offset 12 (source IP)
+     * Also you can put offset 16 to match the destination IP
+     */
+
+    u32_add_filter_on_ht_with_hashmask(sock, link, 1, 
+	    0x0, 0x0, direction, 0,
+	    0, htlink, 0xff000000, direction, NULL);
+
+    /*
+     * For each first byte that we need to match we will create a new hash table
+     * For example: you have those clases: 10.0.0.0/24 and 172.16.0.0/23
+     * For byte 10 and byte 172 will create a separate hash table that will match the second
+     * byte from each class.
+     *
+     */
+
+    
+    /*
+     * Now we will create other filter under (ATENTION) our first hash table (link) 1:
+     * Previous rule redirects the trafic according the hash mask to hash table (link) no 1:
+     * Here we will match the hash tables from 1:0 to 1:ff. Under each hash table we will attach 
+     * other rules that matches next byte from IP source/destination IP and we will repeat the 
+     * previous steps.
+     *
+     */
+    
+    act = rtnl_act_alloc();
+    if (!act) {
+            printf("rtnl_act_alloc() returns %p\n", act);
+            return -1;
+   }
+    rtnl_tc_set_kind(TC_CAST(act), "mirred");
+    rtnl_mirred_set_action(act, TCA_EGRESS_REDIR);
+    rtnl_mirred_set_policy(act, TC_ACT_STOLEN);
+    rtnl_mirred_set_ifindex(act, rtnl_link_name2i(link_cache, "eth1"));
+    // /8 check
+
+    // 10.0.0.0/8
+    ht=get_u32_parse_handle("1:a:");
+    htid = (ht&0xFFFFF000);
+    htlink=get_u32_parse_handle("2:");
+
+    u32_add_filter_on_ht_with_hashmask(sock, link, 1, 
+	    0x0a000000, 0xff000000, direction, 0,
+	    htid, htlink, 0x00ff0000, direction, act);
+
+    rtnl_act_put(act);
+    nl_socket_free(sock);
+    return 0;
+}
diff --git a/tests/util.h b/tests/util.h
new file mode 100644
index 0000000..c675383
--- /dev/null
+++ b/tests/util.h
@@ -0,0 +1,5 @@
+#include <check.h>
+
+#define nl_fail_if(condition, error, message) \
+	fail_if((condition), "nlerr=%d (%s): %s", \
+		(error), nl_geterror(error), (message))