am 85a4412b: am 07df5f2e: Merge "Merge v4.0.0 from aosp/upstream-master"

* commit '85a4412bb3003e803811e2ae7b9802ff3ed43235': (658 commits)
  v4.0.0
  ipnetns: add a runtime check for RTM_GETNSID support
  Revert "ip netns: Fix rtnl error while print netns list"
  Revert "configure: add missing INCLUDE to netnsid detection"
  man tc: Add description about class name option
  configure: add missing INCLUDE to netnsid detection
  docs: make spacing consistent
  man ip-link: Add missing link types - vti,ipvlan,nlmon
  ip-link: Align usage at [link-netns ID] line
  man ip-netns: Fix shifted layout at bottom of 'ip netns del'
  tc class: Ignore if default class name file does not exist
  tc: m_bpf: fix next arg selection after tc opcode
  man ip-netns: Fix syntax in default ns process, indent's
  man ip-link: Add ip-netns(8) in 'SEE ALSO'
  lib utils: fix family during af_bit_len calculation
  xfrm: Fix -o (oneline) being broken in xfrm and correct mark radix
  fix ip -force -batch to continue on errors
  bridge: drop reference to unused option embedded from manpage
  ip: Make uniform the use of synonyms list, show and lst
  tc class: Show class names from file
  ...
diff --git a/.gitignore b/.gitignore
index 3ba2632..98d83c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,16 @@
 .gdbinit
 .gdb_history
 *.gdb
+
+# tests
+testsuite/results
+testsuite/iproute2/iproute2-this
+
+# doc files generated at runtime
+doc/*.aux
+doc/*.log
+doc/*.toc
+doc/*.ps
+doc/*.dvi
+doc/*.html
+doc/*.pdf
diff --git a/Makefile b/Makefile
index c107955..9dbb29f 100644
--- a/Makefile
+++ b/Makefile
@@ -20,10 +20,6 @@
 
 DEFINES+=-DCONFDIR=\"$(CONFDIR)\"
 
-#options if you have a bind>=4.9.4 libresolv (or, maybe, glibc)
-LDLIBS=-lresolv
-ADDLIB=
-
 #options for decnet
 ADDLIB+=dnet_ntop.o dnet_pton.o
 
@@ -34,11 +30,13 @@
 HOSTCC = gcc
 DEFINES += -D_GNU_SOURCE
 CCOPTS = -O2
-WFLAGS = -Wall -Wstrict-prototypes
+WFLAGS := -Wall -Wstrict-prototypes  -Wmissing-prototypes
+WFLAGS += -Wmissing-declarations -Wold-style-definition -Wformat=2
+
 CFLAGS = $(WFLAGS) $(CCOPTS) -I../include $(DEFINES)
 YACCFLAGS = -d -t -v
 
-SUBDIRS=lib ip tc misc netem genl man
+SUBDIRS=lib ip tc bridge misc netem genl man
 
 LIBNETLINK=../lib/libnetlink.a ../lib/libutil.a
 LDLIBS += $(LIBNETLINK)
diff --git a/README b/README
index 99d1aeb..c7a5118 100644
--- a/README
+++ b/README
@@ -4,7 +4,7 @@
     http://www.linuxfoundation.org/collaborate/workgroups/networking/iproute2
 
 Download:
-    http://devresources.linuxfoundation.org/dev/iproute2/download
+    http://www.kernel.org/pub/linux/utils/net/iproute2/
 
 Repository:
     git://git.kernel.org/pub/scm/linux/kernel/git/shemminger/iproute2.git
@@ -37,7 +37,7 @@
    kernel include files.
 
 Stephen Hemminger
-shemminger@linux-foundation.org
+stephen@networkplumber.org
 
 Alexey Kuznetsov
 kuznet@ms2.inr.ac.ru
diff --git a/bridge/.gitignore b/bridge/.gitignore
new file mode 100644
index 0000000..7096907
--- /dev/null
+++ b/bridge/.gitignore
@@ -0,0 +1 @@
+bridge
diff --git a/bridge/Makefile b/bridge/Makefile
new file mode 100644
index 0000000..9800753
--- /dev/null
+++ b/bridge/Makefile
@@ -0,0 +1,18 @@
+BROBJ = bridge.o fdb.o monitor.o link.o mdb.o vlan.o
+
+include ../Config
+
+ifeq ($(IP_CONFIG_SETNS),y)
+	CFLAGS += -DHAVE_SETNS
+endif
+
+all: bridge
+
+bridge: $(BROBJ) $(LIBNETLINK) 
+
+install: all
+	install -m 0755 bridge $(DESTDIR)$(SBINDIR)
+
+clean:
+	rm -f $(BROBJ) bridge
+
diff --git a/bridge/br_common.h b/bridge/br_common.h
new file mode 100644
index 0000000..169a162
--- /dev/null
+++ b/bridge/br_common.h
@@ -0,0 +1,20 @@
+extern int print_linkinfo(const struct sockaddr_nl *who,
+			  struct nlmsghdr *n,
+			  void *arg);
+extern int print_fdb(const struct sockaddr_nl *who,
+		     struct nlmsghdr *n, void *arg);
+extern int print_mdb(const struct sockaddr_nl *who,
+		     struct nlmsghdr *n, void *arg);
+
+extern int do_fdb(int argc, char **argv);
+extern int do_mdb(int argc, char **argv);
+extern int do_monitor(int argc, char **argv);
+extern int do_vlan(int argc, char **argv);
+extern int do_link(int argc, char **argv);
+
+extern int preferred_family;
+extern int show_stats;
+extern int show_details;
+extern int timestamp;
+extern int compress_vlans;
+extern struct rtnl_handle rth;
diff --git a/bridge/bridge.c b/bridge/bridge.c
new file mode 100644
index 0000000..88469ca
--- /dev/null
+++ b/bridge/bridge.c
@@ -0,0 +1,141 @@
+/*
+ * Get/set/delete bridge with netlink
+ *
+ * Authors:	Stephen Hemminger <shemminger@vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <string.h>
+
+#include "SNAPSHOT.h"
+#include "utils.h"
+#include "br_common.h"
+#include "namespace.h"
+
+struct rtnl_handle rth = { .fd = -1 };
+int preferred_family = AF_UNSPEC;
+int resolve_hosts;
+int oneline = 0;
+int show_stats;
+int show_details;
+int compress_vlans;
+int timestamp;
+char * _SL_ = NULL;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+"Usage: bridge [ OPTIONS ] OBJECT { COMMAND | help }\n"
+"where  OBJECT := { link | fdb | mdb | vlan | monitor }\n"
+"       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] |\n"
+"                    -o[neline] | -t[imestamp] | -n[etns] name |\n"
+"                    -c[ompressvlans] }\n");
+	exit(-1);
+}
+
+static int do_help(int argc, char **argv)
+{
+	usage();
+}
+
+
+static const struct cmd {
+	const char *cmd;
+	int (*func)(int argc, char **argv);
+} cmds[] = {
+	{ "link", 	do_link },
+	{ "fdb", 	do_fdb },
+	{ "mdb", 	do_mdb },
+	{ "vlan",	do_vlan },
+	{ "monitor",	do_monitor },
+	{ "help",	do_help },
+	{ 0 }
+};
+
+static int do_cmd(const char *argv0, int argc, char **argv)
+{
+	const struct cmd *c;
+
+	for (c = cmds; c->cmd; ++c) {
+		if (matches(argv0, c->cmd) == 0)
+			return c->func(argc-1, argv+1);
+	}
+
+	fprintf(stderr, "Object \"%s\" is unknown, try \"bridge help\".\n", argv0);
+	return -1;
+}
+
+int
+main(int argc, char **argv)
+{
+	while (argc > 1) {
+		char *opt = argv[1];
+		if (strcmp(opt,"--") == 0) {
+			argc--; argv++;
+			break;
+		}
+		if (opt[0] != '-')
+			break;
+		if (opt[1] == '-')
+			opt++;
+
+		if (matches(opt, "-help") == 0) {
+			usage();
+		} else if (matches(opt, "-Version") == 0) {
+			printf("bridge utility, 0.0\n");
+			exit(0);
+		} else if (matches(opt, "-stats") == 0 ||
+			   matches(opt, "-statistics") == 0) {
+			++show_stats;
+		} else if (matches(opt, "-details") == 0) {
+			++show_details;
+		} else if (matches(opt, "-oneline") == 0) {
+			++oneline;
+		} else if (matches(opt, "-timestamp") == 0) {
+			++timestamp;
+                } else if (matches(opt, "-family") == 0) {
+			argc--;
+			argv++;
+			if (argc <= 1)
+				usage();
+			if (strcmp(argv[1], "inet") == 0)
+				preferred_family = AF_INET;
+			else if (strcmp(argv[1], "inet6") == 0)
+				preferred_family = AF_INET6;
+			else if (strcmp(argv[1], "help") == 0)
+				usage();
+			else
+				invarg("invalid protocol family", argv[1]);
+		} else if (strcmp(opt, "-4") == 0) {
+			preferred_family = AF_INET;
+		} else if (strcmp(opt, "-6") == 0) {
+			preferred_family = AF_INET6;
+		} else if (matches(opt, "-netns") == 0) {
+			NEXT_ARG();
+			if (netns_switch(argv[1]))
+				exit(-1);
+		} else if (matches(opt, "-compressvlans") == 0) {
+			++compress_vlans;
+		} else {
+			fprintf(stderr, "Option \"%s\" is unknown, try \"bridge help\".\n", opt);
+			exit(-1);
+		}
+		argc--;	argv++;
+	}
+
+	_SL_ = oneline ? "\\" : "\n" ;
+
+	if (rtnl_open(&rth, 0) < 0)
+		exit(1);
+
+	if (argc > 1)
+		return do_cmd(argv[1], argc-1, argv+1);
+
+	rtnl_close(&rth);
+	usage();
+}
diff --git a/bridge/fdb.c b/bridge/fdb.c
new file mode 100644
index 0000000..3c33e22
--- /dev/null
+++ b/bridge/fdb.c
@@ -0,0 +1,395 @@
+/*
+ * Get/set/delete fdb table with netlink
+ *
+ * TODO: merge/replace this with ip neighbour
+ *
+ * Authors:	Stephen Hemminger <shemminger@vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <linux/neighbour.h>
+#include <string.h>
+#include <limits.h>
+
+#include "libnetlink.h"
+#include "br_common.h"
+#include "rt_names.h"
+#include "utils.h"
+
+static unsigned int filter_index;
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: bridge fdb { add | append | del | replace } ADDR dev DEV {self|master} [ temp ]\n"
+		        "              [router] [ dst IPADDR] [ vlan VID ]\n"
+		        "              [ port PORT] [ vni VNI ] [via DEV]\n");
+	fprintf(stderr, "       bridge fdb {show} [ br BRDEV ] [ brport DEV ]\n");
+	exit(-1);
+}
+
+static const char *state_n2a(unsigned s)
+{
+	static char buf[32];
+
+	if (s & NUD_PERMANENT)
+		return "permanent";
+
+	if (s & NUD_NOARP)
+		return "static";
+
+	if (s & NUD_STALE)
+		return "stale";
+
+	if (s & NUD_REACHABLE)
+		return "";
+
+	sprintf(buf, "state=%#x", s);
+	return buf;
+}
+
+int print_fdb(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = arg;
+	struct ndmsg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr * tb[NDA_MAX+1];
+
+	if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH) {
+		fprintf(stderr, "Not RTM_NEWNEIGH: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (r->ndm_family != AF_BRIDGE)
+		return 0;
+
+	if (filter_index && filter_index != r->ndm_ifindex)
+		return 0;
+
+	parse_rtattr(tb, NDA_MAX, NDA_RTA(r),
+		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+	if (n->nlmsg_type == RTM_DELNEIGH)
+		fprintf(fp, "Deleted ");
+
+	if (tb[NDA_LLADDR]) {
+		SPRINT_BUF(b1);
+		fprintf(fp, "%s ",
+			ll_addr_n2a(RTA_DATA(tb[NDA_LLADDR]),
+				    RTA_PAYLOAD(tb[NDA_LLADDR]),
+				    ll_index_to_type(r->ndm_ifindex),
+				    b1, sizeof(b1)));
+	}
+
+	if (!filter_index && r->ndm_ifindex)
+		fprintf(fp, "dev %s ", ll_index_to_name(r->ndm_ifindex));
+
+	if (tb[NDA_DST]) {
+		SPRINT_BUF(abuf);
+		int family = AF_INET;
+
+		if (RTA_PAYLOAD(tb[NDA_DST]) == sizeof(struct in6_addr))
+			family = AF_INET6;
+
+		fprintf(fp, "dst %s ",
+			format_host(family,
+				    RTA_PAYLOAD(tb[NDA_DST]),
+				    RTA_DATA(tb[NDA_DST]),
+				    abuf, sizeof(abuf)));
+	}
+
+	if (tb[NDA_VLAN]) {
+		__u16 vid = rta_getattr_u16(tb[NDA_VLAN]);
+		fprintf(fp, "vlan %hu ", vid);
+	}
+
+	if (tb[NDA_PORT])
+		fprintf(fp, "port %d ", ntohs(rta_getattr_u16(tb[NDA_PORT])));
+	if (tb[NDA_VNI])
+		fprintf(fp, "vni %d ", rta_getattr_u32(tb[NDA_VNI]));
+	if (tb[NDA_IFINDEX]) {
+		unsigned int ifindex = rta_getattr_u32(tb[NDA_IFINDEX]);
+
+		if (ifindex) {
+			char ifname[IF_NAMESIZE];
+
+			if (!tb[NDA_LINK_NETNSID] &&
+			    if_indextoname(ifindex, ifname))
+				fprintf(fp, "via %s ", ifname);
+			else
+				fprintf(fp, "via ifindex %u ", ifindex);
+		}
+	}
+	if (tb[NDA_LINK_NETNSID])
+		fprintf(fp, "link-netnsid %d ",
+			rta_getattr_u32(tb[NDA_LINK_NETNSID]));
+
+	if (show_stats && tb[NDA_CACHEINFO]) {
+		struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]);
+		int hz = get_user_hz();
+
+		fprintf(fp, "used %d/%d ", ci->ndm_used/hz,
+		       ci->ndm_updated/hz);
+	}
+	if (r->ndm_flags & NTF_SELF)
+		fprintf(fp, "self ");
+	if (tb[NDA_MASTER])
+		fprintf(fp, "master %s ",
+			ll_index_to_name(rta_getattr_u32(tb[NDA_MASTER])));
+	else if (r->ndm_flags & NTF_MASTER)
+		fprintf(fp, "master ");
+	if (r->ndm_flags & NTF_ROUTER)
+		fprintf(fp, "router ");
+	if (r->ndm_flags & NTF_EXT_LEARNED)
+		fprintf(fp, "external ");
+
+	fprintf(fp, "%s\n", state_n2a(r->ndm_state));
+	return 0;
+}
+
+static int fdb_show(int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr 	n;
+		struct ifinfomsg	ifm;
+		char   			buf[256];
+	} req;
+
+	char *filter_dev = NULL;
+	char *br = NULL;
+	int msg_size = sizeof(struct ifinfomsg);
+
+	memset(&req, 0, sizeof(req));
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+	req.ifm.ifi_family = PF_BRIDGE;
+
+	while (argc > 0) {
+		if ((strcmp(*argv, "brport") == 0) || strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			filter_dev = *argv;
+		} else if (strcmp(*argv, "br") == 0) {
+			NEXT_ARG();
+			br = *argv;
+		} else {
+			if (matches(*argv, "help") == 0)
+				usage();
+		}
+		argc--; argv++;
+	}
+
+	if (br) {
+		int br_ifindex = ll_name_to_index(br);
+		if (br_ifindex == 0) {
+			fprintf(stderr, "Cannot find bridge device \"%s\"\n", br);
+			return -1;
+		}
+		addattr32(&req.n, sizeof(req), IFLA_MASTER, br_ifindex);
+		msg_size += RTA_LENGTH(4);
+	}
+
+	/*we'll keep around filter_dev for older kernels */
+	if (filter_dev) {
+		filter_index = if_nametoindex(filter_dev);
+		if (filter_index == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n",
+				filter_dev);
+			return -1;
+		}
+		req.ifm.ifi_index = filter_index;
+	}
+
+	if (rtnl_dump_request(&rth, RTM_GETNEIGH, &req.ifm, msg_size) < 0) {
+		perror("Cannot send dump request");
+		exit(1);
+	}
+
+	if (rtnl_dump_filter(&rth, print_fdb, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+
+	return 0;
+}
+
+static int fdb_modify(int cmd, int flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr 	n;
+		struct ndmsg 		ndm;
+		char   			buf[256];
+	} req;
+	char *addr = NULL;
+	char *d = NULL;
+	char abuf[ETH_ALEN];
+	int dst_ok = 0;
+	inet_prefix dst;
+	unsigned long port = 0;
+	unsigned long vni = ~0;
+	unsigned int via = 0;
+	char *endptr;
+	short vid = -1;
+
+	memset(&req, 0, sizeof(req));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
+	req.n.nlmsg_type = cmd;
+	req.ndm.ndm_family = PF_BRIDGE;
+	req.ndm.ndm_state = NUD_NOARP;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (strcmp(*argv, "dst") == 0) {
+			NEXT_ARG();
+			if (dst_ok)
+				duparg2("dst", *argv);
+			get_addr(&dst, *argv, preferred_family);
+			dst_ok = 1;
+		} else if (strcmp(*argv, "port") == 0) {
+
+			NEXT_ARG();
+			port = strtoul(*argv, &endptr, 0);
+			if (endptr && *endptr) {
+				struct servent *pse;
+
+				pse = getservbyname(*argv, "udp");
+				if (!pse)
+					invarg("invalid port\n", *argv);
+				port = ntohs(pse->s_port);
+			} else if (port > 0xffff)
+				invarg("invalid port\n", *argv);
+		} else if (strcmp(*argv, "vni") == 0) {
+			NEXT_ARG();
+			vni = strtoul(*argv, &endptr, 0);
+			if ((endptr && *endptr) ||
+			    (vni >> 24) || vni == ULONG_MAX)
+				invarg("invalid VNI\n", *argv);
+		} else if (strcmp(*argv, "via") == 0) {
+			NEXT_ARG();
+			via = if_nametoindex(*argv);
+			if (via == 0)
+				invarg("invalid device\n", *argv);
+		} else if (strcmp(*argv, "self") == 0) {
+			req.ndm.ndm_flags |= NTF_SELF;
+		} else if (matches(*argv, "master") == 0) {
+			req.ndm.ndm_flags |= NTF_MASTER;
+		} else if (matches(*argv, "router") == 0) {
+			req.ndm.ndm_flags |= NTF_ROUTER;
+		} else if (matches(*argv, "local") == 0||
+			   matches(*argv, "permanent") == 0) {
+			req.ndm.ndm_state |= NUD_PERMANENT;
+		} else if (matches(*argv, "temp") == 0) {
+			req.ndm.ndm_state |= NUD_REACHABLE;
+		} else if (matches(*argv, "vlan") == 0) {
+			if (vid >= 0)
+				duparg2("vlan", *argv);
+			NEXT_ARG();
+			vid = atoi(*argv);
+		} else {
+			if (strcmp(*argv, "to") == 0) {
+				NEXT_ARG();
+			}
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (addr)
+				duparg2("to", *argv);
+			addr = *argv;
+		}
+		argc--; argv++;
+	}
+
+	if (d == NULL || addr == NULL) {
+		fprintf(stderr, "Device and address are required arguments.\n");
+		return -1;
+	}
+
+	/* Assume self */
+	if (!(req.ndm.ndm_flags&(NTF_SELF|NTF_MASTER)))
+		req.ndm.ndm_flags |= NTF_SELF;
+
+	/* Assume permanent */
+	if (!(req.ndm.ndm_state&(NUD_PERMANENT|NUD_REACHABLE)))
+		req.ndm.ndm_state |= NUD_PERMANENT;
+
+	if (sscanf(addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+		   abuf, abuf+1, abuf+2,
+		   abuf+3, abuf+4, abuf+5) != 6) {
+		fprintf(stderr, "Invalid mac address %s\n", addr);
+		return -1;
+	}
+
+	addattr_l(&req.n, sizeof(req), NDA_LLADDR, abuf, ETH_ALEN);
+	if (dst_ok)
+		addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen);
+
+	if (vid >= 0)
+		addattr16(&req.n, sizeof(req), NDA_VLAN, vid);
+
+	if (port) {
+		unsigned short dport;
+
+		dport = htons((unsigned short)port);
+		addattr16(&req.n, sizeof(req), NDA_PORT, dport);
+	}
+	if (vni != ~0)
+		addattr32(&req.n, sizeof(req), NDA_VNI, vni);
+	if (via)
+		addattr32(&req.n, sizeof(req), NDA_IFINDEX, via);
+
+	req.ndm.ndm_ifindex = ll_name_to_index(d);
+	if (req.ndm.ndm_ifindex == 0) {
+		fprintf(stderr, "Cannot find device \"%s\"\n", d);
+		return -1;
+	}
+
+	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
+		return -1;
+
+	return 0;
+}
+
+int do_fdb(int argc, char **argv)
+{
+	ll_init_map(&rth);
+
+	if (argc > 0) {
+		if (matches(*argv, "add") == 0)
+			return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+		if (matches(*argv, "append") == 0)
+			return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_APPEND, argc-1, argv+1);
+		if (matches(*argv, "replace") == 0)
+			return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
+		if (matches(*argv, "delete") == 0)
+			return fdb_modify(RTM_DELNEIGH, 0, argc-1, argv+1);
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return fdb_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return fdb_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"bridge fdb help\".\n", *argv);
+	exit(-1);
+}
diff --git a/bridge/link.c b/bridge/link.c
new file mode 100644
index 0000000..1af1cf3
--- /dev/null
+++ b/bridge/link.c
@@ -0,0 +1,476 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <linux/if.h>
+#include <linux/if_bridge.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "libnetlink.h"
+#include "utils.h"
+#include "br_common.h"
+
+static unsigned int filter_index;
+
+static const char *port_states[] = {
+	[BR_STATE_DISABLED] = "disabled",
+	[BR_STATE_LISTENING] = "listening",
+	[BR_STATE_LEARNING] = "learning",
+	[BR_STATE_FORWARDING] = "forwarding",
+	[BR_STATE_BLOCKING] = "blocking",
+};
+
+extern char *if_indextoname (unsigned int __ifindex, char *__ifname);
+
+static void print_link_flags(FILE *fp, unsigned flags)
+{
+	fprintf(fp, "<");
+	if (flags & IFF_UP && !(flags & IFF_RUNNING))
+		fprintf(fp, "NO-CARRIER%s", flags ? "," : "");
+	flags &= ~IFF_RUNNING;
+#define _PF(f) if (flags&IFF_##f) { \
+                  flags &= ~IFF_##f ; \
+                  fprintf(fp, #f "%s", flags ? "," : ""); }
+	_PF(LOOPBACK);
+	_PF(BROADCAST);
+	_PF(POINTOPOINT);
+	_PF(MULTICAST);
+	_PF(NOARP);
+	_PF(ALLMULTI);
+	_PF(PROMISC);
+	_PF(MASTER);
+	_PF(SLAVE);
+	_PF(DEBUG);
+	_PF(DYNAMIC);
+	_PF(AUTOMEDIA);
+	_PF(PORTSEL);
+	_PF(NOTRAILERS);
+	_PF(UP);
+	_PF(LOWER_UP);
+	_PF(DORMANT);
+	_PF(ECHO);
+#undef _PF
+        if (flags)
+		fprintf(fp, "%x", flags);
+	fprintf(fp, "> ");
+}
+
+static const char *oper_states[] = {
+	"UNKNOWN", "NOTPRESENT", "DOWN", "LOWERLAYERDOWN",
+	"TESTING", "DORMANT",	 "UP"
+};
+
+static const char *hw_mode[] = {"VEB", "VEPA"};
+
+static void print_operstate(FILE *f, __u8 state)
+{
+	if (state >= sizeof(oper_states)/sizeof(oper_states[0]))
+		fprintf(f, "state %#x ", state);
+	else
+		fprintf(f, "state %s ", oper_states[state]);
+}
+
+static void print_portstate(FILE *f, __u8 state)
+{
+	if (state <= BR_STATE_BLOCKING)
+		fprintf(f, "state %s ", port_states[state]);
+	else
+		fprintf(f, "state (%d) ", state);
+}
+
+static void print_onoff(FILE *f, char *flag, __u8 val)
+{
+	fprintf(f, "%s %s ", flag, val ? "on" : "off");
+}
+
+static void print_hwmode(FILE *f, __u16 mode)
+{
+	if (mode >= sizeof(hw_mode)/sizeof(hw_mode[0]))
+		fprintf(f, "hwmode %#hx ", mode);
+	else
+		fprintf(f, "hwmode %s ", hw_mode[mode]);
+}
+
+int print_linkinfo(const struct sockaddr_nl *who,
+		   struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = arg;
+	int len = n->nlmsg_len;
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct rtattr * tb[IFLA_MAX+1];
+	char b1[IFNAMSIZ];
+
+	len -= NLMSG_LENGTH(sizeof(*ifi));
+	if (len < 0) {
+		fprintf(stderr, "Message too short!\n");
+		return -1;
+        }
+
+	if (!(ifi->ifi_family == AF_BRIDGE || ifi->ifi_family == AF_UNSPEC))
+		return 0;
+
+	if (filter_index && filter_index != ifi->ifi_index)
+		return 0;
+
+	parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(ifi), len, NLA_F_NESTED);
+
+	if (tb[IFLA_IFNAME] == NULL) {
+		fprintf(stderr, "BUG: nil ifname\n");
+		return -1;
+	}
+
+	if (n->nlmsg_type == RTM_DELLINK)
+		fprintf(fp, "Deleted ");
+
+	fprintf(fp, "%d: %s ", ifi->ifi_index,
+		tb[IFLA_IFNAME] ? rta_getattr_str(tb[IFLA_IFNAME]) : "<nil>");
+
+	if (tb[IFLA_OPERSTATE])
+		print_operstate(fp, rta_getattr_u8(tb[IFLA_OPERSTATE]));
+
+	if (tb[IFLA_LINK]) {
+		SPRINT_BUF(b1);
+		int iflink = rta_getattr_u32(tb[IFLA_LINK]);
+		if (iflink == 0)
+			fprintf(fp, "@NONE: ");
+		else
+			fprintf(fp, "@%s: ",
+				if_indextoname(iflink, b1));
+	} else
+		fprintf(fp, ": ");
+
+	print_link_flags(fp, ifi->ifi_flags);
+
+	if (tb[IFLA_MTU])
+		fprintf(fp, "mtu %u ", rta_getattr_u32(tb[IFLA_MTU]));
+
+	if (tb[IFLA_MASTER])
+		fprintf(fp, "master %s ",
+			if_indextoname(rta_getattr_u32(tb[IFLA_MASTER]), b1));
+
+	if (tb[IFLA_PROTINFO]) {
+		if (tb[IFLA_PROTINFO]->rta_type & NLA_F_NESTED) {
+			struct rtattr *prtb[IFLA_BRPORT_MAX+1];
+
+			parse_rtattr_nested(prtb, IFLA_BRPORT_MAX,
+					    tb[IFLA_PROTINFO]);
+
+			if (prtb[IFLA_BRPORT_STATE])
+				print_portstate(fp,
+						rta_getattr_u8(prtb[IFLA_BRPORT_STATE]));
+			if (prtb[IFLA_BRPORT_PRIORITY])
+				fprintf(fp, "priority %hu ",
+					rta_getattr_u16(prtb[IFLA_BRPORT_PRIORITY]));
+			if (prtb[IFLA_BRPORT_COST])
+				fprintf(fp, "cost %u ",
+					rta_getattr_u32(prtb[IFLA_BRPORT_COST]));
+
+			if (show_details) {
+				fprintf(fp, "%s    ", _SL_);
+
+				if (prtb[IFLA_BRPORT_MODE])
+					print_onoff(fp, "hairpin",
+						    rta_getattr_u8(prtb[IFLA_BRPORT_MODE]));
+				if (prtb[IFLA_BRPORT_GUARD])
+					print_onoff(fp, "guard",
+						    rta_getattr_u8(prtb[IFLA_BRPORT_GUARD]));
+				if (prtb[IFLA_BRPORT_PROTECT])
+					print_onoff(fp, "root_block",
+						    rta_getattr_u8(prtb[IFLA_BRPORT_PROTECT]));
+				if (prtb[IFLA_BRPORT_FAST_LEAVE])
+					print_onoff(fp, "fastleave",
+						    rta_getattr_u8(prtb[IFLA_BRPORT_FAST_LEAVE]));
+				if (prtb[IFLA_BRPORT_LEARNING])
+					print_onoff(fp, "learning",
+						    rta_getattr_u8(prtb[IFLA_BRPORT_LEARNING]));
+				if (prtb[IFLA_BRPORT_LEARNING_SYNC])
+					print_onoff(fp, "learning_sync",
+						    rta_getattr_u8(prtb[IFLA_BRPORT_LEARNING_SYNC]));
+				if (prtb[IFLA_BRPORT_UNICAST_FLOOD])
+					print_onoff(fp, "flood",
+						    rta_getattr_u8(prtb[IFLA_BRPORT_UNICAST_FLOOD]));
+			}
+		} else
+			print_portstate(fp, rta_getattr_u8(tb[IFLA_PROTINFO]));
+	}
+
+	if (tb[IFLA_AF_SPEC]) {
+		/* This is reported by HW devices that have some bridging
+		 * capabilities.
+		 */
+		struct rtattr *aftb[IFLA_BRIDGE_MAX+1];
+
+		parse_rtattr_nested(aftb, IFLA_BRIDGE_MAX, tb[IFLA_AF_SPEC]);
+
+		if (aftb[IFLA_BRIDGE_MODE])
+			print_hwmode(fp, rta_getattr_u16(aftb[IFLA_BRIDGE_MODE]));
+	}
+
+	fprintf(fp, "\n");
+	fflush(fp);
+	return 0;
+}
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: bridge link set dev DEV [ cost COST ] [ priority PRIO ] [ state STATE ]\n");
+	fprintf(stderr, "                               [ guard {on | off} ]\n");
+	fprintf(stderr, "                               [ hairpin {on | off} ] \n");
+	fprintf(stderr, "                               [ fastleave {on | off} ]\n");
+	fprintf(stderr,	"                               [ root_block {on | off} ]\n");
+	fprintf(stderr,	"                               [ learning {on | off} ]\n");
+	fprintf(stderr,	"                               [ learning_sync {on | off} ]\n");
+	fprintf(stderr,	"                               [ flood {on | off} ]\n");
+	fprintf(stderr, "                               [ hwmode {vepa | veb} ]\n");
+	fprintf(stderr, "                               [ self ] [ master ]\n");
+	fprintf(stderr, "       bridge link show [dev DEV]\n");
+	exit(-1);
+}
+
+static bool on_off(char *arg, __s8 *attr, char *val)
+{
+	if (strcmp(val, "on") == 0)
+		*attr = 1;
+	else if (strcmp(val, "off") == 0)
+		*attr = 0;
+	else {
+		fprintf(stderr,
+			"Error: argument of \"%s\" must be \"on\" or \"off\"\n",
+			arg);
+		return false;
+	}
+
+	return true;
+}
+
+static int brlink_modify(int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr  n;
+		struct ifinfomsg ifm;
+		char             buf[512];
+	} req;
+	char *d = NULL;
+	__s8 learning = -1;
+	__s8 learning_sync = -1;
+	__s8 flood = -1;
+	__s8 hairpin = -1;
+	__s8 bpdu_guard = -1;
+	__s8 fast_leave = -1;
+	__s8 root_block = -1;
+	__u32 cost = 0;
+	__s16 priority = -1;
+	__s8 state = -1;
+	__s16 mode = -1;
+	__u16 flags = 0;
+	struct rtattr *nest;
+
+	memset(&req, 0, sizeof(req));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST;
+	req.n.nlmsg_type = RTM_SETLINK;
+	req.ifm.ifi_family = PF_BRIDGE;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (strcmp(*argv, "guard") == 0) {
+			NEXT_ARG();
+			if (!on_off("guard", &bpdu_guard, *argv))
+				return -1;
+		} else if (strcmp(*argv, "hairpin") == 0) {
+			NEXT_ARG();
+			if (!on_off("hairping", &hairpin, *argv))
+				return -1;
+		} else if (strcmp(*argv, "fastleave") == 0) {
+			NEXT_ARG();
+			if (!on_off("fastleave", &fast_leave, *argv))
+				return -1;
+		} else if (strcmp(*argv, "root_block") == 0) {
+			NEXT_ARG();
+			if (!on_off("root_block", &root_block, *argv))
+				return -1;
+		} else if (strcmp(*argv, "learning") == 0) {
+			NEXT_ARG();
+			if (!on_off("learning", &learning, *argv))
+				return -1;
+		} else if (strcmp(*argv, "learning_sync") == 0) {
+			NEXT_ARG();
+			if (!on_off("learning_sync", &learning_sync, *argv))
+				return -1;
+		} else if (strcmp(*argv, "flood") == 0) {
+			NEXT_ARG();
+			if (!on_off("flood", &flood, *argv))
+				return -1;
+		} else if (strcmp(*argv, "cost") == 0) {
+			NEXT_ARG();
+			cost = atoi(*argv);
+		} else if (strcmp(*argv, "priority") == 0) {
+			NEXT_ARG();
+			priority = atoi(*argv);
+		} else if (strcmp(*argv, "state") == 0) {
+			NEXT_ARG();
+			char *endptr;
+			size_t nstates = sizeof(port_states) / sizeof(*port_states);
+			state = strtol(*argv, &endptr, 10);
+			if (!(**argv != '\0' && *endptr == '\0')) {
+				for (state = 0; state < nstates; state++)
+					if (strcmp(port_states[state], *argv) == 0)
+						break;
+				if (state == nstates) {
+					fprintf(stderr,
+						"Error: invalid STP port state\n");
+					return -1;
+				}
+			}
+		} else if (strcmp(*argv, "hwmode") == 0) {
+			NEXT_ARG();
+			flags = BRIDGE_FLAGS_SELF;
+			if (strcmp(*argv, "vepa") == 0)
+				mode = BRIDGE_MODE_VEPA;
+			else if (strcmp(*argv, "veb") == 0)
+				mode = BRIDGE_MODE_VEB;
+			else {
+				fprintf(stderr,
+					"Mode argument must be \"vepa\" or "
+					"\"veb\".\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "self") == 0) {
+			flags |= BRIDGE_FLAGS_SELF;
+		} else if (strcmp(*argv, "master") == 0) {
+			flags |= BRIDGE_FLAGS_MASTER;
+		} else {
+			usage();
+		}
+		argc--; argv++;
+	}
+	if (d == NULL) {
+		fprintf(stderr, "Device is a required argument.\n");
+		return -1;
+	}
+
+
+	req.ifm.ifi_index = ll_name_to_index(d);
+	if (req.ifm.ifi_index == 0) {
+		fprintf(stderr, "Cannot find bridge device \"%s\"\n", d);
+		return -1;
+	}
+
+	/* Nested PROTINFO attribute.  Contains: port flags, cost, priority and
+	 * state.
+	 */
+	nest = addattr_nest(&req.n, sizeof(req),
+			    IFLA_PROTINFO | NLA_F_NESTED);
+	/* Flags first */
+	if (bpdu_guard >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_GUARD, bpdu_guard);
+	if (hairpin >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_MODE, hairpin);
+	if (fast_leave >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_FAST_LEAVE,
+			 fast_leave);
+	if (root_block >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_PROTECT, root_block);
+	if (flood >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_UNICAST_FLOOD, flood);
+	if (learning >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_LEARNING, learning);
+	if (learning_sync >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_LEARNING_SYNC,
+			 learning_sync);
+
+	if (cost > 0)
+		addattr32(&req.n, sizeof(req), IFLA_BRPORT_COST, cost);
+
+	if (priority >= 0)
+		addattr16(&req.n, sizeof(req), IFLA_BRPORT_PRIORITY, priority);
+
+	if (state >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_STATE, state);
+
+	addattr_nest_end(&req.n, nest);
+
+	/* IFLA_AF_SPEC nested attribute. Contains IFLA_BRIDGE_FLAGS that
+	 * designates master or self operation and IFLA_BRIDGE_MODE
+	 * for hw 'vepa' or 'veb' operation modes. The hwmodes are
+	 * only valid in 'self' mode on some devices so far.
+	 */
+	if (mode >= 0 || flags > 0) {
+		nest = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+
+		if (flags > 0)
+			addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, flags);
+
+		if (mode >= 0)
+			addattr16(&req.n, sizeof(req), IFLA_BRIDGE_MODE, mode);
+
+		addattr_nest_end(&req.n, nest);
+	}
+
+	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
+		return -1;
+
+	return 0;
+}
+
+static int brlink_show(int argc, char **argv)
+{
+	char *filter_dev = NULL;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (filter_dev)
+				duparg("dev", *argv);
+			filter_dev = *argv;
+		}
+		argc--; argv++;
+	}
+
+	if (filter_dev) {
+		if ((filter_index = ll_name_to_index(filter_dev)) == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n",
+				filter_dev);
+			return -1;
+		}
+	}
+
+	if (rtnl_wilddump_request(&rth, PF_BRIDGE, RTM_GETLINK) < 0) {
+		perror("Cannon send dump request");
+		exit(1);
+	}
+
+	if (rtnl_dump_filter(&rth, print_linkinfo, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	return 0;
+}
+
+int do_link(int argc, char **argv)
+{
+	ll_init_map(&rth);
+	if (argc > 0) {
+		if (matches(*argv, "set") == 0 ||
+		    matches(*argv, "change") == 0)
+			return brlink_modify(argc-1, argv+1);
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return brlink_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return brlink_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"bridge link help\".\n", *argv);
+	exit(-1);
+}
diff --git a/bridge/mdb.c b/bridge/mdb.c
new file mode 100644
index 0000000..a6b2882
--- /dev/null
+++ b/bridge/mdb.c
@@ -0,0 +1,254 @@
+/*
+ * Get mdb table with netlink
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "libnetlink.h"
+#include "br_common.h"
+#include "rt_names.h"
+#include "utils.h"
+
+#ifndef MDBA_RTA
+#define MDBA_RTA(r) \
+	((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct br_port_msg))))
+#endif
+
+static unsigned int filter_index;
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: bridge mdb { add | del } dev DEV port PORT grp GROUP [permanent | temp]\n");
+	fprintf(stderr, "       bridge mdb {show} [ dev DEV ]\n");
+	exit(-1);
+}
+
+static void br_print_router_ports(FILE *f, struct rtattr *attr)
+{
+	uint32_t *port_ifindex;
+	struct rtattr *i;
+	int rem;
+
+	rem = RTA_PAYLOAD(attr);
+	for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		port_ifindex = RTA_DATA(i);
+		fprintf(f, "%s ", ll_index_to_name(*port_ifindex));
+	}
+
+	fprintf(f, "\n");
+}
+
+static void print_mdb_entry(FILE *f, int ifindex, struct br_mdb_entry *e)
+{
+	SPRINT_BUF(abuf);
+
+	if (e->addr.proto == htons(ETH_P_IP))
+		fprintf(f, "dev %s port %s grp %s %s\n", ll_index_to_name(ifindex),
+			ll_index_to_name(e->ifindex),
+			inet_ntop(AF_INET, &e->addr.u.ip4, abuf, sizeof(abuf)),
+			(e->state & MDB_PERMANENT) ? "permanent" : "temp");
+	else
+		fprintf(f, "dev %s port %s grp %s %s\n", ll_index_to_name(ifindex),
+			ll_index_to_name(e->ifindex),
+			inet_ntop(AF_INET6, &e->addr.u.ip6, abuf, sizeof(abuf)),
+			(e->state & MDB_PERMANENT) ? "permanent" : "temp");
+}
+
+static void br_print_mdb_entry(FILE *f, int ifindex, struct rtattr *attr)
+{
+	struct rtattr *i;
+	int rem;
+	struct br_mdb_entry *e;
+
+	rem = RTA_PAYLOAD(attr);
+	for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		e = RTA_DATA(i);
+		print_mdb_entry(f, ifindex, e);
+	}
+}
+
+int print_mdb(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = arg;
+	struct br_port_msg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr * tb[MDBA_MAX+1];
+
+	if (n->nlmsg_type != RTM_GETMDB && n->nlmsg_type != RTM_NEWMDB && n->nlmsg_type != RTM_DELMDB) {
+		fprintf(stderr, "Not RTM_GETMDB, RTM_NEWMDB or RTM_DELMDB: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (filter_index && filter_index != r->ifindex)
+		return 0;
+
+	parse_rtattr(tb, MDBA_MAX, MDBA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+	if (tb[MDBA_MDB]) {
+		struct rtattr *i;
+		int rem = RTA_PAYLOAD(tb[MDBA_MDB]);
+
+		for (i = RTA_DATA(tb[MDBA_MDB]); RTA_OK(i, rem); i = RTA_NEXT(i, rem))
+			br_print_mdb_entry(fp, r->ifindex, i);
+	}
+
+	if (tb[MDBA_ROUTER]) {
+		if (show_details) {
+			fprintf(fp, "router ports on %s: ", ll_index_to_name(r->ifindex));
+			br_print_router_ports(fp, tb[MDBA_ROUTER]);
+		}
+	}
+
+	return 0;
+}
+
+static int mdb_show(int argc, char **argv)
+{
+	char *filter_dev = NULL;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (filter_dev)
+				duparg("dev", *argv);
+			filter_dev = *argv;
+		}
+		argc--; argv++;
+	}
+
+	if (filter_dev) {
+		filter_index = if_nametoindex(filter_dev);
+		if (filter_index == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n",
+				filter_dev);
+			return -1;
+		}
+	}
+
+	if (rtnl_wilddump_request(&rth, PF_BRIDGE, RTM_GETMDB) < 0) {
+		perror("Cannot send dump request");
+		return -1;
+	}
+
+	if (rtnl_dump_filter(&rth, print_mdb, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int mdb_modify(int cmd, int flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr 	n;
+		struct br_port_msg	bpm;
+		char   			buf[1024];
+	} req;
+	struct br_mdb_entry entry;
+	char *d = NULL, *p = NULL, *grp = NULL;
+
+	memset(&req, 0, sizeof(req));
+	memset(&entry, 0, sizeof(entry));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_port_msg));
+	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
+	req.n.nlmsg_type = cmd;
+	req.bpm.family = PF_BRIDGE;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (strcmp(*argv, "grp") == 0) {
+			NEXT_ARG();
+			grp = *argv;
+		} else if (strcmp(*argv, "port") == 0) {
+			NEXT_ARG();
+			p = *argv;
+		} else if (strcmp(*argv, "permanent") == 0) {
+			if (cmd == RTM_NEWMDB)
+				entry.state |= MDB_PERMANENT;
+		} else if (strcmp(*argv, "temp") == 0) {
+			;/* nothing */
+		} else {
+			if (matches(*argv, "help") == 0)
+				usage();
+		}
+		argc--; argv++;
+	}
+
+	if (d == NULL || grp == NULL || p == NULL) {
+		fprintf(stderr, "Device, group address and port name are required arguments.\n");
+		return -1;
+	}
+
+	req.bpm.ifindex = ll_name_to_index(d);
+	if (req.bpm.ifindex == 0) {
+		fprintf(stderr, "Cannot find device \"%s\"\n", d);
+		return -1;
+	}
+
+	entry.ifindex = ll_name_to_index(p);
+	if (entry.ifindex == 0) {
+		fprintf(stderr, "Cannot find device \"%s\"\n", p);
+		return -1;
+	}
+
+	if (!inet_pton(AF_INET, grp, &entry.addr.u.ip4)) {
+		if (!inet_pton(AF_INET6, grp, &entry.addr.u.ip6)) {
+			fprintf(stderr, "Invalid address \"%s\"\n", grp);
+			return -1;
+		} else
+			entry.addr.proto = htons(ETH_P_IPV6);
+	} else
+		entry.addr.proto = htons(ETH_P_IP);
+
+	addattr_l(&req.n, sizeof(req), MDBA_SET_ENTRY, &entry, sizeof(entry));
+
+	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
+		return -1;
+
+	return 0;
+}
+
+int do_mdb(int argc, char **argv)
+{
+	ll_init_map(&rth);
+
+	if (argc > 0) {
+		if (matches(*argv, "add") == 0)
+			return mdb_modify(RTM_NEWMDB, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+		if (matches(*argv, "delete") == 0)
+			return mdb_modify(RTM_DELMDB, 0, argc-1, argv+1);
+
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return mdb_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return mdb_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"bridge mdb help\".\n", *argv);
+	exit(-1);
+}
diff --git a/bridge/monitor.c b/bridge/monitor.c
new file mode 100644
index 0000000..9e1ed48
--- /dev/null
+++ b/bridge/monitor.c
@@ -0,0 +1,143 @@
+/*
+ * brmonitor.c		"bridge monitor"
+ *
+ *		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.
+ *
+ * Authors:	Stephen Hemminger <shemminger@vyatta.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/neighbour.h>
+#include <string.h>
+
+#include "utils.h"
+#include "br_common.h"
+
+
+static void usage(void) __attribute__((noreturn));
+int prefix_banner;
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: bridge monitor [file | link | fdb | mdb | all]\n");
+	exit(-1);
+}
+
+static int accept_msg(const struct sockaddr_nl *who,
+		      struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = arg;
+
+	if (timestamp)
+		print_timestamp(fp);
+
+	switch (n->nlmsg_type) {
+	case RTM_NEWLINK:
+	case RTM_DELLINK:
+		if (prefix_banner)
+			fprintf(fp, "[LINK]");
+
+		return print_linkinfo(who, n, arg);
+
+	case RTM_NEWNEIGH:
+	case RTM_DELNEIGH:
+		if (prefix_banner)
+			fprintf(fp, "[NEIGH]");
+		return print_fdb(who, n, arg);
+
+	case RTM_NEWMDB:
+	case RTM_DELMDB:
+		if (prefix_banner)
+			fprintf(fp, "[MDB]");
+		return print_mdb(who, n, arg);
+
+	case NLMSG_TSTAMP:
+		print_nlmsg_timestamp(fp, n);
+		return 0;
+
+	default:
+		return 0;
+	}
+}
+
+int do_monitor(int argc, char **argv)
+{
+	char *file = NULL;
+	unsigned groups = ~RTMGRP_TC;
+	int llink=0;
+	int lneigh=0;
+	int lmdb=0;
+
+	rtnl_close(&rth);
+
+	while (argc > 0) {
+		if (matches(*argv, "file") == 0) {
+			NEXT_ARG();
+			file = *argv;
+		} else if (matches(*argv, "link") == 0) {
+			llink=1;
+			groups = 0;
+		} else if (matches(*argv, "fdb") == 0) {
+			lneigh = 1;
+			groups = 0;
+		} else if (matches(*argv, "mdb") == 0) {
+			lmdb = 1;
+			groups = 0;
+		} else if (strcmp(*argv, "all") == 0) {
+			groups = ~RTMGRP_TC;
+			prefix_banner=1;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			fprintf(stderr, "Argument \"%s\" is unknown, try \"bridge monitor help\".\n", *argv);
+			exit(-1);
+		}
+		argc--;	argv++;
+	}
+
+	if (llink)
+		groups |= nl_mgrp(RTNLGRP_LINK);
+
+	if (lneigh) {
+		groups |= nl_mgrp(RTNLGRP_NEIGH);
+	}
+
+	if (lmdb) {
+		groups |= nl_mgrp(RTNLGRP_MDB);
+	}
+
+	if (file) {
+		FILE *fp;
+		int err;
+		fp = fopen(file, "r");
+		if (fp == NULL) {
+			perror("Cannot fopen");
+			exit(-1);
+		}
+		err = rtnl_from_file(fp, accept_msg, stdout);
+		fclose(fp);
+		return err;
+	}
+
+	if (rtnl_open(&rth, groups) < 0)
+		exit(1);
+	ll_init_map(&rth);
+
+	if (rtnl_listen(&rth, accept_msg, stdout) < 0)
+		exit(2);
+
+	return 0;
+}
+
diff --git a/bridge/vlan.c b/bridge/vlan.c
new file mode 100644
index 0000000..2ae739c
--- /dev/null
+++ b/bridge/vlan.c
@@ -0,0 +1,263 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <string.h>
+
+#include "libnetlink.h"
+#include "br_common.h"
+#include "utils.h"
+
+static unsigned int filter_index;
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: bridge vlan { add | del } vid VLAN_ID dev DEV [ pvid] [ untagged ]\n");
+	fprintf(stderr, "                                                     [ self ] [ master ]\n");
+	fprintf(stderr, "       bridge vlan { show } [ dev DEV ]\n");
+	exit(-1);
+}
+
+static int vlan_modify(int cmd, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr 	n;
+		struct ifinfomsg 	ifm;
+		char   			buf[1024];
+	} req;
+	char *d = NULL;
+	short vid = -1;
+	short vid_end = -1;
+	struct rtattr *afspec;
+	struct bridge_vlan_info vinfo;
+	unsigned short flags = 0;
+
+	memset(&vinfo, 0, sizeof(vinfo));
+	memset(&req, 0, sizeof(req));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST;
+	req.n.nlmsg_type = cmd;
+	req.ifm.ifi_family = PF_BRIDGE;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (strcmp(*argv, "vid") == 0) {
+			char *p;
+			NEXT_ARG();
+			p = strchr(*argv, '-');
+			if (p) {
+				*p = '\0';
+				p++;
+				vid = atoi(*argv);
+				vid_end = atoi(p);
+				vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
+			} else {
+				vid = atoi(*argv);
+			}
+		} else if (strcmp(*argv, "self") == 0) {
+			flags |= BRIDGE_FLAGS_SELF;
+		} else if (strcmp(*argv, "master") == 0) {
+			flags |= BRIDGE_FLAGS_MASTER;
+		} else if (strcmp(*argv, "pvid") == 0) {
+			vinfo.flags |= BRIDGE_VLAN_INFO_PVID;
+		} else if (strcmp(*argv, "untagged") == 0) {
+			vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
+		} else {
+			if (matches(*argv, "help") == 0) {
+				NEXT_ARG();
+			}
+		}
+		argc--; argv++;
+	}
+
+	if (d == NULL || vid == -1) {
+		fprintf(stderr, "Device and VLAN ID are required arguments.\n");
+		return -1;
+	}
+
+	req.ifm.ifi_index = ll_name_to_index(d);
+	if (req.ifm.ifi_index == 0) {
+		fprintf(stderr, "Cannot find bridge device \"%s\"\n", d);
+		return -1;
+	}
+
+	if (vid >= 4096) {
+		fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid);
+		return -1;
+	}
+
+	if (vinfo.flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
+		if (vid_end == -1 || vid_end >= 4096 || vid >= vid_end) {
+			fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n",
+				vid, vid_end);
+			return -1;
+		}
+		if (vinfo.flags & BRIDGE_VLAN_INFO_PVID) {
+			fprintf(stderr,
+				"pvid cannot be configured for a vlan range\n");
+			return -1;
+		}
+	}
+
+	afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+
+	if (flags)
+		addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, flags);
+
+	vinfo.vid = vid;
+	if (vid_end != -1) {
+		/* send vlan range start */
+		addattr_l(&req.n, sizeof(req), IFLA_BRIDGE_VLAN_INFO, &vinfo,
+			  sizeof(vinfo));
+		vinfo.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN;
+
+		/* Now send the vlan range end */
+		vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_END;
+		vinfo.vid = vid_end;
+		addattr_l(&req.n, sizeof(req), IFLA_BRIDGE_VLAN_INFO, &vinfo,
+			  sizeof(vinfo));
+	} else {
+		addattr_l(&req.n, sizeof(req), IFLA_BRIDGE_VLAN_INFO, &vinfo,
+			  sizeof(vinfo));
+	}
+
+	addattr_nest_end(&req.n, afspec);
+
+	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
+		return -1;
+
+	return 0;
+}
+
+static int print_vlan(const struct sockaddr_nl *who,
+		      struct nlmsghdr *n,
+		      void *arg)
+{
+	FILE *fp = arg;
+	struct ifinfomsg *ifm = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr * tb[IFLA_MAX+1];
+
+	if (n->nlmsg_type != RTM_NEWLINK) {
+		fprintf(stderr, "Not RTM_NEWLINK: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(sizeof(*ifm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (ifm->ifi_family != AF_BRIDGE)
+		return 0;
+
+	if (filter_index && filter_index != ifm->ifi_index)
+		return 0;
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifm), len);
+
+	/* if AF_SPEC isn't there, vlan table is not preset for this port */
+	if (!tb[IFLA_AF_SPEC]) {
+		fprintf(fp, "%s\tNone\n", ll_index_to_name(ifm->ifi_index));
+		return 0;
+	} else {
+		struct rtattr *i, *list = tb[IFLA_AF_SPEC];
+		int rem = RTA_PAYLOAD(list);
+
+		fprintf(fp, "%s", ll_index_to_name(ifm->ifi_index));
+		for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+			struct bridge_vlan_info *vinfo;
+
+			if (i->rta_type != IFLA_BRIDGE_VLAN_INFO)
+				continue;
+
+			vinfo = RTA_DATA(i);
+			if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_END)
+				fprintf(fp, "-%hu", vinfo->vid);
+			else
+				fprintf(fp, "\t %hu", vinfo->vid);
+			if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN)
+				continue;
+			if (vinfo->flags & BRIDGE_VLAN_INFO_PVID)
+				fprintf(fp, " PVID");
+			if (vinfo->flags & BRIDGE_VLAN_INFO_UNTAGGED)
+				fprintf(fp, " Egress Untagged");
+			fprintf(fp, "\n");
+		}
+	}
+	fprintf(fp, "\n");
+	fflush(fp);
+	return 0;
+}
+
+static int vlan_show(int argc, char **argv)
+{
+	char *filter_dev = NULL;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (filter_dev)
+				duparg("dev", *argv);
+			filter_dev = *argv;
+		}
+		argc--; argv++;
+	}
+
+	if (filter_dev) {
+		if ((filter_index = if_nametoindex(filter_dev)) == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n",
+			       filter_dev);
+			return -1;
+		}
+	}
+
+	if (rtnl_wilddump_req_filter(&rth, PF_BRIDGE, RTM_GETLINK,
+				    (compress_vlans ?
+				    RTEXT_FILTER_BRVLAN_COMPRESSED :
+				    RTEXT_FILTER_BRVLAN)) < 0) {
+		perror("Cannont send dump request");
+		exit(1);
+	}
+
+	printf("port\tvlan ids\n");
+	if (rtnl_dump_filter(&rth, print_vlan, stdout) < 0) {
+		fprintf(stderr, "Dump ternminated\n");
+		exit(1);
+	}
+
+	return 0;
+}
+
+
+int do_vlan(int argc, char **argv)
+{
+	ll_init_map(&rth);
+
+	if (argc > 0) {
+		if (matches(*argv, "add") == 0)
+			return vlan_modify(RTM_SETLINK, argc-1, argv+1);
+		if (matches(*argv, "delete") == 0)
+			return vlan_modify(RTM_DELLINK, argc-1, argv+1);
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return vlan_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return vlan_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"bridge fdb help\".\n", *argv);
+	exit(-1);
+}
diff --git a/configure b/configure
index 0f4444f..c3dacdb 100755
--- a/configure
+++ b/configure
@@ -1,15 +1,43 @@
 #! /bin/bash
-# This is not an autconf generated configure
+# This is not an autoconf generated configure
 #
 INCLUDE=${1:-"$PWD/include"}
 
 # Make a temp directory in build tree.
 TMPDIR=$(mktemp -d config.XXXXXX)
-trap 'status=$?; rm -rf $TMPDIRa; exit $status' EXIT HUP INT QUIT TERM
+trap 'status=$?; rm -rf $TMPDIR; exit $status' EXIT HUP INT QUIT TERM
+
+check_prog()
+{
+    echo -n "$2"
+    command -v $1 >/dev/null 2>&1 && (echo "$3:=y" >> Config; echo "yes") || (echo "no"; return 1)
+}
+
+check_docs()
+{
+    if check_prog latex " latex: " HAVE_LATEX; then
+        check_prog pdflatex " pdflatex: " HAVE_PDFLATEX || echo " WARNING: no PDF docs can be built from LaTeX files"
+        check_prog sgml2latex " sgml2latex: " HAVE_SGML2LATEX || echo " WARNING: no LaTeX files can be build from SGML files"
+    else
+        echo " WARNING: no docs can be built from LaTeX files"
+    fi
+
+    check_prog sgml2html " sgml2html: " HAVE_SGML2HTML || echo " WARNING: no HTML docs can be built from SGML"
+}
+
+check_toolchain()
+{
+    : ${PKG_CONFIG:=pkg-config}
+    : ${AR=ar}
+    : ${CC=gcc}
+    echo "PKG_CONFIG:=${PKG_CONFIG}" >>Config
+    echo "AR:=${AR}" >>Config
+    echo "CC:=${CC}" >>Config
+}
 
 check_atm()
 {
-cat >$TMPDIR/atmtest.c <<EOF
+    cat >$TMPDIR/atmtest.c <<EOF
 #include <atm.h>
 int main(int argc, char **argv) {
 	struct atm_qos qos;
@@ -17,21 +45,22 @@
 	return 0;
 }
 EOF
-gcc -I$INCLUDE -o $TMPDIR/atmtest $TMPDIR/atmtest.c -latm >/dev/null 2>&1 
-if [ $? -eq 0 ]
-then
-    echo "TC_CONFIG_ATM:=y" >>Config
-    echo yes
-else
-    echo no
-fi
-rm -f $TMPDIR/atmtest.c $TMPDIR/atmtest
+
+    $CC -I$INCLUDE -o $TMPDIR/atmtest $TMPDIR/atmtest.c -latm >/dev/null 2>&1
+    if [ $? -eq 0 ]
+    then
+	echo "TC_CONFIG_ATM:=y" >>Config
+	echo yes
+    else
+	echo no
+    fi
+    rm -f $TMPDIR/atmtest.c $TMPDIR/atmtest
 }
 
 check_xt()
 {
-#check if we have xtables from iptables >= 1.4.5.
-cat >$TMPDIR/ipttest.c <<EOF
+    #check if we have xtables from iptables >= 1.4.5.
+    cat >$TMPDIR/ipttest.c <<EOF
 #include <xtables.h>
 #include <linux/netfilter.h>
 static struct xtables_globals test_globals = {
@@ -48,27 +77,27 @@
 	xtables_init_all(&test_globals, NFPROTO_IPV4);
 	return 0;
 }
-
 EOF
 
-if gcc -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL $(pkg-config xtables --cflags --libs) -ldl >/dev/null 2>&1
-then
+    if $CC -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL \
+	$(${PKG_CONFIG} xtables --cflags --libs) -ldl >/dev/null 2>&1
+    then
 	echo "TC_CONFIG_XT:=y" >>Config
 	echo "using xtables"
-fi
-rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
+    fi
+    rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
 }
 
 check_xt_old()
 {
-# bail if previous XT checks has already succeded.
-if grep TC_CONFIG_XT Config > /dev/null
-then
+    # bail if previous XT checks has already succeded.
+    if grep -q TC_CONFIG_XT Config
+    then
 	return
-fi
+    fi
 
-#check if we dont need our internal header ..
-cat >$TMPDIR/ipttest.c <<EOF
+    #check if we dont need our internal header ..
+    cat >$TMPDIR/ipttest.c <<EOF
 #include <xtables.h>
 char *lib_dir;
 unsigned int global_option_offset = 0;
@@ -88,26 +117,26 @@
 }
 
 EOF
-gcc -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL -ldl >/dev/null 2>&1
 
-if [ $? -eq 0 ]
-then
+    $CC -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL -ldl >/dev/null 2>&1
+    if [ $? -eq 0 ]
+    then
 	echo "TC_CONFIG_XT_OLD:=y" >>Config
 	echo "using old xtables (no need for xt-internal.h)"
-fi
-rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
+    fi
+    rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
 }
 
 check_xt_old_internal_h()
 {
-# bail if previous XT checks has already succeded.
-if grep TC_CONFIG_XT Config > /dev/null
-then
+    # bail if previous XT checks has already succeded.
+    if grep -q TC_CONFIG_XT Config
+    then
 	return
-fi
+    fi
 
-#check if we need our own internal.h
-cat >$TMPDIR/ipttest.c <<EOF
+    #check if we need our own internal.h
+    cat >$TMPDIR/ipttest.c <<EOF
 #include <xtables.h>
 #include "xt-internal.h"
 char *lib_dir;
@@ -128,14 +157,14 @@
 }
 
 EOF
-gcc -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL -ldl >/dev/null 2>&1
+	$CC -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL -ldl >/dev/null 2>&1
 
-if [ $? -eq 0 ]
-then
-	echo "using old xtables with xt-internal.h"
-	echo "TC_CONFIG_XT_OLD_H:=y" >>Config
-fi
-rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
+	if [ $? -eq 0 ]
+	then
+	    echo "using old xtables with xt-internal.h"
+	    echo "TC_CONFIG_XT_OLD_H:=y" >>Config
+	fi
+	rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
 }
 
 check_ipt()
@@ -148,7 +177,13 @@
 
 check_ipt_lib_dir()
 {
-	IPT_LIB_DIR=""
+	IPT_LIB_DIR=$(${PKG_CONFIG} --variable=xtlibdir xtables)
+	if [ -n "$IPT_LIB_DIR" ]; then
+		echo $IPT_LIB_DIR
+		echo "IPT_LIB_DIR:=$IPT_LIB_DIR" >> Config
+		return
+	fi
+
 	for dir in /lib /usr/lib /usr/local/lib
 	do
 		for file in $dir/{xtables,iptables}/lib*t_*so ; do
@@ -164,7 +199,7 @@
 
 check_setns()
 {
-cat >$TMPDIR/setnstest.c <<EOF
+    cat >$TMPDIR/setnstest.c <<EOF
 #include <sched.h>
 int main(int argc, char **argv) 
 {
@@ -172,18 +207,62 @@
 	return 0;
 }
 EOF
-gcc -I$INCLUDE -o $TMPDIR/setnstest $TMPDIR/setnstest.c >/dev/null 2>&1
-if [ $? -eq 0 ]
-then
+    $CC -I$INCLUDE -o $TMPDIR/setnstest $TMPDIR/setnstest.c >/dev/null 2>&1
+    if [ $? -eq 0 ]
+    then
 	echo "IP_CONFIG_SETNS:=y" >>Config
 	echo "yes"
-else
+    else
 	echo "no"
-fi
-rm -f $TMPDIR/setnstest.c $TMPDIR/setnstest
+    fi
+    rm -f $TMPDIR/setnstest.c $TMPDIR/setnstest
+}
+
+check_ipset()
+{
+    cat >$TMPDIR/ipsettest.c <<EOF
+#include <linux/netfilter/ipset/ip_set.h>
+#ifndef IP_SET_INVALID
+#define IPSET_DIM_MAX 3
+typedef unsigned short ip_set_id_t;
+#endif
+#include <linux/netfilter/xt_set.h>
+
+struct xt_set_info info;
+#if IPSET_PROTOCOL == 6
+int main(void)
+{
+	return IPSET_MAXNAMELEN;
+}
+#else
+#error unknown ipset version
+#endif
+EOF
+
+    if $CC -I$INCLUDE -o $TMPDIR/ipsettest $TMPDIR/ipsettest.c >/dev/null 2>&1
+    then
+	echo "TC_CONFIG_IPSET:=y" >>Config
+	echo "yes"
+    else
+	echo "no"
+    fi
+    rm -f $TMPDIR/ipsettest.c $TMPDIR/ipsettest
+}
+
+check_selinux()
+# SELinux is a compile time option in the ss utility
+{
+	if ${PKG_CONFIG} libselinux --exists
+	then
+		echo "HAVE_SELINUX:=y" >>Config
+		echo "yes"
+	else
+		echo "no"
+	fi
 }
 
 echo "# Generated config based on" $INCLUDE >Config
+check_toolchain
 
 echo "TC schedulers"
 
@@ -196,8 +275,17 @@
 check_xt_old_internal_h
 check_ipt
 
-echo -n "iptables modules directory: "
+echo -n " IPSET  "
+check_ipset
+
+echo -n -e "\niptables modules directory: "
 check_ipt_lib_dir
 
 echo -n "libc has setns: "
 check_setns
+
+echo -n "SELinux support: "
+check_selinux
+
+echo -e "\nDocs"
+check_docs
diff --git a/doc/Makefile b/doc/Makefile
index 1df6081..e9c0ff7 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -6,7 +6,7 @@
 
 LATEX=latex
 DVIPS=dvips
-SGML2DVI=sgml2latex --output=dvi
+SGML2DVI=sgml2latex
 SGML2HTML=sgml2html -s 0
 LPR=lpr -Zsduplex
 SHELL=bash
@@ -31,8 +31,11 @@
 print: $(PSFILES)
 	$(LPR) $(PSFILES)
 
+%.tex: %.sgml
+	$(SGML2DVI) --output=tex $<
+
 %.dvi: %.sgml
-	$(SGML2DVI) $<
+	$(SGML2DVI) --output=dvi $<
 
 %.dvi: %.tex
 	@set -e; pass=2; echo "Running LaTeX $<"; \
@@ -44,10 +47,17 @@
 		echo "Re-running LaTeX $<, $${pass}d pass"; pass=$$[$$pass + 1]; \
 	done
 
-#%.pdf: %.tex
-#	pdflatex $<
-%.pdf: %.ps
-	ps2pdf $<
+%.pdf: %.tex
+	@set -e; pass=2; echo "Running pdfLaTeX $<"; \
+	while [ `pdflatex $< </dev/null 2>&1 | \
+		 grep -c '^\(LaTeX Warning: Label(s) may\|No file \|! Emergency stop\)'` -ge 1 ]; do \
+		if [ $$pass -gt 3 ]; then \
+			echo "Seems, something is wrong. Try by hands." ; exit 1 ; \
+		fi; \
+		echo "Re-running pdfLaTeX $<, $${pass}d pass"; pass=$$[$$pass + 1]; \
+	done
+#%.pdf: %.ps
+#	ps2pdf $<
 
 %.ps: %.dvi
 	$(DVIPS) $< -o $@
@@ -60,4 +70,4 @@
 	install -m 0644 $(shell echo *.sgml) $(DESTDIR)$(DOCDIR)
 
 clean:
-	rm -f *.aux *.log *.toc $(PSFILES) $(DVIFILES) *.html
+	rm -f *.aux *.log *.toc $(PSFILES) $(DVIFILES) *.html *.pdf
diff --git a/doc/ip-cref.tex b/doc/ip-cref.tex
index d8fed66..e7a79a5 100644
--- a/doc/ip-cref.tex
+++ b/doc/ip-cref.tex
@@ -61,6 +61,9 @@
 appears twice or more, the amount of information increases.
 As a rule, the information is statistics or some time values.
 
+\item \verb|-d|, \verb|-details|
+
+--- output more detailed information.
 
 \item \verb|-f|, \verb|-family| followed by a protocol family
 identifier: \verb|inet|, \verb|inet6| or \verb|link|.
@@ -103,6 +106,38 @@
  \verb|ip| never uses DNS to resolve names to addresses.
 \end{NB}
 
+\item \verb|-b|, \verb|-batch FILE|
+
+--- read commands from provided file or standart input and invoke them.
+First failure will cause termination of \verb|ip|.
+In batch \verb|FILE| everything which begins with \verb|#| symbol is
+ignored and can be used for comments.
+\paragraph{Example:}
+\begin{verbatim}
+kuznet@kaiser $ cat /tmp/ip_batch.ip
+# This is a comment
+tuntap add mode tap tap1 # This is an another comment
+link set up dev tap1
+addr add 10.0.0.1/24 dev tap1
+kuznet@kaiser $ sudo ip -b /tmp/ip_batch.ip
+\end{verbatim}
+or from standart input:
+\begin{verbatim}
+kuznet@kaiser $ cat /tmp/ip_batch.ip | sudo ip -b -
+\end{verbatim}
+
+\item \verb|-force|
+
+--- don't terminate ip on errors in batch mode.
+If there were any errors during execution of the commands,
+the application return code will be non zero.
+
+\item \verb|-l|, \verb|-loops COUNT|
+
+--- specify maximum number of loops the 'ip addr flush' logic will attempt
+before giving up. The default is 10.  Zero (0) means loop until all
+addresses are removed.
+
 \end{itemize}
 
 \verb|OBJECT| is the object to manage or to get information about.
@@ -2542,13 +2577,15 @@
 the \verb|monitor| command is the first in the command line and then
 the object list follows:
 \begin{verbatim}
-  ip monitor [ file FILE ] [ all | OBJECT-LIST ]
+  ip monitor [ file FILE ] [ all | OBJECT-LIST ] [ label ]
 \end{verbatim}
-\verb|OBJECT-LIST| is the list of object types that we want to monitor.
-It may contain \verb|link|, \verb|address| and \verb|route|.
-If no \verb|file| argument is given, \verb|ip| opens RTNETLINK,
-listens on it and dumps state changes in the format described
-in previous sections.
+\verb|OBJECT-LIST| is the list of object types that we want to
+monitor.  It may contain \verb|link|, \verb|address| and \verb|route|.
+Specifying \verb|label| indicates that output lines should be labelled
+with the type of object being printed --- this happens by default if
+\verb|all| is specified.  If no \verb|file| argument is given,
+\verb|ip| opens RTNETLINK, listens on it and dumps state changes in
+the format described in previous sections.
 
 If a file name is given, it does not listen on RTNETLINK,
 but opens the file containing RTNETLINK messages saved in binary format
diff --git a/doc/nstat.sgml b/doc/nstat.sgml
index be9d8bc..48cacc6 100644
--- a/doc/nstat.sgml
+++ b/doc/nstat.sgml
@@ -3,7 +3,7 @@
 <article>
 
 <title>NSTAT, IFSTAT and RTACCT Utilities
-<author>Alexey Kuznetosv, <tt/kuznet@ms2.inr.ac.ru/
+<author>Alexey Kuznetsov, <tt/kuznet@ms2.inr.ac.ru/
 <date>some_negative_number, 20 Sep 2001
 <abstract>
 <tt/nstat/, <tt/ifstat/ and <tt/rtacct/ are simple tools helping
diff --git a/doc/ss.sgml b/doc/ss.sgml
index 0b1b533..3024b57 100644
--- a/doc/ss.sgml
+++ b/doc/ss.sgml
@@ -3,7 +3,7 @@
 <article>
 
 <title>SS Utility: Quick Intro
-<author>Alexey Kuznetosv, <tt/kuznet@ms2.inr.ac.ru/
+<author>Alexey Kuznetsov, <tt/kuznet@ms2.inr.ac.ru/
 <date>some_negative_number, 20 Sep 2001
 <abstract>
 <tt/ss/ is one another utility to investigate sockets.
diff --git a/etc/iproute2/ematch_map b/etc/iproute2/ematch_map
index 7c6a281..1823983 100644
--- a/etc/iproute2/ematch_map
+++ b/etc/iproute2/ematch_map
@@ -3,3 +3,5 @@
 2	nbyte
 3	u32
 4	meta
+7	canid
+8	ipset
diff --git a/etc/iproute2/nl_protos b/etc/iproute2/nl_protos
new file mode 100644
index 0000000..43418f3
--- /dev/null
+++ b/etc/iproute2/nl_protos
@@ -0,0 +1,23 @@
+# Netlink protocol names mapping
+
+0   rtnl
+1   unused
+2   usersock
+3   fw
+4   tcpdiag
+5   nflog
+6   xfrm
+7   selinux
+8   iscsi
+9   audit
+10  fiblookup
+11  connector
+12  nft 
+13  ip6fw
+14  dec-rt
+15  uevent
+16  genl
+18  scsi-trans
+19  ecryptfs
+20  rdma
+21  crypto 
diff --git a/etc/iproute2/rt_dsfield b/etc/iproute2/rt_dsfield
index 496ef66..1426d60 100644
--- a/etc/iproute2/rt_dsfield
+++ b/etc/iproute2/rt_dsfield
@@ -1,17 +1,6 @@
-0x00	default
-0x10	lowdelay
-0x08	throughput
-0x04	reliability
-# This value overlap with ECT, do not use it!
-0x02	mincost
-# These values seems do not want to die, Cisco likes them by a strange reason.
-0x20	priority
-0x40	immediate
-0x60	flash
-0x80	flash-override
-0xa0	critical
-0xc0	internet
-0xe0	network
+# Differentiated field values
+# These include the DSCP and unused bits
+0x0	default
 # Newer RFC2597 values
 0x28	AF11
 0x30	AF12
@@ -25,3 +14,13 @@
 0x88	AF41
 0x90	AF42
 0x98	AF43
+# Older values RFC2474
+0x20	CS1
+0x40	CS2
+0x60	CS3
+0x80	CS4
+0xA0	CS5
+0xC0	CS6
+0xE0	CS7
+# RFC 2598
+0xB8	EF
diff --git a/etc/iproute2/rt_protos b/etc/iproute2/rt_protos
index 38d8ec4..82cf9c4 100644
--- a/etc/iproute2/rt_protos
+++ b/etc/iproute2/rt_protos
@@ -15,6 +15,7 @@
 14	xorp
 15	ntk
 16      dhcp
+42	babel
 
 #
 #	Used by me for gated
diff --git a/examples/cbq.init-v0.7.3 b/examples/cbq.init-v0.7.3
index 888aba4..35a0a05 100644
--- a/examples/cbq.init-v0.7.3
+++ b/examples/cbq.init-v0.7.3
@@ -18,8 +18,7 @@
 #    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, write to the Free Software
-#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#    along with this program; if not, see <http://www.gnu.org/licenses/>.
 #
 #    To get the latest version, check on Freshmeat for actual location:
 #
diff --git a/genl/ctrl.c b/genl/ctrl.c
index 6d97c26..3546129 100644
--- a/genl/ctrl.c
+++ b/genl/ctrl.c
@@ -112,7 +112,7 @@
 	return ret;
 }
 
-void print_ctrl_cmd_flags(FILE *fp, __u32 fl)
+static void print_ctrl_cmd_flags(FILE *fp, __u32 fl)
 {
 	fprintf(fp, "\n\t\tCapabilities (0x%x):\n ", fl);
 	if (!fl) {
@@ -399,7 +399,7 @@
 	if (matches(*argv, "help") == 0)
 		return usage();
 
-	fprintf(stderr, "ctrl command \"%s\" is unknown, try \"ctrl -help\".\n",
+	fprintf(stderr, "ctrl command \"%s\" is unknown, try \"ctrl help\".\n",
 		*argv);
 
 	return -1;
diff --git a/genl/static-syms.c b/genl/static-syms.c
index 1ed3a8a..0bc8074 100644
--- a/genl/static-syms.c
+++ b/genl/static-syms.c
@@ -1,4 +1,12 @@
+/*
+ * This file creates a dummy version of dynamic loading
+ * for environments where dynamic linking
+ * is not used or available.
+ */
+
 #include <string.h>
+#include "dlfcn.h"
+
 void *_dlsym(const char *sym)
 {
 #include "static-syms.h"
diff --git a/include/SNAPSHOT.h b/include/SNAPSHOT.h
index 9395cab..8bd0c56 100644
--- a/include/SNAPSHOT.h
+++ b/include/SNAPSHOT.h
@@ -1 +1 @@
-static const char SNAPSHOT[] = "120521";
+static const char SNAPSHOT[] = "150413";
diff --git a/include/dlfcn.h b/include/dlfcn.h
index b0be5a0..c54f8d8 100644
--- a/include/dlfcn.h
+++ b/include/dlfcn.h
@@ -8,6 +8,7 @@
 #else
 
 #define RTLD_LAZY 0
+#define RTLD_GLOBAL 1
 #define _FAKE_DLFCN_HDL (void *)0xbeefcafe
 
 static inline void *dlopen(const char *file, int flag)
diff --git a/include/hlist.h b/include/hlist.h
new file mode 100644
index 0000000..4e8de9e
--- /dev/null
+++ b/include/hlist.h
@@ -0,0 +1,56 @@
+#ifndef __HLIST_H__
+#define __HLIST_H__ 1
+/* Hash list stuff from kernel */
+
+#include <stddef.h>
+
+#define container_of(ptr, type, member) ({			\
+	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
+	(type *)( (char *)__mptr - offsetof(type,member) );})
+
+struct hlist_head {
+	struct hlist_node *first;
+};
+
+struct hlist_node {
+	struct hlist_node *next, **pprev;
+};
+
+static inline void hlist_del(struct hlist_node *n)
+{
+	struct hlist_node *next = n->next;
+	struct hlist_node **pprev = n->pprev;
+	*pprev = next;
+	if (next)
+		next->pprev = pprev;
+}
+
+static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
+{
+	struct hlist_node *first = h->first;
+	n->next = first;
+	if (first)
+		first->pprev = &n->next;
+	h->first = n;
+	n->pprev = &h->first;
+}
+
+#define hlist_for_each(pos, head) \
+	for (pos = (head)->first; pos ; pos = pos->next)
+
+
+#define hlist_for_each_safe(pos, n, head) \
+	for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
+	     pos = n)
+
+#define hlist_entry_safe(ptr, type, member) \
+	({ typeof(ptr) ____ptr = (ptr); \
+	   ____ptr ? hlist_entry(____ptr, type, member) : NULL; \
+	})
+
+#define hlist_for_each_entry(pos, head, member)				\
+	for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member);\
+	     pos;							\
+	     pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
+
+#endif /* __HLIST_H__ */
diff --git a/include/iptables.h b/include/iptables.h
index dd844c1..f1e62e2 100644
--- a/include/iptables.h
+++ b/include/iptables.h
@@ -143,6 +143,8 @@
 /* Your shared library should call one of these. */
 extern void register_match(struct iptables_match *me);
 extern void register_target(struct iptables_target *me);
+extern void xtables_register_target(struct iptables_target *me);
+extern int build_st(struct iptables_target *target, struct ipt_entry_target *t);
 
 extern struct in_addr *dotted_to_addr(const char *dotted);
 extern char *addr_to_dotted(const struct in_addr *addrp);
diff --git a/include/libgenl.h b/include/libgenl.h
new file mode 100644
index 0000000..9db4baf
--- /dev/null
+++ b/include/libgenl.h
@@ -0,0 +1,25 @@
+#ifndef __LIBGENL_H__
+#define __LIBGENL_H__
+
+#include "libnetlink.h"
+
+#define GENL_REQUEST(_req, _bufsiz, _family, _hdrsiz, _ver, _cmd, _flags) \
+struct {								\
+	struct nlmsghdr		n;					\
+	struct genlmsghdr	g;					\
+	char			buf[NLMSG_ALIGN(_hdrsiz) + (_bufsiz)];	\
+} _req = {								\
+	.n = {								\
+		.nlmsg_type = (_family),				\
+		.nlmsg_flags = (_flags),				\
+		.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN + (_hdrsiz)),	\
+	},								\
+	.g = {								\
+		.cmd = (_cmd),						\
+		.version = (_ver),					\
+	},								\
+}
+
+extern int genl_resolve_family(struct rtnl_handle *grth, const char *family);
+
+#endif /* __LIBGENL_H__ */
diff --git a/include/libnetlink.h b/include/libnetlink.h
index 81649af..898275b 100644
--- a/include/libnetlink.h
+++ b/include/libnetlink.h
@@ -1,6 +1,7 @@
 #ifndef __LIBNETLINK_H__
 #define __LIBNETLINK_H__ 1
 
+#include <stdio.h>
 #include <string.h>
 #include <asm/types.h>
 #include <linux/netlink.h>
@@ -8,6 +9,7 @@
 #include <linux/if_link.h>
 #include <linux/if_addr.h>
 #include <linux/neighbour.h>
+#include <linux/netconf.h>
 
 struct rtnl_handle
 {
@@ -16,15 +18,28 @@
 	struct sockaddr_nl	peer;
 	__u32			seq;
 	__u32			dump;
+	int			proto;
+	FILE		       *dump_fp;
 };
 
 extern int rcvbuf;
 
-extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions);
-extern int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, int protocol);
+extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions)
+	__attribute__((warn_unused_result));
+
+extern int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions,
+			     int protocol)
+	__attribute__((warn_unused_result));
+
 extern void rtnl_close(struct rtnl_handle *rth);
-extern int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type);
-extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len);
+extern int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type)
+	__attribute__((warn_unused_result));
+extern int rtnl_wilddump_req_filter(struct rtnl_handle *rth, int fam, int type,
+				    __u32 filt_mask)
+	__attribute__((warn_unused_result));
+extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req,
+			     int len)
+	__attribute__((warn_unused_result));
 
 typedef int (*rtnl_filter_t)(const struct sockaddr_nl *,
 			     struct nlmsghdr *n, void *);
@@ -40,9 +55,12 @@
 extern int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter,
 			    void *arg);
 extern int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer,
-		     unsigned groups, struct nlmsghdr *answer);
-extern int rtnl_send(struct rtnl_handle *rth, const void *buf, int);
-extern int rtnl_send_check(struct rtnl_handle *rth, const void *buf, int);
+		     unsigned groups, struct nlmsghdr *answer)
+	__attribute__((warn_unused_result));
+extern int rtnl_send(struct rtnl_handle *rth, const void *buf, int)
+	__attribute__((warn_unused_result));
+extern int rtnl_send_check(struct rtnl_handle *rth, const void *buf, int)
+	__attribute__((warn_unused_result));
 
 extern int addattr(struct nlmsghdr *n, int maxlen, int type);
 extern int addattr8(struct nlmsghdr *n, int maxlen, int type, __u8 data);
@@ -61,12 +79,18 @@
 extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data, int alen);
 
 extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len);
+extern int parse_rtattr_flags(struct rtattr *tb[], int max, struct rtattr *rta,
+			      int len, unsigned short flags);
 extern int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, int len);
+extern struct rtattr *parse_rtattr_one(int type, struct rtattr *rta, int len);
 extern int __parse_rtattr_nested_compat(struct rtattr *tb[], int max, struct rtattr *rta, int len);
 
 #define parse_rtattr_nested(tb, max, rta) \
 	(parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta)))
 
+#define parse_rtattr_one_nested(type, rta) \
+	(parse_rtattr_one(type, RTA_DATA(rta), RTA_PAYLOAD(rta)))
+
 #define parse_rtattr_nested_compat(tb, max, rta, data, len) \
 	({ data = RTA_PAYLOAD(rta) >= len ? RTA_DATA(rta) : NULL;	\
 		__parse_rtattr_nested_compat(tb, max, rta, len); })
@@ -134,5 +158,17 @@
 #define NDTA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndtmsg))
 #endif
 
+#ifndef NETNS_RTA
+#define NETNS_RTA(r) \
+	((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))))
+#endif
+#ifndef NETNS_PAYLOAD
+#define NETNS_PAYLOAD(n)	NLMSG_PAYLOAD(n,sizeof(struct rtgenmsg))
+#endif
+
+/* User defined nlmsg_type which is used mostly for logging netlink
+ * messages from dump file */
+#define NLMSG_TSTAMP	15
+
 #endif /* __LIBNETLINK_H__ */
 
diff --git a/include/linux/atm.h b/include/linux/atm.h
index 5e1c36d..08e27be 100644
--- a/include/linux/atm.h
+++ b/include/linux/atm.h
@@ -238,4 +238,4 @@
 
 
 typedef unsigned short atm_backend_t;
-#endif
+#endif /* _LINUX_ATM_H */
diff --git a/include/linux/bpf_common.h b/include/linux/bpf_common.h
new file mode 100644
index 0000000..afe7433
--- /dev/null
+++ b/include/linux/bpf_common.h
@@ -0,0 +1,55 @@
+#ifndef __LINUX_BPF_COMMON_H__
+#define __LINUX_BPF_COMMON_H__
+
+/* Instruction classes */
+#define BPF_CLASS(code) ((code) & 0x07)
+#define		BPF_LD		0x00
+#define		BPF_LDX		0x01
+#define		BPF_ST		0x02
+#define		BPF_STX		0x03
+#define		BPF_ALU		0x04
+#define		BPF_JMP		0x05
+#define		BPF_RET		0x06
+#define		BPF_MISC        0x07
+
+/* ld/ldx fields */
+#define BPF_SIZE(code)  ((code) & 0x18)
+#define		BPF_W		0x00
+#define		BPF_H		0x08
+#define		BPF_B		0x10
+#define BPF_MODE(code)  ((code) & 0xe0)
+#define		BPF_IMM		0x00
+#define		BPF_ABS		0x20
+#define		BPF_IND		0x40
+#define		BPF_MEM		0x60
+#define		BPF_LEN		0x80
+#define		BPF_MSH		0xa0
+
+/* alu/jmp fields */
+#define BPF_OP(code)    ((code) & 0xf0)
+#define		BPF_ADD		0x00
+#define		BPF_SUB		0x10
+#define		BPF_MUL		0x20
+#define		BPF_DIV		0x30
+#define		BPF_OR		0x40
+#define		BPF_AND		0x50
+#define		BPF_LSH		0x60
+#define		BPF_RSH		0x70
+#define		BPF_NEG		0x80
+#define		BPF_MOD		0x90
+#define		BPF_XOR		0xa0
+
+#define		BPF_JA		0x00
+#define		BPF_JEQ		0x10
+#define		BPF_JGT		0x20
+#define		BPF_JGE		0x30
+#define		BPF_JSET        0x40
+#define BPF_SRC(code)   ((code) & 0x08)
+#define		BPF_K		0x00
+#define		BPF_X		0x08
+
+#ifndef BPF_MAXINSNS
+#define BPF_MAXINSNS 4096
+#endif
+
+#endif /* __LINUX_BPF_COMMON_H__ */
diff --git a/include/linux/can.h b/include/linux/can.h
new file mode 100644
index 0000000..d9ba97f
--- /dev/null
+++ b/include/linux/can.h
@@ -0,0 +1,194 @@
+/*
+ * linux/can.h
+ *
+ * Definitions for CAN network layer (socket addr / CAN frame / CAN filter)
+ *
+ * Authors: Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
+ *          Urs Thuermann   <urs.thuermann@volkswagen.de>
+ * Copyright (c) 2002-2007 Volkswagen Group Electronic Research
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Volkswagen nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * Alternatively, provided that this notice is retained in full, this
+ * software may be distributed under the terms of the GNU General
+ * Public License ("GPL") version 2, in which case the provisions of the
+ * GPL apply INSTEAD OF those given above.
+ *
+ * The provided data structures and external interfaces from this code
+ * are not restricted to be used by modules with a GPL compatible license.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CAN_H
+#define _CAN_H
+
+#include <linux/types.h>
+#include <linux/socket.h>
+
+/* controller area network (CAN) kernel definitions */
+
+/* special address description flags for the CAN_ID */
+#define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */
+#define CAN_RTR_FLAG 0x40000000U /* remote transmission request */
+#define CAN_ERR_FLAG 0x20000000U /* error message frame */
+
+/* valid bits in CAN ID for frame formats */
+#define CAN_SFF_MASK 0x000007FFU /* standard frame format (SFF) */
+#define CAN_EFF_MASK 0x1FFFFFFFU /* extended frame format (EFF) */
+#define CAN_ERR_MASK 0x1FFFFFFFU /* omit EFF, RTR, ERR flags */
+
+/*
+ * Controller Area Network Identifier structure
+ *
+ * bit 0-28	: CAN identifier (11/29 bit)
+ * bit 29	: error message frame flag (0 = data frame, 1 = error message)
+ * bit 30	: remote transmission request flag (1 = rtr frame)
+ * bit 31	: frame format flag (0 = standard 11 bit, 1 = extended 29 bit)
+ */
+typedef __u32 canid_t;
+
+#define CAN_SFF_ID_BITS		11
+#define CAN_EFF_ID_BITS		29
+
+/*
+ * Controller Area Network Error Message Frame Mask structure
+ *
+ * bit 0-28	: error class mask (see include/linux/can/error.h)
+ * bit 29-31	: set to zero
+ */
+typedef __u32 can_err_mask_t;
+
+/* CAN payload length and DLC definitions according to ISO 11898-1 */
+#define CAN_MAX_DLC 8
+#define CAN_MAX_DLEN 8
+
+/* CAN FD payload length and DLC definitions according to ISO 11898-7 */
+#define CANFD_MAX_DLC 15
+#define CANFD_MAX_DLEN 64
+
+/**
+ * struct can_frame - basic CAN frame structure
+ * @can_id:  CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
+ * @can_dlc: frame payload length in byte (0 .. 8) aka data length code
+ *           N.B. the DLC field from ISO 11898-1 Chapter 8.4.2.3 has a 1:1
+ *           mapping of the 'data length code' to the real payload length
+ * @data:    CAN frame payload (up to 8 byte)
+ */
+struct can_frame {
+	canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
+	__u8    can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */
+	__u8    data[CAN_MAX_DLEN] __attribute__((aligned(8)));
+};
+
+/*
+ * defined bits for canfd_frame.flags
+ *
+ * The use of struct canfd_frame implies the Extended Data Length (EDL) bit to
+ * be set in the CAN frame bitstream on the wire. The EDL bit switch turns
+ * the CAN controllers bitstream processor into the CAN FD mode which creates
+ * two new options within the CAN FD frame specification:
+ *
+ * Bit Rate Switch - to indicate a second bitrate is/was used for the payload
+ * Error State Indicator - represents the error state of the transmitting node
+ *
+ * As the CANFD_ESI bit is internally generated by the transmitting CAN
+ * controller only the CANFD_BRS bit is relevant for real CAN controllers when
+ * building a CAN FD frame for transmission. Setting the CANFD_ESI bit can make
+ * sense for virtual CAN interfaces to test applications with echoed frames.
+ */
+#define CANFD_BRS 0x01 /* bit rate switch (second bitrate for payload data) */
+#define CANFD_ESI 0x02 /* error state indicator of the transmitting node */
+
+/**
+ * struct canfd_frame - CAN flexible data rate frame structure
+ * @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
+ * @len:    frame payload length in byte (0 .. CANFD_MAX_DLEN)
+ * @flags:  additional flags for CAN FD
+ * @__res0: reserved / padding
+ * @__res1: reserved / padding
+ * @data:   CAN FD frame payload (up to CANFD_MAX_DLEN byte)
+ */
+struct canfd_frame {
+	canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
+	__u8    len;     /* frame payload length in byte */
+	__u8    flags;   /* additional flags for CAN FD */
+	__u8    __res0;  /* reserved / padding */
+	__u8    __res1;  /* reserved / padding */
+	__u8    data[CANFD_MAX_DLEN] __attribute__((aligned(8)));
+};
+
+#define CAN_MTU		(sizeof(struct can_frame))
+#define CANFD_MTU	(sizeof(struct canfd_frame))
+
+/* particular protocols of the protocol family PF_CAN */
+#define CAN_RAW		1 /* RAW sockets */
+#define CAN_BCM		2 /* Broadcast Manager */
+#define CAN_TP16	3 /* VAG Transport Protocol v1.6 */
+#define CAN_TP20	4 /* VAG Transport Protocol v2.0 */
+#define CAN_MCNET	5 /* Bosch MCNet */
+#define CAN_ISOTP	6 /* ISO 15765-2 Transport Protocol */
+#define CAN_NPROTO	7
+
+#define SOL_CAN_BASE 100
+
+/**
+ * struct sockaddr_can - the sockaddr structure for CAN sockets
+ * @can_family:  address family number AF_CAN.
+ * @can_ifindex: CAN network interface index.
+ * @can_addr:    protocol specific address information
+ */
+struct sockaddr_can {
+	__kernel_sa_family_t can_family;
+	int         can_ifindex;
+	union {
+		/* transport protocol class address information (e.g. ISOTP) */
+		struct { canid_t rx_id, tx_id; } tp;
+
+		/* reserved for future CAN protocols address information */
+	} can_addr;
+};
+
+/**
+ * struct can_filter - CAN ID based filter in can_register().
+ * @can_id:   relevant bits of CAN ID which are not masked out.
+ * @can_mask: CAN mask (see description)
+ *
+ * Description:
+ * A filter matches, when
+ *
+ *          <received_can_id> & mask == can_id & mask
+ *
+ * The filter can be inverted (CAN_INV_FILTER bit set in can_id) or it can
+ * filter for error message frames (CAN_ERR_FLAG bit set in mask).
+ */
+struct can_filter {
+	canid_t can_id;
+	canid_t can_mask;
+};
+
+#define CAN_INV_FILTER 0x20000000U /* to be set in can_filter.can_id */
+
+#endif /* !_UAPI_CAN_H */
diff --git a/include/linux/can/netlink.h b/include/linux/can/netlink.h
index 14966dd..6d4ec2a 100644
--- a/include/linux/can/netlink.h
+++ b/include/linux/can/netlink.h
@@ -5,10 +5,18 @@
  *
  * Copyright (c) 2009 Wolfgang Grandegger <wg@grandegger.com>
  *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the version 2 of the GNU General Public License
+ * as published by the Free Software Foundation
+ *
+ * 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.
  */
 
-#ifndef CAN_NETLINK_H
-#define CAN_NETLINK_H
+#ifndef _CAN_NETLINK_H
+#define _CAN_NETLINK_H
 
 #include <linux/types.h>
 
@@ -84,10 +92,13 @@
 };
 
 #define CAN_CTRLMODE_LOOPBACK		0x01	/* Loopback mode */
-#define CAN_CTRLMODE_LISTENONLY		0x02 	/* Listen-only 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 */
+#define CAN_CTRLMODE_FD			0x20	/* CAN FD mode */
+#define CAN_CTRLMODE_PRESUME_ACK	0x40	/* Ignore missing CAN ACKs */
+#define CAN_CTRLMODE_FD_NON_ISO		0x80	/* CAN FD in non-ISO mode */
 
 /*
  * CAN device statistics
@@ -114,9 +125,11 @@
 	IFLA_CAN_RESTART_MS,
 	IFLA_CAN_RESTART,
 	IFLA_CAN_BERR_COUNTER,
+	IFLA_CAN_DATA_BITTIMING,
+	IFLA_CAN_DATA_BITTIMING_CONST,
 	__IFLA_CAN_MAX
 };
 
 #define IFLA_CAN_MAX	(__IFLA_CAN_MAX - 1)
 
-#endif /* CAN_NETLINK_H */
+#endif /* !_UAPI_CAN_NETLINK_H */
diff --git a/include/linux/fib_rules.h b/include/linux/fib_rules.h
index 106d35f..e60291e 100644
--- a/include/linux/fib_rules.h
+++ b/include/linux/fib_rules.h
@@ -44,8 +44,8 @@
 	FRA_FWMARK,	/* mark */
 	FRA_FLOW,	/* flow/class id */
 	FRA_UNUSED6,
-	FRA_UNUSED7,
-	FRA_UNUSED8,
+	FRA_SUPPRESS_IFGROUP,
+	FRA_SUPPRESS_PREFIXLEN,
 	FRA_TABLE,	/* Extended table id */
 	FRA_FWMASK,	/* mask for netfilter mark */
 	FRA_OIFNAME,
diff --git a/include/linux/filter.h b/include/linux/filter.h
new file mode 100644
index 0000000..8688a98
--- /dev/null
+++ b/include/linux/filter.h
@@ -0,0 +1,85 @@
+/*
+ * Linux Socket Filter Data Structures
+ */
+
+#ifndef __LINUX_FILTER_H__
+#define __LINUX_FILTER_H__
+
+
+#include <linux/types.h>
+#include <linux/bpf_common.h>
+
+/*
+ * Current version of the filter code architecture.
+ */
+#define BPF_MAJOR_VERSION 1
+#define BPF_MINOR_VERSION 1
+
+/*
+ *	Try and keep these values and structures similar to BSD, especially
+ *	the BPF code definitions which need to match so you can share filters
+ */
+ 
+struct sock_filter {	/* Filter block */
+	__u16	code;   /* Actual filter code */
+	__u8	jt;	/* Jump true */
+	__u8	jf;	/* Jump false */
+	__u32	k;      /* Generic multiuse field */
+};
+
+struct sock_fprog {	/* Required for SO_ATTACH_FILTER. */
+	unsigned short		len;	/* Number of filter blocks */
+	struct sock_filter *filter;
+};
+
+/* ret - BPF_K and BPF_X also apply */
+#define BPF_RVAL(code)  ((code) & 0x18)
+#define         BPF_A           0x10
+
+/* misc */
+#define BPF_MISCOP(code) ((code) & 0xf8)
+#define         BPF_TAX         0x00
+#define         BPF_TXA         0x80
+
+/*
+ * Macros for filter block array initializers.
+ */
+#ifndef BPF_STMT
+#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k }
+#endif
+#ifndef BPF_JUMP
+#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k }
+#endif
+
+/*
+ * Number of scratch memory words for: BPF_ST and BPF_STX
+ */
+#define BPF_MEMWORDS 16
+
+/* RATIONALE. Negative offsets are invalid in BPF.
+   We use them to reference ancillary data.
+   Unlike introduction new instructions, it does not break
+   existing compilers/optimizers.
+ */
+#define SKF_AD_OFF    (-0x1000)
+#define SKF_AD_PROTOCOL 0
+#define SKF_AD_PKTTYPE 	4
+#define SKF_AD_IFINDEX 	8
+#define SKF_AD_NLATTR	12
+#define SKF_AD_NLATTR_NEST	16
+#define SKF_AD_MARK 	20
+#define SKF_AD_QUEUE	24
+#define SKF_AD_HATYPE	28
+#define SKF_AD_RXHASH	32
+#define SKF_AD_CPU	36
+#define SKF_AD_ALU_XOR_X	40
+#define SKF_AD_VLAN_TAG	44
+#define SKF_AD_VLAN_TAG_PRESENT 48
+#define SKF_AD_PAY_OFFSET	52
+#define SKF_AD_RANDOM	56
+#define SKF_AD_MAX	60
+#define SKF_NET_OFF   (-0x100000)
+#define SKF_LL_OFF    (-0x200000)
+
+
+#endif /* __LINUX_FILTER_H__ */
diff --git a/include/linux/fou.h b/include/linux/fou.h
new file mode 100644
index 0000000..13a78e4
--- /dev/null
+++ b/include/linux/fou.h
@@ -0,0 +1,40 @@
+/* fou.h - FOU Interface */
+
+#ifndef _LINUX_FOU_H
+#define _LINUX_FOU_H
+
+/* NETLINK_GENERIC related info
+ */
+#define FOU_GENL_NAME		"fou"
+#define FOU_GENL_VERSION	0x1
+
+enum {
+	FOU_ATTR_UNSPEC,
+	FOU_ATTR_PORT,				/* u16 */
+	FOU_ATTR_AF,				/* u8 */
+	FOU_ATTR_IPPROTO,			/* u8 */
+	FOU_ATTR_TYPE,				/* u8 */
+	FOU_ATTR_REMCSUM_NOPARTIAL,		/* flag */
+
+	__FOU_ATTR_MAX,
+};
+
+#define FOU_ATTR_MAX		(__FOU_ATTR_MAX - 1)
+
+enum {
+	FOU_CMD_UNSPEC,
+	FOU_CMD_ADD,
+	FOU_CMD_DEL,
+
+	__FOU_CMD_MAX,
+};
+
+enum {
+	FOU_ENCAP_UNSPEC,
+	FOU_ENCAP_DIRECT,
+	FOU_ENCAP_GUE,
+};
+
+#define FOU_CMD_MAX	(__FOU_CMD_MAX - 1)
+
+#endif /* _LINUX_FOU_H */
diff --git a/include/linux/gen_stats.h b/include/linux/gen_stats.h
index 552c8a0..6487317 100644
--- a/include/linux/gen_stats.h
+++ b/include/linux/gen_stats.h
@@ -9,6 +9,7 @@
 	TCA_STATS_RATE_EST,
 	TCA_STATS_QUEUE,
 	TCA_STATS_APP,
+	TCA_STATS_RATE_EST64,
 	__TCA_STATS_MAX,
 };
 #define TCA_STATS_MAX (__TCA_STATS_MAX - 1)
@@ -38,6 +39,16 @@
 };
 
 /**
+ * struct gnet_stats_rate_est64 - rate estimator
+ * @bps: current byte rate
+ * @pps: current packet rate
+ */
+struct gnet_stats_rate_est64 {
+	__u64	bps;
+	__u64	pps;
+};
+
+/**
  * struct gnet_stats_queue - queuing statistics
  * @qlen: queue length
  * @backlog: backlog size of queue
diff --git a/include/linux/genetlink.h b/include/linux/genetlink.h
index 55ce7bb..8a1d500 100644
--- a/include/linux/genetlink.h
+++ b/include/linux/genetlink.h
@@ -27,6 +27,8 @@
  */
 #define GENL_ID_GENERATE	0
 #define GENL_ID_CTRL		NLMSG_MIN_TYPE
+#define GENL_ID_VFS_DQUOT	(NLMSG_MIN_TYPE + 1)
+#define GENL_ID_PMCRAID		(NLMSG_MIN_TYPE + 2)
 
 /**************************************************************************
  * Controller
@@ -81,4 +83,4 @@
 #define CTRL_ATTR_MCAST_GRP_MAX (__CTRL_ATTR_MCAST_GRP_MAX - 1)
 
 
-#endif	/* __LINUX_GENERIC_NETLINK_H */
+#endif /* __LINUX_GENERIC_NETLINK_H */
diff --git a/include/linux/hdlc/ioctl.h b/include/linux/hdlc/ioctl.h
index 5839723..04bc027 100644
--- a/include/linux/hdlc/ioctl.h
+++ b/include/linux/hdlc/ioctl.h
@@ -34,13 +34,15 @@
 #define LMI_CCITT		3 /* ITU-T Annex A */
 #define LMI_CISCO		4 /* The "original" LMI, aka Gang of Four */
 
-typedef struct { 
+#ifndef __ASSEMBLY__
+
+typedef struct {
 	unsigned int clock_rate; /* bits per second */
 	unsigned int clock_type; /* internal, external, TX-internal etc. */
 	unsigned short loopback;
 } sync_serial_settings;          /* V.35, V.24, X.21 */
 
-typedef struct { 
+typedef struct {
 	unsigned int clock_rate; /* bits per second */
 	unsigned int clock_type; /* internal, external, TX-internal etc. */
 	unsigned short loopback;
@@ -78,4 +80,5 @@
 
 /* PPP doesn't need any info now - supply length = 0 to ioctl */
 
+#endif /* __ASSEMBLY__ */
 #endif /* __HDLC_IOCTL_H__ */
diff --git a/include/linux/if.h b/include/linux/if.h
index b3383a9..a55a9e0 100644
--- a/include/linux/if.h
+++ b/include/linux/if.h
@@ -27,62 +27,91 @@
 #define	IFALIASZ	256
 #include <linux/hdlc/ioctl.h>
 
-/* Standard interface flags (netdevice->flags). */
-#define	IFF_UP		0x1		/* interface is up		*/
-#define	IFF_BROADCAST	0x2		/* broadcast address valid	*/
-#define	IFF_DEBUG	0x4		/* turn on debugging		*/
-#define	IFF_LOOPBACK	0x8		/* is a loopback net		*/
-#define	IFF_POINTOPOINT	0x10		/* interface is has p-p link	*/
-#define	IFF_NOTRAILERS	0x20		/* avoid use of trailers	*/
-#define	IFF_RUNNING	0x40		/* interface RFC2863 OPER_UP	*/
-#define	IFF_NOARP	0x80		/* no ARP protocol		*/
-#define	IFF_PROMISC	0x100		/* receive all packets		*/
-#define	IFF_ALLMULTI	0x200		/* receive all multicast packets*/
+/**
+ * enum net_device_flags - &struct net_device flags
+ *
+ * These are the &struct net_device flags, they can be set by drivers, the
+ * kernel and some can be triggered by userspace. Userspace can query and
+ * set these flags using userspace utilities but there is also a sysfs
+ * entry available for all dev flags which can be queried and set. These flags
+ * are shared for all types of net_devices. The sysfs entries are available
+ * via /sys/class/net/<dev>/flags. Flags which can be toggled through sysfs
+ * are annotated below, note that only a few flags can be toggled and some
+ * other flags are always always preserved from the original net_device flags
+ * even if you try to set them via sysfs. Flags which are always preserved
+ * are kept under the flag grouping @IFF_VOLATILE. Flags which are __volatile__
+ * are annotated below as such.
+ *
+ * You should have a pretty good reason to be extending these flags.
+ *
+ * @IFF_UP: interface is up. Can be toggled through sysfs.
+ * @IFF_BROADCAST: broadcast address valid. Volatile.
+ * @IFF_DEBUG: turn on debugging. Can be toggled through sysfs.
+ * @IFF_LOOPBACK: is a loopback net. Volatile.
+ * @IFF_POINTOPOINT: interface is has p-p link. Volatile.
+ * @IFF_NOTRAILERS: avoid use of trailers. Can be toggled through sysfs.
+ *	Volatile.
+ * @IFF_RUNNING: interface RFC2863 OPER_UP. Volatile.
+ * @IFF_NOARP: no ARP protocol. Can be toggled through sysfs. Volatile.
+ * @IFF_PROMISC: receive all packets. Can be toggled through sysfs.
+ * @IFF_ALLMULTI: receive all multicast packets. Can be toggled through
+ *	sysfs.
+ * @IFF_MASTER: master of a load balancer. Volatile.
+ * @IFF_SLAVE: slave of a load balancer. Volatile.
+ * @IFF_MULTICAST: Supports multicast. Can be toggled through sysfs.
+ * @IFF_PORTSEL: can set media type. Can be toggled through sysfs.
+ * @IFF_AUTOMEDIA: auto media select active. Can be toggled through sysfs.
+ * @IFF_DYNAMIC: dialup device with changing addresses. Can be toggled
+ *	through sysfs.
+ * @IFF_LOWER_UP: driver signals L1 up. Volatile.
+ * @IFF_DORMANT: driver signals dormant. Volatile.
+ * @IFF_ECHO: echo sent packets. Volatile.
+ */
+enum net_device_flags {
+	IFF_UP				= 1<<0,  /* sysfs */
+	IFF_BROADCAST			= 1<<1,  /* __volatile__ */
+	IFF_DEBUG			= 1<<2,  /* sysfs */
+	IFF_LOOPBACK			= 1<<3,  /* __volatile__ */
+	IFF_POINTOPOINT			= 1<<4,  /* __volatile__ */
+	IFF_NOTRAILERS			= 1<<5,  /* sysfs */
+	IFF_RUNNING			= 1<<6,  /* __volatile__ */
+	IFF_NOARP			= 1<<7,  /* sysfs */
+	IFF_PROMISC			= 1<<8,  /* sysfs */
+	IFF_ALLMULTI			= 1<<9,  /* sysfs */
+	IFF_MASTER			= 1<<10, /* __volatile__ */
+	IFF_SLAVE			= 1<<11, /* __volatile__ */
+	IFF_MULTICAST			= 1<<12, /* sysfs */
+	IFF_PORTSEL			= 1<<13, /* sysfs */
+	IFF_AUTOMEDIA			= 1<<14, /* sysfs */
+	IFF_DYNAMIC			= 1<<15, /* sysfs */
+	IFF_LOWER_UP			= 1<<16, /* __volatile__ */
+	IFF_DORMANT			= 1<<17, /* __volatile__ */
+	IFF_ECHO			= 1<<18, /* __volatile__ */
+};
 
-#define IFF_MASTER	0x400		/* master of a load balancer 	*/
-#define IFF_SLAVE	0x800		/* slave of a load balancer	*/
-
-#define IFF_MULTICAST	0x1000		/* Supports multicast		*/
-
-#define IFF_PORTSEL	0x2000          /* can set media type		*/
-#define IFF_AUTOMEDIA	0x4000		/* auto media select active	*/
-#define IFF_DYNAMIC	0x8000		/* dialup device with changing addresses*/
-
-#define IFF_LOWER_UP	0x10000		/* driver signals L1 up		*/
-#define IFF_DORMANT	0x20000		/* driver signals dormant	*/
-
-#define IFF_ECHO	0x40000		/* echo sent packets		*/
+#define IFF_UP				IFF_UP
+#define IFF_BROADCAST			IFF_BROADCAST
+#define IFF_DEBUG			IFF_DEBUG
+#define IFF_LOOPBACK			IFF_LOOPBACK
+#define IFF_POINTOPOINT			IFF_POINTOPOINT
+#define IFF_NOTRAILERS			IFF_NOTRAILERS
+#define IFF_RUNNING			IFF_RUNNING
+#define IFF_NOARP			IFF_NOARP
+#define IFF_PROMISC			IFF_PROMISC
+#define IFF_ALLMULTI			IFF_ALLMULTI
+#define IFF_MASTER			IFF_MASTER
+#define IFF_SLAVE			IFF_SLAVE
+#define IFF_MULTICAST			IFF_MULTICAST
+#define IFF_PORTSEL			IFF_PORTSEL
+#define IFF_AUTOMEDIA			IFF_AUTOMEDIA
+#define IFF_DYNAMIC			IFF_DYNAMIC
+#define IFF_LOWER_UP			IFF_LOWER_UP
+#define IFF_DORMANT			IFF_DORMANT
+#define IFF_ECHO			IFF_ECHO
 
 #define IFF_VOLATILE	(IFF_LOOPBACK|IFF_POINTOPOINT|IFF_BROADCAST|IFF_ECHO|\
 		IFF_MASTER|IFF_SLAVE|IFF_RUNNING|IFF_LOWER_UP|IFF_DORMANT)
 
-/* Private (from user) interface flags (netdevice->priv_flags). */
-#define IFF_802_1Q_VLAN 0x1             /* 802.1Q VLAN device.          */
-#define IFF_EBRIDGE	0x2		/* Ethernet bridging device.	*/
-#define IFF_SLAVE_INACTIVE	0x4	/* bonding slave not the curr. active */
-#define IFF_MASTER_8023AD	0x8	/* bonding master, 802.3ad. 	*/
-#define IFF_MASTER_ALB	0x10		/* bonding master, balance-alb.	*/
-#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_DISABLE_NETPOLL	0x1000	/* disable netpoll at run-time */
-#define IFF_MACVLAN_PORT	0x2000	/* device used as macvlan port */
-#define IFF_BRIDGE_PORT	0x4000		/* device used as bridge port */
-#define IFF_OVS_DATAPATH	0x8000	/* device used as Open vSwitch
-					 * datapath port */
-#define IFF_TX_SKB_SHARING	0x10000	/* The interface supports sharing
-					 * skbs on transmit */
-#define IFF_UNICAST_FLT	0x20000		/* Supports unicast filtering	*/
-#define IFF_TEAM_PORT	0x40000		/* device used as team port */
-#define IFF_SUPP_NOFCS	0x80000		/* device supports sending custom FCS */
-
-
 #define IF_GET_IFACE	0x0001		/* for querying only */
 #define IF_GET_PROTO	0x0002
 
diff --git a/include/linux/if_addr.h b/include/linux/if_addr.h
index 58b39f4..cc375e4 100644
--- a/include/linux/if_addr.h
+++ b/include/linux/if_addr.h
@@ -18,6 +18,9 @@
  * 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 {
 	IFA_UNSPEC,
@@ -28,6 +31,7 @@
 	IFA_ANYCAST,
 	IFA_CACHEINFO,
 	IFA_MULTICAST,
+	IFA_FLAGS,
 	__IFA_MAX,
 };
 
@@ -44,6 +48,8 @@
 #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 {
 	__u32	ifa_prefered;
diff --git a/include/linux/if_arp.h b/include/linux/if_arp.h
index 1f9fc5d..d001bdb 100644
--- a/include/linux/if_arp.h
+++ b/include/linux/if_arp.h
@@ -87,10 +87,14 @@
 #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_IEEE802154_MONITOR 805	/* IEEE 802.15.4 network monitor */
 
 #define ARPHRD_PHONET	820		/* PhoNet media type		*/
 #define ARPHRD_PHONET_PIPE 821		/* PhoNet pipe header		*/
 #define ARPHRD_CAIF	822		/* CAIF media type		*/
+#define ARPHRD_IP6GRE	823		/* GRE over IPv6		*/
+#define ARPHRD_NETLINK	824		/* Netlink header		*/
+#define ARPHRD_6LOWPAN	825		/* IPv6 over LoWPAN             */
 
 #define ARPHRD_VOID	  0xFFFF	/* Void type, nothing is known */
 #define ARPHRD_NONE	  0xFFFE	/* zero header length */
@@ -154,4 +158,4 @@
 };
 
 
-#endif	/* _LINUX_IF_ARP_H */
+#endif /* _LINUX_IF_ARP_H */
diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
new file mode 100644
index 0000000..913bd8e
--- /dev/null
+++ b/include/linux/if_bridge.h
@@ -0,0 +1,201 @@
+/*
+ *	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 _LINUX_IF_BRIDGE_H
+#define _LINUX_IF_BRIDGE_H
+
+#include <linux/types.h>
+#include <linux/if_ether.h>
+#include <linux/in6.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[ETH_ALEN];
+	__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 */
+#define BRIDGE_MODE_UNDEF	0xFFFF  /* mode undefined */
+
+/* Bridge management nested attributes
+ * [IFLA_AF_SPEC] = {
+ *     [IFLA_BRIDGE_FLAGS]
+ *     [IFLA_BRIDGE_MODE]
+ *     [IFLA_BRIDGE_VLAN_INFO]
+ * }
+ */
+enum {
+	IFLA_BRIDGE_FLAGS,
+	IFLA_BRIDGE_MODE,
+	IFLA_BRIDGE_VLAN_INFO,
+	__IFLA_BRIDGE_MAX,
+};
+#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1)
+
+#define BRIDGE_VLAN_INFO_MASTER	(1<<0)	/* Operate on Bridge device as well */
+#define BRIDGE_VLAN_INFO_PVID	(1<<1)	/* VLAN is PVID, ingress untagged */
+#define BRIDGE_VLAN_INFO_UNTAGGED	(1<<2)	/* VLAN egresses untagged */
+#define BRIDGE_VLAN_INFO_RANGE_BEGIN	(1<<3) /* VLAN is start of vlan range */
+#define BRIDGE_VLAN_INFO_RANGE_END	(1<<4) /* VLAN is end of vlan range */
+
+struct bridge_vlan_info {
+	__u16 flags;
+	__u16 vid;
+};
+
+/* 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 /* _LINUX_IF_BRIDGE_H */
diff --git a/include/linux/if_ether.h b/include/linux/if_ether.h
index 8b35afe..4678e49 100644
--- a/include/linux/if_ether.h
+++ b/include/linux/if_ether.h
@@ -48,6 +48,7 @@
 #define	ETH_P_BPQ	0x08FF		/* G8BPQ AX.25 Ethernet Packet	[ NOT AN OFFICIALLY REGISTERED ID ] */
 #define ETH_P_IEEEPUP	0x0a00		/* Xerox IEEE802.3 PUP packet */
 #define ETH_P_IEEEPUPAT	0x0a01		/* Xerox IEEE802.3 PUP Addr Trans packet */
+#define ETH_P_BATMAN	0x4305		/* B.A.T.M.A.N.-Advanced packet [ NOT AN OFFICIALLY REGISTERED ID ] */
 #define ETH_P_DEC       0x6000          /* DEC Assigned proto           */
 #define ETH_P_DNA_DL    0x6001          /* DEC DNA Dump/Load            */
 #define ETH_P_DNA_RC    0x6002          /* DEC DNA Remote Console       */
@@ -67,11 +68,11 @@
 #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     */
-#define ETH_P_PPP_SES	0x8864		/* PPPoE session messages	*/
 #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_PPP_DISC	0x8863		/* PPPoE discovery messages     */
+#define ETH_P_PPP_SES	0x8864		/* PPPoE session messages	*/
 #define ETH_P_LINK_CTL	0x886c		/* HPNA, wlan link local tunnel */
 #define ETH_P_ATMFATE	0x8884		/* Frame-based ATM Transport
 					 * over Ethernet
@@ -82,16 +83,23 @@
 #define ETH_P_802_EX1	0x88B5		/* 802.1 Local Experimental 1.  */
 #define ETH_P_TIPC	0x88CA		/* TIPC 			*/
 #define ETH_P_8021AH	0x88E7          /* 802.1ah Backbone Service Tag */
+#define ETH_P_MVRP	0x88F5          /* 802.1Q MVRP                  */
 #define ETH_P_1588	0x88F7		/* IEEE 1588 Timesync */
+#define ETH_P_PRP	0x88FB		/* IEC 62439-3 PRP/HSRv0	*/
 #define ETH_P_FCOE	0x8906		/* Fibre Channel over Ethernet  */
 #define ETH_P_TDLS	0x890D          /* TDLS */
 #define ETH_P_FIP	0x8914		/* FCoE Initialization Protocol */
+#define ETH_P_80221	0x8917		/* IEEE 802.21 Media Independent Handover Protocol */
+#define ETH_P_LOOPBACK	0x9000		/* Ethernet loopback packet, per IEEE 802.3 */
 #define ETH_P_QINQ1	0x9100		/* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
 #define ETH_P_QINQ2	0x9200		/* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
 #define ETH_P_QINQ3	0x9300		/* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
 #define ETH_P_EDSA	0xDADA		/* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
 #define ETH_P_AF_IUCV   0xFBFB		/* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */
 
+#define ETH_P_802_3_MIN	0x0600		/* If the value in the ethernet type is less than this value
+					 * then the frame is Ethernet II. Else it is 802.3 */
+
 /*
  *	Non DIX types. Won't clash for 1500 types.
  */
@@ -105,7 +113,8 @@
 #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_CAN	0x000C		/* CAN: Controller Area Network */
+#define ETH_P_CANFD	0x000D		/* CANFD: CAN flexible data rate*/
 #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)	*/
@@ -119,6 +128,7 @@
 #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	*/
+#define ETH_P_XDSA	0x00F8		/* Multiplexed DSA protocol	*/
 
 /*
  *	This is an Ethernet frame header.
@@ -131,4 +141,4 @@
 } __attribute__((packed));
 
 
-#endif	/* _LINUX_IF_ETHER_H */
+#endif /* _LINUX_IF_ETHER_H */
diff --git a/include/linux/if_link.h b/include/linux/if_link.h
index 06a3a47..3450c3f 100644
--- a/include/linux/if_link.h
+++ b/include/linux/if_link.h
@@ -138,6 +138,15 @@
 	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_CARRIER_CHANGES,
+	IFLA_PHYS_SWITCH_ID,
+	IFLA_LINK_NETNSID,
 	__IFLA_MAX
 };
 
@@ -194,11 +203,52 @@
 	IFLA_INET6_MCAST,	/* MC things. What of them?	*/
 	IFLA_INET6_CACHEINFO,	/* time values and max reasm size */
 	IFLA_INET6_ICMP6STATS,	/* statistics (icmpv6)		*/
+	IFLA_INET6_TOKEN,	/* device token			*/
+	IFLA_INET6_ADDR_GEN_MODE, /* implicit address generator mode */
 	__IFLA_INET6_MAX
 };
 
 #define IFLA_INET6_MAX	(__IFLA_INET6_MAX - 1)
 
+enum in6_addr_gen_mode {
+	IN6_ADDR_GEN_MODE_EUI64,
+	IN6_ADDR_GEN_MODE_NONE,
+};
+
+/* Bridge section */
+
+enum {
+	IFLA_BR_UNSPEC,
+	IFLA_BR_FORWARD_DELAY,
+	IFLA_BR_HELLO_TIME,
+	IFLA_BR_MAX_AGE,
+	__IFLA_BR_MAX,
+};
+
+#define IFLA_BR_MAX	(__IFLA_BR_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_LEARNING,	/* mac learning */
+	IFLA_BRPORT_UNICAST_FLOOD, /* flood unicast traffic */
+	IFLA_BRPORT_PROXYARP,	/* proxy ARP */
+	IFLA_BRPORT_LEARNING_SYNC, /* mac learning sync from device */
+	__IFLA_BRPORT_MAX
+};
+#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
+
 struct ifla_cacheinfo {
 	__u32	max_reasm_len;
 	__u32	tstamp;		/* ipv6InterfaceTable updated timestamp */
@@ -211,6 +261,8 @@
 	IFLA_INFO_KIND,
 	IFLA_INFO_DATA,
 	IFLA_INFO_XSTATS,
+	IFLA_INFO_SLAVE_KIND,
+	IFLA_INFO_SLAVE_DATA,
 	__IFLA_INFO_MAX,
 };
 
@@ -224,6 +276,7 @@
 	IFLA_VLAN_FLAGS,
 	IFLA_VLAN_EGRESS_QOS,
 	IFLA_VLAN_INGRESS_QOS,
+	IFLA_VLAN_PROTOCOL,
 	__IFLA_VLAN_MAX,
 };
 
@@ -251,6 +304,11 @@
 enum {
 	IFLA_MACVLAN_UNSPEC,
 	IFLA_MACVLAN_MODE,
+	IFLA_MACVLAN_FLAGS,
+	IFLA_MACVLAN_MACADDR_MODE,
+	IFLA_MACVLAN_MACADDR,
+	IFLA_MACVLAN_MACADDR_DATA,
+	IFLA_MACVLAN_MACADDR_COUNT,
 	__IFLA_MACVLAN_MAX,
 };
 
@@ -261,8 +319,126 @@
 	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 */
+	MACVLAN_MODE_SOURCE  = 16,/* use source MAC address list to assign */
 };
 
+enum macvlan_macaddr_mode {
+	MACVLAN_MACADDR_ADD,
+	MACVLAN_MACADDR_DEL,
+	MACVLAN_MACADDR_FLUSH,
+	MACVLAN_MACADDR_SET,
+};
+
+#define MACVLAN_FLAG_NOPROMISC	1
+
+/* IPVLAN section */
+enum {
+	IFLA_IPVLAN_UNSPEC,
+	IFLA_IPVLAN_MODE,
+	__IFLA_IPVLAN_MAX
+};
+
+#define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1)
+
+enum ipvlan_mode {
+	IPVLAN_MODE_L2 = 0,
+	IPVLAN_MODE_L3,
+	IPVLAN_MODE_MAX
+};
+
+/* VXLAN section */
+enum {
+	IFLA_VXLAN_UNSPEC,
+	IFLA_VXLAN_ID,
+	IFLA_VXLAN_GROUP,	/* group or remote address */
+	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,	/* source port */
+	IFLA_VXLAN_PROXY,
+	IFLA_VXLAN_RSC,
+	IFLA_VXLAN_L2MISS,
+	IFLA_VXLAN_L3MISS,
+	IFLA_VXLAN_PORT,	/* destination port */
+	IFLA_VXLAN_GROUP6,
+	IFLA_VXLAN_LOCAL6,
+	IFLA_VXLAN_UDP_CSUM,
+	IFLA_VXLAN_UDP_ZERO_CSUM6_TX,
+	IFLA_VXLAN_UDP_ZERO_CSUM6_RX,
+	IFLA_VXLAN_REMCSUM_TX,
+	IFLA_VXLAN_REMCSUM_RX,
+	IFLA_VXLAN_GBP,
+	IFLA_VXLAN_REMCSUM_NOPARTIAL,
+	__IFLA_VXLAN_MAX
+};
+#define IFLA_VXLAN_MAX	(__IFLA_VXLAN_MAX - 1)
+
+struct ifla_vxlan_port_range {
+	__be16	low;
+	__be16	high;
+};
+
+/* Bonding section */
+
+enum {
+	IFLA_BOND_UNSPEC,
+	IFLA_BOND_MODE,
+	IFLA_BOND_ACTIVE_SLAVE,
+	IFLA_BOND_MIIMON,
+	IFLA_BOND_UPDELAY,
+	IFLA_BOND_DOWNDELAY,
+	IFLA_BOND_USE_CARRIER,
+	IFLA_BOND_ARP_INTERVAL,
+	IFLA_BOND_ARP_IP_TARGET,
+	IFLA_BOND_ARP_VALIDATE,
+	IFLA_BOND_ARP_ALL_TARGETS,
+	IFLA_BOND_PRIMARY,
+	IFLA_BOND_PRIMARY_RESELECT,
+	IFLA_BOND_FAIL_OVER_MAC,
+	IFLA_BOND_XMIT_HASH_POLICY,
+	IFLA_BOND_RESEND_IGMP,
+	IFLA_BOND_NUM_PEER_NOTIF,
+	IFLA_BOND_ALL_SLAVES_ACTIVE,
+	IFLA_BOND_MIN_LINKS,
+	IFLA_BOND_LP_INTERVAL,
+	IFLA_BOND_PACKETS_PER_SLAVE,
+	IFLA_BOND_AD_LACP_RATE,
+	IFLA_BOND_AD_SELECT,
+	IFLA_BOND_AD_INFO,
+	__IFLA_BOND_MAX,
+};
+
+#define IFLA_BOND_MAX	(__IFLA_BOND_MAX - 1)
+
+enum {
+	IFLA_BOND_AD_INFO_UNSPEC,
+	IFLA_BOND_AD_INFO_AGGREGATOR,
+	IFLA_BOND_AD_INFO_NUM_PORTS,
+	IFLA_BOND_AD_INFO_ACTOR_KEY,
+	IFLA_BOND_AD_INFO_PARTNER_KEY,
+	IFLA_BOND_AD_INFO_PARTNER_MAC,
+	__IFLA_BOND_AD_INFO_MAX,
+};
+
+#define IFLA_BOND_AD_INFO_MAX	(__IFLA_BOND_AD_INFO_MAX - 1)
+
+enum {
+	IFLA_BOND_SLAVE_UNSPEC,
+	IFLA_BOND_SLAVE_STATE,
+	IFLA_BOND_SLAVE_MII_STATUS,
+	IFLA_BOND_SLAVE_LINK_FAILURE_COUNT,
+	IFLA_BOND_SLAVE_PERM_HWADDR,
+	IFLA_BOND_SLAVE_QUEUE_ID,
+	IFLA_BOND_SLAVE_AD_AGGREGATOR_ID,
+	__IFLA_BOND_SLAVE_MAX,
+};
+
+#define IFLA_BOND_SLAVE_MAX	(__IFLA_BOND_SLAVE_MAX - 1)
+
 /* SR-IOV virtual function management section */
 
 enum {
@@ -277,8 +453,10 @@
 	IFLA_VF_UNSPEC,
 	IFLA_VF_MAC,		/* Hardware queue specific attributes */
 	IFLA_VF_VLAN,
-	IFLA_VF_TX_RATE,	/* TX Bandwidth Allocation */
+	IFLA_VF_TX_RATE,	/* Max TX Bandwidth Allocation */
 	IFLA_VF_SPOOFCHK,	/* Spoof Checking on/off switch */
+	IFLA_VF_LINK_STATE,	/* link state enable/disable/auto switch */
+	IFLA_VF_RATE,		/* Min and Max TX Bandwidth Allocation */
 	__IFLA_VF_MAX,
 };
 
@@ -300,11 +478,29 @@
 	__u32 rate; /* Max TX bandwidth in Mbps, 0 disables throttling */
 };
 
+struct ifla_vf_rate {
+	__u32 vf;
+	__u32 min_tx_rate; /* Min Bandwidth in Mbps */
+	__u32 max_tx_rate; /* Max Bandwidth in Mbps */
+};
+
 struct ifla_vf_spoofchk {
 	__u32 vf;
 	__u32 setting;
 };
 
+enum {
+	IFLA_VF_LINK_STATE_AUTO,	/* link state of the uplink */
+	IFLA_VF_LINK_STATE_ENABLE,	/* link always up */
+	IFLA_VF_LINK_STATE_DISABLE,	/* link always down */
+	__IFLA_VF_LINK_STATE_MAX,
+};
+
+struct ifla_vf_link_state {
+	__u32 vf;
+	__u32 link_state;
+};
+
 /* VF ports management section
  *
  *	Nested layout of set/get msg is:
@@ -377,4 +573,37 @@
 	__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)
+
+
+/* HSR section */
+
+enum {
+	IFLA_HSR_UNSPEC,
+	IFLA_HSR_SLAVE1,
+	IFLA_HSR_SLAVE2,
+	IFLA_HSR_MULTICAST_SPEC,	/* Last byte of supervision addr */
+	IFLA_HSR_SUPERVISION_ADDR,	/* Supervision frame multicast addr */
+	IFLA_HSR_SEQ_NR,
+	__IFLA_HSR_MAX,
+};
+
+#define IFLA_HSR_MAX (__IFLA_HSR_MAX - 1)
+
 #endif /* _LINUX_IF_LINK_H */
diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h
index 4054c1a..ffee583 100644
--- a/include/linux/if_tun.h
+++ b/include/linux/if_tun.h
@@ -22,19 +22,11 @@
 
 /* Read queue size */
 #define TUN_READQ_SIZE	500
-
-/* TUN device flags */
-#define TUN_TUN_DEV 	0x0001	
-#define TUN_TAP_DEV	0x0002
+/* TUN device type flags: deprecated. Use IFF_TUN/IFF_TAP instead. */
+#define TUN_TUN_DEV 	IFF_TUN
+#define TUN_TAP_DEV	IFF_TAP
 #define TUN_TYPE_MASK   0x000f
 
-#define TUN_FASYNC	0x0010
-#define TUN_NOCHECKSUM	0x0020
-#define TUN_NO_PI	0x0040
-#define TUN_ONE_QUEUE	0x0080
-#define TUN_PERSIST 	0x0100	
-#define TUN_VNET_HDR 	0x0200
-
 /* Ioctl defines */
 #define TUNSETNOCSUM  _IOW('T', 200, int) 
 #define TUNSETDEBUG   _IOW('T', 201, int) 
@@ -53,14 +45,29 @@
 #define TUNDETACHFILTER _IOW('T', 214, struct sock_fprog)
 #define TUNGETVNETHDRSZ _IOR('T', 215, int)
 #define TUNSETVNETHDRSZ _IOW('T', 216, int)
+#define TUNSETQUEUE  _IOW('T', 217, int)
+#define TUNSETIFINDEX	_IOW('T', 218, unsigned int)
+#define TUNGETFILTER _IOR('T', 219, struct sock_fprog)
+#define TUNSETVNETLE _IOW('T', 220, int)
+#define TUNGETVNETLE _IOR('T', 221, int)
 
 /* TUNSETIFF ifr flags */
 #define IFF_TUN		0x0001
 #define IFF_TAP		0x0002
 #define IFF_NO_PI	0x1000
+/* This flag has no real effect */
 #define IFF_ONE_QUEUE	0x2000
 #define IFF_VNET_HDR	0x4000
 #define IFF_TUN_EXCL	0x8000
+#define IFF_MULTI_QUEUE 0x0100
+#define IFF_ATTACH_QUEUE 0x0200
+#define IFF_DETACH_QUEUE 0x0400
+/* read-only flag */
+#define IFF_PERSIST	0x0800
+#define IFF_NOFILTER	0x1000
+
+/* Socket options */
+#define TUN_TX_TIMESTAMP 1
 
 /* Features for GSO (TUNSETOFFLOAD). */
 #define TUN_F_CSUM	0x01	/* You can hand me unchecksummed packets. */
diff --git a/include/linux/if_tunnel.h b/include/linux/if_tunnel.h
index 8c819b8..102ce7a 100644
--- a/include/linux/if_tunnel.h
+++ b/include/linux/if_tunnel.h
@@ -37,6 +37,40 @@
 	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_ENCAP_TYPE,
+	IFLA_IPTUN_ENCAP_FLAGS,
+	IFLA_IPTUN_ENCAP_SPORT,
+	IFLA_IPTUN_ENCAP_DPORT,
+	__IFLA_IPTUN_MAX,
+};
+#define IFLA_IPTUN_MAX	(__IFLA_IPTUN_MAX - 1)
+
+enum tunnel_encap_types {
+	TUNNEL_ENCAP_NONE,
+	TUNNEL_ENCAP_FOU,
+	TUNNEL_ENCAP_GUE,
+};
+
+#define TUNNEL_ENCAP_FLAG_CSUM		(1<<0)
+#define TUNNEL_ENCAP_FLAG_CSUM6		(1<<1)
+#define TUNNEL_ENCAP_FLAG_REMCSUM	(1<<2)
+
 /* SIT-mode i_flags */
 #define	SIT_ISATAP	0x0001
 
@@ -71,9 +105,30 @@
 	IFLA_GRE_TTL,
 	IFLA_GRE_TOS,
 	IFLA_GRE_PMTUDISC,
+	IFLA_GRE_ENCAP_LIMIT,
+	IFLA_GRE_FLOWINFO,
+	IFLA_GRE_FLAGS,
+	IFLA_GRE_ENCAP_TYPE,
+	IFLA_GRE_ENCAP_FLAGS,
+	IFLA_GRE_ENCAP_SPORT,
+	IFLA_GRE_ENCAP_DPORT,
 	__IFLA_GRE_MAX,
 };
 
 #define IFLA_GRE_MAX	(__IFLA_GRE_MAX - 1)
 
+/* VTI-mode i_flags */
+#define VTI_ISVTI ((__be16)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 /* _IF_TUNNEL_H_ */
diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h
index 5b808eb..24ae007 100644
--- a/include/linux/if_vlan.h
+++ b/include/linux/if_vlan.h
@@ -34,6 +34,7 @@
 	VLAN_FLAG_REORDER_HDR	= 0x1,
 	VLAN_FLAG_GVRP		= 0x2,
 	VLAN_FLAG_LOOSE_BINDING	= 0x4,
+	VLAN_FLAG_MVRP		= 0x8,
 };
 
 enum vlan_name_types {
@@ -60,4 +61,4 @@
 	short vlan_qos;   
 };
 
-#endif /* !(_LINUX_IF_VLAN_H_) */
+#endif /* _LINUX_IF_VLAN_H_ */
diff --git a/include/linux/in6.h b/include/linux/in6.h
new file mode 100644
index 0000000..994f4c2
--- /dev/null
+++ b/include/linux/in6.h
@@ -0,0 +1,293 @@
+/*
+ *	Types and definitions for AF_INET6 
+ *	Linux INET6 implementation 
+ *
+ *	Authors:
+ *	Pedro Roque		<roque@di.fc.ul.pt>	
+ *
+ *	Sources:
+ *	IPv6 Program Interfaces for BSD Systems
+ *      <draft-ietf-ipngwg-bsd-api-05.txt>
+ *
+ *	Advanced Sockets API for IPv6
+ *	<draft-stevens-advanced-api-00.txt>
+ *
+ *	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 _LINUX_IN6_H
+#define _LINUX_IN6_H
+
+#include <linux/types.h>
+#include <linux/libc-compat.h>
+
+/*
+ *	IPv6 address structure
+ */
+
+#if __UAPI_DEF_IN6_ADDR
+struct in6_addr {
+	union {
+		__u8		u6_addr8[16];
+#if __UAPI_DEF_IN6_ADDR_ALT
+		__be16		u6_addr16[8];
+		__be32		u6_addr32[4];
+#endif
+	} in6_u;
+#define s6_addr			in6_u.u6_addr8
+#if __UAPI_DEF_IN6_ADDR_ALT
+#define s6_addr16		in6_u.u6_addr16
+#define s6_addr32		in6_u.u6_addr32
+#endif
+};
+#endif /* __UAPI_DEF_IN6_ADDR */
+
+#if __UAPI_DEF_SOCKADDR_IN6
+struct sockaddr_in6 {
+	unsigned short int	sin6_family;    /* AF_INET6 */
+	__be16			sin6_port;      /* Transport layer port # */
+	__be32			sin6_flowinfo;  /* IPv6 flow information */
+	struct in6_addr		sin6_addr;      /* IPv6 address */
+	__u32			sin6_scope_id;  /* scope id (new in RFC2553) */
+};
+#endif /* __UAPI_DEF_SOCKADDR_IN6 */
+
+#if __UAPI_DEF_IPV6_MREQ
+struct ipv6_mreq {
+	/* IPv6 multicast address of group */
+	struct in6_addr ipv6mr_multiaddr;
+
+	/* local IPv6 address of interface */
+	int		ipv6mr_ifindex;
+};
+#endif /* __UAPI_DEF_IVP6_MREQ */
+
+#define ipv6mr_acaddr	ipv6mr_multiaddr
+
+struct in6_flowlabel_req {
+	struct in6_addr	flr_dst;
+	__be32	flr_label;
+	__u8	flr_action;
+	__u8	flr_share;
+	__u16	flr_flags;
+	__u16 	flr_expires;
+	__u16	flr_linger;
+	__u32	__flr_pad;
+	/* Options in format of IPV6_PKTOPTIONS */
+};
+
+#define IPV6_FL_A_GET	0
+#define IPV6_FL_A_PUT	1
+#define IPV6_FL_A_RENEW	2
+
+#define IPV6_FL_F_CREATE	1
+#define IPV6_FL_F_EXCL		2
+#define IPV6_FL_F_REFLECT	4
+#define IPV6_FL_F_REMOTE	8
+
+#define IPV6_FL_S_NONE		0
+#define IPV6_FL_S_EXCL		1
+#define IPV6_FL_S_PROCESS	2
+#define IPV6_FL_S_USER		3
+#define IPV6_FL_S_ANY		255
+
+
+/*
+ *	Bitmask constant declarations to help applications select out the 
+ *	flow label and priority fields.
+ *
+ *	Note that this are in host byte order while the flowinfo field of
+ *	sockaddr_in6 is in network byte order.
+ */
+
+#define IPV6_FLOWINFO_FLOWLABEL		0x000fffff
+#define IPV6_FLOWINFO_PRIORITY		0x0ff00000
+
+/* These definitions are obsolete */
+#define IPV6_PRIORITY_UNCHARACTERIZED	0x0000
+#define IPV6_PRIORITY_FILLER		0x0100
+#define IPV6_PRIORITY_UNATTENDED	0x0200
+#define IPV6_PRIORITY_RESERVED1		0x0300
+#define IPV6_PRIORITY_BULK		0x0400
+#define IPV6_PRIORITY_RESERVED2		0x0500
+#define IPV6_PRIORITY_INTERACTIVE	0x0600
+#define IPV6_PRIORITY_CONTROL		0x0700
+#define IPV6_PRIORITY_8			0x0800
+#define IPV6_PRIORITY_9			0x0900
+#define IPV6_PRIORITY_10		0x0a00
+#define IPV6_PRIORITY_11		0x0b00
+#define IPV6_PRIORITY_12		0x0c00
+#define IPV6_PRIORITY_13		0x0d00
+#define IPV6_PRIORITY_14		0x0e00
+#define IPV6_PRIORITY_15		0x0f00
+
+/*
+ *	IPV6 extension headers
+ */
+#if __UAPI_DEF_IPPROTO_V6
+#define IPPROTO_HOPOPTS		0	/* IPv6 hop-by-hop options	*/
+#define IPPROTO_ROUTING		43	/* IPv6 routing header		*/
+#define IPPROTO_FRAGMENT	44	/* IPv6 fragmentation header	*/
+#define IPPROTO_ICMPV6		58	/* ICMPv6			*/
+#define IPPROTO_NONE		59	/* IPv6 no next header		*/
+#define IPPROTO_DSTOPTS		60	/* IPv6 destination options	*/
+#define IPPROTO_MH		135	/* IPv6 mobility header		*/
+#endif /* __UAPI_DEF_IPPROTO_V6 */
+
+/*
+ *	IPv6 TLV options.
+ */
+#define IPV6_TLV_PAD1		0
+#define IPV6_TLV_PADN		1
+#define IPV6_TLV_ROUTERALERT	5
+#define IPV6_TLV_JUMBO		194
+#define IPV6_TLV_HAO		201	/* home address option */
+
+/*
+ *	IPV6 socket options
+ */
+#if __UAPI_DEF_IPV6_OPTIONS
+#define IPV6_ADDRFORM		1
+#define IPV6_2292PKTINFO	2
+#define IPV6_2292HOPOPTS	3
+#define IPV6_2292DSTOPTS	4
+#define IPV6_2292RTHDR		5
+#define IPV6_2292PKTOPTIONS	6
+#define IPV6_CHECKSUM		7
+#define IPV6_2292HOPLIMIT	8
+#define IPV6_NEXTHOP		9
+#define IPV6_AUTHHDR		10	/* obsolete */
+#define IPV6_FLOWINFO		11
+
+#define IPV6_UNICAST_HOPS	16
+#define IPV6_MULTICAST_IF	17
+#define IPV6_MULTICAST_HOPS	18
+#define IPV6_MULTICAST_LOOP	19
+#define IPV6_ADD_MEMBERSHIP	20
+#define IPV6_DROP_MEMBERSHIP	21
+#define IPV6_ROUTER_ALERT	22
+#define IPV6_MTU_DISCOVER	23
+#define IPV6_MTU		24
+#define IPV6_RECVERR		25
+#define IPV6_V6ONLY		26
+#define IPV6_JOIN_ANYCAST	27
+#define IPV6_LEAVE_ANYCAST	28
+
+/* IPV6_MTU_DISCOVER values */
+#define IPV6_PMTUDISC_DONT		0
+#define IPV6_PMTUDISC_WANT		1
+#define IPV6_PMTUDISC_DO		2
+#define IPV6_PMTUDISC_PROBE		3
+/* same as IPV6_PMTUDISC_PROBE, provided for symetry with IPv4
+ * also see comments on IP_PMTUDISC_INTERFACE
+ */
+#define IPV6_PMTUDISC_INTERFACE		4
+/* weaker version of IPV6_PMTUDISC_INTERFACE, which allows packets to
+ * get fragmented if they exceed the interface mtu
+ */
+#define IPV6_PMTUDISC_OMIT		5
+
+/* Flowlabel */
+#define IPV6_FLOWLABEL_MGR	32
+#define IPV6_FLOWINFO_SEND	33
+
+#define IPV6_IPSEC_POLICY	34
+#define IPV6_XFRM_POLICY	35
+#endif
+
+/*
+ * Multicast:
+ * Following socket options are shared between IPv4 and IPv6.
+ *
+ * MCAST_JOIN_GROUP		42
+ * MCAST_BLOCK_SOURCE		43
+ * MCAST_UNBLOCK_SOURCE		44
+ * MCAST_LEAVE_GROUP		45
+ * MCAST_JOIN_SOURCE_GROUP	46
+ * MCAST_LEAVE_SOURCE_GROUP	47
+ * MCAST_MSFILTER		48
+ */
+
+/*
+ * Advanced API (RFC3542) (1)
+ *
+ * Note: IPV6_RECVRTHDRDSTOPTS does not exist. see net/ipv6/datagram.c.
+ */
+
+#define IPV6_RECVPKTINFO	49
+#define IPV6_PKTINFO		50
+#define IPV6_RECVHOPLIMIT	51
+#define IPV6_HOPLIMIT		52
+#define IPV6_RECVHOPOPTS	53
+#define IPV6_HOPOPTS		54
+#define IPV6_RTHDRDSTOPTS	55
+#define IPV6_RECVRTHDR		56
+#define IPV6_RTHDR		57
+#define IPV6_RECVDSTOPTS	58
+#define IPV6_DSTOPTS		59
+#define IPV6_RECVPATHMTU	60
+#define IPV6_PATHMTU		61
+#define IPV6_DONTFRAG		62
+#if 0	/* not yet */
+#define IPV6_USE_MIN_MTU	63
+#endif
+
+/*
+ * Netfilter (1)
+ *
+ * Following socket options are used in ip6_tables;
+ * see include/linux/netfilter_ipv6/ip6_tables.h.
+ *
+ * IP6T_SO_SET_REPLACE / IP6T_SO_GET_INFO		64
+ * IP6T_SO_SET_ADD_COUNTERS / IP6T_SO_GET_ENTRIES	65
+ */
+
+/*
+ * Advanced API (RFC3542) (2)
+ */
+#define IPV6_RECVTCLASS		66
+#define IPV6_TCLASS		67
+
+/*
+ * Netfilter (2)
+ *
+ * Following socket options are used in ip6_tables;
+ * see include/linux/netfilter_ipv6/ip6_tables.h.
+ *
+ * IP6T_SO_GET_REVISION_MATCH	68
+ * IP6T_SO_GET_REVISION_TARGET	69
+ * IP6T_SO_ORIGINAL_DST		80
+ */
+
+#define IPV6_AUTOFLOWLABEL	70
+/* RFC5014: Source address selection */
+#define IPV6_ADDR_PREFERENCES	72
+
+#define IPV6_PREFER_SRC_TMP		0x0001
+#define IPV6_PREFER_SRC_PUBLIC		0x0002
+#define IPV6_PREFER_SRC_PUBTMP_DEFAULT	0x0100
+#define IPV6_PREFER_SRC_COA		0x0004
+#define IPV6_PREFER_SRC_HOME		0x0400
+#define IPV6_PREFER_SRC_CGA		0x0008
+#define IPV6_PREFER_SRC_NONCGA		0x0800
+
+/* RFC5082: Generalized Ttl Security Mechanism */
+#define IPV6_MINHOPCOUNT		73
+
+#define IPV6_ORIGDSTADDR        74
+#define IPV6_RECVORIGDSTADDR    IPV6_ORIGDSTADDR
+#define IPV6_TRANSPARENT        75
+#define IPV6_UNICAST_IF         76
+
+/*
+ * Multicast Routing:
+ * see include/uapi/linux/mroute6.h.
+ *
+ * MRT6_BASE			200
+ * ...
+ * MRT6_MAX
+ */
+#endif /* _LINUX_IN6_H */
diff --git a/include/linux/inet_diag.h b/include/linux/inet_diag.h
index 7e31238..7438dad 100644
--- a/include/linux/inet_diag.h
+++ b/include/linux/inet_diag.h
@@ -1,5 +1,5 @@
 #ifndef _INET_DIAG_H_
-#define _INET_DIAG_H_ 1
+#define _INET_DIAG_H_
 
 #include <linux/types.h>
 
@@ -109,10 +109,11 @@
 	INET_DIAG_TOS,
 	INET_DIAG_TCLASS,
 	INET_DIAG_SKMEMINFO,
+	INET_DIAG_SHUTDOWN,
+	INET_DIAG_DCTCPINFO,
 };
 
-#define INET_DIAG_MAX INET_DIAG_SKMEMINFO
-
+#define INET_DIAG_MAX INET_DIAG_DCTCPINFO
 
 /* INET_DIAG_MEM */
 
@@ -132,5 +133,14 @@
 	__u32	tcpv_minrtt;
 };
 
+/* INET_DIAG_DCTCPINFO */
+
+struct tcp_dctcp_info {
+	__u16	dctcp_enabled;
+	__u16	dctcp_ce_state;
+	__u32	dctcp_alpha;
+	__u32	dctcp_ab_ecn;
+	__u32	dctcp_ab_tot;
+};
 
 #endif /* _INET_DIAG_H_ */
diff --git a/include/linux/ip6_tunnel.h b/include/linux/ip6_tunnel.h
index bf22b03..48af63c 100644
--- a/include/linux/ip6_tunnel.h
+++ b/include/linux/ip6_tunnel.h
@@ -31,4 +31,21 @@
 	struct in6_addr raddr;	/* remote tunnel end-point address */
 };
 
+struct ip6_tnl_parm2 {
+	char name[IFNAMSIZ];	/* name of tunnel device */
+	int link;		/* ifindex of underlying L2 interface */
+	__u8 proto;		/* tunnel protocol */
+	__u8 encap_limit;	/* encapsulation limit for tunnel */
+	__u8 hop_limit;		/* hop limit for tunnel */
+	__be32 flowinfo;	/* traffic class and flowlabel for tunnel */
+	__u32 flags;		/* tunnel flags */
+	struct in6_addr laddr;	/* local tunnel end-point address */
+	struct in6_addr raddr;	/* remote tunnel end-point address */
+
+	__be16			i_flags;
+	__be16			o_flags;
+	__be32			i_key;
+	__be32			o_key;
+};
+
 #endif
diff --git a/include/linux/l2tp.h b/include/linux/l2tp.h
index 5ca74dd..5b0e36d 100644
--- a/include/linux/l2tp.h
+++ b/include/linux/l2tp.h
@@ -35,6 +35,22 @@
 			      sizeof(__u32)];
 };
 
+/**
+ * struct sockaddr_l2tpip6 - the sockaddr structure for L2TP-over-IPv6 sockets
+ * @l2tp_family:  address family number AF_L2TPIP.
+ * @l2tp_addr:    protocol specific address information
+ * @l2tp_conn_id: connection id of tunnel
+ */
+struct sockaddr_l2tpip6 {
+	/* The first fields must match struct sockaddr_in6 */
+	__kernel_sa_family_t l2tp_family; /* AF_INET6 */
+	__be16		l2tp_unused;	/* INET port number (unused) */
+	__be32		l2tp_flowinfo;	/* IPv6 flow information */
+	struct in6_addr	l2tp_addr;	/* IPv6 address */
+	__u32		l2tp_scope_id;	/* scope id (new in RFC2553) */
+	__u32		l2tp_conn_id;	/* Connection ID of tunnel */
+};
+
 /*****************************************************************************
  *  NETLINK_GENERIC netlink family.
  *****************************************************************************/
@@ -104,6 +120,10 @@
 	L2TP_ATTR_MTU,			/* u16 */
 	L2TP_ATTR_MRU,			/* u16 */
 	L2TP_ATTR_STATS,		/* nested */
+	L2TP_ATTR_IP6_SADDR,		/* struct in6_addr */
+	L2TP_ATTR_IP6_DADDR,		/* struct in6_addr */
+	L2TP_ATTR_UDP_ZERO_CSUM6_TX,	/* u8 */
+	L2TP_ATTR_UDP_ZERO_CSUM6_RX,	/* u8 */
 	__L2TP_ATTR_MAX,
 };
 
@@ -156,5 +176,6 @@
  */
 #define L2TP_GENL_NAME		"l2tp"
 #define L2TP_GENL_VERSION	0x1
+#define L2TP_GENL_MCGROUP       "l2tp"
 
-#endif
+#endif /* _LINUX_L2TP_H_ */
diff --git a/include/linux/libc-compat.h b/include/linux/libc-compat.h
new file mode 100644
index 0000000..990332e
--- /dev/null
+++ b/include/linux/libc-compat.h
@@ -0,0 +1,121 @@
+/*
+ * Compatibility interface for userspace libc header coordination:
+ *
+ * Define compatibility macros that are used to control the inclusion or
+ * exclusion of UAPI structures and definitions in coordination with another
+ * userspace C library.
+ *
+ * This header is intended to solve the problem of UAPI definitions that
+ * conflict with userspace definitions. If a UAPI header has such conflicting
+ * definitions then the solution is as follows:
+ *
+ * * Synchronize the UAPI header and the libc headers so either one can be
+ *   used and such that the ABI is preserved. If this is not possible then
+ *   no simple compatibility interface exists (you need to write translating
+ *   wrappers and rename things) and you can't use this interface.
+ *
+ * Then follow this process:
+ *
+ * (a) Include libc-compat.h in the UAPI header.
+ *      e.g. #include <linux/libc-compat.h>
+ *     This include must be as early as possible.
+ *
+ * (b) In libc-compat.h add enough code to detect that the comflicting
+ *     userspace libc header has been included first.
+ *
+ * (c) If the userspace libc header has been included first define a set of
+ *     guard macros of the form __UAPI_DEF_FOO and set their values to 1, else
+ *     set their values to 0.
+ *
+ * (d) Back in the UAPI header with the conflicting definitions, guard the
+ *     definitions with:
+ *     #if __UAPI_DEF_FOO
+ *       ...
+ *     #endif
+ *
+ * This fixes the situation where the linux headers are included *after* the
+ * libc headers. To fix the problem with the inclusion in the other order the
+ * userspace libc headers must be fixed like this:
+ *
+ * * For all definitions that conflict with kernel definitions wrap those
+ *   defines in the following:
+ *   #if !__UAPI_DEF_FOO
+ *     ...
+ *   #endif
+ *
+ * This prevents the redefinition of a construct already defined by the kernel.
+ */
+#ifndef _LIBC_COMPAT_H
+#define _LIBC_COMPAT_H
+
+/* We have included glibc headers... */
+#if defined(__GLIBC__)
+
+/* Coordinate with glibc netinet/in.h header. */
+#if defined(_NETINET_IN_H)
+
+/* GLIBC headers included first so don't define anything
+ * that would already be defined. */
+#define __UAPI_DEF_IN6_ADDR		0
+/* The exception is the in6_addr macros which must be defined
+ * if the glibc code didn't define them. This guard matches
+ * the guard in glibc/inet/netinet/in.h which defines the
+ * additional in6_addr macros e.g. s6_addr16, and s6_addr32. */
+#if defined(__USE_MISC) || defined (__USE_GNU)
+#define __UAPI_DEF_IN6_ADDR_ALT		0
+#else
+#define __UAPI_DEF_IN6_ADDR_ALT		1
+#endif
+#define __UAPI_DEF_SOCKADDR_IN6		0
+#define __UAPI_DEF_IPV6_MREQ		0
+#define __UAPI_DEF_IPPROTO_V6		0
+#define __UAPI_DEF_IPV6_OPTIONS		0
+#define __UAPI_DEF_IN6_PKTINFO		0
+#define __UAPI_DEF_IP6_MTUINFO		0
+
+#else
+
+/* Linux headers included first, and we must define everything
+ * we need. The expectation is that glibc will check the
+ * __UAPI_DEF_* defines and adjust appropriately. */
+#define __UAPI_DEF_IN6_ADDR		1
+/* We unconditionally define the in6_addr macros and glibc must
+ * coordinate. */
+#define __UAPI_DEF_IN6_ADDR_ALT		1
+#define __UAPI_DEF_SOCKADDR_IN6		1
+#define __UAPI_DEF_IPV6_MREQ		1
+#define __UAPI_DEF_IPPROTO_V6		1
+#define __UAPI_DEF_IPV6_OPTIONS		1
+#define __UAPI_DEF_IN6_PKTINFO		1
+#define __UAPI_DEF_IP6_MTUINFO		1
+
+#endif /* _NETINET_IN_H */
+
+/* Definitions for xattr.h */
+#if defined(_SYS_XATTR_H)
+#define __UAPI_DEF_XATTR		0
+#else
+#define __UAPI_DEF_XATTR		1
+#endif
+
+/* If we did not see any headers from any supported C libraries,
+ * or we are being included in the kernel, then define everything
+ * that we need. */
+#else /* !defined(__GLIBC__) */
+
+/* Definitions for in6.h */
+#define __UAPI_DEF_IN6_ADDR		1
+#define __UAPI_DEF_IN6_ADDR_ALT		1
+#define __UAPI_DEF_SOCKADDR_IN6		1
+#define __UAPI_DEF_IPV6_MREQ		1
+#define __UAPI_DEF_IPPROTO_V6		1
+#define __UAPI_DEF_IPV6_OPTIONS		1
+#define __UAPI_DEF_IN6_PKTINFO		1
+#define __UAPI_DEF_IP6_MTUINFO		1
+
+/* Definitions for xattr.h */
+#define __UAPI_DEF_XATTR		1
+
+#endif /* __GLIBC__ */
+
+#endif /* _LIBC_COMPAT_H */
diff --git a/include/linux/neighbour.h b/include/linux/neighbour.h
index b188f68..3873a35 100644
--- a/include/linux/neighbour.h
+++ b/include/linux/neighbour.h
@@ -20,6 +20,12 @@
 	NDA_LLADDR,
 	NDA_CACHEINFO,
 	NDA_PROBES,
+	NDA_VLAN,
+	NDA_PORT,
+	NDA_VNI,
+	NDA_IFINDEX,
+	NDA_MASTER,
+	NDA_LINK_NETNSID,
 	__NDA_MAX
 };
 
@@ -30,7 +36,10 @@
  */
 
 #define NTF_USE		0x01
+#define NTF_SELF	0x02
+#define NTF_MASTER	0x04
 #define NTF_PROXY	0x08	/* == ATF_PUBL */
+#define NTF_EXT_LEARNED	0x10
 #define NTF_ROUTER	0x80
 
 /*
@@ -51,7 +60,7 @@
 
 /* NUD_NOARP & NUD_PERMANENT are pseudostates, they never change
    and make no address resolution or NUD.
-   NUD_PERMANENT is also cannot be deleted by garbage collectors.
+   NUD_PERMANENT also cannot be deleted by garbage collectors.
  */
 
 struct nda_cacheinfo {
diff --git a/include/linux/net_namespace.h b/include/linux/net_namespace.h
new file mode 100644
index 0000000..9a92b7e
--- /dev/null
+++ b/include/linux/net_namespace.h
@@ -0,0 +1,23 @@
+/* Copyright (c) 2015 6WIND S.A.
+ * Author: Nicolas Dichtel <nicolas.dichtel@6wind.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+#ifndef _LINUX_NET_NAMESPACE_H_
+#define _LINUX_NET_NAMESPACE_H_
+
+/* Attributes of RTM_NEWNSID/RTM_GETNSID messages */
+enum {
+	NETNSA_NONE,
+#define NETNSA_NSID_NOT_ASSIGNED -1
+	NETNSA_NSID,
+	NETNSA_PID,
+	NETNSA_FD,
+	__NETNSA_MAX,
+};
+
+#define NETNSA_MAX		(__NETNSA_MAX - 1)
+
+#endif /* _LINUX_NET_NAMESPACE_H_ */
diff --git a/include/linux/netconf.h b/include/linux/netconf.h
new file mode 100644
index 0000000..6ceb170
--- /dev/null
+++ b/include/linux/netconf.h
@@ -0,0 +1,25 @@
+#ifndef _LINUX_NETCONF_H_
+#define _LINUX_NETCONF_H_
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+struct netconfmsg {
+	__u8	ncm_family;
+};
+
+enum {
+	NETCONFA_UNSPEC,
+	NETCONFA_IFINDEX,
+	NETCONFA_FORWARDING,
+	NETCONFA_RP_FILTER,
+	NETCONFA_MC_FORWARDING,
+	NETCONFA_PROXY_NEIGH,
+	__NETCONFA_MAX
+};
+#define NETCONFA_MAX	(__NETCONFA_MAX - 1)
+
+#define NETCONFA_IFINDEX_ALL		-1
+#define NETCONFA_IFINDEX_DEFAULT	-2
+
+#endif /* _LINUX_NETCONF_H_ */
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 6c10e02..66fceb4 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -37,6 +37,12 @@
 #define INIT_NETDEV_GROUP	0
 
 
+/* interface name assignment types (sysfs name_assign_type attribute) */
+#define NET_NAME_UNKNOWN	0	/* unknown origin (not exposed to userspace) */
+#define NET_NAME_ENUM		1	/* enumerated by kernel */
+#define NET_NAME_PREDICTABLE	2	/* predictably named by the kernel */
+#define NET_NAME_USER		3	/* provided by user-space */
+#define NET_NAME_RENAMED	4	/* renamed by user-space */
 
 /* Media selection options. */
 enum {
@@ -49,5 +55,11 @@
         IF_PORT_100BASEFX
 };
 
+/* hardware address assignment types */
+#define NET_ADDR_PERM		0	/* address is permanent (default) */
+#define NET_ADDR_RANDOM		1	/* address is generated randomly */
+#define NET_ADDR_STOLEN		2	/* address is stolen from other device */
+#define NET_ADDR_SET		3	/* address is set using
+					 * dev_set_mac_address() */
 
-#endif	/* _LINUX_NETDEVICE_H */
+#endif /* _LINUX_NETDEVICE_H */
diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h
index 5477131..be0bc18 100644
--- a/include/linux/netfilter.h
+++ b/include/linux/netfilter.h
@@ -5,6 +5,7 @@
 
 #include <linux/sysctl.h>
 
+
 /* Responses from hook functions. */
 #define NF_DROP 0
 #define NF_ACCEPT 1
@@ -50,6 +51,7 @@
 
 enum {
 	NFPROTO_UNSPEC =  0,
+	NFPROTO_INET   =  1,
 	NFPROTO_IPV4   =  2,
 	NFPROTO_ARP    =  3,
 	NFPROTO_BRIDGE =  7,
@@ -66,4 +68,4 @@
 	struct in6_addr	in6;
 };
 
-#endif /*__LINUX_NETFILTER_H*/
+#endif /* __LINUX_NETFILTER_H */
diff --git a/include/linux/netfilter_ipv4.h b/include/linux/netfilter_ipv4.h
index 5490309..a5f4dc7 100644
--- a/include/linux/netfilter_ipv4.h
+++ b/include/linux/netfilter_ipv4.h
@@ -1,9 +1,9 @@
-#ifndef __LINUX_IP_NETFILTER_H
-#define __LINUX_IP_NETFILTER_H
-
 /* IPv4-specific defines for netfilter. 
  * (C)1998 Rusty Russell -- This code is GPL.
  */
+#ifndef __LINUX_IP_NETFILTER_H
+#define __LINUX_IP_NETFILTER_H
+
 
 #include <linux/netfilter.h>
 
@@ -64,6 +64,7 @@
 	NF_IP_PRI_SECURITY = 50,
 	NF_IP_PRI_NAT_SRC = 100,
 	NF_IP_PRI_SELINUX_LAST = 225,
+	NF_IP_PRI_CONNTRACK_HELPER = 300,
 	NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
 	NF_IP_PRI_LAST = INT_MAX,
 };
@@ -75,4 +76,4 @@
 #define SO_ORIGINAL_DST 80
 
 
-#endif /*__LINUX_IP_NETFILTER_H*/
+#endif /* __LINUX_IP_NETFILTER_H */
diff --git a/include/linux/netlink.h b/include/linux/netlink.h
index 5c4f087..e0a09df 100644
--- a/include/linux/netlink.h
+++ b/include/linux/netlink.h
@@ -1,13 +1,14 @@
 #ifndef __LINUX_NETLINK_H
 #define __LINUX_NETLINK_H
 
+#include <linux/kernel.h>
 #include <linux/socket.h> /* for __kernel_sa_family_t */
 #include <linux/types.h>
 
 #define NETLINK_ROUTE		0	/* Routing/device hook				*/
 #define NETLINK_UNUSED		1	/* Unused number				*/
 #define NETLINK_USERSOCK	2	/* Reserved for user mode socket protocols 	*/
-#define NETLINK_FIREWALL	3	/* Firewalling hook				*/
+#define NETLINK_FIREWALL	3	/* Unused number, formerly ip_queue		*/
 #define NETLINK_SOCK_DIAG	4	/* socket monitoring				*/
 #define NETLINK_NFLOG		5	/* netfilter/iptables ULOG */
 #define NETLINK_XFRM		6	/* ipsec */
@@ -78,7 +79,7 @@
 #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))
+#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
 #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
 #define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
 #define NLMSG_NEXT(nlh,len)	 ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
@@ -105,11 +106,42 @@
 #define NETLINK_PKTINFO		3
 #define NETLINK_BROADCAST_ERROR	4
 #define NETLINK_NO_ENOBUFS	5
+#define NETLINK_RX_RING		6
+#define NETLINK_TX_RING		7
 
 struct nl_pktinfo {
 	__u32	group;
 };
 
+struct nl_mmap_req {
+	unsigned int	nm_block_size;
+	unsigned int	nm_block_nr;
+	unsigned int	nm_frame_size;
+	unsigned int	nm_frame_nr;
+};
+
+struct nl_mmap_hdr {
+	unsigned int	nm_status;
+	unsigned int	nm_len;
+	__u32		nm_group;
+	/* credentials */
+	__u32		nm_pid;
+	__u32		nm_uid;
+	__u32		nm_gid;
+};
+
+enum nl_mmap_status {
+	NL_MMAP_STATUS_UNUSED,
+	NL_MMAP_STATUS_RESERVED,
+	NL_MMAP_STATUS_VALID,
+	NL_MMAP_STATUS_COPY,
+	NL_MMAP_STATUS_SKIP,
+};
+
+#define NL_MMAP_MSG_ALIGNMENT		NLMSG_ALIGNTO
+#define NL_MMAP_MSG_ALIGN(sz)		__ALIGN_KERNEL(sz, NL_MMAP_MSG_ALIGNMENT)
+#define NL_MMAP_HDRLEN			NL_MMAP_MSG_ALIGN(sizeof(struct nl_mmap_hdr))
+
 #define NET_MAJOR 36		/* Major 36 is reserved for networking 						*/
 
 enum {
@@ -150,4 +182,4 @@
 #define NLA_HDRLEN		((int) NLA_ALIGN(sizeof(struct nlattr)))
 
 
-#endif	/* __LINUX_NETLINK_H */
+#endif /* __LINUX_NETLINK_H */
diff --git a/include/linux/netlink_diag.h b/include/linux/netlink_diag.h
new file mode 100644
index 0000000..f2159d3
--- /dev/null
+++ b/include/linux/netlink_diag.h
@@ -0,0 +1,53 @@
+#ifndef __NETLINK_DIAG_H__
+#define __NETLINK_DIAG_H__
+
+#include <linux/types.h>
+
+struct netlink_diag_req {
+	__u8	sdiag_family;
+	__u8	sdiag_protocol;
+	__u16	pad;
+	__u32	ndiag_ino;
+	__u32	ndiag_show;
+	__u32	ndiag_cookie[2];
+};
+
+struct netlink_diag_msg {
+	__u8	ndiag_family;
+	__u8	ndiag_type;
+	__u8	ndiag_protocol;
+	__u8	ndiag_state;
+
+	__u32	ndiag_portid;
+	__u32	ndiag_dst_portid;
+	__u32	ndiag_dst_group;
+	__u32	ndiag_ino;
+	__u32	ndiag_cookie[2];
+};
+
+struct netlink_diag_ring {
+	__u32	ndr_block_size;
+	__u32	ndr_block_nr;
+	__u32	ndr_frame_size;
+	__u32	ndr_frame_nr;
+};
+
+enum {
+	/* NETLINK_DIAG_NONE, standard nl API requires this attribute!  */
+	NETLINK_DIAG_MEMINFO,
+	NETLINK_DIAG_GROUPS,
+	NETLINK_DIAG_RX_RING,
+	NETLINK_DIAG_TX_RING,
+
+	__NETLINK_DIAG_MAX,
+};
+
+#define NETLINK_DIAG_MAX (__NETLINK_DIAG_MAX - 1)
+
+#define NDIAG_PROTO_ALL		((__u8) ~0)
+
+#define NDIAG_SHOW_MEMINFO	0x00000001 /* show memory info of a socket */
+#define NDIAG_SHOW_GROUPS	0x00000002 /* show groups of a netlink socket */
+#define NDIAG_SHOW_RING_CFG	0x00000004 /* show ring configuration */
+
+#endif
diff --git a/include/linux/packet_diag.h b/include/linux/packet_diag.h
new file mode 100644
index 0000000..d08c63f
--- /dev/null
+++ b/include/linux/packet_diag.h
@@ -0,0 +1,80 @@
+#ifndef __PACKET_DIAG_H__
+#define __PACKET_DIAG_H__
+
+#include <linux/types.h>
+
+struct packet_diag_req {
+	__u8	sdiag_family;
+	__u8	sdiag_protocol;
+	__u16	pad;
+	__u32	pdiag_ino;
+	__u32	pdiag_show;
+	__u32	pdiag_cookie[2];
+};
+
+#define PACKET_SHOW_INFO	0x00000001 /* Basic packet_sk information */
+#define PACKET_SHOW_MCLIST	0x00000002 /* A set of packet_diag_mclist-s */
+#define PACKET_SHOW_RING_CFG	0x00000004 /* Rings configuration parameters */
+#define PACKET_SHOW_FANOUT	0x00000008
+#define PACKET_SHOW_MEMINFO	0x00000010
+#define PACKET_SHOW_FILTER	0x00000020
+
+struct packet_diag_msg {
+	__u8	pdiag_family;
+	__u8	pdiag_type;
+	__u16	pdiag_num;
+
+	__u32	pdiag_ino;
+	__u32	pdiag_cookie[2];
+};
+
+enum {
+	/* PACKET_DIAG_NONE, standard nl API requires this attribute!  */
+	PACKET_DIAG_INFO,
+	PACKET_DIAG_MCLIST,
+	PACKET_DIAG_RX_RING,
+	PACKET_DIAG_TX_RING,
+	PACKET_DIAG_FANOUT,
+	PACKET_DIAG_UID,
+	PACKET_DIAG_MEMINFO,
+	PACKET_DIAG_FILTER,
+
+	__PACKET_DIAG_MAX,
+};
+
+#define PACKET_DIAG_MAX (__PACKET_DIAG_MAX - 1)
+
+struct packet_diag_info {
+	__u32	pdi_index;
+	__u32	pdi_version;
+	__u32	pdi_reserve;
+	__u32	pdi_copy_thresh;
+	__u32	pdi_tstamp;
+	__u32	pdi_flags;
+
+#define PDI_RUNNING	0x1
+#define PDI_AUXDATA	0x2
+#define PDI_ORIGDEV	0x4
+#define PDI_VNETHDR	0x8
+#define PDI_LOSS	0x10
+};
+
+struct packet_diag_mclist {
+	__u32	pdmc_index;
+	__u32	pdmc_count;
+	__u16	pdmc_type;
+	__u16	pdmc_alen;
+	__u8	pdmc_addr[MAX_ADDR_LEN];
+};
+
+struct packet_diag_ring {
+	__u32	pdr_block_size;
+	__u32	pdr_block_nr;
+	__u32	pdr_frame_size;
+	__u32	pdr_frame_nr;
+	__u32	pdr_retire_tmo;
+	__u32	pdr_sizeof_priv;
+	__u32	pdr_features;
+};
+
+#endif
diff --git a/include/linux/pkt_cls.h b/include/linux/pkt_cls.h
index defbde2..25731df 100644
--- a/include/linux/pkt_cls.h
+++ b/include/linux/pkt_cls.h
@@ -388,6 +388,20 @@
 
 #define TCA_CGROUP_MAX (__TCA_CGROUP_MAX - 1)
 
+/* BPF classifier */
+
+enum {
+	TCA_BPF_UNSPEC,
+	TCA_BPF_ACT,
+	TCA_BPF_POLICE,
+	TCA_BPF_CLASSID,
+	TCA_BPF_OPS_LEN,
+	TCA_BPF_OPS,
+	__TCA_BPF_MAX,
+};
+
+#define TCA_BPF_MAX (__TCA_BPF_MAX - 1)
+
 /* Extended Matches */
 
 struct tcf_ematch_tree_hdr {
@@ -451,8 +465,10 @@
 #define	TCF_EM_U32		3
 #define	TCF_EM_META		4
 #define	TCF_EM_TEXT		5
-#define        TCF_EM_VLAN		6
-#define	TCF_EM_MAX		6
+#define	TCF_EM_VLAN		6
+#define	TCF_EM_CANID		7
+#define	TCF_EM_IPSET		8
+#define	TCF_EM_MAX		8
 
 enum {
 	TCF_EM_PROG_TC
diff --git a/include/linux/pkt_sched.h b/include/linux/pkt_sched.h
index 410b33d..534b847 100644
--- a/include/linux/pkt_sched.h
+++ b/include/linux/pkt_sched.h
@@ -73,9 +73,17 @@
 #define TC_H_ROOT	(0xFFFFFFFFU)
 #define TC_H_INGRESS    (0xFFFFFFF1U)
 
+/* Need to corrospond to iproute2 tc/tc_core.h "enum link_layer" */
+enum tc_link_layer {
+	TC_LINKLAYER_UNAWARE, /* Indicate unaware old iproute2 util */
+	TC_LINKLAYER_ETHERNET,
+	TC_LINKLAYER_ATM,
+};
+#define TC_LINKLAYER_MASK 0x0F /* limit use to lower 4 bits */
+
 struct tc_ratespec {
 	unsigned char	cell_log;
-	unsigned char	__reserved;
+	__u8		linklayer; /* lower 4 bits */
 	unsigned short	overhead;
 	short		cell_align;
 	unsigned short	mpu;
@@ -163,6 +171,10 @@
 	TCA_TBF_PARMS,
 	TCA_TBF_RTAB,
 	TCA_TBF_PTAB,
+	TCA_TBF_RATE64,
+	TCA_TBF_PRATE64,
+	TCA_TBF_BURST,
+	TCA_TBF_PBURST,
 	__TCA_TBF_MAX,
 };
 
@@ -348,6 +360,9 @@
 	TCA_HTB_INIT,
 	TCA_HTB_CTAB,
 	TCA_HTB_RTAB,
+	TCA_HTB_DIRECT_QLEN,
+	TCA_HTB_RATE64,
+	TCA_HTB_CEIL64,
 	__TCA_HTB_MAX,
 };
 
@@ -509,6 +524,8 @@
 	TCA_NETEM_CORRUPT,
 	TCA_NETEM_LOSS,
 	TCA_NETEM_RATE,
+	TCA_NETEM_ECN,
+	TCA_NETEM_RATE64,
 	__TCA_NETEM_MAX,
 };
 
@@ -654,4 +671,178 @@
 	__u32 lmax;
 };
 
+/* CODEL */
+
+enum {
+	TCA_CODEL_UNSPEC,
+	TCA_CODEL_TARGET,
+	TCA_CODEL_LIMIT,
+	TCA_CODEL_INTERVAL,
+	TCA_CODEL_ECN,
+	__TCA_CODEL_MAX
+};
+
+#define TCA_CODEL_MAX	(__TCA_CODEL_MAX - 1)
+
+struct tc_codel_xstats {
+	__u32	maxpacket; /* largest packet we've seen so far */
+	__u32	count;	   /* how many drops we've done since the last time we
+			    * entered dropping state
+			    */
+	__u32	lastcount; /* count at entry to dropping state */
+	__u32	ldelay;    /* in-queue delay seen by most recently dequeued packet */
+	__s32	drop_next; /* time to drop next packet */
+	__u32	drop_overlimit; /* number of time max qdisc packet limit was hit */
+	__u32	ecn_mark;  /* number of packets we ECN marked instead of dropped */
+	__u32	dropping;  /* are we in dropping state ? */
+};
+
+/* FQ_CODEL */
+
+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)
+
+enum {
+	TCA_FQ_CODEL_XSTATS_QDISC,
+	TCA_FQ_CODEL_XSTATS_CLASS,
+};
+
+struct tc_fq_codel_qd_stats {
+	__u32	maxpacket;	/* largest packet we've seen so far */
+	__u32	drop_overlimit; /* number of time max qdisc
+				 * packet limit was hit
+				 */
+	__u32	ecn_mark;	/* number of packets we ECN marked
+				 * instead of being dropped
+				 */
+	__u32	new_flow_count; /* number of time packets
+				 * created a 'new flow'
+				 */
+	__u32	new_flows_len;	/* count of flows in new list */
+	__u32	old_flows_len;	/* count of flows in old list */
+};
+
+struct tc_fq_codel_cl_stats {
+	__s32	deficit;
+	__u32	ldelay;		/* in-queue delay seen by most recently
+				 * dequeued packet
+				 */
+	__u32	count;
+	__u32	lastcount;
+	__u32	dropping;
+	__s32	drop_next;
+};
+
+struct tc_fq_codel_xstats {
+	__u32	type;
+	union {
+		struct tc_fq_codel_qd_stats qdisc_stats;
+		struct tc_fq_codel_cl_stats class_stats;
+	};
+};
+
+/* FQ */
+
+enum {
+	TCA_FQ_UNSPEC,
+
+	TCA_FQ_PLIMIT,		/* limit of total number of packets in queue */
+
+	TCA_FQ_FLOW_PLIMIT,	/* limit of packets per flow */
+
+	TCA_FQ_QUANTUM,		/* RR quantum */
+
+	TCA_FQ_INITIAL_QUANTUM,		/* RR quantum for new flow */
+
+	TCA_FQ_RATE_ENABLE,	/* enable/disable rate limiting */
+
+	TCA_FQ_FLOW_DEFAULT_RATE,/* obsolete, do not use */
+
+	TCA_FQ_FLOW_MAX_RATE,	/* per flow max rate */
+
+	TCA_FQ_BUCKETS_LOG,	/* log2(number of buckets) */
+
+	TCA_FQ_FLOW_REFILL_DELAY,	/* flow credit refill delay in usec */
+
+	TCA_FQ_ORPHAN_MASK,	/* mask applied to orphaned skb hashes */
+
+	__TCA_FQ_MAX
+};
+
+#define TCA_FQ_MAX	(__TCA_FQ_MAX - 1)
+
+struct tc_fq_qd_stats {
+	__u64	gc_flows;
+	__u64	highprio_packets;
+	__u64	tcp_retrans;
+	__u64	throttled;
+	__u64	flows_plimit;
+	__u64	pkts_too_long;
+	__u64	allocation_errors;
+	__s64	time_next_delayed_flow;
+	__u32	flows;
+	__u32	inactive_flows;
+	__u32	throttled_flows;
+	__u32	pad;
+};
+
+/* Heavy-Hitter Filter */
+
+enum {
+	TCA_HHF_UNSPEC,
+	TCA_HHF_BACKLOG_LIMIT,
+	TCA_HHF_QUANTUM,
+	TCA_HHF_HH_FLOWS_LIMIT,
+	TCA_HHF_RESET_TIMEOUT,
+	TCA_HHF_ADMIT_BYTES,
+	TCA_HHF_EVICT_TIMEOUT,
+	TCA_HHF_NON_HH_WEIGHT,
+	__TCA_HHF_MAX
+};
+
+#define TCA_HHF_MAX	(__TCA_HHF_MAX - 1)
+
+struct tc_hhf_xstats {
+	__u32	drop_overlimit; /* number of times max qdisc packet limit
+				 * was hit
+				 */
+	__u32	hh_overlimit;   /* number of times max heavy-hitters was hit */
+	__u32	hh_tot_count;   /* number of captured heavy-hitters so far */
+	__u32	hh_cur_count;   /* number of current heavy-hitters */
+};
+
+/* PIE */
+enum {
+	TCA_PIE_UNSPEC,
+	TCA_PIE_TARGET,
+	TCA_PIE_LIMIT,
+	TCA_PIE_TUPDATE,
+	TCA_PIE_ALPHA,
+	TCA_PIE_BETA,
+	TCA_PIE_ECN,
+	TCA_PIE_BYTEMODE,
+	__TCA_PIE_MAX
+};
+#define TCA_PIE_MAX   (__TCA_PIE_MAX - 1)
+
+struct tc_pie_xstats {
+	__u32 prob;             /* current probability */
+	__u32 delay;            /* current delay in ms */
+	__u32 avg_dq_rate;      /* current average dq_rate in bits/pie_time */
+	__u32 packets_in;       /* total number of packets enqueued */
+	__u32 dropped;          /* packets dropped due to pie_action */
+	__u32 overlimit;        /* dropped due to lack of space in queue */
+	__u32 maxq;             /* maximum queue size */
+	__u32 ecn_mark;         /* packets marked with ecn*/
+};
 #endif
diff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h
index 4ee1f37..5710d48 100644
--- a/include/linux/rtnetlink.h
+++ b/include/linux/rtnetlink.h
@@ -120,6 +120,23 @@
 	RTM_SETDCB,
 #define RTM_SETDCB RTM_SETDCB
 
+	RTM_NEWNETCONF = 80,
+#define RTM_NEWNETCONF RTM_NEWNETCONF
+	RTM_GETNETCONF = 82,
+#define RTM_GETNETCONF RTM_GETNETCONF
+
+	RTM_NEWMDB = 84,
+#define RTM_NEWMDB RTM_NEWMDB
+	RTM_DELMDB = 85,
+#define RTM_DELMDB RTM_DELMDB
+	RTM_GETMDB = 86,
+#define RTM_GETMDB RTM_GETMDB
+
+	RTM_NEWNSID = 88,
+#define RTM_NEWNSID RTM_NEWNSID
+	RTM_GETNSID = 90,
+#define RTM_GETNSID RTM_GETNSID
+
 	__RTM_MAX,
 #define RTM_MAX		(((__RTM_MAX + 3) & ~3) - 1)
 };
@@ -222,6 +239,8 @@
 #define RTPROT_XORP	14	/* XORP */
 #define RTPROT_NTK	15	/* Netsukuku */
 #define RTPROT_DHCP	16      /* DHCP client */
+#define RTPROT_MROUTED	17      /* Multicast daemon */
+#define RTPROT_BABEL	42      /* Babel daemon */
 
 /* rtm_scope
 
@@ -374,6 +393,10 @@
 #define RTAX_RTO_MIN RTAX_RTO_MIN
 	RTAX_INITRWND,
 #define RTAX_INITRWND RTAX_INITRWND
+	RTAX_QUICKACK,
+#define RTAX_QUICKACK RTAX_QUICKACK
+	RTAX_CC_ALGO,
+#define RTAX_CC_ALGO RTAX_CC_ALGO
 	__RTAX_MAX
 };
 
@@ -405,6 +428,12 @@
 	} u;
 };
 
+struct rta_mfc_stats {
+	__u64	mfcs_packets;
+	__u64	mfcs_bytes;
+	__u64	mfcs_wrong_if;
+};
+
 /****
  *		General form of address family dependent message.
  ****/
@@ -587,6 +616,12 @@
 #define RTNLGRP_PHONET_ROUTE	RTNLGRP_PHONET_ROUTE
 	RTNLGRP_DCB,
 #define RTNLGRP_DCB		RTNLGRP_DCB
+	RTNLGRP_IPV4_NETCONF,
+#define RTNLGRP_IPV4_NETCONF	RTNLGRP_IPV4_NETCONF
+	RTNLGRP_IPV6_NETCONF,
+#define RTNLGRP_IPV6_NETCONF	RTNLGRP_IPV6_NETCONF
+	RTNLGRP_MDB,
+#define RTNLGRP_MDB		RTNLGRP_MDB
 	__RTNLGRP_MAX
 };
 #define RTNLGRP_MAX	(__RTNLGRP_MAX - 1)
@@ -604,9 +639,11 @@
 
 /* New extended info filters for IFLA_EXT_MASK */
 #define RTEXT_FILTER_VF		(1 << 0)
+#define RTEXT_FILTER_BRVLAN	(1 << 1)
+#define RTEXT_FILTER_BRVLAN_COMPRESSED	(1 << 2)
 
 /* End of information exported to user level */
 
 
 
-#endif	/* __LINUX_RTNETLINK_H */
+#endif /* __LINUX_RTNETLINK_H */
diff --git a/include/linux/sock_diag.h b/include/linux/sock_diag.h
index 39e4b1c..78996e2 100644
--- a/include/linux/sock_diag.h
+++ b/include/linux/sock_diag.h
@@ -18,8 +18,9 @@
 	SK_MEMINFO_FWD_ALLOC,
 	SK_MEMINFO_WMEM_QUEUED,
 	SK_MEMINFO_OPTMEM,
+	SK_MEMINFO_BACKLOG,
 
 	SK_MEMINFO_VARS,
 };
 
-#endif
+#endif /* __SOCK_DIAG_H__ */
diff --git a/include/linux/tc_act/tc_bpf.h b/include/linux/tc_act/tc_bpf.h
new file mode 100644
index 0000000..5288bd7
--- /dev/null
+++ b/include/linux/tc_act/tc_bpf.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 Jiri Pirko <jiri@resnulli.us>
+ *
+ * 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 __LINUX_TC_BPF_H
+#define __LINUX_TC_BPF_H
+
+#include <linux/pkt_cls.h>
+
+#define TCA_ACT_BPF 13
+
+struct tc_act_bpf {
+	tc_gen;
+};
+
+enum {
+	TCA_ACT_BPF_UNSPEC,
+	TCA_ACT_BPF_TM,
+	TCA_ACT_BPF_PARMS,
+	TCA_ACT_BPF_OPS_LEN,
+	TCA_ACT_BPF_OPS,
+	__TCA_ACT_BPF_MAX,
+};
+#define TCA_ACT_BPF_MAX (__TCA_ACT_BPF_MAX - 1)
+
+#endif
diff --git a/include/linux/tc_act/tc_defact.h b/include/linux/tc_act/tc_defact.h
new file mode 100644
index 0000000..17dddb4
--- /dev/null
+++ b/include/linux/tc_act/tc_defact.h
@@ -0,0 +1,19 @@
+#ifndef __LINUX_TC_DEF_H
+#define __LINUX_TC_DEF_H
+
+#include <linux/pkt_cls.h>
+
+struct tc_defact {
+	tc_gen;
+};
+
+enum {
+	TCA_DEF_UNSPEC,
+	TCA_DEF_TM,
+	TCA_DEF_PARMS,
+	TCA_DEF_DATA,
+	__TCA_DEF_MAX
+};
+#define TCA_DEF_MAX (__TCA_DEF_MAX - 1)
+
+#endif
diff --git a/include/linux/tc_act/tc_ipt.h b/include/linux/tc_act/tc_ipt.h
index a233556..130aaad 100644
--- a/include/linux/tc_act/tc_ipt.h
+++ b/include/linux/tc_act/tc_ipt.h
@@ -4,6 +4,7 @@
 #include <linux/pkt_cls.h>
 
 #define TCA_ACT_IPT 6
+#define TCA_ACT_XT 10
 
 enum {
 	TCA_IPT_UNSPEC,
diff --git a/include/linux/tc_act/tc_vlan.h b/include/linux/tc_act/tc_vlan.h
new file mode 100644
index 0000000..f7b8d44
--- /dev/null
+++ b/include/linux/tc_act/tc_vlan.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us>
+ *
+ * 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 __LINUX_TC_VLAN_H
+#define __LINUX_TC_VLAN_H
+
+#include <linux/pkt_cls.h>
+
+#define TCA_ACT_VLAN 12
+
+#define TCA_VLAN_ACT_POP	1
+#define TCA_VLAN_ACT_PUSH	2
+
+struct tc_vlan {
+	tc_gen;
+	int v_action;
+};
+
+enum {
+	TCA_VLAN_UNSPEC,
+	TCA_VLAN_TM,
+	TCA_VLAN_PARMS,
+	TCA_VLAN_PUSH_VLAN_ID,
+	TCA_VLAN_PUSH_VLAN_PROTOCOL,
+	__TCA_VLAN_MAX,
+};
+#define TCA_VLAN_MAX (__TCA_VLAN_MAX - 1)
+
+#endif
diff --git a/include/linux/tcp.h b/include/linux/tcp.h
new file mode 100644
index 0000000..f96e015
--- /dev/null
+++ b/include/linux/tcp.h
@@ -0,0 +1,205 @@
+/*
+ * 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 TCP protocol.
+ *
+ * Version:	@(#)tcp.h	1.0.2	04/28/93
+ *
+ * Author:	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 _LINUX_TCP_H
+#define _LINUX_TCP_H
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+#include <linux/socket.h>
+
+struct tcphdr {
+	__be16	source;
+	__be16	dest;
+	__be32	seq;
+	__be32	ack_seq;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	__u16	res1:4,
+		doff:4,
+		fin:1,
+		syn:1,
+		rst:1,
+		psh:1,
+		ack:1,
+		urg:1,
+		ece:1,
+		cwr:1;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+	__u16	doff:4,
+		res1:4,
+		cwr:1,
+		ece:1,
+		urg:1,
+		ack:1,
+		psh:1,
+		rst:1,
+		syn:1,
+		fin:1;
+#else
+#error	"Adjust your <asm/byteorder.h> defines"
+#endif	
+	__be16	window;
+	__sum16	check;
+	__be16	urg_ptr;
+};
+
+/*
+ *	The union cast uses a gcc extension to avoid aliasing problems
+ *  (union is compatible to any of its members)
+ *  This means this part of the code is -fstrict-aliasing safe now.
+ */
+union tcp_word_hdr { 
+	struct tcphdr hdr;
+	__be32 		  words[5];
+}; 
+
+#define tcp_flag_word(tp) ( ((union tcp_word_hdr *)(tp))->words [3]) 
+
+enum { 
+	TCP_FLAG_CWR = __constant_cpu_to_be32(0x00800000),
+	TCP_FLAG_ECE = __constant_cpu_to_be32(0x00400000),
+	TCP_FLAG_URG = __constant_cpu_to_be32(0x00200000),
+	TCP_FLAG_ACK = __constant_cpu_to_be32(0x00100000),
+	TCP_FLAG_PSH = __constant_cpu_to_be32(0x00080000),
+	TCP_FLAG_RST = __constant_cpu_to_be32(0x00040000),
+	TCP_FLAG_SYN = __constant_cpu_to_be32(0x00020000),
+	TCP_FLAG_FIN = __constant_cpu_to_be32(0x00010000),
+	TCP_RESERVED_BITS = __constant_cpu_to_be32(0x0F000000),
+	TCP_DATA_OFFSET = __constant_cpu_to_be32(0xF0000000)
+}; 
+
+/*
+ * TCP general constants
+ */
+#define TCP_MSS_DEFAULT		 536U	/* IPv4 (RFC1122, RFC2581) */
+#define TCP_MSS_DESIRED		1220U	/* IPv6 (tunneled), EDNS0 (RFC3226) */
+
+/* TCP socket options */
+#define TCP_NODELAY		1	/* Turn off Nagle's algorithm. */
+#define TCP_MAXSEG		2	/* Limit MSS */
+#define TCP_CORK		3	/* Never send partially complete segments */
+#define TCP_KEEPIDLE		4	/* Start keeplives after this period */
+#define TCP_KEEPINTVL		5	/* Interval between keepalives */
+#define TCP_KEEPCNT		6	/* Number of keepalives before death */
+#define TCP_SYNCNT		7	/* Number of SYN retransmits */
+#define TCP_LINGER2		8	/* Life time of orphaned FIN-WAIT-2 state */
+#define TCP_DEFER_ACCEPT	9	/* Wake up listener only when data arrive */
+#define TCP_WINDOW_CLAMP	10	/* Bound advertised window */
+#define TCP_INFO		11	/* Information about this connection. */
+#define TCP_QUICKACK		12	/* Block/reenable quick acks */
+#define TCP_CONGESTION		13	/* Congestion control algorithm */
+#define TCP_MD5SIG		14	/* TCP MD5 Signature (RFC2385) */
+#define TCP_THIN_LINEAR_TIMEOUTS 16      /* Use linear timeouts for thin streams*/
+#define TCP_THIN_DUPACK         17      /* Fast retrans. after 1 dupack */
+#define TCP_USER_TIMEOUT	18	/* How long for loss retry before timeout */
+#define TCP_REPAIR		19	/* TCP sock is under repair right now */
+#define TCP_REPAIR_QUEUE	20
+#define TCP_QUEUE_SEQ		21
+#define TCP_REPAIR_OPTIONS	22
+#define TCP_FASTOPEN		23	/* Enable FastOpen on listeners */
+#define TCP_TIMESTAMP		24
+#define TCP_NOTSENT_LOWAT	25	/* limit number of unsent bytes in write queue */
+
+struct tcp_repair_opt {
+	__u32	opt_code;
+	__u32	opt_val;
+};
+
+enum {
+	TCP_NO_QUEUE,
+	TCP_RECV_QUEUE,
+	TCP_SEND_QUEUE,
+	TCP_QUEUES_NR,
+};
+
+/* for TCP_INFO socket option */
+#define TCPI_OPT_TIMESTAMPS	1
+#define TCPI_OPT_SACK		2
+#define TCPI_OPT_WSCALE		4
+#define TCPI_OPT_ECN		8 /* ECN was negociated at TCP session init */
+#define TCPI_OPT_ECN_SEEN	16 /* we received at least one packet with ECT */
+#define TCPI_OPT_SYN_DATA	32 /* SYN-ACK acked data in SYN sent or rcvd */
+
+enum tcp_ca_state {
+	TCP_CA_Open = 0,
+#define TCPF_CA_Open	(1<<TCP_CA_Open)
+	TCP_CA_Disorder = 1,
+#define TCPF_CA_Disorder (1<<TCP_CA_Disorder)
+	TCP_CA_CWR = 2,
+#define TCPF_CA_CWR	(1<<TCP_CA_CWR)
+	TCP_CA_Recovery = 3,
+#define TCPF_CA_Recovery (1<<TCP_CA_Recovery)
+	TCP_CA_Loss = 4
+#define TCPF_CA_Loss	(1<<TCP_CA_Loss)
+};
+
+struct tcp_info {
+	__u8	tcpi_state;
+	__u8	tcpi_ca_state;
+	__u8	tcpi_retransmits;
+	__u8	tcpi_probes;
+	__u8	tcpi_backoff;
+	__u8	tcpi_options;
+	__u8	tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
+
+	__u32	tcpi_rto;
+	__u32	tcpi_ato;
+	__u32	tcpi_snd_mss;
+	__u32	tcpi_rcv_mss;
+
+	__u32	tcpi_unacked;
+	__u32	tcpi_sacked;
+	__u32	tcpi_lost;
+	__u32	tcpi_retrans;
+	__u32	tcpi_fackets;
+
+	/* Times. */
+	__u32	tcpi_last_data_sent;
+	__u32	tcpi_last_ack_sent;     /* Not remembered, sorry. */
+	__u32	tcpi_last_data_recv;
+	__u32	tcpi_last_ack_recv;
+
+	/* Metrics. */
+	__u32	tcpi_pmtu;
+	__u32	tcpi_rcv_ssthresh;
+	__u32	tcpi_rtt;
+	__u32	tcpi_rttvar;
+	__u32	tcpi_snd_ssthresh;
+	__u32	tcpi_snd_cwnd;
+	__u32	tcpi_advmss;
+	__u32	tcpi_reordering;
+
+	__u32	tcpi_rcv_rtt;
+	__u32	tcpi_rcv_space;
+
+	__u32	tcpi_total_retrans;
+
+	__u64	tcpi_pacing_rate;
+	__u64	tcpi_max_pacing_rate;
+};
+
+/* for TCP_MD5SIG socket option */
+#define TCP_MD5SIG_MAXKEYLEN	80
+
+struct tcp_md5sig {
+	struct __kernel_sockaddr_storage tcpm_addr;	/* address associated */
+	__u16	__tcpm_pad1;				/* zero */
+	__u16	tcpm_keylen;				/* key length */
+	__u32	__tcpm_pad2;				/* zero */
+	__u8	tcpm_key[TCP_MD5SIG_MAXKEYLEN];		/* key (binary) */
+};
+
+#endif /* _LINUX_TCP_H */
diff --git a/include/linux/tcp_metrics.h b/include/linux/tcp_metrics.h
new file mode 100644
index 0000000..9353392
--- /dev/null
+++ b/include/linux/tcp_metrics.h
@@ -0,0 +1,59 @@
+/* tcp_metrics.h - TCP Metrics Interface */
+
+#ifndef _LINUX_TCP_METRICS_H
+#define _LINUX_TCP_METRICS_H
+
+#include <linux/types.h>
+
+/* NETLINK_GENERIC related info
+ */
+#define TCP_METRICS_GENL_NAME		"tcp_metrics"
+#define TCP_METRICS_GENL_VERSION	0x1
+
+enum tcp_metric_index {
+	TCP_METRIC_RTT,		/* in ms units */
+	TCP_METRIC_RTTVAR,	/* in ms units */
+	TCP_METRIC_SSTHRESH,
+	TCP_METRIC_CWND,
+	TCP_METRIC_REORDERING,
+
+	TCP_METRIC_RTT_US,	/* in usec units */
+	TCP_METRIC_RTTVAR_US,	/* in usec units */
+
+	/* Always last.  */
+	__TCP_METRIC_MAX,
+};
+
+#define TCP_METRIC_MAX	(__TCP_METRIC_MAX - 1)
+
+enum {
+	TCP_METRICS_ATTR_UNSPEC,
+	TCP_METRICS_ATTR_ADDR_IPV4,		/* u32 */
+	TCP_METRICS_ATTR_ADDR_IPV6,		/* binary */
+	TCP_METRICS_ATTR_AGE,			/* msecs */
+	TCP_METRICS_ATTR_TW_TSVAL,		/* u32, raw, rcv tsval */
+	TCP_METRICS_ATTR_TW_TS_STAMP,		/* s32, sec age */
+	TCP_METRICS_ATTR_VALS,			/* nested +1, u32 */
+	TCP_METRICS_ATTR_FOPEN_MSS,		/* u16 */
+	TCP_METRICS_ATTR_FOPEN_SYN_DROPS,	/* u16, count of drops */
+	TCP_METRICS_ATTR_FOPEN_SYN_DROP_TS,	/* msecs age */
+	TCP_METRICS_ATTR_FOPEN_COOKIE,		/* binary */
+	TCP_METRICS_ATTR_SADDR_IPV4,		/* u32 */
+	TCP_METRICS_ATTR_SADDR_IPV6,		/* binary */
+
+	__TCP_METRICS_ATTR_MAX,
+};
+
+#define TCP_METRICS_ATTR_MAX	(__TCP_METRICS_ATTR_MAX - 1)
+
+enum {
+	TCP_METRICS_CMD_UNSPEC,
+	TCP_METRICS_CMD_GET,
+	TCP_METRICS_CMD_DEL,
+
+	__TCP_METRICS_CMD_MAX,
+};
+
+#define TCP_METRICS_CMD_MAX	(__TCP_METRICS_CMD_MAX - 1)
+
+#endif /* _LINUX_TCP_METRICS_H */
diff --git a/include/linux/unix_diag.h b/include/linux/unix_diag.h
index b1d2bf1..1eb0b8d 100644
--- a/include/linux/unix_diag.h
+++ b/include/linux/unix_diag.h
@@ -31,16 +31,20 @@
 };
 
 enum {
+	/* UNIX_DIAG_NONE, standard nl API requires this attribute!  */
 	UNIX_DIAG_NAME,
 	UNIX_DIAG_VFS,
 	UNIX_DIAG_PEER,
 	UNIX_DIAG_ICONS,
 	UNIX_DIAG_RQLEN,
 	UNIX_DIAG_MEMINFO,
+	UNIX_DIAG_SHUTDOWN,
 
-	UNIX_DIAG_MAX,
+	__UNIX_DIAG_MAX,
 };
 
+#define UNIX_DIAG_MAX (__UNIX_DIAG_MAX - 1)
+
 struct unix_diag_vfs {
 	__u32	udiag_vfs_ino;
 	__u32	udiag_vfs_dev;
diff --git a/include/linux/xfrm.h b/include/linux/xfrm.h
index 0aa3805..3a1fd32 100644
--- a/include/linux/xfrm.h
+++ b/include/linux/xfrm.h
@@ -84,6 +84,8 @@
 	__u32	bitmap;
 };
 
+#define XFRMA_REPLAY_ESN_MAX	4096
+
 struct xfrm_replay_state_esn {
 	unsigned int	bmp_len;
 	__u32		oseq;
@@ -295,6 +297,9 @@
 	XFRMA_MARK,		/* struct xfrm_mark */
 	XFRMA_TFCPAD,		/* __u32 */
 	XFRMA_REPLAY_ESN_VAL,	/* struct xfrm_replay_esn */
+	XFRMA_SA_EXTRA_FLAGS,	/* __u32 */
+	XFRMA_PROTO,		/* __u8 */
+	XFRMA_ADDRESS_FILTER,	/* struct xfrm_address_filter */
 	__XFRMA_MAX
 
 #define XFRMA_MAX (__XFRMA_MAX - 1)
@@ -323,6 +328,8 @@
 	XFRMA_SPD_UNSPEC,
 	XFRMA_SPD_INFO,
 	XFRMA_SPD_HINFO,
+	XFRMA_SPD_IPV4_HTHRESH,
+	XFRMA_SPD_IPV6_HTHRESH,
 	__XFRMA_SPD_MAX
 
 #define XFRMA_SPD_MAX (__XFRMA_SPD_MAX - 1)
@@ -342,6 +349,11 @@
 	__u32 spdhmcnt;
 };
 
+struct xfrmu_spdhthresh {
+	__u8 lbits;
+	__u8 rbits;
+};
+
 struct xfrm_usersa_info {
 	struct xfrm_selector		sel;
 	struct xfrm_id			id;
@@ -365,6 +377,8 @@
 #define XFRM_STATE_ESN		128
 };
 
+#define XFRM_SA_XFLAG_DONT_ENCAP_DSCP	1
+
 struct xfrm_usersa_id {
 	xfrm_address_t			daddr;
 	__be32				spi;
@@ -469,6 +483,14 @@
 	__be16				new_sport;
 };
 
+struct xfrm_address_filter {
+	xfrm_address_t			saddr;
+	xfrm_address_t			daddr;
+	__u16				family;
+	__u8				splen;
+	__u8				dplen;
+};
+
 /* backwards compatibility for userspace */
 #define XFRMGRP_ACQUIRE		1
 #define XFRMGRP_EXPIRE		2
diff --git a/include/ll_map.h b/include/ll_map.h
index 752b827..4c78498 100644
--- a/include/ll_map.h
+++ b/include/ll_map.h
@@ -3,13 +3,12 @@
 
 extern int ll_remember_index(const struct sockaddr_nl *who,
 			     struct nlmsghdr *n, void *arg);
-extern int ll_init_map(struct rtnl_handle *rth);
+
+extern void ll_init_map(struct rtnl_handle *rth);
 extern unsigned ll_name_to_index(const char *name);
 extern const char *ll_index_to_name(unsigned idx);
 extern const char *ll_idx_n2a(unsigned idx, char *buf);
 extern int ll_index_to_type(unsigned idx);
-extern unsigned ll_index_to_flags(unsigned idx);
-extern unsigned ll_index_to_addr(unsigned idx, unsigned char *addr,
-				 unsigned alen);
+extern int ll_index_to_flags(unsigned idx);
 
 #endif /* __LL_MAP_H__ */
diff --git a/include/names.h b/include/names.h
new file mode 100644
index 0000000..6fed581
--- /dev/null
+++ b/include/names.h
@@ -0,0 +1,26 @@
+#ifndef DB_NAMES_H_
+#define DB_NAMES_H_ 1
+
+#define IDNAME_MAX 256
+
+struct db_entry {
+	struct db_entry *next;
+	unsigned int id;
+	char *name;
+};
+
+struct db_names {
+	unsigned int size;
+	struct db_entry *cached;
+	struct db_entry **hash;
+	int max;
+};
+
+struct db_names *db_names_alloc(void);
+int db_names_load(struct db_names *db, const char *path);
+void db_names_free(struct db_names *db);
+
+char *id_to_name(struct db_names *db, int id, char *name);
+int name_to_id(struct db_names *db, int *id, const char *name);
+
+#endif
diff --git a/include/namespace.h b/include/namespace.h
new file mode 100644
index 0000000..a2ac7dc
--- /dev/null
+++ b/include/namespace.h
@@ -0,0 +1,54 @@
+#ifndef __NAMESPACE_H__
+#define __NAMESPACE_H__ 1
+
+#include <sched.h>
+#include <sys/mount.h>
+#include <sys/syscall.h>
+#include <errno.h>
+
+#define NETNS_RUN_DIR "/var/run/netns"
+#define NETNS_ETC_DIR "/etc/netns"
+
+#ifndef CLONE_NEWNET
+#define CLONE_NEWNET 0x40000000	/* New network namespace (lo, device, names sockets, etc) */
+#endif
+
+#ifndef MNT_DETACH
+#define MNT_DETACH	0x00000002	/* Just detach from the tree */
+#endif /* MNT_DETACH */
+
+/* sys/mount.h may be out too old to have these */
+#ifndef MS_REC
+#define MS_REC		16384
+#endif
+
+#ifndef MS_SLAVE
+#define MS_SLAVE	(1 << 19)
+#endif
+
+#ifndef MS_SHARED
+#define MS_SHARED	(1 << 20)
+#endif
+
+#ifndef HAVE_SETNS
+static inline int setns(int fd, int nstype)
+{
+#ifdef __NR_setns
+	return syscall(__NR_setns, fd, nstype);
+#else
+	errno = ENOSYS;
+	return -1;
+#endif
+}
+#endif /* HAVE_SETNS */
+
+extern int netns_switch(char *netns);
+extern int netns_get_fd(const char *netns);
+extern int netns_foreach(int (*func)(char *nsname, void *arg), void *arg);
+
+struct netns_func {
+	int (*func)(char *nsname, void *arg);
+	void *arg;
+};
+
+#endif /* __NAMESPACE_H__ */
diff --git a/include/rt_names.h b/include/rt_names.h
index e5dbd45..c0ea4f9 100644
--- a/include/rt_names.h
+++ b/include/rt_names.h
@@ -3,29 +3,33 @@
 
 #include <asm/types.h>
 
-char* rtnl_rtprot_n2a(int id, char *buf, int len);
-char* rtnl_rtscope_n2a(int id, char *buf, int len);
-char* rtnl_rttable_n2a(__u32 id, char *buf, int len);
-char* rtnl_rtrealm_n2a(int id, char *buf, int len);
-char* rtnl_dsfield_n2a(int id, char *buf, int len);
-int rtnl_rtprot_a2n(__u32 *id, char *arg);
-int rtnl_rtscope_a2n(__u32 *id, char *arg);
-int rtnl_rttable_a2n(__u32 *id, char *arg);
-int rtnl_rtrealm_a2n(__u32 *id, char *arg);
-int rtnl_dsfield_a2n(__u32 *id, char *arg);
-int rtnl_group_a2n(int *id, char *arg);
+const char *rtnl_rtprot_n2a(int id, char *buf, int len);
+const char *rtnl_rtscope_n2a(int id, char *buf, int len);
+const char *rtnl_rttable_n2a(__u32 id, char *buf, int len);
+const char *rtnl_rtrealm_n2a(int id, char *buf, int len);
+const char *rtnl_dsfield_n2a(int id, char *buf, int len);
+const char *rtnl_group_n2a(int id, char *buf, int len);
+
+int rtnl_rtprot_a2n(__u32 *id, const char *arg);
+int rtnl_rtscope_a2n(__u32 *id, const char *arg);
+int rtnl_rttable_a2n(__u32 *id, const char *arg);
+int rtnl_rtrealm_a2n(__u32 *id, const char *arg);
+int rtnl_dsfield_a2n(__u32 *id, const char *arg);
+int rtnl_group_a2n(int *id, const char *arg);
 
 const char *inet_proto_n2a(int proto, char *buf, int len);
-int inet_proto_a2n(char *buf);
+int inet_proto_a2n(const char *buf);
 
 
 const char * ll_type_n2a(int type, char *buf, int len);
-
-const char *ll_addr_n2a(unsigned char *addr, int alen, int type, char *buf, int blen);
-int ll_addr_a2n(char *lladdr, int len, char *arg);
+const char *ll_addr_n2a(unsigned char *addr, int alen,
+			int type, char *buf, int blen);
+int ll_addr_a2n(char *lladdr, int len, const char *arg);
 
 const char * ll_proto_n2a(unsigned short id, char *buf, int len);
-int ll_proto_a2n(unsigned short *id, char *buf);
+int ll_proto_a2n(unsigned short *id, const char *buf);
 
+const char *nl_proto_n2a(int id, char *buf, int len);
+int nl_proto_a2n(__u32 *id, const char *arg);
 
 #endif
diff --git a/include/utils.h b/include/utils.h
index 496db68..9151c4f 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -1,23 +1,30 @@
 #ifndef __UTILS_H__
 #define __UTILS_H__ 1
 
+#include <sys/types.h>
 #include <asm/types.h>
 #include <resolv.h>
 #include <stdlib.h>
+#include <stdbool.h>
 
 #include "libnetlink.h"
 #include "ll_map.h"
 #include "rtm_map.h"
 
 extern int preferred_family;
+extern int human_readable;
+extern int use_iec;
 extern int show_stats;
 extern int show_details;
 extern int show_raw;
 extern int resolve_hosts;
 extern int oneline;
 extern int timestamp;
+extern int timestamp_short;
 extern char * _SL_;
 extern int max_flush_loops;
+extern int batch_mode;
+extern bool do_all;
 
 #ifndef IPPROTO_ESP
 #define IPPROTO_ESP	50
@@ -94,9 +101,12 @@
 extern char* hexstring_n2a(const __u8 *str, int len, char *buf, int blen);
 extern __u8* hexstring_a2n(const char *str, __u8 *buf, int blen);
 
+extern int af_bit_len(int af);
+extern int af_byte_len(int af);
+
 extern const char *format_host(int af, int len, const void *addr,
 			       char *buf, int buflen);
-extern const char *rt_addr_n2a(int af, int len, const void *addr,
+extern const char *rt_addr_n2a(int af, const void *addr,
 			       char *buf, int buflen);
 
 void missarg(const char *) __attribute__((noreturn));
@@ -143,15 +153,23 @@
 
 
 int print_timestamp(FILE *fp);
+void print_nlmsg_timestamp(FILE *fp, const struct nlmsghdr *n);
 
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
 
 extern int cmdlineno;
 extern ssize_t getcmdline(char **line, size_t *len, FILE *in);
 extern int makeargs(char *line, char *argv[], int maxargs);
+extern int inet_get_addr(const char *src, __u32 *dst, struct in6_addr *dst6);
 
 struct iplink_req;
 int iplink_parse(int argc, char **argv, struct iplink_req *req,
 		char **name, char **type, char **link, char **dev,
-		int *group);
+		int *group, int *index);
+
+extern int do_each_netns(int (*func)(char *nsname, void *arg), void *arg,
+		bool show_label);
+
+char *int_to_str(int val, char *buf);
+
 #endif /* __UTILS_H__ */
diff --git a/ip/Android.mk b/ip/Android.mk
index 2c49327..a6c72da 100644
--- a/ip/Android.mk
+++ b/ip/Android.mk
@@ -1,14 +1,13 @@
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-# clang cannot compile 'variable length array in structure' in ipxfrm.c
-LOCAL_CLANG := false
 LOCAL_SRC_FILES := ip.c ipaddress.c ipaddrlabel.c iproute.c iprule.c ipnetns.c \
         rtm_map.c iptunnel.c ip6tunnel.c tunnel.c ipneigh.c ipntable.c iplink.c \
         ipmaddr.c ipmonitor.c ipmroute.c ipprefix.c iptuntap.c \
         ipxfrm.c xfrm_state.c xfrm_policy.c xfrm_monitor.c \
         iplink_vlan.c link_veth.c link_gre.c iplink_can.c \
-        iplink_macvlan.c iplink_macvtap.c ipl2tp.c
+        iplink_macvlan.c iplink_macvtap.c ipl2tp.c \
+        ipfou.c iptoken.c tcp_metrics.c ipnetconf.c
 
 LOCAL_MODULE := ip
 
diff --git a/ip/Makefile b/ip/Makefile
index e029ea1..2c742f3 100644
--- a/ip/Makefile
+++ b/ip/Makefile
@@ -1,9 +1,12 @@
 IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \
     rtm_map.o iptunnel.o ip6tunnel.o tunnel.o ipneigh.o ipntable.o iplink.o \
-    ipmaddr.o ipmonitor.o ipmroute.o ipprefix.o iptuntap.o \
+    ipmaddr.o ipmonitor.o ipmroute.o ipprefix.o iptuntap.o iptoken.o \
     ipxfrm.o xfrm_state.o xfrm_policy.o xfrm_monitor.o \
     iplink_vlan.o link_veth.o link_gre.o iplink_can.o \
-    iplink_macvlan.o iplink_macvtap.o ipl2tp.o
+    iplink_macvlan.o iplink_macvtap.o ipl2tp.o link_vti.o link_vti6.o \
+    iplink_vxlan.o tcp_metrics.o iplink_ipoib.o ipnetconf.o link_ip6tnl.o \
+    link_iptnl.o link_gre6.o iplink_bond.o iplink_bond_slave.o iplink_hsr.o \
+    iplink_bridge.o iplink_bridge_slave.o ipfou.o iplink_ipvlan.o
 
 RTMONOBJ=rtmon.o
 
@@ -21,7 +24,6 @@
 
 ip: $(IPOBJ) $(LIBNETLINK)
 
-
 rtmon: $(RTMONOBJ)
 
 install: all
diff --git a/ip/ip.c b/ip/ip.c
index 6486685..6222a33 100644
--- a/ip/ip.c
+++ b/ip/ip.c
@@ -22,17 +22,21 @@
 #include "SNAPSHOT.h"
 #include "utils.h"
 #include "ip_common.h"
+#include "namespace.h"
 
 int preferred_family = AF_UNSPEC;
+int human_readable = 0;
+int use_iec = 0;
 int show_stats = 0;
 int show_details = 0;
 int resolve_hosts = 0;
 int oneline = 0;
 int timestamp = 0;
 char * _SL_ = NULL;
-char *batch_file = NULL;
 int force = 0;
 int max_flush_loops = 10;
+int batch_mode = 0;
+bool do_all = false;
 
 struct rtnl_handle rth = { .fd = -1 };
 
@@ -45,25 +49,28 @@
 "       ip [ -force ] -batch filename\n"
 "where  OBJECT := { link | addr | addrlabel | route | rule | neigh | ntable |\n"
 "                   tunnel | tuntap | maddr | mroute | mrule | monitor | xfrm |\n"
-"                   netns | l2tp }\n"
+"                   netns | l2tp | fou | tcp_metrics | token | netconf }\n"
 "       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n"
-"                    -f[amily] { inet | inet6 | ipx | dnet | link } |\n"
+"                    -h[uman-readable] | -iec |\n"
+"                    -f[amily] { inet | inet6 | ipx | dnet | bridge | link } |\n"
+"                    -4 | -6 | -I | -D | -B | -0 |\n"
 "                    -l[oops] { maximum-addr-flush-attempts } |\n"
-"                    -o[neline] | -t[imestamp] | -b[atch] [filename] |\n"
-"                    -rc[vbuf] [size]}\n");
+"                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |\n"
+"                    -rc[vbuf] [size] | -n[etns] name | -a[ll] }\n");
 	exit(-1);
 }
 
 static int do_help(int argc, char **argv)
 {
 	usage();
+        return 0;
 }
 
 static const struct cmd {
 	const char *cmd;
 	int (*func)(int argc, char **argv);
 } cmds[] = {
-	{ "address", 	do_ipaddr },
+	{ "address",	do_ipaddr },
 	{ "addrlabel",	do_ipaddrlabel },
 	{ "maddress",	do_multiaddr },
 	{ "route",	do_iproute },
@@ -74,17 +81,22 @@
 	{ "ntbl",	do_ipntable },
 	{ "link",	do_iplink },
 	{ "l2tp",	do_ipl2tp },
+	{ "fou",	do_ipfou },
 	{ "tunnel",	do_iptunnel },
 	{ "tunl",	do_iptunnel },
 	{ "tuntap",	do_iptuntap },
 	{ "tap",	do_iptuntap },
+	{ "token",	do_iptoken },
+	{ "tcpmetrics",	do_tcp_metrics },
+	{ "tcp_metrics",do_tcp_metrics },
 	{ "monitor",	do_ipmonitor },
 	{ "xfrm",	do_xfrm },
 	{ "mroute",	do_multiroute },
 	{ "mrule",	do_multirule },
 	{ "netns",	do_netns },
+	{ "netconf",	do_ipnetconf },
 	{ "help",	do_help },
-	{ 0,		0 }
+	{ 0 }
 };
 
 static int do_cmd(const char *argv0, int argc, char **argv)
@@ -108,6 +120,8 @@
 	size_t len = 0;
 	int ret = EXIT_SUCCESS;
 
+	batch_mode = 1;
+
 	if (name && strcmp(name, "-") != 0) {
 		if (freopen(name, "r", stdin) == NULL) {
 			fprintf(stderr, "Cannot open file \"%s\" for reading: %s\n",
@@ -145,9 +159,13 @@
 }
 #endif
 
+
 int main(int argc, char **argv)
 {
 	char *basename;
+#ifndef ANDROID
+	char *batch_file = NULL;
+#endif
 
 	basename = strrchr(argv[0], '/');
 	if (basename == NULL)
@@ -186,10 +204,12 @@
 				preferred_family = AF_PACKET;
 			else if (strcmp(argv[1], "ipx") == 0)
 				preferred_family = AF_IPX;
+			else if (strcmp(argv[1], "bridge") == 0)
+				preferred_family = AF_BRIDGE;
 			else if (strcmp(argv[1], "help") == 0)
 				usage();
 			else
-				invarg(argv[1], "invalid protocol family");
+				invarg("invalid protocol family", argv[1]);
 		} else if (strcmp(opt, "-4") == 0) {
 			preferred_family = AF_INET;
 		} else if (strcmp(opt, "-6") == 0) {
@@ -200,6 +220,13 @@
 			preferred_family = AF_IPX;
 		} else if (strcmp(opt, "-D") == 0) {
 			preferred_family = AF_DECnet;
+		} else if (strcmp(opt, "-B") == 0) {
+			preferred_family = AF_BRIDGE;
+		} else if (matches(opt, "-human") == 0 ||
+			   matches(opt, "-human-readable") == 0) {
+			++human_readable;
+		} else if (matches(opt, "-iec") == 0) {
+			++use_iec;
 		} else if (matches(opt, "-stats") == 0 ||
 			   matches(opt, "-statistics") == 0) {
 			++show_stats;
@@ -211,6 +238,9 @@
 			++oneline;
 		} else if (matches(opt, "-timestamp") == 0) {
 			++timestamp;
+		} else if (matches(opt, "-tshort") == 0) {
+			++timestamp;
+			++timestamp_short;
 #if 0
 		} else if (matches(opt, "-numeric") == 0) {
 			rtnl_names_numeric++;
@@ -243,6 +273,12 @@
 			rcvbuf = size;
 		} else if (matches(opt, "-help") == 0) {
 			usage();
+		} else if (matches(opt, "-netns") == 0) {
+			NEXT_ARG();
+			if (netns_switch(argv[1]))
+				exit(-1);
+		} else if (matches(opt, "-all") == 0) {
+			do_all = true;
 		} else {
 			fprintf(stderr, "Option \"%s\" is unknown, try \"ip -help\".\n", opt);
 			exit(-1);
diff --git a/ip/ip6tunnel.c b/ip/ip6tunnel.c
index d5bee36..62a8240 100644
--- a/ip/ip6tunnel.c
+++ b/ip/ip6tunnel.c
@@ -12,8 +12,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  */
 /*
  * Author:
@@ -48,11 +47,12 @@
 static void usage(void)
 {
 	fprintf(stderr, "Usage: ip -f inet6 tunnel { add | change | del | show } [ NAME ]\n");
-	fprintf(stderr, "          [ mode { ip6ip6 | ipip6 | any } ]\n");
+	fprintf(stderr, "          [ mode { ip6ip6 | ipip6 | ip6gre | vti6 | any } ]\n");
 	fprintf(stderr, "          [ remote ADDR local ADDR ] [ dev PHYS_DEV ]\n");
 	fprintf(stderr, "          [ encaplimit ELIM ]\n");
 	fprintf(stderr ,"          [ hoplimit TTL ] [ tclass TCLASS ] [ flowlabel FLOWLABEL ]\n");
 	fprintf(stderr, "          [ dscp inherit ]\n");
+	fprintf(stderr, "          [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n");
 	fprintf(stderr, "\n");
 	fprintf(stderr, "Where: NAME      := STRING\n");
 	fprintf(stderr, "       ADDR      := IPV6_ADDRESS\n");
@@ -60,12 +60,13 @@
 		IPV6_DEFAULT_TNL_ENCAP_LIMIT);
 	fprintf(stderr, "       TTL       := 0..255 (default=%d)\n",
 		DEFAULT_TNL_HOP_LIMIT);
-	fprintf(stderr, "       TOS       := { 0x0..0xff | inherit }\n");
+	fprintf(stderr, "       TCLASS    := { 0x0..0xff | inherit }\n");
 	fprintf(stderr, "       FLOWLABEL := { 0x0..0xfffff | inherit }\n");
+	fprintf(stderr, "       KEY       := { DOTTED_QUAD | NUMBER }\n");
 	exit(-1);
 }
 
-static void print_tunnel(struct ip6_tnl_parm *p)
+static void print_tunnel(struct ip6_tnl_parm2 *p)
 {
 	char remote[64];
 	char local[64];
@@ -104,9 +105,29 @@
 
 	if (p->flags & IP6_TNL_F_RCV_DSCP_COPY)
 		printf(" dscp inherit");
+
+	if (p->proto == IPPROTO_GRE) {
+		if ((p->i_flags&GRE_KEY) && (p->o_flags&GRE_KEY) && p->o_key == p->i_key)
+			printf(" key %u", ntohl(p->i_key));
+		else if ((p->i_flags|p->o_flags)&GRE_KEY) {
+			if (p->i_flags&GRE_KEY)
+				printf(" ikey %u ", ntohl(p->i_key));
+			if (p->o_flags&GRE_KEY)
+				printf(" okey %u ", ntohl(p->o_key));
+		}
+
+		if (p->i_flags&GRE_SEQ)
+			printf("%s  Drop packets out of sequence.", _SL_);
+		if (p->i_flags&GRE_CSUM)
+			printf("%s  Checksum in received packet is required.", _SL_);
+		if (p->o_flags&GRE_SEQ)
+			printf("%s  Sequence packets on output.", _SL_);
+		if (p->o_flags&GRE_CSUM)
+			printf("%s  Checksum output packets.", _SL_);
+	}
 }
 
-static int parse_args(int argc, char **argv, int cmd, struct ip6_tnl_parm *p)
+static int parse_args(int argc, char **argv, int cmd, struct ip6_tnl_parm2 *p)
 {
 	int count = 0;
 	char medium[IFNAMSIZ];
@@ -119,19 +140,25 @@
 			if (strcmp(*argv, "ipv6/ipv6") == 0 ||
 			    strcmp(*argv, "ip6ip6") == 0)
 				p->proto = IPPROTO_IPV6;
-			else if (strcmp(*argv, "ip/ipv6") == 0 ||
+			else if (strcmp(*argv, "vti6") == 0) {
+				p->proto = IPPROTO_IPV6;
+				p->i_flags |= VTI_ISVTI;
+			} else if (strcmp(*argv, "ip/ipv6") == 0 ||
 				 strcmp(*argv, "ipv4/ipv6") == 0 ||
 				 strcmp(*argv, "ipip6") == 0 ||
 				 strcmp(*argv, "ip4ip6") == 0)
 				p->proto = IPPROTO_IPIP;
+			else if (strcmp(*argv, "ip6gre") == 0 ||
+				 strcmp(*argv, "gre/ipv6") == 0)
+				p->proto = IPPROTO_GRE;
 			else if (strcmp(*argv, "any/ipv6") == 0 ||
 				 strcmp(*argv, "any") == 0)
 				p->proto = 0;
 			else {
-                                fprintf(stderr,"Cannot guess tunnel mode.\n");
-                                exit(-1);
-                        }
-                } else if (strcmp(*argv, "remote") == 0) {
+				fprintf(stderr,"Unknown tunnel mode \"%s\"\n", *argv);
+				exit(-1);
+			}
+		} else if (strcmp(*argv, "remote") == 0) {
 			inet_prefix raddr;
 			NEXT_ARG();
 			get_prefix(&raddr, *argv, preferred_family);
@@ -157,6 +184,7 @@
 				if (get_u8(&uval, *argv, 0) < -1)
 					invarg("invalid ELIM", *argv);
 				p->encap_limit = uval;
+				p->flags &= ~IP6_TNL_F_IGN_ENCAP_LIMIT;
 			}
 		} else if (strcmp(*argv, "hoplimit") == 0 ||
 			   strcmp(*argv, "ttl") == 0 ||
@@ -172,6 +200,7 @@
 			   matches(*argv, "dsfield") == 0) {
 			__u8 uval;
 			NEXT_ARG();
+			p->flowinfo &= ~IP6_FLOWINFO_TCLASS;
 			if (strcmp(*argv, "inherit") == 0)
 				p->flags |= IP6_TNL_F_USE_ORIG_TCLASS;
 			else {
@@ -184,6 +213,7 @@
 			   strcmp(*argv, "fl") == 0) {
 			__u32 uval;
 			NEXT_ARG();
+			p->flowinfo &= ~IP6_FLOWINFO_FLOWLABEL;
 			if (strcmp(*argv, "inherit") == 0)
 				p->flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL;
 			else {
@@ -199,6 +229,60 @@
 			if (strcmp(*argv, "inherit") != 0)
 				invarg("not inherit", *argv);
 			p->flags |= IP6_TNL_F_RCV_DSCP_COPY;
+		} else if (strcmp(*argv, "key") == 0) {
+			unsigned uval;
+			NEXT_ARG();
+			p->i_flags |= GRE_KEY;
+			p->o_flags |= GRE_KEY;
+			if (strchr(*argv, '.'))
+				p->i_key = p->o_key = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0) < 0) {
+					fprintf(stderr, "invalid value of \"key\"\n");
+					exit(-1);
+				}
+				p->i_key = p->o_key = htonl(uval);
+			}
+		} else if (strcmp(*argv, "ikey") == 0) {
+			unsigned uval;
+			NEXT_ARG();
+			p->i_flags |= GRE_KEY;
+			if (strchr(*argv, '.'))
+				p->i_key = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0)<0) {
+					fprintf(stderr, "invalid value of \"ikey\"\n");
+					exit(-1);
+				}
+				p->i_key = htonl(uval);
+			}
+		} else if (strcmp(*argv, "okey") == 0) {
+			unsigned uval;
+			NEXT_ARG();
+			p->o_flags |= GRE_KEY;
+			if (strchr(*argv, '.'))
+				p->o_key = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0)<0) {
+					fprintf(stderr, "invalid value of \"okey\"\n");
+					exit(-1);
+				}
+				p->o_key = htonl(uval);
+			}
+		} else if (strcmp(*argv, "seq") == 0) {
+			p->i_flags |= GRE_SEQ;
+			p->o_flags |= GRE_SEQ;
+		} else if (strcmp(*argv, "iseq") == 0) {
+			p->i_flags |= GRE_SEQ;
+		} else if (strcmp(*argv, "oseq") == 0) {
+			p->o_flags |= GRE_SEQ;
+		} else if (strcmp(*argv, "csum") == 0) {
+			p->i_flags |= GRE_CSUM;
+			p->o_flags |= GRE_CSUM;
+		} else if (strcmp(*argv, "icsum") == 0) {
+			p->i_flags |= GRE_CSUM;
+		} else if (strcmp(*argv, "ocsum") == 0) {
+			p->o_flags |= GRE_CSUM;
 		} else {
 			if (strcmp(*argv, "name") == 0) {
 				NEXT_ARG();
@@ -209,7 +293,7 @@
 				duparg2("name", *argv);
 			strncpy(p->name, *argv, IFNAMSIZ - 1);
 			if (cmd == SIOCCHGTUNNEL && count == 0) {
-				struct ip6_tnl_parm old_p;
+				struct ip6_tnl_parm2 old_p;
 				memset(&old_p, 0, sizeof(old_p));
 				if (tnl_get_ioctl(*argv, &old_p))
 					return -1;
@@ -227,7 +311,7 @@
 	return 0;
 }
 
-static void ip6_tnl_parm_init(struct ip6_tnl_parm *p, int apply_default)
+static void ip6_tnl_parm_init(struct ip6_tnl_parm2 *p, int apply_default)
 {
 	memset(p, 0, sizeof(*p));
 	p->proto = IPPROTO_IPV6;
@@ -241,8 +325,8 @@
  * @p1: user specified parameter
  * @p2: database entry
  */
-static int ip6_tnl_parm_match(const struct ip6_tnl_parm *p1,
-			      const struct ip6_tnl_parm *p2)
+static int ip6_tnl_parm_match(const struct ip6_tnl_parm2 *p1,
+			      const struct ip6_tnl_parm2 *p2)
 {
 	return ((!p1->link || p1->link == p2->link) &&
 		(!p1->name[0] || strcmp(p1->name, p2->name) == 0) &&
@@ -260,7 +344,7 @@
 		(!p1->flags || (p1->flags & p2->flags)));
 }
 
-static int do_tunnels_list(struct ip6_tnl_parm *p)
+static int do_tunnels_list(struct ip6_tnl_parm2 *p)
 {
 	char buf[512];
 	int err = -1;
@@ -284,13 +368,13 @@
 			rx_fifo, rx_frame,
 			tx_bytes, tx_packets, tx_errs, tx_drops,
 			tx_fifo, tx_colls, tx_carrier, rx_multi;
-		struct ip6_tnl_parm p1;
+		struct ip6_tnl_parm2 p1;
 		char *ptr;
 
 		buf[sizeof(buf) - 1] = '\0';
 		if ((ptr = strchr(buf, ':')) == NULL ||
 		    (*ptr++ = 0, sscanf(buf, "%s", name) != 1)) {
-			fprintf(stderr, "Wrong format of /proc/net/dev. Sorry.\n");
+			fprintf(stderr, "Wrong format for /proc/net/dev. Giving up.\n");
 			goto end;
 		}
 		if (sscanf(ptr, "%ld%ld%ld%ld%ld%ld%ld%*d%ld%ld%ld%ld%ld%ld%ld",
@@ -306,13 +390,15 @@
 			continue;
 		type = ll_index_to_type(index);
 		if (type == -1) {
-			fprintf(stderr, "Failed to get type of [%s]\n", name);
+			fprintf(stderr, "Failed to get type of \"%s\"\n", name);
 			continue;
 		}
-		if (type != ARPHRD_TUNNEL6)
+		if (type != ARPHRD_TUNNEL6 && type != ARPHRD_IP6GRE)
 			continue;
 		memset(&p1, 0, sizeof(p1));
 		ip6_tnl_parm_init(&p1, 0);
+		if (type == ARPHRD_IP6GRE)
+			p1.proto = IPPROTO_GRE;
 		strcpy(p1.name, name);
 		p1.link = ll_name_to_index(p1.name);
 		if (p1.link == 0)
@@ -343,7 +429,7 @@
 
 static int do_show(int argc, char **argv)
 {
-        struct ip6_tnl_parm p;
+        struct ip6_tnl_parm2 p;
 
 	ll_init_map(&rth);
 	ip6_tnl_parm_init(&p, 0);
@@ -366,28 +452,38 @@
 
 static int do_add(int cmd, int argc, char **argv)
 {
-	struct ip6_tnl_parm p;
+	struct ip6_tnl_parm2 p;
+	const char *basedev = "ip6tnl0";
 
 	ip6_tnl_parm_init(&p, 1);
 
 	if (parse_args(argc, argv, cmd, &p) < 0)
 		return -1;
 
-	return tnl_add_ioctl(cmd,
-			     cmd == SIOCCHGTUNNEL && p.name[0] ?
-			     p.name : "ip6tnl0", p.name, &p);
+	if (p.proto == IPPROTO_GRE)
+		basedev = "ip6gre0";
+	else if (p.i_flags & VTI_ISVTI)
+		basedev = "ip6_vti0";
+
+	return tnl_add_ioctl(cmd, basedev, p.name, &p);
 }
 
 static int do_del(int argc, char **argv)
 {
-	struct ip6_tnl_parm p;
+	struct ip6_tnl_parm2 p;
+	const char *basedev = "ip6tnl0";
 
 	ip6_tnl_parm_init(&p, 1);
 
 	if (parse_args(argc, argv, SIOCDELTUNNEL, &p) < 0)
 		return -1;
 
-	return tnl_del_ioctl(p.name[0] ? p.name : "ip6tnl0", p.name, &p);
+	if (p.proto == IPPROTO_GRE)
+		basedev = "ip6gre0";
+	else if (p.i_flags & VTI_ISVTI)
+		basedev = "ip6_vti0";
+
+	return tnl_del_ioctl(basedev, p.name, &p);
 }
 
 int do_ip6tunnel(int argc, char **argv)
@@ -399,7 +495,7 @@
 	case AF_INET6:
 		break;
 	default:
-		fprintf(stderr, "Unsupported family:%d\n", preferred_family);
+		fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
 		exit(-1);
 	}
 
@@ -408,7 +504,7 @@
 			return do_add(SIOCADDTUNNEL, argc - 1, argv + 1);
 		if (matches(*argv, "change") == 0)
 			return do_add(SIOCCHGTUNNEL, argc - 1, argv + 1);
-		if (matches(*argv, "del") == 0)
+		if (matches(*argv, "delete") == 0)
 			return do_del(argc - 1, argv + 1);
 		if (matches(*argv, "show") == 0 ||
 		    matches(*argv, "lst") == 0 ||
diff --git a/ip/ip_common.h b/ip/ip_common.h
index 5fa2cc0..89a495e 100644
--- a/ip/ip_common.h
+++ b/ip/ip_common.h
@@ -13,18 +13,27 @@
 			struct nlmsghdr *n, void *arg);
 extern int ipaddr_list(int argc, char **argv);
 extern int ipaddr_list_link(int argc, char **argv);
+void ipaddr_get_vf_rate(int, int *, int *, int);
 extern int iproute_monitor(int argc, char **argv);
 extern void iplink_usage(void) __attribute__((noreturn));
-extern void iproute_reset_filter(void);
-extern void ipaddr_reset_filter(int);
-extern void ipneigh_reset_filter(void);
+
+extern void iproute_reset_filter(int ifindex);
+extern void ipmroute_reset_filter(int ifindex);
+extern void ipaddr_reset_filter(int oneline, int ifindex);
+extern void ipneigh_reset_filter(int ifindex);
 extern void ipntable_reset_filter(void);
+extern void ipnetconf_reset_filter(int ifindex);
+
 extern int print_route(const struct sockaddr_nl *who,
 		       struct nlmsghdr *n, void *arg);
+extern int print_mroute(const struct sockaddr_nl *who,
+			struct nlmsghdr *n, void *arg);
 extern int print_prefix(const struct sockaddr_nl *who,
 			struct nlmsghdr *n, void *arg);
 extern int print_rule(const struct sockaddr_nl *who,
 		      struct nlmsghdr *n, void *arg);
+extern int print_netconf(const struct sockaddr_nl *who,
+			 struct nlmsghdr *n, void *arg);
 extern int do_ipaddr(int argc, char **argv);
 extern int do_ipaddrlabel(int argc, char **argv);
 extern int do_iproute(int argc, char **argv);
@@ -42,6 +51,11 @@
 extern int do_netns(int argc, char **argv);
 extern int do_xfrm(int argc, char **argv);
 extern int do_ipl2tp(int argc, char **argv);
+extern int do_ipfou(int argc, char **argv);
+extern int do_tcp_metrics(int argc, char **argv);
+extern int do_ipnetconf(int argc, char **argv);
+extern int do_iptoken(int argc, char **argv);
+extern int iplink_get(unsigned int flags, char *name, __u32 filt_mask);
 
 static inline int rtm_get_table(struct rtmsg *r, struct rtattr **tb)
 {
@@ -53,6 +67,8 @@
 
 extern struct rtnl_handle rth;
 
+#include <stdbool.h>
+
 struct link_util
 {
 	struct link_util	*next;
@@ -64,10 +80,13 @@
 					     struct rtattr *[]);
 	void			(*print_xstats)(struct link_util *, FILE *,
 					     struct rtattr *);
+	void			(*print_help)(struct link_util *, int, char **,
+					     FILE *);
+	bool			slave;
 };
 
 struct link_util *get_link_kind(const char *kind);
-int get_netns_fd(const char *name);
+struct link_util *get_link_slave_kind(const char *slave_kind);
 
 #ifndef	INFINITY_LIFE_TIME
 #define     INFINITY_LIFE_TIME      0xFFFFFFFFU
diff --git a/ip/ipaddress.c b/ip/ipaddress.c
index 9ab65ec..99a6ab5 100644
--- a/ip/ipaddress.c
+++ b/ip/ipaddress.c
@@ -19,7 +19,7 @@
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
-#include <sys/errno.h>
+#include <errno.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <string.h>
@@ -28,12 +28,18 @@
 #include <linux/netdevice.h>
 #include <linux/if_arp.h>
 #include <linux/sockios.h>
+#include <linux/net_namespace.h>
 
 #include "rt_names.h"
 #include "utils.h"
 #include "ll_map.h"
 #include "ip_common.h"
 
+enum {
+	IPADD_LIST,
+	IPADD_FLUSH,
+	IPADD_SAVE,
+};
 
 static struct
 {
@@ -51,6 +57,8 @@
 	int flushp;
 	int flushe;
 	int group;
+	int master;
+	char *kind;
 } filter;
 
 static int do_link;
@@ -64,34 +72,35 @@
 	}
 	fprintf(stderr, "Usage: ip addr {add|change|replace} IFADDR dev STRING [ LIFETIME ]\n");
 	fprintf(stderr, "                                                      [ CONFFLAG-LIST ]\n");
-	fprintf(stderr, "       ip addr del IFADDR dev STRING\n");
-	fprintf(stderr, "       ip addr {show|flush} [ dev STRING ] [ scope SCOPE-ID ]\n");
-	fprintf(stderr, "                            [ to PREFIX ] [ FLAG-LIST ] [ label PATTERN ]\n");
+	fprintf(stderr, "       ip addr del IFADDR dev STRING [mngtmpaddr]\n");
+	fprintf(stderr, "       ip addr {show|save|flush} [ dev STRING ] [ scope SCOPE-ID ]\n");
+	fprintf(stderr, "                            [ to PREFIX ] [ FLAG-LIST ] [ label PATTERN ] [up]\n");
+	fprintf(stderr, "       ip addr {showdump|restore}\n");
 	fprintf(stderr, "IFADDR := PREFIX | ADDR peer PREFIX\n");
 	fprintf(stderr, "          [ broadcast ADDR ] [ anycast ADDR ]\n");
 	fprintf(stderr, "          [ label STRING ] [ scope SCOPE-ID ]\n");
 	fprintf(stderr, "SCOPE-ID := [ host | link | global | NUMBER ]\n");
 	fprintf(stderr, "FLAG-LIST := [ FLAG-LIST ] FLAG\n");
 	fprintf(stderr, "FLAG  := [ permanent | dynamic | secondary | primary |\n");
-	fprintf(stderr, "           tentative | deprecated | dadfailed | temporary |\n");
+	fprintf(stderr, "           [-]tentative | [-]deprecated | [-]dadfailed | temporary |\n");
 	fprintf(stderr, "           CONFFLAG-LIST ]\n");
 	fprintf(stderr, "CONFFLAG-LIST := [ CONFFLAG-LIST ] CONFFLAG\n");
-	fprintf(stderr, "CONFFLAG  := [ home | nodad ]\n");
+	fprintf(stderr, "CONFFLAG  := [ home | nodad | mngtmpaddr | noprefixroute ]\n");
 	fprintf(stderr, "LIFETIME := [ valid_lft LFT ] [ preferred_lft LFT ]\n");
 	fprintf(stderr, "LFT := forever | SECONDS\n");
 
 	exit(-1);
 }
 
-void print_link_flags(FILE *fp, unsigned flags, unsigned mdown)
+static void print_link_flags(FILE *fp, unsigned flags, unsigned mdown)
 {
 	fprintf(fp, "<");
 	if (flags & IFF_UP && !(flags & IFF_RUNNING))
 		fprintf(fp, "NO-CARRIER%s", flags ? "," : "");
 	flags &= ~IFF_RUNNING;
 #define _PF(f) if (flags&IFF_##f) { \
-                  flags &= ~IFF_##f ; \
-                  fprintf(fp, #f "%s", flags ? "," : ""); }
+		  flags &= ~IFF_##f ; \
+		  fprintf(fp, #f "%s", flags ? "," : ""); }
 	_PF(LOOPBACK);
 	_PF(BROADCAST);
 	_PF(POINTOPOINT);
@@ -111,7 +120,7 @@
 	_PF(DORMANT);
 	_PF(ECHO);
 #undef _PF
-        if (flags)
+	if (flags)
 		fprintf(fp, "%x", flags);
 	if (mdown)
 		fprintf(fp, ",M-DOWN");
@@ -119,7 +128,7 @@
 }
 
 static const char *oper_states[] = {
-	"UNKNOWN", "NOTPRESENT", "DOWN", "LOWERLAYERDOWN", 
+	"UNKNOWN", "NOTPRESENT", "DOWN", "LOWERLAYERDOWN",
 	"TESTING", "DORMANT",	 "UP"
 };
 
@@ -157,7 +166,7 @@
 		memset(&ifr, 0, sizeof(ifr));
 		strcpy(ifr.ifr_name, rta_getattr_str(tb[IFLA_IFNAME]));
 		if (ioctl(s, SIOCGIFTXQLEN, &ifr) < 0) {
-			fprintf(f, "ioctl(SIOCGIFXQLEN) failed: %s\n", strerror(errno));
+			fprintf(f, "ioctl(SIOCGIFTXQLEN) failed: %s\n", strerror(errno));
 			close(s);
 			return;
 		}
@@ -182,38 +191,91 @@
 		fprintf(f, "mode %s ", link_modes[mode]);
 }
 
+static char *parse_link_kind(struct rtattr *tb)
+{
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+
+	parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb);
+
+	if (linkinfo[IFLA_INFO_KIND])
+		return RTA_DATA(linkinfo[IFLA_INFO_KIND]);
+
+	return "";
+}
+
 static void print_linktype(FILE *fp, struct rtattr *tb)
 {
 	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
 	struct link_util *lu;
+	struct link_util *slave_lu;
 	char *kind;
+	char *slave_kind;
 
 	parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb);
 
-	if (!linkinfo[IFLA_INFO_KIND])
-		return;
-	kind = RTA_DATA(linkinfo[IFLA_INFO_KIND]);
+	if (linkinfo[IFLA_INFO_KIND]) {
+		kind = RTA_DATA(linkinfo[IFLA_INFO_KIND]);
 
-	fprintf(fp, "%s", _SL_);
-	fprintf(fp, "    %s ", kind);
+		fprintf(fp, "%s", _SL_);
+		fprintf(fp, "    %s ", kind);
 
-	lu = get_link_kind(kind);
-	if (!lu || !lu->print_opt)
-		return;
+		lu = get_link_kind(kind);
+		if (lu && lu->print_opt) {
+			struct rtattr *attr[lu->maxattr+1], **data = NULL;
 
-	if (1) {
-		struct rtattr *attr[lu->maxattr+1], **data = NULL;
+			if (linkinfo[IFLA_INFO_DATA]) {
+				parse_rtattr_nested(attr, lu->maxattr,
+						    linkinfo[IFLA_INFO_DATA]);
+				data = attr;
+			}
+			lu->print_opt(lu, fp, data);
 
-		if (linkinfo[IFLA_INFO_DATA]) {
-			parse_rtattr_nested(attr, lu->maxattr,
-					    linkinfo[IFLA_INFO_DATA]);
-			data = attr;
+			if (linkinfo[IFLA_INFO_XSTATS] && show_stats &&
+			    lu->print_xstats)
+				lu->print_xstats(lu, fp, linkinfo[IFLA_INFO_XSTATS]);
 		}
-		lu->print_opt(lu, fp, data);
+	}
 
-		if (linkinfo[IFLA_INFO_XSTATS] && show_stats &&
-		    lu->print_xstats)
-			lu->print_xstats(lu, fp, linkinfo[IFLA_INFO_XSTATS]);
+	if (linkinfo[IFLA_INFO_SLAVE_KIND]) {
+		slave_kind = RTA_DATA(linkinfo[IFLA_INFO_SLAVE_KIND]);
+
+		fprintf(fp, "%s", _SL_);
+		fprintf(fp, "    %s_slave ", slave_kind);
+
+		slave_lu = get_link_slave_kind(slave_kind);
+		if (slave_lu && slave_lu->print_opt) {
+			struct rtattr *attr[slave_lu->maxattr+1], **data = NULL;
+
+			if (linkinfo[IFLA_INFO_SLAVE_DATA]) {
+				parse_rtattr_nested(attr, slave_lu->maxattr,
+						    linkinfo[IFLA_INFO_SLAVE_DATA]);
+				data = attr;
+			}
+			slave_lu->print_opt(slave_lu, fp, data);
+		}
+	}
+}
+
+static void print_af_spec(FILE *fp, struct rtattr *af_spec_attr)
+{
+	struct rtattr *inet6_attr;
+	struct rtattr *tb[IFLA_INET6_MAX + 1];
+
+	inet6_attr = parse_rtattr_one_nested(AF_INET6, af_spec_attr);
+	if (!inet6_attr)
+		return;
+
+	parse_rtattr_nested(tb, IFLA_INET6_MAX, inet6_attr);
+
+	if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
+		switch (rta_getattr_u8(tb[IFLA_INET6_ADDR_GEN_MODE])) {
+		case IN6_ADDR_GEN_MODE_EUI64:
+			fprintf(fp, "addrgenmode eui64 ");
+			break;
+		case IN6_ADDR_GEN_MODE_NONE:
+			fprintf(fp, "addrgenmode none ");
+			break;
+		}
 	}
 }
 
@@ -223,7 +285,8 @@
 	struct ifla_vf_vlan *vf_vlan;
 	struct ifla_vf_tx_rate *vf_tx_rate;
 	struct ifla_vf_spoofchk *vf_spoofchk;
-	struct rtattr *vf[IFLA_VF_MAX+1];
+	struct ifla_vf_link_state *vf_linkstate;
+	struct rtattr *vf[IFLA_VF_MAX + 1] = {};
 	struct rtattr *tmp;
 	SPRINT_BUF(b1);
 
@@ -249,6 +312,20 @@
 	else
 		vf_spoofchk = RTA_DATA(vf[IFLA_VF_SPOOFCHK]);
 
+	if (vf_spoofchk) {
+		/* Check if the link state vf info type is supported by
+		 * this kernel.
+		 */
+		tmp = (struct rtattr *)((char *)vf[IFLA_VF_SPOOFCHK] +
+				vf[IFLA_VF_SPOOFCHK]->rta_len);
+
+		if (tmp->rta_type != IFLA_VF_LINK_STATE)
+			vf_linkstate = NULL;
+		else
+			vf_linkstate = RTA_DATA(vf[IFLA_VF_LINK_STATE]);
+	} else
+		vf_linkstate = NULL;
+
 	fprintf(fp, "\n    vf %d MAC %s", vf_mac->vf,
 		ll_addr_n2a((unsigned char *)&vf_mac->mac,
 		ETH_ALEN, 0, b1, sizeof(b1)));
@@ -258,105 +335,219 @@
 		fprintf(fp, ", qos %d", vf_vlan->qos);
 	if (vf_tx_rate->rate)
 		fprintf(fp, ", tx rate %d (Mbps)", vf_tx_rate->rate);
+
+	if (vf[IFLA_VF_RATE]) {
+		struct ifla_vf_rate *vf_rate = RTA_DATA(vf[IFLA_VF_RATE]);
+
+		if (vf_rate->max_tx_rate)
+			fprintf(fp, ", max_tx_rate %dMbps", vf_rate->max_tx_rate);
+		if (vf_rate->min_tx_rate)
+			fprintf(fp, ", min_tx_rate %dMbps", vf_rate->min_tx_rate);
+	}
+
 	if (vf_spoofchk && vf_spoofchk->setting != -1) {
 		if (vf_spoofchk->setting)
 			fprintf(fp, ", spoof checking on");
 		else
 			fprintf(fp, ", spoof checking off");
 	}
-}
-
-static void print_link_stats64(FILE *fp, const struct rtnl_link_stats64 *s) {
-	fprintf(fp, "%s", _SL_);
-	fprintf(fp, "    RX: bytes  packets  errors  dropped overrun mcast   %s%s",
-		s->rx_compressed ? "compressed" : "", _SL_);
-	fprintf(fp, "    %-10"PRIu64" %-8"PRIu64" %-7"PRIu64" %-7"PRIu64" %-7"PRIu64" %-7"PRIu64"",
-		(uint64_t)s->rx_bytes,
-		(uint64_t)s->rx_packets,
-		(uint64_t)s->rx_errors,
-		(uint64_t)s->rx_dropped,
-		(uint64_t)s->rx_over_errors,
-		(uint64_t)s->multicast);
-	if (s->rx_compressed)
-		fprintf(fp, " %-7"PRIu64"",
-			(uint64_t)s->rx_compressed);
-	if (show_stats > 1) {
-		fprintf(fp, "%s", _SL_);
-		fprintf(fp, "    RX errors: length  crc     frame   fifo    missed%s", _SL_);
-		fprintf(fp, "               %-7"PRIu64"  %-7"PRIu64" %-7"PRIu64" %-7"PRIu64" %-7"PRIu64"",
-			(uint64_t)s->rx_length_errors,
-			(uint64_t)s->rx_crc_errors,
-			(uint64_t)s->rx_frame_errors,
-			(uint64_t)s->rx_fifo_errors,
-			(uint64_t)s->rx_missed_errors);
-	}
-	fprintf(fp, "%s", _SL_);
-	fprintf(fp, "    TX: bytes  packets  errors  dropped carrier collsns %s%s",
-		(uint64_t)s->tx_compressed ? "compressed" : "", _SL_);
-	fprintf(fp, "    %-10"PRIu64" %-8"PRIu64" %-7"PRIu64" %-7"PRIu64" %-7"PRIu64" %-7"PRIu64"",
-		(uint64_t)s->tx_bytes,
-		(uint64_t)s->tx_packets,
-		(uint64_t)s->tx_errors,
-		(uint64_t)s->tx_dropped,
-		(uint64_t)s->tx_carrier_errors,
-		(uint64_t)s->collisions);
-	if (s->tx_compressed)
-		fprintf(fp, " %-7"PRIu64"",
-			(uint64_t)s->tx_compressed);
-	if (show_stats > 1) {
-		fprintf(fp, "%s", _SL_);
-		fprintf(fp, "    TX errors: aborted fifo    window  heartbeat%s", _SL_);
-		fprintf(fp, "               %-7"PRIu64"  %-7"PRIu64" %-7"PRIu64" %-7"PRIu64"",
-			(uint64_t)s->tx_aborted_errors,
-			(uint64_t)s->tx_fifo_errors,
-			(uint64_t)s->tx_window_errors,
-			(uint64_t)s->tx_heartbeat_errors);
+	if (vf_linkstate) {
+		if (vf_linkstate->link_state == IFLA_VF_LINK_STATE_AUTO)
+			fprintf(fp, ", link-state auto");
+		else if (vf_linkstate->link_state == IFLA_VF_LINK_STATE_ENABLE)
+			fprintf(fp, ", link-state enable");
+		else
+			fprintf(fp, ", link-state disable");
 	}
 }
 
-static void print_link_stats(FILE *fp, const struct rtnl_link_stats *s)
+static void print_num(FILE *fp, unsigned width, uint64_t count)
 {
-	fprintf(fp, "%s", _SL_);
+	const char *prefix = "kMGTPE";
+	const unsigned int base = use_iec ? 1024 : 1000;
+	uint64_t powi = 1;
+	uint16_t powj = 1;
+	uint8_t precision = 2;
+	char buf[64];
+
+	if (!human_readable || count < base) {
+		fprintf(fp, "%-*"PRIu64" ", width, count);
+		return;
+	}
+
+	/* increase value by a factor of 1000/1024 and print
+	 * if result is something a human can read */
+	for(;;) {
+		powi *= base;
+		if (count / base < powi)
+			break;
+
+		if (!prefix[1])
+			break;
+		++prefix;
+	}
+
+	/* try to guess a good number of digits for precision */
+	for (; precision > 0; precision--) {
+		powj *= 10;
+		if (count / powi < powj)
+			break;
+	}
+
+	snprintf(buf, sizeof(buf), "%.*f%c%s", precision,
+		(double) count / powi, *prefix, use_iec ? "i" : "");
+
+	fprintf(fp, "%-*s ", width, buf);
+}
+
+static void print_link_stats64(FILE *fp, const struct rtnl_link_stats64 *s,
+                               const struct rtattr *carrier_changes)
+{
+	/* RX stats */
 	fprintf(fp, "    RX: bytes  packets  errors  dropped overrun mcast   %s%s",
 		s->rx_compressed ? "compressed" : "", _SL_);
-	fprintf(fp, "    %-10u %-8u %-7u %-7u %-7u %-7u",
-		s->rx_bytes, s->rx_packets, s->rx_errors,
-		s->rx_dropped, s->rx_over_errors,
-		s->multicast
-		);
+
+	fprintf(fp, "    ");
+	print_num(fp, 10, s->rx_bytes);
+	print_num(fp, 8, s->rx_packets);
+	print_num(fp, 7, s->rx_errors);
+	print_num(fp, 7, s->rx_dropped);
+	print_num(fp, 7, s->rx_over_errors);
+	print_num(fp, 7, s->multicast);
 	if (s->rx_compressed)
-		fprintf(fp, " %-7u", s->rx_compressed);
+		print_num(fp, 7, s->rx_compressed);
+
+	/* RX error stats */
 	if (show_stats > 1) {
 		fprintf(fp, "%s", _SL_);
-		fprintf(fp, "    RX errors: length  crc     frame   fifo    missed%s", _SL_);
-		fprintf(fp, "               %-7u  %-7u %-7u %-7u %-7u",
-			s->rx_length_errors,
-			s->rx_crc_errors,
-			s->rx_frame_errors,
-			s->rx_fifo_errors,
-			s->rx_missed_errors
-			);
+		fprintf(fp, "    RX errors: length   crc     frame   fifo    missed%s", _SL_);
+
+		fprintf(fp, "               ");
+		print_num(fp, 8, s->rx_length_errors);
+		print_num(fp, 7, s->rx_crc_errors);
+		print_num(fp, 7, s->rx_frame_errors);
+		print_num(fp, 7, s->rx_fifo_errors);
+		print_num(fp, 7, s->rx_missed_errors);
 	}
 	fprintf(fp, "%s", _SL_);
+
+	/* TX stats */
 	fprintf(fp, "    TX: bytes  packets  errors  dropped carrier collsns %s%s",
 		s->tx_compressed ? "compressed" : "", _SL_);
-	fprintf(fp, "    %-10u %-8u %-7u %-7u %-7u %-7u",
-		s->tx_bytes, s->tx_packets, s->tx_errors,
-		s->tx_dropped, s->tx_carrier_errors, s->collisions);
+
+
+	fprintf(fp, "    ");
+	print_num(fp, 10, s->tx_bytes);
+	print_num(fp, 8, s->tx_packets);
+	print_num(fp, 7, s->tx_errors);
+	print_num(fp, 7, s->tx_dropped);
+	print_num(fp, 7, s->tx_carrier_errors);
+	print_num(fp, 7, s->collisions);
 	if (s->tx_compressed)
-		fprintf(fp, " %-7u", s->tx_compressed);
+		print_num(fp, 7, s->tx_compressed);
+
+	/* TX error stats */
 	if (show_stats > 1) {
 		fprintf(fp, "%s", _SL_);
-		fprintf(fp, "    TX errors: aborted fifo    window  heartbeat%s", _SL_);
-		fprintf(fp, "               %-7u  %-7u %-7u %-7u",
-			s->tx_aborted_errors,
-			s->tx_fifo_errors,
-			s->tx_window_errors,
-			s->tx_heartbeat_errors
-			);
+		fprintf(fp, "    TX errors: aborted  fifo   window heartbeat");
+                if (carrier_changes)
+			fprintf(fp, " transns");
+		fprintf(fp, "%s", _SL_);
+
+		fprintf(fp, "               ");
+		print_num(fp, 8, s->tx_aborted_errors);
+		print_num(fp, 7, s->tx_fifo_errors);
+		print_num(fp, 7, s->tx_window_errors);
+		print_num(fp, 7, s->tx_heartbeat_errors);
+		if (carrier_changes)
+			print_num(fp, 7, *(uint32_t*)RTA_DATA(carrier_changes));
 	}
 }
 
+static void print_link_stats32(FILE *fp, const struct rtnl_link_stats *s,
+			       const struct rtattr *carrier_changes)
+{
+	/* RX stats */
+	fprintf(fp, "    RX: bytes  packets  errors  dropped overrun mcast   %s%s",
+		s->rx_compressed ? "compressed" : "", _SL_);
+
+
+	fprintf(fp, "    ");
+	print_num(fp, 10, s->rx_bytes);
+	print_num(fp, 8, s->rx_packets);
+	print_num(fp, 7, s->rx_errors);
+	print_num(fp, 7, s->rx_dropped);
+	print_num(fp, 7, s->rx_over_errors);
+	print_num(fp, 7, s->multicast);
+	if (s->rx_compressed)
+		print_num(fp, 7, s->rx_compressed);
+
+	/* RX error stats */
+	if (show_stats > 1) {
+		fprintf(fp, "%s", _SL_);
+		fprintf(fp, "    RX errors: length   crc     frame   fifo    missed%s", _SL_);
+		fprintf(fp, "               ");
+		print_num(fp, 8, s->rx_length_errors);
+		print_num(fp, 7, s->rx_crc_errors);
+		print_num(fp, 7, s->rx_frame_errors);
+		print_num(fp, 7, s->rx_fifo_errors);
+		print_num(fp, 7, s->rx_missed_errors);
+	}
+	fprintf(fp, "%s", _SL_);
+
+	/* TX stats */
+	fprintf(fp, "    TX: bytes  packets  errors  dropped carrier collsns %s%s",
+		s->tx_compressed ? "compressed" : "", _SL_);
+
+	fprintf(fp, "    ");
+	print_num(fp, 10, s->tx_bytes);
+	print_num(fp, 8, s->tx_packets);
+	print_num(fp, 7, s->tx_errors);
+	print_num(fp, 7, s->tx_dropped);
+	print_num(fp, 7, s->tx_carrier_errors);
+	print_num(fp, 7, s->collisions);
+	if (s->tx_compressed)
+		print_num(fp, 7, s->tx_compressed);
+
+	/* TX error stats */
+	if (show_stats > 1) {
+		fprintf(fp, "%s", _SL_);
+		fprintf(fp, "    TX errors: aborted  fifo   window heartbeat");
+                if (carrier_changes)
+			fprintf(fp, " transns");
+		fprintf(fp, "%s", _SL_);
+
+		fprintf(fp, "               ");
+		print_num(fp, 8, s->tx_aborted_errors);
+		print_num(fp, 7, s->tx_fifo_errors);
+		print_num(fp, 7, s->tx_window_errors);
+		print_num(fp, 7, s->tx_heartbeat_errors);
+		if (carrier_changes)
+			print_num(fp, 7, *(uint32_t*)RTA_DATA(carrier_changes));
+	}
+}
+
+static void __print_link_stats(FILE *fp, struct rtattr **tb)
+{
+	if (tb[IFLA_STATS64])
+		print_link_stats64(fp, RTA_DATA(tb[IFLA_STATS64]),
+					tb[IFLA_CARRIER_CHANGES]);
+	else if (tb[IFLA_STATS])
+		print_link_stats32(fp, RTA_DATA(tb[IFLA_STATS]),
+					tb[IFLA_CARRIER_CHANGES]);
+}
+
+static void print_link_stats(FILE *fp, struct nlmsghdr *n)
+{
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct rtattr * tb[IFLA_MAX+1];
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi),
+		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi)));
+	__print_link_stats(fp, tb);
+	fprintf(fp, "%s", _SL_);
+}
+
 int print_linkinfo(const struct sockaddr_nl *who,
 		   struct nlmsghdr *n, void *arg)
 {
@@ -389,10 +580,29 @@
 
 	if (tb[IFLA_GROUP]) {
 		int group = *(int*)RTA_DATA(tb[IFLA_GROUP]);
-		if (group != filter.group)
+		if (filter.group != -1 && group != filter.group)
 			return -1;
 	}
 
+	if (tb[IFLA_MASTER]) {
+		int master = *(int*)RTA_DATA(tb[IFLA_MASTER]);
+		if (filter.master > 0 && master != filter.master)
+			return -1;
+	}
+	else if (filter.master > 0)
+		return -1;
+
+	if (filter.kind) {
+		if (tb[IFLA_LINKINFO]) {
+			char *kind = parse_link_kind(tb[IFLA_LINKINFO]);
+
+			if (strcmp(kind, filter.kind))
+				return -1;
+		} else {
+			return -1;
+		}
+	}
+
 	if (n->nlmsg_type == RTM_DELLINK)
 		fprintf(fp, "Deleted ");
 
@@ -405,9 +615,13 @@
 		if (iflink == 0)
 			fprintf(fp, "@NONE: ");
 		else {
-			fprintf(fp, "@%s: ", ll_idx_n2a(iflink, b1));
-			m_flag = ll_index_to_flags(iflink);
-			m_flag = !(m_flag & IFF_UP);
+			if (tb[IFLA_LINK_NETNSID])
+				fprintf(fp, "@if%d: ", iflink);
+			else {
+				fprintf(fp, "@%s: ", ll_idx_n2a(iflink, b1));
+				m_flag = ll_index_to_flags(iflink);
+				m_flag = !(m_flag & IFF_UP);
+			}
 		}
 	} else {
 		fprintf(fp, ": ");
@@ -423,16 +637,30 @@
 		fprintf(fp, "master %s ", ll_idx_n2a(*(int*)RTA_DATA(tb[IFLA_MASTER]), b1));
 	}
 
+	if (tb[IFLA_PHYS_PORT_ID]) {
+		SPRINT_BUF(b1);
+		fprintf(fp, "portid %s ",
+			hexstring_n2a(RTA_DATA(tb[IFLA_PHYS_PORT_ID]),
+				      RTA_PAYLOAD(tb[IFLA_PHYS_PORT_ID]),
+				      b1, sizeof(b1)));
+	}
+
 	if (tb[IFLA_OPERSTATE])
 		print_operstate(fp, rta_getattr_u8(tb[IFLA_OPERSTATE]));
 
 	if (do_link && tb[IFLA_LINKMODE])
 		print_linkmode(fp, tb[IFLA_LINKMODE]);
 
+	if (tb[IFLA_GROUP]) {
+		SPRINT_BUF(b1);
+		int group = *(int*)RTA_DATA(tb[IFLA_GROUP]);
+		fprintf(fp, "group %s ", rtnl_group_n2a(group, b1, sizeof(b1)));
+	}
+
 	if (filter.showqueue)
 		print_queuelen(fp, tb);
 
-	if (!filter.family || filter.family == AF_PACKET) {
+	if (!filter.family || filter.family == AF_PACKET || show_details) {
 		SPRINT_BUF(b1);
 		fprintf(fp, "%s", _SL_);
 		fprintf(fp, "    link/%s ", ll_type_n2a(ifi->ifi_type, b1, sizeof(b1)));
@@ -455,21 +683,36 @@
 		}
 	}
 
-	if (do_link && tb[IFLA_LINKINFO] && show_details)
-		print_linktype(fp, tb[IFLA_LINKINFO]);
+	if (tb[IFLA_LINK_NETNSID]) {
+		int id = *(int*)RTA_DATA(tb[IFLA_LINK_NETNSID]);
 
-	if (do_link && tb[IFLA_IFALIAS])
-		fprintf(fp,"\n    alias %s", 
-			rta_getattr_str(tb[IFLA_IFALIAS]));
-
-	if (do_link && show_stats) {
-		if (tb[IFLA_STATS64])
-			print_link_stats64(fp, RTA_DATA(tb[IFLA_STATS64]));
-		else if (tb[IFLA_STATS])
-			print_link_stats(fp, RTA_DATA(tb[IFLA_STATS]));
+		if (id >= 0)
+			fprintf(fp, " link-netnsid %d", id);
+		else
+			fprintf(fp, " link-netnsid unknown");
 	}
 
-	if (do_link && tb[IFLA_VFINFO_LIST] && tb[IFLA_NUM_VF]) {
+	if (tb[IFLA_PROMISCUITY] && show_details)
+		fprintf(fp, " promiscuity %u ",
+			*(int*)RTA_DATA(tb[IFLA_PROMISCUITY]));
+
+	if (tb[IFLA_LINKINFO] && show_details)
+		print_linktype(fp, tb[IFLA_LINKINFO]);
+
+	if (do_link && tb[IFLA_AF_SPEC] && show_details)
+		print_af_spec(fp, tb[IFLA_AF_SPEC]);
+
+	if ((do_link || show_details) && tb[IFLA_IFALIAS]) {
+		fprintf(fp, "%s    alias %s", _SL_,
+			rta_getattr_str(tb[IFLA_IFALIAS]));
+	}
+
+	if (do_link && show_stats) {
+		fprintf(fp, "%s", _SL_);
+		__print_link_stats(fp, tb);
+	}
+
+	if ((do_link || show_details) && tb[IFLA_VFINFO_LIST] && tb[IFLA_NUM_VF]) {
 		struct rtattr *i, *vflist = tb[IFLA_VFINFO_LIST];
 		int rem = RTA_PAYLOAD(vflist);
 		for (i = RTA_DATA(vflist); RTA_OK(i, rem); i = RTA_NEXT(i, rem))
@@ -478,7 +721,7 @@
 
 	fprintf(fp, "\n");
 	fflush(fp);
-	return 0;
+	return 1;
 }
 
 static int flush_update(void)
@@ -501,10 +744,17 @@
 	return 0;
 }
 
+static unsigned int get_ifa_flags(struct ifaddrmsg *ifa,
+				  struct rtattr *ifa_flags_attr)
+{
+	return ifa_flags_attr ? rta_getattr_u32(ifa_flags_attr) :
+				ifa->ifa_flags;
+}
+
 int print_addrinfo(const struct sockaddr_nl *who, struct nlmsghdr *n,
 		   void *arg)
 {
-	FILE *fp = (FILE*)arg;
+	FILE *fp = arg;
 	struct ifaddrmsg *ifa = NLMSG_DATA(n);
 	int len = n->nlmsg_len;
 	int deprecated = 0;
@@ -525,7 +775,10 @@
 	if (filter.flushb && n->nlmsg_type != RTM_NEWADDR)
 		return 0;
 
-	parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
+	parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa),
+		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
+
+	ifa_flags = get_ifa_flags(ifa, rta_tb[IFA_FLAGS]);
 
 	if (!rta_tb[IFA_LOCAL])
 		rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS];
@@ -536,7 +789,7 @@
 		return 0;
 	if ((filter.scope^ifa->ifa_scope)&filter.scopemask)
 		return 0;
-	if ((filter.flags^ifa->ifa_flags)&filter.flagmask)
+	if ((filter.flags ^ ifa_flags) & filter.flagmask)
 		return 0;
 	if (filter.label) {
 		SPRINT_BUF(b1);
@@ -596,17 +849,18 @@
 		fprintf(fp, "    family %d ", ifa->ifa_family);
 
 	if (rta_tb[IFA_LOCAL]) {
-		fprintf(fp, "%s", rt_addr_n2a(ifa->ifa_family,
+		fprintf(fp, "%s", format_host(ifa->ifa_family,
 					      RTA_PAYLOAD(rta_tb[IFA_LOCAL]),
 					      RTA_DATA(rta_tb[IFA_LOCAL]),
 					      abuf, sizeof(abuf)));
 
 		if (rta_tb[IFA_ADDRESS] == NULL ||
-		    memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]), 4) == 0) {
+		    memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]),
+			   ifa->ifa_family == AF_INET ? 4 : 16) == 0) {
 			fprintf(fp, "/%d ", ifa->ifa_prefixlen);
 		} else {
 			fprintf(fp, " peer %s/%d ",
-				rt_addr_n2a(ifa->ifa_family,
+				format_host(ifa->ifa_family,
 					    RTA_PAYLOAD(rta_tb[IFA_ADDRESS]),
 					    RTA_DATA(rta_tb[IFA_ADDRESS]),
 					    abuf, sizeof(abuf)),
@@ -616,49 +870,56 @@
 
 	if (rta_tb[IFA_BROADCAST]) {
 		fprintf(fp, "brd %s ",
-			rt_addr_n2a(ifa->ifa_family,
+			format_host(ifa->ifa_family,
 				    RTA_PAYLOAD(rta_tb[IFA_BROADCAST]),
 				    RTA_DATA(rta_tb[IFA_BROADCAST]),
 				    abuf, sizeof(abuf)));
 	}
 	if (rta_tb[IFA_ANYCAST]) {
 		fprintf(fp, "any %s ",
-			rt_addr_n2a(ifa->ifa_family,
+			format_host(ifa->ifa_family,
 				    RTA_PAYLOAD(rta_tb[IFA_ANYCAST]),
 				    RTA_DATA(rta_tb[IFA_ANYCAST]),
 				    abuf, sizeof(abuf)));
 	}
 	fprintf(fp, "scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1)));
-	ifa_flags = ifa->ifa_flags;
-	if (ifa->ifa_flags&IFA_F_SECONDARY) {
+	if (ifa_flags & IFA_F_SECONDARY) {
 		ifa_flags &= ~IFA_F_SECONDARY;
 		if (ifa->ifa_family == AF_INET6)
 			fprintf(fp, "temporary ");
 		else
 			fprintf(fp, "secondary ");
 	}
-	if (ifa->ifa_flags&IFA_F_TENTATIVE) {
+	if (ifa_flags & IFA_F_TENTATIVE) {
 		ifa_flags &= ~IFA_F_TENTATIVE;
 		fprintf(fp, "tentative ");
 	}
-	if (ifa->ifa_flags&IFA_F_DEPRECATED) {
+	if (ifa_flags & IFA_F_DEPRECATED) {
 		ifa_flags &= ~IFA_F_DEPRECATED;
 		deprecated = 1;
 		fprintf(fp, "deprecated ");
 	}
-	if (ifa->ifa_flags&IFA_F_HOMEADDRESS) {
+	if (ifa_flags & IFA_F_HOMEADDRESS) {
 		ifa_flags &= ~IFA_F_HOMEADDRESS;
 		fprintf(fp, "home ");
 	}
-	if (ifa->ifa_flags&IFA_F_NODAD) {
+	if (ifa_flags & IFA_F_NODAD) {
 		ifa_flags &= ~IFA_F_NODAD;
 		fprintf(fp, "nodad ");
 	}
-	if (!(ifa->ifa_flags&IFA_F_PERMANENT)) {
+	if (ifa_flags & IFA_F_MANAGETEMPADDR) {
+		ifa_flags &= ~IFA_F_MANAGETEMPADDR;
+		fprintf(fp, "mngtmpaddr ");
+	}
+	if (ifa_flags & IFA_F_NOPREFIXROUTE) {
+		ifa_flags &= ~IFA_F_NOPREFIXROUTE;
+		fprintf(fp, "noprefixroute ");
+	}
+	if (!(ifa_flags & IFA_F_PERMANENT)) {
 		fprintf(fp, "dynamic ");
 	} else
 		ifa_flags &= ~IFA_F_PERMANENT;
-	if (ifa->ifa_flags&IFA_F_DADFAILED) {
+	if (ifa_flags & IFA_F_DADFAILED) {
 		ifa_flags &= ~IFA_F_DADFAILED;
 		fprintf(fp, "dadfailed ");
 	}
@@ -689,8 +950,8 @@
 	return 0;
 }
 
-int print_addrinfo_primary(const struct sockaddr_nl *who, struct nlmsghdr *n,
-			   void *arg)
+static int print_addrinfo_primary(const struct sockaddr_nl *who,
+				  struct nlmsghdr *n, void *arg)
 {
 	struct ifaddrmsg *ifa = NLMSG_DATA(n);
 
@@ -700,8 +961,8 @@
 	return print_addrinfo(who, n, arg);
 }
 
-int print_addrinfo_secondary(const struct sockaddr_nl *who, struct nlmsghdr *n,
-			     void *arg)
+static int print_addrinfo_secondary(const struct sockaddr_nl *who,
+				    struct nlmsghdr *n, void *arg)
 {
 	struct ifaddrmsg *ifa = NLMSG_DATA(n);
 
@@ -717,7 +978,14 @@
 	struct nlmsghdr	  h;
 };
 
-static int print_selected_addrinfo(int ifindex, struct nlmsg_list *ainfo, FILE *fp)
+struct nlmsg_chain
+{
+	struct nlmsg_list *head;
+	struct nlmsg_list *tail;
+};
+
+static int print_selected_addrinfo(struct ifinfomsg *ifi,
+				   struct nlmsg_list *ainfo, FILE *fp)
 {
 	for ( ;ainfo ;  ainfo = ainfo->next) {
 		struct nlmsghdr *n = &ainfo->h;
@@ -729,10 +997,13 @@
 		if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifa)))
 			return -1;
 
-		if (ifa->ifa_index != ifindex ||
+		if (ifa->ifa_index != ifi->ifi_index ||
 		    (filter.family && filter.family != ifa->ifa_family))
 			continue;
 
+		if (filter.up && !(ifi->ifi_flags&IFF_UP))
+			continue;
+
 		print_addrinfo(NULL, n, fp);
 	}
 	return 0;
@@ -742,9 +1013,8 @@
 static int store_nlmsg(const struct sockaddr_nl *who, struct nlmsghdr *n,
 		       void *arg)
 {
-	struct nlmsg_list **linfo = (struct nlmsg_list**)arg;
+	struct nlmsg_chain *lchain = (struct nlmsg_chain *)arg;
 	struct nlmsg_list *h;
-	struct nlmsg_list **lp;
 
 	h = malloc(n->nlmsg_len+sizeof(void*));
 	if (h == NULL)
@@ -753,30 +1023,270 @@
 	memcpy(&h->h, n, n->nlmsg_len);
 	h->next = NULL;
 
-	for (lp = linfo; *lp; lp = &(*lp)->next) /* NOTHING */;
-	*lp = h;
+	if (lchain->tail)
+		lchain->tail->next = h;
+	else
+		lchain->head = h;
+	lchain->tail = h;
 
 	ll_remember_index(who, n, NULL);
 	return 0;
 }
 
-static int ipaddr_list_or_flush(int argc, char **argv, int flush)
+static __u32 ipadd_dump_magic = 0x47361222;
+
+static int ipadd_save_prep(void)
 {
-	struct nlmsg_list *linfo = NULL;
-	struct nlmsg_list *ainfo = NULL;
+	int ret;
+
+	if (isatty(STDOUT_FILENO)) {
+		fprintf(stderr, "Not sending a binary stream to stdout\n");
+		return -1;
+	}
+
+	ret = write(STDOUT_FILENO, &ipadd_dump_magic, sizeof(ipadd_dump_magic));
+	if (ret != sizeof(ipadd_dump_magic)) {
+		fprintf(stderr, "Can't write magic to dump file\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int ipadd_dump_check_magic(void)
+{
+	int ret;
+	__u32 magic = 0;
+
+	if (isatty(STDIN_FILENO)) {
+		fprintf(stderr, "Can't restore addr dump from a terminal\n");
+		return -1;
+	}
+
+	ret = fread(&magic, sizeof(magic), 1, stdin);
+	if (magic != ipadd_dump_magic) {
+		fprintf(stderr, "Magic mismatch (%d elems, %x magic)\n", ret, magic);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int save_nlmsg(const struct sockaddr_nl *who, struct nlmsghdr *n,
+		       void *arg)
+{
+	int ret;
+
+	ret = write(STDOUT_FILENO, n, n->nlmsg_len);
+	if ((ret > 0) && (ret != n->nlmsg_len)) {
+		fprintf(stderr, "Short write while saving nlmsg\n");
+		ret = -EIO;
+	}
+
+	return ret == n->nlmsg_len ? 0 : ret;
+}
+
+static int show_handler(const struct sockaddr_nl *nl, struct nlmsghdr *n, void *arg)
+{
+	struct ifaddrmsg *ifa = NLMSG_DATA(n);
+
+	printf("if%d:\n", ifa->ifa_index);
+	print_addrinfo(NULL, n, stdout);
+	return 0;
+}
+
+static int ipaddr_showdump(void)
+{
+	if (ipadd_dump_check_magic())
+		exit(-1);
+
+	exit(rtnl_from_file(stdin, &show_handler, NULL));
+}
+
+static int restore_handler(const struct sockaddr_nl *nl, struct nlmsghdr *n, void *arg)
+{
+	int ret;
+
+	n->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+
+	ll_init_map(&rth);
+
+	ret = rtnl_talk(&rth, n, 0, 0, n);
+	if ((ret < 0) && (errno == EEXIST))
+		ret = 0;
+
+	return ret;
+}
+
+static int ipaddr_restore(void)
+{
+	if (ipadd_dump_check_magic())
+		exit(-1);
+
+	exit(rtnl_from_file(stdin, &restore_handler, NULL));
+}
+
+static void free_nlmsg_chain(struct nlmsg_chain *info)
+{
 	struct nlmsg_list *l, *n;
+
+	for (l = info->head; l; l = n) {
+		n = l->next;
+		free(l);
+	}
+}
+
+static void ipaddr_filter(struct nlmsg_chain *linfo, struct nlmsg_chain *ainfo)
+{
+	struct nlmsg_list *l, **lp;
+
+	lp = &linfo->head;
+	while ( (l = *lp) != NULL) {
+		int ok = 0;
+		int missing_net_address = 1;
+		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+		struct nlmsg_list *a;
+
+		for (a = ainfo->head; a; a = a->next) {
+			struct nlmsghdr *n = &a->h;
+			struct ifaddrmsg *ifa = NLMSG_DATA(n);
+			struct rtattr *tb[IFA_MAX + 1];
+			unsigned int ifa_flags;
+
+			if (ifa->ifa_index != ifi->ifi_index)
+				continue;
+			missing_net_address = 0;
+			if (filter.family && filter.family != ifa->ifa_family)
+				continue;
+			if ((filter.scope^ifa->ifa_scope)&filter.scopemask)
+				continue;
+
+			parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(n));
+			ifa_flags = get_ifa_flags(ifa, tb[IFA_FLAGS]);
+
+			if ((filter.flags ^ ifa_flags) & filter.flagmask)
+				continue;
+			if (filter.pfx.family || filter.label) {
+				if (!tb[IFA_LOCAL])
+					tb[IFA_LOCAL] = tb[IFA_ADDRESS];
+
+				if (filter.pfx.family && tb[IFA_LOCAL]) {
+					inet_prefix dst;
+					memset(&dst, 0, sizeof(dst));
+					dst.family = ifa->ifa_family;
+					memcpy(&dst.data, RTA_DATA(tb[IFA_LOCAL]), RTA_PAYLOAD(tb[IFA_LOCAL]));
+					if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen))
+						continue;
+				}
+				if (filter.label) {
+					SPRINT_BUF(b1);
+					const char *label;
+					if (tb[IFA_LABEL])
+						label = RTA_DATA(tb[IFA_LABEL]);
+					else
+						label = ll_idx_n2a(ifa->ifa_index, b1);
+					if (fnmatch(filter.label, label, 0) != 0)
+						continue;
+				}
+			}
+
+			ok = 1;
+			break;
+		}
+		if (missing_net_address &&
+		    (filter.family == AF_UNSPEC || filter.family == AF_PACKET))
+			ok = 1;
+		if (!ok) {
+			*lp = l->next;
+			free(l);
+		} else
+			lp = &l->next;
+	}
+}
+
+static int ipaddr_flush(void)
+{
+	int round = 0;
+	char flushb[4096-512];
+
+	filter.flushb = flushb;
+	filter.flushp = 0;
+	filter.flushe = sizeof(flushb);
+
+	while ((max_flush_loops == 0) || (round < max_flush_loops)) {
+		const struct rtnl_dump_filter_arg a[3] = {
+			{
+				.filter = print_addrinfo_secondary,
+				.arg1 = stdout,
+			},
+			{
+				.filter = print_addrinfo_primary,
+				.arg1 = stdout,
+			},
+			{
+				.filter = NULL,
+				.arg1 = NULL,
+			},
+		};
+		if (rtnl_wilddump_request(&rth, filter.family, RTM_GETADDR) < 0) {
+			perror("Cannot send dump request");
+			exit(1);
+		}
+		filter.flushed = 0;
+		if (rtnl_dump_filter_l(&rth, a) < 0) {
+			fprintf(stderr, "Flush terminated\n");
+			exit(1);
+		}
+		if (filter.flushed == 0) {
+ flush_done:
+			if (show_stats) {
+				if (round == 0)
+					printf("Nothing to flush.\n");
+				else
+					printf("*** Flush is complete after %d round%s ***\n", round, round>1?"s":"");
+			}
+			fflush(stdout);
+			return 0;
+		}
+		round++;
+		if (flush_update() < 0)
+			return 1;
+
+		if (show_stats) {
+			printf("\n*** Round %d, deleting %d addresses ***\n", round, filter.flushed);
+			fflush(stdout);
+		}
+
+		/* If we are flushing, and specifying primary, then we
+		 * want to flush only a single round.  Otherwise, we'll
+		 * start flushing secondaries that were promoted to
+		 * primaries.
+		 */
+		if (!(filter.flags & IFA_F_SECONDARY) && (filter.flagmask & IFA_F_SECONDARY))
+			goto flush_done;
+	}
+	fprintf(stderr, "*** Flush remains incomplete after %d rounds. ***\n", max_flush_loops);
+	fflush(stderr);
+	return 1;
+}
+
+static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
+{
+	struct nlmsg_chain linfo = { NULL, NULL};
+	struct nlmsg_chain ainfo = { NULL, NULL};
+	struct nlmsg_list *l;
 	char *filter_dev = NULL;
 	int no_link = 0;
 
-	ipaddr_reset_filter(oneline);
+	ipaddr_reset_filter(oneline, 0);
 	filter.showqueue = 1;
 
 	if (filter.family == AF_UNSPEC)
 		filter.family = preferred_family;
 
-	filter.group = INIT_NETDEV_GROUP;
+	filter.group = -1;
 
-	if (flush) {
+	if (action == IPADD_FLUSH) {
 		if (argc <= 0) {
 			fprintf(stderr, "Flush requires arguments.\n");
 
@@ -823,18 +1333,33 @@
 		} else if (strcmp(*argv, "tentative") == 0) {
 			filter.flags |= IFA_F_TENTATIVE;
 			filter.flagmask |= IFA_F_TENTATIVE;
+		} else if (strcmp(*argv, "-tentative") == 0) {
+			filter.flags &= ~IFA_F_TENTATIVE;
+			filter.flagmask |= IFA_F_TENTATIVE;
 		} else if (strcmp(*argv, "deprecated") == 0) {
 			filter.flags |= IFA_F_DEPRECATED;
 			filter.flagmask |= IFA_F_DEPRECATED;
+		} else if (strcmp(*argv, "-deprecated") == 0) {
+			filter.flags &= ~IFA_F_DEPRECATED;
+			filter.flagmask |= IFA_F_DEPRECATED;
 		} else if (strcmp(*argv, "home") == 0) {
 			filter.flags |= IFA_F_HOMEADDRESS;
 			filter.flagmask |= IFA_F_HOMEADDRESS;
 		} else if (strcmp(*argv, "nodad") == 0) {
 			filter.flags |= IFA_F_NODAD;
 			filter.flagmask |= IFA_F_NODAD;
+		} else if (strcmp(*argv, "mngtmpaddr") == 0) {
+			filter.flags |= IFA_F_MANAGETEMPADDR;
+			filter.flagmask |= IFA_F_MANAGETEMPADDR;
+		} else if (strcmp(*argv, "noprefixroute") == 0) {
+			filter.flags |= IFA_F_NOPREFIXROUTE;
+			filter.flagmask |= IFA_F_NOPREFIXROUTE;
 		} else if (strcmp(*argv, "dadfailed") == 0) {
 			filter.flags |= IFA_F_DADFAILED;
 			filter.flagmask |= IFA_F_DADFAILED;
+		} else if (strcmp(*argv, "-dadfailed") == 0) {
+			filter.flags &= ~IFA_F_DADFAILED;
+			filter.flagmask |= IFA_F_DADFAILED;
 		} else if (strcmp(*argv, "label") == 0) {
 			NEXT_ARG();
 			filter.label = *argv;
@@ -842,6 +1367,16 @@
 			NEXT_ARG();
 			if (rtnl_group_a2n(&filter.group, *argv))
 				invarg("Invalid \"group\" value\n", *argv);
+		} else if (strcmp(*argv, "master") == 0) {
+			int ifindex;
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Device does not exist\n", *argv);
+			filter.master = ifindex;
+		} else if (do_link && strcmp(*argv, "type") == 0) {
+			NEXT_ARG();
+			filter.kind = *argv;
 		} else {
 			if (strcmp(*argv, "dev") == 0) {
 				NEXT_ARG();
@@ -855,6 +1390,47 @@
 		argv++; argc--;
 	}
 
+	if (filter_dev) {
+		filter.ifindex = ll_name_to_index(filter_dev);
+		if (filter.ifindex <= 0) {
+			fprintf(stderr, "Device \"%s\" does not exist.\n", filter_dev);
+			return -1;
+		}
+	}
+
+	if (action == IPADD_FLUSH)
+		return ipaddr_flush();
+
+	if (action == IPADD_SAVE) {
+		if (ipadd_save_prep())
+			exit(1);
+
+		if (rtnl_wilddump_request(&rth, preferred_family, RTM_GETADDR) < 0) {
+			perror("Cannot send dump request");
+			exit(1);
+		}
+
+		if (rtnl_dump_filter(&rth, save_nlmsg, stdout) < 0) {
+			fprintf(stderr, "Save terminated\n");
+			exit(1);
+		}
+
+		exit(0);
+	}
+
+	/*
+	 * If only filter_dev present and none of the other
+	 * link filters are present, use RTM_GETLINK to get
+	 * the link device
+	 */
+	if (filter_dev && filter.group == -1 && do_link == 1) {
+		if (iplink_get(0, filter_dev, RTEXT_FILTER_VF) < 0) {
+			perror("Cannot send link get request");
+			exit(1);
+		}
+		exit(0);
+	}
+
 	if (rtnl_wilddump_request(&rth, preferred_family, RTM_GETLINK) < 0) {
 		perror("Cannot send dump request");
 		exit(1);
@@ -865,80 +1441,10 @@
 		exit(1);
 	}
 
-	if (filter_dev) {
-		filter.ifindex = ll_name_to_index(filter_dev);
-		if (filter.ifindex <= 0) {
-			fprintf(stderr, "Device \"%s\" does not exist.\n", filter_dev);
-			return -1;
-		}
-	}
-
-	if (flush) {
-		int round = 0;
-		char flushb[4096-512];
-
-		filter.flushb = flushb;
-		filter.flushp = 0;
-		filter.flushe = sizeof(flushb);
-
-		while ((max_flush_loops == 0) || (round < max_flush_loops)) {
-			const struct rtnl_dump_filter_arg a[3] = {
-				{
-					.filter = print_addrinfo_secondary,
-					.arg1 = stdout,
-				},
-				{
-					.filter = print_addrinfo_primary,
-					.arg1 = stdout,
-				},
-				{
-					.filter = NULL,
-					.arg1 = NULL,
-				},
-			};
-			if (rtnl_wilddump_request(&rth, filter.family, RTM_GETADDR) < 0) {
-				perror("Cannot send dump request");
-				exit(1);
-			}
-			filter.flushed = 0;
-			if (rtnl_dump_filter_l(&rth, a) < 0) {
-				fprintf(stderr, "Flush terminated\n");
-				exit(1);
-			}
-			if (filter.flushed == 0) {
-flush_done:
-				if (show_stats) {
-					if (round == 0)
-						printf("Nothing to flush.\n");
-					else 
-						printf("*** Flush is complete after %d round%s ***\n", round, round>1?"s":"");
-				}
-				fflush(stdout);
-				return 0;
-			}
-			round++;
-			if (flush_update() < 0)
-				return 1;
-
-			if (show_stats) {
-				printf("\n*** Round %d, deleting %d addresses ***\n", round, filter.flushed);
-				fflush(stdout);
-			}
-
-			/* If we are flushing, and specifying primary, then we
-			 * want to flush only a single round.  Otherwise, we'll
-			 * start flushing secondaries that were promoted to
-			 * primaries.
-			 */
-			if (!(filter.flags & IFA_F_SECONDARY) && (filter.flagmask & IFA_F_SECONDARY))
-				goto flush_done;
-		}
-		fprintf(stderr, "*** Flush remains incomplete after %d rounds. ***\n", max_flush_loops);
-		fflush(stderr);
-		return 1;
-	}
-
 	if (filter.family != AF_PACKET) {
+		if (filter.oneline)
+			no_link = 1;
+
 		if (rtnl_wilddump_request(&rth, filter.family, RTM_GETADDR) < 0) {
 			perror("Cannot send dump request");
 			exit(1);
@@ -948,93 +1454,99 @@
 			fprintf(stderr, "Dump terminated\n");
 			exit(1);
 		}
+
+		ipaddr_filter(&linfo, &ainfo);
 	}
 
+	for (l = linfo.head; l; l = l->next) {
+		int res = 0;
 
-	if (filter.family && filter.family != AF_PACKET) {
-		struct nlmsg_list **lp;
-		lp=&linfo;
-
-		if (filter.oneline)
-			no_link = 1;
-
-		while ((l=*lp)!=NULL) {
-			int ok = 0;
-			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
-			struct nlmsg_list *a;
-
-			for (a=ainfo; a; a=a->next) {
-				struct nlmsghdr *n = &a->h;
-				struct ifaddrmsg *ifa = NLMSG_DATA(n);
-
-				if (ifa->ifa_index != ifi->ifi_index ||
-				    (filter.family && filter.family != ifa->ifa_family))
-					continue;
-				if ((filter.scope^ifa->ifa_scope)&filter.scopemask)
-					continue;
-				if ((filter.flags^ifa->ifa_flags)&filter.flagmask)
-					continue;
-				if (filter.pfx.family || filter.label) {
-					struct rtattr *tb[IFA_MAX+1];
-					parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(n));
-					if (!tb[IFA_LOCAL])
-						tb[IFA_LOCAL] = tb[IFA_ADDRESS];
-
-					if (filter.pfx.family && tb[IFA_LOCAL]) {
-						inet_prefix dst;
-						memset(&dst, 0, sizeof(dst));
-						dst.family = ifa->ifa_family;
-						memcpy(&dst.data, RTA_DATA(tb[IFA_LOCAL]), RTA_PAYLOAD(tb[IFA_LOCAL]));
-						if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen))
-							continue;
-					}
-					if (filter.label) {
-						SPRINT_BUF(b1);
-						const char *label;
-						if (tb[IFA_LABEL])
-							label = RTA_DATA(tb[IFA_LABEL]);
-						else
-							label = ll_idx_n2a(ifa->ifa_index, b1);
-						if (fnmatch(filter.label, label, 0) != 0)
-							continue;
-					}
-				}
-
-				ok = 1;
-				break;
-			}
-			if (!ok)
-				*lp = l->next;
-			else
-				lp = &l->next;
-		}
-	}
-
-	for (l=linfo; l; l = n) {
-		n = l->next;
-		if (no_link || print_linkinfo(NULL, &l->h, stdout) == 0) {
+		if (no_link || (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
 			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
 			if (filter.family != AF_PACKET)
-				print_selected_addrinfo(ifi->ifi_index, ainfo, stdout);
+				print_selected_addrinfo(ifi,
+							ainfo.head, stdout);
+			if (res > 0 && !do_link && show_stats)
+				print_link_stats(stdout, &l->h);
 		}
-		fflush(stdout);
-		free(l);
 	}
+	fflush(stdout);
+
+	free_nlmsg_chain(&ainfo);
+	free_nlmsg_chain(&linfo);
 
 	return 0;
 }
 
+static void
+ipaddr_loop_each_vf(struct rtattr *tb[], int vfnum, int *min, int *max)
+{
+	struct rtattr *vflist = tb[IFLA_VFINFO_LIST];
+	struct rtattr *i, *vf[IFLA_VF_MAX+1];
+	struct ifla_vf_rate *vf_rate;
+	int rem;
+
+	rem = RTA_PAYLOAD(vflist);
+
+	for (i = RTA_DATA(vflist); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		parse_rtattr_nested(vf, IFLA_VF_MAX, i);
+		vf_rate = RTA_DATA(vf[IFLA_VF_RATE]);
+		if (vf_rate->vf == vfnum) {
+			*min = vf_rate->min_tx_rate;
+			*max = vf_rate->max_tx_rate;
+			return;
+		}
+	}
+	fprintf(stderr, "Cannot find VF %d\n", vfnum);
+	exit(1);
+}
+
+void ipaddr_get_vf_rate(int vfnum, int *min, int *max, int idx)
+{
+	struct nlmsg_chain linfo = { NULL, NULL};
+	struct rtattr *tb[IFLA_MAX+1];
+	struct ifinfomsg *ifi;
+	struct nlmsg_list *l;
+	struct nlmsghdr *n;
+	int len;
+
+	if (rtnl_wilddump_request(&rth, AF_UNSPEC, RTM_GETLINK) < 0) {
+		perror("Cannot send dump request");
+		exit(1);
+	}
+	if (rtnl_dump_filter(&rth, store_nlmsg, &linfo) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	for (l = linfo.head; l; l = l->next) {
+		n = &l->h;
+		ifi = NLMSG_DATA(n);
+
+		len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0 || (idx && idx != ifi->ifi_index))
+			continue;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+		if ((tb[IFLA_VFINFO_LIST] && tb[IFLA_NUM_VF])) {
+			ipaddr_loop_each_vf(tb, vfnum, min, max);
+			return;
+		}
+	}
+}
+
 int ipaddr_list_link(int argc, char **argv)
 {
 	preferred_family = AF_PACKET;
 	do_link = 1;
-	return ipaddr_list_or_flush(argc, argv, 0);
+	return ipaddr_list_flush_or_save(argc, argv, IPADD_LIST);
 }
 
-void ipaddr_reset_filter(int oneline)
+void ipaddr_reset_filter(int oneline, int ifindex)
 {
 	memset(&filter, 0, sizeof(filter));
 	filter.oneline = oneline;
+	filter.ifindex = ifindex;
 }
 
 static int default_scope(inet_prefix *lcl)
@@ -1049,9 +1561,9 @@
 static int ipaddr_modify(int cmd, int flags, int argc, char **argv)
 {
 	struct {
-		struct nlmsghdr 	n;
-		struct ifaddrmsg 	ifa;
-		char   			buf[256];
+		struct nlmsghdr	n;
+		struct ifaddrmsg	ifa;
+		char			buf[256];
 	} req;
 	char  *d = NULL;
 	char  *l = NULL;
@@ -1068,6 +1580,7 @@
 	__u32 preferred_lft = INFINITY_LIFE_TIME;
 	__u32 valid_lft = INFINITY_LIFE_TIME;
 	struct ifa_cacheinfo cinfo;
+	unsigned int ifa_flags = 0;
 
 	memset(&req, 0, sizeof(req));
 
@@ -1120,7 +1633,7 @@
 			unsigned scope = 0;
 			NEXT_ARG();
 			if (rtnl_rtscope_a2n(&scope, *argv))
-				invarg(*argv, "invalid scope value.");
+				invarg("invalid scope value.", *argv);
 			req.ifa.ifa_scope = scope;
 			scoped = 1;
 		} else if (strcmp(*argv, "dev") == 0) {
@@ -1145,9 +1658,13 @@
 			if (set_lifetime(&preferred_lft, *argv))
 				invarg("preferred_lft value", *argv);
 		} else if (strcmp(*argv, "home") == 0) {
-			req.ifa.ifa_flags |= IFA_F_HOMEADDRESS;
+			ifa_flags |= IFA_F_HOMEADDRESS;
 		} else if (strcmp(*argv, "nodad") == 0) {
-			req.ifa.ifa_flags |= IFA_F_NODAD;
+			ifa_flags |= IFA_F_NODAD;
+		} else if (strcmp(*argv, "mngtmpaddr") == 0) {
+			ifa_flags |= IFA_F_MANAGETEMPADDR;
+		} else if (strcmp(*argv, "noprefixroute") == 0) {
+			ifa_flags |= IFA_F_NOPREFIXROUTE;
 		} else {
 			if (strcmp(*argv, "local") == 0) {
 				NEXT_ARG();
@@ -1165,6 +1682,11 @@
 		}
 		argc--; argv++;
 	}
+	if (ifa_flags <= 0xff)
+		req.ifa.ifa_flags = ifa_flags;
+	else
+		addattr32(&req.n, sizeof(req), IFA_FLAGS, ifa_flags);
+
 	if (d == NULL) {
 		fprintf(stderr, "Not enough information: \"dev\" argument is required.\n");
 		return -1;
@@ -1198,7 +1720,7 @@
 		}
 		brd = peer;
 		if (brd.bitlen <= 30) {
-			for (i=31; i>=brd.bitlen; i--) {
+			for (i = 31; i >= brd.bitlen; i--) {
 				if (brd_len == -1)
 					brd.data[0] |= htonl(1<<(31-i));
 				else
@@ -1211,8 +1733,6 @@
 	if (!scoped && cmd != RTM_DELADDR)
 		req.ifa.ifa_scope = default_scope(&lcl);
 
-	ll_init_map(&rth);
-
 	if ((req.ifa.ifa_index = ll_name_to_index(d)) == 0) {
 		fprintf(stderr, "Cannot find device \"%s\"\n", d);
 		return -1;
@@ -1244,7 +1764,7 @@
 int do_ipaddr(int argc, char **argv)
 {
 	if (argc < 1)
-		return ipaddr_list_or_flush(0, NULL, 0);
+		return ipaddr_list_flush_or_save(0, NULL, IPADD_LIST);
 	if (matches(*argv, "add") == 0)
 		return ipaddr_modify(RTM_NEWADDR, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
 	if (matches(*argv, "change") == 0 ||
@@ -1256,12 +1776,17 @@
 		return ipaddr_modify(RTM_DELADDR, 0, argc-1, argv+1);
 	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
 	    || matches(*argv, "lst") == 0)
-		return ipaddr_list_or_flush(argc-1, argv+1, 0);
+		return ipaddr_list_flush_or_save(argc-1, argv+1, IPADD_LIST);
 	if (matches(*argv, "flush") == 0)
-		return ipaddr_list_or_flush(argc-1, argv+1, 1);
+		return ipaddr_list_flush_or_save(argc-1, argv+1, IPADD_FLUSH);
+	if (matches(*argv, "save") == 0)
+		return ipaddr_list_flush_or_save(argc-1, argv+1, IPADD_SAVE);
+	if (matches(*argv, "showdump") == 0)
+		return ipaddr_showdump();
+	if (matches(*argv, "restore") == 0)
+		return ipaddr_restore();
 	if (matches(*argv, "help") == 0)
 		usage();
 	fprintf(stderr, "Command \"%s\" is unknown, try \"ip addr help\".\n", *argv);
 	exit(-1);
 }
-
diff --git a/ip/ipaddrlabel.c b/ip/ipaddrlabel.c
index eb6a48c..f6a638b 100644
--- a/ip/ipaddrlabel.c
+++ b/ip/ipaddrlabel.c
@@ -14,8 +14,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  *
  *
  * Based on iprule.c.
@@ -86,10 +85,10 @@
 	if (ifal->ifal_index)
 		fprintf(fp, "dev %s ", ll_index_to_name(ifal->ifal_index));
 
-	if (tb[IFAL_LABEL] && RTA_PAYLOAD(tb[IFAL_LABEL]) == sizeof(int32_t)) {
-		int32_t label;
+	if (tb[IFAL_LABEL] && RTA_PAYLOAD(tb[IFAL_LABEL]) == sizeof(uint32_t)) {
+		uint32_t label;
 		memcpy(&label, RTA_DATA(tb[IFAL_LABEL]), sizeof(label));
-		fprintf(fp, "label %d ", label);
+		fprintf(fp, "label %u ", label);
 	}
 
 	fprintf(fp, "\n");
@@ -126,15 +125,15 @@
 static int ipaddrlabel_modify(int cmd, int argc, char **argv)
 {
 	struct {
-		struct nlmsghdr 	n;
+		struct nlmsghdr	n;
 		struct ifaddrlblmsg	ifal;
-		char   			buf[1024];
+		char  			buf[1024];
 	} req;
 
 	inet_prefix prefix;
 	uint32_t label = 0xffffffffUL;
 	char *p = NULL;
-	char *l = NULL;        
+	char *l = NULL;
 
 	memset(&req, 0, sizeof(req));
 	memset(&prefix, 0, sizeof(prefix));
@@ -246,11 +245,10 @@
 
 int do_ipaddrlabel(int argc, char **argv)
 {
-	ll_init_map(&rth);
-
 	if (argc < 1) {
 		return ipaddrlabel_list(0, NULL);
 	} else if (matches(argv[0], "list") == 0 ||
+		   matches(argv[0], "lst") == 0 ||
 		   matches(argv[0], "show") == 0) {
 		return ipaddrlabel_list(argc-1, argv+1);
 	} else if (matches(argv[0], "add") == 0) {
diff --git a/ip/ipfou.c b/ip/ipfou.c
new file mode 100644
index 0000000..2676045
--- /dev/null
+++ b/ip/ipfou.c
@@ -0,0 +1,159 @@
+/*
+ * ipfou.c	FOU (foo over UDP) support
+ *
+ *              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.
+ *
+ * Authors:	Tom Herbert <therbert@google.com>
+ */
+
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/fou.h>
+#include <linux/genetlink.h>
+#include <linux/ip.h>
+#include <arpa/inet.h>
+
+#include "libgenl.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ip fou add port PORT { ipproto PROTO  | gue }\n");
+	fprintf(stderr, "       ip fou del port PORT\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, "Where: PROTO { ipproto-name | 1..255 }\n");
+	fprintf(stderr, "       PORT { 1..65535 }\n");
+
+	exit(-1);
+}
+
+/* netlink socket */
+static struct rtnl_handle genl_rth = { .fd = -1 };
+static int genl_family = -1;
+
+#define FOU_REQUEST(_req, _bufsiz, _cmd, _flags)	\
+	GENL_REQUEST(_req, _bufsiz, genl_family, 0,	\
+		     FOU_GENL_VERSION, _cmd, _flags)
+
+static int fou_parse_opt(int argc, char **argv, struct nlmsghdr *n,
+			 bool adding)
+{
+	__u16 port;
+	int port_set = 0;
+	__u8 ipproto, type;
+	bool gue_set = false;
+	int ipproto_set = 0;
+
+	while (argc > 0) {
+		if (!matches(*argv, "port")) {
+			NEXT_ARG();
+
+			if (get_u16(&port, *argv, 0) || port == 0)
+				invarg("invalid port", *argv);
+			port = htons(port);
+			port_set = 1;
+		} else if (!matches(*argv, "ipproto")) {
+			struct protoent *servptr;
+
+			NEXT_ARG();
+
+			servptr = getprotobyname(*argv);
+			if (servptr)
+				ipproto = servptr->p_proto;
+			else if (get_u8(&ipproto, *argv, 0) || ipproto == 0)
+				invarg("invalid ipproto", *argv);
+			ipproto_set = 1;
+		} else if (!matches(*argv, "gue")) {
+			gue_set = true;
+		} else {
+			fprintf(stderr, "fou: unknown command \"%s\"?\n", *argv);
+			usage();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (!port_set) {
+		fprintf(stderr, "fou: missing port\n");
+		return -1;
+	}
+
+	if (!ipproto_set && !gue_set && adding) {
+		fprintf(stderr, "fou: must set ipproto or gue\n");
+		return -1;
+	}
+
+	if (ipproto_set && gue_set) {
+		fprintf(stderr, "fou: cannot set ipproto and gue\n");
+		return -1;
+	}
+
+	type = gue_set ? FOU_ENCAP_GUE : FOU_ENCAP_DIRECT;
+
+	addattr16(n, 1024, FOU_ATTR_PORT, port);
+	addattr8(n, 1024, FOU_ATTR_TYPE, type);
+
+	if (ipproto_set)
+		addattr8(n, 1024, FOU_ATTR_IPPROTO, ipproto);
+
+	return 0;
+}
+
+static int do_add(int argc, char **argv)
+{
+	FOU_REQUEST(req, 1024, FOU_CMD_ADD, NLM_F_REQUEST);
+
+	fou_parse_opt(argc, argv, &req.n, true);
+
+	if (rtnl_talk(&genl_rth, &req.n, 0, 0, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static int do_del(int argc, char **argv)
+{
+	FOU_REQUEST(req, 1024, FOU_CMD_DEL, NLM_F_REQUEST);
+
+	fou_parse_opt(argc, argv, &req.n, false);
+
+	if (rtnl_talk(&genl_rth, &req.n, 0, 0, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+int do_ipfou(int argc, char **argv)
+{
+	if (genl_family < 0) {
+		if (rtnl_open_byproto(&genl_rth, 0, NETLINK_GENERIC) < 0) {
+			fprintf(stderr, "Cannot open generic netlink socket\n");
+			exit(1);
+		}
+
+		genl_family = genl_resolve_family(&genl_rth, FOU_GENL_NAME);
+		if (genl_family < 0)
+			exit(1);
+	}
+
+	if (argc < 1)
+		usage();
+
+	if (matches(*argv, "add") == 0)
+		return do_add(argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return do_del(argc-1, argv+1);
+	if (matches(*argv, "help") == 0)
+		usage();
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip fou help\".\n", *argv);
+	exit(-1);
+}
+
diff --git a/ip/ipl2tp.c b/ip/ipl2tp.c
index c5683f5..5cd8632 100644
--- a/ip/ipl2tp.c
+++ b/ip/ipl2tp.c
@@ -25,6 +25,7 @@
 
 #include <linux/genetlink.h>
 #include <linux/l2tp.h>
+#include "libgenl.h"
 
 #include "utils.h"
 #include "ip_common.h"
@@ -50,8 +51,8 @@
 	uint8_t cookie[8];
 	int peer_cookie_len;
 	uint8_t peer_cookie[8];
-	struct in_addr local_ip;
-	struct in_addr peer_ip;
+	inet_prefix local_ip;
+	inet_prefix peer_ip;
 
 	uint16_t pw_type;
 	uint16_t mtu;
@@ -64,6 +65,8 @@
 	int session:1;
 	int reorder_timeout;
 	const char *ifname;
+	uint8_t l2spec_type;
+	uint8_t l2spec_len;
 };
 
 struct l2tp_stats {
@@ -92,26 +95,25 @@
 
 static int create_tunnel(struct l2tp_parm *p)
 {
-	struct {
-		struct nlmsghdr 	n;
-		struct genlmsghdr	g;
-		char   			buf[1024];
-	} req;
+	uint32_t local_attr = L2TP_ATTR_IP_SADDR;
+	uint32_t peer_attr = L2TP_ATTR_IP_DADDR;
 
-	memset(&req, 0, sizeof(req));
-	req.n.nlmsg_type = genl_family;
-	req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
-	req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
-	req.g.cmd = L2TP_CMD_TUNNEL_CREATE;
-	req.g.version = L2TP_GENL_VERSION;
+	GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_TUNNEL_CREATE, NLM_F_REQUEST | NLM_F_ACK);
 
 	addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id);
 	addattr32(&req.n, 1024, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id);
 	addattr8(&req.n, 1024, L2TP_ATTR_PROTO_VERSION, 3);
 	addattr16(&req.n, 1024, L2TP_ATTR_ENCAP_TYPE, p->encap);
 
-	addattr32(&req.n, 1024, L2TP_ATTR_IP_SADDR, p->local_ip.s_addr);
-	addattr32(&req.n, 1024, L2TP_ATTR_IP_DADDR, p->peer_ip.s_addr);
+	if (p->local_ip.family == AF_INET6)
+		local_attr = L2TP_ATTR_IP6_SADDR;
+	addattr_l(&req.n, 1024, local_attr, &p->local_ip.data, p->local_ip.bytelen);
+
+	if (p->peer_ip.family == AF_INET6)
+		peer_attr = L2TP_ATTR_IP6_DADDR;
+	addattr_l(&req.n, 1024, peer_attr, &p->peer_ip.data, p->peer_ip.bytelen);
+
 	if (p->encap == L2TP_ENCAPTYPE_UDP) {
 		addattr16(&req.n, 1024, L2TP_ATTR_UDP_SPORT, p->local_udp_port);
 		addattr16(&req.n, 1024, L2TP_ATTR_UDP_DPORT, p->peer_udp_port);
@@ -125,18 +127,8 @@
 
 static int delete_tunnel(struct l2tp_parm *p)
 {
-	struct {
-		struct nlmsghdr 	n;
-		struct genlmsghdr	g;
-		char   			buf[128];
-	} req;
-
-	memset(&req, 0, sizeof(req));
-	req.n.nlmsg_type = genl_family;
-	req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
-	req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
-	req.g.cmd = L2TP_CMD_TUNNEL_DELETE;
-	req.g.version = L2TP_GENL_VERSION;
+	GENL_REQUEST(req, 128, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_TUNNEL_DELETE, NLM_F_REQUEST | NLM_F_ACK);
 
 	addattr32(&req.n, 128, L2TP_ATTR_CONN_ID, p->tunnel_id);
 
@@ -148,24 +140,16 @@
 
 static int create_session(struct l2tp_parm *p)
 {
-	struct {
-		struct nlmsghdr 	n;
-		struct genlmsghdr	g;
-		char   			buf[1024];
-	} req;
-
-	memset(&req, 0, sizeof(req));
-	req.n.nlmsg_type = genl_family;
-	req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
-	req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
-	req.g.cmd = L2TP_CMD_SESSION_CREATE;
-	req.g.version = L2TP_GENL_VERSION;
+	GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_SESSION_CREATE, NLM_F_REQUEST | NLM_F_ACK);
 
 	addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id);
 	addattr32(&req.n, 1024, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id);
 	addattr32(&req.n, 1024, L2TP_ATTR_SESSION_ID, p->session_id);
 	addattr32(&req.n, 1024, L2TP_ATTR_PEER_SESSION_ID, p->peer_session_id);
 	addattr16(&req.n, 1024, L2TP_ATTR_PW_TYPE, p->pw_type);
+	addattr8(&req.n, 1024, L2TP_ATTR_L2SPEC_TYPE, p->l2spec_type);
+	addattr8(&req.n, 1024, L2TP_ATTR_L2SPEC_LEN, p->l2spec_len);
 
 	if (p->mtu)		addattr16(&req.n, 1024, L2TP_ATTR_MTU, p->mtu);
 	if (p->recv_seq)	addattr(&req.n, 1024, L2TP_ATTR_RECV_SEQ);
@@ -190,18 +174,8 @@
 
 static int delete_session(struct l2tp_parm *p)
 {
-	struct {
-		struct nlmsghdr 	n;
-		struct genlmsghdr	g;
-		char   			buf[128];
-	} req;
-
-	memset(&req, 0, sizeof(req));
-	req.n.nlmsg_type = genl_family;
-	req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
-	req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
-	req.g.cmd = L2TP_CMD_SESSION_DELETE;
-	req.g.version = L2TP_GENL_VERSION;
+	GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_SESSION_DELETE, NLM_F_REQUEST | NLM_F_ACK);
 
 	addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id);
 	addattr32(&req.n, 1024, L2TP_ATTR_SESSION_ID, p->session_id);
@@ -225,13 +199,14 @@
 static void print_tunnel(const struct l2tp_data *data)
 {
 	const struct l2tp_parm *p = &data->config;
+	char buf[INET6_ADDRSTRLEN];
 
 	printf("Tunnel %u, encap %s\n",
 	       p->tunnel_id,
 	       p->encap == L2TP_ENCAPTYPE_UDP ? "UDP" :
 	       p->encap == L2TP_ENCAPTYPE_IP ? "IP" : "??");
-	printf("  From %s ", inet_ntoa(p->local_ip));
-	printf("to %s\n", inet_ntoa(p->peer_ip));
+	printf("  From %s ", inet_ntop(p->local_ip.family, p->local_ip.data, buf, sizeof(buf)));
+	printf("to %s\n", inet_ntop(p->peer_ip.family, p->peer_ip.data, buf, sizeof(buf)));
 	printf("  Peer tunnel %u\n",
 	       p->peer_tunnel_id);
 
@@ -300,6 +275,10 @@
 		p->session_id = rta_getattr_u32(attrs[L2TP_ATTR_SESSION_ID]);
 	if (attrs[L2TP_ATTR_PEER_SESSION_ID])
 		p->peer_session_id = rta_getattr_u32(attrs[L2TP_ATTR_PEER_SESSION_ID]);
+	if (attrs[L2TP_ATTR_L2SPEC_TYPE])
+		p->l2spec_type = rta_getattr_u8(attrs[L2TP_ATTR_L2SPEC_TYPE]);
+	if (attrs[L2TP_ATTR_L2SPEC_LEN])
+		p->l2spec_len = rta_getattr_u8(attrs[L2TP_ATTR_L2SPEC_LEN]);
 
 	p->udp_csum = !!attrs[L2TP_ATTR_UDP_CSUM];
 	if (attrs[L2TP_ATTR_COOKIE])
@@ -315,10 +294,30 @@
 
 	if (attrs[L2TP_ATTR_RECV_TIMEOUT])
 		p->reorder_timeout = rta_getattr_u64(attrs[L2TP_ATTR_RECV_TIMEOUT]);
-	if (attrs[L2TP_ATTR_IP_SADDR])
-		p->local_ip.s_addr = rta_getattr_u32(attrs[L2TP_ATTR_IP_SADDR]);
-	if (attrs[L2TP_ATTR_IP_DADDR])
-		p->peer_ip.s_addr = rta_getattr_u32(attrs[L2TP_ATTR_IP_DADDR]);
+	if (attrs[L2TP_ATTR_IP_SADDR]) {
+		p->local_ip.family = AF_INET;
+		p->local_ip.data[0] = rta_getattr_u32(attrs[L2TP_ATTR_IP_SADDR]);
+		p->local_ip.bytelen = 4;
+		p->local_ip.bitlen = -1;
+	}
+	if (attrs[L2TP_ATTR_IP_DADDR]) {
+		p->peer_ip.family = AF_INET;
+		p->peer_ip.data[0] = rta_getattr_u32(attrs[L2TP_ATTR_IP_DADDR]);
+		p->peer_ip.bytelen = 4;
+		p->peer_ip.bitlen = -1;
+	}
+	if (attrs[L2TP_ATTR_IP6_SADDR]) {
+		p->local_ip.family = AF_INET6;
+		memcpy(&p->local_ip.data, RTA_DATA(attrs[L2TP_ATTR_IP6_SADDR]),
+			p->local_ip.bytelen = 16);
+		p->local_ip.bitlen = -1;
+	}
+	if (attrs[L2TP_ATTR_IP6_DADDR]) {
+		p->peer_ip.family = AF_INET6;
+		memcpy(&p->peer_ip.data, RTA_DATA(attrs[L2TP_ATTR_IP6_DADDR]),
+			p->peer_ip.bytelen = 16);
+		p->peer_ip.bitlen = -1;
+	}
 	if (attrs[L2TP_ATTR_UDP_SPORT])
 		p->local_udp_port = rta_getattr_u16(attrs[L2TP_ATTR_UDP_SPORT]);
 	if (attrs[L2TP_ATTR_UDP_DPORT])
@@ -367,21 +366,12 @@
 
 static int get_session(struct l2tp_data *p)
 {
-	struct {
-		struct nlmsghdr 	n;
-		struct genlmsghdr	g;
-		char buf[128];
-	} req;
+	GENL_REQUEST(req, 128, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_SESSION_GET,
+		     NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST);
 
-	memset(&req, 0, sizeof(req));
-	req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
-	req.n.nlmsg_type = genl_family;
-	req.n.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
 	req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq;
 
-	req.g.cmd = L2TP_CMD_SESSION_GET;
-	req.g.version = L2TP_GENL_VERSION;
-
 	if (p->config.tunnel_id && p->config.session_id) {
 		addattr32(&req.n, 128, L2TP_ATTR_CONN_ID, p->config.tunnel_id);
 		addattr32(&req.n, 128, L2TP_ATTR_SESSION_ID, p->config.session_id);
@@ -410,21 +400,12 @@
 
 static int get_tunnel(struct l2tp_data *p)
 {
-	struct {
-		struct nlmsghdr 	n;
-		struct genlmsghdr	g;
-		char buf[1024];
-	} req;
+	GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_TUNNEL_GET,
+		     NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST);
 
-	memset(&req, 0, sizeof(req));
-	req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
-	req.n.nlmsg_type = genl_family;
-	req.n.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
 	req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq;
 
-	req.g.cmd = L2TP_CMD_TUNNEL_GET;
-	req.g.version = L2TP_GENL_VERSION;
-
 	if (p->config.tunnel_id)
 		addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->config.tunnel_id);
 
@@ -493,6 +474,7 @@
 	fprintf(stderr, "          session_id ID peer_session_id ID\n");
 	fprintf(stderr, "          [ cookie HEXSTR ] [ peer_cookie HEXSTR ]\n");
 	fprintf(stderr, "          [ offset OFFSET ] [ peer_offset OFFSET ]\n");
+	fprintf(stderr, "          [ l2spec_type L2SPEC ]\n");
 	fprintf(stderr, "       ip l2tp del tunnel tunnel_id ID\n");
 	fprintf(stderr, "       ip l2tp del session tunnel_id ID session_id ID\n");
 	fprintf(stderr, "       ip l2tp show tunnel [ tunnel_id ID ]\n");
@@ -503,6 +485,7 @@
 	fprintf(stderr, "       PORT   := { 0..65535 }\n");
 	fprintf(stderr, "       ID     := { 1..4294967295 }\n");
 	fprintf(stderr, "       HEXSTR := { 8 or 16 hex digits (4 / 8 bytes) }\n");
+	fprintf(stderr, "       L2SPEC := { none | default }\n");
 	exit(-1);
 }
 
@@ -513,6 +496,10 @@
 	if (argc == 0)
 		usage();
 
+	/* Defaults */
+	p->l2spec_type = L2TP_L2SPECTYPE_DEFAULT;
+	p->l2spec_len = 4;
+
 	while (argc > 0) {
 		if (strcmp(*argv, "encap") == 0) {
 			NEXT_ARG();
@@ -521,7 +508,7 @@
 			} else if (strcmp(*argv, "udp") == 0) {
 				p->encap = L2TP_ENCAPTYPE_UDP;
 			} else {
-				fprintf(stderr, "Unknown tunnel encapsulation.\n");
+				fprintf(stderr, "Unknown tunnel encapsulation \"%s\"\n", *argv);
 				exit(-1);
 			}
 		} else if (strcmp(*argv, "name") == 0) {
@@ -529,10 +516,12 @@
 			p->ifname = *argv;
 		} else if (strcmp(*argv, "remote") == 0) {
 			NEXT_ARG();
-			p->peer_ip.s_addr = get_addr32(*argv);
+			if (get_addr(&p->peer_ip, *argv, AF_UNSPEC))
+				invarg("invalid remote address\n", *argv);
 		} else if (strcmp(*argv, "local") == 0) {
 			NEXT_ARG();
-			p->local_ip.s_addr = get_addr32(*argv);
+			if (get_addr(&p->local_ip, *argv, AF_UNSPEC))
+				invarg("invalid local address\n", *argv);
 		} else if ((strcmp(*argv, "tunnel_id") == 0) ||
 			   (strcmp(*argv, "tid") == 0)) {
 			__u32 uval;
@@ -605,6 +594,18 @@
 			p->peer_cookie_len = slen / 2;
 			if (hex2mem(*argv, p->peer_cookie, p->peer_cookie_len) < 0)
 				invarg("cookie must be a hex string\n", *argv);
+		} else if (strcmp(*argv, "l2spec_type") == 0) {
+			NEXT_ARG();
+			if (strcasecmp(*argv, "default") == 0) {
+				p->l2spec_type = L2TP_L2SPECTYPE_DEFAULT;
+				p->l2spec_len = 4;
+			} else if (strcasecmp(*argv, "none") == 0) {
+				p->l2spec_type = L2TP_L2SPECTYPE_NONE;
+				p->l2spec_len = 0;
+			} else {
+				fprintf(stderr, "Unknown layer2specific header type \"%s\"\n", *argv);
+				exit(-1);
+			}
 		} else if (strcmp(*argv, "tunnel") == 0) {
 			p->tunnel = 1;
 		} else if (strcmp(*argv, "session") == 0) {
@@ -648,10 +649,10 @@
 		missarg("peer_tunnel_id");
 
 	if (p.tunnel) {
-		if (p.local_ip.s_addr == 0)
+		if (p.local_ip.family == AF_UNSPEC)
 			missarg("local");
 
-		if (p.peer_ip.s_addr == 0)
+		if (p.peer_ip.family == AF_UNSPEC)
 			missarg("remote");
 
 		if (p.encap == L2TP_ENCAPTYPE_UDP) {
@@ -716,67 +717,6 @@
 	return 0;
 }
 
-static int genl_parse_getfamily(struct nlmsghdr *nlh)
-{
-	struct rtattr *tb[CTRL_ATTR_MAX + 1];
-	struct genlmsghdr *ghdr = NLMSG_DATA(nlh);
-	int len = nlh->nlmsg_len;
-	struct rtattr *attrs;
-
-	if (nlh->nlmsg_type != GENL_ID_CTRL) {
-		fprintf(stderr, "Not a controller message, nlmsg_len=%d "
-			"nlmsg_type=0x%x\n", nlh->nlmsg_len, nlh->nlmsg_type);
-		return -1;
-	}
-
-	if (ghdr->cmd != CTRL_CMD_NEWFAMILY) {
-		fprintf(stderr, "Unknown controller command %d\n", ghdr->cmd);
-		return -1;
-	}
-
-	len -= NLMSG_LENGTH(GENL_HDRLEN);
-
-	if (len < 0) {
-		fprintf(stderr, "wrong controller message len %d\n", len);
-		return -1;
-	}
-
-	attrs = (struct rtattr *) ((char *) ghdr + GENL_HDRLEN);
-	parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len);
-
-	if (tb[CTRL_ATTR_FAMILY_ID] == NULL) {
-		fprintf(stderr, "Missing family id TLV\n");
-		return -1;
-	}
-
-	return rta_getattr_u16(tb[CTRL_ATTR_FAMILY_ID]);
-}
-
-int genl_ctrl_resolve_family(const char *family)
-{
-	struct {
-		struct nlmsghdr         n;
-		struct genlmsghdr	g;
-		char                    buf[1024];
-	} req;
-
-	memset(&req, 0, sizeof(req));
-	req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
-	req.n.nlmsg_flags = NLM_F_REQUEST;
-	req.n.nlmsg_type = GENL_ID_CTRL;
-	req.g.cmd = CTRL_CMD_GETFAMILY;
-
-	addattr_l(&req.n, 1024, CTRL_ATTR_FAMILY_NAME,
-		  family, strlen(family) + 1);
-
-	if (rtnl_talk(&genl_rth, &req.n, 0, 0, &req.n) < 0) {
-		fprintf(stderr, "Error talking to the kernel\n");
-		return -2;
-	}
-
-	return genl_parse_getfamily(&req.n);
-}
-
 int do_ipl2tp(int argc, char **argv)
 {
 	if (genl_family < 0) {
@@ -785,7 +725,7 @@
 			exit(1);
 		}
 
-		genl_family = genl_ctrl_resolve_family(L2TP_GENL_NAME);
+		genl_family = genl_resolve_family(&genl_rth, L2TP_GENL_NAME);
 		if (genl_family < 0)
 			exit(1);
 	}
@@ -795,7 +735,7 @@
 
 	if (matches(*argv, "add") == 0)
 		return do_add(argc-1, argv+1);
-	if (matches(*argv, "del") == 0)
+	if (matches(*argv, "delete") == 0)
 		return do_del(argc-1, argv+1);
 	if (matches(*argv, "show") == 0 ||
 	    matches(*argv, "lst") == 0 ||
diff --git a/ip/iplink.c b/ip/iplink.c
index 679091e..e6f30e9 100644
--- a/ip/iplink.c
+++ b/ip/iplink.c
@@ -27,10 +27,12 @@
 #include <string.h>
 #include <sys/ioctl.h>
 #include <linux/sockios.h>
+#include <stdbool.h>
 
 #include "rt_names.h"
 #include "utils.h"
 #include "ip_common.h"
+#include "namespace.h"
 
 #define IPLINK_IOCTL_COMPAT	1
 #ifndef LIBDIR
@@ -47,7 +49,9 @@
 		fprintf(stderr, "                   [ txqueuelen PACKETS ]\n");
 		fprintf(stderr, "                   [ address LLADDR ]\n");
 		fprintf(stderr, "                   [ broadcast LLADDR ]\n");
-		fprintf(stderr, "                   [ mtu MTU ]\n");
+		fprintf(stderr, "                   [ mtu MTU ] [index IDX ]\n");
+		fprintf(stderr, "                   [ numtxqueues QUEUE_COUNT ]\n");
+		fprintf(stderr, "                   [ numrxqueues QUEUE_COUNT ]\n");
 		fprintf(stderr, "                   type TYPE [ ARGS ]\n");
 		fprintf(stderr, "       ip link delete DEV type TYPE [ ARGS ]\n");
 		fprintf(stderr, "\n");
@@ -68,6 +72,7 @@
 	fprintf(stderr, "	                  [ mtu MTU ]\n");
 	fprintf(stderr, "	                  [ netns PID ]\n");
 	fprintf(stderr, "	                  [ netns NAME ]\n");
+	fprintf(stderr, "	                  [ link-netnsid ID ]\n");
 	fprintf(stderr, "			  [ alias NAME ]\n");
 	fprintf(stderr, "	                  [ vf NUM [ mac LLADDR ]\n");
 	fprintf(stderr, "				   [ vlan VLANID [ qos VLAN-QOS ] ]\n");
@@ -75,13 +80,19 @@
 	fprintf(stderr, "				   [ rate TXRATE ] ] \n");
 
 	fprintf(stderr, "				   [ spoofchk { on | off} ] ] \n");
+	fprintf(stderr, "				   [ state { auto | enable | disable} ] ]\n");
 	fprintf(stderr, "			  [ master DEVICE ]\n");
 	fprintf(stderr, "			  [ nomaster ]\n");
-	fprintf(stderr, "       ip link show [ DEVICE | group GROUP ]\n");
+	fprintf(stderr, "			  [ addrgenmode { eui64 | none } ]\n");
+	fprintf(stderr, "       ip link show [ DEVICE | group GROUP ] [up] [master DEV] [type TYPE]\n");
 
 	if (iplink_have_newlink()) {
+		fprintf(stderr, "       ip link help [ TYPE ]\n");
 		fprintf(stderr, "\n");
-		fprintf(stderr, "TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | can | bridge }\n");
+		fprintf(stderr, "TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | macvtap |\n");
+		fprintf(stderr, "          bridge | bond | ipoib | ip6tnl | ipip | sit | vxlan |\n");
+		fprintf(stderr, "          gre | gretap | ip6gre | ip6gretap | vti | nlmon |\n");
+		fprintf(stderr, "          bond_slave | ipvlan }\n");
 	}
 	exit(-1);
 }
@@ -91,23 +102,24 @@
 	iplink_usage();
 }
 
-static int on_off(char *msg)
+static int on_off(const char *msg, const char *realval)
 {
-	fprintf(stderr, "Error: argument of \"%s\" must be \"on\" or \"off\"\n", msg);
+	fprintf(stderr, "Error: argument of \"%s\" must be \"on\" or \"off\", not \"%s\"\n", msg, realval);
 	return -1;
 }
 
 static void *BODY;		/* cached dlopen(NULL) handle */
 static struct link_util *linkutil_list;
 
-struct link_util *get_link_kind(const char *id)
+static struct link_util *__get_link_kind(const char *id, bool slave)
 {
 	void *dlh;
 	char buf[256];
 	struct link_util *l;
 
 	for (l = linkutil_list; l; l = l->next)
-		if (strcmp(l->id, id) == 0)
+		if (strcmp(l->id, id) == 0 &&
+		    l->slave == slave)
 			return l;
 
 	snprintf(buf, sizeof(buf), LIBDIR "/ip/link_%s.so", id);
@@ -122,7 +134,10 @@
 		}
 	}
 
-	snprintf(buf, sizeof(buf), "%s_link_util", id);
+	if (slave)
+		snprintf(buf, sizeof(buf), "%s_slave_link_util", id);
+	else
+		snprintf(buf, sizeof(buf), "%s_link_util", id);
 	l = dlsym(dlh, buf);
 	if (l == NULL)
 		return NULL;
@@ -132,7 +147,17 @@
 	return l;
 }
 
-int get_link_mode(const char *mode)
+struct link_util *get_link_kind(const char *id)
+{
+	return __get_link_kind(id, false);
+}
+
+struct link_util *get_link_slave_kind(const char *id)
+{
+	return __get_link_kind(id, true);
+}
+
+static int get_link_mode(const char *mode)
 {
 	if (strcasecmp(mode, "default") == 0)
 		return IF_LINK_MODE_DEFAULT;
@@ -141,6 +166,15 @@
 	return -1;
 }
 
+static int get_addr_gen_mode(const char *mode)
+{
+	if (strcasecmp(mode, "eui64") == 0)
+		return IN6_ADDR_GEN_MODE_EUI64;
+	if (strcasecmp(mode, "none") == 0)
+		return IN6_ADDR_GEN_MODE_NONE;
+	return -1;
+}
+
 #if IPLINK_IOCTL_COMPAT
 static int have_rtnl_newlink = -1;
 
@@ -173,7 +207,10 @@
 		req.n.nlmsg_type = RTM_NEWLINK;
 		req.i.ifi_family = AF_UNSPEC;
 
-		rtnl_send(&rth, &req.n, req.n.nlmsg_len);
+		if (rtnl_send(&rth, &req.n, req.n.nlmsg_len) < 0) {
+			perror("request send failed");
+			exit(1);
+		}
 		rtnl_listen(&rth, accept_msg, NULL);
 	}
 	return have_rtnl_newlink;
@@ -191,17 +228,41 @@
 	char			buf[1024];
 };
 
-int iplink_parse_vf(int vf, int *argcp, char ***argvp,
-			   struct iplink_req *req)
+static int iplink_parse_vf(int vf, int *argcp, char ***argvp,
+			   struct iplink_req *req, int dev_index)
 {
+	char new_rate_api = 0, count = 0, override_legacy_rate = 0;
+	struct ifla_vf_rate tivt;
 	int len, argc = *argcp;
 	char **argv = *argvp;
 	struct rtattr *vfinfo;
 
+	tivt.min_tx_rate = -1;
+	tivt.max_tx_rate = -1;
+
 	vfinfo = addattr_nest(&req->n, sizeof(*req), IFLA_VF_INFO);
 
 	while (NEXT_ARG_OK()) {
 		NEXT_ARG();
+		count++;
+		if (!matches(*argv, "max_tx_rate")) {
+			/* new API in use */
+			new_rate_api = 1;
+			/* override legacy rate */
+			override_legacy_rate = 1;
+		} else if (!matches(*argv, "min_tx_rate")) {
+			/* new API in use */
+			new_rate_api = 1;
+		}
+	}
+
+	while (count--) {
+		/* rewind arg */
+		PREV_ARG();
+	}
+
+	while (NEXT_ARG_OK()) {
+		NEXT_ARG();
 		if (matches(*argv, "mac") == 0) {
 			struct ifla_vf_mac ivm;
 			NEXT_ARG();
@@ -238,8 +299,26 @@
 				invarg("Invalid \"rate\" value\n", *argv);
 			}
 			ivt.vf = vf;
-			addattr_l(&req->n, sizeof(*req), IFLA_VF_TX_RATE, &ivt, sizeof(ivt));
-		
+			if (!new_rate_api)
+				addattr_l(&req->n, sizeof(*req),
+					  IFLA_VF_TX_RATE, &ivt, sizeof(ivt));
+			else if (!override_legacy_rate)
+				tivt.max_tx_rate = ivt.rate;
+
+		} else if (matches(*argv, "max_tx_rate") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&tivt.max_tx_rate, *argv, 0))
+				invarg("Invalid \"max tx rate\" value\n",
+				       *argv);
+			tivt.vf = vf;
+
+		} else if (matches(*argv, "min_tx_rate") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&tivt.min_tx_rate, *argv, 0))
+				invarg("Invalid \"min tx rate\" value\n",
+				       *argv);
+			tivt.vf = vf;
+
 		} else if (matches(*argv, "spoofchk") == 0) {
 			struct ifla_vf_spoofchk ivs;
 			NEXT_ARG();
@@ -252,6 +331,19 @@
 			ivs.vf = vf;
 			addattr_l(&req->n, sizeof(*req), IFLA_VF_SPOOFCHK, &ivs, sizeof(ivs));
 
+		} else if (matches(*argv, "state") == 0) {
+			struct ifla_vf_link_state ivl;
+			NEXT_ARG();
+			if (matches(*argv, "auto") == 0)
+				ivl.link_state = IFLA_VF_LINK_STATE_AUTO;
+			else if (matches(*argv, "enable") == 0)
+				ivl.link_state = IFLA_VF_LINK_STATE_ENABLE;
+			else if (matches(*argv, "disable") == 0)
+				ivl.link_state = IFLA_VF_LINK_STATE_DISABLE;
+			else
+				invarg("Invalid \"state\" value\n", *argv);
+			ivl.vf = vf;
+			addattr_l(&req->n, sizeof(*req), IFLA_VF_LINK_STATE, &ivl, sizeof(ivl));
 		} else {
 			/* rewind arg */
 			PREV_ARG();
@@ -259,6 +351,20 @@
 		}
 	}
 
+	if (new_rate_api) {
+		int tmin, tmax;
+
+		if (tivt.min_tx_rate == -1 || tivt.max_tx_rate == -1) {
+			ipaddr_get_vf_rate(tivt.vf, &tmin, &tmax, dev_index);
+			if (tivt.min_tx_rate == -1)
+				tivt.min_tx_rate = tmin;
+			if (tivt.max_tx_rate == -1)
+				tivt.max_tx_rate = tmax;
+		}
+		addattr_l(&req->n, sizeof(*req), IFLA_VF_RATE, &tivt,
+			  sizeof(tivt));
+	}
+
 	if (argc == *argcp)
 		incomplete_command();
 
@@ -269,9 +375,8 @@
 	return 0;
 }
 
-
 int iplink_parse(int argc, char **argv, struct iplink_req *req,
-		char **name, char **type, char **link, char **dev, int *group)
+		char **name, char **type, char **link, char **dev, int *group, int *index)
 {
 	int ret, len;
 	char abuf[32];
@@ -279,6 +384,10 @@
 	int mtu = -1;
 	int netns = -1;
 	int vf = -1;
+	int numtxqueues = -1;
+	int numrxqueues = -1;
+	int dev_index = 0;
+	int link_netnsid = -1;
 
 	*group = -1;
 	ret = argc;
@@ -293,6 +402,11 @@
 		} else if (strcmp(*argv, "name") == 0) {
 			NEXT_ARG();
 			*name = *argv;
+		} else if (strcmp(*argv, "index") == 0) {
+			NEXT_ARG();
+			*index = atoi(*argv);
+			if (*index < 0)
+				invarg("Invalid \"index\" value", *argv);
 		} else if (matches(*argv, "link") == 0) {
 			NEXT_ARG();
 			*link = *argv;
@@ -325,16 +439,16 @@
 			if (get_integer(&mtu, *argv, 0))
 				invarg("Invalid \"mtu\" value\n", *argv);
 			addattr_l(&req->n, sizeof(*req), IFLA_MTU, &mtu, 4);
-                } else if (strcmp(*argv, "netns") == 0) {
-                        NEXT_ARG();
-                        if (netns != -1)
-                                duparg("netns", *argv);
-			if ((netns = get_netns_fd(*argv)) >= 0)
+		} else if (strcmp(*argv, "netns") == 0) {
+			NEXT_ARG();
+			if (netns != -1)
+				duparg("netns", *argv);
+			if ((netns = netns_get_fd(*argv)) >= 0)
 				addattr_l(&req->n, sizeof(*req), IFLA_NET_NS_FD, &netns, 4);
 			else if (get_integer(&netns, *argv, 0) == 0)
 				addattr_l(&req->n, sizeof(*req), IFLA_NET_NS_PID, &netns, 4);
 			else
-                                invarg("Invalid \"netns\" value\n", *argv);
+				invarg("Invalid \"netns\" value\n", *argv);
 		} else if (strcmp(*argv, "multicast") == 0) {
 			NEXT_ARG();
 			req->i.ifi_change |= IFF_MULTICAST;
@@ -343,7 +457,7 @@
 			} else if (strcmp(*argv, "off") == 0) {
 				req->i.ifi_flags &= ~IFF_MULTICAST;
 			} else
-				return on_off("multicast");
+				return on_off("multicast", *argv);
 		} else if (strcmp(*argv, "allmulticast") == 0) {
 			NEXT_ARG();
 			req->i.ifi_change |= IFF_ALLMULTI;
@@ -352,7 +466,7 @@
 			} else if (strcmp(*argv, "off") == 0) {
 				req->i.ifi_flags &= ~IFF_ALLMULTI;
 			} else
-				return on_off("allmulticast");
+				return on_off("allmulticast", *argv);
 		} else if (strcmp(*argv, "promisc") == 0) {
 			NEXT_ARG();
 			req->i.ifi_change |= IFF_PROMISC;
@@ -361,7 +475,7 @@
 			} else if (strcmp(*argv, "off") == 0) {
 				req->i.ifi_flags &= ~IFF_PROMISC;
 			} else
-				return on_off("promisc");
+				return on_off("promisc", *argv);
 		} else if (strcmp(*argv, "trailers") == 0) {
 			NEXT_ARG();
 			req->i.ifi_change |= IFF_NOTRAILERS;
@@ -370,7 +484,7 @@
 			} else if (strcmp(*argv, "on") == 0) {
 				req->i.ifi_flags &= ~IFF_NOTRAILERS;
 			} else
-				return on_off("trailers");
+				return on_off("trailers", *argv);
 		} else if (strcmp(*argv, "arp") == 0) {
 			NEXT_ARG();
 			req->i.ifi_change |= IFF_NOARP;
@@ -379,7 +493,7 @@
 			} else if (strcmp(*argv, "off") == 0) {
 				req->i.ifi_flags |= IFF_NOARP;
 			} else
-				return on_off("noarp");
+				return on_off("noarp", *argv);
 		} else if (strcmp(*argv, "vf") == 0) {
 			struct rtattr *vflist;
 			NEXT_ARG();
@@ -388,7 +502,10 @@
 			}
 			vflist = addattr_nest(&req->n, sizeof(*req),
 					      IFLA_VFINFO_LIST);
-			len = iplink_parse_vf(vf, &argc, &argv, req);
+			if (dev_index == 0)
+				missarg("dev");
+
+			len = iplink_parse_vf(vf, &argc, &argv, req, dev_index);
 			if (len < 0)
 				return -1;
 			addattr_nest_end(&req->n, vflist);
@@ -412,7 +529,7 @@
 			} else if (strcmp(*argv, "off") == 0) {
 				req->i.ifi_flags &= ~IFF_DYNAMIC;
 			} else
-				return on_off("dynamic");
+				return on_off("dynamic", *argv);
 		} else if (matches(*argv, "type") == 0) {
 			NEXT_ARG();
 			*type = *argv;
@@ -445,6 +562,42 @@
 				invarg("Invalid operstate\n", *argv);
 
 			addattr8(&req->n, sizeof(*req), IFLA_OPERSTATE, state);
+		} else if (matches(*argv, "numtxqueues") == 0) {
+			NEXT_ARG();
+			if (numtxqueues != -1)
+				duparg("numtxqueues", *argv);
+			if (get_integer(&numtxqueues, *argv, 0))
+				invarg("Invalid \"numtxqueues\" value\n", *argv);
+			addattr_l(&req->n, sizeof(*req), IFLA_NUM_TX_QUEUES,
+				  &numtxqueues, 4);
+		} else if (matches(*argv, "numrxqueues") == 0) {
+			NEXT_ARG();
+			if (numrxqueues != -1)
+				duparg("numrxqueues", *argv);
+			if (get_integer(&numrxqueues, *argv, 0))
+				invarg("Invalid \"numrxqueues\" value\n", *argv);
+			addattr_l(&req->n, sizeof(*req), IFLA_NUM_RX_QUEUES,
+				  &numrxqueues, 4);
+		} else if (matches(*argv, "addrgenmode") == 0) {
+			struct rtattr *afs, *afs6;
+			int mode;
+			NEXT_ARG();
+			mode = get_addr_gen_mode(*argv);
+			if (mode < 0)
+				invarg("Invalid address generation mode\n", *argv);
+			afs = addattr_nest(&req->n, sizeof(*req), IFLA_AF_SPEC);
+			afs6 = addattr_nest(&req->n, sizeof(*req), AF_INET6);
+			addattr8(&req->n, sizeof(*req), IFLA_INET6_ADDR_GEN_MODE, mode);
+			addattr_nest_end(&req->n, afs6);
+			addattr_nest_end(&req->n, afs);
+		} else if (matches(*argv, "link-netnsid") == 0) {
+			NEXT_ARG();
+			if (link_netnsid != -1)
+				duparg("link-netnsid", *argv);
+			if (get_integer(&link_netnsid, *argv, 0))
+				invarg("Invalid \"link-netnsid\" value\n", *argv);
+			addattr32(&req->n, sizeof(*req), IFLA_LINK_NETNSID,
+				  link_netnsid);
 		} else {
 			if (strcmp(*argv, "dev") == 0) {
 				NEXT_ARG();
@@ -454,6 +607,7 @@
 			if (*dev)
 				duparg2("dev", *argv);
 			*dev = *argv;
+			dev_index = ll_name_to_index(*dev);
 		}
 		argc--; argv++;
 	}
@@ -468,6 +622,7 @@
 	char *name = NULL;
 	char *link = NULL;
 	char *type = NULL;
+	int index = -1;
 	int group;
 	struct link_util *lu = NULL;
 	struct iplink_req req;
@@ -480,7 +635,7 @@
 	req.n.nlmsg_type = cmd;
 	req.i.ifi_family = preferred_family;
 
-	ret = iplink_parse(argc, argv, &req, &name, &type, &link, &dev, &group);
+	ret = iplink_parse(argc, argv, &req, &name, &type, &link, &dev, &group, &index);
 	if (ret < 0)
 		return ret;
 
@@ -512,14 +667,17 @@
 		}
 	}
 
-	ll_init_map(&rth);
-
 	if (!(flags & NLM_F_CREATE)) {
 		if (!dev) {
 			fprintf(stderr, "Not enough information: \"dev\" "
 					"argument is required.\n");
 			exit(-1);
 		}
+		if (cmd == RTM_NEWLINK && index != -1) {
+			fprintf(stderr, "index can be used only when "
+					"creating devices.\n");
+			exit(-1);
+		}
 
 		req.i.ifi_index = ll_name_to_index(dev);
 		if (req.i.ifi_index == 0) {
@@ -542,6 +700,11 @@
 			}
 			addattr_l(&req.n, sizeof(req), IFLA_LINK, &ifindex, 4);
 		}
+
+		if (index == -1)
+			req.i.ifi_index = 0;
+		else
+			req.i.ifi_index = index;
 	}
 
 	if (name) {
@@ -554,21 +717,35 @@
 	}
 
 	if (type) {
-		struct rtattr *linkinfo = NLMSG_TAIL(&req.n);
-		addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
+		struct rtattr *linkinfo;
+		char slavebuf[128], *ulinep = strchr(type, '_');
+		int iflatype;
+
+		linkinfo = addattr_nest(&req.n, sizeof(req), IFLA_LINKINFO);
 		addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, type,
 			 strlen(type));
 
-		lu = get_link_kind(type);
+		if (ulinep && !strcmp(ulinep, "_slave")) {
+			strncpy(slavebuf, type, sizeof(slavebuf));
+			slavebuf[sizeof(slavebuf) - 1] = '\0';
+			ulinep = strchr(slavebuf, '_');
+			/* check in case it was after sizeof(slavebuf) - 1*/
+			if (ulinep)
+				*ulinep = '\0';
+			lu = get_link_slave_kind(slavebuf);
+			iflatype = IFLA_INFO_SLAVE_DATA;
+		} else {
+			lu = get_link_kind(type);
+			iflatype = IFLA_INFO_DATA;
+		}
 		if (lu && argc) {
-			struct rtattr * data = NLMSG_TAIL(&req.n);
-			addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0);
+			struct rtattr *data = addattr_nest(&req.n, sizeof(req), iflatype);
 
 			if (lu->parse_opt &&
 			    lu->parse_opt(lu, argc, argv, &req.n))
 				return -1;
 
-			data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data;
+			addattr_nest_end(&req.n, data);
 		} else if (argc) {
 			if (matches(*argv, "help") == 0)
 				usage();
@@ -576,7 +753,7 @@
 					"Try \"ip link help\".\n", *argv);
 			return -1;
 		}
-		linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo;
+		addattr_nest_end(&req.n, linkinfo);
 	} else if (flags & NLM_F_CREATE) {
 		fprintf(stderr, "Not enough information: \"type\" argument "
 				"is required\n");
@@ -589,6 +766,38 @@
 	return 0;
 }
 
+int iplink_get(unsigned int flags, char *name, __u32 filt_mask)
+{
+	int len;
+	struct iplink_req req;
+	char answer[16384];
+
+	memset(&req, 0, sizeof(req));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
+	req.n.nlmsg_type = RTM_GETLINK;
+	req.i.ifi_family = preferred_family;
+
+	if (name) {
+		len = strlen(name) + 1;
+		if (len == 1)
+			invarg("\"\" is not a valid device identifier\n",
+				   "name");
+		if (len > IFNAMSIZ)
+			invarg("\"name\" too long\n", name);
+		addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name, len);
+	}
+	addattr32(&req.n, sizeof(req), IFLA_EXT_MASK, filt_mask);
+
+	if (rtnl_talk(&rth, &req.n, 0, 0, (struct nlmsghdr *)answer) < 0)
+		return -2;
+
+	print_linkinfo(NULL, (struct nlmsghdr *)answer, stdout);
+
+	return 0;
+}
+
 #if IPLINK_IOCTL_COMPAT
 static int get_ctl_fd(void)
 {
@@ -778,7 +987,6 @@
 	return 0;
 }
 
-
 static int do_set(int argc, char **argv)
 {
 	char *dev = NULL;
@@ -831,7 +1039,7 @@
 			} else if (strcmp(*argv, "off") == 0) {
 				flags &= ~IFF_MULTICAST;
 			} else
-				return on_off("multicast");
+				return on_off("multicast", *argv);
 		} else if (strcmp(*argv, "allmulticast") == 0) {
 			NEXT_ARG();
 			mask |= IFF_ALLMULTI;
@@ -840,7 +1048,7 @@
 			} else if (strcmp(*argv, "off") == 0) {
 				flags &= ~IFF_ALLMULTI;
 			} else
-				return on_off("allmulticast");
+				return on_off("allmulticast", *argv);
 		} else if (strcmp(*argv, "promisc") == 0) {
 			NEXT_ARG();
 			mask |= IFF_PROMISC;
@@ -849,7 +1057,7 @@
 			} else if (strcmp(*argv, "off") == 0) {
 				flags &= ~IFF_PROMISC;
 			} else
-				return on_off("promisc");
+				return on_off("promisc", *argv);
 		} else if (strcmp(*argv, "trailers") == 0) {
 			NEXT_ARG();
 			mask |= IFF_NOTRAILERS;
@@ -858,7 +1066,7 @@
 			} else if (strcmp(*argv, "on") == 0) {
 				flags &= ~IFF_NOTRAILERS;
 			} else
-				return on_off("trailers");
+				return on_off("trailers", *argv);
 		} else if (strcmp(*argv, "arp") == 0) {
 			NEXT_ARG();
 			mask |= IFF_NOARP;
@@ -867,7 +1075,7 @@
 			} else if (strcmp(*argv, "off") == 0) {
 				flags |= IFF_NOARP;
 			} else
-				return on_off("noarp");
+				return on_off("noarp", *argv);
 		} else if (matches(*argv, "dynamic") == 0) {
 			NEXT_ARG();
 			mask |= IFF_DYNAMIC;
@@ -876,9 +1084,9 @@
 			} else if (strcmp(*argv, "off") == 0) {
 				flags &= ~IFF_DYNAMIC;
 			} else
-				return on_off("dynamic");
+				return on_off("dynamic", *argv);
 		} else {
-                        if (strcmp(*argv, "dev") == 0) {
+			if (strcmp(*argv, "dev") == 0) {
 				NEXT_ARG();
 			}
 			if (matches(*argv, "help") == 0)
@@ -940,6 +1148,23 @@
 }
 #endif /* IPLINK_IOCTL_COMPAT */
 
+static void do_help(int argc, char **argv)
+{
+	struct link_util *lu = NULL;
+
+	if (argc <= 0) {
+		usage();
+		return ;
+	}
+
+	lu = get_link_kind(*argv);
+
+	if (lu && lu->print_help)
+		lu->print_help(lu, argc-1, argv+1, stdout);
+	else
+		usage();
+}
+
 int do_iplink(int argc, char **argv)
 {
 	if (argc > 0) {
@@ -969,8 +1194,10 @@
 		    matches(*argv, "lst") == 0 ||
 		    matches(*argv, "list") == 0)
 			return ipaddr_list_link(argc-1, argv+1);
-		if (matches(*argv, "help") == 0)
-			usage();
+		if (matches(*argv, "help") == 0) {
+			do_help(argc-1, argv+1);
+			return 0;
+		}
 	} else
 		return ipaddr_list_link(0, NULL);
 
diff --git a/ip/iplink_bond.c b/ip/iplink_bond.c
new file mode 100644
index 0000000..3009ec9
--- /dev/null
+++ b/ip/iplink_bond.c
@@ -0,0 +1,550 @@
+/*
+ * iplink_bond.c	Bonding device support
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@resnulli.us>
+ *              Scott Feldman <sfeldma@cumulusnetworks.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_link.h>
+#include <linux/if_ether.h>
+#include <net/if.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+#define BOND_MAX_ARP_TARGETS    16
+
+static const char *mode_tbl[] = {
+	"balance-rr",
+	"active-backup",
+	"balance-xor",
+	"broadcast",
+	"802.3ad",
+	"balance-tlb",
+	"balance-alb",
+	NULL,
+};
+
+static const char *arp_validate_tbl[] = {
+	"none",
+	"active",
+	"backup",
+	"all",
+	NULL,
+};
+
+static const char *arp_all_targets_tbl[] = {
+	"any",
+	"all",
+	NULL,
+};
+
+static const char *primary_reselect_tbl[] = {
+	"always",
+	"better",
+	"failure",
+	NULL,
+};
+
+static const char *fail_over_mac_tbl[] = {
+	"none",
+	"active",
+	"follow",
+	NULL,
+};
+
+static const char *xmit_hash_policy_tbl[] = {
+	"layer2",
+	"layer3+4",
+	"layer2+3",
+	"encap2+3",
+	"encap3+4",
+	NULL,
+};
+
+static const char *lacp_rate_tbl[] = {
+	"slow",
+	"fast",
+	NULL,
+};
+
+static const char *ad_select_tbl[] = {
+	"stable",
+	"bandwidth",
+	"count",
+	NULL,
+};
+
+static const char *get_name(const char **tbl, int index)
+{
+	int i;
+
+	for (i = 0; tbl[i]; i++)
+		if (i == index)
+			return tbl[i];
+
+	return "UNKNOWN";
+}
+
+static int get_index(const char **tbl, char *name)
+{
+	int i, index;
+
+	/* check for integer index passed in instead of name */
+	if (get_integer(&index, name, 10) == 0)
+		for (i = 0; tbl[i]; i++)
+			if (i == index)
+				return i;
+
+	for (i = 0; tbl[i]; i++)
+		if (strcmp(tbl[i], name) == 0)
+			return i;
+
+	return -1;
+}
+
+static void print_explain(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... bond [ mode BONDMODE ] [ active_slave SLAVE_DEV ]\n"
+		"                [ clear_active_slave ] [ miimon MIIMON ]\n"
+		"                [ updelay UPDELAY ] [ downdelay DOWNDELAY ]\n"
+		"                [ use_carrier USE_CARRIER ]\n"
+		"                [ arp_interval ARP_INTERVAL ]\n"
+		"                [ arp_validate ARP_VALIDATE ]\n"
+		"                [ arp_all_targets ARP_ALL_TARGETS ]\n"
+		"                [ arp_ip_target [ ARP_IP_TARGET, ... ] ]\n"
+		"                [ primary SLAVE_DEV ]\n"
+		"                [ primary_reselect PRIMARY_RESELECT ]\n"
+		"                [ fail_over_mac FAIL_OVER_MAC ]\n"
+		"                [ xmit_hash_policy XMIT_HASH_POLICY ]\n"
+		"                [ resend_igmp RESEND_IGMP ]\n"
+		"                [ num_grat_arp|num_unsol_na NUM_GRAT_ARP|NUM_UNSOL_NA ]\n"
+		"                [ all_slaves_active ALL_SLAVES_ACTIVE ]\n"
+		"                [ min_links MIN_LINKS ]\n"
+		"                [ lp_interval LP_INTERVAL ]\n"
+		"                [ packets_per_slave PACKETS_PER_SLAVE ]\n"
+		"                [ lacp_rate LACP_RATE ]\n"
+		"                [ ad_select AD_SELECT ]\n"
+		"\n"
+		"BONDMODE := balance-rr|active-backup|balance-xor|broadcast|802.3ad|balance-tlb|balance-alb\n"
+		"ARP_VALIDATE := none|active|backup|all\n"
+		"ARP_ALL_TARGETS := any|all\n"
+		"PRIMARY_RESELECT := always|better|failure\n"
+		"FAIL_OVER_MAC := none|active|follow\n"
+		"XMIT_HASH_POLICY := layer2|layer2+3|layer3+4\n"
+		"LACP_RATE := slow|fast\n"
+		"AD_SELECT := stable|bandwidth|count\n"
+	);
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static int bond_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	__u8 mode, use_carrier, primary_reselect, fail_over_mac;
+	__u8 xmit_hash_policy, num_peer_notif, all_slaves_active;
+	__u8 lacp_rate, ad_select;
+	__u32 miimon, updelay, downdelay, arp_interval, arp_validate;
+	__u32 arp_all_targets, resend_igmp, min_links, lp_interval;
+	__u32 packets_per_slave;
+	unsigned ifindex;
+
+	while (argc > 0) {
+		if (matches(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (get_index(mode_tbl, *argv) < 0) {
+				invarg("invalid mode", *argv);
+				return -1;
+			}
+			mode = get_index(mode_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_MODE, mode);
+		} else if (matches(*argv, "active_slave") == 0) {
+			NEXT_ARG();
+			ifindex = if_nametoindex(*argv);
+			if (!ifindex)
+				return -1;
+			addattr32(n, 1024, IFLA_BOND_ACTIVE_SLAVE, ifindex);
+		} else if (matches(*argv, "clear_active_slave") == 0) {
+			addattr32(n, 1024, IFLA_BOND_ACTIVE_SLAVE, 0);
+		} else if (matches(*argv, "miimon") == 0) {
+			NEXT_ARG();
+			if (get_u32(&miimon, *argv, 0)) {
+				invarg("invalid miimon", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BOND_MIIMON, miimon);
+		} else if (matches(*argv, "updelay") == 0) {
+			NEXT_ARG();
+			if (get_u32(&updelay, *argv, 0)) {
+				invarg("invalid updelay", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BOND_UPDELAY, updelay);
+		} else if (matches(*argv, "downdelay") == 0) {
+			NEXT_ARG();
+			if (get_u32(&downdelay, *argv, 0)) {
+				invarg("invalid downdelay", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BOND_DOWNDELAY, downdelay);
+		} else if (matches(*argv, "use_carrier") == 0) {
+			NEXT_ARG();
+			if (get_u8(&use_carrier, *argv, 0)) {
+				invarg("invalid use_carrier", *argv);
+				return -1;
+			}
+			addattr8(n, 1024, IFLA_BOND_USE_CARRIER, use_carrier);
+		} else if (matches(*argv, "arp_interval") == 0) {
+			NEXT_ARG();
+			if (get_u32(&arp_interval, *argv, 0)) {
+				invarg("invalid arp_interval", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BOND_ARP_INTERVAL, arp_interval);
+		} else if (matches(*argv, "arp_ip_target") == 0) {
+			struct rtattr * nest = addattr_nest(n, 1024,
+				IFLA_BOND_ARP_IP_TARGET);
+			if (NEXT_ARG_OK()) {
+				NEXT_ARG();
+				char *targets = strdupa(*argv);
+				char *target = strtok(targets, ",");
+				int i;
+
+				for(i = 0; target && i < BOND_MAX_ARP_TARGETS; i++) {
+					__u32 addr = get_addr32(target);
+					addattr32(n, 1024, i, addr);
+					target = strtok(NULL, ",");
+				}
+				addattr_nest_end(n, nest);
+			}
+			addattr_nest_end(n, nest);
+		} else if (matches(*argv, "arp_validate") == 0) {
+			NEXT_ARG();
+			if (get_index(arp_validate_tbl, *argv) < 0) {
+				invarg("invalid arp_validate", *argv);
+				return -1;
+			}
+			arp_validate = get_index(arp_validate_tbl, *argv);
+			addattr32(n, 1024, IFLA_BOND_ARP_VALIDATE, arp_validate);
+		} else if (matches(*argv, "arp_all_targets") == 0) {
+			NEXT_ARG();
+			if (get_index(arp_all_targets_tbl, *argv) < 0) {
+				invarg("invalid arp_all_targets", *argv);
+				return -1;
+			}
+			arp_all_targets = get_index(arp_all_targets_tbl, *argv);
+			addattr32(n, 1024, IFLA_BOND_ARP_ALL_TARGETS, arp_all_targets);
+		} else if (matches(*argv, "primary") == 0) {
+			NEXT_ARG();
+			ifindex = if_nametoindex(*argv);
+			if (!ifindex)
+				return -1;
+			addattr32(n, 1024, IFLA_BOND_PRIMARY, ifindex);
+		} else if (matches(*argv, "primary_reselect") == 0) {
+			NEXT_ARG();
+			if (get_index(primary_reselect_tbl, *argv) < 0) {
+				invarg("invalid primary_reselect", *argv);
+				return -1;
+			}
+			primary_reselect = get_index(primary_reselect_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_PRIMARY_RESELECT,
+				 primary_reselect);
+		} else if (matches(*argv, "fail_over_mac") == 0) {
+			NEXT_ARG();
+			if (get_index(fail_over_mac_tbl, *argv) < 0) {
+				invarg("invalid fail_over_mac", *argv);
+				return -1;
+			}
+			fail_over_mac = get_index(fail_over_mac_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_FAIL_OVER_MAC,
+				 fail_over_mac);
+		} else if (matches(*argv, "xmit_hash_policy") == 0) {
+			NEXT_ARG();
+			if (get_index(xmit_hash_policy_tbl, *argv) < 0) {
+				invarg("invalid xmit_hash_policy", *argv);
+				return -1;
+			}
+			xmit_hash_policy = get_index(xmit_hash_policy_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_XMIT_HASH_POLICY,
+				 xmit_hash_policy);
+		} else if (matches(*argv, "resend_igmp") == 0) {
+			NEXT_ARG();
+			if (get_u32(&resend_igmp, *argv, 0)) {
+				invarg("invalid resend_igmp", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BOND_RESEND_IGMP, resend_igmp);
+		} else if (matches(*argv, "num_grat_arp") == 0 ||
+			   matches(*argv, "num_unsol_na") == 0) {
+			NEXT_ARG();
+			if (get_u8(&num_peer_notif, *argv, 0)) {
+				invarg("invalid num_grat_arp|num_unsol_na",
+				       *argv);
+				return -1;
+			}
+			addattr8(n, 1024, IFLA_BOND_NUM_PEER_NOTIF,
+				 num_peer_notif);
+		} else if (matches(*argv, "all_slaves_active") == 0) {
+			NEXT_ARG();
+			if (get_u8(&all_slaves_active, *argv, 0)) {
+				invarg("invalid all_slaves_active", *argv);
+				return -1;
+			}
+			addattr8(n, 1024, IFLA_BOND_ALL_SLAVES_ACTIVE,
+				 all_slaves_active);
+		} else if (matches(*argv, "min_links") == 0) {
+			NEXT_ARG();
+			if (get_u32(&min_links, *argv, 0)) {
+				invarg("invalid min_links", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BOND_MIN_LINKS, min_links);
+		} else if (matches(*argv, "lp_interval") == 0) {
+			NEXT_ARG();
+			if (get_u32(&lp_interval, *argv, 0)) {
+				invarg("invalid lp_interval", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BOND_LP_INTERVAL, lp_interval);
+		} else if (matches(*argv, "packets_per_slave") == 0) {
+			NEXT_ARG();
+			if (get_u32(&packets_per_slave, *argv, 0)) {
+				invarg("invalid packets_per_slave", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BOND_PACKETS_PER_SLAVE,
+				  packets_per_slave);
+		} else if (matches(*argv, "lacp_rate") == 0) {
+			NEXT_ARG();
+			if (get_index(lacp_rate_tbl, *argv) < 0) {
+				invarg("invalid lacp_rate", *argv);
+				return -1;
+			}
+			lacp_rate = get_index(lacp_rate_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_AD_LACP_RATE, lacp_rate);
+		} else if (matches(*argv, "ad_select") == 0) {
+			NEXT_ARG();
+			if (get_index(ad_select_tbl, *argv) < 0) {
+				invarg("invalid ad_select", *argv);
+				return -1;
+			}
+			ad_select = get_index(ad_select_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_AD_SELECT, ad_select);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "bond: unknown command \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void bond_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	unsigned ifindex;
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_BOND_MODE]) {
+		const char *mode = get_name(mode_tbl,
+			rta_getattr_u8(tb[IFLA_BOND_MODE]));
+		fprintf(f, "mode %s ", mode);
+	}
+
+	if (tb[IFLA_BOND_ACTIVE_SLAVE] &&
+	    (ifindex = rta_getattr_u32(tb[IFLA_BOND_ACTIVE_SLAVE]))) {
+		char buf[IFNAMSIZ];
+		const char *n = if_indextoname(ifindex, buf);
+
+		if (n)
+			fprintf(f, "active_slave %s ", n);
+		else
+			fprintf(f, "active_slave %u ", ifindex);
+	}
+
+	if (tb[IFLA_BOND_MIIMON])
+		fprintf(f, "miimon %u ", rta_getattr_u32(tb[IFLA_BOND_MIIMON]));
+
+	if (tb[IFLA_BOND_UPDELAY])
+		fprintf(f, "updelay %u ", rta_getattr_u32(tb[IFLA_BOND_UPDELAY]));
+
+	if (tb[IFLA_BOND_DOWNDELAY])
+		fprintf(f, "downdelay %u ",
+			rta_getattr_u32(tb[IFLA_BOND_DOWNDELAY]));
+
+	if (tb[IFLA_BOND_USE_CARRIER])
+		fprintf(f, "use_carrier %u ",
+			rta_getattr_u8(tb[IFLA_BOND_USE_CARRIER]));
+
+	if (tb[IFLA_BOND_ARP_INTERVAL])
+		fprintf(f, "arp_interval %u ",
+			rta_getattr_u32(tb[IFLA_BOND_ARP_INTERVAL]));
+
+	if (tb[IFLA_BOND_ARP_IP_TARGET]) {
+		struct rtattr *iptb[BOND_MAX_ARP_TARGETS + 1];
+		char buf[INET_ADDRSTRLEN];
+		int i;
+
+		parse_rtattr_nested(iptb, BOND_MAX_ARP_TARGETS,
+			tb[IFLA_BOND_ARP_IP_TARGET]);
+
+		if (iptb[0])
+			fprintf(f, "arp_ip_target ");
+
+		for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) {
+			if (iptb[i])
+				fprintf(f, "%s",
+					rt_addr_n2a(AF_INET,
+						    RTA_DATA(iptb[i]),
+						    buf,
+						    INET_ADDRSTRLEN));
+			if (i < BOND_MAX_ARP_TARGETS-1 && iptb[i+1])
+				fprintf(f, ",");
+		}
+
+		if (iptb[0])
+			fprintf(f, " ");
+	}
+
+	if (tb[IFLA_BOND_ARP_VALIDATE]) {
+		const char *arp_validate = get_name(arp_validate_tbl,
+			rta_getattr_u32(tb[IFLA_BOND_ARP_VALIDATE]));
+		fprintf(f, "arp_validate %s ", arp_validate);
+	}
+
+	if (tb[IFLA_BOND_ARP_ALL_TARGETS]) {
+		const char *arp_all_targets = get_name(arp_all_targets_tbl,
+			rta_getattr_u32(tb[IFLA_BOND_ARP_ALL_TARGETS]));
+		fprintf(f, "arp_all_targets %s ", arp_all_targets);
+	}
+
+	if (tb[IFLA_BOND_PRIMARY] &&
+	    (ifindex = rta_getattr_u32(tb[IFLA_BOND_PRIMARY]))) {
+		char buf[IFNAMSIZ];
+		const char *n = if_indextoname(ifindex, buf);
+
+		if (n)
+			fprintf(f, "primary %s ", n);
+		else
+			fprintf(f, "primary %u ", ifindex);
+	}
+
+	if (tb[IFLA_BOND_PRIMARY_RESELECT]) {
+		const char *primary_reselect = get_name(primary_reselect_tbl,
+			rta_getattr_u8(tb[IFLA_BOND_PRIMARY_RESELECT]));
+		fprintf(f, "primary_reselect %s ", primary_reselect);
+	}
+
+	if (tb[IFLA_BOND_FAIL_OVER_MAC]) {
+		const char *fail_over_mac = get_name(fail_over_mac_tbl,
+			rta_getattr_u8(tb[IFLA_BOND_FAIL_OVER_MAC]));
+		fprintf(f, "fail_over_mac %s ", fail_over_mac);
+	}
+
+	if (tb[IFLA_BOND_XMIT_HASH_POLICY]) {
+		const char *xmit_hash_policy = get_name(xmit_hash_policy_tbl,
+			rta_getattr_u8(tb[IFLA_BOND_XMIT_HASH_POLICY]));
+		fprintf(f, "xmit_hash_policy %s ", xmit_hash_policy);
+	}
+
+	if (tb[IFLA_BOND_RESEND_IGMP])
+		fprintf(f, "resend_igmp %u ",
+			rta_getattr_u32(tb[IFLA_BOND_RESEND_IGMP]));
+
+	if (tb[IFLA_BOND_NUM_PEER_NOTIF])
+		fprintf(f, "num_grat_arp %u ",
+			rta_getattr_u8(tb[IFLA_BOND_NUM_PEER_NOTIF]));
+
+	if (tb[IFLA_BOND_ALL_SLAVES_ACTIVE])
+		fprintf(f, "all_slaves_active %u ",
+			rta_getattr_u8(tb[IFLA_BOND_ALL_SLAVES_ACTIVE]));
+
+	if (tb[IFLA_BOND_MIN_LINKS])
+		fprintf(f, "min_links %u ",
+			rta_getattr_u32(tb[IFLA_BOND_MIN_LINKS]));
+
+	if (tb[IFLA_BOND_LP_INTERVAL])
+		fprintf(f, "lp_interval %u ",
+			rta_getattr_u32(tb[IFLA_BOND_LP_INTERVAL]));
+
+	if (tb[IFLA_BOND_PACKETS_PER_SLAVE])
+		fprintf(f, "packets_per_slave %u ",
+			rta_getattr_u32(tb[IFLA_BOND_PACKETS_PER_SLAVE]));
+
+	if (tb[IFLA_BOND_AD_LACP_RATE]) {
+		const char *lacp_rate = get_name(lacp_rate_tbl,
+			rta_getattr_u8(tb[IFLA_BOND_AD_LACP_RATE]));
+		fprintf(f, "lacp_rate %s ", lacp_rate);
+	}
+
+	if (tb[IFLA_BOND_AD_SELECT]) {
+		const char *ad_select = get_name(ad_select_tbl,
+			rta_getattr_u8(tb[IFLA_BOND_AD_SELECT]));
+		fprintf(f, "ad_select %s ", ad_select);
+	}
+
+	if (tb[IFLA_BOND_AD_INFO]) {
+		struct rtattr *adtb[IFLA_BOND_AD_INFO_MAX + 1];
+
+		parse_rtattr_nested(adtb, IFLA_BOND_AD_INFO_MAX,
+			tb[IFLA_BOND_AD_INFO]);
+
+		if (adtb[IFLA_BOND_AD_INFO_AGGREGATOR])
+			fprintf(f, "ad_aggregator %d ",
+			  rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_AGGREGATOR]));
+
+		if (adtb[IFLA_BOND_AD_INFO_NUM_PORTS])
+			fprintf(f, "ad_num_ports %d ",
+			  rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_NUM_PORTS]));
+
+		if (adtb[IFLA_BOND_AD_INFO_ACTOR_KEY])
+			fprintf(f, "ad_actor_key %d ",
+			  rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_ACTOR_KEY]));
+
+		if (adtb[IFLA_BOND_AD_INFO_PARTNER_KEY])
+			fprintf(f, "ad_partner_key %d ",
+			  rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_PARTNER_KEY]));
+
+		if (adtb[IFLA_BOND_AD_INFO_PARTNER_MAC]) {
+			unsigned char *p =
+				RTA_DATA(adtb[IFLA_BOND_AD_INFO_PARTNER_MAC]);
+			SPRINT_BUF(b);
+			fprintf(f, "ad_partner_mac %s ",
+				ll_addr_n2a(p, ETH_ALEN, 0, b, sizeof(b)));
+		}
+	}
+}
+
+static void bond_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_explain(f);
+}
+
+struct link_util bond_link_util = {
+	.id		= "bond",
+	.maxattr	= IFLA_BOND_MAX,
+	.parse_opt	= bond_parse_opt,
+	.print_opt	= bond_print_opt,
+	.print_help	= bond_print_help,
+};
diff --git a/ip/iplink_bond_slave.c b/ip/iplink_bond_slave.c
new file mode 100644
index 0000000..aacba14
--- /dev/null
+++ b/ip/iplink_bond_slave.c
@@ -0,0 +1,108 @@
+/*
+ * iplink_bond_slave.c	Bonding slave device support
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <linux/if_bonding.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static const char *slave_states[] = {
+	[BOND_STATE_ACTIVE] = "ACTIVE",
+	[BOND_STATE_BACKUP] = "BACKUP",
+};
+
+static void print_slave_state(FILE *f, struct rtattr *tb)
+{
+	unsigned int state = rta_getattr_u8(tb);
+
+	if (state >= sizeof(slave_states) / sizeof(slave_states[0]))
+		fprintf(f, "state %d ", state);
+	else
+		fprintf(f, "state %s ", slave_states[state]);
+}
+
+static const char *slave_mii_status[] = {
+	[BOND_LINK_UP] = "UP",
+	[BOND_LINK_FAIL] = "GOING_DOWN",
+	[BOND_LINK_DOWN] = "DOWN",
+	[BOND_LINK_BACK] = "GOING_BACK",
+};
+
+static void print_slave_mii_status(FILE *f, struct rtattr *tb)
+{
+	unsigned int status = rta_getattr_u8(tb);
+
+	if (status >= sizeof(slave_mii_status) / sizeof(slave_mii_status[0]))
+		fprintf(f, "mii_status %d ", status);
+	else
+		fprintf(f, "mii_status %s ", slave_mii_status[status]);
+}
+
+static void bond_slave_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	SPRINT_BUF(b1);
+	if (!tb)
+		return;
+
+	if (tb[IFLA_BOND_SLAVE_STATE])
+		print_slave_state(f, tb[IFLA_BOND_SLAVE_STATE]);
+
+	if (tb[IFLA_BOND_SLAVE_MII_STATUS])
+		print_slave_mii_status(f, tb[IFLA_BOND_SLAVE_MII_STATUS]);
+
+	if (tb[IFLA_BOND_SLAVE_LINK_FAILURE_COUNT])
+		fprintf(f, "link_failure_count %d ",
+			rta_getattr_u32(tb[IFLA_BOND_SLAVE_LINK_FAILURE_COUNT]));
+
+	if (tb[IFLA_BOND_SLAVE_PERM_HWADDR])
+		fprintf(f, "perm_hwaddr %s ",
+			ll_addr_n2a(RTA_DATA(tb[IFLA_BOND_SLAVE_PERM_HWADDR]),
+				    RTA_PAYLOAD(tb[IFLA_BOND_SLAVE_PERM_HWADDR]),
+				    0, b1, sizeof(b1)));
+
+	if (tb[IFLA_BOND_SLAVE_QUEUE_ID])
+		fprintf(f, "queue_id %d ",
+			rta_getattr_u16(tb[IFLA_BOND_SLAVE_QUEUE_ID]));
+
+	if (tb[IFLA_BOND_SLAVE_AD_AGGREGATOR_ID])
+		fprintf(f, "ad_aggregator_id %d ",
+			rta_getattr_u16(tb[IFLA_BOND_SLAVE_AD_AGGREGATOR_ID]));
+}
+
+static int bond_slave_parse_opt(struct link_util *lu, int argc, char **argv,
+				struct nlmsghdr *n)
+{
+	__u16 queue_id;
+
+	while (argc > 0) {
+		if (matches(*argv, "queue_id") == 0) {
+			NEXT_ARG();
+			if (get_u16(&queue_id, *argv, 0))
+				invarg("queue_id is invalid", *argv);
+			addattr16(n, 1024, IFLA_BOND_SLAVE_QUEUE_ID, queue_id);
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+struct link_util bond_slave_link_util = {
+	.id		= "bond",
+	.maxattr	= IFLA_BOND_SLAVE_MAX,
+	.print_opt	= bond_slave_print_opt,
+	.parse_opt	= bond_slave_parse_opt,
+	.slave		= true,
+};
+
diff --git a/ip/iplink_bridge.c b/ip/iplink_bridge.c
new file mode 100644
index 0000000..0cea7d1
--- /dev/null
+++ b/ip/iplink_bridge.c
@@ -0,0 +1,93 @@
+/*
+ * iplink_bridge.c	Bridge device support
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_link.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void explain(void)
+{
+	fprintf(stderr,
+		"Usage: ... bridge [ forward_delay FORWARD_DELAY ]\n"
+		"                  [ hello_time HELLO_TIME ]\n"
+		"                  [ max_age MAX_AGE ]\n"
+	);
+}
+
+static int bridge_parse_opt(struct link_util *lu, int argc, char **argv,
+			    struct nlmsghdr *n)
+{
+	__u32 val;
+
+	while (argc > 0) {
+		if (matches(*argv, "forward_delay") == 0) {
+			NEXT_ARG();
+			if (get_u32(&val, *argv, 0)) {
+				invarg("invalid forward_delay", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BR_FORWARD_DELAY, val);
+		} else if (matches(*argv, "hello_time") == 0) {
+			NEXT_ARG();
+			if (get_u32(&val, *argv, 0)) {
+				invarg("invalid hello_time", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BR_HELLO_TIME, val);
+		} else if (matches(*argv, "max_age") == 0) {
+			NEXT_ARG();
+			if (get_u32(&val, *argv, 0)) {
+				invarg("invalid max_age", *argv);
+				return -1;
+			}
+			addattr32(n, 1024, IFLA_BR_MAX_AGE, val);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "bridge: unknown command \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void bridge_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_BR_FORWARD_DELAY])
+		fprintf(f, "forward_delay %u ",
+			rta_getattr_u32(tb[IFLA_BR_FORWARD_DELAY]));
+
+	if (tb[IFLA_BR_HELLO_TIME])
+		fprintf(f, "hello_time %u ",
+			rta_getattr_u32(tb[IFLA_BR_HELLO_TIME]));
+
+	if (tb[IFLA_BR_MAX_AGE])
+		fprintf(f, "max_age %u ",
+			rta_getattr_u32(tb[IFLA_BR_MAX_AGE]));
+}
+
+struct link_util bridge_link_util = {
+	.id		= "bridge",
+	.maxattr	= IFLA_BR_MAX,
+	.parse_opt	= bridge_parse_opt,
+	.print_opt	= bridge_print_opt,
+};
diff --git a/ip/iplink_bridge_slave.c b/ip/iplink_bridge_slave.c
new file mode 100644
index 0000000..a285185
--- /dev/null
+++ b/ip/iplink_bridge_slave.c
@@ -0,0 +1,181 @@
+/*
+ * iplink_bridge_slave.c	Bridge slave device support
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <linux/if_link.h>
+#include <linux/if_bridge.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void explain(void)
+{
+	fprintf(stderr,
+		"Usage: ... bridge_slave [ state STATE ] [ priority PRIO ] [cost COST ]\n"
+		"                        [ guard {on | off} ]\n"
+		"                        [ hairpin {on | off} ] \n"
+		"                        [ fastleave {on | off} ]\n"
+		"                        [ root_block {on | off} ]\n"
+		"                        [ learning {on | off} ]\n"
+		"                        [ flood {on | off} ]\n"
+	);
+}
+
+static const char *port_states[] = {
+	[BR_STATE_DISABLED] = "disabled",
+	[BR_STATE_LISTENING] = "listening",
+	[BR_STATE_LEARNING] = "learning",
+	[BR_STATE_FORWARDING] = "forwarding",
+	[BR_STATE_BLOCKING] = "blocking",
+};
+
+static void print_portstate(FILE *f, __u8 state)
+{
+	if (state <= BR_STATE_BLOCKING)
+		fprintf(f, "state %s ", port_states[state]);
+	else
+		fprintf(f, "state (%d) ", state);
+}
+
+static void print_onoff(FILE *f, char *flag, __u8 val)
+{
+	fprintf(f, "%s %s ", flag, val ? "on" : "off");
+}
+
+static void bridge_slave_print_opt(struct link_util *lu, FILE *f,
+				   struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_BRPORT_STATE])
+		print_portstate(f, rta_getattr_u8(tb[IFLA_BRPORT_STATE]));
+
+	if (tb[IFLA_BRPORT_PRIORITY])
+		fprintf(f, "priority %d ",
+			rta_getattr_u16(tb[IFLA_BRPORT_PRIORITY]));
+
+	if (tb[IFLA_BRPORT_COST])
+		fprintf(f, "cost %d ",
+			rta_getattr_u32(tb[IFLA_BRPORT_COST]));
+
+	if (tb[IFLA_BRPORT_MODE])
+		print_onoff(f, "hairpin",
+			    rta_getattr_u8(tb[IFLA_BRPORT_MODE]));
+
+	if (tb[IFLA_BRPORT_GUARD])
+		print_onoff(f, "guard",
+			    rta_getattr_u8(tb[IFLA_BRPORT_GUARD]));
+
+	if (tb[IFLA_BRPORT_PROTECT])
+		print_onoff(f, "root_block",
+			    rta_getattr_u8(tb[IFLA_BRPORT_PROTECT]));
+
+	if (tb[IFLA_BRPORT_FAST_LEAVE])
+		print_onoff(f, "fastleave",
+			    rta_getattr_u8(tb[IFLA_BRPORT_FAST_LEAVE]));
+
+	if (tb[IFLA_BRPORT_LEARNING])
+		print_onoff(f, "learning",
+			rta_getattr_u8(tb[IFLA_BRPORT_LEARNING]));
+
+	if (tb[IFLA_BRPORT_UNICAST_FLOOD])
+		print_onoff(f, "flood",
+			rta_getattr_u8(tb[IFLA_BRPORT_UNICAST_FLOOD]));
+}
+
+static void bridge_slave_parse_on_off(char *arg_name, char *arg_val,
+				      struct nlmsghdr *n, int type)
+{
+	__u8 val;
+
+	if (strcmp(arg_val, "on") == 0)
+		val = 1;
+	else if (strcmp(arg_val, "off") == 0)
+		val = 0;
+	else
+		invarg("should be \"on\" or \"off\"", arg_name);
+
+	addattr8(n, 1024, type, val);
+}
+
+static int bridge_slave_parse_opt(struct link_util *lu, int argc, char **argv,
+				  struct nlmsghdr *n)
+{
+	__u8 state;
+	__u16 priority;
+	__u32 cost;
+
+	while (argc > 0) {
+		if (matches(*argv, "state") == 0) {
+			NEXT_ARG();
+			if (get_u8(&state, *argv, 0))
+				invarg("state is invalid", *argv);
+			addattr8(n, 1024, IFLA_BRPORT_STATE, state);
+		} else if (matches(*argv, "priority") == 0) {
+			NEXT_ARG();
+			if (get_u16(&priority, *argv, 0))
+				invarg("priority is invalid", *argv);
+			addattr16(n, 1024, IFLA_BRPORT_PRIORITY, priority);
+		} else if (matches(*argv, "cost") == 0) {
+			NEXT_ARG();
+			if (get_u32(&cost, *argv, 0))
+				invarg("cost is invalid", *argv);
+			addattr32(n, 1024, IFLA_BRPORT_COST, cost);
+		} else if (matches(*argv, "hairpin") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("hairpin", *argv, n,
+						  IFLA_BRPORT_MODE);
+		} else if (matches(*argv, "guard") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("guard", *argv, n,
+						  IFLA_BRPORT_GUARD);
+		} else if (matches(*argv, "root_block") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("root_block", *argv, n,
+						  IFLA_BRPORT_PROTECT);
+		} else if (matches(*argv, "fastleave") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("fastleave", *argv, n,
+						  IFLA_BRPORT_FAST_LEAVE);
+		} else if (matches(*argv, "learning") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("learning", *argv, n,
+						  IFLA_BRPORT_LEARNING);
+		} else if (matches(*argv, "flood") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("flood", *argv, n,
+						  IFLA_BRPORT_UNICAST_FLOOD);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "bridge_slave: unknown option \"%s\"?\n",
+				*argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+struct link_util bridge_slave_link_util = {
+	.id		= "bridge",
+	.maxattr	= IFLA_BRPORT_MAX,
+	.print_opt	= bridge_slave_print_opt,
+	.parse_opt	= bridge_slave_parse_opt,
+	.slave		= true,
+};
diff --git a/ip/iplink_can.c b/ip/iplink_can.c
index c8af4bc..f1b089d 100644
--- a/ip/iplink_can.c
+++ b/ip/iplink_can.c
@@ -1,12 +1,12 @@
 /*
  * iplink_can.c	CAN device support
  *
- *              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 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.
  *
- * Authors:     Wolfgang Grandegger <wg@grandegger.com>
+ * Authors:	Wolfgang Grandegger <wg@grandegger.com>
  */
 
 #include <stdio.h>
@@ -19,34 +19,46 @@
 #include "utils.h"
 #include "ip_common.h"
 
-static void usage(void)
+static void print_usage(FILE *f)
 {
-	fprintf(stderr,
+	fprintf(f,
 		"Usage: ip link set DEVICE type can\n"
-	        "\t[ bitrate BITRATE [ sample-point SAMPLE-POINT] ] | \n"
-	        "\t[ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1\n "
+		"\t[ bitrate BITRATE [ sample-point SAMPLE-POINT] ] | \n"
+		"\t[ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1\n "
 		"\t  phase-seg2 PHASE-SEG2 [ sjw SJW ] ]\n"
 		"\n"
-	        "\t[ loopback { on | off } ]\n"
-	        "\t[ listen-only { on | off } ]\n"
-	        "\t[ triple-sampling { on | off } ]\n"
-	        "\t[ one-shot { on | off } ]\n"
-	        "\t[ berr-reporting { on | off } ]\n"
+		"\t[ dbitrate BITRATE [ dsample-point SAMPLE-POINT] ] | \n"
+		"\t[ dtq TQ dprop-seg PROP_SEG dphase-seg1 PHASE-SEG1\n "
+		"\t  dphase-seg2 PHASE-SEG2 [ dsjw SJW ] ]\n"
 		"\n"
-	        "\t[ restart-ms TIME-MS ]\n"
-	        "\t[ restart ]\n"
+		"\t[ loopback { on | off } ]\n"
+		"\t[ listen-only { on | off } ]\n"
+		"\t[ triple-sampling { on | off } ]\n"
+		"\t[ one-shot { on | off } ]\n"
+		"\t[ berr-reporting { on | off } ]\n"
+		"\t[ fd { on | off } ]\n"
+		"\t[ fd-non-iso { on | off } ]\n"
+		"\t[ presume-ack { on | off } ]\n"
 		"\n"
-		"\tWhere: BITRATE       := { 1..1000000 }\n"
-		"\t       SAMPLE-POINT  := { 0.000..0.999 }\n"
-		"\t       TQ            := { NUMBER }\n"
-		"\t       PROP-SEG      := { 1..8 }\n"
-		"\t       PHASE-SEG1    := { 1..8 }\n"
-		"\t       PHASE-SEG2    := { 1..8 }\n"
-		"\t       SJW           := { 1..4 }\n"
-		"\t       RESTART-MS    := { 0 | NUMBER }\n"
+		"\t[ restart-ms TIME-MS ]\n"
+		"\t[ restart ]\n"
+		"\n"
+		"\tWhere: BITRATE	:= { 1..1000000 }\n"
+		"\t	  SAMPLE-POINT	:= { 0.000..0.999 }\n"
+		"\t	  TQ		:= { NUMBER }\n"
+		"\t	  PROP-SEG	:= { 1..8 }\n"
+		"\t	  PHASE-SEG1	:= { 1..8 }\n"
+		"\t	  PHASE-SEG2	:= { 1..8 }\n"
+		"\t	  SJW		:= { 1..4 }\n"
+		"\t	  RESTART-MS	:= { 0 | NUMBER }\n"
 		);
 }
 
+static void usage(void)
+{
+	print_usage(stderr);
+}
+
 static int get_float(float *val, const char *arg)
 {
 	float res;
@@ -68,8 +80,8 @@
 		cm->flags |= flags;
 	} else if (strcmp(arg, "off") != 0) {
 		fprintf(stderr,
-			"Error: argument of \"%s\" must be \"on\" or \"off\"\n",
-			name);
+			"Error: argument of \"%s\" must be \"on\" or \"off\", not \"%s\"\n",
+			name, arg);
 		exit(-1);
 	}
 	cm->mask |= flags;
@@ -88,6 +100,9 @@
 	_PF(CAN_CTRLMODE_3_SAMPLES, "TRIPLE-SAMPLING");
 	_PF(CAN_CTRLMODE_ONE_SHOT, "ONE-SHOT");
 	_PF(CAN_CTRLMODE_BERR_REPORTING, "BERR-REPORTING");
+	_PF(CAN_CTRLMODE_FD, "FD");
+	_PF(CAN_CTRLMODE_FD_NON_ISO, "FD-NON-ISO");
+	_PF(CAN_CTRLMODE_PRESUME_ACK, "PRESUME-ACK");
 #undef _PF
 	if (cm)
 		fprintf(f, "%x", cm);
@@ -97,10 +112,11 @@
 static int can_parse_opt(struct link_util *lu, int argc, char **argv,
 			 struct nlmsghdr *n)
 {
-	struct can_bittiming bt;
+	struct can_bittiming bt, dbt;
 	struct can_ctrlmode cm = {0, 0};
 
 	memset(&bt, 0, sizeof(bt));
+	memset(&dbt, 0, sizeof(dbt));
 	while (argc > 0) {
 		if (matches(*argv, "bitrate") == 0) {
 			NEXT_ARG();
@@ -134,6 +150,37 @@
 			NEXT_ARG();
 			if (get_u32(&bt.sjw, *argv, 0))
 				invarg("invalid \"sjw\" value\n", *argv);
+		} else if (matches(*argv, "dbitrate") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.bitrate, *argv, 0))
+				invarg("invalid \"dbitrate\" value\n", *argv);
+		} else if (matches(*argv, "dsample-point") == 0) {
+			float sp;
+
+			NEXT_ARG();
+			if (get_float(&sp, *argv))
+				invarg("invalid \"dsample-point\" value\n", *argv);
+			dbt.sample_point = (__u32)(sp * 1000);
+		} else if (matches(*argv, "dtq") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.tq, *argv, 0))
+				invarg("invalid \"dtq\" value\n", *argv);
+		} else if (matches(*argv, "dprop-seg") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.prop_seg, *argv, 0))
+				invarg("invalid \"dprop-seg\" value\n", *argv);
+		} else if (matches(*argv, "dphase-seg1") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.phase_seg1, *argv, 0))
+				invarg("invalid \"dphase-seg1\" value\n", *argv);
+		} else if (matches(*argv, "dphase-seg2") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.phase_seg2, *argv, 0))
+				invarg("invalid \"dphase-seg2\" value\n", *argv);
+		} else if (matches(*argv, "dsjw") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.sjw, *argv, 0))
+				invarg("invalid \"dsjw\" value\n", *argv);
 		} else if (matches(*argv, "loopback") == 0) {
 			NEXT_ARG();
 			set_ctrlmode("loopback", *argv, &cm,
@@ -154,6 +201,18 @@
 			NEXT_ARG();
 			set_ctrlmode("berr-reporting", *argv, &cm,
 				     CAN_CTRLMODE_BERR_REPORTING);
+		} else if (matches(*argv, "fd") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("fd", *argv, &cm,
+				     CAN_CTRLMODE_FD);
+		} else if (matches(*argv, "fd-non-iso") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("fd-non-iso", *argv, &cm,
+				     CAN_CTRLMODE_FD_NON_ISO);
+		} else if (matches(*argv, "presume-ack") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("presume-ack", *argv, &cm,
+				     CAN_CTRLMODE_PRESUME_ACK);
 		} else if (matches(*argv, "restart") == 0) {
 			__u32 val = 1;
 
@@ -169,7 +228,7 @@
 			usage();
 			return -1;
 		} else {
-			fprintf(stderr, "can: what is \"%s\"?\n", *argv);
+			fprintf(stderr, "can: unknown option \"%s\"\n", *argv);
 			usage();
 			return -1;
 		}
@@ -178,6 +237,8 @@
 
 	if (bt.bitrate || bt.tq)
 		addattr_l(n, 1024, IFLA_CAN_BITTIMING, &bt, sizeof(bt));
+	if (dbt.bitrate || dbt.tq)
+		addattr_l(n, 1024, IFLA_CAN_DATA_BITTIMING, &dbt, sizeof(dbt));
 	if (cm.mask)
 		addattr_l(n, 1024, IFLA_CAN_CTRLMODE, &cm, sizeof(cm));
 
@@ -185,12 +246,12 @@
 }
 
 static const char *can_state_names[] = {
-		[CAN_STATE_ERROR_ACTIVE] = "ERROR-ACTIVE",
-		[CAN_STATE_ERROR_WARNING] = "ERROR-WARNING",
-		[CAN_STATE_ERROR_PASSIVE] = "ERROR-PASSIVE",
-		[CAN_STATE_BUS_OFF] = "BUS-OFF",
-		[CAN_STATE_STOPPED] = "STOPPED",
-		[CAN_STATE_SLEEPING] = "SLEEPING"
+	[CAN_STATE_ERROR_ACTIVE] = "ERROR-ACTIVE",
+	[CAN_STATE_ERROR_WARNING] = "ERROR-WARNING",
+	[CAN_STATE_ERROR_PASSIVE] = "ERROR-PASSIVE",
+	[CAN_STATE_BUS_OFF] = "BUS-OFF",
+	[CAN_STATE_STOPPED] = "STOPPED",
+	[CAN_STATE_SLEEPING] = "SLEEPING"
 };
 
 static void can_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
@@ -228,10 +289,10 @@
 	if (tb[IFLA_CAN_BITTIMING]) {
 		struct can_bittiming *bt = RTA_DATA(tb[IFLA_CAN_BITTIMING]);
 
-		fprintf(f, "\n    "
+		fprintf(f, "\n	  "
 			"bitrate %d sample-point %.3f ",
-		        bt->bitrate, (float)bt->sample_point / 1000.);
-		fprintf(f, "\n    "
+			bt->bitrate, (float)bt->sample_point / 1000.);
+		fprintf(f, "\n	  "
 			"tq %d prop-seg %d phase-seg1 %d phase-seg2 %d sjw %d",
 			bt->tq, bt->prop_seg, bt->phase_seg1, bt->phase_seg2,
 			bt->sjw);
@@ -241,18 +302,44 @@
 		struct can_bittiming_const *btc =
 			RTA_DATA(tb[IFLA_CAN_BITTIMING_CONST]);
 
-		fprintf(f, "\n    "
+		fprintf(f, "\n	  "
 			"%s: tseg1 %d..%d tseg2 %d..%d "
 			"sjw 1..%d brp %d..%d brp-inc %d",
-		        btc->name, btc->tseg1_min, btc->tseg1_max,
+			btc->name, btc->tseg1_min, btc->tseg1_max,
 			btc->tseg2_min, btc->tseg2_max, btc->sjw_max,
 			btc->brp_min, btc->brp_max, btc->brp_inc);
 	}
 
+	if (tb[IFLA_CAN_DATA_BITTIMING]) {
+		struct can_bittiming *dbt =
+			RTA_DATA(tb[IFLA_CAN_DATA_BITTIMING]);
+
+		fprintf(f, "\n	  "
+			"dbitrate %d dsample-point %.3f ",
+			dbt->bitrate, (float)dbt->sample_point / 1000.);
+		fprintf(f, "\n	  "
+			"dtq %d dprop-seg %d dphase-seg1 %d "
+			"dphase-seg2 %d dsjw %d",
+			dbt->tq, dbt->prop_seg, dbt->phase_seg1,
+			dbt->phase_seg2, dbt->sjw);
+	}
+
+	if (tb[IFLA_CAN_DATA_BITTIMING_CONST]) {
+		struct can_bittiming_const *dbtc =
+			RTA_DATA(tb[IFLA_CAN_DATA_BITTIMING_CONST]);
+
+		fprintf(f, "\n	  "
+			"%s: dtseg1 %d..%d dtseg2 %d..%d "
+			"dsjw 1..%d dbrp %d..%d dbrp-inc %d",
+			dbtc->name, dbtc->tseg1_min, dbtc->tseg1_max,
+			dbtc->tseg2_min, dbtc->tseg2_max, dbtc->sjw_max,
+			dbtc->brp_min, dbtc->brp_max, dbtc->brp_inc);
+	}
+
 	if (tb[IFLA_CAN_CLOCK]) {
 		struct can_clock *clock = RTA_DATA(tb[IFLA_CAN_CLOCK]);
 
-		fprintf(f, "\n    clock %d", clock->freq);
+		fprintf(f, "\n	  clock %d", clock->freq);
 	}
 
 }
@@ -264,20 +351,27 @@
 
 	if (xstats && RTA_PAYLOAD(xstats) == sizeof(*stats)) {
 		stats = RTA_DATA(xstats);
-		fprintf(f, "\n    "
+		fprintf(f, "\n	  "
 			"re-started bus-errors arbit-lost "
 			"error-warn error-pass bus-off");
-		fprintf(f, "\n    %-10d %-10d %-10d %-10d %-10d %-10d",
+		fprintf(f, "\n	  %-10d %-10d %-10d %-10d %-10d %-10d",
 			stats->restarts, stats->bus_error,
 			stats->arbitration_lost, stats->error_warning,
 			stats->error_passive, stats->bus_off);
 	}
 }
 
+static void can_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
+}
+
 struct link_util can_link_util = {
 	.id		= "can",
 	.maxattr	= IFLA_CAN_MAX,
 	.parse_opt	= can_parse_opt,
 	.print_opt	= can_print_opt,
-	.print_xstats 	= can_print_xstats,
+	.print_xstats	= can_print_xstats,
+	.print_help	= can_print_help,
 };
diff --git a/ip/iplink_hsr.c b/ip/iplink_hsr.c
new file mode 100644
index 0000000..65fbec8
--- /dev/null
+++ b/ip/iplink_hsr.c
@@ -0,0 +1,141 @@
+/*
+ * iplink_hsr.c	HSR device support
+ *
+ *		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.
+ *
+ * Authors:	Arvid Brodin <arvid.brodin@alten.se>
+ *
+ *		Based on iplink_vlan.c by Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>  /* Needed by linux/if.h for some reason */
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_usage(FILE *f)
+{
+	fprintf(f,
+"Usage:\tip link add name NAME type hsr slave1 SLAVE1-IF slave2 SLAVE2-IF\n"
+"\t[ supervision ADDR-BYTE ]\n"
+"\n"
+"NAME\n"
+"	name of new hsr device (e.g. hsr0)\n"
+"SLAVE1-IF, SLAVE2-IF\n"
+"	the two slave devices bound to the HSR device\n"
+"ADDR-BYTE\n"
+"	0-255; the last byte of the multicast address used for HSR supervision\n"
+"	frames (default = 0)\n");
+}
+
+static void usage(void)
+{
+	print_usage(stderr);
+}
+
+static int hsr_parse_opt(struct link_util *lu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	int ifindex;
+	unsigned char multicast_spec;
+
+	while (argc > 0) {
+		if (matches(*argv, "supervision") == 0) {
+			NEXT_ARG();
+			if (get_u8(&multicast_spec, *argv, 0))
+				invarg("ADDR-BYTE is invalid", *argv);
+			addattr_l(n, 1024, IFLA_HSR_MULTICAST_SPEC,
+				  &multicast_spec, 1);
+		} else if (matches(*argv, "slave1") == 0) {
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (ifindex == 0)
+				invarg("No such interface", *argv);
+			addattr_l(n, 1024, IFLA_HSR_SLAVE1, &ifindex, 4);
+		} else if (matches(*argv, "slave2") == 0) {
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (ifindex == 0)
+				invarg("No such interface", *argv);
+			addattr_l(n, 1024, IFLA_HSR_SLAVE2, &ifindex, 4);
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+			return -1;
+		} else {
+			fprintf(stderr, "hsr: what is \"%s\"?\n", *argv);
+			usage();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void hsr_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	SPRINT_BUF(b1);
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_HSR_SLAVE1] &&
+	    RTA_PAYLOAD(tb[IFLA_HSR_SLAVE1]) < sizeof(__u32))
+		return;
+	if (tb[IFLA_HSR_SLAVE2] &&
+	    RTA_PAYLOAD(tb[IFLA_HSR_SLAVE2]) < sizeof(__u32))
+		return;
+	if (tb[IFLA_HSR_SEQ_NR] &&
+	    RTA_PAYLOAD(tb[IFLA_HSR_SEQ_NR]) < sizeof(__u16))
+		return;
+	if (tb[IFLA_HSR_SUPERVISION_ADDR] &&
+	    RTA_PAYLOAD(tb[IFLA_HSR_SUPERVISION_ADDR]) < ETH_ALEN)
+		return;
+
+	fprintf(f, "slave1 ");
+	if (tb[IFLA_HSR_SLAVE1])
+		fprintf(f, "%s ",
+			ll_index_to_name(rta_getattr_u32(tb[IFLA_HSR_SLAVE1])));
+	else
+		fprintf(f, "<none> ");
+
+	fprintf(f, "slave2 ");
+	if (tb[IFLA_HSR_SLAVE2])
+		fprintf(f, "%s ",
+			ll_index_to_name(rta_getattr_u32(tb[IFLA_HSR_SLAVE2])));
+	else
+		fprintf(f, "<none> ");
+
+	if (tb[IFLA_HSR_SEQ_NR])
+		fprintf(f, "sequence %d ",
+			rta_getattr_u16(tb[IFLA_HSR_SEQ_NR]));
+
+	if (tb[IFLA_HSR_SUPERVISION_ADDR])
+		fprintf(f, "supervision %s ",
+			ll_addr_n2a(RTA_DATA(tb[IFLA_HSR_SUPERVISION_ADDR]),
+				    RTA_PAYLOAD(tb[IFLA_HSR_SUPERVISION_ADDR]),
+				    ARPHRD_VOID,
+				    b1, sizeof(b1)));
+}
+
+static void hsr_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
+}
+
+struct link_util hsr_link_util = {
+	.id		= "hsr",
+	.maxattr	= IFLA_VLAN_MAX,
+	.parse_opt	= hsr_parse_opt,
+	.print_opt	= hsr_print_opt,
+	.print_help	= hsr_print_help,
+};
diff --git a/ip/iplink_ipoib.c b/ip/iplink_ipoib.c
new file mode 100644
index 0000000..6087cbe
--- /dev/null
+++ b/ip/iplink_ipoib.c
@@ -0,0 +1,125 @@
+/*
+ * iplink_ipoib.c	IPoIB device support
+ *
+ *              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.
+ *
+ * Authors:     Or Gerlitz <ogerlitz@mellanox.com>
+ *		copied iflink_vlan.c authored by Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_link.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... ipoib [pkey PKEY] [mode {datagram | connected}]"
+		"[umcast {0|1}]\n"
+		"\n"
+		"PKEY  := 0x8001-0xffff\n"
+	);
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static int mode_arg(void)
+{
+	fprintf(stderr, "Error: argument of \"mode\" must be \"datagram\""
+		"or \"connected\"\n");
+	return -1;
+}
+
+static int ipoib_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	__u16 pkey, mode, umcast;
+
+	while (argc > 0) {
+		if (matches(*argv, "pkey") == 0) {
+			NEXT_ARG();
+			if (get_u16(&pkey, *argv, 0))
+				invarg("pkey is invalid", *argv);
+			addattr_l(n, 1024, IFLA_IPOIB_PKEY, &pkey, 2);
+		} else if (matches(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "datagram") == 0)
+				mode = IPOIB_MODE_DATAGRAM;
+			else if (strcmp(*argv, "connected") == 0)
+				mode = IPOIB_MODE_CONNECTED;
+			else
+				return mode_arg();
+			addattr_l(n, 1024, IFLA_IPOIB_MODE, &mode, 2);
+		} else if (matches(*argv, "umcast") == 0) {
+			NEXT_ARG();
+			if (get_u16(&umcast, *argv, 0))
+				invarg("umcast is invalid", *argv);
+			addattr_l(n, 1024, IFLA_IPOIB_UMCAST, &umcast, 2);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "ipoib: unknown option \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void ipoib_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	__u16 mode;
+
+	if (!tb)
+		return;
+
+	if (!tb[IFLA_IPOIB_PKEY] ||
+	    RTA_PAYLOAD(tb[IFLA_IPOIB_PKEY]) < sizeof(__u16))
+		return;
+
+	fprintf(f, "pkey  %#.4x ", rta_getattr_u16(tb[IFLA_IPOIB_PKEY]));
+
+	if (!tb[IFLA_IPOIB_MODE] ||
+	    RTA_PAYLOAD(tb[IFLA_IPOIB_MODE]) < sizeof(__u16))
+		return;
+
+	mode = rta_getattr_u16(tb[IFLA_IPOIB_MODE]);
+	fprintf(f, "mode  %s ",
+		mode == IPOIB_MODE_DATAGRAM ? "datagram" :
+		mode == IPOIB_MODE_CONNECTED ? "connected" :
+		"unknown");
+
+	if (!tb[IFLA_IPOIB_UMCAST] ||
+	    RTA_PAYLOAD(tb[IFLA_IPOIB_UMCAST]) < sizeof(__u16))
+		return;
+
+	fprintf(f, "umcast  %.4x ", rta_getattr_u16(tb[IFLA_IPOIB_UMCAST]));
+}
+
+static void ipoib_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_explain(f);
+}
+
+struct link_util ipoib_link_util = {
+	.id		= "ipoib",
+	.maxattr	= IFLA_IPOIB_MAX,
+	.parse_opt	= ipoib_parse_opt,
+	.print_opt	= ipoib_print_opt,
+	.print_help	= ipoib_print_help,
+};
diff --git a/ip/iplink_ipvlan.c b/ip/iplink_ipvlan.c
new file mode 100644
index 0000000..e08fc39
--- /dev/null
+++ b/ip/iplink_ipvlan.c
@@ -0,0 +1,98 @@
+/* iplink_ipvlan.c	IPVLAN device support
+ *
+ *              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.
+ *
+ * Authors:     Mahesh Bandewar <maheshb@google.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <linux/if_link.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void ipvlan_explain(FILE *f)
+{
+	fprintf(f, "Usage: ... ipvlan [ mode { l2 | l3 } ]\n");
+}
+
+static void explain(void)
+{
+	ipvlan_explain(stderr);
+}
+
+static int mode_arg(void)
+{
+	fprintf(stderr, "Error: argument of \"mode\" must be either \"l2\", "
+		"or \"l3\"\n");
+	return -1;
+}
+
+static int ipvlan_parse_opt(struct link_util *lu, int argc, char **argv,
+			    struct nlmsghdr *n)
+{
+	while (argc > 0) {
+		if (matches(*argv, "mode") == 0) {
+			__u16 mode = 0;
+			NEXT_ARG();
+
+			if (strcmp(*argv, "l2") == 0)
+				mode = IPVLAN_MODE_L2;
+			else if (strcmp(*argv, "l3") == 0)
+				mode = IPVLAN_MODE_L3;
+			else
+				return mode_arg();
+
+			addattr16(n, 1024, IFLA_IPVLAN_MODE, mode);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "ipvlan: unknown option \"%s\"?\n",
+				*argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void ipvlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_IPVLAN_MODE]) {
+		if (RTA_PAYLOAD(tb[IFLA_IPVLAN_MODE]) == sizeof(__u16)) {
+			__u16 mode = rta_getattr_u16(tb[IFLA_IPVLAN_MODE]);
+
+			fprintf(f, " mode %s ",
+				mode == IPVLAN_MODE_L2 ? "l2" :
+				mode == IPVLAN_MODE_L3 ? "l3" : "unknown");
+		}
+	}
+}
+
+static void ipvlan_print_help(struct link_util *lu, int argc, char **argv,
+			      FILE *f)
+{
+	ipvlan_explain(f);
+}
+
+struct link_util ipvlan_link_util = {
+	.id		= "ipvlan",
+	.maxattr	= IFLA_IPVLAN_MAX,
+	.parse_opt	= ipvlan_parse_opt,
+	.print_opt	= ipvlan_print_opt,
+	.print_help	= ipvlan_print_help,
+};
diff --git a/ip/iplink_macvlan.c b/ip/iplink_macvlan.c
index ed0e34b..826b659 100644
--- a/ip/iplink_macvlan.c
+++ b/ip/iplink_macvlan.c
@@ -20,13 +20,18 @@
 #include "utils.h"
 #include "ip_common.h"
 
-static void explain(void)
+static void print_explain(FILE *f)
 {
-	fprintf(stderr,
+	fprintf(f,
 		"Usage: ... macvlan mode { private | vepa | bridge | passthru }\n"
 	);
 }
 
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
 static int mode_arg(void)
 {
         fprintf(stderr, "Error: argument of \"mode\" must be \"private\", "
@@ -58,7 +63,7 @@
 			explain();
 			return -1;
 		} else {
-			fprintf(stderr, "macvlan: what is \"%s\"?\n", *argv);
+			fprintf(stderr, "macvlan: unknown option \"%s\"?\n", *argv);
 			explain();
 			return -1;
 		}
@@ -79,7 +84,7 @@
 	    RTA_PAYLOAD(tb[IFLA_MACVLAN_MODE]) < sizeof(__u32))
 		return;
 
-	mode = rta_getattr_u32(tb[IFLA_VLAN_ID]);
+	mode = rta_getattr_u32(tb[IFLA_MACVLAN_MODE]);
 	fprintf(f, " mode %s ",
 		  mode == MACVLAN_MODE_PRIVATE ? "private"
 		: mode == MACVLAN_MODE_VEPA    ? "vepa"
@@ -88,9 +93,16 @@
 		:				 "unknown");
 }
 
+static void macvlan_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_explain(f);
+}
+
 struct link_util macvlan_link_util = {
 	.id		= "macvlan",
 	.maxattr	= IFLA_MACVLAN_MAX,
 	.parse_opt	= macvlan_parse_opt,
 	.print_opt	= macvlan_print_opt,
+	.print_help	= macvlan_print_help,
 };
diff --git a/ip/iplink_macvtap.c b/ip/iplink_macvtap.c
index 6c7fe1f..9c2cd74 100644
--- a/ip/iplink_macvtap.c
+++ b/ip/iplink_macvtap.c
@@ -17,17 +17,22 @@
 #include "utils.h"
 #include "ip_common.h"
 
-static void explain(void)
+static void print_explain(FILE *f)
 {
 	fprintf(stderr,
 		"Usage: ... macvtap mode { private | vepa | bridge | passthru }\n"
 	);
 }
 
-static int mode_arg(void)
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static int mode_arg(const char *arg)
 {
         fprintf(stderr, "Error: argument of \"mode\" must be \"private\", "
-		"\"vepa\", \"bridge\" or \"passthru\" \n");
+		"\"vepa\", \"bridge\" or \"passthru\", not \"%s\"\n", arg);
         return -1;
 }
 
@@ -48,14 +53,14 @@
 			else if (strcmp(*argv, "passthru") == 0)
 				mode = MACVLAN_MODE_PASSTHRU;
 			else
-				return mode_arg();
+				return mode_arg(*argv);
 
 			addattr32(n, 1024, IFLA_MACVLAN_MODE, mode);
 		} else if (matches(*argv, "help") == 0) {
 			explain();
 			return -1;
 		} else {
-			fprintf(stderr, "macvtap: what is \"%s\"?\n", *argv);
+			fprintf(stderr, "macvtap: unknown command \"%s\"?\n", *argv);
 			explain();
 			return -1;
 		}
@@ -85,9 +90,16 @@
 		:				 "unknown");
 }
 
+static void macvtap_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_explain(f);
+}
+
 struct link_util macvtap_link_util = {
 	.id		= "macvtap",
 	.maxattr	= IFLA_MACVLAN_MAX,
 	.parse_opt	= macvtap_parse_opt,
 	.print_opt	= macvtap_print_opt,
+	.print_help	= macvtap_print_help,
 };
diff --git a/ip/iplink_vlan.c b/ip/iplink_vlan.c
index 97af8d6..5bd766f 100644
--- a/ip/iplink_vlan.c
+++ b/ip/iplink_vlan.c
@@ -18,24 +18,31 @@
 #include "utils.h"
 #include "ip_common.h"
 
-static void explain(void)
+static void print_explain(FILE *f)
 {
-	fprintf(stderr,
-		"Usage: ... vlan id VLANID [ FLAG-LIST ]\n"
-		"                          [ ingress-qos-map QOS-MAP ] [ egress-qos-map QOS-MAP ]\n"
+	fprintf(f,
+		"Usage: ... vlan [ protocol VLANPROTO ] id VLANID"
+		"                [ FLAG-LIST ]\n"
+		"                [ ingress-qos-map QOS-MAP ] [ egress-qos-map QOS-MAP ]\n"
 		"\n"
+		"VLANPROTO: [ 802.1Q / 802.1ad ]\n"
 		"VLANID := 0-4095\n"
 		"FLAG-LIST := [ FLAG-LIST ] FLAG\n"
-		"FLAG := [ reorder_hdr { on | off } ] [ gvrp { on | off } ]\n"
+		"FLAG := [ reorder_hdr { on | off } ] [ gvrp { on | off } ] [ mvrp { on | off } ]\n"
 		"        [ loose_binding { on | off } ]\n"
 		"QOS-MAP := [ QOS-MAP ] QOS-MAPPING\n"
 		"QOS-MAPPING := FROM:TO\n"
 	);
 }
 
-static int on_off(char *msg)
+static void explain(void)
 {
-	fprintf(stderr, "Error: argument of \"%s\" must be \"on\" or \"off\"\n", msg);
+	print_explain(stderr);
+}
+
+static int on_off(const char *msg, const char *arg)
+{
+	fprintf(stderr, "Error: argument of \"%s\" must be \"on\" or \"off\", not \"%s\"\n", msg, arg);
 	return -1;
 }
 
@@ -77,10 +84,15 @@
 			  struct nlmsghdr *n)
 {
 	struct ifla_vlan_flags flags = { 0 };
-	__u16 id;
+	__u16 id, proto;
 
 	while (argc > 0) {
-		if (matches(*argv, "id") == 0) {
+		if (matches(*argv, "protocol") == 0) {
+			NEXT_ARG();
+			if (ll_proto_a2n(&proto, *argv))
+				invarg("protocol is invalid", *argv);
+			addattr_l(n, 1024, IFLA_VLAN_PROTOCOL, &proto, 2);
+		} else if (matches(*argv, "id") == 0) {
 			NEXT_ARG();
 			if (get_u16(&id, *argv, 0))
 				invarg("id is invalid", *argv);
@@ -93,7 +105,7 @@
 			else if (strcmp(*argv, "off") == 0)
 				flags.flags &= ~VLAN_FLAG_REORDER_HDR;
 			else
-				return on_off("reorder_hdr");
+				return on_off("reorder_hdr", *argv);
 		} else if (matches(*argv, "gvrp") == 0) {
 			NEXT_ARG();
 			flags.mask |= VLAN_FLAG_GVRP;
@@ -102,7 +114,16 @@
 			else if (strcmp(*argv, "off") == 0)
 				flags.flags &= ~VLAN_FLAG_GVRP;
 			else
-				return on_off("gvrp");
+				return on_off("gvrp", *argv);
+		} else if (matches(*argv, "mvrp") == 0) {
+			NEXT_ARG();
+			flags.mask |= VLAN_FLAG_MVRP;
+			if (strcmp(*argv, "on") == 0)
+				flags.flags |= VLAN_FLAG_MVRP;
+			else if (strcmp(*argv, "off") == 0)
+				flags.flags &= ~VLAN_FLAG_MVRP;
+			else
+				return on_off("mvrp", *argv);
 		} else if (matches(*argv, "loose_binding") == 0) {
 			NEXT_ARG();
 			flags.mask |= VLAN_FLAG_LOOSE_BINDING;
@@ -111,7 +132,7 @@
 			else if (strcmp(*argv, "off") == 0)
 				flags.flags &= ~VLAN_FLAG_LOOSE_BINDING;
 			else
-				return on_off("loose_binding");
+				return on_off("loose_binding", *argv);
 		} else if (matches(*argv, "ingress-qos-map") == 0) {
 			NEXT_ARG();
 			if (vlan_parse_qos_map(&argc, &argv, n,
@@ -128,7 +149,7 @@
 			explain();
 			return -1;
 		} else {
-			fprintf(stderr, "vlan: what is \"%s\"?\n", *argv);
+			fprintf(stderr, "vlan: unknown command \"%s\"?\n", *argv);
 			explain();
 			return -1;
 		}
@@ -166,6 +187,7 @@
 		}
 	_PF(REORDER_HDR);
 	_PF(GVRP);
+	_PF(MVRP);
 	_PF(LOOSE_BINDING);
 #undef _PF
 	if (flags)
@@ -176,13 +198,25 @@
 static void vlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
 {
 	struct ifla_vlan_flags *flags;
+	SPRINT_BUF(b1);
+
 	if (!tb)
 		return;
 
+	if (tb[IFLA_VLAN_PROTOCOL] &&
+	    RTA_PAYLOAD(tb[IFLA_VLAN_PROTOCOL]) < sizeof(__u16))
+		return;
 	if (!tb[IFLA_VLAN_ID] ||
 	    RTA_PAYLOAD(tb[IFLA_VLAN_ID]) < sizeof(__u16))
 		return;
 
+	if (tb[IFLA_VLAN_PROTOCOL])
+		fprintf(f, "protocol %s ",
+			ll_proto_n2a(rta_getattr_u16(tb[IFLA_VLAN_PROTOCOL]),
+				     b1, sizeof(b1)));
+	else
+		fprintf(f, "protocol 802.1q ");
+
 	fprintf(f, "id %u ", rta_getattr_u16(tb[IFLA_VLAN_ID]));
 
 	if (tb[IFLA_VLAN_FLAGS]) {
@@ -197,9 +231,16 @@
 		vlan_print_map(f, "egress-qos-map", tb[IFLA_VLAN_EGRESS_QOS]);
 }
 
+static void vlan_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_explain(f);
+}
+
 struct link_util vlan_link_util = {
 	.id		= "vlan",
 	.maxattr	= IFLA_VLAN_MAX,
 	.parse_opt	= vlan_parse_opt,
 	.print_opt	= vlan_print_opt,
+	.print_help	= vlan_print_help,
 };
diff --git a/ip/iplink_vxlan.c b/ip/iplink_vxlan.c
new file mode 100644
index 0000000..473ff97
--- /dev/null
+++ b/ip/iplink_vxlan.c
@@ -0,0 +1,426 @@
+/*
+ * iplink_vxlan.c	VXLAN device support
+ *
+ *              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.
+ *
+ * Authors:     Stephen Hemminger <shemminger@vyatta.com
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/ip.h>
+#include <linux/if_link.h>
+#include <arpa/inet.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+	fprintf(f, "Usage: ... vxlan id VNI [ { group | remote } ADDR ] [ local ADDR ]\n");
+	fprintf(f, "                 [ ttl TTL ] [ tos TOS ] [ dev PHYS_DEV ]\n");
+	fprintf(f, "                 [ dstport PORT ] [ srcport MIN MAX ]\n");
+	fprintf(f, "                 [ [no]learning ] [ [no]proxy ] [ [no]rsc ]\n");
+	fprintf(f, "                 [ [no]l2miss ] [ [no]l3miss ]\n");
+	fprintf(f, "                 [ ageing SECONDS ] [ maxaddress NUMBER ]\n");
+	fprintf(f, "                 [ [no]udpcsum ] [ [no]udp6zerocsumtx ] [ [no]udp6zerocsumrx ]\n");
+	fprintf(f, "                 [ gbp ]\n");
+	fprintf(f, "\n");
+	fprintf(f, "Where: VNI := 0-16777215\n");
+	fprintf(f, "       ADDR := { IP_ADDRESS | any }\n");
+	fprintf(f, "       TOS  := { NUMBER | inherit }\n");
+	fprintf(f, "       TTL  := { 1..255 | inherit }\n");
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	__u32 vni = 0;
+	int vni_set = 0;
+	__u32 saddr = 0;
+	__u32 gaddr = 0;
+	__u32 daddr = 0;
+	struct in6_addr saddr6 = IN6ADDR_ANY_INIT;
+	struct in6_addr gaddr6 = IN6ADDR_ANY_INIT;
+	struct in6_addr daddr6 = IN6ADDR_ANY_INIT;
+	unsigned link = 0;
+	__u8 tos = 0;
+	__u8 ttl = 0;
+	__u8 learning = 1;
+	__u8 proxy = 0;
+	__u8 rsc = 0;
+	__u8 l2miss = 0;
+	__u8 l3miss = 0;
+	__u8 noage = 0;
+	__u32 age = 0;
+	__u32 maxaddr = 0;
+	__u16 dstport = 0;
+	__u8 udpcsum = 0;
+	__u8 udp6zerocsumtx = 0;
+	__u8 udp6zerocsumrx = 0;
+	__u8 gbp = 0;
+	int dst_port_set = 0;
+	struct ifla_vxlan_port_range range = { 0, 0 };
+
+	while (argc > 0) {
+		if (!matches(*argv, "id") ||
+		    !matches(*argv, "vni")) {
+			NEXT_ARG();
+			if (get_u32(&vni, *argv, 0) ||
+			    vni >= 1u << 24)
+				invarg("invalid id", *argv);
+			vni_set = 1;
+		} else if (!matches(*argv, "group")) {
+			NEXT_ARG();
+			if (!inet_get_addr(*argv, &gaddr, &gaddr6)) {
+				fprintf(stderr, "Invalid address \"%s\"\n", *argv);
+				return -1;
+			}
+			if (!IN6_IS_ADDR_MULTICAST(&gaddr6) && !IN_MULTICAST(ntohl(gaddr)))
+				invarg("invalid group address", *argv);
+		} else if (!matches(*argv, "remote")) {
+			NEXT_ARG();
+			if (!inet_get_addr(*argv, &daddr, &daddr6)) {
+				fprintf(stderr, "Invalid address \"%s\"\n", *argv);
+				return -1;
+			}
+			if (IN6_IS_ADDR_MULTICAST(&daddr6) || IN_MULTICAST(ntohl(daddr)))
+				invarg("invalid remote address", *argv);
+		} else if (!matches(*argv, "local")) {
+			NEXT_ARG();
+			if (strcmp(*argv, "any")) {
+				if (!inet_get_addr(*argv, &saddr, &saddr6)) {
+					fprintf(stderr, "Invalid address \"%s\"\n", *argv);
+					return -1;
+				}
+			}
+
+			if (IN_MULTICAST(ntohl(saddr)) || IN6_IS_ADDR_MULTICAST(&saddr6))
+				invarg("invalid local address", *argv);
+		} else if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			link = if_nametoindex(*argv);
+			if (link == 0) {
+				fprintf(stderr, "Cannot find device \"%s\"\n",
+					*argv);
+				exit(-1);
+			}
+		} else if (!matches(*argv, "ttl") ||
+			   !matches(*argv, "hoplimit")) {
+			unsigned uval;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (get_unsigned(&uval, *argv, 0))
+					invarg("invalid TTL", *argv);
+				if (uval > 255)
+					invarg("TTL must be <= 255", *argv);
+				ttl = uval;
+			}
+		} else if (!matches(*argv, "tos") ||
+			   !matches(*argv, "dsfield")) {
+			__u32 uval;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (rtnl_dsfield_a2n(&uval, *argv))
+					invarg("bad TOS value", *argv);
+				tos = uval;
+			} else
+				tos = 1;
+		} else if (!matches(*argv, "ageing")) {
+			NEXT_ARG();
+			if (strcmp(*argv, "none") == 0)
+				noage = 1;
+			else if (get_u32(&age, *argv, 0))
+				invarg("ageing timer", *argv);
+		} else if (!matches(*argv, "maxaddress")) {
+			NEXT_ARG();
+			if (strcmp(*argv, "unlimited") == 0)
+				maxaddr = 0;
+			else if (get_u32(&maxaddr, *argv, 0))
+				invarg("max addresses", *argv);
+		} else if (!matches(*argv, "port") ||
+			   !matches(*argv, "srcport")) {
+			__u16 minport, maxport;
+			NEXT_ARG();
+			if (get_u16(&minport, *argv, 0))
+				invarg("min port", *argv);
+			NEXT_ARG();
+			if (get_u16(&maxport, *argv, 0))
+				invarg("max port", *argv);
+			range.low = htons(minport);
+			range.high = htons(maxport);
+		} else if (!matches(*argv, "dstport")){
+			NEXT_ARG();
+			if (get_u16(&dstport, *argv, 0))
+				invarg("dst port", *argv);
+			dst_port_set = 1;
+		} else if (!matches(*argv, "nolearning")) {
+			learning = 0;
+		} else if (!matches(*argv, "learning")) {
+			learning = 1;
+		} else if (!matches(*argv, "noproxy")) {
+			proxy = 0;
+		} else if (!matches(*argv, "proxy")) {
+			proxy = 1;
+		} else if (!matches(*argv, "norsc")) {
+			rsc = 0;
+		} else if (!matches(*argv, "rsc")) {
+			rsc = 1;
+		} else if (!matches(*argv, "nol2miss")) {
+			l2miss = 0;
+		} else if (!matches(*argv, "l2miss")) {
+			l2miss = 1;
+		} else if (!matches(*argv, "nol3miss")) {
+			l3miss = 0;
+		} else if (!matches(*argv, "l3miss")) {
+			l3miss = 1;
+		} else if (!matches(*argv, "udpcsum")) {
+			udpcsum = 1;
+		} else if (!matches(*argv, "noudpcsum")) {
+			udpcsum = 0;
+		} else if (!matches(*argv, "udp6zerocsumtx")) {
+			udp6zerocsumtx = 1;
+		} else if (!matches(*argv, "noudp6zerocsumtx")) {
+			udp6zerocsumtx = 0;
+		} else if (!matches(*argv, "udp6zerocsumrx")) {
+			udp6zerocsumrx = 1;
+		} else if (!matches(*argv, "noudp6zerocsumrx")) {
+			udp6zerocsumrx = 0;
+		} else if (!matches(*argv, "gbp")) {
+			gbp = 1;
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "vxlan: unknown command \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (!vni_set) {
+		fprintf(stderr, "vxlan: missing virtual network identifier\n");
+		return -1;
+	}
+
+	if ((gaddr && daddr) ||
+		(memcmp(&gaddr6, &in6addr_any, sizeof(gaddr6)) &&
+		 memcmp(&daddr6, &in6addr_any, sizeof(daddr6)))) {
+		fprintf(stderr, "vxlan: both group and remote cannot be specified\n");
+		return -1;
+	}
+
+	if (!dst_port_set) {
+		fprintf(stderr, "vxlan: destination port not specified\n"
+			"Will use Linux kernel default (non-standard value)\n");
+		fprintf(stderr,
+			"Use 'dstport 4789' to get the IANA assigned value\n"
+			"Use 'dstport 0' to get default and quiet this message\n");
+	}
+
+	addattr32(n, 1024, IFLA_VXLAN_ID, vni);
+	if (gaddr)
+		addattr_l(n, 1024, IFLA_VXLAN_GROUP, &gaddr, 4);
+	else if (daddr)
+		addattr_l(n, 1024, IFLA_VXLAN_GROUP, &daddr, 4);
+	if (memcmp(&gaddr6, &in6addr_any, sizeof(gaddr6)) != 0)
+		addattr_l(n, 1024, IFLA_VXLAN_GROUP6, &gaddr6, sizeof(struct in6_addr));
+	else if (memcmp(&daddr6, &in6addr_any, sizeof(daddr6)) != 0)
+		addattr_l(n, 1024, IFLA_VXLAN_GROUP6, &daddr6, sizeof(struct in6_addr));
+
+	if (saddr)
+		addattr_l(n, 1024, IFLA_VXLAN_LOCAL, &saddr, 4);
+	else if (memcmp(&saddr6, &in6addr_any, sizeof(saddr6)) != 0)
+		addattr_l(n, 1024, IFLA_VXLAN_LOCAL6, &saddr6, sizeof(struct in6_addr));
+
+	if (link)
+		addattr32(n, 1024, IFLA_VXLAN_LINK, link);
+	addattr8(n, 1024, IFLA_VXLAN_TTL, ttl);
+	addattr8(n, 1024, IFLA_VXLAN_TOS, tos);
+	addattr8(n, 1024, IFLA_VXLAN_LEARNING, learning);
+	addattr8(n, 1024, IFLA_VXLAN_PROXY, proxy);
+	addattr8(n, 1024, IFLA_VXLAN_RSC, rsc);
+	addattr8(n, 1024, IFLA_VXLAN_L2MISS, l2miss);
+	addattr8(n, 1024, IFLA_VXLAN_L3MISS, l3miss);
+	addattr8(n, 1024, IFLA_VXLAN_UDP_CSUM, udpcsum);
+	addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_TX, udp6zerocsumtx);
+	addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_RX, udp6zerocsumrx);
+
+	if (noage)
+		addattr32(n, 1024, IFLA_VXLAN_AGEING, 0);
+	else if (age)
+		addattr32(n, 1024, IFLA_VXLAN_AGEING, age);
+	if (maxaddr)
+		addattr32(n, 1024, IFLA_VXLAN_LIMIT, maxaddr);
+	if (range.low || range.high)
+		addattr_l(n, 1024, IFLA_VXLAN_PORT_RANGE,
+			  &range, sizeof(range));
+	if (dstport)
+		addattr16(n, 1024, IFLA_VXLAN_PORT, htons(dstport));
+
+	if (gbp)
+		addattr_l(n, 1024, IFLA_VXLAN_GBP, NULL, 0);
+
+
+	return 0;
+}
+
+static void vxlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	__u32 vni;
+	unsigned link;
+	__u8 tos;
+	__u32 maxaddr;
+	char s1[1024];
+	char s2[64];
+
+	if (!tb)
+		return;
+
+	if (!tb[IFLA_VXLAN_ID] ||
+	    RTA_PAYLOAD(tb[IFLA_VXLAN_ID]) < sizeof(__u32))
+		return;
+
+	vni = rta_getattr_u32(tb[IFLA_VXLAN_ID]);
+	fprintf(f, "id %u ", vni);
+
+	if (tb[IFLA_VXLAN_GROUP]) {
+		__be32 addr = rta_getattr_u32(tb[IFLA_VXLAN_GROUP]);
+		if (addr) {
+			if (IN_MULTICAST(ntohl(addr)))
+				fprintf(f, "group %s ",
+					format_host(AF_INET, 4, &addr, s1, sizeof(s1)));
+			else
+				fprintf(f, "remote %s ",
+					format_host(AF_INET, 4, &addr, s1, sizeof(s1)));
+		}
+	} else if (tb[IFLA_VXLAN_GROUP6]) {
+		struct in6_addr addr;
+		memcpy(&addr, RTA_DATA(tb[IFLA_VXLAN_GROUP6]), sizeof(struct in6_addr));
+		if (memcmp(&addr, &in6addr_any, sizeof(addr)) != 0) {
+			if (IN6_IS_ADDR_MULTICAST(&addr))
+				fprintf(f, "group %s ",
+					format_host(AF_INET6, sizeof(struct in6_addr), &addr, s1, sizeof(s1)));
+			else
+				fprintf(f, "remote %s ",
+					format_host(AF_INET6, sizeof(struct in6_addr), &addr, s1, sizeof(s1)));
+		}
+	}
+
+	if (tb[IFLA_VXLAN_LOCAL]) {
+		__be32 addr = rta_getattr_u32(tb[IFLA_VXLAN_LOCAL]);
+		if (addr)
+			fprintf(f, "local %s ",
+				format_host(AF_INET, 4, &addr, s1, sizeof(s1)));
+	} else if (tb[IFLA_VXLAN_LOCAL6]) {
+		struct in6_addr addr;
+		memcpy(&addr, RTA_DATA(tb[IFLA_VXLAN_LOCAL6]), sizeof(struct in6_addr));
+		if (memcmp(&addr, &in6addr_any, sizeof(addr)) != 0)
+			fprintf(f, "local %s ",
+				format_host(AF_INET6, sizeof(struct in6_addr), &addr, s1, sizeof(s1)));
+	}
+
+	if (tb[IFLA_VXLAN_LINK] &&
+	    (link = rta_getattr_u32(tb[IFLA_VXLAN_LINK]))) {
+		const char *n = if_indextoname(link, s2);
+
+		if (n)
+			fprintf(f, "dev %s ", n);
+		else
+			fprintf(f, "dev %u ", link);
+	}
+
+	if (tb[IFLA_VXLAN_PORT_RANGE]) {
+		const struct ifla_vxlan_port_range *r
+			= RTA_DATA(tb[IFLA_VXLAN_PORT_RANGE]);
+		fprintf(f, "srcport %u %u ", ntohs(r->low), ntohs(r->high));
+	}
+
+	if (tb[IFLA_VXLAN_PORT])
+		fprintf(f, "dstport %u ",
+			ntohs(rta_getattr_u16(tb[IFLA_VXLAN_PORT])));
+
+	if (tb[IFLA_VXLAN_LEARNING] &&
+	    !rta_getattr_u8(tb[IFLA_VXLAN_LEARNING]))
+		fputs("nolearning ", f);
+
+	if (tb[IFLA_VXLAN_PROXY] && rta_getattr_u8(tb[IFLA_VXLAN_PROXY]))
+		fputs("proxy ", f);
+
+	if (tb[IFLA_VXLAN_RSC] && rta_getattr_u8(tb[IFLA_VXLAN_RSC]))
+		fputs("rsc ", f);
+
+	if (tb[IFLA_VXLAN_L2MISS] && rta_getattr_u8(tb[IFLA_VXLAN_L2MISS]))
+		fputs("l2miss ", f);
+
+	if (tb[IFLA_VXLAN_L3MISS] && rta_getattr_u8(tb[IFLA_VXLAN_L3MISS]))
+		fputs("l3miss ", f);
+
+	if (tb[IFLA_VXLAN_TOS] &&
+	    (tos = rta_getattr_u8(tb[IFLA_VXLAN_TOS]))) {
+		if (tos == 1)
+			fprintf(f, "tos inherit ");
+		else
+			fprintf(f, "tos %#x ", tos);
+	}
+
+	if (tb[IFLA_VXLAN_TTL]) {
+		__u8 ttl = rta_getattr_u8(tb[IFLA_VXLAN_TTL]);
+		if (ttl)
+			fprintf(f, "ttl %d ", ttl);
+	}
+
+	if (tb[IFLA_VXLAN_AGEING]) {
+		__u32 age = rta_getattr_u32(tb[IFLA_VXLAN_AGEING]);
+		if (age == 0)
+			fprintf(f, "ageing none ");
+		else
+			fprintf(f, "ageing %u ", age);
+	}
+
+	if (tb[IFLA_VXLAN_LIMIT] &&
+	    ((maxaddr = rta_getattr_u32(tb[IFLA_VXLAN_LIMIT])) != 0))
+		    fprintf(f, "maxaddr %u ", maxaddr);
+
+	if (tb[IFLA_VXLAN_UDP_CSUM] && rta_getattr_u8(tb[IFLA_VXLAN_UDP_CSUM]))
+		fputs("udpcsum ", f);
+
+	if (tb[IFLA_VXLAN_UDP_ZERO_CSUM6_TX] &&
+	    rta_getattr_u8(tb[IFLA_VXLAN_UDP_ZERO_CSUM6_TX]))
+		fputs("udp6zerocsumtx ", f);
+
+	if (tb[IFLA_VXLAN_UDP_ZERO_CSUM6_RX] &&
+	    rta_getattr_u8(tb[IFLA_VXLAN_UDP_ZERO_CSUM6_RX]))
+		fputs("udp6zerocsumrx ", f);
+
+	if (tb[IFLA_VXLAN_GBP])
+		fputs("gbp ", f);
+}
+
+static void vxlan_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_explain(f);
+}
+
+struct link_util vxlan_link_util = {
+	.id		= "vxlan",
+	.maxattr	= IFLA_VXLAN_MAX,
+	.parse_opt	= vxlan_parse_opt,
+	.print_opt	= vxlan_print_opt,
+	.print_help	= vxlan_print_help,
+};
diff --git a/ip/ipmaddr.c b/ip/ipmaddr.c
index 3ae9478..a77a18f 100644
--- a/ip/ipmaddr.c
+++ b/ip/ipmaddr.c
@@ -28,6 +28,7 @@
 
 #include "rt_names.h"
 #include "utils.h"
+#include "ip_common.h"
 
 static struct {
 	char *dev;
@@ -45,7 +46,7 @@
 
 static int parse_hex(char *str, unsigned char *addr, size_t size)
 {
-	int len=0;
+	int len = 0;
 
 	while (*str && (len < 2 * size)) {
 		int tmp;
@@ -70,11 +71,11 @@
 	inet_prefix	addr;
 };
 
-void maddr_ins(struct ma_info **lst, struct ma_info *m)
+static void maddr_ins(struct ma_info **lst, struct ma_info *m)
 {
 	struct ma_info *mp;
 
-	for (; (mp=*lst) != NULL; lst = &mp->next) {
+	for (; (mp = *lst) != NULL; lst = &mp->next) {
 		if (mp->index > m->index)
 			break;
 	}
@@ -82,7 +83,7 @@
 	*lst = m;
 }
 
-void read_dev_mcast(struct ma_info **result_p)
+static void read_dev_mcast(struct ma_info **result_p)
 {
 	char buf[256];
 	FILE *fp = fopen("/proc/net/dev_mcast", "r");
@@ -119,7 +120,7 @@
 	fclose(fp);
 }
 
-void read_igmp(struct ma_info **result_p)
+static void read_igmp(struct ma_info **result_p)
 {
 	struct ma_info m;
 	char buf[256];
@@ -158,7 +159,7 @@
 }
 
 
-void read_igmp6(struct ma_info **result_p)
+static void read_igmp6(struct ma_info **result_p)
 {
 	char buf[256];
 	FILE *fp = fopen("/proc/net/igmp6", "r");
@@ -275,7 +276,7 @@
 	return 0;
 }
 
-int multiaddr_modify(int cmd, int argc, char **argv)
+static int multiaddr_modify(int cmd, int argc, char **argv)
 {
 	struct ifreq ifr;
 	int fd;
diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c
index 4b1d469..6b5e665 100644
--- a/ip/ipmonitor.c
+++ b/ip/ipmonitor.c
@@ -29,25 +29,51 @@
 
 static void usage(void)
 {
-	fprintf(stderr, "Usage: ip monitor [ all | LISTofOBJECTS ]\n");
+	fprintf(stderr, "Usage: ip monitor [ all | LISTofOBJECTS ] [ FILE ]"
+			"[ label ] [dev DEVICE]\n");
+	fprintf(stderr, "LISTofOBJECTS := link | address | route | mroute | prefix |\n");
+	fprintf(stderr, "                 neigh | netconf | rule\n");
+	fprintf(stderr, "FILE := file FILENAME\n");
 	exit(-1);
 }
 
-
-int accept_msg(const struct sockaddr_nl *who,
-	       struct nlmsghdr *n, void *arg)
+static int accept_msg(const struct sockaddr_nl *who,
+		      struct nlmsghdr *n, void *arg)
 {
 	FILE *fp = (FILE*)arg;
 
+	if (n->nlmsg_type == RTM_NEWROUTE || n->nlmsg_type == RTM_DELROUTE) {
+		struct rtmsg *r = NLMSG_DATA(n);
+		int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
+
+		if (len < 0) {
+			fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+			return -1;
+		}
+
+		if (r->rtm_flags & RTM_F_CLONED)
+			return 0;
+
+		if (timestamp)
+			print_timestamp(fp);
+
+		if (r->rtm_family == RTNL_FAMILY_IPMR ||
+		    r->rtm_family == RTNL_FAMILY_IP6MR) {
+			if (prefix_banner)
+				fprintf(fp, "[MROUTE]");
+			print_mroute(who, n, arg);
+			return 0;
+		} else {
+			if (prefix_banner)
+				fprintf(fp, "[ROUTE]");
+			print_route(who, n, arg);
+			return 0;
+		}
+	}
+
 	if (timestamp)
 		print_timestamp(fp);
 
-	if (n->nlmsg_type == RTM_NEWROUTE || n->nlmsg_type == RTM_DELROUTE) {
-		if (prefix_banner)
-			fprintf(fp, "[ROUTE]");
-		print_route(who, n, arg);
-		return 0;
-	}
 	if (n->nlmsg_type == RTM_NEWLINK || n->nlmsg_type == RTM_DELLINK) {
 		ll_remember_index(who, n, NULL);
 		if (prefix_banner)
@@ -67,7 +93,15 @@
 		print_addrlabel(who, n, arg);
 		return 0;
 	}
-	if (n->nlmsg_type == RTM_NEWNEIGH || n->nlmsg_type == RTM_DELNEIGH) {
+	if (n->nlmsg_type == RTM_NEWNEIGH || n->nlmsg_type == RTM_DELNEIGH ||
+	    n->nlmsg_type == RTM_GETNEIGH) {
+		if (preferred_family) {
+			struct ndmsg *r = NLMSG_DATA(n);
+
+			if (r->ndm_family != preferred_family)
+				return 0;
+		}
+
 		if (prefix_banner)
 			fprintf(fp, "[NEIGH]");
 		print_neigh(who, n, arg);
@@ -85,26 +119,22 @@
 		print_rule(who, n, arg);
 		return 0;
 	}
-	if (n->nlmsg_type == 15) {
-		char *tstr;
-		time_t secs = ((__u32*)NLMSG_DATA(n))[0];
-		long usecs = ((__u32*)NLMSG_DATA(n))[1];
-		tstr = asctime(localtime(&secs));
-		tstr[strlen(tstr)-1] = 0;
-		fprintf(fp, "Timestamp: %s %lu us\n", tstr, usecs);
+	if (n->nlmsg_type == RTM_NEWNETCONF) {
+		if (prefix_banner)
+			fprintf(fp, "[NETCONF]");
+		print_netconf(who, n, arg);
 		return 0;
 	}
-	if (n->nlmsg_type == RTM_NEWQDISC ||
-	    n->nlmsg_type == RTM_DELQDISC ||
-	    n->nlmsg_type == RTM_NEWTCLASS ||
-	    n->nlmsg_type == RTM_DELTCLASS ||
-	    n->nlmsg_type == RTM_NEWTFILTER ||
-	    n->nlmsg_type == RTM_DELTFILTER)
+	if (n->nlmsg_type == NLMSG_TSTAMP) {
+		print_nlmsg_timestamp(fp, n);
 		return 0;
+	}
 	if (n->nlmsg_type != NLMSG_ERROR && n->nlmsg_type != NLMSG_NOOP &&
 	    n->nlmsg_type != NLMSG_DONE) {
-		fprintf(fp, "Unknown message: %08x %08x %08x\n",
-			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		fprintf(fp, "Unknown message: type=0x%08x(%d) flags=0x%08x(%d)"
+			"len=0x%08x(%d)\n", n->nlmsg_type, n->nlmsg_type,
+			n->nlmsg_flags, n->nlmsg_flags, n->nlmsg_len,
+			n->nlmsg_len);
 	}
 	return 0;
 }
@@ -112,22 +142,39 @@
 int do_ipmonitor(int argc, char **argv)
 {
 	char *file = NULL;
-	unsigned groups = ~RTMGRP_TC;
+	unsigned groups = 0;
 	int llink=0;
 	int laddr=0;
 	int lroute=0;
+	int lmroute=0;
 	int lprefix=0;
 	int lneigh=0;
+	int lnetconf=0;
+	int lrule=0;
+	int ifindex=0;
+
+	groups |= nl_mgrp(RTNLGRP_LINK);
+	groups |= nl_mgrp(RTNLGRP_IPV4_IFADDR);
+	groups |= nl_mgrp(RTNLGRP_IPV6_IFADDR);
+	groups |= nl_mgrp(RTNLGRP_IPV4_ROUTE);
+	groups |= nl_mgrp(RTNLGRP_IPV6_ROUTE);
+	groups |= nl_mgrp(RTNLGRP_IPV4_MROUTE);
+	groups |= nl_mgrp(RTNLGRP_IPV6_MROUTE);
+	groups |= nl_mgrp(RTNLGRP_IPV6_PREFIX);
+	groups |= nl_mgrp(RTNLGRP_NEIGH);
+	groups |= nl_mgrp(RTNLGRP_IPV4_NETCONF);
+	groups |= nl_mgrp(RTNLGRP_IPV6_NETCONF);
+	groups |= nl_mgrp(RTNLGRP_IPV4_RULE);
+	groups |= nl_mgrp(RTNLGRP_IPV6_RULE);
 
 	rtnl_close(&rth);
-	ipaddr_reset_filter(1);
-	iproute_reset_filter();
-	ipneigh_reset_filter();
 
 	while (argc > 0) {
 		if (matches(*argv, "file") == 0) {
 			NEXT_ARG();
 			file = *argv;
+		} else if (matches(*argv, "label") == 0) {
+			prefix_banner = 1;
 		} else if (matches(*argv, "link") == 0) {
 			llink=1;
 			groups = 0;
@@ -137,17 +184,31 @@
 		} else if (matches(*argv, "route") == 0) {
 			lroute=1;
 			groups = 0;
+		} else if (matches(*argv, "mroute") == 0) {
+			lmroute=1;
+			groups = 0;
 		} else if (matches(*argv, "prefix") == 0) {
 			lprefix=1;
 			groups = 0;
 		} else if (matches(*argv, "neigh") == 0) {
 			lneigh = 1;
 			groups = 0;
+		} else if (matches(*argv, "netconf") == 0) {
+			lnetconf = 1;
+			groups = 0;
+		} else if (matches(*argv, "rule") == 0) {
+			lrule = 1;
+			groups = 0;
 		} else if (strcmp(*argv, "all") == 0) {
-			groups = ~RTMGRP_TC;
 			prefix_banner=1;
 		} else if (matches(*argv, "help") == 0) {
 			usage();
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Device does not exist\n", *argv);
 		} else {
 			fprintf(stderr, "Argument \"%s\" is unknown, try \"ip monitor help\".\n", *argv);
 			exit(-1);
@@ -155,6 +216,12 @@
 		argc--;	argv++;
 	}
 
+	ipaddr_reset_filter(1, ifindex);
+	iproute_reset_filter(ifindex);
+	ipmroute_reset_filter(ifindex);
+	ipneigh_reset_filter(ifindex);
+	ipnetconf_reset_filter(ifindex);
+
 	if (llink)
 		groups |= nl_mgrp(RTNLGRP_LINK);
 	if (laddr) {
@@ -169,6 +236,12 @@
 		if (!preferred_family || preferred_family == AF_INET6)
 			groups |= nl_mgrp(RTNLGRP_IPV6_ROUTE);
 	}
+	if (lmroute) {
+		if (!preferred_family || preferred_family == AF_INET)
+			groups |= nl_mgrp(RTNLGRP_IPV4_MROUTE);
+		if (!preferred_family || preferred_family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_MROUTE);
+	}
 	if (lprefix) {
 		if (!preferred_family || preferred_family == AF_INET6)
 			groups |= nl_mgrp(RTNLGRP_IPV6_PREFIX);
@@ -176,6 +249,18 @@
 	if (lneigh) {
 		groups |= nl_mgrp(RTNLGRP_NEIGH);
 	}
+	if (lnetconf) {
+		if (!preferred_family || preferred_family == AF_INET)
+			groups |= nl_mgrp(RTNLGRP_IPV4_NETCONF);
+		if (!preferred_family || preferred_family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_NETCONF);
+	}
+	if (lrule) {
+		if (!preferred_family || preferred_family == AF_INET)
+			groups |= nl_mgrp(RTNLGRP_IPV4_RULE);
+		if (!preferred_family || preferred_family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_RULE);
+	}
 	if (file) {
 		FILE *fp;
 		fp = fopen(file, "r");
diff --git a/ip/ipmroute.c b/ip/ipmroute.c
index 945727d..b4ed9f1 100644
--- a/ip/ipmroute.c
+++ b/ip/ipmroute.c
@@ -15,6 +15,7 @@
 #include <unistd.h>
 #include <syslog.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -26,167 +27,231 @@
 #include <linux/if_arp.h>
 #include <linux/sockios.h>
 
+#include <rt_names.h>
 #include "utils.h"
-
-char filter_dev[16];
-int  filter_family;
+#include "ip_common.h"
 
 static void usage(void) __attribute__((noreturn));
 
 static void usage(void)
 {
-	fprintf(stderr, "Usage: ip mroute show [ PREFIX ] [ from PREFIX ] [ iif DEVICE ]\n");
+	fprintf(stderr, "Usage: ip mroute show [ [ to ] PREFIX ] [ from PREFIX ] [ iif DEVICE ]\n");
+	fprintf(stderr, "                      [ table TABLE_ID ]\n");
+	fprintf(stderr, "TABLE_ID := [ local | main | default | all | NUMBER ]\n");
 #if 0
 	fprintf(stderr, "Usage: ip mroute [ add | del ] DESTINATION from SOURCE [ iif DEVICE ] [ oif DEVICE ]\n");
 #endif
 	exit(-1);
 }
 
-static char *viftable[32];
-
 struct rtfilter
 {
+	int tb;
+	int af;
+	int iif;
 	inet_prefix mdst;
 	inet_prefix msrc;
 } filter;
 
-static void read_viftable(void)
+int print_mroute(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
 {
-	char buf[256];
-	FILE *fp = fopen("/proc/net/ip_mr_vif", "r");
+	FILE *fp = (FILE*)arg;
+	struct rtmsg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr * tb[RTA_MAX+1];
+	char abuf[256];
+	char obuf[256];
+	SPRINT_BUF(b1);
+	__u32 table;
+	int iif = 0;
+	int family;
 
-	if (!fp)
-		return;
-
-	if (!fgets(buf, sizeof(buf), fp)) {
-		fclose(fp);
-		return;
+	if ((n->nlmsg_type != RTM_NEWROUTE &&
+	     n->nlmsg_type != RTM_DELROUTE) ||
+	    !(n->nlmsg_flags & NLM_F_MULTI)) {
+		fprintf(stderr, "Not a multicast route: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
 	}
-	while (fgets(buf, sizeof(buf), fp)) {
-		int vifi;
-		char dev[256];
-
-		if (sscanf(buf, "%d%s", &vifi, dev) < 2)
-			continue;
-
-		if (vifi<0 || vifi>31)
-			continue;
-
-		viftable[vifi] = strdup(dev);
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
 	}
-	fclose(fp);
-}
-
-static void read_mroute_list(FILE *ofp)
-{
-	char buf[256];
-	FILE *fp = fopen("/proc/net/ip_mr_cache", "r");
-
-	if (!fp)
-		return;
-
-	if (!fgets(buf, sizeof(buf), fp)) {
-		fclose(fp);
-		return;
+	if (r->rtm_type != RTN_MULTICAST) {
+		fprintf(stderr, "Not a multicast route (type: %s)\n",
+			rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)));
+		return 0;
 	}
 
-	while (fgets(buf, sizeof(buf), fp)) {
-		inet_prefix maddr, msrc;
-		unsigned pkts, b, w;
-		int vifi;
-		char oiflist[256];
-		char sbuf[256];
-		char mbuf[256];
-		char obuf[256];
+	parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+	table = rtm_get_table(r, tb);
 
-		oiflist[0] = 0;
-		if (sscanf(buf, "%x%x%d%u%u%u %[^\n]",
-			   maddr.data, msrc.data, &vifi,
-			   &pkts, &b, &w, oiflist) < 6)
-			continue;
+	if (filter.tb > 0 && filter.tb != table)
+		return 0;
 
-		if (vifi!=-1 && (vifi < 0 || vifi>31))
-			continue;
+	if (tb[RTA_IIF])
+		iif = *(int*)RTA_DATA(tb[RTA_IIF]);
+	if (filter.iif && filter.iif != iif)
+		return 0;
 
-		if (filter_dev[0] && (vifi<0 || strcmp(filter_dev, viftable[vifi])))
-			continue;
-		if (filter.mdst.family && inet_addr_match(&maddr, &filter.mdst, filter.mdst.bitlen))
-			continue;
-		if (filter.msrc.family && inet_addr_match(&msrc, &filter.msrc, filter.msrc.bitlen))
-			continue;
+	if (filter.af && filter.af != r->rtm_family)
+		return 0;
 
-		snprintf(obuf, sizeof(obuf), "(%s, %s)",
-			 format_host(AF_INET, 4, &msrc.data[0], sbuf, sizeof(sbuf)),
-			 format_host(AF_INET, 4, &maddr.data[0], mbuf, sizeof(mbuf)));
+	if (tb[RTA_DST] &&
+	    filter.mdst.bitlen > 0 &&
+	    inet_addr_match(RTA_DATA(tb[RTA_DST]), &filter.mdst, filter.mdst.bitlen))
+		return 0;
 
-		fprintf(ofp, "%-32s Iif: ", obuf);
+	if (tb[RTA_SRC] &&
+	    filter.msrc.bitlen > 0 &&
+	    inet_addr_match(RTA_DATA(tb[RTA_SRC]), &filter.msrc, filter.msrc.bitlen))
+		return 0;
 
-		if (vifi == -1)
-			fprintf(ofp, "unresolved ");
-		else
-			fprintf(ofp, "%-10s ", viftable[vifi]);
+	family = r->rtm_family == RTNL_FAMILY_IPMR ? AF_INET : AF_INET6;
 
-		if (oiflist[0]) {
-			char *next = NULL;
-			char *p = oiflist;
-			int ovifi, ottl;
+	if (n->nlmsg_type == RTM_DELROUTE)
+		fprintf(fp, "Deleted ");
 
-			fprintf(ofp, "Oifs: ");
+	if (tb[RTA_SRC])
+		len = snprintf(obuf, sizeof(obuf),
+			       "(%s, ", rt_addr_n2a(family,
+						    RTA_DATA(tb[RTA_SRC]),
+						    abuf, sizeof(abuf)));
+	else
+		len = sprintf(obuf, "(unknown, ");
+	if (tb[RTA_DST])
+		snprintf(obuf + len, sizeof(obuf) - len,
+			 "%s)", rt_addr_n2a(family,
+					    RTA_DATA(tb[RTA_DST]),
+					    abuf, sizeof(abuf)));
+	else
+		snprintf(obuf + len, sizeof(obuf) - len, "unknown) ");
 
-			while (p) {
-				next = strchr(p, ' ');
-				if (next) {
-					*next = 0;
-					next++;
-				}
-				if (sscanf(p, "%d:%d", &ovifi, &ottl)<2) {
-					p = next;
-					continue;
-				}
-				p = next;
+	fprintf(fp, "%-32s Iif: ", obuf);
+	if (iif)
+		fprintf(fp, "%-10s ", ll_index_to_name(iif));
+	else
+		fprintf(fp, "unresolved ");
 
-				fprintf(ofp, "%s", viftable[ovifi]);
-				if (ottl>1)
-					fprintf(ofp, "(ttl %d) ", ovifi);
-				else
-					fprintf(ofp, " ");
+	if (tb[RTA_MULTIPATH]) {
+		struct rtnexthop *nh = RTA_DATA(tb[RTA_MULTIPATH]);
+		int first = 1;
+
+		len = RTA_PAYLOAD(tb[RTA_MULTIPATH]);
+
+		for (;;) {
+			if (len < sizeof(*nh))
+				break;
+			if (nh->rtnh_len > len)
+				break;
+
+			if (first) {
+				fprintf(fp, "Oifs: ");
+				first = 0;
 			}
+			fprintf(fp, "%s", ll_index_to_name(nh->rtnh_ifindex));
+			if (nh->rtnh_hops > 1)
+				fprintf(fp, "(ttl %d) ", nh->rtnh_hops);
+			else
+				fprintf(fp, " ");
+			len -= NLMSG_ALIGN(nh->rtnh_len);
+			nh = RTNH_NEXT(nh);
 		}
-
-		if (show_stats && b) {
-			fprintf(ofp, "%s  %u packets, %u bytes", _SL_, pkts, b);
-			if (w)
-				fprintf(ofp, ", %u arrived on wrong iif.", w);
-		}
-		fprintf(ofp, "\n");
 	}
-	fclose(fp);
+	if (show_stats && tb[RTA_MFC_STATS]) {
+		struct rta_mfc_stats *mfcs = RTA_DATA(tb[RTA_MFC_STATS]);
+
+		fprintf(fp, "%s  %"PRIu64" packets, %"PRIu64" bytes", _SL_,
+			(uint64_t)mfcs->mfcs_packets,
+			(uint64_t)mfcs->mfcs_bytes);
+		if (mfcs->mfcs_wrong_if)
+			fprintf(fp, ", %"PRIu64" arrived on wrong iif.",
+				(uint64_t)mfcs->mfcs_wrong_if);
+	}
+	fprintf(fp, "\n");
+	fflush(fp);
+	return 0;
 }
 
+void ipmroute_reset_filter(int ifindex)
+{
+	memset(&filter, 0, sizeof(filter));
+	filter.mdst.bitlen = -1;
+	filter.msrc.bitlen = -1;
+	filter.iif = ifindex;
+}
 
 static int mroute_list(int argc, char **argv)
 {
+	char *id = NULL;
+	int family;
+
+	ipmroute_reset_filter(0);
+	if (preferred_family == AF_UNSPEC)
+		family = AF_INET;
+	else
+		family = AF_INET6;
+	if (family == AF_INET) {
+		filter.af = RTNL_FAMILY_IPMR;
+		filter.tb = RT_TABLE_DEFAULT;  /* for backward compatibility */
+	} else
+		filter.af = RTNL_FAMILY_IP6MR;
+
 	while (argc > 0) {
-		if (strcmp(*argv, "iif") == 0) {
+		if (matches(*argv, "table") == 0) {
+			__u32 tid;
 			NEXT_ARG();
-			strncpy(filter_dev, *argv, sizeof(filter_dev)-1);
+			if (rtnl_rttable_a2n(&tid, *argv)) {
+				if (strcmp(*argv, "all") == 0) {
+					filter.tb = 0;
+				} else if (strcmp(*argv, "help") == 0) {
+					usage();
+				} else {
+					invarg("table id value is invalid\n", *argv);
+				}
+			} else
+				filter.tb = tid;
+		} else if (strcmp(*argv, "iif") == 0) {
+			NEXT_ARG();
+			id = *argv;
 		} else if (matches(*argv, "from") == 0) {
 			NEXT_ARG();
-			get_prefix(&filter.msrc, *argv, AF_INET);
+			get_prefix(&filter.msrc, *argv, family);
 		} else {
 			if (strcmp(*argv, "to") == 0) {
 				NEXT_ARG();
 			}
 			if (matches(*argv, "help") == 0)
 				usage();
-			get_prefix(&filter.mdst, *argv, AF_INET);
+			get_prefix(&filter.mdst, *argv, family);
 		}
-		argv++; argc--;
+		argc--; argv++;
 	}
 
-	read_viftable();
-	read_mroute_list(stdout);
-	return 0;
+	ll_init_map(&rth);
+
+	if (id)  {
+		int idx;
+
+		if ((idx = ll_name_to_index(id)) == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n", id);
+			return -1;
+		}
+		filter.iif = idx;
+	}
+
+	if (rtnl_wilddump_request(&rth, filter.af, RTM_GETROUTE) < 0) {
+		perror("Cannot send dump request");
+		return 1;
+	}
+
+	if (rtnl_dump_filter(&rth, print_mroute, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+
+	exit(0);
 }
 
 int do_multiroute(int argc, char **argv)
diff --git a/ip/ipneigh.c b/ip/ipneigh.c
index 93cfba2..eeec7bd 100644
--- a/ip/ipneigh.c
+++ b/ip/ipneigh.c
@@ -52,7 +52,7 @@
 	exit(-1);
 }
 
-int nud_state_a2n(unsigned *state, char *arg)
+static int nud_state_a2n(unsigned *state, const char *arg)
 {
 	if (matches(arg, "permanent") == 0)
 		*state = NUD_PERMANENT;
@@ -95,9 +95,9 @@
 static int ipneigh_modify(int cmd, int flags, int argc, char **argv)
 {
 	struct {
-		struct nlmsghdr 	n;
-		struct ndmsg 		ndm;
-		char   			buf[256];
+		struct nlmsghdr	n;
+		struct ndmsg		ndm;
+		char  			buf[256];
 	} req;
 	char  *d = NULL;
 	int dst_ok = 0;
@@ -157,14 +157,19 @@
 		exit(-1);
 	}
 	req.ndm.ndm_family = dst.family;
-	addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen);
+	if (addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen) < 0)
+		return -1;
 
 	if (lla && strcmp(lla, "null")) {
 		char llabuf[20];
 		int l;
 
 		l = ll_addr_a2n(llabuf, sizeof(llabuf), lla);
-		addattr_l(&req.n, sizeof(req), NDA_LLADDR, llabuf, l);
+		if (l < 0)
+			return -1;
+
+		if (addattr_l(&req.n, sizeof(req), NDA_LLADDR, llabuf, l) < 0)
+			return -1;
 	}
 
 	ll_init_map(&rth);
@@ -189,7 +194,8 @@
 	struct rtattr * tb[NDA_MAX+1];
 	char abuf[256];
 
-	if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH) {
+	if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH &&
+	    n->nlmsg_type != RTM_GETNEIGH) {
 		fprintf(stderr, "Not RTM_NEWNEIGH: %08x %08x %08x\n",
 			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
 
@@ -249,6 +255,10 @@
 			return 0;
 	}
 
+	if (n->nlmsg_type == RTM_DELNEIGH)
+		fprintf(fp, "delete ");
+	else if (n->nlmsg_type == RTM_GETNEIGH)
+		fprintf(fp, "miss ");
 	if (tb[NDA_DST]) {
 		fprintf(fp, "%s ",
 			format_host(r->ndm_family,
@@ -308,19 +318,20 @@
 	return 0;
 }
 
-void ipneigh_reset_filter()
+void ipneigh_reset_filter(int ifindex)
 {
 	memset(&filter, 0, sizeof(filter));
 	filter.state = ~0;
+	filter.index = ifindex;
 }
 
-int do_show_or_flush(int argc, char **argv, int flush)
+static int do_show_or_flush(int argc, char **argv, int flush)
 {
 	char *filter_dev = NULL;
 	int state_given = 0;
 	struct ndmsg ndm = { 0 };
 
-	ipneigh_reset_filter();
+	ipneigh_reset_filter(0);
 
 	if (!filter.family)
 		filter.family = preferred_family;
diff --git a/ip/ipnetconf.c b/ip/ipnetconf.c
new file mode 100644
index 0000000..aa31ead
--- /dev/null
+++ b/ip/ipnetconf.c
@@ -0,0 +1,207 @@
+/*
+ * ipnetconf.c		"ip netconf".
+ *
+ *		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.
+ *
+ * Authors:	Nicolas Dichtel, <nicolas.dichtel@6wind.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static struct
+{
+	int family;
+	int ifindex;
+} filter;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ip netconf show [ dev STRING ]\n");
+	exit(-1);
+}
+
+#define NETCONF_RTA(r)	((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct netconfmsg))))
+
+int print_netconf(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE*)arg;
+	struct netconfmsg *ncm = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[NETCONFA_MAX+1];
+
+	if (n->nlmsg_type == NLMSG_ERROR)
+		return -1;
+	if (n->nlmsg_type != RTM_NEWNETCONF) {
+		fprintf(stderr, "Not RTM_NEWNETCONF: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+
+		return -1;
+	}
+	len -= NLMSG_SPACE(sizeof(*ncm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (filter.family && filter.family != ncm->ncm_family)
+		return 0;
+
+	parse_rtattr(tb, NETCONFA_MAX, NETCONF_RTA(ncm),
+		     NLMSG_PAYLOAD(n, sizeof(*ncm)));
+
+	switch (ncm->ncm_family) {
+	case AF_INET:
+		fprintf(fp, "ipv4 ");
+		break;
+	case AF_INET6:
+		fprintf(fp, "ipv6 ");
+		break;
+	default:
+		fprintf(fp, "unknown ");
+		break;
+	}
+
+	if (tb[NETCONFA_IFINDEX]) {
+		int *ifindex = (int *)RTA_DATA(tb[NETCONFA_IFINDEX]);
+
+		switch (*ifindex) {
+		case NETCONFA_IFINDEX_ALL:
+			fprintf(fp, "all ");
+			break;
+		case NETCONFA_IFINDEX_DEFAULT:
+			fprintf(fp, "default ");
+			break;
+		default:
+			fprintf(fp, "dev %s ", ll_index_to_name(*ifindex));
+			break;
+		}
+	}
+
+	if (tb[NETCONFA_FORWARDING])
+		fprintf(fp, "forwarding %s ",
+			*(int *)RTA_DATA(tb[NETCONFA_FORWARDING])?"on":"off");
+	if (tb[NETCONFA_RP_FILTER]) {
+		int rp_filter = *(int *)RTA_DATA(tb[NETCONFA_RP_FILTER]);
+
+		if (rp_filter == 0)
+			fprintf(fp, "rp_filter off ");
+		else if (rp_filter == 1)
+			fprintf(fp, "rp_filter strict ");
+		else if (rp_filter == 2)
+			fprintf(fp, "rp_filter loose ");
+		else
+			fprintf(fp, "rp_filter unknown mode ");
+	}
+	if (tb[NETCONFA_MC_FORWARDING])
+		fprintf(fp, "mc_forwarding %d ",
+			*(int *)RTA_DATA(tb[NETCONFA_MC_FORWARDING]));
+
+	if (tb[NETCONFA_PROXY_NEIGH])
+		fprintf(fp, "proxy_neigh %s ",
+			*(int *)RTA_DATA(tb[NETCONFA_PROXY_NEIGH])?"on":"off");
+
+	fprintf(fp, "\n");
+	fflush(fp);
+	return 0;
+}
+
+void ipnetconf_reset_filter(int ifindex)
+{
+	memset(&filter, 0, sizeof(filter));
+	filter.ifindex = ifindex;
+}
+
+static int do_show(int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr		n;
+		struct netconfmsg	ncm;
+		char			buf[1024];
+	} req;
+
+	ipnetconf_reset_filter(0);
+	filter.family = preferred_family;
+	if (filter.family == AF_UNSPEC)
+		filter.family = AF_INET;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			filter.ifindex = ll_name_to_index(*argv);
+			if (filter.ifindex <= 0) {
+				fprintf(stderr, "Device \"%s\" does not exist.\n",
+					*argv);
+				return -1;
+			}
+		}
+		argv++; argc--;
+	}
+
+	ll_init_map(&rth);
+	if (filter.ifindex) {
+		memset(&req, 0, sizeof(req));
+		req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct netconfmsg));
+		req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK;
+		req.n.nlmsg_type = RTM_GETNETCONF;
+		req.ncm.ncm_family = filter.family;
+		if (filter.ifindex)
+			addattr_l(&req.n, sizeof(req), NETCONFA_IFINDEX,
+				  &filter.ifindex, sizeof(filter.ifindex));
+
+		if (rtnl_send(&rth, &req.n, req.n.nlmsg_len) < 0) {
+			perror("Can not send request");
+			exit(1);
+		}
+		rtnl_listen(&rth, print_netconf, stdout);
+	} else {
+dump:
+		if (rtnl_wilddump_request(&rth, filter.family, RTM_GETNETCONF) < 0) {
+			perror("Cannot send dump request");
+			exit(1);
+		}
+		if (rtnl_dump_filter(&rth, print_netconf, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+		if (preferred_family == AF_UNSPEC) {
+			preferred_family = AF_INET6;
+			filter.family = AF_INET6;
+			goto dump;
+		}
+	}
+	return 0;
+}
+
+int do_ipnetconf(int argc, char **argv)
+{
+	if (argc > 0) {
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return do_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return do_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip netconf help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ipnetns.c b/ip/ipnetns.c
index e41a598..24df167 100644
--- a/ip/ipnetns.c
+++ b/ip/ipnetns.c
@@ -13,65 +13,127 @@
 #include <dirent.h>
 #include <errno.h>
 #include <unistd.h>
+#include <ctype.h>
+
+#include <linux/net_namespace.h>
 
 #include "utils.h"
 #include "ip_common.h"
+#include "namespace.h"
 
-#define NETNS_RUN_DIR "/var/run/netns"
-#define NETNS_ETC_DIR "/etc/netns"
-
-#ifndef CLONE_NEWNET
-#define CLONE_NEWNET 0x40000000	/* New network namespace (lo, device, names sockets, etc) */
-#endif
-
-#ifndef MNT_DETACH
-#define MNT_DETACH	0x00000002	/* Just detach from the tree */
-#endif /* MNT_DETACH */
-
-#ifndef HAVE_SETNS
-static int setns(int fd, int nstype)
-{
-#ifdef __NR_setns
-	return syscall(__NR_setns, fd, nstype);
-#else
-	errno = ENOSYS;
-	return -1;
-#endif
-}
-#endif /* HAVE_SETNS */
-
-
-static void usage(void) __attribute__((noreturn));
-
-static void usage(void)
+static int usage(void)
 {
 	fprintf(stderr, "Usage: ip netns list\n");
 	fprintf(stderr, "       ip netns add NAME\n");
-	fprintf(stderr, "       ip netns delete NAME\n");
-	fprintf(stderr, "       ip netns exec NAME cmd ...\n");
+	fprintf(stderr, "       ip netns set NAME NETNSID\n");
+	fprintf(stderr, "       ip [-all] netns delete [NAME]\n");
+	fprintf(stderr, "       ip netns identify [PID]\n");
+	fprintf(stderr, "       ip netns pids NAME\n");
+	fprintf(stderr, "       ip [-all] netns exec [NAME] cmd ...\n");
 	fprintf(stderr, "       ip netns monitor\n");
 	exit(-1);
 }
 
-int get_netns_fd(const char *name)
-{
-	char pathbuf[MAXPATHLEN];
-	const char *path, *ptr;
+static int have_rtnl_getnsid = -1;
 
-	path = name;
-	ptr = strchr(name, '/');
-	if (!ptr) {
-		snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
-			NETNS_RUN_DIR, name );
-		path = pathbuf;
+static int ipnetns_accept_msg(const struct sockaddr_nl *who,
+			      struct nlmsghdr *n, void *arg)
+{
+	struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(n);
+
+	if (n->nlmsg_type == NLMSG_ERROR &&
+	    (err->error == -EOPNOTSUPP || err->error == -EINVAL))
+		have_rtnl_getnsid = 0;
+	else
+		have_rtnl_getnsid = 1;
+	return -1;
+}
+
+static int ipnetns_have_nsid(void)
+{
+	struct {
+		struct nlmsghdr n;
+		struct rtgenmsg g;
+		char            buf[1024];
+	} req;
+	int fd;
+
+	if (have_rtnl_getnsid < 0) {
+		memset(&req, 0, sizeof(req));
+		req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
+		req.n.nlmsg_flags = NLM_F_REQUEST;
+		req.n.nlmsg_type = RTM_GETNSID;
+		req.g.rtgen_family = AF_UNSPEC;
+
+		fd = open("/proc/self/ns/net", O_RDONLY);
+		if (fd < 0) {
+			perror("open(\"/proc/self/ns/net\")");
+			exit(1);
+		}
+
+		addattr32(&req.n, 1024, NETNSA_FD, fd);
+
+		if (rtnl_send(&rth, &req.n, req.n.nlmsg_len) < 0) {
+			perror("request send failed");
+			exit(1);
+		}
+		rtnl_listen(&rth, ipnetns_accept_msg, NULL);
+		close(fd);
 	}
-	return open(path, O_RDONLY);
+
+	return have_rtnl_getnsid;
+}
+
+static int get_netnsid_from_name(const char *name)
+{
+	struct {
+		struct nlmsghdr n;
+		struct rtgenmsg g;
+		char            buf[1024];
+	} req, answer;
+	struct rtattr *tb[NETNSA_MAX + 1];
+	struct rtgenmsg *rthdr;
+	int len, fd;
+
+	memset(&req, 0, sizeof(req));
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST;
+	req.n.nlmsg_type = RTM_GETNSID;
+	req.g.rtgen_family = AF_UNSPEC;
+
+	fd = netns_get_fd(name);
+	if (fd < 0)
+		return fd;
+
+	addattr32(&req.n, 1024, NETNSA_FD, fd);
+	if (rtnl_talk(&rth, &req.n, 0, 0, &answer.n) < 0) {
+		close(fd);
+		return -2;
+	}
+	close(fd);
+
+	/* Validate message and parse attributes */
+	if (answer.n.nlmsg_type == NLMSG_ERROR)
+		return -1;
+
+	rthdr = NLMSG_DATA(&answer.n);
+	len = answer.n.nlmsg_len - NLMSG_SPACE(sizeof(*rthdr));
+	if (len < 0)
+		return -1;
+
+	parse_rtattr(tb, NETNSA_MAX, NETNS_RTA(rthdr), len);
+
+	if (tb[NETNSA_NSID])
+		return rta_getattr_u32(tb[NETNSA_NSID]);
+
+	return -1;
 }
 
 static int netns_list(int argc, char **argv)
 {
 	struct dirent *entry;
 	DIR *dir;
+	int id;
 
 	dir = opendir(NETNS_RUN_DIR);
 	if (!dir)
@@ -82,38 +144,57 @@
 			continue;
 		if (strcmp(entry->d_name, "..") == 0)
 			continue;
-		printf("%s\n", entry->d_name);
+		printf("%s", entry->d_name);
+		if (ipnetns_have_nsid()) {
+			id = get_netnsid_from_name(entry->d_name);
+			if (id >= 0)
+				printf(" (id: %d)", id);
+		}
+		printf("\n");
 	}
 	closedir(dir);
 	return 0;
 }
 
-static void bind_etc(const char *name)
+static int cmd_exec(const char *cmd, char **argv, bool do_fork)
 {
-	char etc_netns_path[MAXPATHLEN];
-	char netns_name[MAXPATHLEN];
-	char etc_name[MAXPATHLEN];
-	struct dirent *entry;
-	DIR *dir;
+	fflush(stdout);
+	if (do_fork) {
+		int status;
+		pid_t pid;
 
-	snprintf(etc_netns_path, sizeof(etc_netns_path), "%s/%s", NETNS_ETC_DIR, name);
-	dir = opendir(etc_netns_path);
-	if (!dir)
-		return;
+		pid = fork();
+		if (pid < 0) {
+			perror("fork");
+			exit(1);
+		}
 
-	while ((entry = readdir(dir)) != NULL) {
-		if (strcmp(entry->d_name, ".") == 0)
-			continue;
-		if (strcmp(entry->d_name, "..") == 0)
-			continue;
-		snprintf(netns_name, sizeof(netns_name), "%s/%s", etc_netns_path, entry->d_name);
-		snprintf(etc_name, sizeof(etc_name), "/etc/%s", entry->d_name);
-		if (mount(netns_name, etc_name, "none", MS_BIND, NULL) < 0) {
-			fprintf(stderr, "Bind %s -> %s failed: %s\n",
-				netns_name, etc_name, strerror(errno));
+		if (pid != 0) {
+			/* Parent  */
+			if (waitpid(pid, &status, 0) < 0) {
+				perror("waitpid");
+				exit(1);
+			}
+
+			if (WIFEXITED(status)) {
+				return WEXITSTATUS(status);
+			}
+
+			exit(1);
 		}
 	}
-	closedir(dir);
+
+	if (execvp(cmd, argv)  < 0)
+		fprintf(stderr, "exec of \"%s\" failed: %s\n",
+				cmd, strerror(errno));
+	_exit(1);
+}
+
+static int on_netns_exec(char *nsname, void *arg)
+{
+	char **argv = arg;
+	cmd_exec(argv[1], argv + 1, true);
+	return 0;
 }
 
 static int netns_exec(int argc, char **argv)
@@ -121,20 +202,60 @@
 	/* Setup the proper environment for apps that are not netns
 	 * aware, and execute a program in that environment.
 	 */
-	const char *name, *cmd;
+	const char *cmd;
+
+	if (argc < 1 && !do_all) {
+		fprintf(stderr, "No netns name specified\n");
+		return -1;
+	}
+	if ((argc < 2 && !do_all) || (argc < 1 && do_all)) {
+		fprintf(stderr, "No command specified\n");
+		return -1;
+	}
+
+	if (do_all)
+		return do_each_netns(on_netns_exec, --argv, 1);
+
+	if (netns_switch(argv[0]))
+		return -1;
+
+	/* ip must return the status of the child,
+	 * but do_cmd() will add a minus to this,
+	 * so let's add another one here to cancel it.
+	 */
+	cmd = argv[1];
+	return -cmd_exec(cmd, argv + 1, !!batch_mode);
+}
+
+static int is_pid(const char *str)
+{
+	int ch;
+	for (; (ch = *str); str++) {
+		if (!isdigit(ch))
+			return 0;
+	}
+	return 1;
+}
+
+static int netns_pids(int argc, char **argv)
+{
+	const char *name;
 	char net_path[MAXPATHLEN];
 	int netns;
+	struct stat netst;
+	DIR *dir;
+	struct dirent *entry;
 
 	if (argc < 1) {
 		fprintf(stderr, "No netns name specified\n");
 		return -1;
 	}
-	if (argc < 2) {
-		fprintf(stderr, "No cmd specified\n");
+	if (argc > 1) {
+		fprintf(stderr, "extra arguments specified\n");
 		return -1;
 	}
+
 	name = argv[0];
-	cmd = argv[1];
 	snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name);
 	netns = open(net_path, O_RDONLY);
 	if (netns < 0) {
@@ -142,53 +263,145 @@
 			strerror(errno));
 		return -1;
 	}
-	if (setns(netns, CLONE_NEWNET) < 0) {
-		fprintf(stderr, "seting the network namespace failed: %s\n",
+	if (fstat(netns, &netst) < 0) {
+		fprintf(stderr, "Stat of netns failed: %s\n",
 			strerror(errno));
 		return -1;
 	}
+	dir = opendir("/proc/");
+	if (!dir) {
+		fprintf(stderr, "Open of /proc failed: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	while((entry = readdir(dir))) {
+		char pid_net_path[MAXPATHLEN];
+		struct stat st;
+		if (!is_pid(entry->d_name))
+			continue;
+		snprintf(pid_net_path, sizeof(pid_net_path), "/proc/%s/ns/net",
+			entry->d_name);
+		if (stat(pid_net_path, &st) != 0)
+			continue;
+		if ((st.st_dev == netst.st_dev) &&
+		    (st.st_ino == netst.st_ino)) {
+			printf("%s\n", entry->d_name);
+		}
+	}
+	closedir(dir);
+	return 0;
 
-	if (unshare(CLONE_NEWNS) < 0) {
-		fprintf(stderr, "unshare failed: %s\n", strerror(errno));
+}
+
+static int netns_identify(int argc, char **argv)
+{
+	const char *pidstr;
+	char net_path[MAXPATHLEN];
+	int netns;
+	struct stat netst;
+	DIR *dir;
+	struct dirent *entry;
+
+	if (argc < 1) {
+		pidstr = "self";
+	} else if (argc > 1) {
+		fprintf(stderr, "extra arguments specified\n");
+		return -1;
+	} else {
+		pidstr = argv[0];
+		if (!is_pid(pidstr)) {
+			fprintf(stderr, "Specified string '%s' is not a pid\n",
+					pidstr);
+			return -1;
+		}
+	}
+
+	snprintf(net_path, sizeof(net_path), "/proc/%s/ns/net", pidstr);
+	netns = open(net_path, O_RDONLY);
+	if (netns < 0) {
+		fprintf(stderr, "Cannot open network namespace: %s\n",
+			strerror(errno));
 		return -1;
 	}
-	/* Mount a version of /sys that describes the network namespace */
-	if (umount2("/sys", MNT_DETACH) < 0) {
-		fprintf(stderr, "umount of /sys failed: %s\n", strerror(errno));
+	if (fstat(netns, &netst) < 0) {
+		fprintf(stderr, "Stat of netns failed: %s\n",
+			strerror(errno));
 		return -1;
 	}
-	if (mount(name, "/sys", "sysfs", 0, NULL) < 0) {
-		fprintf(stderr, "mount of /sys failed: %s\n",strerror(errno));
+	dir = opendir(NETNS_RUN_DIR);
+	if (!dir) {
+		/* Succeed treat a missing directory as an empty directory */
+		if (errno == ENOENT)
+			return 0;
+
+		fprintf(stderr, "Failed to open directory %s:%s\n",
+			NETNS_RUN_DIR, strerror(errno));
 		return -1;
 	}
 
-	/* Setup bind mounts for config files in /etc */
-	bind_etc(name);
+	while((entry = readdir(dir))) {
+		char name_path[MAXPATHLEN];
+		struct stat st;
 
-	if (execvp(cmd, argv + 1)  < 0)
-		fprintf(stderr, "exec of %s failed: %s\n",
-			cmd, strerror(errno));
-	exit(-1);
+		if (strcmp(entry->d_name, ".") == 0)
+			continue;
+		if (strcmp(entry->d_name, "..") == 0)
+			continue;
+
+		snprintf(name_path, sizeof(name_path), "%s/%s",	NETNS_RUN_DIR,
+			entry->d_name);
+
+		if (stat(name_path, &st) != 0)
+			continue;
+
+		if ((st.st_dev == netst.st_dev) &&
+		    (st.st_ino == netst.st_ino)) {
+			printf("%s\n", entry->d_name);
+		}
+	}
+	closedir(dir);
+	return 0;
+
+}
+
+static int on_netns_del(char *nsname, void *arg)
+{
+	char netns_path[MAXPATHLEN];
+
+	snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, nsname);
+	umount2(netns_path, MNT_DETACH);
+	if (unlink(netns_path) < 0) {
+		fprintf(stderr, "Cannot remove namespace file \"%s\": %s\n",
+			netns_path, strerror(errno));
+		return -1;
+	}
+	return 0;
 }
 
 static int netns_delete(int argc, char **argv)
 {
-	const char *name;
-	char netns_path[MAXPATHLEN];
-
-	if (argc < 1) {
+	if (argc < 1 && !do_all) {
 		fprintf(stderr, "No netns name specified\n");
 		return -1;
 	}
 
-	name = argv[0];
-	snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name);
-	umount2(netns_path, MNT_DETACH);
-	if (unlink(netns_path) < 0) {
-		fprintf(stderr, "Cannot remove %s: %s\n",
-			netns_path, strerror(errno));
-		return -1;
+	if (do_all)
+		return netns_foreach(on_netns_del, NULL);
+
+	return on_netns_del(argv[0], NULL);
+}
+
+static int create_netns_dir(void)
+{
+	/* Create the base netns directory if it doesn't exist */
+	if (mkdir(NETNS_RUN_DIR, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) {
+		if (errno != EEXIST) {
+			fprintf(stderr, "mkdir %s failed: %s\n",
+				NETNS_RUN_DIR, strerror(errno));
+			return -1;
+		}
 	}
+
 	return 0;
 }
 
@@ -205,6 +418,7 @@
 	char netns_path[MAXPATHLEN];
 	const char *name;
 	int fd;
+	int made_netns_run_dir_mount = 0;
 
 	if (argc < 1) {
 		fprintf(stderr, "No netns name specified\n");
@@ -214,20 +428,43 @@
 
 	snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name);
 
-	/* Create the base netns directory if it doesn't exist */
-	mkdir(NETNS_RUN_DIR, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
+	if (create_netns_dir())
+		return -1;
+
+	/* Make it possible for network namespace mounts to propagate between
+	 * mount namespaces.  This makes it likely that a unmounting a network
+	 * namespace file in one namespace will unmount the network namespace
+	 * file in all namespaces allowing the network namespace to be freed
+	 * sooner.
+	 */
+	while (mount("", NETNS_RUN_DIR, "none", MS_SHARED | MS_REC, NULL)) {
+		/* Fail unless we need to make the mount point */
+		if (errno != EINVAL || made_netns_run_dir_mount) {
+			fprintf(stderr, "mount --make-shared %s failed: %s\n",
+				NETNS_RUN_DIR, strerror(errno));
+			return -1;
+		}
+
+		/* Upgrade NETNS_RUN_DIR to a mount point */
+		if (mount(NETNS_RUN_DIR, NETNS_RUN_DIR, "none", MS_BIND, NULL)) {
+			fprintf(stderr, "mount --bind %s %s failed: %s\n",
+				NETNS_RUN_DIR, NETNS_RUN_DIR, strerror(errno));
+			return -1;
+		}
+		made_netns_run_dir_mount = 1;
+	}
 
 	/* Create the filesystem state */
 	fd = open(netns_path, O_RDONLY|O_CREAT|O_EXCL, 0);
 	if (fd < 0) {
-		fprintf(stderr, "Could not create %s: %s\n",
+		fprintf(stderr, "Cannot create namespace file \"%s\": %s\n",
 			netns_path, strerror(errno));
 		return -1;
 	}
 	close(fd);
 	if (unshare(CLONE_NEWNET) < 0) {
-		fprintf(stderr, "Failed to create a new network namespace: %s\n",
-			strerror(errno));
+		fprintf(stderr, "Failed to create a new network namespace \"%s\": %s\n",
+			name, strerror(errno));
 		goto out_delete;
 	}
 
@@ -240,10 +477,64 @@
 	return 0;
 out_delete:
 	netns_delete(argc, argv);
-	exit(-1);
 	return -1;
 }
 
+static int set_netnsid_from_name(const char *name, int nsid)
+{
+	struct {
+		struct nlmsghdr n;
+		struct rtgenmsg g;
+		char            buf[1024];
+	} req;
+	int fd, err = 0;
+
+	memset(&req, 0, sizeof(req));
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST;
+	req.n.nlmsg_type = RTM_NEWNSID;
+	req.g.rtgen_family = AF_UNSPEC;
+
+	fd = netns_get_fd(name);
+	if (fd < 0)
+		return fd;
+
+	addattr32(&req.n, 1024, NETNSA_FD, fd);
+	addattr32(&req.n, 1024, NETNSA_NSID, nsid);
+	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
+		err = -2;
+
+	close(fd);
+	return err;
+}
+
+static int netns_set(int argc, char **argv)
+{
+	char netns_path[MAXPATHLEN];
+	const char *name;
+	int netns, nsid;
+
+	if (argc < 1) {
+		fprintf(stderr, "No netns name specified\n");
+		return -1;
+	}
+	if (argc < 2) {
+		fprintf(stderr, "No nsid specified\n");
+		return -1;
+	}
+	name = argv[0];
+	nsid = atoi(argv[1]);
+
+	snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name);
+	netns = open(netns_path, O_RDONLY | O_CLOEXEC);
+	if (netns < 0) {
+		fprintf(stderr, "Cannot open network namespace \"%s\": %s\n",
+			name, strerror(errno));
+		return -1;
+	}
+
+	return set_netnsid_from_name(name, nsid);
+}
 
 static int netns_monitor(int argc, char **argv)
 {
@@ -256,6 +547,10 @@
 			strerror(errno));
 		return -1;
 	}
+
+	if (create_netns_dir())
+		return -1;
+
 	if (inotify_add_watch(fd, NETNS_RUN_DIR, IN_CREATE | IN_DELETE) < 0) {
 		fprintf(stderr, "inotify_add_watch failed: %s\n",
 			strerror(errno));
@@ -290,14 +585,23 @@
 		return netns_list(argc-1, argv+1);
 
 	if (matches(*argv, "help") == 0)
-		usage();
+		return usage();
 
 	if (matches(*argv, "add") == 0)
 		return netns_add(argc-1, argv+1);
 
+	if (matches(*argv, "set") == 0)
+		return netns_set(argc-1, argv+1);
+
 	if (matches(*argv, "delete") == 0)
 		return netns_delete(argc-1, argv+1);
 
+	if (matches(*argv, "identify") == 0)
+		return netns_identify(argc-1, argv+1);
+
+	if (matches(*argv, "pids") == 0)
+		return netns_pids(argc-1, argv+1);
+
 	if (matches(*argv, "exec") == 0)
 		return netns_exec(argc-1, argv+1);
 
diff --git a/ip/ipntable.c b/ip/ipntable.c
index 639f512..ea7ca2d 100644
--- a/ip/ipntable.c
+++ b/ip/ipntable.c
@@ -12,8 +12,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  */
 /*
  * based on ipneigh.c
@@ -27,6 +26,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/time.h>
+#include <sys/socket.h>
 #include <time.h>
 
 #include "utils.h"
@@ -63,9 +63,9 @@
 static int ipntable_modify(int cmd, int flags, int argc, char **argv)
 {
 	struct {
-		struct nlmsghdr 	n;
+		struct nlmsghdr	n;
 		struct ndtmsg		ndtm;
-		char   			buf[1024];
+		char  			buf[1024];
 	} req;
 	char *namep = NULL;
 	char *threshsp = NULL;
@@ -304,7 +304,7 @@
 	if (!namep)
 		missarg("NAME");
 	if (!threshsp && !gc_intp && !parms_change) {
-		fprintf(stderr, "Not enough information: changable attributes required.\n");
+		fprintf(stderr, "Not enough information: changeable attributes required.\n");
 		exit(-1);
 	}
 
@@ -430,7 +430,7 @@
 		fprintf(fp, "thresh3 %u ", thresh3);
 	}
 	if (tb[NDTA_GC_INTERVAL]) {
-		__u64 gc_int = rta_getattr_u64(tb[NDTA_GC_INTERVAL]);
+		unsigned long long gc_int = rta_getattr_u64(tb[NDTA_GC_INTERVAL]);
 		fprintf(fp, "gc_int %llu ", gc_int);
 	}
 
@@ -483,15 +483,15 @@
 			fprintf(fp, "refcnt %u ", refcnt);
 		}
 		if (tpb[NDTPA_REACHABLE_TIME]) {
-			__u64 reachable = rta_getattr_u64(tpb[NDTPA_REACHABLE_TIME]);
+			unsigned long long reachable = rta_getattr_u64(tpb[NDTPA_REACHABLE_TIME]);
 			fprintf(fp, "reachable %llu ", reachable);
 		}
 		if (tpb[NDTPA_BASE_REACHABLE_TIME]) {
-			__u64 breachable = rta_getattr_u64(tpb[NDTPA_BASE_REACHABLE_TIME]);
+			unsigned long long breachable = rta_getattr_u64(tpb[NDTPA_BASE_REACHABLE_TIME]);
 			fprintf(fp, "base_reachable %llu ", breachable);
 		}
 		if (tpb[NDTPA_RETRANS_TIME]) {
-			__u64 retrans = rta_getattr_u64(tpb[NDTPA_RETRANS_TIME]);
+			unsigned long long retrans = rta_getattr_u64(tpb[NDTPA_RETRANS_TIME]);
 			fprintf(fp, "retrans %llu ", retrans);
 		}
 
@@ -500,11 +500,11 @@
 		fprintf(fp, "    ");
 
 		if (tpb[NDTPA_GC_STALETIME]) {
-			__u64 gc_stale = rta_getattr_u64(tpb[NDTPA_GC_STALETIME]);
+			unsigned long long gc_stale = rta_getattr_u64(tpb[NDTPA_GC_STALETIME]);
 			fprintf(fp, "gc_stale %llu ", gc_stale);
 		}
 		if (tpb[NDTPA_DELAY_PROBE_TIME]) {
-			__u64 delay_probe = rta_getattr_u64(tpb[NDTPA_DELAY_PROBE_TIME]);
+			unsigned long long delay_probe = rta_getattr_u64(tpb[NDTPA_DELAY_PROBE_TIME]);
 			fprintf(fp, "delay_probe %llu ", delay_probe);
 		}
 		if (tpb[NDTPA_QUEUE_LEN]) {
@@ -534,11 +534,11 @@
 		fprintf(fp, "    ");
 
 		if (tpb[NDTPA_ANYCAST_DELAY]) {
-			__u64 anycast_delay = rta_getattr_u64(tpb[NDTPA_ANYCAST_DELAY]);
+			unsigned long long anycast_delay = rta_getattr_u64(tpb[NDTPA_ANYCAST_DELAY]);
 			fprintf(fp, "anycast_delay %llu ", anycast_delay);
 		}
 		if (tpb[NDTPA_PROXY_DELAY]) {
-			__u64 proxy_delay = rta_getattr_u64(tpb[NDTPA_PROXY_DELAY]);
+			unsigned long long proxy_delay = rta_getattr_u64(tpb[NDTPA_PROXY_DELAY]);
 			fprintf(fp, "proxy_delay %llu ", proxy_delay);
 		}
 		if (tpb[NDTPA_PROXY_QLEN]) {
@@ -546,7 +546,7 @@
 			fprintf(fp, "proxy_queue %u ", pqueue);
 		}
 		if (tpb[NDTPA_LOCKTIME]) {
-			__u64 locktime = rta_getattr_u64(tpb[NDTPA_LOCKTIME]);
+			unsigned long long locktime = rta_getattr_u64(tpb[NDTPA_LOCKTIME]);
 			fprintf(fp, "locktime %llu ", locktime);
 		}
 
@@ -559,28 +559,38 @@
 		fprintf(fp, "    ");
 		fprintf(fp, "stats ");
 
-		fprintf(fp, "allocs %llu ", ndts->ndts_allocs);
-		fprintf(fp, "destroys %llu ", ndts->ndts_destroys);
-		fprintf(fp, "hash_grows %llu ", ndts->ndts_hash_grows);
+		fprintf(fp, "allocs %llu ",
+			(unsigned long long) ndts->ndts_allocs);
+		fprintf(fp, "destroys %llu ",
+			(unsigned long long) ndts->ndts_destroys);
+		fprintf(fp, "hash_grows %llu ",
+			(unsigned long long) ndts->ndts_hash_grows);
 
 		fprintf(fp, "%s", _SL_);
 		fprintf(fp, "        ");
 
-		fprintf(fp, "res_failed %llu ", ndts->ndts_res_failed);
-		fprintf(fp, "lookups %llu ", ndts->ndts_lookups);
-		fprintf(fp, "hits %llu ", ndts->ndts_hits);
+		fprintf(fp, "res_failed %llu ",
+			(unsigned long long) ndts->ndts_res_failed);
+		fprintf(fp, "lookups %llu ",
+			(unsigned long long) ndts->ndts_lookups);
+		fprintf(fp, "hits %llu ",
+			(unsigned long long) ndts->ndts_hits);
 
 		fprintf(fp, "%s", _SL_);
 		fprintf(fp, "        ");
 
-		fprintf(fp, "rcv_probes_mcast %llu ", ndts->ndts_rcv_probes_mcast);
-		fprintf(fp, "rcv_probes_ucast %llu ", ndts->ndts_rcv_probes_ucast);
+		fprintf(fp, "rcv_probes_mcast %llu ",
+			(unsigned long long) ndts->ndts_rcv_probes_mcast);
+		fprintf(fp, "rcv_probes_ucast %llu ",
+			(unsigned long long) ndts->ndts_rcv_probes_ucast);
 
 		fprintf(fp, "%s", _SL_);
 		fprintf(fp, "        ");
 
-		fprintf(fp, "periodic_gc_runs %llu ", ndts->ndts_periodic_gc_runs);
-		fprintf(fp, "forced_gc_runs %llu ", ndts->ndts_forced_gc_runs);
+		fprintf(fp, "periodic_gc_runs %llu ",
+			(unsigned long long) ndts->ndts_periodic_gc_runs);
+		fprintf(fp, "forced_gc_runs %llu ",
+			(unsigned long long) ndts->ndts_forced_gc_runs);
 
 		fprintf(fp, "%s", _SL_);
 	}
diff --git a/ip/ipprefix.c b/ip/ipprefix.c
index d8327be..02c0efc 100644
--- a/ip/ipprefix.c
+++ b/ip/ipprefix.c
@@ -12,8 +12,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  */
 /*
  * based on ip.c, iproute.c
@@ -26,8 +25,11 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/socket.h>
 #include <netinet/icmp6.h>
+
 #include "utils.h"
+#include "ip_common.h"
 
 /* prefix flags; see kernel's net/ipv6/addrconf.c and include/net/if_inet6.h */
 #define IF_PREFIX_ONLINK	0x01
@@ -59,7 +61,7 @@
 		return 0;
 
 	if (prefix->prefix_family != AF_INET6) {
-		fprintf(stderr, "wrong family %d\n", prefix->prefix_family);
+		fprintf(stderr, "incorrect protocol family: %d\n", prefix->prefix_family);
 		return 0;
 	}
 	if (prefix->prefix_type != ND_OPT_PREFIX_INFORMATION) {
@@ -78,7 +80,7 @@
 		pfx = (struct in6_addr *)RTA_DATA(tb[PREFIX_ADDRESS]);
 
 		memset(abuf, '\0', sizeof(abuf));
-		fprintf(fp, "%s", rt_addr_n2a(family, sizeof(*pfx), pfx,
+		fprintf(fp, "%s", rt_addr_n2a(family, pfx,
 					      abuf, sizeof(abuf)));
 	}
 	fprintf(fp, "/%u ", prefix->prefix_len);
diff --git a/ip/iproute.c b/ip/iproute.c
index 8dc0bc8..25635eb 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -52,6 +52,8 @@
 	[RTAX_FEATURES] = "features",
 	[RTAX_RTO_MIN]	= "rto_min",
 	[RTAX_INITRWND]	= "initrwnd",
+	[RTAX_QUICKACK]	= "quickack",
+	[RTAX_CC_ALGO]	= "congctl",
 };
 static void usage(void) __attribute__((noreturn));
 
@@ -60,8 +62,9 @@
 	fprintf(stderr, "Usage: ip route { list | flush } SELECTOR\n");
 	fprintf(stderr, "       ip route save SELECTOR\n");
 	fprintf(stderr, "       ip route restore\n");
+	fprintf(stderr, "       ip route showdump\n");
 	fprintf(stderr, "       ip route get ADDRESS [ from ADDRESS iif STRING ]\n");
-	fprintf(stderr, "                            [ oif STRING ]  [ tos TOS ]\n");
+	fprintf(stderr, "                            [ oif STRING ] [ tos TOS ]\n");
 	fprintf(stderr, "                            [ mark NUMBER ] [ uid NUMBER ]\n");
 	fprintf(stderr, "       ip route { add | del | change | append | replace } ROUTE\n");
 	fprintf(stderr, "SELECTOR := [ root PREFIX ] [ match PREFIX ] [ exact PREFIX ]\n");
@@ -74,25 +77,27 @@
 	fprintf(stderr, "INFO_SPEC := NH OPTIONS FLAGS [ nexthop NH ]...\n");
 	fprintf(stderr, "NH := [ via ADDRESS ] [ dev STRING ] [ weight NUMBER ] NHFLAGS\n");
 	fprintf(stderr, "OPTIONS := FLAGS [ mtu NUMBER ] [ advmss NUMBER ]\n");
-	fprintf(stderr, "           [ rtt TIME ] [ rttvar TIME ] [reordering NUMBER ]\n");
+	fprintf(stderr, "           [ rtt TIME ] [ rttvar TIME ] [ reordering NUMBER ]\n");
 	fprintf(stderr, "           [ window NUMBER] [ cwnd NUMBER ] [ initcwnd NUMBER ]\n");
 	fprintf(stderr, "           [ ssthresh NUMBER ] [ realms REALM ] [ src ADDRESS ]\n");
 	fprintf(stderr, "           [ rto_min TIME ] [ hoplimit NUMBER ] [ initrwnd NUMBER ]\n");
+	fprintf(stderr, "           [ features FEATURES ] [ quickack BOOL ] [ congctl NAME ]\n");
 	fprintf(stderr, "TYPE := [ unicast | local | broadcast | multicast | throw |\n");
 	fprintf(stderr, "          unreachable | prohibit | blackhole | nat ]\n");
 	fprintf(stderr, "TABLE_ID := [ local | main | default | all | NUMBER ]\n");
 	fprintf(stderr, "SCOPE := [ host | link | global | NUMBER ]\n");
-	fprintf(stderr, "MP_ALGO := { rr | drr | random | wrandom }\n");
 	fprintf(stderr, "NHFLAGS := [ onlink | pervasive ]\n");
 	fprintf(stderr, "RTPROTO := [ kernel | boot | static | NUMBER ]\n");
 	fprintf(stderr, "TIME := NUMBER[s|ms]\n");
+	fprintf(stderr, "BOOL := [1|0]\n");
+	fprintf(stderr, "FEATURES := ecn\n");
 	exit(-1);
 }
 
 
 static struct
 {
-	int tb;
+	unsigned int tb;
 	int cloned;
 	int flushed;
 	char *flushb;
@@ -124,7 +129,7 @@
 	return 0;
 }
 
-int filter_nlmsg(struct nlmsghdr *n, struct rtattr **tb, int host_len)
+static int filter_nlmsg(struct nlmsghdr *n, struct rtattr **tb, int host_len)
 {
 	struct rtmsg *r = NLMSG_DATA(n);
 	inet_prefix dst;
@@ -263,18 +268,17 @@
 	return 1;
 }
 
-int calc_host_len(struct rtmsg *r)
+static void print_rtax_features(FILE *fp, unsigned int features)
 {
-	if (r->rtm_family == AF_INET6)
-		return 128;
-	else if (r->rtm_family == AF_INET)
-		return 32;
-	else if (r->rtm_family == AF_DECnet)
-		return 16;
-	else if (r->rtm_family == AF_IPX)
-		return 80;
-	else
-		return -1;
+	unsigned int of = features;
+
+	if (features & RTAX_FEATURE_ECN) {
+		fprintf(fp, " ecn");
+		features &= ~RTAX_FEATURE_ECN;
+	}
+
+	if (features)
+		fprintf(fp, " 0x%x", of);
 }
 
 int print_route(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
@@ -284,7 +288,7 @@
 	int len = n->nlmsg_len;
 	struct rtattr * tb[RTA_MAX+1];
 	char abuf[256];
-	int host_len = -1;
+	int host_len;
 	__u32 table;
 	SPRINT_BUF(b1);
 	static int hz;
@@ -302,7 +306,7 @@
 		return -1;
 	}
 
-	host_len = calc_host_len(r);
+	host_len = af_bit_len(r->rtm_family);
 
 	parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
 	table = rtm_get_table(r, tb);
@@ -329,13 +333,12 @@
 
 	if (n->nlmsg_type == RTM_DELROUTE)
 		fprintf(fp, "Deleted ");
-	if (r->rtm_type != RTN_UNICAST && !filter.type)
+	if ((r->rtm_type != RTN_UNICAST || show_details > 0) && !filter.type)
 		fprintf(fp, "%s ", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)));
 
 	if (tb[RTA_DST]) {
 		if (r->rtm_dst_len != host_len) {
 			fprintf(fp, "%s/%u ", rt_addr_n2a(r->rtm_family,
-							 RTA_PAYLOAD(tb[RTA_DST]),
 							 RTA_DATA(tb[RTA_DST]),
 							 abuf, sizeof(abuf)),
 				r->rtm_dst_len
@@ -355,7 +358,6 @@
 	if (tb[RTA_SRC]) {
 		if (r->rtm_src_len != host_len) {
 			fprintf(fp, "from %s/%u ", rt_addr_n2a(r->rtm_family,
-							 RTA_PAYLOAD(tb[RTA_SRC]),
 							 RTA_DATA(tb[RTA_SRC]),
 							 abuf, sizeof(abuf)),
 				r->rtm_src_len
@@ -386,11 +388,11 @@
 		fprintf(fp, "dev %s ", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_OIF])));
 
 	if (!(r->rtm_flags&RTM_F_CLONED)) {
-		if (table != RT_TABLE_MAIN && !filter.tb)
+		if ((table != RT_TABLE_MAIN || show_details > 0) && !filter.tb)
 			fprintf(fp, " table %s ", rtnl_rttable_n2a(table, b1, sizeof(b1)));
-		if (r->rtm_protocol != RTPROT_BOOT && filter.protocolmask != -1)
+		if ((r->rtm_protocol != RTPROT_BOOT || show_details > 0) && filter.protocolmask != -1)
 			fprintf(fp, " proto %s ", rtnl_rtprot_n2a(r->rtm_protocol, b1, sizeof(b1)));
-		if (r->rtm_scope != RT_SCOPE_UNIVERSE && filter.scopemask != -1)
+		if ((r->rtm_scope != RT_SCOPE_UNIVERSE || show_details > 0) && filter.scopemask != -1)
 			fprintf(fp, " scope %s ", rtnl_rtscope_n2a(r->rtm_scope, b1, sizeof(b1)));
 	}
 	if (tb[RTA_PREFSRC] && filter.rprefsrc.bitlen != host_len) {
@@ -399,7 +401,6 @@
 		 */
 		fprintf(fp, " src %s ",
 			rt_addr_n2a(r->rtm_family,
-				    RTA_PAYLOAD(tb[RTA_PREFSRC]),
 				    RTA_DATA(tb[RTA_PREFSRC]),
 				    abuf, sizeof(abuf)));
 	}
@@ -524,7 +525,7 @@
 			mxlock = *(unsigned*)RTA_DATA(mxrta[RTAX_LOCK]);
 
 		for (i=2; i<= RTAX_MAX; i++) {
-			unsigned val;
+			__u32 val;
 
 			if (mxrta[i] == NULL)
 				continue;
@@ -533,11 +534,16 @@
 				fprintf(fp, " %s", mx_names[i]);
 			else
 				fprintf(fp, " metric %d", i);
+
 			if (mxlock & (1<<i))
 				fprintf(fp, " lock");
+			if (i != RTAX_CC_ALGO)
+				val = rta_getattr_u32(mxrta[i]);
 
-			val = *(unsigned*)RTA_DATA(mxrta[i]);
 			switch (i) {
+			case RTAX_FEATURES:
+				print_rtax_features(fp, val);
+				break;
 			case RTAX_HOPLIMIT:
 				if ((int)val == -1)
 					val = 0;
@@ -558,6 +564,10 @@
 					fprintf(fp, " %gs", val/1e3);
 				else
 					fprintf(fp, " %ums", val);
+				break;
+			case RTAX_CC_ALGO:
+				fprintf(fp, " %s", rta_getattr_str(mxrta[i]));
+				break;
 			}
 		}
 	}
@@ -628,16 +638,22 @@
 }
 
 
-int parse_one_nh(struct rtattr *rta, struct rtnexthop *rtnh, int *argcp, char ***argvp)
+static int parse_one_nh(struct rtmsg *r, struct rtattr *rta,
+			struct rtnexthop *rtnh,
+			int *argcp, char ***argvp)
 {
 	int argc = *argcp;
 	char **argv = *argvp;
 
 	while (++argv, --argc > 0) {
 		if (strcmp(*argv, "via") == 0) {
+			inet_prefix addr;
 			NEXT_ARG();
-			rta_addattr32(rta, 4096, RTA_GATEWAY, get_addr32(*argv));
-			rtnh->rtnh_len += sizeof(struct rtattr) + 4;
+			get_addr(&addr, *argv, r->rtm_family);
+			if (r->rtm_family == AF_UNSPEC)
+				r->rtm_family = addr.family;
+			rta_addattr_l(rta, 4096, RTA_GATEWAY, &addr.data, addr.bytelen);
+			rtnh->rtnh_len += sizeof(struct rtattr) + addr.bytelen;
 		} else if (strcmp(*argv, "dev") == 0) {
 			NEXT_ARG();
 			if ((rtnh->rtnh_ifindex = ll_name_to_index(*argv)) == 0) {
@@ -667,7 +683,8 @@
 	return 0;
 }
 
-int parse_nexthops(struct nlmsghdr *n, struct rtmsg *r, int argc, char **argv)
+static int parse_nexthops(struct nlmsghdr *n, struct rtmsg *r,
+			  int argc, char **argv)
 {
 	char buf[1024];
 	struct rtattr *rta = (void*)buf;
@@ -689,7 +706,7 @@
 		memset(rtnh, 0, sizeof(*rtnh));
 		rtnh->rtnh_len = sizeof(*rtnh);
 		rta->rta_len += rtnh->rtnh_len;
-		parse_one_nh(rta, rtnh, &argc, &argv);
+		parse_one_nh(r, rta, rtnh, &argc, &argv);
 		rtnh = RTNH_NEXT(rtnh);
 	}
 
@@ -698,13 +715,12 @@
 	return 0;
 }
 
-
-int iproute_modify(int cmd, unsigned flags, int argc, char **argv)
+static int iproute_modify(int cmd, unsigned flags, int argc, char **argv)
 {
 	struct {
-		struct nlmsghdr 	n;
-		struct rtmsg 		r;
-		char   			buf[1024];
+		struct nlmsghdr	n;
+		struct rtmsg		r;
+		char  			buf[1024];
 	} req;
 	char  mxbuf[256];
 	struct rtattr * mxrta = (void*)mxbuf;
@@ -831,7 +847,7 @@
 			}
 			if (get_time_rtt(&rtt, *argv, &raw))
 				invarg("\"rtt\" value is invalid\n", *argv);
-			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_RTT, 
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_RTT,
 				(raw) ? rtt : rtt * 8);
 		} else if (strcmp(*argv, "rto_min") == 0) {
 			unsigned rto_min;
@@ -882,6 +898,36 @@
 			if (get_unsigned(&win, *argv, 0))
 				invarg("\"initrwnd\" value is invalid\n", *argv);
 			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_INITRWND, win);
+		} else if (matches(*argv, "features") == 0) {
+			unsigned int features = 0;
+
+			while (argc > 0) {
+				NEXT_ARG();
+
+				if (strcmp(*argv, "ecn") == 0)
+					features |= RTAX_FEATURE_ECN;
+				else
+					invarg("\"features\" value not valid\n", *argv);
+				break;
+			}
+
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_FEATURES, features);
+		} else if (matches(*argv, "quickack") == 0) {
+			unsigned quickack;
+			NEXT_ARG();
+			if (get_unsigned(&quickack, *argv, 0))
+				invarg("\"quickack\" value is invalid\n", *argv);
+			if (quickack != 1 && quickack != 0)
+				invarg("\"quickack\" value should be 0 or 1\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_QUICKACK, quickack);
+		} else if (matches(*argv, "congctl") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= 1 << RTAX_CC_ALGO;
+				NEXT_ARG();
+			}
+			rta_addattr_l(mxrta, sizeof(mxbuf), RTAX_CC_ALGO, *argv,
+				      strlen(*argv));
 		} else if (matches(*argv, "rttvar") == 0) {
 			unsigned win;
 			NEXT_ARG();
@@ -964,11 +1010,12 @@
 		argc--; argv++;
 	}
 
+	if (!dst_ok)
+		usage();
+
 	if (d || nhs_ok)  {
 		int idx;
 
-		ll_init_map(&rth);
-
 		if (d) {
 			if ((idx = ll_name_to_index(d)) == 0) {
 				fprintf(stderr, "Cannot find device \"%s\"\n", d);
@@ -1015,7 +1062,7 @@
 		req.r.rtm_family = AF_INET;
 
 	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
-		exit(2);
+		return -1;
 
 	return 0;
 }
@@ -1067,20 +1114,18 @@
 	return 0;
 }
 
-int save_route(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
+static __u32 route_dump_magic = 0x45311224;
+
+static int save_route(const struct sockaddr_nl *who, struct nlmsghdr *n,
+		      void *arg)
 {
 	int ret;
 	int len = n->nlmsg_len;
 	struct rtmsg *r = NLMSG_DATA(n);
 	struct rtattr *tb[RTA_MAX+1];
-	int host_len = -1;
+	int host_len;
 
-	if (isatty(STDOUT_FILENO)) {
-		fprintf(stderr, "Not sending binary stream to stdout\n");
-		return -1;
-	}
-
-	host_len = calc_host_len(r);
+	host_len = af_bit_len(r->rtm_family);
 	len -= NLMSG_LENGTH(sizeof(*r));
 	parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
 
@@ -1096,6 +1141,24 @@
 	return ret == n->nlmsg_len ? 0 : ret;
 }
 
+static int save_route_prep(void)
+{
+	int ret;
+
+	if (isatty(STDOUT_FILENO)) {
+		fprintf(stderr, "Not sending a binary stream to stdout\n");
+		return -1;
+	}
+
+	ret = write(STDOUT_FILENO, &route_dump_magic, sizeof(route_dump_magic));
+	if (ret != sizeof(route_dump_magic)) {
+		fprintf(stderr, "Can't write magic to dump file\n");
+		return -1;
+	}
+
+	return 0;
+}
+
 static int iproute_list_flush_or_save(int argc, char **argv, int action)
 {
 	int do_ipv6 = preferred_family;
@@ -1104,12 +1167,15 @@
 	unsigned int mark = 0;
 	rtnl_filter_t filter_fn;
 
-	if (action == IPROUTE_SAVE)
+	if (action == IPROUTE_SAVE) {
+		if (save_route_prep())
+			return -1;
+
 		filter_fn = save_route;
-	else
+	} else
 		filter_fn = print_route;
 
-	iproute_reset_filter();
+	iproute_reset_filter(0);
 	filter.tb = RT_TABLE_MAIN;
 
 	if ((action == IPROUTE_FLUSH) && argc <= 0) {
@@ -1243,8 +1309,6 @@
 	if (do_ipv6 == AF_UNSPEC && filter.tb)
 		do_ipv6 = AF_INET;
 
-	ll_init_map(&rth);
-
 	if (id || od)  {
 		int idx;
 
@@ -1312,7 +1376,7 @@
 
 			if (time(0) - start > 30) {
 				printf("\n*** Flush not completed after %ld seconds, %d entries remain ***\n",
-				       time(0) - start, filter.flushed);
+				       (long)(time(0) - start), filter.flushed);
 				exit(1);
 			}
 
@@ -1344,12 +1408,12 @@
 }
 
 
-int iproute_get(int argc, char **argv)
+static int iproute_get(int argc, char **argv)
 {
 	struct {
-		struct nlmsghdr 	n;
-		struct rtmsg 		r;
-		char   			buf[1024];
+		struct nlmsghdr	n;
+		struct rtmsg		r;
+		char  			buf[1024];
 	} req;
 	char  *idev = NULL;
 	char  *odev = NULL;
@@ -1359,7 +1423,7 @@
 
 	memset(&req, 0, sizeof(req));
 
-	iproute_reset_filter();
+	iproute_reset_filter(0);
 	filter.cloned = 2;
 
 	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
@@ -1431,12 +1495,10 @@
 	}
 
 	if (req.r.rtm_dst_len == 0) {
-		fprintf(stderr, "need at least destination address\n");
+		fprintf(stderr, "need at least a destination address\n");
 		exit(1);
 	}
 
-	ll_init_map(&rth);
-
 	if (idev || odev)  {
 		int idx;
 
@@ -1514,7 +1576,8 @@
 	exit(0);
 }
 
-int restore_handler(const struct sockaddr_nl *nl, struct nlmsghdr *n, void *arg)
+static int restore_handler(const struct sockaddr_nl *nl, struct nlmsghdr *n,
+			   void *arg)
 {
 	int ret;
 
@@ -1529,16 +1592,55 @@
 	return ret;
 }
 
-int iproute_restore(void)
+static int route_dump_check_magic(void)
 {
+	int ret;
+	__u32 magic = 0;
+
+	if (isatty(STDIN_FILENO)) {
+		fprintf(stderr, "Can't restore route dump from a terminal\n");
+		return -1;
+	}
+
+	ret = fread(&magic, sizeof(magic), 1, stdin);
+	if (magic != route_dump_magic) {
+		fprintf(stderr, "Magic mismatch (%d elems, %x magic)\n", ret, magic);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int iproute_restore(void)
+{
+	if (route_dump_check_magic())
+		exit(-1);
+
 	exit(rtnl_from_file(stdin, &restore_handler, NULL));
 }
 
-void iproute_reset_filter()
+static int show_handler(const struct sockaddr_nl *nl, struct nlmsghdr *n, void *arg)
+{
+	print_route(nl, n, stdout);
+	return 0;
+}
+
+static int iproute_showdump(void)
+{
+	if (route_dump_check_magic())
+		exit(-1);
+
+	exit(rtnl_from_file(stdin, &show_handler, NULL));
+}
+
+void iproute_reset_filter(int ifindex)
 {
 	memset(&filter, 0, sizeof(filter));
 	filter.mdst.bitlen = -1;
 	filter.msrc.bitlen = -1;
+	filter.oif = ifindex;
+	if (filter.oif > 0)
+		filter.oifmask = -1;
 }
 
 int do_iproute(int argc, char **argv)
@@ -1578,6 +1680,8 @@
 		return iproute_list_flush_or_save(argc-1, argv+1, IPROUTE_SAVE);
 	if (matches(*argv, "restore") == 0)
 		return iproute_restore();
+	if (matches(*argv, "showdump") == 0)
+		return iproute_showdump();
 	if (matches(*argv, "help") == 0)
 		usage();
 	fprintf(stderr, "Command \"%s\" is unknown, try \"ip route help\".\n", *argv);
diff --git a/ip/iprule.c b/ip/iprule.c
index 3a716ce..9304cf0 100644
--- a/ip/iprule.c
+++ b/ip/iprule.c
@@ -39,6 +39,9 @@
 	fprintf(stderr, "          [ prohibit | reject | unreachable ]\n");
 	fprintf(stderr, "          [ realms [SRCREALM/]DSTREALM ]\n");
 	fprintf(stderr, "          [ goto NUMBER ]\n");
+	fprintf(stderr, "          SUPPRESSOR\n");
+	fprintf(stderr, "SUPPRESSOR := [ suppress_prefixlength NUMBER ]\n");
+	fprintf(stderr, "              [ suppress_ifgroup DEVGROUP ]\n");
 	fprintf(stderr, "TABLE_ID := [ local | main | default | NUMBER ]\n");
 	exit(-1);
 }
@@ -63,14 +66,7 @@
 
 	parse_rtattr(tb, FRA_MAX, RTM_RTA(r), len);
 
-	if (r->rtm_family == AF_INET)
-		host_len = 32;
-	else if (r->rtm_family == AF_INET6)
-		host_len = 128;
-	else if (r->rtm_family == AF_DECnet)
-		host_len = 16;
-	else if (r->rtm_family == AF_IPX)
-		host_len = 80;
+	host_len = af_bit_len(r->rtm_family);
 
 	if (n->nlmsg_type == RTM_DELRULE)
 		fprintf(fp, "Deleted ");
@@ -86,7 +82,6 @@
 	if (tb[FRA_SRC]) {
 		if (r->rtm_src_len != host_len) {
 			fprintf(fp, "from %s/%u ", rt_addr_n2a(r->rtm_family,
-							 RTA_PAYLOAD(tb[FRA_SRC]),
 							 RTA_DATA(tb[FRA_SRC]),
 							 abuf, sizeof(abuf)),
 				r->rtm_src_len
@@ -107,7 +102,6 @@
 	if (tb[FRA_DST]) {
 		if (r->rtm_dst_len != host_len) {
 			fprintf(fp, "to %s/%u ", rt_addr_n2a(r->rtm_family,
-							 RTA_PAYLOAD(tb[FRA_DST]),
 							 RTA_DATA(tb[FRA_DST]),
 							 abuf, sizeof(abuf)),
 				r->rtm_dst_len
@@ -127,7 +121,7 @@
 		fprintf(fp, "tos %s ", rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1)));
 	}
 
- 	if (tb[FRA_FWMARK] || tb[FRA_FWMASK]) {
+	if (tb[FRA_FWMARK] || tb[FRA_FWMASK]) {
 		__u32 mark = 0, mask = 0;
 
 		if (tb[FRA_FWMARK])
@@ -166,9 +160,24 @@
 	}
 
 	table = rtm_get_table(r, tb);
-	if (table)
+	if (table) {
 		fprintf(fp, "lookup %s ", rtnl_rttable_n2a(table, b1, sizeof(b1)));
 
+		if (tb[FRA_SUPPRESS_PREFIXLEN]) {
+			int pl = rta_getattr_u32(tb[FRA_SUPPRESS_PREFIXLEN]);
+			if (pl != -1) {
+				fprintf(fp, "suppress_prefixlength %d ", pl);
+			}
+		}
+		if (tb[FRA_SUPPRESS_IFGROUP]) {
+			int group = rta_getattr_u32(tb[FRA_SUPPRESS_IFGROUP]);
+			if (group != -1) {
+				SPRINT_BUF(b1);
+				fprintf(fp, "suppress_ifgroup %s ", rtnl_group_n2a(group, b1, sizeof(b1)));
+			}
+		}
+	}
+
 	if (tb[FRA_FLOW]) {
 		__u32 to = rta_getattr_u32(tb[FRA_FLOW]);
 		__u32 from = to>>16;
@@ -238,9 +247,9 @@
 {
 	int table_ok = 0;
 	struct {
-		struct nlmsghdr 	n;
-		struct rtmsg 		r;
-		char   			buf[1024];
+		struct nlmsghdr	n;
+		struct rtmsg		r;
+		char  			buf[1024];
 	} req;
 
 	memset(&req, 0, sizeof(req));
@@ -323,6 +332,20 @@
 				addattr32(&req.n, sizeof(req), FRA_TABLE, tid);
 			}
 			table_ok = 1;
+		} else if (matches(*argv, "suppress_prefixlength") == 0 ||
+			   strcmp(*argv, "sup_pl") == 0) {
+			int pl;
+			NEXT_ARG();
+			if (get_s32(&pl, *argv, 0) || pl < 0)
+				invarg("suppress_prefixlength value is invalid\n", *argv);
+			addattr32(&req.n, sizeof(req), FRA_SUPPRESS_PREFIXLEN, pl);
+		} else if (matches(*argv, "suppress_ifgroup") == 0 ||
+			   strcmp(*argv, "sup_group") == 0) {
+			NEXT_ARG();
+			int group;
+			if (rtnl_group_a2n(&group, *argv))
+				invarg("Invalid \"suppress_ifgroup\" value\n", *argv);
+			addattr32(&req.n, sizeof(req), FRA_SUPPRESS_IFGROUP, group);
 		} else if (strcmp(*argv, "dev") == 0 ||
 			   strcmp(*argv, "iif") == 0) {
 			NEXT_ARG();
diff --git a/ip/iptoken.c b/ip/iptoken.c
new file mode 100644
index 0000000..655f160
--- /dev/null
+++ b/ip/iptoken.c
@@ -0,0 +1,211 @@
+/*
+ * iptoken.c    "ip token"
+ *
+ *              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.
+ *
+ * Authors:     Daniel Borkmann, <borkmann@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <linux/types.h>
+#include <linux/if.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+extern struct rtnl_handle rth;
+
+struct rtnl_dump_args {
+	FILE *fp;
+	int ifindex;
+};
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ip token [ list | set | get ] [ TOKEN ] [ dev DEV ]\n");
+	exit(-1);
+}
+
+static int print_token(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
+{
+	struct rtnl_dump_args *args = arg;
+	FILE *fp = args->fp;
+	int ifindex = args->ifindex;
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *ltb[IFLA_INET6_MAX + 1];
+	char abuf[256];
+
+	if (n->nlmsg_type != RTM_NEWLINK)
+		return -1;
+
+	len -= NLMSG_LENGTH(sizeof(*ifi));
+	if (len < 0)
+		return -1;
+
+	if (ifi->ifi_family != AF_INET6)
+		return -1;
+	if (ifi->ifi_index == 0)
+		return -1;
+	if (ifindex > 0 && ifi->ifi_index != ifindex)
+		return 0;
+	if (ifi->ifi_flags & (IFF_LOOPBACK | IFF_NOARP))
+		return 0;
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+	if (!tb[IFLA_PROTINFO])
+		return -1;
+
+	parse_rtattr_nested(ltb, IFLA_INET6_MAX, tb[IFLA_PROTINFO]);
+	if (!ltb[IFLA_INET6_TOKEN]) {
+		fprintf(stderr, "Seems there's no support for IPv6 token!\n");
+		return -1;
+	}
+
+	fprintf(fp, "token %s ",
+		format_host(ifi->ifi_family,
+			    RTA_PAYLOAD(ltb[IFLA_INET6_TOKEN]),
+			    RTA_DATA(ltb[IFLA_INET6_TOKEN]),
+			    abuf, sizeof(abuf)));
+	fprintf(fp, "dev %s ", ll_index_to_name(ifi->ifi_index));
+	fprintf(fp, "\n");
+	fflush(fp);
+
+	return 0;
+}
+
+static int iptoken_list(int argc, char **argv)
+{
+	int af = AF_INET6;
+	struct rtnl_dump_args da;
+	const struct rtnl_dump_filter_arg a[2] = {
+		{ .filter = print_token, .arg1 = &da, },
+		{ .filter = NULL, .arg1 = NULL, },
+	};
+
+	memset(&da, 0, sizeof(da));
+	da.fp = stdout;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if ((da.ifindex = ll_name_to_index(*argv)) == 0)
+				invarg("dev is invalid\n", *argv);
+			break;
+		}
+		argc--; argv++;
+	}
+
+	if (rtnl_wilddump_request(&rth, af, RTM_GETLINK) < 0) {
+		perror("Cannot send dump request");
+		return -1;
+	}
+
+	if (rtnl_dump_filter_l(&rth, a) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int iptoken_set(int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg ifi;
+		char buf[512];
+	} req;
+	struct rtattr *afs, *afs6;
+	bool have_token = false, have_dev = false;
+	inet_prefix addr;
+
+	memset(&addr, 0, sizeof(addr));
+	memset(&req, 0, sizeof(req));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST;
+	req.n.nlmsg_type = RTM_SETLINK;
+	req.ifi.ifi_family = AF_INET6;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (!have_dev) {
+				if ((req.ifi.ifi_index =
+				     ll_name_to_index(*argv)) == 0)
+					invarg("dev is invalid\n", *argv);
+				have_dev = true;
+			}
+		} else {
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (!have_token) {
+				afs = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+				afs6 = addattr_nest(&req.n, sizeof(req), AF_INET6);
+				get_prefix(&addr, *argv, req.ifi.ifi_family);
+				addattr_l(&req.n, sizeof(req), IFLA_INET6_TOKEN,
+					  &addr.data, addr.bytelen);
+				addattr_nest_end(&req.n, afs6);
+				addattr_nest_end(&req.n, afs);
+				have_token = true;
+			}
+		}
+		argc--; argv++;
+	}
+
+	if (!have_token) {
+		fprintf(stderr, "Not enough information: token "
+			"is required.\n");
+		return -1;
+	}
+	if (!have_dev) {
+		fprintf(stderr, "Not enough information: \"dev\" "
+			"argument is required.\n");
+		return -1;
+	}
+
+	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+int do_iptoken(int argc, char **argv)
+{
+	ll_init_map(&rth);
+
+	if (argc < 1) {
+		return iptoken_list(0, NULL);
+	} else if (matches(argv[0], "list") == 0 ||
+		   matches(argv[0], "lst") == 0 ||
+		   matches(argv[0], "show") == 0) {
+		return iptoken_list(argc - 1, argv + 1);
+	} else if (matches(argv[0], "set") == 0 ||
+		   matches(argv[0], "add") == 0) {
+		return iptoken_set(argc - 1, argv + 1);
+	} else if (matches(argv[0], "get") == 0) {
+		return iptoken_list(argc - 1, argv + 1);
+	} else if (matches(argv[0], "help") == 0)
+		usage();
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip token help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/iptunnel.c b/ip/iptunnel.c
index 3d41a27..caf8a28 100644
--- a/ip/iptunnel.c
+++ b/ip/iptunnel.c
@@ -33,7 +33,7 @@
 static void usage(void)
 {
 	fprintf(stderr, "Usage: ip tunnel { add | change | del | show | prl | 6rd } [ NAME ]\n");
-	fprintf(stderr, "          [ mode { ipip | gre | sit | isatap } ] [ remote ADDR ] [ local ADDR ]\n");
+	fprintf(stderr, "          [ mode { ipip | gre | sit | isatap | vti } ] [ remote ADDR ] [ local ADDR ]\n");
 	fprintf(stderr, "          [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n");
 	fprintf(stderr, "          [ prl-default ADDR ] [ prl-nodefault ADDR ] [ prl-delete ADDR ]\n");
 	fprintf(stderr, "          [ 6rd-prefix ADDR ] [ 6rd-relay_prefix ADDR ] [ 6rd-reset ]\n");
@@ -41,7 +41,7 @@
 	fprintf(stderr, "\n");
 	fprintf(stderr, "Where: NAME := STRING\n");
 	fprintf(stderr, "       ADDR := { IP_ADDRESS | any }\n");
-	fprintf(stderr, "       TOS  := { NUMBER | inherit }\n");
+	fprintf(stderr, "       TOS  := { STRING | 00..ff | inherit | inherit/STRING | inherit/00..ff }\n");
 	fprintf(stderr, "       TTL  := { 1..255 | inherit }\n");
 	fprintf(stderr, "       KEY  := { DOTTED_QUAD | NUMBER }\n");
 	exit(-1);
@@ -94,8 +94,15 @@
 				}
 				p->iph.protocol = IPPROTO_IPV6;
 				isatap++;
+			} else if (strcmp(*argv, "vti") == 0) {
+				if (p->iph.protocol && p->iph.protocol != IPPROTO_IPIP) {
+					fprintf(stderr, "You managed to ask for more than one tunnel mode.\n");
+					exit(-1);
+				}
+				p->iph.protocol = IPPROTO_IPIP;
+				p->i_flags |= VTI_ISVTI;
 			} else {
-				fprintf(stderr,"Cannot guess tunnel mode.\n");
+				fprintf(stderr,"Unknown tunnel mode \"%s\"\n", *argv);
 				exit(-1);
 			}
 		} else if (strcmp(*argv, "key") == 0) {
@@ -107,7 +114,7 @@
 				p->i_key = p->o_key = get_addr32(*argv);
 			else {
 				if (get_unsigned(&uval, *argv, 0)<0) {
-					fprintf(stderr, "invalid value of \"key\"\n");
+					fprintf(stderr, "invalid value for \"key\": \"%s\"; it should be an unsigned integer\n", *argv);
 					exit(-1);
 				}
 				p->i_key = p->o_key = htonl(uval);
@@ -120,7 +127,7 @@
 				p->i_key = get_addr32(*argv);
 			else {
 				if (get_unsigned(&uval, *argv, 0)<0) {
-					fprintf(stderr, "invalid value of \"ikey\"\n");
+					fprintf(stderr, "invalid value for \"ikey\": \"%s\"; it should be an unsigned integer\n", *argv);
 					exit(-1);
 				}
 				p->i_key = htonl(uval);
@@ -133,7 +140,7 @@
 				p->o_key = get_addr32(*argv);
 			else {
 				if (get_unsigned(&uval, *argv, 0)<0) {
-					fprintf(stderr, "invalid value of \"okey\"\n");
+					fprintf(stderr, "invalid value for \"okey\": \"%s\"; it should be an unsigned integer\n", *argv);
 					exit(-1);
 				}
 				p->o_key = htonl(uval);
@@ -181,14 +188,21 @@
 		} else if (strcmp(*argv, "tos") == 0 ||
 			   strcmp(*argv, "tclass") == 0 ||
 			   matches(*argv, "dsfield") == 0) {
+			char *dsfield;
 			__u32 uval;
 			NEXT_ARG();
+			dsfield = *argv;
+			strsep(&dsfield, "/");
 			if (strcmp(*argv, "inherit") != 0) {
-				if (rtnl_dsfield_a2n(&uval, *argv))
-					invarg("bad TOS value", *argv);
-				p->iph.tos = uval;
+				dsfield = *argv;
+				p->iph.tos = 0;
 			} else
 				p->iph.tos = 1;
+			if (dsfield) {
+				if (rtnl_dsfield_a2n(&uval, dsfield))
+					invarg("bad TOS value", *argv);
+				p->iph.tos |= uval;
+			}
 		} else {
 			if (strcmp(*argv, "name") == 0) {
 				NEXT_ARG();
@@ -220,20 +234,27 @@
 		else if (memcmp(p->name, "isatap", 6) == 0) {
 			p->iph.protocol = IPPROTO_IPV6;
 			isatap++;
+		} else if (memcmp(p->name, "vti", 3) == 0) {
+			p->iph.protocol = IPPROTO_IPIP;
+			p->i_flags |= VTI_ISVTI;
 		}
 	}
 
-	if (p->iph.protocol == IPPROTO_IPIP || p->iph.protocol == IPPROTO_IPV6) {
-		if ((p->i_flags & GRE_KEY) || (p->o_flags & GRE_KEY)) {
-			fprintf(stderr, "Keys are not allowed with ipip and sit.\n");
+	if ((p->i_flags & GRE_KEY) || (p->o_flags & GRE_KEY)) {
+		if (!(p->i_flags & VTI_ISVTI) &&
+		    (p->iph.protocol != IPPROTO_GRE)) {
+			fprintf(stderr, "Keys are not allowed with ipip and sit tunnels\n");
 			return -1;
 		}
 	}
 
 	if (medium[0]) {
 		p->link = if_nametoindex(medium);
-		if (p->link == 0)
+		if (p->link == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n",
+				medium);
 			return -1;
+		}
 	}
 
 	if (p->i_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) {
@@ -245,7 +266,7 @@
 		p->o_flags |= GRE_KEY;
 	}
 	if (IN_MULTICAST(ntohl(p->iph.daddr)) && !p->iph.saddr) {
-		fprintf(stderr, "Broadcast tunnel requires a source address.\n");
+		fprintf(stderr, "A broadcast tunnel requires a source address\n");
 		return -1;
 	}
 	if (isatap)
@@ -263,19 +284,22 @@
 		return -1;
 
 	if (p.iph.ttl && p.iph.frag_off == 0) {
-		fprintf(stderr, "ttl != 0 and noptmudisc are incompatible\n");
+		fprintf(stderr, "ttl != 0 and nopmtudisc are incompatible\n");
 		return -1;
 	}
 
 	switch (p.iph.protocol) {
 	case IPPROTO_IPIP:
-		return tnl_add_ioctl(cmd, "tunl0", p.name, &p);
+		if (p.i_flags & VTI_ISVTI)
+			return tnl_add_ioctl(cmd, "ip_vti0", p.name, &p);
+		else
+			return tnl_add_ioctl(cmd, "tunl0", p.name, &p);
 	case IPPROTO_GRE:
 		return tnl_add_ioctl(cmd, "gre0", p.name, &p);
 	case IPPROTO_IPV6:
 		return tnl_add_ioctl(cmd, "sit0", p.name, &p);
 	default:
-		fprintf(stderr, "cannot determine tunnel mode (ipip, gre or sit)\n");
+		fprintf(stderr, "cannot determine tunnel mode (ipip, gre, vti or sit)\n");
 		return -1;
 	}
 	return -1;
@@ -290,7 +314,10 @@
 
 	switch (p.iph.protocol) {
 	case IPPROTO_IPIP:
-		return tnl_del_ioctl("tunl0", p.name, &p);
+		if (p.i_flags & VTI_ISVTI)
+			return tnl_del_ioctl("ip_vti0", p.name, &p);
+		else
+			return tnl_del_ioctl("tunl0", p.name, &p);
 	case IPPROTO_GRE:
 		return tnl_del_ioctl("gre0", p.name, &p);
 	case IPPROTO_IPV6:
@@ -316,21 +343,21 @@
 	       p->name,
 	       tnl_strproto(p->iph.protocol),
 	       p->iph.daddr ? format_host(AF_INET, 4, &p->iph.daddr, s1, sizeof(s1))  : "any",
-	       p->iph.saddr ? rt_addr_n2a(AF_INET, 4, &p->iph.saddr, s2, sizeof(s2)) : "any");
+	       p->iph.saddr ? rt_addr_n2a(AF_INET, &p->iph.saddr, s2, sizeof(s2)) : "any");
 
-	if (p->i_flags & SIT_ISATAP) {
+	if (p->iph.protocol == IPPROTO_IPV6 && (p->i_flags & SIT_ISATAP)) {
 		struct ip_tunnel_prl prl[16];
 		int i;
-		
+
 		memset(prl, 0, sizeof(prl));
 		prl[0].datalen = sizeof(prl) - sizeof(prl[0]);
 		prl[0].addr = htonl(INADDR_ANY);
-	
+
 		if (!tnl_prl_ioctl(SIOCGETPRL, p->name, prl))
 			for (i = 1; i < sizeof(prl) / sizeof(prl[0]); i++)
 		{
 			if (prl[i].addr != htonl(INADDR_ANY)) {
-				printf(" %s %s ", 
+				printf(" %s %s ",
 					(prl[i].flags & PRL_DEFAULT) ? "pdr" : "pr",
 					format_host(AF_INET, 4, &prl[i].addr, s1, sizeof(s1)));
 			}
@@ -382,7 +409,7 @@
 	}
 
 	if (p->i_flags&GRE_SEQ)
-		printf("%s  Drop packets out of sequence.\n", _SL_);
+		printf("%s  Drop packets out of sequence.", _SL_);
 	if (p->i_flags&GRE_CSUM)
 		printf("%s  Checksum in received packet is required.", _SL_);
 	if (p->o_flags&GRE_SEQ)
@@ -421,7 +448,7 @@
 		buf[sizeof(buf) - 1] = 0;
 		if ((ptr = strchr(buf, ':')) == NULL ||
 		    (*ptr++ = 0, sscanf(buf, "%s", name) != 1)) {
-			fprintf(stderr, "Wrong format of /proc/net/dev. Sorry.\n");
+			fprintf(stderr, "Wrong format for /proc/net/dev. Giving up.\n");
 			fclose(fp);
 			return -1;
 		}
@@ -438,7 +465,7 @@
 			continue;
 		type = ll_index_to_type(index);
 		if (type == -1) {
-			fprintf(stderr, "Failed to get type of [%s]\n", name);
+			fprintf(stderr, "Failed to get type of \"%s\"\n", name);
 			continue;
 		}
 		if (type != ARPHRD_TUNNEL && type != ARPHRD_IPGRE && type != ARPHRD_SIT)
@@ -479,7 +506,10 @@
 
 	switch (p.iph.protocol) {
 	case IPPROTO_IPIP:
-		err = tnl_get_ioctl(p.name[0] ? p.name : "tunl0", &p);
+		if (p.i_flags & VTI_ISVTI)
+			err = tnl_get_ioctl(p.name[0] ? p.name : "ip_vti0", &p);
+		else
+			err = tnl_get_ioctl(p.name[0] ? p.name : "tunl0", &p);
 		break;
 	case IPPROTO_GRE:
 		err = tnl_get_ioctl(p.name[0] ? p.name : "gre0", &p);
@@ -532,17 +562,17 @@
 			strncpy(medium, *argv, IFNAMSIZ-1);
 			devname++;
 		} else {
-			fprintf(stderr,"%s: Invalid PRL parameter.\n", *argv);
+			fprintf(stderr,"Invalid PRL parameter \"%s\"\n", *argv);
 			exit(-1);
 		}
 		if (count > 1) {
-			fprintf(stderr,"One PRL entry at a time.\n");
+			fprintf(stderr,"One PRL entry at a time\n");
 			exit(-1);
 		}
 		argc--; argv++;
 	}
 	if (devname == 0) {
-		fprintf(stderr, "Must specify dev.\n");
+		fprintf(stderr, "Must specify device\n");
 		exit(-1);
 	}
 
@@ -582,13 +612,13 @@
 			strncpy(medium, *argv, IFNAMSIZ-1);
 			devname++;
 		} else {
-			fprintf(stderr,"%s: Invalid 6RD parameter.\n", *argv);
+			fprintf(stderr,"Invalid 6RD parameter \"%s\"\n", *argv);
 			exit(-1);
 		}
 		argc--; argv++;
 	}
 	if (devname == 0) {
-		fprintf(stderr, "Must specify dev.\n");
+		fprintf(stderr, "Must specify device\n");
 		exit(-1);
 	}
 
@@ -611,7 +641,7 @@
 	case AF_INET6:
 		return do_ip6tunnel(argc, argv);
 	default:
-		fprintf(stderr, "Unsupported family:%d\n", preferred_family);
+		fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
 		exit(-1);
 	}
 
@@ -620,7 +650,7 @@
 			return do_add(SIOCADDTUNNEL, argc-1, argv+1);
 		if (matches(*argv, "change") == 0)
 			return do_add(SIOCCHGTUNNEL, argc-1, argv+1);
-		if (matches(*argv, "del") == 0)
+		if (matches(*argv, "delete") == 0)
 			return do_del(argc-1, argv+1);
 		if (matches(*argv, "show") == 0 ||
 		    matches(*argv, "lst") == 0 ||
@@ -635,6 +665,6 @@
 	} else
 		return do_show(0, NULL);
 
-	fprintf(stderr, "Command \"%s\" is unknown, try \"ip tunnel help\".\n", *argv);
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip tunnel help\"\n", *argv);
 	exit(-1);
 }
diff --git a/ip/iptuntap.c b/ip/iptuntap.c
index 29f2777..b9b28a1 100644
--- a/ip/iptuntap.c
+++ b/ip/iptuntap.c
@@ -36,9 +36,9 @@
 
 static void usage(void)
 {
-	fprintf(stderr, "Usage: ip tuntap { add | del } [ dev PHYS_DEV ] \n");
+	fprintf(stderr, "Usage: ip tuntap { add | del | show | list | lst | help } [ dev PHYS_DEV ] \n");
 	fprintf(stderr, "          [ mode { tun | tap } ] [ user USER ] [ group GROUP ]\n");
-	fprintf(stderr, "          [ one_queue ] [ pi ] [ vnet_hdr ]\n");
+	fprintf(stderr, "          [ one_queue ] [ pi ] [ vnet_hdr ] [ multi_queue ]\n");
 	fprintf(stderr, "\n");
 	fprintf(stderr, "Where: USER  := { STRING | NUMBER }\n");
 	fprintf(stderr, "       GROUP := { STRING | NUMBER }\n");
@@ -128,7 +128,7 @@
 				}
 				ifr->ifr_flags |= IFF_TAP;
 			} else {
-				fprintf(stderr,"Cannot guess tunnel mode.\n");
+				fprintf(stderr,"Unknown tunnel mode \"%s\"\n", *argv);
 				exit(-1);
 			}
 		} else if (uid && matches(*argv, "user") == 0) {
@@ -168,6 +168,8 @@
 			ifr->ifr_flags |= IFF_ONE_QUEUE;
 		} else if (matches(*argv, "vnet_hdr") == 0) {
 			ifr->ifr_flags |= IFF_VNET_HDR;
+		} else if (matches(*argv, "multi_queue") == 0) {
+			ifr->ifr_flags |= IFF_MULTI_QUEUE;
 		} else if (matches(*argv, "dev") == 0) {
 			NEXT_ARG();
 			strncpy(ifr->ifr_name, *argv, IFNAMSIZ-1);
@@ -184,6 +186,11 @@
 		argc--; argv++;
 	}
 
+	if (!(ifr->ifr_flags & TUN_TYPE_MASK)) {
+		fprintf(stderr, "You failed to specify a tunnel mode\n");
+		return -1;
+	}
+
 	return 0;
 }
 
@@ -197,10 +204,6 @@
 	if (parse_args(argc, argv, &ifr, &uid, &gid) < 0)
 		return -1;
 
-	if (!(ifr.ifr_flags & TUN_TYPE_MASK)) {
-		fprintf(stderr, "You failed to specify a tunnel mode\n");
-		return -1;
-	}
 	return tap_add_ioctl(&ifr, uid, gid);
 }
 
@@ -307,7 +310,7 @@
 	if (argc > 0) {
 		if (matches(*argv, "add") == 0)
 			return do_add(argc-1, argv+1);
-		if (matches(*argv, "del") == 0)
+		if (matches(*argv, "delete") == 0)
 			return do_del(argc-1, argv+1);
 		if (matches(*argv, "show") == 0 ||
                     matches(*argv, "lst") == 0 ||
diff --git a/ip/ipxfrm.c b/ip/ipxfrm.c
index 6be62e0..95f91a5 100644
--- a/ip/ipxfrm.c
+++ b/ip/ipxfrm.c
@@ -14,8 +14,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  */
 /*
  * based on ip.c, iproute.c
@@ -25,22 +24,21 @@
  *	Masahide NAKAMURA @USAGI
  */
 
+#include <alloca.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
-#include <endian.h>
 #include <time.h>
 #include <netdb.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
 #include <linux/xfrm.h>
-#include <linux/in.h>
-#include <linux/in6.h>
 
 #include "utils.h"
 #include "xfrm.h"
+#include "ip_common.h"
 
 #define STRBUF_SIZE	(128)
 #define STRBUF_CAT(buf, str) \
@@ -290,10 +288,10 @@
 		fputs(title, fp);
 
 	memset(abuf, '\0', sizeof(abuf));
-	fprintf(fp, "src %s ", rt_addr_n2a(family, sizeof(*saddr),
+	fprintf(fp, "src %s ", rt_addr_n2a(family,
 					   saddr, abuf, sizeof(abuf)));
 	memset(abuf, '\0', sizeof(abuf));
-	fprintf(fp, "dst %s", rt_addr_n2a(family, sizeof(id->daddr),
+	fprintf(fp, "dst %s", rt_addr_n2a(family,
 					  &id->daddr, abuf, sizeof(abuf)));
 	fprintf(fp, "%s", _SL_);
 
@@ -359,7 +357,7 @@
 
 	if (prefix)
 		fputs(prefix, fp);
-	fprintf(fp, "  replay-window %u replay %u failed %u%s", 
+	fprintf(fp, "  replay-window %u replay %u failed %u%s",
 		s->replay_window, s->replay, s->integrity_failed, _SL_);
 }
 
@@ -410,7 +408,7 @@
 
 		if (prefix)
 			fputs(prefix, fp);
-		fprintf(fp, "  expire add: soft %llu(sec), hard %llu(sec)%s", 
+		fprintf(fp, "  expire add: soft %llu(sec), hard %llu(sec)%s",
 			(unsigned long long) cfg->soft_add_expires_seconds,
 			(unsigned long long) cfg->hard_add_expires_seconds,
 			_SL_);
@@ -457,13 +455,11 @@
 		fputs(prefix, fp);
 
 	memset(abuf, '\0', sizeof(abuf));
-	fprintf(fp, "src %s/%u ", rt_addr_n2a(f, sizeof(sel->saddr),
-					      &sel->saddr, abuf, sizeof(abuf)),
+	fprintf(fp, "src %s/%u ", rt_addr_n2a(f, &sel->saddr, abuf, sizeof(abuf)),
 		sel->prefixlen_s);
 
 	memset(abuf, '\0', sizeof(abuf));
-	fprintf(fp, "dst %s/%u ", rt_addr_n2a(f, sizeof(sel->daddr),
-					      &sel->daddr, abuf, sizeof(abuf)),
+	fprintf(fp, "dst %s/%u ", rt_addr_n2a(f, &sel->daddr, abuf, sizeof(abuf)),
 		sel->prefixlen_d);
 
 	if (sel->proto)
@@ -537,12 +533,14 @@
 		goto fin;
 	}
 
-	fprintf(fp, "0x");
-	for (i = 0; i < keylen; i ++)
-		fprintf(fp, "%.2x", (unsigned char)algo->alg_key[i]);
+	if (keylen > 0) {
+		fprintf(fp, "0x");
+		for (i = 0; i < keylen; i ++)
+			fprintf(fp, "%.2x", (unsigned char)algo->alg_key[i]);
 
-	if (show_stats > 0)
-		fprintf(fp, " (%d bits)", algo->alg_key_len);
+		if (show_stats > 0)
+			fprintf(fp, " (%d bits)", algo->alg_key_len);
+	}
 
  fin:
 	if (newline)
@@ -558,16 +556,13 @@
 static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
 			    FILE *fp, const char *prefix)
 {
-	struct {
-		struct xfrm_algo algo;
-		char key[algo->alg_key_len / 8];
-	} base;
+	struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
 
-	memcpy(base.algo.alg_name, algo->alg_name, sizeof(base.algo.alg_name));
-	base.algo.alg_key_len = algo->alg_key_len;
-	memcpy(base.algo.alg_key, algo->alg_key, algo->alg_key_len / 8);
+	memcpy(base_algo->alg_name, algo->alg_name, sizeof(base_algo->alg_name));
+	base_algo->alg_key_len = algo->alg_key_len;
+	memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
 
-	__xfrm_algo_print(&base.algo, XFRMA_ALG_AEAD, len, fp, prefix, 0);
+	__xfrm_algo_print(base_algo, XFRMA_ALG_AEAD, len, fp, prefix, 0);
 
 	fprintf(fp, " %d", algo->alg_icv_len);
 
@@ -577,16 +572,13 @@
 static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
 				  FILE *fp, const char *prefix)
 {
-	struct {
-		struct xfrm_algo algo;
-		char key[algo->alg_key_len / 8];
-	} base;
+	struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
 
-	memcpy(base.algo.alg_name, algo->alg_name, sizeof(base.algo.alg_name));
-	base.algo.alg_key_len = algo->alg_key_len;
-	memcpy(base.algo.alg_key, algo->alg_key, algo->alg_key_len / 8);
+	memcpy(base_algo->alg_name, algo->alg_name, sizeof(base_algo->alg_name));
+	base_algo->alg_key_len = algo->alg_key_len;
+	memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
 
-	__xfrm_algo_print(&base.algo, XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0);
+	__xfrm_algo_print(base_algo, XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0);
 
 	fprintf(fp, " %d", algo->alg_trunc_len);
 
@@ -594,7 +586,7 @@
 }
 
 static void xfrm_tmpl_print(struct xfrm_user_tmpl *tmpls, int len,
-			    __u16 family, FILE *fp, const char *prefix)
+			    FILE *fp, const char *prefix)
 {
 	int ntmpls = len / sizeof(struct xfrm_user_tmpl);
 	int i;
@@ -665,7 +657,7 @@
 
 	NEXT_ARG();
 	if (get_u32(&mark->v, *argv, 0)) {
-		invarg("Illegal \"mark\" value\n", *argv);
+		invarg("MARK value is invalid\n", *argv);
 	}
 	if (argc > 1)
 		NEXT_ARG();
@@ -677,7 +669,7 @@
 	if (strcmp(*argv, "mask") == 0) {
 		NEXT_ARG();
 		if (get_u32(&mark->m, *argv, 0)) {
-			invarg("Illegal \"mark\" mask\n", *argv);
+			invarg("MASK value is invalid\n", *argv);
 		}
 	} else {
 		mark->m = 0xffffffff;
@@ -697,7 +689,8 @@
 	if (tb[XFRMA_MARK]) {
 		struct rtattr *rta = tb[XFRMA_MARK];
 		struct xfrm_mark *m = (struct xfrm_mark *) RTA_DATA(rta);
-		fprintf(fp, "\tmark %d/0x%x\n", m->v, m->m);
+		fprintf(fp, "\tmark %#x/%#x", m->v, m->m);
+		fprintf(fp, "%s", _SL_);
 	}
 
 	if (tb[XFRMA_ALG_AUTH] && !tb[XFRMA_ALG_AUTH_TRUNC]) {
@@ -762,15 +755,14 @@
 
 		memset(abuf, '\0', sizeof(abuf));
 		fprintf(fp, "addr %s",
-			rt_addr_n2a(family, sizeof(e->encap_oa),
-				    &e->encap_oa, abuf, sizeof(abuf)));
+			rt_addr_n2a(family, &e->encap_oa, abuf, sizeof(abuf)));
 		fprintf(fp, "%s", _SL_);
 	}
 
 	if (tb[XFRMA_TMPL]) {
 		struct rtattr *rta = tb[XFRMA_TMPL];
 		xfrm_tmpl_print((struct xfrm_user_tmpl *) RTA_DATA(rta),
-				RTA_PAYLOAD(rta), family, fp, prefix);
+				RTA_PAYLOAD(rta), fp, prefix);
 	}
 
 	if (tb[XFRMA_COADDR]) {
@@ -791,7 +783,7 @@
 
 		memset(abuf, '\0', sizeof(abuf));
 		fprintf(fp, "%s",
-			rt_addr_n2a(family, sizeof(*coa), coa,
+			rt_addr_n2a(family, coa,
 				    abuf, sizeof(abuf)));
 		fprintf(fp, "%s", _SL_);
 	}
@@ -815,6 +807,62 @@
 		fprintf(fp, "%s", _SL_);
 	}
 
+	if (tb[XFRMA_REPLAY_VAL]) {
+		struct xfrm_replay_state *replay;
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "anti-replay context: ");
+
+		if (RTA_PAYLOAD(tb[XFRMA_REPLAY_VAL]) < sizeof(*replay)) {
+			fprintf(fp, "(ERROR truncated)");
+			fprintf(fp, "%s", _SL_);
+			return;
+		}
+
+		replay = (struct xfrm_replay_state *)RTA_DATA(tb[XFRMA_REPLAY_VAL]);
+		fprintf(fp, "seq 0x%x, oseq 0x%x, bitmap 0x%08x",
+			replay->seq, replay->oseq, replay->bitmap);
+		fprintf(fp, "%s", _SL_);
+	}
+
+	if (tb[XFRMA_REPLAY_ESN_VAL]) {
+		struct xfrm_replay_state_esn *replay;
+		unsigned int i, j;
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "anti-replay esn context:");
+
+		if (RTA_PAYLOAD(tb[XFRMA_REPLAY_ESN_VAL]) < sizeof(*replay)) {
+			fprintf(fp, "(ERROR truncated)");
+			fprintf(fp, "%s", _SL_);
+			return;
+		}
+		fprintf(fp, "%s", _SL_);
+
+		replay = (struct xfrm_replay_state_esn *)RTA_DATA(tb[XFRMA_REPLAY_ESN_VAL]);
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, " seq-hi 0x%x, seq 0x%x, oseq-hi 0x%0x, oseq 0x%0x",
+			replay->seq_hi, replay->seq, replay->oseq_hi,
+			replay->oseq);
+		fprintf(fp, "%s", _SL_);
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, " replay_window %u, bitmap-length %u",
+			replay->replay_window, replay->bmp_len);
+		for (i = replay->bmp_len, j = 0; i; i--) {
+			if (j++ % 8 == 0) {
+				fprintf(fp, "%s", _SL_);
+				if (prefix)
+					fputs(prefix, fp);
+				fprintf(fp, " ");
+			}
+			fprintf(fp, "%08x ", replay->bmp[i - 1]);
+		}
+		fprintf(fp, "%s", _SL_);
+	}
 }
 
 static int xfrm_selector_iszero(struct xfrm_selector *s)
@@ -858,9 +906,20 @@
 		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_ICMP, "icmp");
 		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_AF_UNSPEC, "af-unspec");
 		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_ALIGN4, "align4");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_ESN, "esn");
 		if (flags)
 			fprintf(fp, "%x", flags);
 	}
+	if (show_stats > 0 && tb[XFRMA_SA_EXTRA_FLAGS]) {
+		__u32 extra_flags = *(__u32 *)RTA_DATA(tb[XFRMA_SA_EXTRA_FLAGS]);
+
+		fprintf(fp, "extra_flag ");
+		XFRM_FLAG_PRINT(fp, extra_flags,
+				XFRM_SA_XFLAG_DONT_ENCAP_DSCP,
+				"dont-encap-dscp");
+		if (extra_flags)
+			fprintf(fp, "%x", extra_flags);
+	}
 	if (show_stats > 0)
 		fprintf(fp, " (0x%s)", strxf_mask8(xsinfo->flags));
 	fprintf(fp, "%s", _SL_);
@@ -1015,7 +1074,7 @@
 
 			get_prefix(&src, *argv, preferred_family);
 			if (src.family == AF_UNSPEC)
-				invarg("\"src\" address family is AF_UNSPEC", *argv);
+				invarg("value after \"src\" has an unrecognized address family", *argv);
 			if (family)
 				*family = src.family;
 
@@ -1028,7 +1087,7 @@
 
 			get_prefix(&dst, *argv, preferred_family);
 			if (dst.family == AF_UNSPEC)
-				invarg("\"dst\" address family is AF_UNSPEC", *argv);
+				invarg("value after \"dst\" has an unrecognized address family", *argv);
 			if (family)
 				*family = dst.family;
 
@@ -1043,7 +1102,7 @@
 
 			ret = xfrm_xfrmproto_getbyname(*argv);
 			if (ret < 0)
-				invarg("\"XFRM-PROTO\" is invalid", *argv);
+				invarg("XFRM-PROTO value is invalid", *argv);
 
 			id->proto = (__u8)ret;
 
@@ -1054,7 +1113,7 @@
 
 			NEXT_ARG();
 			if (get_u32(&spi, *argv, 0))
-				invarg("\"SPI\" is invalid", *argv);
+				invarg("SPI value is invalid", *argv);
 
 			spi = htonl(spi);
 			id->spi = spi;
@@ -1072,7 +1131,19 @@
 	}
 
 	if (src.family && dst.family && (src.family != dst.family))
-		invarg("the same address family is required between \"src\" and \"dst\"", *argv);
+		invarg("the same address family is required between values after \"src\" and \"dst\"", *argv);
+
+	if (id->spi && id->proto) {
+		if (xfrm_xfrmproto_is_ro(id->proto)) {
+			fprintf(stderr, "\"spi\" is invalid with XFRM-PROTO value \"%s\"\n",
+			        strxf_xfrmproto(id->proto));
+			exit(1);
+		} else if (id->proto == IPPROTO_COMP && ntohl(id->spi) >= 0x10000) {
+			fprintf(stderr, "SPI value is too large with XFRM-PROTO value \"%s\"\n",
+			        strxf_xfrmproto(id->proto));
+			exit(1);
+		}
+	}
 
 	if (loose == 0 && id->proto == 0)
 		missarg("XFRM-PROTO");
@@ -1101,7 +1172,7 @@
 	else if (matches(*argv, "beet") == 0)
 		*mode = XFRM_MODE_BEET;
 	else
-		invarg("\"MODE\" is invalid", *argv);
+		invarg("MODE value is invalid", *argv);
 
 	*argcp = argc;
 	*argvp = argv;
@@ -1119,7 +1190,7 @@
 	else if (strcmp(*argv, "espinudp") == 0)
 		*type = 2;
 	else
-		invarg("\"ENCAP-TYPE\" is invalid", *argv);
+		invarg("ENCAP-TYPE value is invalid", *argv);
 
 	*argcp = argc;
 	*argvp = argv;
@@ -1134,7 +1205,7 @@
 	char **argv = *argvp;
 
 	if (get_u32(reqid, *argv, 0))
-		invarg("\"REQID\" is invalid", *argv);
+		invarg("REQID value is invalid", *argv);
 
 	*argcp = argc;
 	*argvp = argv;
@@ -1168,7 +1239,7 @@
 					upspec = pp->p_proto;
 				else {
 					if (get_u8(&upspec, *argv, 0))
-						invarg("\"PROTO\" is invalid", *argv);
+						invarg("PROTO value is invalid", *argv);
 				}
 			}
 			sel->proto = upspec;
@@ -1181,7 +1252,7 @@
 			NEXT_ARG();
 
 			if (get_u16(&sel->sport, *argv, 0))
-				invarg("\"PORT\" is invalid", *argv);
+				invarg("value after \"sport\" is invalid", *argv);
 			sel->sport = htons(sel->sport);
 			if (sel->sport)
 				sel->sport_mask = ~((__u16)0);
@@ -1194,7 +1265,7 @@
 			NEXT_ARG();
 
 			if (get_u16(&sel->dport, *argv, 0))
-				invarg("\"PORT\" is invalid", *argv);
+				invarg("value after \"dport\" is invalid", *argv);
 			sel->dport = htons(sel->dport);
 			if (sel->dport)
 				sel->dport_mask = ~((__u16)0);
@@ -1208,7 +1279,7 @@
 
 			if (get_u16(&sel->sport, *argv, 0) ||
 			    (sel->sport & ~((__u16)0xff)))
-				invarg("\"type\" value is invalid", *argv);
+				invarg("value after \"type\" is invalid", *argv);
 			sel->sport = htons(sel->sport);
 			sel->sport_mask = ~((__u16)0);
 
@@ -1222,7 +1293,7 @@
 
 			if (get_u16(&sel->dport, *argv, 0) ||
 			    (sel->dport & ~((__u16)0xff)))
-				invarg("\"code\" value is invalid", *argv);
+				invarg("value after \"code\" is invalid", *argv);
 			sel->dport = htons(sel->dport);
 			sel->dport_mask = ~((__u16)0);
 
@@ -1239,7 +1310,7 @@
 				uval = htonl(get_addr32(*argv));
 			else {
 				if (get_unsigned(&uval, *argv, 0)<0) {
-					fprintf(stderr, "invalid value of \"key\"\n");
+					fprintf(stderr, "value after \"key\" is invalid\n");
 					exit(-1);
 				}
 			}
@@ -1270,7 +1341,7 @@
 		case IPPROTO_DCCP:
 			break;
 		default:
-			fprintf(stderr, "\"sport\" and \"dport\" are invalid with proto=%s\n", strxf_proto(sel->proto));
+			fprintf(stderr, "\"sport\" and \"dport\" are invalid with PROTO value \"%s\"\n", strxf_proto(sel->proto));
 			exit(1);
 		}
 	}
@@ -1281,7 +1352,7 @@
 		case IPPROTO_MH:
 			break;
 		default:
-			fprintf(stderr, "\"type\" and \"code\" are invalid with proto=%s\n", strxf_proto(sel->proto));
+			fprintf(stderr, "\"type\" and \"code\" are invalid with PROTO value \"%s\"\n", strxf_proto(sel->proto));
 			exit(1);
 		}
 	}
@@ -1290,7 +1361,7 @@
 		case IPPROTO_GRE:
 			break;
 		default:
-			fprintf(stderr, "\"key\" is invalid with proto=%s\n", strxf_proto(sel->proto));
+			fprintf(stderr, "\"key\" is invalid with PROTO value \"%s\"\n", strxf_proto(sel->proto));
 			exit(1);
 		}
 	}
@@ -1318,7 +1389,7 @@
 
 			get_prefix(&src, *argv, preferred_family);
 			if (src.family == AF_UNSPEC)
-				invarg("\"src\" address family is AF_UNSPEC", *argv);
+				invarg("value after \"src\" has an unrecognized address family", *argv);
 			sel->family = src.family;
 
 			memcpy(&sel->saddr, &src.data, sizeof(sel->saddr));
@@ -1331,7 +1402,7 @@
 
 			get_prefix(&dst, *argv, preferred_family);
 			if (dst.family == AF_UNSPEC)
-				invarg("\"dst\" address family is AF_UNSPEC", *argv);
+				invarg("value after \"dst\" has an unrecognized address family", *argv);
 			sel->family = dst.family;
 
 			memcpy(&sel->daddr, &dst.data, sizeof(sel->daddr));
@@ -1349,7 +1420,7 @@
 			else {
 				ifindex = ll_name_to_index(*argv);
 				if (ifindex <= 0)
-					invarg("\"DEV\" is invalid", *argv);
+					invarg("DEV value is invalid", *argv);
 			}
 			sel->ifindex = ifindex;
 
@@ -1372,7 +1443,7 @@
 	}
 
 	if (src.family && dst.family && (src.family != dst.family))
-		invarg("the same address family is required between \"src\" and \"dst\"", *argv);
+		invarg("the same address family is required between values after \"src\" and \"dst\"", *argv);
 
 	if (argc == *argcp)
 		missarg("SELECTOR");
@@ -1394,44 +1465,44 @@
 		NEXT_ARG();
 		ret = get_u64(&lft->soft_add_expires_seconds, *argv, 0);
 		if (ret)
-			invarg("\"time-soft\" value is invalid", *argv);
+			invarg("value after \"time-soft\" is invalid", *argv);
 	} else if (strcmp(*argv, "time-hard") == 0) {
 		NEXT_ARG();
 		ret = get_u64(&lft->hard_add_expires_seconds, *argv, 0);
 		if (ret)
-			invarg("\"time-hard\" value is invalid", *argv);
+			invarg("value after \"time-hard\" is invalid", *argv);
 	} else if (strcmp(*argv, "time-use-soft") == 0) {
 		NEXT_ARG();
 		ret = get_u64(&lft->soft_use_expires_seconds, *argv, 0);
 		if (ret)
-			invarg("\"time-use-soft\" value is invalid", *argv);
+			invarg("value after \"time-use-soft\" is invalid", *argv);
 	} else if (strcmp(*argv, "time-use-hard") == 0) {
 		NEXT_ARG();
 		ret = get_u64(&lft->hard_use_expires_seconds, *argv, 0);
 		if (ret)
-			invarg("\"time-use-hard\" value is invalid", *argv);
+			invarg("value after \"time-use-hard\" is invalid", *argv);
 	} else if (strcmp(*argv, "byte-soft") == 0) {
 		NEXT_ARG();
 		ret = get_u64(&lft->soft_byte_limit, *argv, 0);
 		if (ret)
-			invarg("\"byte-soft\" value is invalid", *argv);
+			invarg("value after \"byte-soft\" is invalid", *argv);
 	} else if (strcmp(*argv, "byte-hard") == 0) {
 		NEXT_ARG();
 		ret = get_u64(&lft->hard_byte_limit, *argv, 0);
 		if (ret)
-			invarg("\"byte-hard\" value is invalid", *argv);
+			invarg("value after \"byte-hard\" is invalid", *argv);
 	} else if (strcmp(*argv, "packet-soft") == 0) {
 		NEXT_ARG();
 		ret = get_u64(&lft->soft_packet_limit, *argv, 0);
 		if (ret)
-			invarg("\"packet-soft\" value is invalid", *argv);
+			invarg("value after \"packet-soft\" is invalid", *argv);
 	} else if (strcmp(*argv, "packet-hard") == 0) {
 		NEXT_ARG();
 		ret = get_u64(&lft->hard_packet_limit, *argv, 0);
 		if (ret)
-			invarg("\"packet-hard\" value is invalid", *argv);
+			invarg("value after \"packet-hard\" is invalid", *argv);
 	} else
-		invarg("\"LIMIT\" is invalid", *argv);
+		invarg("LIMIT value is invalid", *argv);
 
 	*argcp = argc;
 	*argvp = argv;
diff --git a/ip/link_gre.c b/ip/link_gre.c
index 839fb29..1d78387 100644
--- a/ip/link_gre.c
+++ b/ip/link_gre.c
@@ -23,19 +23,27 @@
 #include "ip_common.h"
 #include "tunnel.h"
 
+static void print_usage(FILE *f)
+{
+	fprintf(f, "Usage: ip link { add | set | change | replace | del } NAME\n");
+	fprintf(f, "          type { gre | gretap } [ remote ADDR ] [ local ADDR ]\n");
+	fprintf(f, "          [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n");
+	fprintf(f, "          [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ]\n");
+	fprintf(f, "          [ noencap ] [ encap { fou | gue | none } ]\n");
+	fprintf(f, "          [ encap-sport PORT ] [ encap-dport PORT ]\n");
+	fprintf(f, "          [ [no]encap-csum ] [ [no]encap-csum6 ] [ [no]encap-remcsum ]\n");
+	fprintf(f, "\n");
+	fprintf(f, "Where: NAME := STRING\n");
+	fprintf(f, "       ADDR := { IP_ADDRESS | any }\n");
+	fprintf(f, "       TOS  := { NUMBER | inherit }\n");
+	fprintf(f, "       TTL  := { 1..255 | inherit }\n");
+	fprintf(f, "       KEY  := { DOTTED_QUAD | NUMBER }\n");
+}
+
 static void usage(void) __attribute__((noreturn));
 static void usage(void)
 {
-	fprintf(stderr, "Usage: ip link { add | set | change | replace | del } NAME\n");
-	fprintf(stderr, "          type { gre | gretap } [ remote ADDR ] [ local ADDR ]\n");
-	fprintf(stderr, "          [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n");
-	fprintf(stderr, "          [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ]\n");
-	fprintf(stderr, "\n");
-	fprintf(stderr, "Where: NAME := STRING\n");
-	fprintf(stderr, "       ADDR := { IP_ADDRESS | any }\n");
-	fprintf(stderr, "       TOS  := { NUMBER | inherit }\n");
-	fprintf(stderr, "       TTL  := { 1..255 | inherit }\n");
-	fprintf(stderr, "       KEY  := { DOTTED_QUAD | NUMBER }\n");
+	print_usage(stderr);
 	exit(-1);
 }
 
@@ -62,6 +70,10 @@
 	__u8 ttl = 0;
 	__u8 tos = 0;
 	int len;
+	__u16 encaptype = 0;
+	__u16 encapflags = 0;
+	__u16 encapsport = 0;
+	__u16 encapdport = 0;
 
 	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
 		memset(&req, 0, sizeof(req));
@@ -127,6 +139,15 @@
 
 		if (greinfo[IFLA_GRE_LINK])
 			link = rta_getattr_u8(greinfo[IFLA_GRE_LINK]);
+
+		if (greinfo[IFLA_GRE_ENCAP_TYPE])
+			encaptype = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_TYPE]);
+		if (greinfo[IFLA_GRE_ENCAP_FLAGS])
+			encapflags = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_FLAGS]);
+		if (greinfo[IFLA_GRE_ENCAP_SPORT])
+			encapsport = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_SPORT]);
+		if (greinfo[IFLA_GRE_ENCAP_DPORT])
+			encapdport = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_DPORT]);
 	}
 
 	while (argc > 0) {
@@ -141,7 +162,7 @@
 			else {
 				if (get_unsigned(&uval, *argv, 0) < 0) {
 					fprintf(stderr,
-						"Invalid value for \"key\"\n");
+						"Invalid value for \"key\": \"%s\"; it should be an unsigned integer\n", *argv);
 					exit(-1);
 				}
 				uval = htonl(uval);
@@ -157,7 +178,7 @@
 				uval = get_addr32(*argv);
 			else {
 				if (get_unsigned(&uval, *argv, 0)<0) {
-					fprintf(stderr, "invalid value of \"ikey\"\n");
+					fprintf(stderr, "invalid value for \"ikey\": \"%s\"; it should be an unsigned integer\n", *argv);
 					exit(-1);
 				}
 				uval = htonl(uval);
@@ -172,7 +193,7 @@
 				uval = get_addr32(*argv);
 			else {
 				if (get_unsigned(&uval, *argv, 0)<0) {
-					fprintf(stderr, "invalid value of \"okey\"\n");
+					fprintf(stderr, "invalid value for \"okey\": \"%s\"; it should be an unsigned integer\n", *argv);
 					exit(-1);
 				}
 				uval = htonl(uval);
@@ -207,8 +228,11 @@
 		} else if (!matches(*argv, "dev")) {
 			NEXT_ARG();
 			link = if_nametoindex(*argv);
-			if (link == 0)
+			if (link == 0) {
+				fprintf(stderr, "Cannot find device \"%s\"\n",
+					*argv);
 				exit(-1);
+			}
 		} else if (!matches(*argv, "ttl") ||
 			   !matches(*argv, "hoplimit")) {
 			unsigned uval;
@@ -233,7 +257,41 @@
 				tos = uval;
 			} else
 				tos = 1;
-		} else 
+		} else if (strcmp(*argv, "noencap") == 0) {
+			encaptype = TUNNEL_ENCAP_NONE;
+		} else if (strcmp(*argv, "encap") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "fou") == 0)
+				encaptype = TUNNEL_ENCAP_FOU;
+			else if (strcmp(*argv, "gue") == 0)
+				encaptype = TUNNEL_ENCAP_GUE;
+			else if (strcmp(*argv, "none") == 0)
+				encaptype = TUNNEL_ENCAP_NONE;
+			else
+				invarg("Invalid encap type.", *argv);
+		} else if (strcmp(*argv, "encap-sport") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "auto") == 0)
+				encapsport = 0;
+			else if (get_u16(&encapsport, *argv, 0))
+				invarg("Invalid source port.", *argv);
+		} else if (strcmp(*argv, "encap-dport") == 0) {
+			NEXT_ARG();
+			if (get_u16(&encapdport, *argv, 0))
+				invarg("Invalid destination port.", *argv);
+		} else if (strcmp(*argv, "encap-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "noencap-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "encap-udp6-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "noencap-udp6-csum") == 0) {
+			encapflags |= ~TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "encap-remcsum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "noencap-remcsum") == 0) {
+			encapflags |= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else
 			usage();
 		argc--; argv++;
 	}
@@ -247,7 +305,7 @@
 		oflags |= GRE_KEY;
 	}
 	if (IN_MULTICAST(ntohl(daddr)) && !saddr) {
-		fprintf(stderr, "Broadcast tunnel requires a source address.\n");
+		fprintf(stderr, "A broadcast tunnel requires a source address.\n");
 		return -1;
 	}
 
@@ -263,6 +321,11 @@
 	addattr_l(n, 1024, IFLA_GRE_TTL, &ttl, 1);
 	addattr_l(n, 1024, IFLA_GRE_TOS, &tos, 1);
 
+	addattr16(n, 1024, IFLA_GRE_ENCAP_TYPE, encaptype);
+	addattr16(n, 1024, IFLA_GRE_ENCAP_FLAGS, encapflags);
+	addattr16(n, 1024, IFLA_GRE_ENCAP_SPORT, htons(encapsport));
+	addattr16(n, 1024, IFLA_GRE_ENCAP_DPORT, htons(encapdport));
+
 	return 0;
 }
 
@@ -349,6 +412,55 @@
 		fputs("icsum ", f);
 	if (oflags & GRE_CSUM)
 		fputs("ocsum ", f);
+
+	if (tb[IFLA_GRE_ENCAP_TYPE] &&
+	    *(__u16 *)RTA_DATA(tb[IFLA_GRE_ENCAP_TYPE]) != TUNNEL_ENCAP_NONE) {
+		__u16 type = rta_getattr_u16(tb[IFLA_GRE_ENCAP_TYPE]);
+		__u16 flags = rta_getattr_u16(tb[IFLA_GRE_ENCAP_FLAGS]);
+		__u16 sport = rta_getattr_u16(tb[IFLA_GRE_ENCAP_SPORT]);
+		__u16 dport = rta_getattr_u16(tb[IFLA_GRE_ENCAP_DPORT]);
+
+		fputs("encap ", f);
+		switch (type) {
+		case TUNNEL_ENCAP_FOU:
+			fputs("fou ", f);
+			break;
+		case TUNNEL_ENCAP_GUE:
+			fputs("gue ", f);
+			break;
+		default:
+			fputs("unknown ", f);
+			break;
+		}
+
+		if (sport == 0)
+			fputs("encap-sport auto ", f);
+		else
+			fprintf(f, "encap-sport %u", ntohs(sport));
+
+		fprintf(f, "encap-dport %u ", ntohs(dport));
+
+		if (flags & TUNNEL_ENCAP_FLAG_CSUM)
+			fputs("encap-csum ", f);
+		else
+			fputs("noencap-csum ", f);
+
+		if (flags & TUNNEL_ENCAP_FLAG_CSUM6)
+			fputs("encap-csum6 ", f);
+		else
+			fputs("noencap-csum6 ", f);
+
+		if (flags & TUNNEL_ENCAP_FLAG_REMCSUM)
+			fputs("encap-remcsum ", f);
+		else
+			fputs("noencap-remcsum ", f);
+	}
+}
+
+static void gre_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
 }
 
 struct link_util gre_link_util = {
@@ -356,6 +468,7 @@
 	.maxattr = IFLA_GRE_MAX,
 	.parse_opt = gre_parse_opt,
 	.print_opt = gre_print_opt,
+	.print_help = gre_print_help,
 };
 
 struct link_util gretap_link_util = {
@@ -363,4 +476,5 @@
 	.maxattr = IFLA_GRE_MAX,
 	.parse_opt = gre_parse_opt,
 	.print_opt = gre_print_opt,
+	.print_help = gre_print_help,
 };
diff --git a/ip/link_gre6.c b/ip/link_gre6.c
new file mode 100644
index 0000000..f18919c
--- /dev/null
+++ b/ip/link_gre6.c
@@ -0,0 +1,414 @@
+/*
+ * link_gre6.c	gre driver module
+ *
+ *		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.
+ *
+ * Authors:	Dmitry Kozlov <xeb@mail.ru>
+ *
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+#define IP6_FLOWINFO_TCLASS	htonl(0x0FF00000)
+#define IP6_FLOWINFO_FLOWLABEL	htonl(0x000FFFFF)
+
+#define DEFAULT_TNL_HOP_LIMIT	(64)
+
+static void print_usage(FILE *f)
+{
+	fprintf(f, "Usage: ip link { add | set | change | replace | del } NAME\n");
+	fprintf(f, "          type { ip6gre | ip6gretap } [ remote ADDR ] [ local ADDR ]\n");
+	fprintf(f, "          [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n");
+	fprintf(f, "          [ hoplimit TTL ] [ encaplimit ELIM ]\n");
+	fprintf(f, "          [ tclass TCLASS ] [ flowlabel FLOWLABEL ]\n");
+	fprintf(f, "          [ dscp inherit ] [ dev PHYS_DEV ]\n");
+	fprintf(f, "\n");
+	fprintf(f, "Where: NAME      := STRING\n");
+	fprintf(f, "       ADDR      := IPV6_ADDRESS\n");
+	fprintf(f, "       TTL       := { 0..255 } (default=%d)\n",
+		DEFAULT_TNL_HOP_LIMIT);
+	fprintf(f, "       KEY       := { DOTTED_QUAD | NUMBER }\n");
+	fprintf(f, "       ELIM      := { none | 0..255 }(default=%d)\n",
+		IPV6_DEFAULT_TNL_ENCAP_LIMIT);
+	fprintf(f, "       TCLASS    := { 0x0..0xff | inherit }\n");
+	fprintf(f, "       FLOWLABEL := { 0x0..0xfffff | inherit }\n");
+}
+
+static void usage(void) __attribute__((noreturn));
+static void usage(void)
+{
+	print_usage(stderr);
+	exit(-1);
+}
+
+static int gre_parse_opt(struct link_util *lu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+		char buf[1024];
+	} req;
+	struct ifinfomsg *ifi = (struct ifinfomsg *)(n + 1);
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *greinfo[IFLA_GRE_MAX + 1];
+	__u16 iflags = 0;
+	__u16 oflags = 0;
+	unsigned ikey = 0;
+	unsigned okey = 0;
+	struct in6_addr raddr = IN6ADDR_ANY_INIT;
+	struct in6_addr laddr = IN6ADDR_ANY_INIT;
+	unsigned link = 0;
+	unsigned flowinfo = 0;
+	unsigned flags = 0;
+	__u8 hop_limit = DEFAULT_TNL_HOP_LIMIT;
+	__u8 encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
+	int len;
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		memset(&req, 0, sizeof(req));
+
+		req.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
+		req.n.nlmsg_flags = NLM_F_REQUEST;
+		req.n.nlmsg_type = RTM_GETLINK;
+		req.i.ifi_family = preferred_family;
+		req.i.ifi_index = ifi->ifi_index;
+
+		if (rtnl_talk(&rth, &req.n, 0, 0, &req.n) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = req.n.nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(&req.i), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(greinfo, IFLA_GRE_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		if (greinfo[IFLA_GRE_IKEY])
+			ikey = rta_getattr_u32(greinfo[IFLA_GRE_IKEY]);
+
+		if (greinfo[IFLA_GRE_OKEY])
+			okey = rta_getattr_u32(greinfo[IFLA_GRE_OKEY]);
+
+		if (greinfo[IFLA_GRE_IFLAGS])
+			iflags = rta_getattr_u16(greinfo[IFLA_GRE_IFLAGS]);
+
+		if (greinfo[IFLA_GRE_OFLAGS])
+			oflags = rta_getattr_u16(greinfo[IFLA_GRE_OFLAGS]);
+
+		if (greinfo[IFLA_GRE_LOCAL])
+			memcpy(&laddr, RTA_DATA(greinfo[IFLA_GRE_LOCAL]), sizeof(laddr));
+
+		if (greinfo[IFLA_GRE_REMOTE])
+			memcpy(&raddr, RTA_DATA(greinfo[IFLA_GRE_REMOTE]), sizeof(raddr));
+
+		if (greinfo[IFLA_GRE_TTL])
+			hop_limit = rta_getattr_u8(greinfo[IFLA_GRE_TTL]);
+
+		if (greinfo[IFLA_GRE_LINK])
+			link = rta_getattr_u32(greinfo[IFLA_GRE_LINK]);
+
+		if (greinfo[IFLA_GRE_ENCAP_LIMIT])
+			encap_limit = rta_getattr_u8(greinfo[IFLA_GRE_ENCAP_LIMIT]);
+
+		if (greinfo[IFLA_GRE_FLOWINFO])
+			flowinfo = rta_getattr_u32(greinfo[IFLA_GRE_FLOWINFO]);
+
+		if (greinfo[IFLA_GRE_FLAGS])
+			flags = rta_getattr_u32(greinfo[IFLA_GRE_FLAGS]);
+	}
+
+	while (argc > 0) {
+		if (!matches(*argv, "key")) {
+			unsigned uval;
+
+			NEXT_ARG();
+			iflags |= GRE_KEY;
+			oflags |= GRE_KEY;
+			if (strchr(*argv, '.'))
+				uval = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0) < 0) {
+					fprintf(stderr,
+						"Invalid value for \"key\"\n");
+					exit(-1);
+				}
+				uval = htonl(uval);
+			}
+
+			ikey = okey = uval;
+		} else if (!matches(*argv, "ikey")) {
+			unsigned uval;
+
+			NEXT_ARG();
+			iflags |= GRE_KEY;
+			if (strchr(*argv, '.'))
+				uval = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0)<0) {
+					fprintf(stderr, "invalid value of \"ikey\"\n");
+					exit(-1);
+				}
+				uval = htonl(uval);
+			}
+			ikey = uval;
+		} else if (!matches(*argv, "okey")) {
+			unsigned uval;
+
+			NEXT_ARG();
+			oflags |= GRE_KEY;
+			if (strchr(*argv, '.'))
+				uval = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0)<0) {
+					fprintf(stderr, "invalid value of \"okey\"\n");
+					exit(-1);
+				}
+				uval = htonl(uval);
+			}
+			okey = uval;
+		} else if (!matches(*argv, "seq")) {
+			iflags |= GRE_SEQ;
+			oflags |= GRE_SEQ;
+		} else if (!matches(*argv, "iseq")) {
+			iflags |= GRE_SEQ;
+		} else if (!matches(*argv, "oseq")) {
+			oflags |= GRE_SEQ;
+		} else if (!matches(*argv, "csum")) {
+			iflags |= GRE_CSUM;
+			oflags |= GRE_CSUM;
+		} else if (!matches(*argv, "icsum")) {
+			iflags |= GRE_CSUM;
+		} else if (!matches(*argv, "ocsum")) {
+			oflags |= GRE_CSUM;
+		} else if (!matches(*argv, "remote")) {
+			inet_prefix addr;
+			NEXT_ARG();
+			get_prefix(&addr, *argv, preferred_family);
+			if (addr.family == AF_UNSPEC)
+				invarg("\"remote\" address family is AF_UNSPEC", *argv);
+			memcpy(&raddr, &addr.data, sizeof(raddr));
+		} else if (!matches(*argv, "local")) {
+			inet_prefix addr;
+			NEXT_ARG();
+			get_prefix(&addr, *argv, preferred_family);
+			if (addr.family == AF_UNSPEC)
+				invarg("\"local\" address family is AF_UNSPEC", *argv);
+			memcpy(&laddr, &addr.data, sizeof(laddr));
+		} else if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			link = if_nametoindex(*argv);
+			if (link == 0) {
+				fprintf(stderr, "Cannot find device \"%s\"\n",
+					*argv);
+				exit(-1);
+			}
+		} else if (!matches(*argv, "ttl") ||
+			   !matches(*argv, "hoplimit")) {
+			__u8 uval;
+			NEXT_ARG();
+			if (get_u8(&uval, *argv, 0))
+				invarg("invalid TTL", *argv);
+			hop_limit = uval;
+		} else if (!matches(*argv, "tos") ||
+			   !matches(*argv, "tclass") ||
+			   !matches(*argv, "dsfield")) {
+			__u8 uval;
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") == 0)
+				flags |= IP6_TNL_F_USE_ORIG_TCLASS;
+			else {
+				if (get_u8(&uval, *argv, 16))
+					invarg("invalid TClass", *argv);
+				flowinfo |= htonl((__u32)uval << 20) & IP6_FLOWINFO_TCLASS;
+				flags &= ~IP6_TNL_F_USE_ORIG_TCLASS;
+			}
+		} else if (strcmp(*argv, "flowlabel") == 0 ||
+			   strcmp(*argv, "fl") == 0) {
+			__u32 uval;
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") == 0)
+				flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL;
+			else {
+				if (get_u32(&uval, *argv, 16))
+					invarg("invalid Flowlabel", *argv);
+				if (uval > 0xFFFFF)
+					invarg("invalid Flowlabel", *argv);
+				flowinfo |= htonl(uval) & IP6_FLOWINFO_FLOWLABEL;
+				flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL;
+			}
+		} else if (strcmp(*argv, "dscp") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0)
+				invarg("not inherit", *argv);
+			flags |= IP6_TNL_F_RCV_DSCP_COPY;
+		} else
+			usage();
+		argc--; argv++;
+	}
+
+	addattr32(n, 1024, IFLA_GRE_IKEY, ikey);
+	addattr32(n, 1024, IFLA_GRE_OKEY, okey);
+	addattr_l(n, 1024, IFLA_GRE_IFLAGS, &iflags, 2);
+	addattr_l(n, 1024, IFLA_GRE_OFLAGS, &oflags, 2);
+	addattr_l(n, 1024, IFLA_GRE_LOCAL, &laddr, sizeof(laddr));
+	addattr_l(n, 1024, IFLA_GRE_REMOTE, &raddr, sizeof(raddr));
+	if (link)
+		addattr32(n, 1024, IFLA_GRE_LINK, link);
+	addattr_l(n, 1024, IFLA_GRE_TTL, &hop_limit, 1);
+	addattr_l(n, 1024, IFLA_GRE_ENCAP_LIMIT, &encap_limit, 1);
+	addattr_l(n, 1024, IFLA_GRE_FLOWINFO, &flowinfo, 4);
+	addattr_l(n, 1024, IFLA_GRE_FLAGS, &flowinfo, 4);
+
+	return 0;
+}
+
+static void gre_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s1[1024];
+	char s2[64];
+	const char *local = "any";
+	const char *remote = "any";
+	unsigned iflags = 0;
+	unsigned oflags = 0;
+	unsigned flags = 0;
+	unsigned flowinfo = 0;
+	struct in6_addr in6_addr_any = IN6ADDR_ANY_INIT;
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_GRE_FLAGS])
+		flags = rta_getattr_u32(tb[IFLA_GRE_FLAGS]);
+
+	if (tb[IFLA_GRE_FLOWINFO])
+		flags = rta_getattr_u32(tb[IFLA_GRE_FLOWINFO]);
+
+	if (tb[IFLA_GRE_REMOTE]) {
+		struct in6_addr addr;
+		memcpy(&addr, RTA_DATA(tb[IFLA_GRE_REMOTE]), sizeof(addr));
+
+		if (memcmp(&addr, &in6_addr_any, sizeof(addr)))
+			remote = format_host(AF_INET6, sizeof(addr), &addr, s1, sizeof(s1));
+	}
+
+	fprintf(f, "remote %s ", remote);
+
+	if (tb[IFLA_GRE_LOCAL]) {
+		struct in6_addr addr;
+		memcpy(&addr, RTA_DATA(tb[IFLA_GRE_LOCAL]), sizeof(addr));
+
+		if (memcmp(&addr, &in6_addr_any, sizeof(addr)))
+			local = format_host(AF_INET6, sizeof(addr), &addr, s1, sizeof(s1));
+	}
+
+	fprintf(f, "local %s ", local);
+
+	if (tb[IFLA_GRE_LINK] && rta_getattr_u32(tb[IFLA_GRE_LINK])) {
+		unsigned link = rta_getattr_u32(tb[IFLA_GRE_LINK]);
+		const char *n = if_indextoname(link, s2);
+
+		if (n)
+			fprintf(f, "dev %s ", n);
+		else
+			fprintf(f, "dev %u ", link);
+	}
+
+	if (tb[IFLA_GRE_TTL] && rta_getattr_u8(tb[IFLA_GRE_TTL]))
+		fprintf(f, "hoplimit %d ", rta_getattr_u8(tb[IFLA_GRE_TTL]));
+
+	if (flags & IP6_TNL_F_IGN_ENCAP_LIMIT)
+		fprintf(f, "encaplimit none ");
+	else if (tb[IFLA_GRE_ENCAP_LIMIT]) {
+		int encap_limit = rta_getattr_u8(tb[IFLA_GRE_ENCAP_LIMIT]);
+
+		fprintf(f, "encaplimit %d ", encap_limit);
+	}
+
+	if (flags & IP6_TNL_F_USE_ORIG_FLOWLABEL)
+		fprintf(f, "flowlabel inherit ");
+	else
+		fprintf(f, "flowlabel 0x%05x ", ntohl(flowinfo & IP6_FLOWINFO_FLOWLABEL));
+
+	if (flags & IP6_TNL_F_RCV_DSCP_COPY)
+		fprintf(f, "dscp inherit ");
+
+	if (tb[IFLA_GRE_IFLAGS])
+		iflags = rta_getattr_u16(tb[IFLA_GRE_IFLAGS]);
+
+	if (tb[IFLA_GRE_OFLAGS])
+		oflags = rta_getattr_u16(tb[IFLA_GRE_OFLAGS]);
+
+	if ((iflags & GRE_KEY) && tb[IFLA_GRE_IKEY]) {
+		inet_ntop(AF_INET, RTA_DATA(tb[IFLA_GRE_IKEY]), s2, sizeof(s2));
+		fprintf(f, "ikey %s ", s2);
+	}
+
+	if ((oflags & GRE_KEY) && tb[IFLA_GRE_OKEY]) {
+		inet_ntop(AF_INET, RTA_DATA(tb[IFLA_GRE_OKEY]), s2, sizeof(s2));
+		fprintf(f, "okey %s ", s2);
+	}
+
+	if (iflags & GRE_SEQ)
+		fputs("iseq ", f);
+	if (oflags & GRE_SEQ)
+		fputs("oseq ", f);
+	if (iflags & GRE_CSUM)
+		fputs("icsum ", f);
+	if (oflags & GRE_CSUM)
+		fputs("ocsum ", f);
+}
+
+static void gre_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
+}
+
+struct link_util ip6gre_link_util = {
+	.id = "ip6gre",
+	.maxattr = IFLA_GRE_MAX,
+	.parse_opt = gre_parse_opt,
+	.print_opt = gre_print_opt,
+	.print_help = gre_print_help,
+};
+
+struct link_util ip6gretap_link_util = {
+	.id = "ip6gretap",
+	.maxattr = IFLA_GRE_MAX,
+	.parse_opt = gre_parse_opt,
+	.print_opt = gre_print_opt,
+	.print_help = gre_print_help,
+};
diff --git a/ip/link_ip6tnl.c b/ip/link_ip6tnl.c
new file mode 100644
index 0000000..5ed3d5a
--- /dev/null
+++ b/ip/link_ip6tnl.c
@@ -0,0 +1,355 @@
+/*
+ * link_ip6tnl.c	ip6tnl driver module
+ *
+ *		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.
+ *
+ * Authors:	Nicolas Dichtel <nicolas.dichtel@6wind.com>
+ *
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+#define IP6_FLOWINFO_TCLASS	htonl(0x0FF00000)
+#define IP6_FLOWINFO_FLOWLABEL	htonl(0x000FFFFF)
+
+#define DEFAULT_TNL_HOP_LIMIT	(64)
+
+static void print_usage(FILE *f)
+{
+	fprintf(f, "Usage: ip link { add | set | change | replace | del } NAME\n");
+	fprintf(f, "          [ mode { ip6ip6 | ipip6 | any } ]\n");
+	fprintf(f, "          type ip6tnl [ remote ADDR ] [ local ADDR ]\n");
+	fprintf(f, "          [ dev PHYS_DEV ] [ encaplimit ELIM ]\n");
+	fprintf(f ,"          [ hoplimit HLIM ] [ tclass TCLASS ] [ flowlabel FLOWLABEL ]\n");
+	fprintf(f, "          [ dscp inherit ] [ fwmark inherit ]\n");
+	fprintf(f, "\n");
+	fprintf(f, "Where: NAME      := STRING\n");
+	fprintf(f, "       ADDR      := IPV6_ADDRESS\n");
+	fprintf(f, "       ELIM      := { none | 0..255 }(default=%d)\n",
+		IPV6_DEFAULT_TNL_ENCAP_LIMIT);
+	fprintf(f, "       HLIM      := 0..255 (default=%d)\n",
+		DEFAULT_TNL_HOP_LIMIT);
+	fprintf(f, "       TCLASS    := { 0x0..0xff | inherit }\n");
+	fprintf(f, "       FLOWLABEL := { 0x0..0xfffff | inherit }\n");
+}
+
+static void usage(void) __attribute__((noreturn));
+static void usage(void)
+{
+	print_usage(stderr);
+	exit(-1);
+}
+
+static int ip6tunnel_parse_opt(struct link_util *lu, int argc, char **argv,
+			       struct nlmsghdr *n)
+{
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+		char buf[2048];
+	} req;
+	struct ifinfomsg *ifi = (struct ifinfomsg *)(n + 1);
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *iptuninfo[IFLA_IPTUN_MAX + 1];
+	int len;
+	struct in6_addr laddr;
+	struct in6_addr raddr;
+	__u8 hop_limit = DEFAULT_TNL_HOP_LIMIT;
+	__u8 encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
+	__u32 flowinfo = 0;
+	__u32 flags = 0;
+	__u32 link = 0;
+	__u8 proto = 0;
+
+	memset(&laddr, 0, sizeof(laddr));
+	memset(&raddr, 0, sizeof(raddr));
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		memset(&req, 0, sizeof(req));
+
+		req.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
+		req.n.nlmsg_flags = NLM_F_REQUEST;
+		req.n.nlmsg_type = RTM_GETLINK;
+		req.i.ifi_family = preferred_family;
+		req.i.ifi_index = ifi->ifi_index;
+
+		if (rtnl_talk(&rth, &req.n, 0, 0, &req.n) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = req.n.nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(&req.i), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(iptuninfo, IFLA_IPTUN_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		if (iptuninfo[IFLA_IPTUN_LOCAL])
+			memcpy(&laddr, RTA_DATA(iptuninfo[IFLA_IPTUN_LOCAL]),
+			       sizeof(laddr));
+
+		if (iptuninfo[IFLA_IPTUN_REMOTE])
+			memcpy(&raddr, RTA_DATA(iptuninfo[IFLA_IPTUN_REMOTE]),
+			       sizeof(raddr));
+
+		if (iptuninfo[IFLA_IPTUN_TTL])
+			hop_limit = rta_getattr_u8(iptuninfo[IFLA_IPTUN_TTL]);
+
+		if (iptuninfo[IFLA_IPTUN_ENCAP_LIMIT])
+			encap_limit = rta_getattr_u8(iptuninfo[IFLA_IPTUN_ENCAP_LIMIT]);
+
+		if (iptuninfo[IFLA_IPTUN_FLOWINFO])
+			flowinfo = rta_getattr_u32(iptuninfo[IFLA_IPTUN_FLOWINFO]);
+
+		if (iptuninfo[IFLA_IPTUN_FLAGS])
+			flags = rta_getattr_u32(iptuninfo[IFLA_IPTUN_FLAGS]);
+
+		if (iptuninfo[IFLA_IPTUN_LINK])
+			link = rta_getattr_u32(iptuninfo[IFLA_IPTUN_LINK]);
+
+		if (iptuninfo[IFLA_IPTUN_PROTO])
+			proto = rta_getattr_u8(iptuninfo[IFLA_IPTUN_PROTO]);
+	}
+
+	while (argc > 0) {
+		if (matches(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "ipv6/ipv6") == 0 ||
+			    strcmp(*argv, "ip6ip6") == 0)
+				proto = IPPROTO_IPV6;
+			else if (strcmp(*argv, "ip/ipv6") == 0 ||
+				 strcmp(*argv, "ipv4/ipv6") == 0 ||
+				 strcmp(*argv, "ipip6") == 0 ||
+				 strcmp(*argv, "ip4ip6") == 0)
+				proto = IPPROTO_IPIP;
+			else if (strcmp(*argv, "any/ipv6") == 0 ||
+				 strcmp(*argv, "any") == 0)
+				proto = 0;
+			else
+				invarg("Cannot guess tunnel mode.", *argv);
+		} else if (strcmp(*argv, "remote") == 0) {
+			inet_prefix addr;
+			NEXT_ARG();
+			get_prefix(&addr, *argv, preferred_family);
+			if (addr.family == AF_UNSPEC)
+				invarg("\"remote\" address family is AF_UNSPEC", *argv);
+			memcpy(&raddr, addr.data, addr.bytelen);
+		} else if (strcmp(*argv, "local") == 0) {
+			inet_prefix addr;
+			NEXT_ARG();
+			get_prefix(&addr, *argv, preferred_family);
+			if (addr.family == AF_UNSPEC)
+				invarg("\"local\" address family is AF_UNSPEC", *argv);
+			memcpy(&laddr, addr.data, addr.bytelen);
+		} else if (matches(*argv, "dev") == 0) {
+			NEXT_ARG();
+			link = if_nametoindex(*argv);
+			if (link == 0)
+				invarg("\"dev\" is invalid", *argv);
+		} else if (strcmp(*argv, "hoplimit") == 0 ||
+			   strcmp(*argv, "ttl") == 0 ||
+			   strcmp(*argv, "hlim") == 0) {
+			__u8 uval;
+			NEXT_ARG();
+			if (get_u8(&uval, *argv, 0))
+				invarg("invalid HLIM", *argv);
+			hop_limit = uval;
+		} else if (matches(*argv, "encaplimit") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "none") == 0) {
+				flags |= IP6_TNL_F_IGN_ENCAP_LIMIT;
+			} else {
+				__u8 uval;
+				if (get_u8(&uval, *argv, 0) < -1)
+					invarg("invalid ELIM", *argv);
+				encap_limit = uval;
+				flags &= ~IP6_TNL_F_IGN_ENCAP_LIMIT;
+			}
+		} else if (strcmp(*argv, "tclass") == 0 ||
+			   strcmp(*argv, "tc") == 0 ||
+			   strcmp(*argv, "tos") == 0 ||
+			   matches(*argv, "dsfield") == 0) {
+			__u8 uval;
+			NEXT_ARG();
+			flowinfo &= ~IP6_FLOWINFO_TCLASS;
+			if (strcmp(*argv, "inherit") == 0)
+				flags |= IP6_TNL_F_USE_ORIG_TCLASS;
+			else {
+				if (get_u8(&uval, *argv, 16))
+					invarg("invalid TClass", *argv);
+				flowinfo |= htonl((__u32)uval << 20) & IP6_FLOWINFO_TCLASS;
+				flags &= ~IP6_TNL_F_USE_ORIG_TCLASS;
+			}
+		} else if (strcmp(*argv, "flowlabel") == 0 ||
+			   strcmp(*argv, "fl") == 0) {
+			__u32 uval;
+			NEXT_ARG();
+			flowinfo &= ~IP6_FLOWINFO_FLOWLABEL;
+			if (strcmp(*argv, "inherit") == 0)
+				flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL;
+			else {
+				if (get_u32(&uval, *argv, 16))
+					invarg("invalid Flowlabel", *argv);
+				if (uval > 0xFFFFF)
+					invarg("invalid Flowlabel", *argv);
+				flowinfo |= htonl(uval) & IP6_FLOWINFO_FLOWLABEL;
+				flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL;
+			}
+		} else if (strcmp(*argv, "dscp") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0)
+				invarg("not inherit", *argv);
+			flags |= IP6_TNL_F_RCV_DSCP_COPY;
+		} else if (strcmp(*argv, "fwmark") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0)
+				invarg("not inherit", *argv);
+			flags |= IP6_TNL_F_USE_ORIG_FWMARK;
+		} else
+			usage();
+		argc--, argv++;
+	}
+
+	addattr8(n, 1024, IFLA_IPTUN_PROTO, proto);
+	addattr_l(n, 1024, IFLA_IPTUN_LOCAL, &laddr, sizeof(laddr));
+	addattr_l(n, 1024, IFLA_IPTUN_REMOTE, &raddr, sizeof(raddr));
+	addattr8(n, 1024, IFLA_IPTUN_TTL, hop_limit);
+	addattr8(n, 1024, IFLA_IPTUN_ENCAP_LIMIT, encap_limit);
+	addattr32(n, 1024, IFLA_IPTUN_FLOWINFO, flowinfo);
+	addattr32(n, 1024, IFLA_IPTUN_FLAGS, flags);
+	addattr32(n, 1024, IFLA_IPTUN_LINK, link);
+
+	return 0;
+}
+
+static void ip6tunnel_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s1[256];
+	char s2[64];
+	int flags = 0;
+	__u32 flowinfo = 0;
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_IPTUN_FLAGS])
+		flags = rta_getattr_u32(tb[IFLA_IPTUN_FLAGS]);
+
+	if (tb[IFLA_IPTUN_FLOWINFO])
+		flowinfo = rta_getattr_u32(tb[IFLA_IPTUN_FLOWINFO]);
+
+	if (tb[IFLA_IPTUN_PROTO]) {
+		switch (rta_getattr_u8(tb[IFLA_IPTUN_PROTO])) {
+		case IPPROTO_IPIP:
+			fprintf(f, "ipip6 ");
+			break;
+		case IPPROTO_IPV6:
+			fprintf(f, "ip6ip6 ");
+			break;
+		case 0:
+			fprintf(f, "any ");
+			break;
+		}
+	}
+
+	if (tb[IFLA_IPTUN_REMOTE]) {
+		fprintf(f, "remote %s ",
+			rt_addr_n2a(AF_INET6,
+				    RTA_DATA(tb[IFLA_IPTUN_REMOTE]),
+				    s1, sizeof(s1)));
+	}
+
+	if (tb[IFLA_IPTUN_LOCAL]) {
+		fprintf(f, "local %s ",
+			rt_addr_n2a(AF_INET6,
+				    RTA_DATA(tb[IFLA_IPTUN_LOCAL]),
+				    s1, sizeof(s1)));
+	}
+
+	if (tb[IFLA_IPTUN_LINK] && rta_getattr_u32(tb[IFLA_IPTUN_LINK])) {
+		unsigned link = rta_getattr_u32(tb[IFLA_IPTUN_LINK]);
+		const char *n = if_indextoname(link, s2);
+
+		if (n)
+			fprintf(f, "dev %s ", n);
+		else
+			fprintf(f, "dev %u ", link);
+	}
+
+	if (flags & IP6_TNL_F_IGN_ENCAP_LIMIT)
+		printf("encaplimit none ");
+	else if (tb[IFLA_IPTUN_ENCAP_LIMIT])
+		fprintf(f, "encaplimit %u ",
+			rta_getattr_u8(tb[IFLA_IPTUN_ENCAP_LIMIT]));
+
+	if (tb[IFLA_IPTUN_TTL])
+		fprintf(f, "hoplimit %u ", rta_getattr_u8(tb[IFLA_IPTUN_TTL]));
+
+	if (flags & IP6_TNL_F_USE_ORIG_TCLASS)
+		printf("tclass inherit ");
+	else if (tb[IFLA_IPTUN_FLOWINFO]) {
+		__u32 val = ntohl(flowinfo & IP6_FLOWINFO_TCLASS);
+
+		printf("tclass 0x%02x ", (__u8)(val >> 20));
+	}
+
+	if (flags & IP6_TNL_F_USE_ORIG_FLOWLABEL)
+		printf("flowlabel inherit ");
+	else
+		printf("flowlabel 0x%05x ", ntohl(flowinfo & IP6_FLOWINFO_FLOWLABEL));
+
+	printf("(flowinfo 0x%08x) ", ntohl(flowinfo));
+
+	if (flags & IP6_TNL_F_RCV_DSCP_COPY)
+		printf("dscp inherit ");
+
+	if (flags & IP6_TNL_F_MIP6_DEV)
+		fprintf(f, "mip6 ");
+
+	if (flags & IP6_TNL_F_USE_ORIG_FWMARK)
+		fprintf(f, "fwmark inherit ");
+}
+
+static void ip6tunnel_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
+}
+
+struct link_util ip6tnl_link_util = {
+	.id = "ip6tnl",
+	.maxattr = IFLA_IPTUN_MAX,
+	.parse_opt = ip6tunnel_parse_opt,
+	.print_opt = ip6tunnel_print_opt,
+	.print_help = ip6tunnel_print_help,
+};
diff --git a/ip/link_iptnl.c b/ip/link_iptnl.c
new file mode 100644
index 0000000..cab174f
--- /dev/null
+++ b/ip/link_iptnl.c
@@ -0,0 +1,473 @@
+/*
+ * link_iptnl.c	ipip and sit driver module
+ *
+ *		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.
+ *
+ * Authors:	Nicolas Dichtel <nicolas.dichtel@6wind.com>
+ *
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void print_usage(FILE *f, int sit)
+{
+	fprintf(f, "Usage: ip link { add | set | change | replace | del } NAME\n");
+	fprintf(f, "          type { ipip | sit } [ remote ADDR ] [ local ADDR ]\n");
+	fprintf(f, "          [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ]\n");
+	fprintf(f, "          [ 6rd-prefix ADDR ] [ 6rd-relay_prefix ADDR ] [ 6rd-reset ]\n");
+	fprintf(f, "          [ noencap ] [ encap { fou | gue | none } ]\n");
+	fprintf(f, "          [ encap-sport PORT ] [ encap-dport PORT ]\n");
+	fprintf(f, "          [ [no]encap-csum ] [ [no]encap-csum6 ] [ [no]encap-remcsum ]\n");
+	if (sit) {
+		fprintf(f, "          [ mode { ip6ip | ipip | any } ]\n");
+		fprintf(f, "          [ isatap ]\n");
+	}
+	fprintf(f, "\n");
+	fprintf(f, "Where: NAME := STRING\n");
+	fprintf(f, "       ADDR := { IP_ADDRESS | any }\n");
+	fprintf(f, "       TOS  := { NUMBER | inherit }\n");
+	fprintf(f, "       TTL  := { 1..255 | inherit }\n");
+}
+
+static void usage(int sit) __attribute__((noreturn));
+static void usage(int sit)
+{
+	print_usage(stderr, sit);
+	exit(-1);
+}
+
+static int iptunnel_parse_opt(struct link_util *lu, int argc, char **argv,
+			      struct nlmsghdr *n)
+{
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+		char buf[2048];
+	} req;
+	struct ifinfomsg *ifi = (struct ifinfomsg *)(n + 1);
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *iptuninfo[IFLA_IPTUN_MAX + 1];
+	int len;
+	__u32 link = 0;
+	__u32 laddr = 0;
+	__u32 raddr = 0;
+	__u8 ttl = 0;
+	__u8 tos = 0;
+	__u8 pmtudisc = 1;
+	__u16 iflags = 0;
+	__u8 proto = 0;
+	struct in6_addr ip6rdprefix;
+	__u16 ip6rdprefixlen = 0;
+	__u32 ip6rdrelayprefix = 0;
+	__u16 ip6rdrelayprefixlen = 0;
+	__u16 encaptype = 0;
+	__u16 encapflags = 0;
+	__u16 encapsport = 0;
+	__u16 encapdport = 0;
+
+	memset(&ip6rdprefix, 0, sizeof(ip6rdprefix));
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		memset(&req, 0, sizeof(req));
+
+		req.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
+		req.n.nlmsg_flags = NLM_F_REQUEST;
+		req.n.nlmsg_type = RTM_GETLINK;
+		req.i.ifi_family = preferred_family;
+		req.i.ifi_index = ifi->ifi_index;
+
+		if (rtnl_talk(&rth, &req.n, 0, 0, &req.n) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = req.n.nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(&req.i), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(iptuninfo, IFLA_IPTUN_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		if (iptuninfo[IFLA_IPTUN_LOCAL])
+			laddr = rta_getattr_u32(iptuninfo[IFLA_IPTUN_LOCAL]);
+
+		if (iptuninfo[IFLA_IPTUN_REMOTE])
+			raddr = rta_getattr_u32(iptuninfo[IFLA_IPTUN_REMOTE]);
+
+		if (iptuninfo[IFLA_IPTUN_TTL])
+			ttl = rta_getattr_u8(iptuninfo[IFLA_IPTUN_TTL]);
+
+		if (iptuninfo[IFLA_IPTUN_TOS])
+			tos = rta_getattr_u8(iptuninfo[IFLA_IPTUN_TOS]);
+
+		if (iptuninfo[IFLA_IPTUN_PMTUDISC])
+			pmtudisc =
+				rta_getattr_u8(iptuninfo[IFLA_IPTUN_PMTUDISC]);
+
+		if (iptuninfo[IFLA_IPTUN_FLAGS])
+			iflags = rta_getattr_u16(iptuninfo[IFLA_IPTUN_FLAGS]);
+
+		if (iptuninfo[IFLA_IPTUN_LINK])
+			link = rta_getattr_u32(iptuninfo[IFLA_IPTUN_LINK]);
+
+		if (iptuninfo[IFLA_IPTUN_PROTO])
+			proto = rta_getattr_u8(iptuninfo[IFLA_IPTUN_PROTO]);
+
+		if (iptuninfo[IFLA_IPTUN_ENCAP_TYPE])
+			encaptype = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_TYPE]);
+		if (iptuninfo[IFLA_IPTUN_ENCAP_FLAGS])
+			encapflags = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_FLAGS]);
+		if (iptuninfo[IFLA_IPTUN_ENCAP_SPORT])
+			encapsport = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_SPORT]);
+		if (iptuninfo[IFLA_IPTUN_ENCAP_DPORT])
+			encapdport = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_DPORT]);
+		if (iptuninfo[IFLA_IPTUN_6RD_PREFIX])
+			memcpy(&ip6rdprefix,
+			       RTA_DATA(iptuninfo[IFLA_IPTUN_6RD_PREFIX]),
+			       sizeof(laddr));
+
+		if (iptuninfo[IFLA_IPTUN_6RD_PREFIXLEN])
+			ip6rdprefixlen =
+				rta_getattr_u16(iptuninfo[IFLA_IPTUN_6RD_PREFIXLEN]);
+
+		if (iptuninfo[IFLA_IPTUN_6RD_RELAY_PREFIX])
+			ip6rdrelayprefix =
+				rta_getattr_u32(iptuninfo[IFLA_IPTUN_6RD_RELAY_PREFIX]);
+
+		if (iptuninfo[IFLA_IPTUN_6RD_RELAY_PREFIXLEN])
+			ip6rdrelayprefixlen =
+				rta_getattr_u16(iptuninfo[IFLA_IPTUN_6RD_RELAY_PREFIXLEN]);
+	}
+
+	while (argc > 0) {
+		if (strcmp(*argv, "remote") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "any"))
+				raddr = get_addr32(*argv);
+			else
+				raddr = 0;
+		} else if (strcmp(*argv, "local") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "any"))
+				laddr = get_addr32(*argv);
+			else
+				laddr = 0;
+		} else if (matches(*argv, "dev") == 0) {
+			NEXT_ARG();
+			link = if_nametoindex(*argv);
+			if (link == 0)
+				invarg("\"dev\" is invalid", *argv);
+		} else if (strcmp(*argv, "ttl") == 0 ||
+			   strcmp(*argv, "hoplimit") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (get_u8(&ttl, *argv, 0))
+					invarg("invalid TTL\n", *argv);
+			} else
+				ttl = 0;
+		} else if (strcmp(*argv, "tos") == 0 ||
+			   strcmp(*argv, "tclass") == 0 ||
+			   matches(*argv, "dsfield") == 0) {
+			__u32 uval;
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (rtnl_dsfield_a2n(&uval, *argv))
+					invarg("bad TOS value", *argv);
+				tos = uval;
+			} else
+				tos = 1;
+		} else if (strcmp(*argv, "nopmtudisc") == 0) {
+			pmtudisc = 0;
+		} else if (strcmp(*argv, "pmtudisc") == 0) {
+			pmtudisc = 1;
+		} else if (strcmp(lu->id, "sit") == 0 &&
+			   strcmp(*argv, "isatap") == 0) {
+			iflags |= SIT_ISATAP;
+		} else if (strcmp(lu->id, "sit") == 0 &&
+			   strcmp(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "ipv6/ipv4") == 0 ||
+			    strcmp(*argv, "ip6ip") == 0)
+				proto = IPPROTO_IPV6;
+			else if (strcmp(*argv, "ipv4/ipv4") == 0 ||
+				 strcmp(*argv, "ipip") == 0 ||
+				 strcmp(*argv, "ip4ip4") == 0)
+				proto = IPPROTO_IPIP;
+			else if (strcmp(*argv, "any/ipv4") == 0 ||
+				 strcmp(*argv, "any") == 0)
+				proto = 0;
+			else
+				invarg("Cannot guess tunnel mode.", *argv);
+		} else if (strcmp(*argv, "noencap") == 0) {
+			encaptype = TUNNEL_ENCAP_NONE;
+		} else if (strcmp(*argv, "encap") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "fou") == 0)
+				encaptype = TUNNEL_ENCAP_FOU;
+			else if (strcmp(*argv, "gue") == 0)
+				encaptype = TUNNEL_ENCAP_GUE;
+			else if (strcmp(*argv, "none") == 0)
+				encaptype = TUNNEL_ENCAP_NONE;
+			else
+				invarg("Invalid encap type.", *argv);
+		} else if (strcmp(*argv, "encap-sport") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "auto") == 0)
+				encapsport = 0;
+			else if (get_u16(&encapsport, *argv, 0))
+				invarg("Invalid source port.", *argv);
+		} else if (strcmp(*argv, "encap-dport") == 0) {
+			NEXT_ARG();
+			if (get_u16(&encapdport, *argv, 0))
+				invarg("Invalid destination port.", *argv);
+		} else if (strcmp(*argv, "encap-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "noencap-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "encap-udp6-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "noencap-udp6-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "encap-remcsum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "noencap-remcsum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "6rd-prefix") == 0) {
+			inet_prefix prefix;
+			NEXT_ARG();
+			if (get_prefix(&prefix, *argv, AF_INET6))
+				invarg("invalid 6rd_prefix\n", *argv);
+			memcpy(&ip6rdprefix, prefix.data, 16);
+			ip6rdprefixlen = prefix.bitlen;
+		} else if (strcmp(*argv, "6rd-relay_prefix") == 0) {
+			inet_prefix prefix;
+			NEXT_ARG();
+			if (get_prefix(&prefix, *argv, AF_INET))
+				invarg("invalid 6rd-relay_prefix\n", *argv);
+			memcpy(&ip6rdrelayprefix, prefix.data, 4);
+			ip6rdrelayprefixlen = prefix.bitlen;
+		} else if (strcmp(*argv, "6rd-reset") == 0) {
+			inet_prefix prefix;
+			get_prefix(&prefix, "2002::", AF_INET6);
+			memcpy(&ip6rdprefix, prefix.data, 16);
+			ip6rdprefixlen = 16;
+			ip6rdrelayprefix = 0;
+			ip6rdrelayprefixlen = 0;
+		} else
+			usage(strcmp(lu->id, "sit") == 0);
+		argc--, argv++;
+	}
+
+	if (ttl && pmtudisc == 0) {
+		fprintf(stderr, "ttl != 0 and nopmtudisc are incompatible\n");
+		exit(-1);
+	}
+
+	addattr32(n, 1024, IFLA_IPTUN_LINK, link);
+	addattr32(n, 1024, IFLA_IPTUN_LOCAL, laddr);
+	addattr32(n, 1024, IFLA_IPTUN_REMOTE, raddr);
+	addattr8(n, 1024, IFLA_IPTUN_TTL, ttl);
+	addattr8(n, 1024, IFLA_IPTUN_TOS, tos);
+	addattr8(n, 1024, IFLA_IPTUN_PMTUDISC, pmtudisc);
+
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_TYPE, encaptype);
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_FLAGS, encapflags);
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_SPORT, htons(encapsport));
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_DPORT, htons(encapdport));
+
+	if (strcmp(lu->id, "sit") == 0) {
+		addattr16(n, 1024, IFLA_IPTUN_FLAGS, iflags);
+		addattr8(n, 1024, IFLA_IPTUN_PROTO, proto);
+		if (ip6rdprefixlen) {
+			addattr_l(n, 1024, IFLA_IPTUN_6RD_PREFIX,
+				  &ip6rdprefix, sizeof(ip6rdprefix));
+			addattr16(n, 1024, IFLA_IPTUN_6RD_PREFIXLEN,
+				  ip6rdprefixlen);
+			addattr32(n, 1024, IFLA_IPTUN_6RD_RELAY_PREFIX,
+				  ip6rdrelayprefix);
+			addattr16(n, 1024, IFLA_IPTUN_6RD_RELAY_PREFIXLEN,
+				  ip6rdrelayprefixlen);
+		}
+	}
+
+	return 0;
+}
+
+static void iptunnel_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s1[1024];
+	char s2[64];
+	const char *local = "any";
+	const char *remote = "any";
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_IPTUN_REMOTE]) {
+		unsigned addr = rta_getattr_u32(tb[IFLA_IPTUN_REMOTE]);
+
+		if (addr)
+			remote = format_host(AF_INET, 4, &addr, s1, sizeof(s1));
+	}
+
+	fprintf(f, "remote %s ", remote);
+
+	if (tb[IFLA_IPTUN_LOCAL]) {
+		unsigned addr = rta_getattr_u32(tb[IFLA_IPTUN_LOCAL]);
+
+		if (addr)
+			local = format_host(AF_INET, 4, &addr, s1, sizeof(s1));
+	}
+
+	fprintf(f, "local %s ", local);
+
+	if (tb[IFLA_IPTUN_LINK] && rta_getattr_u32(tb[IFLA_IPTUN_LINK])) {
+		unsigned link = rta_getattr_u32(tb[IFLA_IPTUN_LINK]);
+		const char *n = if_indextoname(link, s2);
+
+		if (n)
+			fprintf(f, "dev %s ", n);
+		else
+			fprintf(f, "dev %u ", link);
+	}
+
+	if (tb[IFLA_IPTUN_TTL] && rta_getattr_u8(tb[IFLA_IPTUN_TTL]))
+		fprintf(f, "ttl %d ", rta_getattr_u8(tb[IFLA_IPTUN_TTL]));
+	else
+		fprintf(f, "ttl inherit ");
+
+	if (tb[IFLA_IPTUN_TOS] && rta_getattr_u8(tb[IFLA_IPTUN_TOS])) {
+		int tos = rta_getattr_u8(tb[IFLA_IPTUN_TOS]);
+
+		fputs("tos ", f);
+		if (tos == 1)
+			fputs("inherit ", f);
+		else
+			fprintf(f, "0x%x ", tos);
+	}
+
+	if (tb[IFLA_IPTUN_PMTUDISC] && rta_getattr_u8(tb[IFLA_IPTUN_PMTUDISC]))
+		fprintf(f, "pmtudisc ");
+	else
+		fprintf(f, "nopmtudisc ");
+
+	if (tb[IFLA_IPTUN_FLAGS]) {
+		__u16 iflags = rta_getattr_u16(tb[IFLA_IPTUN_FLAGS]);
+
+		if (iflags & SIT_ISATAP)
+			fprintf(f, "isatap ");
+	}
+
+	if (tb[IFLA_IPTUN_6RD_PREFIXLEN] &&
+	    *(__u16 *)RTA_DATA(tb[IFLA_IPTUN_6RD_PREFIXLEN])) {
+		__u16 prefixlen = rta_getattr_u16(tb[IFLA_IPTUN_6RD_PREFIXLEN]);
+		__u16 relayprefixlen =
+			rta_getattr_u16(tb[IFLA_IPTUN_6RD_RELAY_PREFIXLEN]);
+		__u32 relayprefix =
+			rta_getattr_u32(tb[IFLA_IPTUN_6RD_RELAY_PREFIX]);
+
+		printf("6rd-prefix %s/%u ",
+		       inet_ntop(AF_INET6, RTA_DATA(tb[IFLA_IPTUN_6RD_PREFIX]),
+				 s1, sizeof(s1)),
+		       prefixlen);
+		if (relayprefix) {
+			printf("6rd-relay_prefix %s/%u ",
+			       format_host(AF_INET, 4, &relayprefix, s1,
+					   sizeof(s1)),
+			       relayprefixlen);
+		}
+	}
+
+	if (tb[IFLA_IPTUN_ENCAP_TYPE] &&
+	    *(__u16 *)RTA_DATA(tb[IFLA_IPTUN_ENCAP_TYPE]) != TUNNEL_ENCAP_NONE) {
+		__u16 type = rta_getattr_u16(tb[IFLA_IPTUN_ENCAP_TYPE]);
+		__u16 flags = rta_getattr_u16(tb[IFLA_IPTUN_ENCAP_FLAGS]);
+		__u16 sport = rta_getattr_u16(tb[IFLA_IPTUN_ENCAP_SPORT]);
+		__u16 dport = rta_getattr_u16(tb[IFLA_IPTUN_ENCAP_DPORT]);
+
+		fputs("encap ", f);
+		switch (type) {
+		case TUNNEL_ENCAP_FOU:
+			fputs("fou ", f);
+			break;
+		case TUNNEL_ENCAP_GUE:
+			fputs("gue ", f);
+			break;
+		default:
+			fputs("unknown ", f);
+			break;
+		}
+
+		if (sport == 0)
+			fputs("encap-sport auto ", f);
+		else
+			fprintf(f, "encap-sport %u", ntohs(sport));
+
+		fprintf(f, "encap-dport %u ", ntohs(dport));
+
+		if (flags & TUNNEL_ENCAP_FLAG_CSUM)
+			fputs("encap-csum ", f);
+		else
+			fputs("noencap-csum ", f);
+
+		if (flags & TUNNEL_ENCAP_FLAG_CSUM6)
+			fputs("encap-csum6 ", f);
+		else
+			fputs("noencap-csum6 ", f);
+
+		if (flags & TUNNEL_ENCAP_FLAG_REMCSUM)
+			fputs("encap-remcsum ", f);
+		else
+			fputs("noencap-remcsum ", f);
+	}
+}
+
+static void iptunnel_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f, strcmp(lu->id, "sit") == 0);
+}
+
+struct link_util ipip_link_util = {
+	.id = "ipip",
+	.maxattr = IFLA_IPTUN_MAX,
+	.parse_opt = iptunnel_parse_opt,
+	.print_opt = iptunnel_print_opt,
+	.print_help = iptunnel_print_help,
+};
+
+struct link_util sit_link_util = {
+	.id = "sit",
+	.maxattr = IFLA_IPTUN_MAX,
+	.parse_opt = iptunnel_parse_opt,
+	.print_opt = iptunnel_print_opt,
+	.print_help = iptunnel_print_help,
+};
diff --git a/ip/link_veth.c b/ip/link_veth.c
index 3d19b01..314216c 100644
--- a/ip/link_veth.c
+++ b/ip/link_veth.c
@@ -17,33 +17,49 @@
 #include "utils.h"
 #include "ip_common.h"
 
+static void print_usage(FILE *f)
+{
+	printf("Usage: ip link <options> type veth [peer <options>]\n"
+	       "To get <options> type 'ip link add help'\n");
+}
+
 static void usage(void)
 {
-	printf("Usage: ip link <options> type veth "
-	       "[peer <options>]\nTo get <options> type "
-	       "'ip link add help'\n");
+	print_usage(stderr);
 }
 
 static int veth_parse_opt(struct link_util *lu, int argc, char **argv,
 			  struct nlmsghdr *hdr)
 {
-	char *name, *type, *link, *dev;
+	char *dev = NULL;
+	char *name = NULL;
+	char *link = NULL;
+	char *type = NULL;
+	int index = 0;
 	int err, len;
 	struct rtattr * data;
 	int group;
+	struct ifinfomsg *ifm, *peer_ifm;
+	unsigned int ifi_flags, ifi_change;
 
 	if (strcmp(argv[0], "peer") != 0) {
 		usage();
 		return -1;
 	}
 
+	ifm = NLMSG_DATA(hdr);
+	ifi_flags = ifm->ifi_flags;
+	ifi_change = ifm->ifi_change;
+	ifm->ifi_flags = 0;
+	ifm->ifi_change = 0;
+
 	data = NLMSG_TAIL(hdr);
 	addattr_l(hdr, 1024, VETH_INFO_PEER, NULL, 0);
 
 	hdr->nlmsg_len += sizeof(struct ifinfomsg);
 
 	err = iplink_parse(argc - 1, argv + 1, (struct iplink_req *)hdr,
-			   &name, &type, &link, &dev, &group);
+			   &name, &type, &link, &dev, &group, &index);
 	if (err < 0)
 		return err;
 
@@ -54,11 +70,28 @@
 		addattr_l(hdr, 1024, IFLA_IFNAME, name, len);
 	}
 
+	peer_ifm = RTA_DATA(data);
+	peer_ifm->ifi_index = index;
+	peer_ifm->ifi_flags = ifm->ifi_flags;
+	peer_ifm->ifi_change = ifm->ifi_change;
+	ifm->ifi_flags = ifi_flags;
+	ifm->ifi_change = ifi_change;
+
+	if (group != -1)
+		addattr32(hdr, 1024, IFLA_GROUP, group);
+
 	data->rta_len = (void *)NLMSG_TAIL(hdr) - (void *)data;
 	return argc - 1 - err;
 }
 
+static void veth_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
+}
+
 struct link_util veth_link_util = {
 	.id = "veth",
 	.parse_opt = veth_parse_opt,
+	.print_help = veth_print_help,
 };
diff --git a/ip/link_vti.c b/ip/link_vti.c
new file mode 100644
index 0000000..59ac4c4
--- /dev/null
+++ b/ip/link_vti.c
@@ -0,0 +1,260 @@
+/*
+ * link_vti.c	VTI driver module
+ *
+ *		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.
+ *
+ * Authors:	Herbert Xu <herbert@gondor.apana.org.au>
+ *          Saurabh Mohan <saurabh.mohan@vyatta.com> Modified link_gre.c for VTI
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+
+static void print_usage(FILE *f)
+{
+	fprintf(f, "Usage: ip link { add | set | change | replace | del } NAME\n");
+	fprintf(f, "          type { vti } [ remote ADDR ] [ local ADDR ]\n");
+	fprintf(f, "          [ [i|o]key KEY ]\n");
+	fprintf(f, "          [ dev PHYS_DEV ]\n");
+	fprintf(f, "\n");
+	fprintf(f, "Where: NAME := STRING\n");
+	fprintf(f, "       ADDR := { IP_ADDRESS }\n");
+	fprintf(f, "       KEY  := { DOTTED_QUAD | NUMBER }\n");
+}
+
+static void usage(void) __attribute__((noreturn));
+static void usage(void)
+{
+	print_usage(stderr);
+	exit(-1);
+}
+
+static int vti_parse_opt(struct link_util *lu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+		char buf[1024];
+	} req;
+	struct ifinfomsg *ifi = (struct ifinfomsg *)(n + 1);
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *vtiinfo[IFLA_VTI_MAX + 1];
+	unsigned ikey = 0;
+	unsigned okey = 0;
+	unsigned saddr = 0;
+	unsigned daddr = 0;
+	unsigned link = 0;
+	int len;
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		memset(&req, 0, sizeof(req));
+
+		req.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
+		req.n.nlmsg_flags = NLM_F_REQUEST;
+		req.n.nlmsg_type = RTM_GETLINK;
+		req.i.ifi_family = preferred_family;
+		req.i.ifi_index = ifi->ifi_index;
+
+		if (rtnl_talk(&rth, &req.n, 0, 0, &req.n) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = req.n.nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(&req.i), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(vtiinfo, IFLA_VTI_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		if (vtiinfo[IFLA_VTI_IKEY])
+			ikey = *(__u32 *)RTA_DATA(vtiinfo[IFLA_VTI_IKEY]);
+
+		if (vtiinfo[IFLA_VTI_OKEY])
+			okey = *(__u32 *)RTA_DATA(vtiinfo[IFLA_VTI_OKEY]);
+
+		if (vtiinfo[IFLA_VTI_LOCAL])
+			saddr = *(__u32 *)RTA_DATA(vtiinfo[IFLA_VTI_LOCAL]);
+
+		if (vtiinfo[IFLA_VTI_REMOTE])
+			daddr = *(__u32 *)RTA_DATA(vtiinfo[IFLA_VTI_REMOTE]);
+
+		if (vtiinfo[IFLA_VTI_LINK])
+			link = *(__u8 *)RTA_DATA(vtiinfo[IFLA_VTI_LINK]);
+	}
+
+	while (argc > 0) {
+		if (!matches(*argv, "key")) {
+			unsigned uval;
+
+			NEXT_ARG();
+			if (strchr(*argv, '.'))
+				uval = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0) < 0) {
+					fprintf(stderr,
+						"Invalid value for \"key\": \"%s\"; it should be an unsigned integer\n", *argv);
+					exit(-1);
+				}
+				uval = htonl(uval);
+			}
+
+			ikey = okey = uval;
+		} else if (!matches(*argv, "ikey")) {
+			unsigned uval;
+
+			NEXT_ARG();
+			if (strchr(*argv, '.'))
+				uval = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0) < 0) {
+					fprintf(stderr, "invalid value for \"ikey\": \"%s\"; it should be an unsigned integer\n", *argv);
+					exit(-1);
+				}
+				uval = htonl(uval);
+			}
+			ikey = uval;
+		} else if (!matches(*argv, "okey")) {
+			unsigned uval;
+
+			NEXT_ARG();
+			if (strchr(*argv, '.'))
+				uval = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0) < 0) {
+					fprintf(stderr, "invalid value for \"okey\": \"%s\"; it should be an unsigned integer\n", *argv);
+					exit(-1);
+				}
+				uval = htonl(uval);
+			}
+			okey = uval;
+		} else if (!matches(*argv, "remote")) {
+			NEXT_ARG();
+			if (!strcmp(*argv, "any")) {
+				fprintf(stderr, "invalid value for \"remote\": \"%s\"\n", *argv);
+				exit(-1);
+			} else {
+				daddr = get_addr32(*argv);
+			}
+		} else if (!matches(*argv, "local")) {
+			NEXT_ARG();
+			if (!strcmp(*argv, "any")) {
+				fprintf(stderr, "invalid value for \"local\": \"%s\"\n", *argv);
+				exit(-1);
+			} else {
+				saddr = get_addr32(*argv);
+			}
+		} else if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			link = if_nametoindex(*argv);
+			if (link == 0) {
+				fprintf(stderr, "Cannot find device \"%s\"\n",
+					*argv);
+				exit(-1);
+			}
+		} else
+			usage();
+		argc--; argv++;
+	}
+
+	addattr32(n, 1024, IFLA_VTI_IKEY, ikey);
+	addattr32(n, 1024, IFLA_VTI_OKEY, okey);
+	addattr_l(n, 1024, IFLA_VTI_LOCAL, &saddr, 4);
+	addattr_l(n, 1024, IFLA_VTI_REMOTE, &daddr, 4);
+	if (link)
+		addattr32(n, 1024, IFLA_VTI_LINK, link);
+
+	return 0;
+}
+
+static void vti_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s1[1024];
+	char s2[64];
+	const char *local = "any";
+	const char *remote = "any";
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_VTI_REMOTE]) {
+		unsigned addr = *(__u32 *)RTA_DATA(tb[IFLA_VTI_REMOTE]);
+
+		if (addr)
+			remote = format_host(AF_INET, 4, &addr, s1, sizeof(s1));
+	}
+
+	fprintf(f, "remote %s ", remote);
+
+	if (tb[IFLA_VTI_LOCAL]) {
+		unsigned addr = *(__u32 *)RTA_DATA(tb[IFLA_VTI_LOCAL]);
+
+		if (addr)
+			local = format_host(AF_INET, 4, &addr, s1, sizeof(s1));
+	}
+
+	fprintf(f, "local %s ", local);
+
+	if (tb[IFLA_VTI_LINK] && *(__u32 *)RTA_DATA(tb[IFLA_VTI_LINK])) {
+		unsigned link = *(__u32 *)RTA_DATA(tb[IFLA_VTI_LINK]);
+		const char *n = if_indextoname(link, s2);
+
+		if (n)
+			fprintf(f, "dev %s ", n);
+		else
+			fprintf(f, "dev %u ", link);
+	}
+
+	if (tb[IFLA_VTI_IKEY]) {
+		inet_ntop(AF_INET, RTA_DATA(tb[IFLA_VTI_IKEY]), s2, sizeof(s2));
+		fprintf(f, "ikey %s ", s2);
+	}
+
+	if (tb[IFLA_VTI_OKEY]) {
+		inet_ntop(AF_INET, RTA_DATA(tb[IFLA_VTI_OKEY]), s2, sizeof(s2));
+		fprintf(f, "okey %s ", s2);
+	}
+}
+
+static void vti_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
+}
+
+struct link_util vti_link_util = {
+	.id = "vti",
+	.maxattr = IFLA_VTI_MAX,
+	.parse_opt = vti_parse_opt,
+	.print_opt = vti_print_opt,
+	.print_help = vti_print_help,
+};
diff --git a/ip/link_vti6.c b/ip/link_vti6.c
new file mode 100644
index 0000000..282896d
--- /dev/null
+++ b/ip/link_vti6.c
@@ -0,0 +1,250 @@
+/*
+ * link_vti6.c	VTI driver module
+ *
+ *		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.
+ *
+ * Authors:	Herbert Xu <herbert@gondor.apana.org.au>
+ *		Saurabh Mohan <saurabh.mohan@vyatta.com> Modified link_gre.c for VTI
+ *		Steffen Klassert <steffen.klassert@secunet.com> Modified link_vti.c for IPv6
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+
+static void usage(void) __attribute__((noreturn));
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ip link { add | set | change | replace | del } NAME\n");
+	fprintf(stderr, "          type { vti6 } [ remote ADDR ] [ local ADDR ]\n");
+	fprintf(stderr, "          [ [i|o]key KEY ]\n");
+	fprintf(stderr, "          [ dev PHYS_DEV ]\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, "Where: NAME := STRING\n");
+	fprintf(stderr, "       ADDR := { IPV6_ADDRESS }\n");
+	fprintf(stderr, "       KEY  := { DOTTED_QUAD | NUMBER }\n");
+	exit(-1);
+}
+
+static int vti6_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+		char buf[1024];
+	} req;
+	struct ifinfomsg *ifi = (struct ifinfomsg *)(n + 1);
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *vtiinfo[IFLA_VTI_MAX + 1];
+	struct in6_addr saddr;
+	struct in6_addr daddr;
+	unsigned ikey = 0;
+	unsigned okey = 0;
+	unsigned link = 0;
+	int len;
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		memset(&req, 0, sizeof(req));
+
+		req.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
+		req.n.nlmsg_flags = NLM_F_REQUEST;
+		req.n.nlmsg_type = RTM_GETLINK;
+		req.i.ifi_family = preferred_family;
+		req.i.ifi_index = ifi->ifi_index;
+
+		if (rtnl_talk(&rth, &req.n, 0, 0, &req.n) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = req.n.nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(&req.i), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(vtiinfo, IFLA_VTI_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		if (vtiinfo[IFLA_VTI_IKEY])
+			ikey = rta_getattr_u32(vtiinfo[IFLA_VTI_IKEY]);
+
+		if (vtiinfo[IFLA_VTI_OKEY])
+			okey = rta_getattr_u32(vtiinfo[IFLA_VTI_OKEY]);
+
+		if (vtiinfo[IFLA_VTI_LOCAL])
+			memcpy(&saddr, RTA_DATA(vtiinfo[IFLA_VTI_LOCAL]), sizeof(saddr));
+
+		if (vtiinfo[IFLA_VTI_REMOTE])
+			memcpy(&daddr, RTA_DATA(vtiinfo[IFLA_VTI_REMOTE]), sizeof(daddr));
+
+		if (vtiinfo[IFLA_VTI_LINK])
+			link = rta_getattr_u8(vtiinfo[IFLA_VTI_LINK]);
+	}
+
+	while (argc > 0) {
+		if (!matches(*argv, "key")) {
+			unsigned uval;
+
+			NEXT_ARG();
+			if (strchr(*argv, '.'))
+				uval = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0) < 0) {
+					fprintf(stderr,
+						"Invalid value for \"key\": \"%s\"; it should be an unsigned integer\n", *argv);
+					exit(-1);
+				}
+				uval = htonl(uval);
+			}
+
+			ikey = okey = uval;
+		} else if (!matches(*argv, "ikey")) {
+			unsigned uval;
+
+			NEXT_ARG();
+			if (strchr(*argv, '.'))
+				uval = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0) < 0) {
+					fprintf(stderr, "invalid value for \"ikey\": \"%s\"; it should be an unsigned integer\n", *argv);
+					exit(-1);
+				}
+				uval = htonl(uval);
+			}
+			ikey = uval;
+		} else if (!matches(*argv, "okey")) {
+			unsigned uval;
+
+			NEXT_ARG();
+			if (strchr(*argv, '.'))
+				uval = get_addr32(*argv);
+			else {
+				if (get_unsigned(&uval, *argv, 0) < 0) {
+					fprintf(stderr, "invalid value for \"okey\": \"%s\"; it should be an unsigned integer\n", *argv);
+					exit(-1);
+				}
+				uval = htonl(uval);
+			}
+			okey = uval;
+		} else if (!matches(*argv, "remote")) {
+			NEXT_ARG();
+			if (!strcmp(*argv, "any")) {
+				fprintf(stderr, "invalid value for \"remote\": \"%s\"\n", *argv);
+				exit(-1);
+			} else {
+				inet_prefix addr;
+				get_prefix(&addr, *argv, AF_INET6);
+				memcpy(&daddr, addr.data, addr.bytelen);
+			}
+		} else if (!matches(*argv, "local")) {
+			NEXT_ARG();
+			if (!strcmp(*argv, "any")) {
+				fprintf(stderr, "invalid value for \"local\": \"%s\"\n", *argv);
+				exit(-1);
+			} else {
+				inet_prefix addr;
+				get_prefix(&addr, *argv, AF_INET6);
+				memcpy(&saddr, addr.data, addr.bytelen);
+			}
+		} else if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			link = if_nametoindex(*argv);
+			if (link == 0)
+				exit(-1);
+		} else
+			usage();
+		argc--; argv++;
+	}
+
+	addattr32(n, 1024, IFLA_VTI_IKEY, ikey);
+	addattr32(n, 1024, IFLA_VTI_OKEY, okey);
+	addattr_l(n, 1024, IFLA_VTI_LOCAL, &saddr, sizeof(saddr));
+	addattr_l(n, 1024, IFLA_VTI_REMOTE, &daddr, sizeof(daddr));
+	if (link)
+		addattr32(n, 1024, IFLA_VTI_LINK, link);
+
+	return 0;
+}
+
+static void vti6_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s1[1024];
+	char s2[64];
+	const char *local = "any";
+	const char *remote = "any";
+	struct in6_addr saddr;
+	struct in6_addr daddr;
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_VTI_REMOTE]) {
+		memcpy(&daddr, RTA_DATA(tb[IFLA_VTI_REMOTE]), sizeof(daddr));
+
+		remote = format_host(AF_INET6, 16, &daddr, s1, sizeof(s1));
+	}
+
+	fprintf(f, "remote %s ", remote);
+
+	if (tb[IFLA_VTI_LOCAL]) {
+		memcpy(&saddr, RTA_DATA(tb[IFLA_VTI_LOCAL]), sizeof(saddr));
+
+		local = format_host(AF_INET6, 16, &saddr, s1, sizeof(s1));
+	}
+
+	fprintf(f, "local %s ", local);
+
+	if (tb[IFLA_VTI_LINK] && *(__u32 *)RTA_DATA(tb[IFLA_VTI_LINK])) {
+		unsigned link = *(__u32 *)RTA_DATA(tb[IFLA_VTI_LINK]);
+		const char *n = if_indextoname(link, s2);
+
+		if (n)
+			fprintf(f, "dev %s ", n);
+		else
+			fprintf(f, "dev %u ", link);
+	}
+
+	if (tb[IFLA_VTI_IKEY]) {
+		inet_ntop(AF_INET, RTA_DATA(tb[IFLA_VTI_IKEY]), s2, sizeof(s2));
+		fprintf(f, "ikey %s ", s2);
+	}
+
+	if (tb[IFLA_VTI_OKEY]) {
+		inet_ntop(AF_INET, RTA_DATA(tb[IFLA_VTI_OKEY]), s2, sizeof(s2));
+		fprintf(f, "okey %s ", s2);
+	}
+}
+
+struct link_util vti6_link_util = {
+	.id = "vti6",
+	.maxattr = IFLA_VTI_MAX,
+	.parse_opt = vti6_parse_opt,
+	.print_opt = vti6_print_opt,
+};
diff --git a/ip/rtmon.c b/ip/rtmon.c
index c1416a0..ff685e5 100644
--- a/ip/rtmon.c
+++ b/ip/rtmon.c
@@ -34,7 +34,7 @@
 	struct nlmsghdr *n1 = (void*)buf;
 	struct timeval tv;
 
-	n1->nlmsg_type = 15;
+	n1->nlmsg_type = NLMSG_TSTAMP;
 	n1->nlmsg_flags = 0;
 	n1->nlmsg_seq = 0;
 	n1->nlmsg_pid = 0;
@@ -56,7 +56,7 @@
 	return 0;
 }
 
-void usage(void)
+static void usage(void)
 {
 	fprintf(stderr, "Usage: rtmon file FILE [ all | LISTofOBJECTS]\n");
 	fprintf(stderr, "LISTofOBJECTS := [ link ] [ address ] [ route ]\n");
diff --git a/ip/static-syms.c b/ip/static-syms.c
index 1ed3a8a..0bc8074 100644
--- a/ip/static-syms.c
+++ b/ip/static-syms.c
@@ -1,4 +1,12 @@
+/*
+ * This file creates a dummy version of dynamic loading
+ * for environments where dynamic linking
+ * is not used or available.
+ */
+
 #include <string.h>
+#include "dlfcn.h"
+
 void *_dlsym(const char *sym)
 {
 #include "static-syms.h"
diff --git a/ip/tcp_metrics.c b/ip/tcp_metrics.c
new file mode 100644
index 0000000..bbbb4cc
--- /dev/null
+++ b/ip/tcp_metrics.c
@@ -0,0 +1,511 @@
+/*
+ * tcp_metrics.c	"ip tcp_metrics/tcpmetrics"
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		version 2 as published by the Free Software Foundation;
+ *
+ * Authors:	Julian Anastasov <ja@ssi.bg>, August 2012
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+
+#include <linux/genetlink.h>
+#include <linux/tcp_metrics.h>
+
+#include "utils.h"
+#include "ip_common.h"
+#include "libgenl.h"
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ip tcp_metrics/tcpmetrics { COMMAND | help }\n");
+	fprintf(stderr, "       ip tcp_metrics { show | flush } SELECTOR\n");
+	fprintf(stderr, "       ip tcp_metrics delete [ address ] ADDRESS\n");
+	fprintf(stderr, "SELECTOR := [ [ address ] PREFIX ]\n");
+	exit(-1);
+}
+
+/* netlink socket */
+static struct rtnl_handle grth = { .fd = -1 };
+static int genl_family = -1;
+
+#define TCPM_REQUEST(_req, _bufsiz, _cmd, _flags) \
+	GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
+		     TCP_METRICS_GENL_VERSION, _cmd, _flags)
+
+#define CMD_LIST	0x0001	/* list, lst, show		*/
+#define CMD_DEL		0x0002	/* delete, remove		*/
+#define CMD_FLUSH	0x0004	/* flush			*/
+
+static struct {
+	char	*name;
+	int	code;
+} cmds[] = {
+	{	"list",		CMD_LIST	},
+	{	"lst",		CMD_LIST	},
+	{	"show",		CMD_LIST	},
+	{	"delete",	CMD_DEL		},
+	{	"remove",	CMD_DEL		},
+	{	"flush",	CMD_FLUSH	},
+};
+
+static char *metric_name[TCP_METRIC_MAX + 1] = {
+	[TCP_METRIC_RTT]		= "rtt",
+	[TCP_METRIC_RTTVAR]		= "rttvar",
+	[TCP_METRIC_SSTHRESH]		= "ssthresh",
+	[TCP_METRIC_CWND]		= "cwnd",
+	[TCP_METRIC_REORDERING]		= "reordering",
+};
+
+static struct
+{
+	int flushed;
+	char *flushb;
+	int flushp;
+	int flushe;
+	int cmd;
+	inet_prefix daddr;
+	inet_prefix saddr;
+} f;
+
+static int flush_update(void)
+{
+	if (rtnl_send_check(&grth, f.flushb, f.flushp) < 0) {
+		perror("Failed to send flush request\n");
+		return -1;
+	}
+	f.flushp = 0;
+	return 0;
+}
+
+static int process_msg(const struct sockaddr_nl *who, struct nlmsghdr *n,
+		       void *arg)
+{
+	FILE *fp = (FILE *) arg;
+	struct genlmsghdr *ghdr;
+	struct rtattr *attrs[TCP_METRICS_ATTR_MAX + 1], *a;
+	int len = n->nlmsg_len;
+	char abuf[256];
+	inet_prefix daddr, saddr;
+	int family, i, atype, stype, dlen = 0, slen = 0;
+
+	if (n->nlmsg_type != genl_family)
+		return -1;
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+	if (len < 0)
+		return -1;
+
+	ghdr = NLMSG_DATA(n);
+	if (ghdr->cmd != TCP_METRICS_CMD_GET)
+		return 0;
+
+	parse_rtattr(attrs, TCP_METRICS_ATTR_MAX, (void *) ghdr + GENL_HDRLEN,
+		     len);
+
+	a = attrs[TCP_METRICS_ATTR_ADDR_IPV4];
+	if (a) {
+		if (f.daddr.family && f.daddr.family != AF_INET)
+			return 0;
+		memcpy(&daddr.data, RTA_DATA(a), 4);
+		daddr.bytelen = 4;
+		family = AF_INET;
+		atype = TCP_METRICS_ATTR_ADDR_IPV4;
+		dlen = RTA_PAYLOAD(a);
+	} else {
+		a = attrs[TCP_METRICS_ATTR_ADDR_IPV6];
+		if (a) {
+			if (f.daddr.family && f.daddr.family != AF_INET6)
+				return 0;
+			memcpy(&daddr.data, RTA_DATA(a), 16);
+			daddr.bytelen = 16;
+			family = AF_INET6;
+			atype = TCP_METRICS_ATTR_ADDR_IPV6;
+			dlen = RTA_PAYLOAD(a);
+		} else
+			return 0;
+	}
+
+	a = attrs[TCP_METRICS_ATTR_SADDR_IPV4];
+	if (a) {
+		if (f.saddr.family && f.saddr.family != AF_INET)
+			return 0;
+		memcpy(&saddr.data, RTA_DATA(a), 4);
+		saddr.bytelen = 4;
+		stype = TCP_METRICS_ATTR_SADDR_IPV4;
+		slen = RTA_PAYLOAD(a);
+	} else {
+		a = attrs[TCP_METRICS_ATTR_SADDR_IPV6];
+		if (a) {
+			if (f.saddr.family && f.saddr.family != AF_INET6)
+				return 0;
+			memcpy(&saddr.data, RTA_DATA(a), 16);
+			saddr.bytelen = 16;
+			stype = TCP_METRICS_ATTR_SADDR_IPV6;
+			slen = RTA_PAYLOAD(a);
+		}
+	}
+
+	if (f.daddr.family && f.daddr.bitlen >= 0 &&
+	    inet_addr_match(&daddr, &f.daddr, f.daddr.bitlen))
+	       return 0;
+	/* Only check for the source-address if the kernel supports it,
+	 * meaning slen != 0.
+	 */
+	if (slen && f.saddr.family && f.saddr.bitlen >= 0 &&
+	    inet_addr_match(&saddr, &f.saddr, f.saddr.bitlen))
+		return 0;
+
+	if (f.flushb) {
+		struct nlmsghdr *fn;
+		TCPM_REQUEST(req2, 128, TCP_METRICS_CMD_DEL, NLM_F_REQUEST);
+
+		addattr_l(&req2.n, sizeof(req2), atype, &daddr.data,
+			  daddr.bytelen);
+		if (slen)
+			addattr_l(&req2.n, sizeof(req2), stype, &saddr.data,
+				  saddr.bytelen);
+
+		if (NLMSG_ALIGN(f.flushp) + req2.n.nlmsg_len > f.flushe) {
+			if (flush_update())
+				return -1;
+		}
+		fn = (struct nlmsghdr *) (f.flushb + NLMSG_ALIGN(f.flushp));
+		memcpy(fn, &req2.n, req2.n.nlmsg_len);
+		fn->nlmsg_seq = ++grth.seq;
+		f.flushp = (((char *) fn) + req2.n.nlmsg_len) - f.flushb;
+		f.flushed++;
+		if (show_stats < 2)
+			return 0;
+	}
+
+	if (f.cmd & (CMD_DEL | CMD_FLUSH))
+		fprintf(fp, "Deleted ");
+
+	fprintf(fp, "%s",
+		format_host(family, dlen, &daddr.data, abuf, sizeof(abuf)));
+
+	a = attrs[TCP_METRICS_ATTR_AGE];
+	if (a) {
+		unsigned long long val = rta_getattr_u64(a);
+
+		fprintf(fp, " age %llu.%03llusec",
+			val / 1000, val % 1000);
+	}
+
+	a = attrs[TCP_METRICS_ATTR_TW_TS_STAMP];
+	if (a) {
+		__s32 val = (__s32) rta_getattr_u32(a);
+		__u32 tsval;
+
+		a = attrs[TCP_METRICS_ATTR_TW_TSVAL];
+		tsval = a ? rta_getattr_u32(a) : 0;
+		fprintf(fp, " tw_ts %u/%dsec ago", tsval, val);
+	}
+
+	a = attrs[TCP_METRICS_ATTR_VALS];
+	if (a) {
+		struct rtattr *m[TCP_METRIC_MAX + 1 + 1];
+		unsigned long rtt = 0, rttvar = 0;
+
+		parse_rtattr_nested(m, TCP_METRIC_MAX + 1, a);
+
+		for (i = 0; i < TCP_METRIC_MAX + 1; i++) {
+			unsigned long val;
+
+			a = m[i + 1];
+			if (!a)
+				continue;
+			if (i != TCP_METRIC_RTT &&
+			    i != TCP_METRIC_RTT_US &&
+			    i != TCP_METRIC_RTTVAR &&
+			    i != TCP_METRIC_RTTVAR_US) {
+				if (metric_name[i])
+					fprintf(fp, " %s ", metric_name[i]);
+				else
+					fprintf(fp, " metric_%d ", i);
+			}
+			val = rta_getattr_u32(a);
+			switch (i) {
+			case TCP_METRIC_RTT:
+				if (!rtt)
+					rtt = (val * 1000UL) >> 3;
+				break;
+			case TCP_METRIC_RTTVAR:
+				if (!rttvar)
+					rttvar = (val * 1000UL) >> 2;
+				break;
+			case TCP_METRIC_RTT_US:
+				rtt = val >> 3;
+				break;
+			case TCP_METRIC_RTTVAR_US:
+				rttvar = val >> 2;
+				break;
+			case TCP_METRIC_SSTHRESH:
+			case TCP_METRIC_CWND:
+			case TCP_METRIC_REORDERING:
+			default:
+				fprintf(fp, "%lu", val);
+				break;
+			}
+		}
+		if (rtt)
+			fprintf(fp, " rtt %luus", rtt);
+		if (rttvar)
+			fprintf(fp, " rttvar %luus", rttvar);
+	}
+
+	a = attrs[TCP_METRICS_ATTR_FOPEN_MSS];
+	if (a)
+		fprintf(fp, " fo_mss %u", rta_getattr_u16(a));
+
+	a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROPS];
+	if (a) {
+		__u16 syn_loss = rta_getattr_u16(a);
+		unsigned long long ts;
+
+		a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROP_TS];
+		ts = a ? rta_getattr_u64(a) : 0;
+
+		fprintf(fp, " fo_syn_drops %u/%llu.%03llusec ago",
+			syn_loss, ts / 1000, ts % 1000);
+	}
+
+	a = attrs[TCP_METRICS_ATTR_FOPEN_COOKIE];
+	if (a) {
+		char cookie[32 + 1];
+		unsigned char *ptr = RTA_DATA(a);
+		int i, max = RTA_PAYLOAD(a);
+
+		if (max > 16)
+			max = 16;
+		cookie[0] = 0;
+		for (i = 0; i < max; i++)
+			sprintf(cookie + i + i, "%02x", ptr[i]);
+		fprintf(fp, " fo_cookie %s", cookie);
+	}
+
+	if (slen) {
+		fprintf(fp, " source %s",
+			format_host(family, slen, &saddr.data, abuf,
+				    sizeof(abuf)));
+	}
+
+	fprintf(fp, "\n");
+
+	fflush(fp);
+	return 0;
+}
+
+static int tcpm_do_cmd(int cmd, int argc, char **argv)
+{
+	TCPM_REQUEST(req, 1024, TCP_METRICS_CMD_GET, NLM_F_REQUEST);
+	int atype = -1, stype = -1;
+	int ack;
+
+	memset(&f, 0, sizeof(f));
+	f.daddr.bitlen = -1;
+	f.daddr.family = preferred_family;
+	f.saddr.bitlen = -1;
+	f.saddr.family = preferred_family;
+
+	switch (preferred_family) {
+	case AF_UNSPEC:
+	case AF_INET:
+	case AF_INET6:
+		break;
+	default:
+		fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
+		return -1;
+	}
+
+	for (; argc > 0; argc--, argv++) {
+		if (strcmp(*argv, "src") == 0 ||
+		    strcmp(*argv, "source") == 0) {
+			char *who = *argv;
+			NEXT_ARG();
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (f.saddr.bitlen >= 0)
+				duparg2(who, *argv);
+
+			get_prefix(&f.saddr, *argv, preferred_family);
+			if (f.saddr.bytelen && f.saddr.bytelen * 8 == f.saddr.bitlen) {
+				if (f.saddr.family == AF_INET)
+					stype = TCP_METRICS_ATTR_SADDR_IPV4;
+				else if (f.saddr.family == AF_INET6)
+					stype = TCP_METRICS_ATTR_SADDR_IPV6;
+			}
+
+			if (stype < 0) {
+				fprintf(stderr, "Error: a specific IP address is expected rather than \"%s\"\n",
+					*argv);
+				return -1;
+			}
+		} else {
+			char *who = "address";
+			if (strcmp(*argv, "addr") == 0 ||
+			    strcmp(*argv, "address") == 0) {
+				who = *argv;
+				NEXT_ARG();
+			}
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (f.daddr.bitlen >= 0)
+				duparg2(who, *argv);
+
+			get_prefix(&f.daddr, *argv, preferred_family);
+			if (f.daddr.bytelen && f.daddr.bytelen * 8 == f.daddr.bitlen) {
+				if (f.daddr.family == AF_INET)
+					atype = TCP_METRICS_ATTR_ADDR_IPV4;
+				else if (f.daddr.family == AF_INET6)
+					atype = TCP_METRICS_ATTR_ADDR_IPV6;
+			}
+			if ((CMD_DEL & cmd) && atype < 0) {
+				fprintf(stderr, "Error: a specific IP address is expected rather than \"%s\"\n",
+					*argv);
+				return -1;
+			}
+		}
+		argc--; argv++;
+	}
+
+	if (cmd == CMD_DEL && atype < 0)
+		missarg("address");
+
+	/* flush for exact address ? Single del */
+	if (cmd == CMD_FLUSH && atype >= 0)
+		cmd = CMD_DEL;
+
+	/* flush for all addresses ? Single del without address */
+	if (cmd == CMD_FLUSH && f.daddr.bitlen <= 0 &&
+	    f.saddr.bitlen <= 0 && preferred_family == AF_UNSPEC) {
+		cmd = CMD_DEL;
+		req.g.cmd = TCP_METRICS_CMD_DEL;
+		ack = 1;
+	} else if (cmd == CMD_DEL) {
+		req.g.cmd = TCP_METRICS_CMD_DEL;
+		ack = 1;
+	} else {	/* CMD_FLUSH, CMD_LIST */
+		ack = 0;
+	}
+
+	if (genl_family < 0) {
+		if (rtnl_open_byproto(&grth, 0, NETLINK_GENERIC) < 0) {
+			fprintf(stderr, "Cannot open generic netlink socket\n");
+			exit(1);
+		}
+		genl_family = genl_resolve_family(&grth,
+						  TCP_METRICS_GENL_NAME);
+		if (genl_family < 0)
+			exit(1);
+		req.n.nlmsg_type = genl_family;
+	}
+
+	if (!(cmd & CMD_FLUSH) && (atype >= 0 || (cmd & CMD_DEL))) {
+		if (ack)
+			req.n.nlmsg_flags |= NLM_F_ACK;
+		if (atype >= 0)
+			addattr_l(&req.n, sizeof(req), atype, &f.daddr.data,
+				  f.daddr.bytelen);
+		if (stype >= 0)
+			addattr_l(&req.n, sizeof(req), stype, &f.saddr.data,
+				  f.saddr.bytelen);
+	} else {
+		req.n.nlmsg_flags |= NLM_F_DUMP;
+	}
+
+	f.cmd = cmd;
+	if (cmd & CMD_FLUSH) {
+		int round = 0;
+		char flushb[4096-512];
+
+		f.flushb = flushb;
+		f.flushp = 0;
+		f.flushe = sizeof(flushb);
+
+		for (;;) {
+			req.n.nlmsg_seq = grth.dump = ++grth.seq;
+			if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
+				perror("Failed to send flush request");
+				exit(1);
+			}
+			f.flushed = 0;
+			if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
+				fprintf(stderr, "Flush terminated\n");
+				exit(1);
+			}
+			if (f.flushed == 0) {
+				if (round == 0) {
+					fprintf(stderr, "Nothing to flush.\n");
+				} else if (show_stats)
+					printf("*** Flush is complete after %d round%s ***\n",
+					       round, round > 1 ? "s" : "");
+				fflush(stdout);
+				return 0;
+			}
+			round++;
+			if (flush_update() < 0)
+				exit(1);
+			if (show_stats) {
+				printf("\n*** Round %d, deleting %d entries ***\n",
+				       round, f.flushed);
+				fflush(stdout);
+			}
+		}
+		return 0;
+	}
+
+	if (ack) {
+		if (rtnl_talk(&grth, &req.n, 0, 0, NULL) < 0)
+			return -2;
+	} else if (atype >= 0) {
+		if (rtnl_talk(&grth, &req.n, 0, 0, &req.n) < 0)
+			return -2;
+		if (process_msg(NULL, &req.n, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+	} else {
+		req.n.nlmsg_seq = grth.dump = ++grth.seq;
+		if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
+			perror("Failed to send dump request");
+			exit(1);
+		}
+
+		if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+	}
+	return 0;
+}
+
+int do_tcp_metrics(int argc, char **argv)
+{
+	int i;
+
+	if (argc < 1)
+		return tcpm_do_cmd(CMD_LIST, 0, NULL);
+	for (i = 0; i < ARRAY_SIZE(cmds); i++) {
+		if (matches(argv[0], cmds[i].name) == 0)
+			return tcpm_do_cmd(cmds[i].code, argc-1, argv+1);
+	}
+	if (matches(argv[0], "help") == 0)
+		usage();
+
+	fprintf(stderr, "Command \"%s\" is unknown, "
+			"try \"ip tcp_metrics help\".\n", *argv);
+	exit(-1);
+}
+
diff --git a/ip/tunnel.c b/ip/tunnel.c
index b176d3f..33c78e3 100644
--- a/ip/tunnel.c
+++ b/ip/tunnel.c
@@ -12,8 +12,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  */
 /*
  * split from ip_tunnel.c
@@ -52,6 +51,9 @@
 	case IPPROTO_IPV6:
 		strcpy(buf, "ipv6");
 		break;
+	case IPPROTO_ESP:
+		strcpy(buf, "esp");
+		break;
 	case 0:
 		strcpy(buf, "any");
 		break;
@@ -74,7 +76,7 @@
 	fd = socket(preferred_family, SOCK_DGRAM, 0);
 	err = ioctl(fd, SIOCGETTUNNEL, &ifr);
 	if (err)
-		fprintf(stderr, "get tunnel %s failed: %s\n", basedev, 
+		fprintf(stderr, "get tunnel \"%s\" failed: %s\n", basedev,
 			strerror(errno));
 
 	close(fd);
@@ -95,7 +97,7 @@
 	fd = socket(preferred_family, SOCK_DGRAM, 0);
 	err = ioctl(fd, cmd, &ifr);
 	if (err)
-		fprintf(stderr, "add tunnel %s failed: %s\n", ifr.ifr_name,
+		fprintf(stderr, "add tunnel \"%s\" failed: %s\n", ifr.ifr_name,
 			strerror(errno));
 	close(fd);
 	return err;
@@ -116,13 +118,13 @@
 	fd = socket(preferred_family, SOCK_DGRAM, 0);
 	err = ioctl(fd, SIOCDELTUNNEL, &ifr);
 	if (err)
-		fprintf(stderr, "delete tunnel %s failed: %s\n",
+		fprintf(stderr, "delete tunnel \"%s\" failed: %s\n",
 			ifr.ifr_name, strerror(errno));
 	close(fd);
 	return err;
 }
 
-static int tnl_gen_ioctl(int cmd, const char *name, 
+static int tnl_gen_ioctl(int cmd, const char *name,
 			 void *p, int skiperr)
 {
 	struct ifreq ifr;
diff --git a/ip/tunnel.h b/ip/tunnel.h
index 7e7fe13..9c2f5d2 100644
--- a/ip/tunnel.h
+++ b/ip/tunnel.h
@@ -12,8 +12,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  */
 /*
  * Author:
diff --git a/ip/xfrm.h b/ip/xfrm.h
index 784a201..773c92e 100644
--- a/ip/xfrm.h
+++ b/ip/xfrm.h
@@ -14,8 +14,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  */
 /*
  * Authors:
diff --git a/ip/xfrm_monitor.c b/ip/xfrm_monitor.c
index 0c8fcb0..50116a7 100644
--- a/ip/xfrm_monitor.c
+++ b/ip/xfrm_monitor.c
@@ -14,8 +14,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  */
 /*
  * based on ipmonitor.c
@@ -28,7 +27,6 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <endian.h>
 #include <linux/xfrm.h>
 #include "utils.h"
 #include "xfrm.h"
@@ -38,7 +36,8 @@
 
 static void usage(void)
 {
-	fprintf(stderr, "Usage: ip xfrm monitor [ all | LISTofXFRM-OBJECTS ]\n");
+	fprintf(stderr, "Usage: ip xfrm monitor [ all | OBJECTS | help ]\n");
+	fprintf(stderr, "OBJECTS := { acquire | expire | SA | aevent | policy | report }\n");
 	exit(-1);
 }
 
@@ -207,7 +206,7 @@
 	return 0;
 }
 
-void xfrm_ae_flags_print(__u32 flags, void *arg)
+static void xfrm_ae_flags_print(__u32 flags, void *arg)
 {
 	FILE *fp = (FILE*)arg;
 	fprintf(fp, " (0x%x) ", flags);
@@ -227,8 +226,8 @@
 	char buf[256];
 
 	buf[0] = 0;
-	fprintf(fp, "dst %s ", rt_addr_n2a(sa_id->family,
-		sizeof(sa_id->daddr), &sa_id->daddr, buf, sizeof(buf)));
+	fprintf(fp, "dst %s ",
+		rt_addr_n2a(sa_id->family, &sa_id->daddr, buf, sizeof(buf)));
 
 	fprintf(fp, " reqid 0x%x", reqid);
 
@@ -247,9 +246,8 @@
 	xfrm_ae_flags_print(id->flags, arg);
 	fprintf(fp,"\n\t");
 	memset(abuf, '\0', sizeof(abuf));
-	fprintf(fp, "src %s ", rt_addr_n2a(id->sa_id.family,
-		sizeof(id->saddr), &id->saddr,
-		abuf, sizeof(abuf)));
+	fprintf(fp, "src %s ", rt_addr_n2a(id->sa_id.family, &id->saddr,
+					   abuf, sizeof(abuf)));
 
 	xfrm_usersa_print(&id->sa_id, id->reqid, fp);
 
@@ -259,12 +257,12 @@
 	return 0;
 }
 
-static void xfrm_print_addr(FILE *fp, int family, xfrm_address_t *a, size_t s)
+static void xfrm_print_addr(FILE *fp, int family, xfrm_address_t *a)
 {
 	char buf[256];
 
 	buf[0] = 0;
-	fprintf(fp, "%s", rt_addr_n2a(family, s, a, buf, sizeof(buf)));
+	fprintf(fp, "%s", rt_addr_n2a(family, a, buf, sizeof(buf)));
 }
 
 static int xfrm_mapping_print(const struct sockaddr_nl *who,
@@ -274,12 +272,10 @@
 	struct xfrm_user_mapping *map = NLMSG_DATA(n);
 
 	fprintf(fp, "Mapping change ");
-	xfrm_print_addr(fp, map->id.family, &map->old_saddr,
-			sizeof(map->old_saddr));
+	xfrm_print_addr(fp, map->id.family, &map->old_saddr);
 
 	fprintf(fp, ":%d -> ", ntohs(map->old_sport));
-	xfrm_print_addr(fp, map->id.family, &map->new_saddr,
-			sizeof(map->new_saddr));
+	xfrm_print_addr(fp, map->id.family, &map->new_saddr);
 	fprintf(fp, ":%d\n\t", ntohs(map->new_sport));
 
 	xfrm_usersa_print(&map->id, map->reqid, fp);
@@ -379,7 +375,7 @@
 			groups = 0;
 		} else if (matches(*argv, "help") == 0) {
 			usage();
-		} else {
+		} else if (strcmp(*argv, "all")) {
 			fprintf(stderr, "Argument \"%s\" is unknown, try \"ip xfrm monitor help\".\n", *argv);
 			exit(-1);
 		}
@@ -409,8 +405,6 @@
 		return rtnl_from_file(fp, xfrm_accept_msg, (void*)stdout);
 	}
 
-	//ll_init_map(&rth);
-
 	if (rtnl_open_byproto(&rth, groups, NETLINK_XFRM) < 0)
 		exit(1);
 
diff --git a/ip/xfrm_policy.c b/ip/xfrm_policy.c
index 437f61e..2337d35 100644
--- a/ip/xfrm_policy.c
+++ b/ip/xfrm_policy.c
@@ -14,8 +14,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  */
 /*
  * based on iproute.c
@@ -31,9 +30,6 @@
 #include <netdb.h>
 #include <linux/netlink.h>
 #include <linux/xfrm.h>
-#include <linux/in.h>
-#include <linux/in6.h>
-
 #include "utils.h"
 #include "xfrm.h"
 #include "ip_common.h"
@@ -99,7 +95,7 @@
 	fprintf(stderr, "%s | ", strxf_xfrmproto(IPPROTO_COMP));
 	fprintf(stderr, "%s | ", strxf_xfrmproto(IPPROTO_ROUTING));
 	fprintf(stderr, "%s\n", strxf_xfrmproto(IPPROTO_DSTOPTS));
- 	fprintf(stderr, "MODE := transport | tunnel | ro | in_trigger | beet\n");
+	fprintf(stderr, "MODE := transport | tunnel | beet | ro | in_trigger\n");
 	fprintf(stderr, "LEVEL := required | use\n");
 
 	exit(-1);
@@ -117,7 +113,7 @@
 	else if (strcmp(*argv, "fwd") == 0)
 		*dir = XFRM_POLICY_FWD;
 	else
-		invarg("\"DIR\" is invalid", *argv);
+		invarg("DIR value is invalid", *argv);
 
 	*argcp = argc;
 	*argvp = argv;
@@ -135,7 +131,7 @@
 	else if (strcmp(*argv, "sub") == 0)
 		*ptype = XFRM_POLICY_TYPE_SUB;
 	else
-		invarg("\"PTYPE\" is invalid", *argv);
+		invarg("PTYPE value is invalid", *argv);
 
 	*argcp = argc;
 	*argvp = argv;
@@ -153,7 +149,7 @@
 		__u8 val = 0;
 
 		if (get_u8(&val, *argv, 16))
-			invarg("\"FLAG\" is invalid", *argv);
+			invarg("FLAG value is invalid", *argv);
 		*flags = val;
 	} else {
 		while (1) {
@@ -200,7 +196,7 @@
 			else if (strcmp(*argv, "use") == 0)
 				tmpl->optional = 1;
 			else
-				invarg("\"LEVEL\" is invalid\n", *argv);
+				invarg("LEVEL value is invalid\n", *argv);
 
 		} else {
 			if (idp) {
@@ -303,7 +299,7 @@
 		} else if (strcmp(*argv, "index") == 0) {
 			NEXT_ARG();
 			if (get_u32(&req.xpinfo.index, *argv, 0))
-				invarg("\"INDEX\" is invalid", *argv);
+				invarg("INDEX value is invalid", *argv);
 		} else if (strcmp(*argv, "ptype") == 0) {
 			if (ptypep)
 				duparg("ptype", *argv);
@@ -318,11 +314,11 @@
 			else if (strcmp(*argv, "block") == 0)
 				req.xpinfo.action = XFRM_POLICY_BLOCK;
 			else
-				invarg("\"action\" value is invalid\n", *argv);
+				invarg("ACTION value is invalid\n", *argv);
 		} else if (strcmp(*argv, "priority") == 0) {
 			NEXT_ARG();
 			if (get_u32(&req.xpinfo.priority, *argv, 0))
-				invarg("\"PRIORITY\" is invalid", *argv);
+				invarg("PRIORITY value is invalid", *argv);
 		} else if (strcmp(*argv, "flag") == 0) {
 			NEXT_ARG();
 			xfrm_policy_flag_parse(&req.xpinfo.flags, &argc,
@@ -362,7 +358,7 @@
 	}
 
 	if (!dirp) {
-		fprintf(stderr, "Not enough information: \"DIR\" is required.\n");
+		fprintf(stderr, "Not enough information: DIR is required.\n");
 		exit(1);
 	}
 
@@ -376,7 +372,7 @@
 			  (void *)tmpls_buf, tmpls_len);
 	}
 
-	if (mark.m & mark.v) {
+	if (mark.m) {
 		int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
 				  (void *)&mark, sizeof(mark));
 		if (r < 0) {
@@ -614,7 +610,7 @@
 
 			NEXT_ARG();
 			if (get_u32(&req.xpid.index, *argv, 0))
-				invarg("\"INDEX\" is invalid", *argv);
+				invarg("INDEX value is invalid", *argv);
 
 		} else if (strcmp(*argv, "ptype") == 0) {
 			if (ptypep)
@@ -639,7 +635,7 @@
 	}
 
 	if (!dirp) {
-		fprintf(stderr, "Not enough information: \"DIR\" is required.\n");
+		fprintf(stderr, "Not enough information: DIR is required.\n");
 		exit(1);
 	}
 	if (ptypep) {
@@ -647,7 +643,7 @@
 			  (void *)&upt, sizeof(upt));
 	}
 	if (!selp && !indexp) {
-		fprintf(stderr, "Not enough information: either \"SELECTOR\" or \"INDEX\" is required.\n");
+		fprintf(stderr, "Not enough information: either SELECTOR or INDEX is required.\n");
 		exit(1);
 	}
 	if (selp && indexp)
@@ -789,7 +785,7 @@
 		} else if (strcmp(*argv, "index") == 0) {
 			NEXT_ARG();
 			if (get_u32(&filter.xpinfo.index, *argv, 0))
-				invarg("\"INDEX\" is invalid", *argv);
+				invarg("INDEX value is invalid", *argv);
 
 			filter.index_mask = XFRM_FILTER_MASK_FULL;
 
@@ -806,14 +802,14 @@
 			else if (strcmp(*argv, "block") == 0)
 				filter.xpinfo.action = XFRM_POLICY_BLOCK;
 			else
-				invarg("\"ACTION\" is invalid\n", *argv);
+				invarg("ACTION value is invalid\n", *argv);
 
 			filter.action_mask = XFRM_FILTER_MASK_FULL;
 
 		} else if (strcmp(*argv, "priority") == 0) {
 			NEXT_ARG();
 			if (get_u32(&filter.xpinfo.priority, *argv, 0))
-				invarg("\"PRIORITY\" is invalid", *argv);
+				invarg("PRIORITY value is invalid", *argv);
 
 			filter.priority_mask = XFRM_FILTER_MASK_FULL;
 
@@ -899,7 +895,7 @@
 	exit(0);
 }
 
-int print_spdinfo( struct nlmsghdr *n, void *arg)
+static int print_spdinfo( struct nlmsghdr *n, void *arg)
 {
 	FILE *fp = (FILE*)arg;
 	__u32 *f = NLMSG_DATA(n);
@@ -965,7 +961,7 @@
 	struct {
 		struct nlmsghdr			n;
 		__u32				flags;
-		char 				ans[128];
+		char				ans[128];
 	} req;
 
 	memset(&req, 0, sizeof(req));
diff --git a/ip/xfrm_state.c b/ip/xfrm_state.c
index 57f0333..2ad3d8d 100644
--- a/ip/xfrm_state.c
+++ b/ip/xfrm_state.c
@@ -14,8 +14,7 @@
  * 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  */
 /*
  * based on iproute.c
@@ -29,11 +28,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <netdb.h>
-#include <endian.h>
 #include <linux/xfrm.h>
-#include <linux/in.h>
-#include <linux/in6.h>
-
 #include "utils.h"
 #include "xfrm.h"
 #include "ip_common.h"
@@ -63,8 +58,9 @@
 	fprintf(stderr, "Usage: ip xfrm state { add | update } ID [ ALGO-LIST ] [ mode MODE ]\n");
 	fprintf(stderr, "        [ mark MARK [ mask MASK ] ] [ reqid REQID ] [ seq SEQ ]\n");
 	fprintf(stderr, "        [ replay-window SIZE ] [ replay-seq SEQ ] [ replay-oseq SEQ ]\n");
+	fprintf(stderr, "        [ replay-seq-hi SEQ ] [ replay-oseq-hi SEQ ]\n");
 	fprintf(stderr, "        [ flag FLAG-LIST ] [ sel SELECTOR ] [ LIMIT-LIST ] [ encap ENCAP ]\n");
-	fprintf(stderr, "        [ coa ADDR[/PLEN] ] [ ctx CTX ]\n");
+	fprintf(stderr, "        [ coa ADDR[/PLEN] ] [ ctx CTX ] [ extra-flag EXTRA-FLAG-LIST ]\n");
 	fprintf(stderr, "Usage: ip xfrm state allocspi ID [ mode MODE ] [ mark MARK [ mask MASK ] ]\n");
 	fprintf(stderr, "        [ reqid REQID ] [ seq SEQ ] [ min SPI max SPI ]\n");
 	fprintf(stderr, "Usage: ip xfrm state { delete | get } ID [ mark MARK [ mask MASK ] ]\n");
@@ -82,16 +78,19 @@
 	fprintf(stderr, "ALGO-LIST := [ ALGO-LIST ] ALGO\n");
 	fprintf(stderr, "ALGO := { ");
 	fprintf(stderr, "%s | ", strxf_algotype(XFRMA_ALG_CRYPT));
-	fprintf(stderr, "%s | ", strxf_algotype(XFRMA_ALG_AUTH));
-	fprintf(stderr, "%s", strxf_algotype(XFRMA_ALG_COMP));
-	fprintf(stderr, " } ALGO-NAME ALGO-KEY |\n");
-	fprintf(stderr, "        %s", strxf_algotype(XFRMA_ALG_AEAD));
-	fprintf(stderr, " ALGO-NAME ALGO-KEY ALGO-ICV-LEN |\n");
+	fprintf(stderr, "%s", strxf_algotype(XFRMA_ALG_AUTH));
+	fprintf(stderr, " } ALGO-NAME ALGO-KEYMAT |\n");
 	fprintf(stderr, "        %s", strxf_algotype(XFRMA_ALG_AUTH_TRUNC));
-	fprintf(stderr, " ALGO-NAME ALGO-KEY ALGO-TRUNC-LEN\n");
- 	fprintf(stderr, "MODE := transport | tunnel | ro | in_trigger | beet\n");
+	fprintf(stderr, " ALGO-NAME ALGO-KEYMAT ALGO-TRUNC-LEN |\n");
+	fprintf(stderr, "        %s", strxf_algotype(XFRMA_ALG_AEAD));
+	fprintf(stderr, " ALGO-NAME ALGO-KEYMAT ALGO-ICV-LEN |\n");
+	fprintf(stderr, "        %s", strxf_algotype(XFRMA_ALG_COMP));
+	fprintf(stderr, " ALGO-NAME\n");
+	fprintf(stderr, "MODE := transport | tunnel | beet | ro | in_trigger\n");
 	fprintf(stderr, "FLAG-LIST := [ FLAG-LIST ] FLAG\n");
-	fprintf(stderr, "FLAG := noecn | decap-dscp | nopmtudisc | wildrecv | icmp | af-unspec | align4\n");
+	fprintf(stderr, "FLAG := noecn | decap-dscp | nopmtudisc | wildrecv | icmp | af-unspec | align4 | esn\n");
+	fprintf(stderr, "EXTRA-FLAG-LIST := [ EXTRA-FLAG-LIST ] EXTRA-FLAG\n");
+	fprintf(stderr, "EXTRA-FLAG := dont-encap-dscp\n");
 	fprintf(stderr, "SELECTOR := [ src ADDR[/PLEN] ] [ dst ADDR[/PLEN] ] [ dev DEV ] [ UPSPEC ]\n");
 	fprintf(stderr, "UPSPEC := proto { { ");
 	fprintf(stderr, "%s | ", strxf_proto(IPPROTO_TCP));
@@ -122,7 +121,7 @@
 
 #if 0
 	/* XXX: verifying both name and key is required! */
-	fprintf(stderr, "warning: ALGO-NAME/ALGO-KEY will send to kernel promiscuously! (verifying them isn't implemented yet)\n");
+	fprintf(stderr, "warning: ALGO-NAME/ALGO-KEYMAT values will be sent to the kernel promiscuously! (verifying them isn't implemented yet)\n");
 #endif
 
 	strncpy(alg->alg_name, name, sizeof(alg->alg_name));
@@ -142,7 +141,7 @@
 		/* calculate length of the converted values(real key) */
 		len = (plen + 1) / 2;
 		if (len > max)
-			invarg("\"ALGO-KEY\" makes buffer overflow\n", key);
+			invarg("ALGO-KEYMAT value makes buffer overflow\n", key);
 
 		for (i = - (plen % 2), j = 0; j < len; i += 2, j++) {
 			char vbuf[3];
@@ -153,7 +152,7 @@
 			vbuf[2] = '\0';
 
 			if (get_u8(&val, vbuf, 16))
-				invarg("\"ALGO-KEY\" is invalid", key);
+				invarg("ALGO-KEYMAT value is invalid", key);
 
 			buf[j] = val;
 		}
@@ -161,9 +160,9 @@
 		len = slen;
 		if (len > 0) {
 			if (len > max)
-				invarg("\"ALGO-KEY\" makes buffer overflow\n", key);
+				invarg("ALGO-KEYMAT value makes buffer overflow\n", key);
 
-			strncpy(buf, key, len);
+			memcpy(buf, key, len);
 		}
 	}
 
@@ -178,7 +177,7 @@
 	char **argv = *argvp;
 
 	if (get_u32(seq, *argv, 0))
-		invarg("\"SEQ\" is invalid", *argv);
+		invarg("SEQ value is invalid", *argv);
 
 	*seq = htonl(*seq);
 
@@ -198,7 +197,7 @@
 		__u8 val = 0;
 
 		if (get_u8(&val, *argv, 16))
-			invarg("\"FLAG\" is invalid", *argv);
+			invarg("FLAG value is invalid", *argv);
 		*flags = val;
 	} else {
 		while (1) {
@@ -216,6 +215,41 @@
 				*flags |= XFRM_STATE_AF_UNSPEC;
 			else if (strcmp(*argv, "align4") == 0)
 				*flags |= XFRM_STATE_ALIGN4;
+			else if (strcmp(*argv, "esn") == 0)
+				*flags |= XFRM_STATE_ESN;
+			else {
+				PREV_ARG(); /* back track */
+				break;
+			}
+
+			if (!NEXT_ARG_OK())
+				break;
+			NEXT_ARG();
+		}
+	}
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static int xfrm_state_extra_flag_parse(__u32 *extra_flags, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int len = strlen(*argv);
+
+	if (len > 2 && strncmp(*argv, "0x", 2) == 0) {
+		__u32 val = 0;
+
+		if (get_u32(&val, *argv, 16))
+			invarg("\"EXTRA-FLAG\" is invalid", *argv);
+		*extra_flags = val;
+	} else {
+		while (1) {
+			if (strcmp(*argv, "dont-encap-dscp") == 0)
+				*extra_flags |= XFRM_SA_XFLAG_DONT_ENCAP_DSCP;
 			else {
 				PREV_ARG(); /* back track */
 				break;
@@ -237,11 +271,14 @@
 {
 	struct rtnl_handle rth;
 	struct {
-		struct nlmsghdr 	n;
+		struct nlmsghdr	n;
 		struct xfrm_usersa_info xsinfo;
-		char   			buf[RTA_BUF_SIZE];
+		char  			buf[RTA_BUF_SIZE];
 	} req;
 	struct xfrm_replay_state replay;
+	struct xfrm_replay_state_esn replay_esn;
+	__u32 replay_window = 0;
+	__u32 seq = 0, oseq = 0, seq_hi = 0, oseq_hi = 0;
 	char *idp = NULL;
 	char *aeadop = NULL;
 	char *ealgop = NULL;
@@ -249,6 +286,7 @@
 	char *calgop = NULL;
 	char *coap = NULL;
 	char *sctxp = NULL;
+	__u32 extra_flags = 0;
 	struct xfrm_mark mark = {0, 0};
 	struct {
 		struct xfrm_user_sec_ctx sctx;
@@ -257,6 +295,7 @@
 
 	memset(&req, 0, sizeof(req));
 	memset(&replay, 0, sizeof(replay));
+	memset(&replay_esn, 0, sizeof(replay_esn));
 	memset(&ctx, 0, sizeof(ctx));
 
 	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xsinfo));
@@ -283,22 +322,35 @@
 			xfrm_seq_parse(&req.xsinfo.seq, &argc, &argv);
 		} else if (strcmp(*argv, "replay-window") == 0) {
 			NEXT_ARG();
-			if (get_u8(&req.xsinfo.replay_window, *argv, 0))
-				invarg("\"replay-window\" value is invalid", *argv);
+			if (get_u32(&replay_window, *argv, 0))
+				invarg("value after \"replay-window\" is invalid", *argv);
 		} else if (strcmp(*argv, "replay-seq") == 0) {
 			NEXT_ARG();
-			if (get_u32(&replay.seq, *argv, 0))
-				invarg("\"replay-seq\" value is invalid", *argv);
+			if (get_u32(&seq, *argv, 0))
+				invarg("value after \"replay-seq\" is invalid", *argv);
+		} else if (strcmp(*argv, "replay-seq-hi") == 0) {
+			NEXT_ARG();
+			if (get_u32(&seq_hi, *argv, 0))
+				invarg("value after \"replay-seq-hi\" is invalid", *argv);
 		} else if (strcmp(*argv, "replay-oseq") == 0) {
 			NEXT_ARG();
-			if (get_u32(&replay.oseq, *argv, 0))
-				invarg("\"replay-oseq\" value is invalid", *argv);
+			if (get_u32(&oseq, *argv, 0))
+				invarg("value after \"replay-oseq\" is invalid", *argv);
+		} else if (strcmp(*argv, "replay-oseq-hi") == 0) {
+			NEXT_ARG();
+			if (get_u32(&oseq_hi, *argv, 0))
+				invarg("value after \"replay-oseq-hi\" is invalid", *argv);
 		} else if (strcmp(*argv, "flag") == 0) {
 			NEXT_ARG();
 			xfrm_state_flag_parse(&req.xsinfo.flags, &argc, &argv);
+		} else if (strcmp(*argv, "extra-flag") == 0) {
+			NEXT_ARG();
+			xfrm_state_extra_flag_parse(&extra_flags, &argc, &argv);
 		} else if (strcmp(*argv, "sel") == 0) {
 			NEXT_ARG();
+			preferred_family = AF_UNSPEC;
 			xfrm_selector_parse(&req.xsinfo.sel, &argc, &argv);
+			preferred_family = req.xsinfo.sel.family;
 		} else if (strcmp(*argv, "limit") == 0) {
 			NEXT_ARG();
 			xfrm_lifetime_cfg_parse(&req.xsinfo.lft, &argc, &argv);
@@ -309,11 +361,11 @@
 			xfrm_encap_type_parse(&encap.encap_type, &argc, &argv);
 			NEXT_ARG();
 			if (get_u16(&encap.encap_sport, *argv, 0))
-				invarg("\"encap\" sport value is invalid", *argv);
+				invarg("SPORT value after \"encap\" is invalid", *argv);
 			encap.encap_sport = htons(encap.encap_sport);
 			NEXT_ARG();
 			if (get_u16(&encap.encap_dport, *argv, 0))
-				invarg("\"encap\" dport value is invalid", *argv);
+				invarg("DPORT value after \"encap\" is invalid", *argv);
 			encap.encap_dport = htons(encap.encap_dport);
 			NEXT_ARG();
 			get_addr(&oa, *argv, AF_UNSPEC);
@@ -332,9 +384,9 @@
 
 			get_prefix(&coa, *argv, preferred_family);
 			if (coa.family == AF_UNSPEC)
-				invarg("\"coa\" address family is AF_UNSPEC", *argv);
+				invarg("value after \"coa\" has an unrecognized address family", *argv);
 			if (coa.bytelen > sizeof(xcoa))
-				invarg("\"coa\" address length is too large", *argv);
+				invarg("value after \"coa\" is too large", *argv);
 
 			memset(&xcoa, 0, sizeof(xcoa));
 			memcpy(&xcoa, &coa.data, coa.bytelen);
@@ -376,23 +428,23 @@
 				int len;
 				__u32 icvlen, trunclen;
 				char *name;
-				char *key;
+				char *key = "";
 				char *buf;
 
 				switch (type) {
 				case XFRMA_ALG_AEAD:
-					if (aeadop)
+					if (ealgop || aalgop || aeadop)
 						duparg("ALGO-TYPE", *argv);
 					aeadop = *argv;
 					break;
 				case XFRMA_ALG_CRYPT:
-					if (ealgop)
+					if (ealgop || aeadop)
 						duparg("ALGO-TYPE", *argv);
 					ealgop = *argv;
 					break;
 				case XFRMA_ALG_AUTH:
 				case XFRMA_ALG_AUTH_TRUNC:
-					if (aalgop)
+					if (aalgop || aeadop)
 						duparg("ALGO-TYPE", *argv);
 					aalgop = *argv;
 					break;
@@ -403,7 +455,7 @@
 					break;
 				default:
 					/* not reached */
-					invarg("\"ALGO-TYPE\" is invalid\n", *argv);
+					invarg("ALGO-TYPE value is invalid\n", *argv);
 				}
 
 				if (!NEXT_ARG_OK())
@@ -411,10 +463,17 @@
 				NEXT_ARG();
 				name = *argv;
 
-				if (!NEXT_ARG_OK())
-					missarg("ALGO-KEY");
-				NEXT_ARG();
-				key = *argv;
+				switch (type) {
+				case XFRMA_ALG_AEAD:
+				case XFRMA_ALG_CRYPT:
+				case XFRMA_ALG_AUTH:
+				case XFRMA_ALG_AUTH_TRUNC:
+					if (!NEXT_ARG_OK())
+						missarg("ALGO-KEYMAT");
+					NEXT_ARG();
+					key = *argv;
+					break;
+				}
 
 				buf = alg.u.alg.alg_key;
 				len = sizeof(alg.u.alg);
@@ -425,7 +484,7 @@
 						missarg("ALGO-ICV-LEN");
 					NEXT_ARG();
 					if (get_u32(&icvlen, *argv, 0))
-						invarg("\"aead\" ICV length is invalid",
+						invarg("ALGO-ICV-LEN value is invalid",
 						       *argv);
 					alg.u.aead.alg_icv_len = icvlen;
 
@@ -437,7 +496,7 @@
 						missarg("ALGO-TRUNC-LEN");
 					NEXT_ARG();
 					if (get_u32(&trunclen, *argv, 0))
-						invarg("\"auth\" trunc length is invalid",
+						invarg("ALGO-TRUNC-LEN value is invalid",
 						       *argv);
 					alg.u.auth.alg_trunc_len = trunclen;
 
@@ -470,16 +529,50 @@
 		argc--; argv++;
 	}
 
-	if (replay.seq || replay.oseq)
-		addattr_l(&req.n, sizeof(req.buf), XFRMA_REPLAY_VAL,
-			  (void *)&replay, sizeof(replay));
+	if (req.xsinfo.flags & XFRM_STATE_ESN &&
+	    replay_window == 0) {
+		fprintf(stderr, "Error: esn flag set without replay-window.\n");
+		exit(-1);
+	}
+
+	if (replay_window > XFRMA_REPLAY_ESN_MAX) {
+		fprintf(stderr,
+			"Error: replay-window (%u) > XFRMA_REPLAY_ESN_MAX (%u).\n",
+			replay_window, XFRMA_REPLAY_ESN_MAX);
+		exit(-1);
+	}
+
+	if (req.xsinfo.flags & XFRM_STATE_ESN ||
+	    replay_window > (sizeof(replay.bitmap) * 8)) {
+		replay_esn.seq = seq;
+		replay_esn.oseq = oseq;
+		replay_esn.seq_hi = seq_hi;
+		replay_esn.oseq_hi = oseq_hi;
+		replay_esn.replay_window = replay_window;
+		replay_esn.bmp_len = (replay_window + sizeof(__u32) * 8 - 1) /
+				     (sizeof(__u32) * 8);
+		addattr_l(&req.n, sizeof(req.buf), XFRMA_REPLAY_ESN_VAL,
+			  &replay_esn, sizeof(replay_esn));
+	} else {
+		if (seq || oseq) {
+			replay.seq = seq;
+			replay.oseq = oseq;
+			addattr_l(&req.n, sizeof(req.buf), XFRMA_REPLAY_VAL,
+				  &replay, sizeof(replay));
+		}
+		req.xsinfo.replay_window = replay_window;
+	}
+
+	if (extra_flags)
+		addattr32(&req.n, sizeof(req.buf), XFRMA_SA_EXTRA_FLAGS,
+			  extra_flags);
 
 	if (!idp) {
-		fprintf(stderr, "Not enough information: \"ID\" is required\n");
+		fprintf(stderr, "Not enough information: ID is required\n");
 		exit(1);
 	}
 
-	if (mark.m & mark.v) {
+	if (mark.m) {
 		int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
 				  (void *)&mark, sizeof(mark));
 		if (r < 0) {
@@ -488,58 +581,105 @@
 		}
 	}
 
-	switch (req.xsinfo.mode) {
-	case XFRM_MODE_TRANSPORT:
-	case XFRM_MODE_TUNNEL:
-		if (!xfrm_xfrmproto_is_ipsec(req.xsinfo.id.proto)) {
-			fprintf(stderr, "\"mode\" is invalid with proto=%s\n",
+	if (xfrm_xfrmproto_is_ipsec(req.xsinfo.id.proto)) {
+		switch (req.xsinfo.mode) {
+		case XFRM_MODE_TRANSPORT:
+		case XFRM_MODE_TUNNEL:
+			break;
+		case XFRM_MODE_BEET:
+			if (req.xsinfo.id.proto == IPPROTO_ESP)
+				break;
+		default:
+			fprintf(stderr, "MODE value is invalid with XFRM-PROTO value \"%s\"\n",
 				strxf_xfrmproto(req.xsinfo.id.proto));
 			exit(1);
 		}
-		break;
-	case XFRM_MODE_ROUTEOPTIMIZATION:
-	case XFRM_MODE_IN_TRIGGER:
-		if (!xfrm_xfrmproto_is_ro(req.xsinfo.id.proto)) {
-			fprintf(stderr, "\"mode\" is invalid with proto=%s\n",
+
+		switch (req.xsinfo.id.proto) {
+		case IPPROTO_ESP:
+			if (calgop) {
+				fprintf(stderr, "ALGO-TYPE value \"%s\" is invalid with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_COMP),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			if (!ealgop && !aeadop) {
+				fprintf(stderr, "ALGO-TYPE value \"%s\" or \"%s\" is required with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_CRYPT),
+					strxf_algotype(XFRMA_ALG_AEAD),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			break;
+		case IPPROTO_AH:
+			if (ealgop || aeadop || calgop) {
+				fprintf(stderr, "ALGO-TYPE values \"%s\", \"%s\", and \"%s\" are invalid with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_CRYPT),
+					strxf_algotype(XFRMA_ALG_AEAD),
+					strxf_algotype(XFRMA_ALG_COMP),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			if (!aalgop) {
+				fprintf(stderr, "ALGO-TYPE value \"%s\" or \"%s\" is required with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_AUTH),
+					strxf_algotype(XFRMA_ALG_AUTH_TRUNC),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			break;
+		case IPPROTO_COMP:
+			if (ealgop || aalgop || aeadop) {
+				fprintf(stderr, "ALGO-TYPE values \"%s\", \"%s\", \"%s\", and \"%s\" are invalid with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_CRYPT),
+					strxf_algotype(XFRMA_ALG_AUTH),
+					strxf_algotype(XFRMA_ALG_AUTH_TRUNC),
+					strxf_algotype(XFRMA_ALG_AEAD),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			if (!calgop) {
+				fprintf(stderr, "ALGO-TYPE value \"%s\" is required with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_COMP),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			break;
+		}
+	} else {
+		if (ealgop || aalgop || aeadop || calgop) {
+			fprintf(stderr, "ALGO is invalid with XFRM-PROTO value \"%s\"\n",
 				strxf_xfrmproto(req.xsinfo.id.proto));
 			exit(1);
 		}
-		if (req.xsinfo.id.spi != 0) {
-			fprintf(stderr, "\"spi\" must be 0 with proto=%s\n",
-				strxf_xfrmproto(req.xsinfo.id.proto));
-			exit(1);
-		}
-		break;
-	default:
-		break;
 	}
 
-	if (aeadop || ealgop || aalgop || calgop) {
-		if (!xfrm_xfrmproto_is_ipsec(req.xsinfo.id.proto)) {
-			fprintf(stderr, "\"ALGO\" is invalid with proto=%s\n",
+	if (xfrm_xfrmproto_is_ro(req.xsinfo.id.proto)) {
+		switch (req.xsinfo.mode) {
+		case XFRM_MODE_ROUTEOPTIMIZATION:
+		case XFRM_MODE_IN_TRIGGER:
+			break;
+		case 0:
+			fprintf(stderr, "\"mode\" is required with XFRM-PROTO value \"%s\"\n",
+				strxf_xfrmproto(req.xsinfo.id.proto));
+			exit(1);
+		default:
+			fprintf(stderr, "MODE value is invalid with XFRM-PROTO value \"%s\"\n",
+				strxf_xfrmproto(req.xsinfo.id.proto));
+			exit(1);
+		}
+
+		if (!coap) {
+			fprintf(stderr, "\"coa\" is required with XFRM-PROTO value \"%s\"\n",
 				strxf_xfrmproto(req.xsinfo.id.proto));
 			exit(1);
 		}
 	} else {
-		if (xfrm_xfrmproto_is_ipsec(req.xsinfo.id.proto)) {
-			fprintf(stderr, "\"ALGO\" is required with proto=%s\n",
-				strxf_xfrmproto(req.xsinfo.id.proto));
-			exit (1);
-		}
-	}
-
-	if (coap) {
-		if (!xfrm_xfrmproto_is_ro(req.xsinfo.id.proto)) {
-			fprintf(stderr, "\"coa\" is invalid with proto=%s\n",
+		if (coap) {
+			fprintf(stderr, "\"coa\" is invalid with XFRM-PROTO value \"%s\"\n",
 				strxf_xfrmproto(req.xsinfo.id.proto));
 			exit(1);
 		}
-	} else {
-		if (xfrm_xfrmproto_is_ro(req.xsinfo.id.proto)) {
-			fprintf(stderr, "\"coa\" is required with proto=%s\n",
-				strxf_xfrmproto(req.xsinfo.id.proto));
-			exit (1);
-		}
 	}
 
 	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
@@ -560,9 +700,9 @@
 {
 	struct rtnl_handle rth;
 	struct {
-		struct nlmsghdr 	n;
+		struct nlmsghdr	n;
 		struct xfrm_userspi_info xspi;
-		char   			buf[RTA_BUF_SIZE];
+		char  			buf[RTA_BUF_SIZE];
 	} req;
 	char *idp = NULL;
 	char *minp = NULL;
@@ -607,7 +747,7 @@
 			NEXT_ARG();
 
 			if (get_u32(&req.xspi.min, *argv, 0))
-				invarg("\"min\" value is invalid", *argv);
+				invarg("value after \"min\" is invalid", *argv);
 		} else if (strcmp(*argv, "max") == 0) {
 			if (maxp)
 				duparg("max", *argv);
@@ -616,7 +756,7 @@
 			NEXT_ARG();
 
 			if (get_u32(&req.xspi.max, *argv, 0))
-				invarg("\"max\" value is invalid", *argv);
+				invarg("value after \"max\" is invalid", *argv);
 		} else {
 			/* try to assume ID */
 			if (idp)
@@ -627,7 +767,7 @@
 			xfrm_id_parse(&req.xspi.info.saddr, &req.xspi.info.id,
 				      &req.xspi.info.family, 0, &argc, &argv);
 			if (req.xspi.info.id.spi) {
-				fprintf(stderr, "\"SPI\" must be zero\n");
+				fprintf(stderr, "\"spi\" is invalid\n");
 				exit(1);
 			}
 			if (preferred_family == AF_UNSPEC)
@@ -637,7 +777,7 @@
 	}
 
 	if (!idp) {
-		fprintf(stderr, "Not enough information: \"ID\" is required\n");
+		fprintf(stderr, "Not enough information: ID is required\n");
 		exit(1);
 	}
 
@@ -647,7 +787,7 @@
 			exit(1);
 		}
 		if (req.xspi.min > req.xspi.max) {
-			fprintf(stderr, "\"min\" value is larger than \"max\" value\n");
+			fprintf(stderr, "value after \"min\" is larger than value after \"max\"\n");
 			exit(1);
 		}
 	} else {
@@ -817,9 +957,9 @@
 {
 	struct rtnl_handle rth;
 	struct {
-		struct nlmsghdr 	n;
+		struct nlmsghdr	n;
 		struct xfrm_usersa_id	xsid;
-		char   			buf[RTA_BUF_SIZE];
+		char  			buf[RTA_BUF_SIZE];
 	} req;
 	struct xfrm_id id;
 	char *idp = NULL;
@@ -1041,7 +1181,30 @@
 		}
 
 	} else {
-		if (rtnl_wilddump_request(&rth, preferred_family, XFRM_MSG_GETSA) < 0) {
+		struct xfrm_address_filter addrfilter = {
+			.saddr = filter.xsinfo.saddr,
+			.daddr = filter.xsinfo.id.daddr,
+			.family = filter.xsinfo.family,
+			.splen = filter.id_src_mask,
+			.dplen = filter.id_dst_mask,
+		};
+		struct {
+			struct nlmsghdr n;
+			char buf[NLMSG_BUF_SIZE];
+		} req = {
+			.n.nlmsg_len = NLMSG_HDRLEN,
+			.n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+			.n.nlmsg_type = XFRM_MSG_GETSA,
+			.n.nlmsg_seq = rth.dump = ++rth.seq,
+		};
+
+		if (filter.xsinfo.id.proto)
+			addattr8(&req.n, sizeof(req), XFRMA_PROTO,
+				 filter.xsinfo.id.proto);
+		addattr_l(&req.n, sizeof(req), XFRMA_ADDRESS_FILTER,
+			  &addrfilter, sizeof(addrfilter));
+
+		if (rtnl_send(&rth, (void *)&req, req.n.nlmsg_len) < 0) {
 			perror("Cannot send dump request");
 			exit(1);
 		}
@@ -1057,7 +1220,7 @@
 	exit(0);
 }
 
-int print_sadinfo(struct nlmsghdr *n, void *arg)
+static int print_sadinfo(struct nlmsghdr *n, void *arg)
 {
 	FILE *fp = (FILE*)arg;
 	__u32 *f = NLMSG_DATA(n);
@@ -1093,7 +1256,7 @@
 				fprintf(fp,"BAD SAD length returned\n");
 				return -1;
 			}
-				
+
 			si = RTA_DATA(tb[XFRMA_SAD_HINFO]);
 			fprintf(fp," (buckets ");
 			fprintf(fp,"count %d", si->sadhcnt);
@@ -1162,7 +1325,7 @@
 
 			ret = xfrm_xfrmproto_getbyname(*argv);
 			if (ret < 0)
-				invarg("\"XFRM-PROTO\" is invalid", *argv);
+				invarg("XFRM-PROTO value is invalid", *argv);
 
 			req.xsf.proto = (__u8)ret;
 		} else
@@ -1175,7 +1338,7 @@
 		exit(1);
 
 	if (show_stats > 1)
-		fprintf(stderr, "Flush state proto=%s\n",
+		fprintf(stderr, "Flush state with XFRM-PROTO value \"%s\"\n",
 			strxf_xfrmproto(req.xsf.proto));
 
 	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
diff --git a/lib/Android.mk b/lib/Android.mk
index f0b12ad..73a9f78 100644
--- a/lib/Android.mk
+++ b/lib/Android.mk
@@ -1,9 +1,9 @@
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-# clang cannot compile 'variable length array in structure' in ipxfrm.c
-LOCAL_CLANG := false
-LOCAL_SRC_FILES := utils.c rt_names.c ll_types.c ll_proto.c ll_addr.c inet_proto.c
+LOCAL_SRC_FILES := \
+    utils.c rt_names.c ll_types.c ll_proto.c ll_addr.c inet_proto.c \
+    namespace.c names.c libgenl.c libnetlink.c
 LOCAL_MODULE := libiprouteutil
 LOCAL_SYSTEM_SHARED_LIBRARIES := libc
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include
@@ -30,6 +30,8 @@
 	-DHAVE_EXT2_IOCTLS \
 	-DHAVE_LINUX_FD_H \
 	-DHAVE_TYPE_SSIZE_T \
+	-DHAVE_SETNS \
+	-D_GNU_SOURCE \
 	-Wno-pointer-arith \
 	-Wno-sign-compare \
 	-Wno-unused-parameter \
@@ -41,8 +43,6 @@
 include $(BUILD_SHARED_LIBRARY)
 
 include $(CLEAR_VARS)
-# clang cannot compile 'variable length array in structure' in ipxfrm.c
-LOCAL_CLANG := false
 LOCAL_SRC_FILES := ll_map.c libnetlink.c
 LOCAL_MODULE := libnetlink
 LOCAL_SYSTEM_SHARED_LIBRARIES := libc
diff --git a/lib/Makefile b/lib/Makefile
index da2f0fc..4c7cbc2 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,8 +1,15 @@
+include ../Config
+
+ifeq ($(IP_CONFIG_SETNS),y)
+	CFLAGS += -DHAVE_SETNS
+endif
+
 CFLAGS += -fPIC
 
-UTILOBJ=utils.o rt_names.o ll_types.o ll_proto.o ll_addr.o inet_proto.o
+UTILOBJ=utils.o rt_names.o ll_types.o ll_proto.o ll_addr.o inet_proto.o namespace.o \
+	names.o
 
-NLOBJ=ll_map.o libnetlink.o
+NLOBJ=libgenl.o ll_map.o libnetlink.o
 
 all: libnetlink.a libutil.a
 
diff --git a/lib/inet_proto.c b/lib/inet_proto.c
index a55e0e7..57a8351 100644
--- a/lib/inet_proto.c
+++ b/lib/inet_proto.c
@@ -20,9 +20,10 @@
 #include <netdb.h>
 #include <string.h>
 
+#include "rt_names.h"
 #include "utils.h"
 
-char *inet_proto_n2a(int proto, char *buf, int len)
+const char *inet_proto_n2a(int proto, char *buf, int len)
 {
 	static char ncache[16];
 	static int icache = -1;
@@ -42,7 +43,7 @@
 	return buf;
 }
 
-int inet_proto_a2n(char *buf)
+int inet_proto_a2n(const char *buf)
 {
 	static char ncache[16];
 	static int icache = -1;
diff --git a/lib/ipx_ntop.c b/lib/ipx_ntop.c
index 7b6d728..1e46bc2 100644
--- a/lib/ipx_ntop.c
+++ b/lib/ipx_ntop.c
@@ -1,5 +1,6 @@
 #include <errno.h>
 #include <sys/types.h>
+#include <sys/socket.h>
 #include <netinet/in.h>
 
 #include "utils.h"
diff --git a/lib/ipx_pton.c b/lib/ipx_pton.c
index 1a52b7f..3dca271 100644
--- a/lib/ipx_pton.c
+++ b/lib/ipx_pton.c
@@ -1,6 +1,7 @@
 #include <errno.h>
 #include <string.h>
 #include <sys/types.h>
+#include <sys/socket.h>
 #include <netinet/in.h>
 
 #include "utils.h"
diff --git a/lib/libgenl.c b/lib/libgenl.c
new file mode 100644
index 0000000..ef3e5db
--- /dev/null
+++ b/lib/libgenl.c
@@ -0,0 +1,63 @@
+/*
+ * libgenl.c	GENL library
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <linux/genetlink.h>
+#include "libgenl.h"
+
+static int genl_parse_getfamily(struct nlmsghdr *nlh)
+{
+	struct rtattr *tb[CTRL_ATTR_MAX + 1];
+	struct genlmsghdr *ghdr = NLMSG_DATA(nlh);
+	int len = nlh->nlmsg_len;
+	struct rtattr *attrs;
+
+	if (nlh->nlmsg_type != GENL_ID_CTRL) {
+		fprintf(stderr, "Not a controller message, nlmsg_len=%d "
+			"nlmsg_type=0x%x\n", nlh->nlmsg_len, nlh->nlmsg_type);
+		return -1;
+	}
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+
+	if (len < 0) {
+		fprintf(stderr, "wrong controller message len %d\n", len);
+		return -1;
+	}
+
+	if (ghdr->cmd != CTRL_CMD_NEWFAMILY) {
+		fprintf(stderr, "Unknown controller command %d\n", ghdr->cmd);
+		return -1;
+	}
+
+	attrs = (struct rtattr *) ((char *) ghdr + GENL_HDRLEN);
+	parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len);
+
+	if (tb[CTRL_ATTR_FAMILY_ID] == NULL) {
+		fprintf(stderr, "Missing family id TLV\n");
+		return -1;
+	}
+
+	return rta_getattr_u16(tb[CTRL_ATTR_FAMILY_ID]);
+}
+
+int genl_resolve_family(struct rtnl_handle *grth, const char *family)
+{
+	GENL_REQUEST(req, 1024, GENL_ID_CTRL, 0, 0, CTRL_CMD_GETFAMILY,
+		     NLM_F_REQUEST);
+
+	addattr_l(&req.n, sizeof(req), CTRL_ATTR_FAMILY_NAME,
+		  family, strlen(family) + 1);
+
+	if (rtnl_talk(grth, &req.n, 0, 0, &req.n) < 0) {
+		fprintf(stderr, "Error talking to the kernel\n");
+		return -2;
+	}
+
+	return genl_parse_getfamily(&req.n);
+}
+
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index 878911e..77e07ef 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -43,7 +43,8 @@
 
 	memset(rth, 0, sizeof(*rth));
 
-	rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol);
+	rth->proto = protocol;
+	rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
 	if (rth->fd < 0) {
 		perror("Cannot open netlink socket");
 		return -1;
@@ -91,11 +92,17 @@
 
 int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type)
 {
+	return rtnl_wilddump_req_filter(rth, family, type, RTEXT_FILTER_VF);
+}
+
+int rtnl_wilddump_req_filter(struct rtnl_handle *rth, int family, int type,
+			    __u32 filt_mask)
+{
 	struct {
 		struct nlmsghdr nlh;
-		struct rtgenmsg g;
-		__u16 align_rta;	/* attribute has to be 32bit aligned */
-		struct rtattr ext_req;
+		struct ifinfomsg ifm;
+		/* attribute has to be NLMSG aligned */
+		struct rtattr ext_req __attribute__ ((aligned(NLMSG_ALIGNTO)));
 		__u32 ext_filter_mask;
 	} req;
 
@@ -105,11 +112,11 @@
 	req.nlh.nlmsg_flags = NLM_F_DUMP|NLM_F_REQUEST;
 	req.nlh.nlmsg_pid = 0;
 	req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
-	req.g.rtgen_family = family;
+	req.ifm.ifi_family = family;
 
 	req.ext_req.rta_type = IFLA_EXT_MASK;
 	req.ext_req.rta_len = RTA_LENGTH(sizeof(__u32));
-	req.ext_filter_mask = RTEXT_FILTER_VF;
+	req.ext_filter_mask = filt_mask;
 
 	return send(rth->fd, (void*)&req, sizeof(req), 0);
 }
@@ -188,6 +195,7 @@
 		.msg_iovlen = 1,
 	};
 	char buf[16384];
+	int dump_intr = 0;
 
 	iov.iov_base = buf;
 	while (1) {
@@ -212,18 +220,24 @@
 			return -1;
 		}
 
+		if (rth->dump_fp)
+			fwrite(buf, 1, NLMSG_ALIGN(status), rth->dump_fp);
+
 		for (a = arg; a->filter; a++) {
 			struct nlmsghdr *h = (struct nlmsghdr*)buf;
 			msglen = status;
 
 			while (NLMSG_OK(h, msglen)) {
-				int err;
+				int err = 0;
 
 				if (nladdr.nl_pid != 0 ||
 				    h->nlmsg_pid != rth->local.nl_pid ||
 				    h->nlmsg_seq != rth->dump)
 					goto skip_it;
 
+				if (h->nlmsg_flags & NLM_F_DUMP_INTR)
+					dump_intr = 1;
+
 				if (h->nlmsg_type == NLMSG_DONE) {
 					found_done = 1;
 					break; /* process next filter */
@@ -235,21 +249,33 @@
 							"ERROR truncated\n");
 					} else {
 						errno = -err->error;
+						if (rth->proto == NETLINK_SOCK_DIAG &&
+						    (errno == ENOENT ||
+						     errno == EOPNOTSUPP))
+							return -1;
+
 						perror("RTNETLINK answers");
 					}
 					return -1;
 				}
-				err = a->filter(&nladdr, h, a->arg1);
-				if (err < 0)
-					return err;
+
+				if (!rth->dump_fp) {
+					err = a->filter(&nladdr, h, a->arg1);
+					if (err < 0)
+						return err;
+				}
 
 skip_it:
 				h = NLMSG_NEXT(h, msglen);
 			}
 		}
 
-		if (found_done)
+		if (found_done) {
+			if (dump_intr)
+				fprintf(stderr,
+					"Dump was interrupted and may be inconsistent.\n");
 			return 0;
+		}
 
 		if (msg.msg_flags & MSG_TRUNC) {
 			fprintf(stderr, "Message truncated\n");
@@ -360,13 +386,14 @@
 				if (l < sizeof(struct nlmsgerr)) {
 					fprintf(stderr, "ERROR truncated\n");
 				} else {
-					errno = -err->error;
-					if (errno == 0) {
+					if (!err->error) {
 						if (answer)
 							memcpy(answer, h, h->nlmsg_len);
 						return 0;
 					}
-					perror("RTNETLINK answers");
+
+					fprintf(stderr, "RTNETLINK answers: %s\n", strerror(-err->error));
+					errno = -err->error;
 				}
 				return -1;
 			}
@@ -405,7 +432,7 @@
 		.msg_iov = &iov,
 		.msg_iovlen = 1,
 	};
-	char   buf[8192];
+	char   buf[16384];
 
 	memset(&nladdr, 0, sizeof(nladdr));
 	nladdr.nl_family = AF_NETLINK;
@@ -471,7 +498,7 @@
 {
 	int status;
 	struct sockaddr_nl nladdr;
-	char   buf[8192];
+	char   buf[16384];
 	struct nlmsghdr *h = (void*)buf;
 
 	memset(&nladdr, 0, sizeof(nladdr));
@@ -651,10 +678,19 @@
 
 int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
 {
+	return parse_rtattr_flags(tb, max, rta, len, 0);
+}
+
+int parse_rtattr_flags(struct rtattr *tb[], int max, struct rtattr *rta,
+		       int len, unsigned short flags)
+{
+	unsigned short type;
+
 	memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
 	while (RTA_OK(rta, len)) {
-		if ((rta->rta_type <= max) && (!tb[rta->rta_type]))
-			tb[rta->rta_type] = rta;
+		type = rta->rta_type & ~flags;
+		if ((type <= max) && (!tb[type]))
+			tb[type] = rta;
 		rta = RTA_NEXT(rta,len);
 	}
 	if (len)
@@ -677,6 +713,18 @@
 	return i;
 }
 
+struct rtattr *parse_rtattr_one(int type, struct rtattr *rta, int len)
+{
+	while (RTA_OK(rta, len)) {
+		if (rta->rta_type == type)
+			return rta;
+		rta = RTA_NEXT(rta, len);
+	}
+	if (len)
+		fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, rta->rta_len);
+	return NULL;
+}
+
 int __parse_rtattr_nested_compat(struct rtattr *tb[], int max, struct rtattr *rta,
 			         int len)
 {
diff --git a/lib/ll_addr.c b/lib/ll_addr.c
index f558050..c12ab07 100644
--- a/lib/ll_addr.c
+++ b/lib/ll_addr.c
@@ -57,7 +57,7 @@
 }
 
 /*NB: lladdr is char * (rather than u8 *) because sa_data is char * (1003.1g) */
-int ll_addr_a2n(char *lladdr, int len, char *arg)
+int ll_addr_a2n(char *lladdr, int len, const char *arg)
 {
 	if (strchr(arg, '.')) {
 		inet_prefix pfx;
diff --git a/lib/ll_map.c b/lib/ll_map.c
index 1ca781e..db34a2a 100644
--- a/lib/ll_map.c
+++ b/lib/ll_map.c
@@ -18,78 +18,123 @@
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <string.h>
-#include <linux/if.h>
+#include <net/if.h>
 
 #include "libnetlink.h"
 #include "ll_map.h"
+#include "hlist.h"
 
-extern unsigned int if_nametoindex (const char *);
-
-struct ll_cache
-{
-	struct ll_cache   *idx_next;
+struct ll_cache {
+	struct hlist_node idx_hash;
+	struct hlist_node name_hash;
 	unsigned	flags;
-	int		index;
+	unsigned 	index;
 	unsigned short	type;
-	unsigned short	alen;
 	char		name[IFNAMSIZ];
-	unsigned char	addr[20];
 };
 
 #define IDXMAP_SIZE	1024
-static struct ll_cache *idx_head[IDXMAP_SIZE];
+static struct hlist_head idx_head[IDXMAP_SIZE];
+static struct hlist_head name_head[IDXMAP_SIZE];
 
-static inline struct ll_cache *idxhead(int idx)
+static struct ll_cache *ll_get_by_index(unsigned index)
 {
-	return idx_head[idx & (IDXMAP_SIZE - 1)];
+	struct hlist_node *n;
+	unsigned h = index & (IDXMAP_SIZE - 1);
+
+	hlist_for_each(n, &idx_head[h]) {
+		struct ll_cache *im
+			= container_of(n, struct ll_cache, idx_hash);
+		if (im->index == index)
+			return im;
+	}
+
+	return NULL;
+}
+
+static unsigned namehash(const char *str)
+{
+	unsigned hash = 5381;
+
+	while (*str)
+		hash = ((hash << 5) + hash) + *str++; /* hash * 33 + c */
+
+	return hash;
+}
+
+static struct ll_cache *ll_get_by_name(const char *name)
+{
+	struct hlist_node *n;
+	unsigned h = namehash(name) & (IDXMAP_SIZE - 1);
+
+	hlist_for_each(n, &name_head[h]) {
+		struct ll_cache *im
+			= container_of(n, struct ll_cache, name_hash);
+
+		if (strncmp(im->name, name, IFNAMSIZ) == 0)
+			return im;
+	}
+
+	return NULL;
 }
 
 int ll_remember_index(const struct sockaddr_nl *who,
 		      struct nlmsghdr *n, void *arg)
 {
-	int h;
+	unsigned int h;
+	const char *ifname;
 	struct ifinfomsg *ifi = NLMSG_DATA(n);
-	struct ll_cache *im, **imp;
+	struct ll_cache *im;
 	struct rtattr *tb[IFLA_MAX+1];
 
-	if (n->nlmsg_type != RTM_NEWLINK)
+	if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
 		return 0;
 
 	if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifi)))
 		return -1;
 
+	im = ll_get_by_index(ifi->ifi_index);
+	if (n->nlmsg_type == RTM_DELLINK) {
+		if (im) {
+			hlist_del(&im->name_hash);
+			hlist_del(&im->idx_hash);
+			free(im);
+		}
+		return 0;
+	}
+
 	memset(tb, 0, sizeof(tb));
 	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n));
-	if (tb[IFLA_IFNAME] == NULL)
+	ifname = rta_getattr_str(tb[IFLA_IFNAME]);
+	if (ifname == NULL)
 		return 0;
 
-	h = ifi->ifi_index & (IDXMAP_SIZE - 1);
-	for (imp = &idx_head[h]; (im=*imp)!=NULL; imp = &im->idx_next)
-		if (im->index == ifi->ifi_index)
-			break;
+	if (im) {
+		/* change to existing entry */
+		if (strcmp(im->name, ifname) != 0) {
+			hlist_del(&im->name_hash);
+			h = namehash(ifname) & (IDXMAP_SIZE - 1);
+			hlist_add_head(&im->name_hash, &name_head[h]);
+		}
 
-	if (im == NULL) {
-		im = malloc(sizeof(*im));
-		if (im == NULL)
-			return 0;
-		im->idx_next = *imp;
-		im->index = ifi->ifi_index;
-		*imp = im;
+		im->flags = ifi->ifi_flags;
+		return 0;
 	}
 
+	im = malloc(sizeof(*im));
+	if (im == NULL)
+		return 0;
+	im->index = ifi->ifi_index;
+	strcpy(im->name, ifname);
 	im->type = ifi->ifi_type;
 	im->flags = ifi->ifi_flags;
-	if (tb[IFLA_ADDRESS]) {
-		int alen;
-		im->alen = alen = RTA_PAYLOAD(tb[IFLA_ADDRESS]);
-		if (alen > sizeof(im->addr))
-			alen = sizeof(im->addr);
-		memcpy(im->addr, RTA_DATA(tb[IFLA_ADDRESS]), alen);
-	} else {
-		im->alen = 0;
-		memset(im->addr, 0, sizeof(im->addr));
-	}
-	strcpy(im->name, RTA_DATA(tb[IFLA_IFNAME]));
+
+	h = ifi->ifi_index & (IDXMAP_SIZE - 1);
+	hlist_add_head(&im->idx_hash, &idx_head[h]);
+
+	h = namehash(ifname) & (IDXMAP_SIZE - 1);
+	hlist_add_head(&im->name_hash, &name_head[h]);
+
 	return 0;
 }
 
@@ -100,15 +145,16 @@
 	if (idx == 0)
 		return "*";
 
-	for (im = idxhead(idx); im; im = im->idx_next)
-		if (im->index == idx)
-			return im->name;
+	im = ll_get_by_index(idx);
+	if (im)
+		return im->name;
 
-	snprintf(buf, IFNAMSIZ, "if%d", idx);
+	if (if_indextoname(idx, buf) == NULL)
+		snprintf(buf, IFNAMSIZ, "if%d", idx);
+
 	return buf;
 }
 
-
 const char *ll_index_to_name(unsigned idx)
 {
 	static char nbuf[IFNAMSIZ];
@@ -122,69 +168,33 @@
 
 	if (idx == 0)
 		return -1;
-	for (im = idxhead(idx); im; im = im->idx_next)
-		if (im->index == idx)
-			return im->type;
-	return -1;
+
+	im = ll_get_by_index(idx);
+	return im ? im->type : -1;
 }
 
-unsigned ll_index_to_flags(unsigned idx)
+int ll_index_to_flags(unsigned idx)
 {
 	const struct ll_cache *im;
 
 	if (idx == 0)
 		return 0;
 
-	for (im = idxhead(idx); im; im = im->idx_next)
-		if (im->index == idx)
-			return im->flags;
-	return 0;
-}
-
-unsigned ll_index_to_addr(unsigned idx, unsigned char *addr,
-			  unsigned alen)
-{
-	const struct ll_cache *im;
-
-	if (idx == 0)
-		return 0;
-
-	for (im = idxhead(idx); im; im = im->idx_next) {
-		if (im->index == idx) {
-			if (alen > sizeof(im->addr))
-				alen = sizeof(im->addr);
-			if (alen > im->alen)
-				alen = im->alen;
-			memcpy(addr, im->addr, alen);
-			return alen;
-		}
-	}
-	return 0;
+	im = ll_get_by_index(idx);
+	return im ? im->flags : -1;
 }
 
 unsigned ll_name_to_index(const char *name)
 {
-	static char ncache[IFNAMSIZ];
-	static int icache;
-	struct ll_cache *im;
-	int i;
+	const struct ll_cache *im;
 	unsigned idx;
 
 	if (name == NULL)
 		return 0;
 
-	if (icache && strcmp(name, ncache) == 0)
-		return icache;
-
-	for (i=0; i<IDXMAP_SIZE; i++) {
-		for (im = idx_head[i]; im; im = im->idx_next) {
-			if (strcmp(im->name, name) == 0) {
-				icache = im->index;
-				strcpy(ncache, name);
-				return im->index;
-			}
-		}
-	}
+	im = ll_get_by_name(name);
+	if (im)
+		return im->index;
 
 	idx = if_nametoindex(name);
 	if (idx == 0)
@@ -192,12 +202,12 @@
 	return idx;
 }
 
-int ll_init_map(struct rtnl_handle *rth)
+void ll_init_map(struct rtnl_handle *rth)
 {
 	static int initialized;
 
 	if (initialized)
-		return 0;
+		return;
 
 	if (rtnl_wilddump_request(rth, AF_UNSPEC, RTM_GETLINK) < 0) {
 		perror("Cannot send dump request");
@@ -210,6 +220,4 @@
 	}
 
 	initialized = 1;
-
-	return 0;
 }
diff --git a/lib/ll_proto.c b/lib/ll_proto.c
index 3337b14..d8df68c 100644
--- a/lib/ll_proto.c
+++ b/lib/ll_proto.c
@@ -78,6 +78,8 @@
 __PF(ECONET,econet)
 __PF(TIPC,tipc)
 __PF(AOE,aoe)
+__PF(8021Q,802.1Q)
+__PF(8021AD,802.1ad)
 
 { 0x8100, "802.1Q" },
 { 0x88cc, "LLDP" },
@@ -100,10 +102,10 @@
         return buf;
 }
 
-int ll_proto_a2n(unsigned short *id, char *buf)
+int ll_proto_a2n(unsigned short *id, const char *buf)
 {
         int i;
-        for (i=0; i<sizeof(llproto_names)/sizeof(llproto_names[0]); i++) {
+        for (i=0; i < sizeof(llproto_names)/sizeof(llproto_names[0]); i++) {
                  if (strcasecmp(llproto_names[i].name, buf) == 0) {
 			 *id = htons(llproto_names[i].id);
 			 return 0;
diff --git a/lib/ll_types.c b/lib/ll_types.c
index 448892b..2c5bf8b 100644
--- a/lib/ll_types.c
+++ b/lib/ll_types.c
@@ -103,6 +103,8 @@
 __PF(PHONET, phonet)
 __PF(PHONET_PIPE, phonet_pipe)
 __PF(CAIF, caif)
+__PF(IP6GRE, gre6)
+__PF(NETLINK, netlink)
 
 __PF(NONE, none)
 __PF(VOID,void)
diff --git a/lib/names.c b/lib/names.c
new file mode 100644
index 0000000..3b5b0b1
--- /dev/null
+++ b/lib/names.c
@@ -0,0 +1,183 @@
+/*
+ * names.c		db names
+ *
+ *		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.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "names.h"
+#include "utils.h"
+
+#define MAX_ENTRIES  256
+#define NAME_MAX_LEN 512
+
+static int read_id_name(FILE *fp, int *id, char *name)
+{
+	char buf[NAME_MAX_LEN];
+	int min, maj;
+
+	while (fgets(buf, sizeof(buf), fp)) {
+		char *p = buf;
+
+		while (*p == ' ' || *p == '\t')
+			p++;
+
+		if (*p == '#' || *p == '\n' || *p == 0)
+			continue;
+
+		if (sscanf(p, "%x:%x %s\n", &maj, &min, name) == 3) {
+			*id = (maj << 16) | min;
+		} else if (sscanf(p, "%x:%x %s #", &maj, &min, name) == 3) {
+			*id = (maj << 16) | min;
+		} else if (sscanf(p, "0x%x %s\n", id, name) != 2 &&
+				sscanf(p, "0x%x %s #", id, name) != 2 &&
+				sscanf(p, "%d %s\n", id, name) != 2 &&
+				sscanf(p, "%d %s #", id, name) != 2) {
+			strcpy(name, p);
+			return -1;
+		}
+		return 1;
+	}
+
+	return 0;
+}
+
+struct db_names *db_names_alloc(void)
+{
+	struct db_names *db;
+
+	db = malloc(sizeof(*db));
+	if (!db)
+		return NULL;
+
+	memset(db, 0, sizeof(*db));
+
+	db->size = MAX_ENTRIES;
+	db->hash = malloc(sizeof(struct db_entry *) * db->size);
+	memset(db->hash, 0, sizeof(struct db_entry *) * db->size);
+
+	return db;
+}
+
+int db_names_load(struct db_names *db, const char *path)
+{
+	struct db_entry *entry;
+	FILE *fp;
+	int id;
+	char namebuf[NAME_MAX_LEN] = {0};
+	int ret = -1;
+
+	fp = fopen(path, "r");
+	if (!fp)
+		return -ENOENT;
+
+	while ((ret = read_id_name(fp, &id, &namebuf[0]))) {
+		if (ret == -1) {
+			fprintf(stderr, "Database %s is corrupted at %s\n",
+					path, namebuf);
+			goto Exit;
+		}
+		ret = -1;
+
+		if (id < 0)
+			continue;
+
+		entry = malloc(sizeof(*entry));
+		if (!entry)
+			goto Exit;
+
+		entry->name = strdup(namebuf);
+		if (!entry->name) {
+			free(entry);
+			goto Exit;
+		}
+
+		entry->id   = id;
+		entry->next = db->hash[id & (db->size - 1)];
+		db->hash[id & (db->size - 1)] = entry;
+	}
+	ret = 0;
+
+Exit:
+	fclose(fp);
+	return ret;
+}
+
+void db_names_free(struct db_names *db)
+{
+	int i;
+
+	if (!db)
+		return;
+
+	for (i = 0; i < db->size; i++) {
+		struct db_entry *entry = db->hash[i];
+
+		while (entry) {
+			struct db_entry *next = entry->next;
+
+			free(entry->name);
+			free(entry);
+			entry = next;
+		}
+	}
+
+	free(db->hash);
+	free(db);
+}
+
+char *id_to_name(struct db_names *db, int id, char *name)
+{
+	struct db_entry *entry;
+
+	if (!db)
+		return NULL;
+
+	entry = db->hash[id & (db->size - 1)];
+	while (entry && entry->id != id)
+		entry = entry->next;
+
+	if (entry) {
+		strncpy(name, entry->name, IDNAME_MAX);
+		return name;
+	}
+
+	snprintf(name, IDNAME_MAX, "%d", id);
+	return NULL;
+}
+
+int name_to_id(struct db_names *db, int *id, const char *name)
+{
+	struct db_entry *entry;
+	int i;
+
+	if (!db)
+		return -1;
+
+	if (db->cached && strcmp(db->cached->name, name) == 0) {
+		*id = db->cached->id;
+		return 0;
+	}
+
+	for (i = 0; i < db->size; i++) {
+		entry = db->hash[i];
+		while (entry && strcmp(entry->name, name))
+			entry = entry->next;
+
+		if (entry) {
+			db->cached = entry;
+			*id = entry->id;
+			return 0;
+		}
+	}
+
+	return -1;
+}
diff --git a/lib/namespace.c b/lib/namespace.c
new file mode 100644
index 0000000..c03a103
--- /dev/null
+++ b/lib/namespace.c
@@ -0,0 +1,123 @@
+/*
+ * namespace.c
+ *
+ *		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.
+ */
+
+#include <fcntl.h>
+#include <dirent.h>
+
+#include "utils.h"
+#include "namespace.h"
+
+static void bind_etc(const char *name)
+{
+	char etc_netns_path[MAXPATHLEN];
+	char netns_name[MAXPATHLEN];
+	char etc_name[MAXPATHLEN];
+	struct dirent *entry;
+	DIR *dir;
+
+	snprintf(etc_netns_path, sizeof(etc_netns_path), "%s/%s", NETNS_ETC_DIR, name);
+	dir = opendir(etc_netns_path);
+	if (!dir)
+		return;
+
+	while ((entry = readdir(dir)) != NULL) {
+		if (strcmp(entry->d_name, ".") == 0)
+			continue;
+		if (strcmp(entry->d_name, "..") == 0)
+			continue;
+		snprintf(netns_name, sizeof(netns_name), "%s/%s", etc_netns_path, entry->d_name);
+		snprintf(etc_name, sizeof(etc_name), "/etc/%s", entry->d_name);
+		if (mount(netns_name, etc_name, "none", MS_BIND, NULL) < 0) {
+			fprintf(stderr, "Bind %s -> %s failed: %s\n",
+				netns_name, etc_name, strerror(errno));
+		}
+	}
+	closedir(dir);
+}
+
+int netns_switch(char *name)
+{
+	char net_path[MAXPATHLEN];
+	int netns;
+
+	snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name);
+	netns = open(net_path, O_RDONLY | O_CLOEXEC);
+	if (netns < 0) {
+		fprintf(stderr, "Cannot open network namespace \"%s\": %s\n",
+			name, strerror(errno));
+		return -1;
+	}
+
+	if (setns(netns, CLONE_NEWNET) < 0) {
+		fprintf(stderr, "setting the network namespace \"%s\" failed: %s\n",
+			name, strerror(errno));
+		return -1;
+	}
+
+	if (unshare(CLONE_NEWNS) < 0) {
+		fprintf(stderr, "unshare failed: %s\n", strerror(errno));
+		return -1;
+	}
+	/* Don't let any mounts propagate back to the parent */
+	if (mount("", "/", "none", MS_SLAVE | MS_REC, NULL)) {
+		fprintf(stderr, "\"mount --make-rslave /\" failed: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	/* Mount a version of /sys that describes the network namespace */
+	if (umount2("/sys", MNT_DETACH) < 0) {
+		fprintf(stderr, "umount of /sys failed: %s\n", strerror(errno));
+		return -1;
+	}
+	if (mount(name, "/sys", "sysfs", 0, NULL) < 0) {
+		fprintf(stderr, "mount of /sys failed: %s\n",strerror(errno));
+		return -1;
+	}
+
+	/* Setup bind mounts for config files in /etc */
+	bind_etc(name);
+	return 0;
+}
+
+int netns_get_fd(const char *name)
+{
+	char pathbuf[MAXPATHLEN];
+	const char *path, *ptr;
+
+	path = name;
+	ptr = strchr(name, '/');
+	if (!ptr) {
+		snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
+			NETNS_RUN_DIR, name );
+		path = pathbuf;
+	}
+	return open(path, O_RDONLY);
+}
+
+int netns_foreach(int (*func)(char *nsname, void *arg), void *arg)
+{
+	DIR *dir;
+	struct dirent *entry;
+
+	dir = opendir(NETNS_RUN_DIR);
+	if (!dir)
+		return -1;
+
+	while ((entry = readdir(dir)) != NULL) {
+		if (strcmp(entry->d_name, ".") == 0)
+			continue;
+		if (strcmp(entry->d_name, "..") == 0)
+			continue;
+		if (func(entry->d_name, arg))
+			break;
+	}
+
+	closedir(dir);
+	return 0;
+}
diff --git a/lib/rt_names.c b/lib/rt_names.c
index 52ecdb2..e87c65d 100644
--- a/lib/rt_names.c
+++ b/lib/rt_names.c
@@ -27,43 +27,63 @@
 #define CONFDIR "/etc/iproute2"
 #endif
 
+#define NAME_MAX_LEN 512
+
 struct rtnl_hash_entry {
 	struct rtnl_hash_entry *next;
-	char *			name;
+	const char *		name;
 	unsigned int		id;
 };
 
+static int fread_id_name(FILE *fp, int *id, char *namebuf)
+{
+	char buf[NAME_MAX_LEN];
+
+	while (fgets(buf, sizeof(buf), fp)) {
+		char *p = buf;
+
+		while (*p == ' ' || *p == '\t')
+			p++;
+
+		if (*p == '#' || *p == '\n' || *p == 0)
+			continue;
+
+		if (sscanf(p, "0x%x %s\n", id, namebuf) != 2 &&
+				sscanf(p, "0x%x %s #", id, namebuf) != 2 &&
+				sscanf(p, "%d %s\n", id, namebuf) != 2 &&
+				sscanf(p, "%d %s #", id, namebuf) != 2) {
+			strcpy(namebuf, p);
+			return -1;
+		}
+		return 1;
+	}
+	return 0;
+}
+
 static void
-rtnl_hash_initialize(char *file, struct rtnl_hash_entry **hash, int size)
+rtnl_hash_initialize(const char *file, struct rtnl_hash_entry **hash, int size)
 {
 	struct rtnl_hash_entry *entry;
-	char buf[512];
 	FILE *fp;
+	int id;
+	char namebuf[NAME_MAX_LEN] = {0};
+	int ret;
 
 	fp = fopen(file, "r");
 	if (!fp)
 		return;
-	while (fgets(buf, sizeof(buf), fp)) {
-		char *p = buf;
-		int id;
-		char namebuf[512];
 
-		while (*p == ' ' || *p == '\t')
-			p++;
-		if (*p == '#' || *p == '\n' || *p == 0)
-			continue;
-		if (sscanf(p, "0x%x %s\n", &id, namebuf) != 2 &&
-		    sscanf(p, "0x%x %s #", &id, namebuf) != 2 &&
-		    sscanf(p, "%d %s\n", &id, namebuf) != 2 &&
-		    sscanf(p, "%d %s #", &id, namebuf) != 2) {
+	while ((ret = fread_id_name(fp, &id, &namebuf[0]))) {
+		if (ret == -1) {
 			fprintf(stderr, "Database %s is corrupted at %s\n",
-				file, p);
+					file, namebuf);
 			fclose(fp);
 			return;
 		}
 
 		if (id<0)
 			continue;
+
 		entry = malloc(sizeof(*entry));
 		entry->id   = id;
 		entry->name = strdup(namebuf);
@@ -73,33 +93,24 @@
 	fclose(fp);
 }
 
-static void rtnl_tab_initialize(char *file, char **tab, int size)
+static void rtnl_tab_initialize(const char *file, char **tab, int size)
 {
-	char buf[512];
 	FILE *fp;
+	int id;
+	char namebuf[NAME_MAX_LEN] = {0};
+	int ret;
 
 	fp = fopen(file, "r");
 	if (!fp)
 		return;
-	while (fgets(buf, sizeof(buf), fp)) {
-		char *p = buf;
-		int id;
-		char namebuf[512];
 
-		while (*p == ' ' || *p == '\t')
-			p++;
-		if (*p == '#' || *p == '\n' || *p == 0)
-			continue;
-		if (sscanf(p, "0x%x %s\n", &id, namebuf) != 2 &&
-		    sscanf(p, "0x%x %s #", &id, namebuf) != 2 &&
-		    sscanf(p, "%d %s\n", &id, namebuf) != 2 &&
-		    sscanf(p, "%d %s #", &id, namebuf) != 2) {
+	while ((ret = fread_id_name(fp, &id, &namebuf[0]))) {
+		if (ret == -1) {
 			fprintf(stderr, "Database %s is corrupted at %s\n",
-				file, p);
+					file, namebuf);
 			fclose(fp);
 			return;
 		}
-
 		if (id<0 || id>size)
 			continue;
 
@@ -120,6 +131,7 @@
 	[RTPROT_MRT] =	"mrt",
 	[RTPROT_ZEBRA] ="zebra",
 	[RTPROT_BIRD] = "bird",
+	[RTPROT_BABEL] = "babel",
 	[RTPROT_DNROUTED] = "dnrouted",
 	[RTPROT_XORP] = "xorp",
 	[RTPROT_NTK] = "ntk",
@@ -127,7 +139,6 @@
 };
 
 
-
 static int rtnl_rtprot_init;
 
 static void rtnl_rtprot_initialize(void)
@@ -137,10 +148,10 @@
 			    rtnl_rtprot_tab, 256);
 }
 
-char * rtnl_rtprot_n2a(int id, char *buf, int len)
+const char * rtnl_rtprot_n2a(int id, char *buf, int len)
 {
 	if (id<0 || id>=256) {
-		snprintf(buf, len, "%d", id);
+		snprintf(buf, len, "%u", id);
 		return buf;
 	}
 	if (!rtnl_rtprot_tab[id]) {
@@ -149,11 +160,11 @@
 	}
 	if (rtnl_rtprot_tab[id])
 		return rtnl_rtprot_tab[id];
-	snprintf(buf, len, "%d", id);
+	snprintf(buf, len, "%u", id);
 	return buf;
 }
 
-int rtnl_rtprot_a2n(__u32 *id, char *arg)
+int rtnl_rtprot_a2n(__u32 *id, const char *arg)
 {
 	static char *cache = NULL;
 	static unsigned long res;
@@ -185,8 +196,6 @@
 	return 0;
 }
 
-
-
 static char * rtnl_rtscope_tab[256] = {
 	"global",
 };
@@ -196,15 +205,15 @@
 static void rtnl_rtscope_initialize(void)
 {
 	rtnl_rtscope_init = 1;
-	rtnl_rtscope_tab[255] = "nowhere";
-	rtnl_rtscope_tab[254] = "host";
-	rtnl_rtscope_tab[253] = "link";
-	rtnl_rtscope_tab[200] = "site";
+	rtnl_rtscope_tab[RT_SCOPE_NOWHERE] = "nowhere";
+	rtnl_rtscope_tab[RT_SCOPE_HOST]    = "host";
+	rtnl_rtscope_tab[RT_SCOPE_LINK]    = "link";
+	rtnl_rtscope_tab[RT_SCOPE_SITE]    = "site";
 	rtnl_tab_initialize(CONFDIR "/rt_scopes",
 			    rtnl_rtscope_tab, 256);
 }
 
-char * rtnl_rtscope_n2a(int id, char *buf, int len)
+const char *rtnl_rtscope_n2a(int id, char *buf, int len)
 {
 	if (id<0 || id>=256) {
 		snprintf(buf, len, "%d", id);
@@ -220,9 +229,9 @@
 	return buf;
 }
 
-int rtnl_rtscope_a2n(__u32 *id, char *arg)
+int rtnl_rtscope_a2n(__u32 *id, const char *arg)
 {
-	static char *cache = NULL;
+	static const char *cache = NULL;
 	static unsigned long res;
 	char *end;
 	int i;
@@ -253,7 +262,6 @@
 }
 
 
-
 static char * rtnl_rtrealm_tab[256] = {
 	"unknown",
 };
@@ -267,7 +275,7 @@
 			    rtnl_rtrealm_tab, 256);
 }
 
-char * rtnl_rtrealm_n2a(int id, char *buf, int len)
+const char *rtnl_rtrealm_n2a(int id, char *buf, int len)
 {
 	if (id<0 || id>=256) {
 		snprintf(buf, len, "%d", id);
@@ -284,7 +292,7 @@
 }
 
 
-int rtnl_rtrealm_a2n(__u32 *id, char *arg)
+int rtnl_rtrealm_a2n(__u32 *id, const char *arg)
 {
 	static char *cache = NULL;
 	static unsigned long res;
@@ -317,26 +325,32 @@
 }
 
 
-static struct rtnl_hash_entry dflt_table_entry  = { .id = 253, .name = "default" };
-static struct rtnl_hash_entry main_table_entry  = { .id = 254, .name = "main" };
-static struct rtnl_hash_entry local_table_entry = { .id = 255, .name = "local" };
+static struct rtnl_hash_entry dflt_table_entry  = { .name = "default" };
+static struct rtnl_hash_entry main_table_entry  = { .name = "main" };
+static struct rtnl_hash_entry local_table_entry = { .name = "local" };
 
 static struct rtnl_hash_entry * rtnl_rttable_hash[256] = {
-	[253] = &dflt_table_entry,
-	[254] = &main_table_entry,
-	[255] = &local_table_entry,
+	[RT_TABLE_DEFAULT] = &dflt_table_entry,
+	[RT_TABLE_MAIN]    = &main_table_entry,
+	[RT_TABLE_LOCAL]   = &local_table_entry,
 };
 
 static int rtnl_rttable_init;
 
 static void rtnl_rttable_initialize(void)
 {
+	int i;
+
 	rtnl_rttable_init = 1;
+	for (i = 0; i < 256; i++) {
+		if (rtnl_rttable_hash[i])
+			rtnl_rttable_hash[i]->id = i;
+	}
 	rtnl_hash_initialize(CONFDIR "/rt_tables",
 			     rtnl_rttable_hash, 256);
 }
 
-char * rtnl_rttable_n2a(__u32 id, char *buf, int len)
+const char * rtnl_rttable_n2a(__u32 id, char *buf, int len)
 {
 	struct rtnl_hash_entry *entry;
 
@@ -355,9 +369,9 @@
 	return buf;
 }
 
-int rtnl_rttable_a2n(__u32 *id, char *arg)
+int rtnl_rttable_a2n(__u32 *id, const char *arg)
 {
-	static char *cache = NULL;
+	static const char *cache = NULL;
 	static unsigned long res;
 	struct rtnl_hash_entry *entry;
 	char *end;
@@ -404,7 +418,7 @@
 			    rtnl_rtdsfield_tab, 256);
 }
 
-char * rtnl_dsfield_n2a(int id, char *buf, int len)
+const char *rtnl_dsfield_n2a(int id, char *buf, int len)
 {
 	if (id<0 || id>=256) {
 		snprintf(buf, len, "%d", id);
@@ -421,7 +435,7 @@
 }
 
 
-int rtnl_dsfield_a2n(__u32 *id, char *arg)
+int rtnl_dsfield_a2n(__u32 *id, const char *arg)
 {
 	static char *cache = NULL;
 	static unsigned long res;
@@ -465,13 +479,13 @@
 static void rtnl_group_initialize(void)
 {
 	rtnl_group_init = 1;
-	rtnl_hash_initialize("/etc/iproute2/group",
+	rtnl_hash_initialize(CONFDIR "/group",
 			     rtnl_group_hash, 256);
 }
 
-int rtnl_group_a2n(int *id, char *arg)
+int rtnl_group_a2n(int *id, const char *arg)
 {
-	static char *cache = NULL;
+	static const char *cache = NULL;
 	static unsigned long res;
 	struct rtnl_hash_entry *entry;
 	char *end;
@@ -503,3 +517,104 @@
 	*id = i;
 	return 0;
 }
+
+const char *rtnl_group_n2a(int id, char *buf, int len)
+{
+	struct rtnl_hash_entry *entry;
+	int i;
+
+	if (!rtnl_group_init)
+		rtnl_group_initialize();
+
+	for (i=0; i<256; i++) {
+		entry = rtnl_group_hash[i];
+		if (entry && entry->id == id) {
+			return entry->name;
+		}
+	}
+
+	snprintf(buf, len, "%d", id);
+	return buf;
+}
+
+static char *nl_proto_tab[256] = {
+	[NETLINK_ROUTE]          = "rtnl",
+	[NETLINK_UNUSED]         = "unused",
+	[NETLINK_USERSOCK]       = "usersock",
+	[NETLINK_FIREWALL]       = "fw",
+	[NETLINK_SOCK_DIAG]      = "tcpdiag",
+	[NETLINK_NFLOG]          = "nflog",
+	[NETLINK_XFRM]           = "xfrm",
+	[NETLINK_SELINUX]        = "selinux",
+	[NETLINK_ISCSI]          = "iscsi",
+	[NETLINK_AUDIT]          = "audit",
+	[NETLINK_FIB_LOOKUP]     = "fiblookup",
+	[NETLINK_CONNECTOR]      = "connector",
+	[NETLINK_NETFILTER]      = "nft",
+	[NETLINK_IP6_FW]         = "ip6fw",
+	[NETLINK_DNRTMSG]        = "dec-rt",
+	[NETLINK_KOBJECT_UEVENT] = "uevent",
+	[NETLINK_GENERIC]        = "genl",
+	[NETLINK_SCSITRANSPORT]  = "scsi-trans",
+	[NETLINK_ECRYPTFS]       = "ecryptfs",
+	[NETLINK_RDMA]           = "rdma",
+	[NETLINK_CRYPTO]         = "crypto",
+};
+
+static int nl_proto_init;
+
+static void nl_proto_initialize(void)
+{
+	nl_proto_init = 1;
+	rtnl_tab_initialize(CONFDIR "/nl_protos",
+			    nl_proto_tab, 256);
+}
+
+const char *nl_proto_n2a(int id, char *buf, int len)
+{
+	if (id < 0 || id >= 256) {
+		snprintf(buf, len, "%u", id);
+		return buf;
+	}
+
+	if (!nl_proto_init)
+		nl_proto_initialize();
+
+	if (nl_proto_tab[id])
+		return nl_proto_tab[id];
+
+	snprintf(buf, len, "%u", id);
+	return buf;
+}
+
+int nl_proto_a2n(__u32 *id, const char *arg)
+{
+	static char *cache = NULL;
+	static unsigned long res;
+	char *end;
+	int i;
+
+	if (cache && strcmp(cache, arg) == 0) {
+		*id = res;
+		return 0;
+	}
+
+	if (!nl_proto_init)
+		nl_proto_initialize();
+
+	for (i = 0; i < 256; i++) {
+		if (nl_proto_tab[i] &&
+		    strcmp(nl_proto_tab[i], arg) == 0) {
+			cache = nl_proto_tab[i];
+			res = i;
+			*id = res;
+			return 0;
+		}
+	}
+
+	res = strtoul(arg, &end, 0);
+	if (!end || end == arg || *end || res > 255)
+		return -1;
+	*id = res;
+	return 0;
+}
diff --git a/lib/utils.c b/lib/utils.c
index 1ad7616..a3f4268 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -12,23 +12,28 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <math.h>
 #include <unistd.h>
 #include <syslog.h>
 #include <fcntl.h>
+#include <limits.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <string.h>
 #include <netdb.h>
 #include <arpa/inet.h>
-#include <resolv.h>
 #include <asm/types.h>
 #include <linux/pkt_sched.h>
+#include <linux/param.h>
 #include <time.h>
 #include <sys/time.h>
 #include <errno.h>
 
 
 #include "utils.h"
+#include "namespace.h"
+
+int timestamp_short = 0;
 
 int get_integer(int *val, const char *arg, int base)
 {
@@ -37,9 +42,28 @@
 
 	if (!arg || !*arg)
 		return -1;
+
 	res = strtol(arg, &ptr, base);
-	if (!ptr || ptr == arg || *ptr || res > INT_MAX || res < INT_MIN)
+
+	/* If there were no digits at all, strtol()  stores
+         * the original value of nptr in *endptr (and returns 0).
+	 * In particular, if *nptr is not '\0' but **endptr is '\0' on return,
+	 * the entire string is valid.
+	 */
+	if (!ptr || ptr == arg || *ptr)
 		return -1;
+
+	/* If an underflow occurs, strtol() returns LONG_MIN.
+	 * If an overflow occurs,  strtol() returns LONG_MAX.
+	 * In both cases, errno is set to ERANGE.
+	 */
+	if ((res == LONG_MAX || res == LONG_MIN) && errno == ERANGE)
+		return -1;
+
+	/* Outside range of int */
+	if (res < INT_MIN || res > INT_MAX)
+		return -1;
+
 	*val = res;
 	return 0;
 }
@@ -86,9 +110,21 @@
 
 	if (!arg || !*arg)
 		return -1;
+
 	res = strtoul(arg, &ptr, base);
-	if (!ptr || ptr == arg || *ptr || res > UINT_MAX)
+
+	/* empty string or trailing non-digits */
+	if (!ptr || ptr == arg || *ptr)
 		return -1;
+
+	/* overflow */
+	if (res == ULONG_MAX && errno == ERANGE)
+		return -1;
+
+	/* out side range of unsigned */
+	if (res > UINT_MAX)
+		return -1;
+
 	*val = res;
 	return 0;
 }
@@ -106,17 +142,32 @@
 	unsigned long res;
 	char *p;
 
-	if (strchr(arg,'.') != NULL) {
-		t = strtod(arg,&p);
+	if (strchr(arg, '.') != NULL) {
+		t = strtod(arg, &p);
 		if (t < 0.0)
 			return -1;
-	}
-	else {
-		res = strtoul(arg, &p, 0);
-		if (res > UINT_MAX)
+
+		/* no digits? */
+		if (!p || p == arg)
 			return -1;
+
+		/* over/underflow */
+		if ((t == HUGE_VALF || t == HUGE_VALL) && errno == ERANGE)
+			return -1;
+	} else {
+		res = strtoul(arg, &p, 0);
+
+		/* empty string? */
+		if (!p || p == arg)
+			return -1;
+
+		/* overflow */
+		if (res == ULONG_MAX && errno == ERANGE)
+			return -1;
+
 		t = (double)res;
 	}
+
 	if (p == arg)
 		return -1;
 	*raw = 1;
@@ -150,9 +201,21 @@
 
 	if (!arg || !*arg)
 		return -1;
+
 	res = strtoull(arg, &ptr, base);
-	if (!ptr || ptr == arg || *ptr || res == 0xFFFFFFFFULL)
- 		return -1;
+
+	/* empty string or trailing non-digits */
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+
+	/* overflow */
+	if (res == ULLONG_MAX && errno == ERANGE)
+		return -1;
+
+	/* in case ULL is 128 bits */
+	if (res > 0xFFFFFFFFFFFFFFFFULL)
+		return -1;
+
  	*val = res;
  	return 0;
 }
@@ -165,8 +228,19 @@
 	if (!arg || !*arg)
 		return -1;
 	res = strtoul(arg, &ptr, base);
-	if (!ptr || ptr == arg || *ptr || res > 0xFFFFFFFFUL)
+
+	/* empty string or trailing non-digits */
+	if (!ptr || ptr == arg || *ptr)
 		return -1;
+
+	/* overflow */
+	if (res == ULONG_MAX && errno == ERANGE)
+		return -1;
+
+	/* in case UL > 32 bits */
+	if (res > 0xFFFFFFFFUL)
+		return -1;
+
 	*val = res;
 	return 0;
 }
@@ -179,8 +253,18 @@
 	if (!arg || !*arg)
 		return -1;
 	res = strtoul(arg, &ptr, base);
-	if (!ptr || ptr == arg || *ptr || res > 0xFFFF)
+
+	/* empty string or trailing non-digits */
+	if (!ptr || ptr == arg || *ptr)
 		return -1;
+
+	/* overflow */
+	if (res == ULONG_MAX && errno == ERANGE)
+		return -1;
+
+	if (res > 0xFFFFUL)
+		return -1;
+
 	*val = res;
 	return 0;
 }
@@ -192,9 +276,19 @@
 
 	if (!arg || !*arg)
 		return -1;
+
 	res = strtoul(arg, &ptr, base);
-	if (!ptr || ptr == arg || *ptr || res > 0xFF)
+	/* empty string or trailing non-digits */
+	if (!ptr || ptr == arg || *ptr)
 		return -1;
+
+	/* overflow */
+	if (res == ULONG_MAX && errno == ERANGE)
+		return -1;
+
+	if (res > 0xFFUL)
+		return -1;
+
 	*val = res;
 	return 0;
 }
@@ -209,10 +303,13 @@
 	if (!arg || !*arg)
 		return -1;
 	res = strtol(arg, &ptr, base);
-	if (ptr == arg || *ptr ||
-	    ((res ==  LONG_MIN || res == LONG_MAX) && errno == ERANGE) ||
-	    res > INT32_MAX || res < INT32_MIN)
+	if (!ptr || ptr == arg || *ptr)
 		return -1;
+	if ((res == LONG_MIN || res == LONG_MAX) && errno == ERANGE)
+		return -1;
+	if (res > INT32_MAX || res < INT32_MIN)
+		return -1;
+
 	*val = res;
 	return 0;
 }
@@ -225,8 +322,13 @@
 	if (!arg || !*arg)
 		return -1;
 	res = strtol(arg, &ptr, base);
-	if (!ptr || ptr == arg || *ptr || res > 0x7FFF || res < -0x8000)
+	if (!ptr || ptr == arg || *ptr)
 		return -1;
+	if ((res == LONG_MIN || res == LONG_MAX) && errno == ERANGE)
+		return -1;
+	if (res > 0x7FFF || res < -0x8000)
+		return -1;
+
 	*val = res;
 	return 0;
 }
@@ -239,7 +341,11 @@
 	if (!arg || !*arg)
 		return -1;
 	res = strtol(arg, &ptr, base);
-	if (!ptr || ptr == arg || *ptr || res > 0x7F || res < -0x80)
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+	if ((res == LONG_MIN || res == LONG_MAX) && errno == ERANGE)
+		return -1;
+	if (res > 0x7F || res < -0x80)
 		return -1;
 	*val = res;
 	return 0;
@@ -327,6 +433,27 @@
 	return 0;
 }
 
+int af_bit_len(int af)
+{
+	switch (af) {
+	case AF_INET6:
+		return 128;
+	case AF_INET:
+		return 32;
+	case AF_DECnet:
+		return 16;
+	case AF_IPX:
+		return 80;
+	}
+
+	return 0;
+}
+
+int af_byte_len(int af)
+{
+	return af_bit_len(af) / 8;
+}
+
 int get_prefix_1(inet_prefix *dst, char *arg, int family)
 {
 	int err;
@@ -352,20 +479,11 @@
 
 	err = get_addr_1(dst, arg, family);
 	if (err == 0) {
-		switch(dst->family) {
-			case AF_INET6:
-				dst->bitlen = 128;
-				break;
-			case AF_DECnet:
-				dst->bitlen = 16;
-				break;
-			default:
-			case AF_INET:
-				dst->bitlen = 32;
-		}
+		dst->bitlen = af_bit_len(dst->family);
+
 		if (slash) {
 			if (get_netmask(&plen, slash+1, 0)
-					|| plen > dst->bitlen) {
+			    || plen > dst->bitlen) {
 				err = -1;
 				goto done;
 			}
@@ -520,7 +638,7 @@
 	return sysconf(_SC_CLK_TCK);
 }
 
-const char *rt_addr_n2a(int af, int len, const void *addr, char *buf, int buflen)
+const char *rt_addr_n2a(int af, const void *addr, char *buf, int buflen)
 {
 	switch (af) {
 	case AF_INET:
@@ -595,7 +713,6 @@
 }
 #endif
 
-
 const char *format_host(int af, int len, const void *addr,
 			char *buf, int buflen)
 {
@@ -603,33 +720,14 @@
 	if (resolve_hosts) {
 		const char *n;
 
-		if (len <= 0) {
-			switch (af) {
-			case AF_INET:
-				len = 4;
-				break;
-			case AF_INET6:
-				len = 16;
-				break;
-			case AF_IPX:
-				len = 10;
-				break;
-#ifdef AF_DECnet
-			/* I see no reasons why gethostbyname
-			   may not work for DECnet */
-			case AF_DECnet:
-				len = 2;
-				break;
-#endif
-			default: ;
-			}
-		}
+		len = len <= 0 ? af_byte_len(af) : len;
+
 		if (len > 0 &&
 		    (n = resolve_address(addr, len, af)) != NULL)
 			return n;
 	}
 #endif
-	return rt_addr_n2a(af, len, addr, buf, buflen);
+	return rt_addr_n2a(af, addr, buf, buflen);
 }
 
 
@@ -644,10 +742,6 @@
 		sprintf(ptr, "%02x", str[i]);
 		ptr += 2;
 		blen -= 2;
-		if (i != len-1 && blen > 1) {
-			*ptr++ = ':';
-			blen--;
-		}
 	}
 	return buf;
 }
@@ -655,52 +749,46 @@
 __u8* hexstring_a2n(const char *str, __u8 *buf, int blen)
 {
 	int cnt = 0;
+	char *endptr;
 
-	for (;;) {
-		unsigned acc;
-		char ch;
+	if (strlen(str) % 2)
+		return NULL;
+	while (cnt < blen && strlen(str) > 1) {
+		unsigned int tmp;
+		char tmpstr[3];
 
-		acc = 0;
-
-		while ((ch = *str) != ':' && ch != 0) {
-			if (ch >= '0' && ch <= '9')
-				ch -= '0';
-			else if (ch >= 'a' && ch <= 'f')
-				ch -= 'a'-10;
-			else if (ch >= 'A' && ch <= 'F')
-				ch -= 'A'-10;
-			else
-				return NULL;
-			acc = (acc<<4) + ch;
-			str++;
-		}
-
-		if (acc > 255)
+		strncpy(tmpstr, str, 2);
+		tmpstr[2] = '\0';
+		tmp = strtoul(tmpstr, &endptr, 16);
+		if (errno != 0 || tmp > 0xFF || *endptr != '\0')
 			return NULL;
-		if (cnt < blen) {
-			buf[cnt] = acc;
-			cnt++;
-		}
-		if (ch == 0)
-			break;
-		++str;
+		buf[cnt++] = tmp;
+		str += 2;
 	}
-	if (cnt < blen)
-		memset(buf+cnt, 0, blen-cnt);
 	return buf;
 }
 
 int print_timestamp(FILE *fp)
 {
 	struct timeval tv;
-	char *tstr;
+	struct tm *tm;
 
-	memset(&tv, 0, sizeof(tv));
 	gettimeofday(&tv, NULL);
+	tm = localtime(&tv.tv_sec);
 
-	tstr = asctime(localtime(&tv.tv_sec));
-	tstr[strlen(tstr)-1] = 0;
-	fprintf(fp, "Timestamp: %s %lu usec\n", tstr, tv.tv_usec);
+	if (timestamp_short) {
+		char tshort[40];
+
+		strftime(tshort, sizeof(tshort), "%Y-%m-%dT%H:%M:%S", tm);
+		fprintf(fp, "[%s.%06ld] ", tshort, tv.tv_usec);
+	} else {
+		char *tstr = asctime(tm);
+
+		tstr[strlen(tstr)-1] = 0;
+		fprintf(fp, "Timestamp: %s %ld usec\n",
+			tstr, tv.tv_usec);
+	}
+
 	return 0;
 }
 
@@ -771,3 +859,54 @@
 
 	return argc;
 }
+
+int inet_get_addr(const char *src, __u32 *dst, struct in6_addr *dst6)
+{
+	if (strchr(src, ':'))
+		return inet_pton(AF_INET6, src, dst6);
+	else
+		return inet_pton(AF_INET, src, dst);
+}
+
+void print_nlmsg_timestamp(FILE *fp, const struct nlmsghdr *n)
+{
+	char *tstr;
+	time_t secs = ((__u32*)NLMSG_DATA(n))[0];
+	long usecs = ((__u32*)NLMSG_DATA(n))[1];
+	tstr = asctime(localtime(&secs));
+	tstr[strlen(tstr)-1] = 0;
+	fprintf(fp, "Timestamp: %s %lu us\n", tstr, usecs);
+}
+
+static int on_netns(char *nsname, void *arg)
+{
+	struct netns_func *f = arg;
+
+	if (netns_switch(nsname))
+		return -1;
+
+	return f->func(nsname, f->arg);
+}
+
+static int on_netns_label(char *nsname, void *arg)
+{
+	printf("\nnetns: %s\n", nsname);
+	return on_netns(nsname, arg);
+}
+
+int do_each_netns(int (*func)(char *nsname, void *arg), void *arg,
+		bool show_label)
+{
+	struct netns_func nsf = { .func = func, .arg = arg };
+
+	if (show_label)
+		return netns_foreach(on_netns_label, &nsf);
+
+	return netns_foreach(on_netns, &nsf);
+}
+
+char *int_to_str(int val, char *buf)
+{
+	sprintf(buf, "%d", val);
+	return buf;
+}
diff --git a/man/Makefile b/man/Makefile
index 67fea05..749faa1 100644
--- a/man/Makefile
+++ b/man/Makefile
@@ -2,19 +2,13 @@
 INSTALLDIR=install -m 0755 -d
 INSTALLMAN=install -m 0644
 
-SUBDIRS = man3 man8
+SUBDIRS = man3 man7 man8
 
-all:
-	@for subdir in $(SUBDIRS); do $(MAKE) -C $$subdir; done
+all clean install:
+	@for subdir in $(SUBDIRS); do $(MAKE) -C $$subdir $@ || exit $$?; done
 
 distclean: clean
 
-clean:
-	@for subdir in $(SUBDIRS); do $(MAKE) -C $$subdir clean; done
-
-install:
-	@for subdir in $(SUBDIRS); do $(MAKE) -C $$subdir install; done
-
 .PHONY: install clean distclean
 
 .EXPORT_ALL_VARIABLES:
diff --git a/man/man3/libnetlink.3 b/man/man3/libnetlink.3
index 15a478a..e999bd6 100644
--- a/man/man3/libnetlink.3
+++ b/man/man3/libnetlink.3
@@ -100,7 +100,7 @@
 means to not use a filter.
 .B junk
 is used to filter messages not destined to the local socket.
-Only one message bundle is received. Unless there is no message 
+Only one message bundle is received. If there is a message
 pending, this function does not block.
 
 .TP
@@ -112,7 +112,7 @@
 and the
 .B jarg
 cookie as arguments. It will get called for all received messages.
-Only one message bundle is received. Unless there is no message 
+Only one message bundle is received. If there is a message
 pending this function does not block.
 
 .TP
@@ -123,7 +123,7 @@
 .B file
 and passes the messages to
 .B handler
-for parsing. The file contains raw data as received from a rtnetlink socket.
+for parsing. The file should contain raw data as received from a rtnetlink socket.
 .PP
 The following functions are useful to construct custom rtnetlink messages. For
 simple database dumping with filtering it is better to use the higher level
@@ -183,6 +183,8 @@
 with a variable length data value.
 
 .SH BUGS
+This library is meant for internal use, use libmnl for new programs.
+
 The functions sometimes use fprintf and exit when a fatal error occurs.
 This library should be named librtnetlink.
 
diff --git a/man/man7/Makefile b/man/man7/Makefile
new file mode 100644
index 0000000..ccfd839
--- /dev/null
+++ b/man/man7/Makefile
@@ -0,0 +1,13 @@
+MAN7PAGES = tc-hfsc.7
+
+all:
+
+distclean: clean
+
+clean:
+
+install:
+	$(INSTALLDIR) $(DESTDIR)$(MANDIR)/man7
+	$(INSTALLMAN) $(MAN7PAGES) $(DESTDIR)$(MANDIR)/man7
+
+.PHONY: install clean distclean
diff --git a/man/man7/tc-hfsc.7 b/man/man7/tc-hfsc.7
index eff8a0f..ca04961 100644
--- a/man/man7/tc-hfsc.7
+++ b/man/man7/tc-hfsc.7
@@ -4,13 +4,12 @@
 .
 .SH "HISTORY & INTRODUCTION"
 .
-HFSC \- \fBHierarchical Fair Service Curve\fR was first presented at
+HFSC (Hierarchical Fair Service Curve) is a network packet scheduling algorithm that was first presented at
 SIGCOMM'97. Developed as a part of ALTQ (ALTernative Queuing) on NetBSD, found
 its way quickly to other BSD systems, and then a few years ago became part of
 the linux kernel. Still, it's not the most popular scheduling algorithm \-
-especially if compared to HTB \- and it's not well documented from enduser's
-perspective. This introduction aims to explain how HFSC works without
-going to deep into math side of things (although some if it will be
+especially if compared to HTB \- and it's not well documented for the enduser. This introduction aims to explain how HFSC works without using
+too much math (although some math it will be
 inevitable).
 
 In short HFSC aims to:
@@ -30,10 +29,10 @@
 .
 The main "selling" point of HFSC is feature \fB(1)\fR, which is achieved by
 using nonlinear service curves (more about what it actually is later). This is
-particularly useful in VoIP or games, where not only guarantee of consistent
-bandwidth is important, but initial delay of a data stream as well. Note that
+particularly useful in VoIP or games, where not only a guarantee of consistent
+bandwidth is important, but also limiting the initial delay of a data stream. Note that
 it matters only for leaf classes (where the actual queues are) \- thus class
-hierarchy is ignored in realtime case.
+hierarchy is ignored in the realtime case.
 
 Feature \fB(2)\fR is well, obvious \- any algorithm featuring class hierarchy
 (such as HTB or CBQ) strives to achieve that. HFSC does that well, although
@@ -44,8 +43,8 @@
 situations where it's either not possible to guarantee service of all curves at
 the same time, and/or it's impossible to do so fairly. Both will be explained
 later. Note that this is mainly related to interior (aka aggregate) classes, as
-the leafs are already handled by \fB(1)\fR. Still \- it's perfectly possible to
-create a leaf class w/o realtime service, and in such case \- the caveats will
+the leafs are already handled by \fB(1)\fR. Still, it's perfectly possible to
+create a leaf class without realtime service, and in such a case the caveats will
 naturally extend to leaf classes as well.
 
 .SH ABBREVIATIONS
@@ -62,21 +61,22 @@
 .SH "BASICS OF HFSC"
 .
 To understand how HFSC works, we must first introduce a service curve.
-Overall, it's a nondecreasing function of some time unit, returning amount of
-service (allowed or allocated amount of bandwidth) by some specific point in
-time. The purpose of it should be subconsciously obvious \- if a class was
-allowed to transfer not less than the amount specified by its service curve \-
-then service curve is not violated.
+Overall, it's a nondecreasing function of some time unit, returning the amount
+of
+service (an allowed or allocated amount of bandwidth) at some specific point in
+time. The purpose of it should be subconsciously obvious: if a class was
+allowed to transfer not less than the amount specified by its service curve,
+then the service curve is not violated.
 
-Still \- we need more elaborate criterion than just the above (although in
-most generic case it can be reduced to it). The criterion has to take two
+Still, we need more elaborate criterion than just the above (although in
+the most generic case it can be reduced to it). The criterion has to take two
 things into account:
 .
 .RS 4
 .IP \(bu 4
 idling periods
 .IP \(bu
-ability to "look back", so if during current active period service curve is violated, maybe it
+the ability to "look back", so if during current active period the service curve is violated, maybe it
 isn't if we count excess bandwidth received during earlier active period(s)
 .RE
 .PP
@@ -102,9 +102,9 @@
 .RE
 .
 .PP
-Consider \fB(a)\fR \- if the service received during both periods meets
-\fB(1)\fR, then all is good. But what if it doesn't do so during the 2nd
-period ? If the amount of service received during the 1st period is bigger
+Consider \fB(a)\fR: if the service received during both periods meets
+\fB(1)\fR, then all is well. But what if it doesn't do so during the 2nd
+period? If the amount of service received during the 1st period is larger
 than the service curve, then it might compensate for smaller service during
 the 2nd period \fIand\fR the gap \- if the gap is small enough.
 
@@ -172,42 +172,43 @@
 .SH "REALTIME CRITERION"
 .
 RT criterion \fIignores class hierarchy\fR and guarantees precise bandwidth and
-delay allocation. We say that packet is eligible for sending, when current real
-time is bigger than eligible time. From all packets eligible, the one most
-suited for sending, is the one with the smallest deadline time. Sounds simply,
-but consider following example:
+delay allocation. We say that a packet is eligible for sending, when the
+current real
+time is later than the eligible time of the packet. From all eligible packets, the one most
+suited for sending is the one with the shortest deadline time. This sounds
+simple, but consider the following example:
 
-Interface 10mbit, two classes, both with two\-piece linear service curves:
+Interface 10Mbit, two classes, both with two\-piece linear service curves:
 .RS 4
 .IP \(bu 4
-1st class \- 2mbit for 100ms, then 7mbit (convex \- 1st slope < 2nd slope)
+1st class \- 2Mbit for 100ms, then 7Mbit (convex \- 1st slope < 2nd slope)
 .IP \(bu
-2nd class \- 7mbit for 100ms, then 2mbit (concave \- 1st slope > 2nd slope)
+2nd class \- 7Mbit for 100ms, then 2Mbit (concave \- 1st slope > 2nd slope)
 .RE
 .PP
 Assume for a moment, that we only use D() for both finding eligible packets,
 and choosing the most fitting one, thus eligible time would be computed as
 D^(\-1)(w) and deadline time would be computed as D^(\-1)(w+l). If the 2nd
 class starts sending packets 1 second after the 1st class, it's of course
-impossible to guarantee 14mbit, as the interface capability is only 10mbit.
+impossible to guarantee 14Mbit, as the interface capability is only 10Mbit.
 The only workaround in this scenario is to allow the 1st class to send the
 packets earlier that would normally be allowed. That's where separate E() comes
 to help. Putting all the math aside (see HFSC paper for details), E() for RT
 concave service curve is just like D(), but for the RT convex service curve \-
 it's constructed using \fIonly\fR RT service curve's 2nd slope (in our example
-\- 7mbit).
+ 7Mbit).
 
 The effect of such E() \- packets will be sent earlier, and at the same time
-D() \fIwill\fR be updated \- so current deadline time calculated from it will
-be bigger. Thus, when the 2nd class starts sending packets later, both the 1st
-and the 2nd class will be eligible, but the 2nd session's deadline time will be
-smaller and its packets will be sent first. When the 1st class becomes idle at
-some later point, the 2nd class will be able to "buffer" up again for later
-active period of the 1st class.
+D() \fIwill\fR be updated \- so the current deadline time calculated from it
+will be later. Thus, when the 2nd class starts sending packets later, both
+the 1st and the 2nd class will be eligible, but the 2nd session's deadline
+time will be smaller and its packets will be sent first. When the 1st class
+becomes idle at some later point, the 2nd class will be able to "buffer" up
+again for later active period of the 1st class.
 
 A short remark \- in a situation, where the total amount of bandwidth
-available on the interface is bigger than the allocated total realtime parts
-(imagine interface 10 mbit, but 1mbit/2mbit and 2mbit/1mbit classes), the sole
+available on the interface is larger than the allocated total realtime parts
+(imagine a 10 Mbit interface, but 1Mbit/2Mbit and 2Mbit/1Mbit classes), the sole
 speed of the interface could suffice to guarantee the times.
 
 Important part of RT criterion is that apart from updating its D() and E(),
@@ -233,18 +234,18 @@
 of virtual times of all active subclasses \- the one with the smallest vt wins
 and gets scheduled. One immediate conclusion from this fact is that absolute
 values don't matter \- only ratios between them (so for example, two children
-classes with simple linear 1mbit service curves will get the same treatment
-from LS criterion's perspective, as if they were 5mbit). The other conclusion
+classes with simple linear 1Mbit service curves will get the same treatment
+from LS criterion's perspective, as if they were 5Mbit). The other conclusion
 is, that in perfectly fluid system with linear curves, all virtual times across
 whole class hierarchy would be equal.
 
-Why is VC defined in term of virtual time (and what is it) ?
+Why is VC defined in term of virtual time (and what is it)?
 
 Imagine an example: class A with two children \- A1 and A2, both with let's say
-10mbit SCs. If A2 is idle, A1 receives all the bandwidth of A (and update its
+10Mbit SCs. If A2 is idle, A1 receives all the bandwidth of A (and update its
 V() in the process). When A2 becomes active, A1's virtual time is already
-\fIfar\fR bigger than A2's one. Considering the type of decision made by LS
-criterion, A1 would become idle for a lot of time. We can workaround this
+\fIfar\fR later than A2's one. Considering the type of decision made by LS
+criterion, A1 would become idle for a long time. We can workaround this
 situation by adjusting virtual time of the class becoming active \- we do that
 by getting such time "up to date". HFSC uses a mean of the smallest and the
 biggest virtual time of currently active children fit for sending. As it's not
@@ -259,20 +260,20 @@
 during certain time periods:
 
 .RS 4
-Recall the example from RT section, slightly modified (with 3mbit slopes
-instead of 2mbit ones):
+Recall the example from RT section, slightly modified (with 3Mbit slopes
+instead of 2Mbit ones):
 
 .IP \(bu 4
-1st class \- 3mbit for 100ms, then 7mbit (convex \- 1st slope < 2nd slope)
+1st class \- 3Mbit for 100ms, then 7Mbit (convex \- 1st slope < 2nd slope)
 .IP \(bu
-2nd class \- 7mbit for 100ms, then 3mbit (concave \- 1st slope > 2nd slope)
+2nd class \- 7Mbit for 100ms, then 3Mbit (concave \- 1st slope > 2nd slope)
 
 .PP
-They sum up nicely to 10mbit \- interface's capacity. But if we wanted to only
+They sum up nicely to 10Mbit \- the interface's capacity. But if we wanted to only
 use LS for guarantees and fairness \- it simply won't work. In LS context,
 only V() is used for making decision which class to schedule. If the 2nd class
 becomes active when the 1st one is in its second slope, the fairness will be
-preserved \- ratio will be 1:1 (7mbit:7mbit), but LS itself is of course
+preserved \- ratio will be 1:1 (7Mbit:7Mbit), but LS itself is of course
 unable to guarantee the absolute values themselves \- as it would have to go
 beyond of what the interface is capable of.
 .RE
@@ -287,28 +288,28 @@
 subtrees, arbitrated by their common (root here) parent:
 
 .nf
-R (root) -\ 10mbit
+R (root) -\ 10Mbit
 
-A  \- 7mbit, then 3mbit
-A1 \- 5mbit, then 2mbit
-A2 \- 2mbit, then 1mbit
+A  \- 7Mbit, then 3Mbit
+A1 \- 5Mbit, then 2Mbit
+A2 \- 2Mbit, then 1Mbit
 
-B  \- 3mbit, then 7mbit
+B  \- 3Mbit, then 7Mbit
 .fi
 
 R arbitrates between left subtree (A) and right (B). Assume that A2 and B are
 constantly backlogged, and at some later point A1 becomes backlogged (when all
 other classes are in their 2nd linear part).
 
-What happens now ? B (choice made by R) will \fIalways\fR get 7 mbit as R is
+What happens now? B (choice made by R) will \fIalways\fR get 7 Mbit as R is
 only (obviously) concerned with the ratio between its direct children. Thus A
-subtree gets 3mbit, but its children would want (at the point when A1 became
-backlogged) 5mbit + 1mbit. That's of course impossible, as they can only get
-3mbit due to interface limitation.
+subtree gets 3Mbit, but its children would want (at the point when A1 became
+backlogged) 5Mbit + 1Mbit. That's of course impossible, as they can only get
+3Mbit due to interface limitation.
 
 In the left subtree \- we have the same situation as previously (fair split
 between A1 and A2, but violated guarantees), but in the whole tree \- there's
-no fairness (B got 7mbit, but A1 and A2 have to fit together in 3mbit) and
+no fairness (B got 7Mbit, but A1 and A2 have to fit together in 3Mbit) and
 there's no guarantees for all classes (only B got what it wanted). Even if we
 violated fairness in the A subtree and set A2's service curve to 0, A1 would
 still not get the required bandwidth.
@@ -317,83 +318,83 @@
 .SH "UPPERLIMIT CRITERION"
 .
 UL criterion is an extensions to LS one, that permits sending packets only
-if current real time is bigger than fit\-time ('ft'). So the modified LS
+if current real time is later than fit\-time ('ft'). So the modified LS
 criterion becomes: choose the smallest virtual time from all active children,
 such that fit\-time < current real time also holds. Fit\-time is calculated
-from F(), which is based on UL service curve. As you can see, it's role is
+from F(), which is based on UL service curve. As you can see, its role is
 kinda similar to E() used in RT criterion. Also, for obvious reasons \- you
 can't specify UL service curve without LS one.
 
-Main purpose of UL service curve is to limit HFSC to bandwidth available on the
+The main purpose of the UL service curve is to limit HFSC to bandwidth available on the
 upstream router (think adsl home modem/router, and linux server as
-nat/firewall/etc. with 100mbit+ connection to mentioned modem/router).
+NAT/firewall/etc. with 100Mbit+ connection to mentioned modem/router).
 Typically, it's used to create a single class directly under root, setting
-linear UL service curve to available bandwidth \- and then creating your class
-structure from that class downwards. Of course, you're free to add UL service
-(linear or not) curve to any class with LS criterion.
+a linear UL service curve to available bandwidth \- and then creating your class
+structure from that class downwards. Of course, you're free to add a UL service
+curve (linear or not) to any class with LS criterion.
 
-Important part about UL service curve is, that whenever at some point in time
+An important part about the UL service curve is that whenever at some point in time
 a class doesn't qualify for linksharing due to its fit\-time, the next time it
-does qualify, it will update its virtual time to the smallest virtual time of
-all active children fit for linksharing. This way, one of the main things LS
+does qualify it will update its virtual time to the smallest virtual time of
+all active children fit for linksharing. This way, one of the main things the LS
 criterion tries to achieve \- equality of all virtual times across whole
 hierarchy \- is preserved (in perfectly fluid system with only linear curves,
 all virtual times would be equal).
 
 Without that, 'vt' would lag behind other virtual times, and could cause
-problems. Consider interface with capacity 10mbit, and following leaf classes
+problems. Consider an interface with a capacity of 10Mbit, and the following leaf classes
 (just in case you're skipping this text quickly \- this example shows behavior
 that \f(BIdoesn't happen\fR):
 
 .nf
-A \- ls 5.0mbit
-B \- ls 2.5mbit
-C \- ls 2.5mbit, ul 2.5mbit
+A \- ls 5.0Mbit
+B \- ls 2.5Mbit
+C \- ls 2.5Mbit, ul 2.5Mbit
 .fi
 
-If B was idle, while A and C were constantly backlogged, they would normally
+If B was idle, while A and C were constantly backlogged, A and C would normally
 (as far as LS criterion is concerned) divide bandwidth in 2:1 ratio. But due
-to UL service curve in place, C would get at most 2.5mbit, and A would get the
-remaining 7.5mbit. The longer the backlogged period, the more virtual times of
+to UL service curve in place, C would get at most 2.5Mbit, and A would get the
+remaining 7.5Mbit. The longer the backlogged period, the more the virtual times of
 A and C would drift apart. If B became backlogged at some later point in time,
 its virtual time would be set to (A's\~vt\~+\~C's\~vt)/2, thus blocking A from
-sending any traffic, until B's virtual time catches up with A.
+sending any traffic until B's virtual time catches up with A.
 .
 .SH "SEPARATE LS / RT SCs"
 .
-Another difference from original HFSC paper, is that RT and LS SCs can be
-specified separately. Moreover \- leaf classes are allowed to have only either
-RT SC or LS SC. For interior classes, only LS SCs make sense \- Any RT SC will
+Another difference from the original HFSC paper is that RT and LS SCs can be
+specified separately. Moreover, leaf classes are allowed to have only either
+RT SC or LS SC. For interior classes, only LS SCs make sense: any RT SC will
 be ignored.
 .
 .SH "CORNER CASES"
 .
-Separate service curves for LS and RT criteria can lead to certain traps,
+Separate service curves for LS and RT criteria can lead to certain traps
 that come from "fighting" between ideal linksharing and enforced realtime
 guarantees. Those situations didn't exist in original HFSC paper, where
 specifying separate LS / RT service curves was not discussed.
 
-Consider interface with capacity 10mbit, with following leaf classes:
+Consider an interface with a 10Mbit capacity, with the following leaf classes:
 
 .nf
-A \- ls 5.0mbit, rt 8mbit
-B \- ls 2.5mbit
-C \- ls 2.5mbit
+A \- ls 5.0Mbit, rt 8Mbit
+B \- ls 2.5Mbit
+C \- ls 2.5Mbit
 .fi
 
 Imagine A and C are constantly backlogged. As B is idle, A and C would divide
 bandwidth in 2:1 ratio, considering LS service curve (so in theory \- 6.66 and
-3.33). Alas RT criterion takes priority, so A will get 8mbit and LS will be
-able to compensate class C for only 2 mbit \- this will cause discrepancy
+3.33). Alas RT criterion takes priority, so A will get 8Mbit and LS will be
+able to compensate class C for only 2 Mbit \- this will cause discrepancy
 between virtual times of A and C.
 
-Assume this situation lasts for a lot of time with no idle periods, and
+Assume this situation lasts for a long time with no idle periods, and
 suddenly B becomes active. B's virtual time will be updated to
 (A's\~vt\~+\~C's\~vt)/2, effectively landing in the middle between A's and C's
 virtual time. The effect \- B, having no RT guarantees, will be punished and
 will not be allowed to transfer until C's virtual time catches up.
 
-If the interface had higher capacity \- for example 100mbit, this example
+If the interface had a higher capacity, for example 100Mbit, this example
 would behave perfectly fine though.
 
 Let's look a bit closer at the above example \- it "cleverly" invalidates one
@@ -401,8 +402,8 @@
 times across class hierarchy. Leaf classes without RT service curves are
 literally left to their own fate (governed by messed up virtual times).
 
-Also - it doesn't make much sense. Class A will always be guaranteed up to
-8mbit, and this is more than any absolute bandwidth that could happen from its
+Also, it doesn't make much sense. Class A will always be guaranteed up to
+8Mbit, and this is more than any absolute bandwidth that could happen from its
 LS criterion (excluding trivial case of only A being active). If the bandwidth
 taken by A is smaller than absolute value from LS criterion, the unused part
 will be automatically assigned to other active classes (as A has idling periods
@@ -411,7 +412,7 @@
 if extra speed is needed (e.g. due to latency), non linear service curves
 should be used in such case.
 
-In the other words - LS criterion is meaningless in the above example.
+In the other words: the LS criterion is meaningless in the above example.
 
 You can quickly "workaround" it by making sure each leaf class has RT service
 curve assigned (thus guaranteeing all of them will get some bandwidth), but it
@@ -422,13 +423,13 @@
 "overusing" RT curve a bit:
 
 .nf
-A \- ls 5.0mbit, rt 9mbit/30ms, then 1mbit
-B \- ls 2.5mbit
-C \- ls 2.5mbit
+A \- ls 5.0Mbit, rt 9Mbit/30ms, then 1Mbit
+B \- ls 2.5Mbit
+C \- ls 2.5Mbit
 .fi
 
 Here, the vt of A will "spike" in the initial period, but then A will never get more
-than 1mbit, until B & C catch up. Then everything will be back to normal.
+than 1Mbit until B & C catch up. Then everything will be back to normal.
 .
 .SH "LINUX AND TIMER RESOLUTION"
 .
@@ -457,43 +458,43 @@
 
 This is important to keep those settings in mind, as in scenario like: no
 tickless, no HR timers, frequency set to 100hz \- throttling accuracy would be
-at 10ms. It doesn't automatically mean you would be limited to ~0.8mbit/s
+at 10ms. It doesn't automatically mean you would be limited to ~0.8Mbit/s
 (assuming packets at ~1KB) \- as long as your queues are prepared to cover for
-timer inaccuracy. Of course, in case of e.g. locally generated udp traffic \-
+timer inaccuracy. Of course, in case of e.g. locally generated UDP traffic \-
 appropriate socket size is needed as well. Short example to make it more
 understandable (assume hardcore anti\-schedule settings \- HZ=100, no HR
 timers, no tickless):
 
 .nf
 tc qdisc add dev eth0 root handle 1:0 hfsc default 1
-tc class add dev eth0 parent 1:0 classid 1:1 hfsc rt m2 10mbit
+tc class add dev eth0 parent 1:0 classid 1:1 hfsc rt m2 10Mbit
 .fi
 
-Assuming packet of ~1KB size and HZ=100, that averages to ~0.8mbit \- anything
-beyond it (e.g. the above example with specified rate over 10x bigger) will
+Assuming packet of ~1KB size and HZ=100, that averages to ~0.8Mbit \- anything
+beyond it (e.g. the above example with specified rate over 10x larger) will
 require appropriate queuing and cause bursts every ~10 ms. As you can
 imagine, any HFSC's RT guarantees will be seriously invalidated by that.
 Aforementioned example is mainly important if you deal with old hardware \- as
-it's particularly popular for home server chores. Even then, you can easily
+is particularly popular for home server chores. Even then, you can easily
 set HZ=1000 and have very accurate scheduling for typical adsl speeds.
 
 Anything modern (apic or even hpet msi based timers + \&'tickless system')
-will provide enough accuracy for superb 1gbit scheduling. For example, on one
-of basically cheap dual core AMD boards I have with following settings:
+will provide enough accuracy for superb 1Gbit scheduling. For example, on one
+of my cheap dual-core AMD boards I have the following settings:
 
 .nf
 tc qdisc add dev eth0 parent root handle 1:0 hfsc default 1
-tc class add dev eth0 paretn 1:0 classid 1:1 hfsc rt m2 300mbit
+tc class add dev eth0 parent 1:0 classid 1:1 hfsc rt m2 300mbit
 .fi
 
-And simple:
+And a simple:
 
 .nf
 nc \-u dst.host.com 54321 </dev/zero
 nc \-l \-p 54321 >/dev/null
 .fi
 
-\&...will yield following effects over period of ~10 seconds (taken from
+\&...will yield the following effects over a period of ~10 seconds (taken from
 /proc/interrupts):
 
 .nf
@@ -502,16 +503,16 @@
 .fi
 
 That's roughly 31000/s. Now compare it with HZ=1000 setting. The obvious
-drawback of it is that cpu load can be rather extensive with servicing that
-many timer interrupts. Example with 300mbit RT service curve on 1gbit link is
+drawback of it is that cpu load can be rather high with servicing that
+many timer interrupts. The example with 300Mbit RT service curve on 1Gbit link is
 particularly ugly, as it requires a lot of throttling with minuscule delays.
 
-Also note that it's just an example showing capability of current hardware.
-The above example (essentially 300mbit TBF emulator) is pointless on internal
-interface to begin with \- you will pretty much always want regular LS service
-curve there, and in such scenario HFSC simply doesn't throttle at all.
+Also note that it's just an example showing the capabilities of current hardware.
+The above example (essentially a 300Mbit TBF emulator) is pointless on an internal
+interface to begin with: you will pretty much always want a regular LS service
+curve there, and in such a scenario HFSC simply doesn't throttle at all.
 
-300mbit RT service curve (selected columns from mpstat \-P ALL 1):
+300Mbit RT service curve (selected columns from mpstat \-P ALL 1):
 
 .nf
 10:56:43 PM  CPU  %sys     %irq   %soft   %idle
@@ -520,28 +521,28 @@
 10:56:44 PM    1   4.95   12.87    6.93   73.27
 .fi
 
-So, in rare case you need those speeds with only RT service curve, or with UL
-service curve \- remember about drawbacks.
+So, in the rare case you need those speeds with only a RT service curve, or with a UL
+service curve: remember the drawbacks.
 .
 .SH "CAVEAT: RANDOM ONLINE EXAMPLES"
 .
 For reasons unknown (though well guessed), many examples you can google love to
 overuse UL criterion and stuff it in every node possible. This makes no sense
 and works against what HFSC tries to do (and does pretty damn well). Use UL
-where it makes sense - on the uppermost node to match upstream router's uplink
-capacity. Or - in special cases, such as testing (limit certain subtree to some
-speed) or customers that must never get more than certain speed. In the last
-case you can usually achieve the same by just using RT criterion without LS+UL
+where it makes sense: on the uppermost node to match upstream router's uplink
+capacity. Or in special cases, such as testing (limit certain subtree to some
+speed), or customers that must never get more than certain speed. In the last
+case you can usually achieve the same by just using a RT criterion without LS+UL
 on leaf nodes.
 
-As for router case - remember it's good to differentiate between "traffic to
+As for the router case - remember it's good to differentiate between "traffic to
 router" (remote console, web config, etc.) and "outgoing traffic", so for
 example:
 
 .nf
 tc qdisc add dev eth0 root handle 1:0 hfsc default 0x8002
-tc class add dev eth0 parent 1:0 classid 1:999 hfsc rt m2 50mbit
-tc class add dev eth0 parent 1:0 classid 1:1 hfsc ls m2 2mbit ul m2 2mbit
+tc class add dev eth0 parent 1:0 classid 1:999 hfsc rt m2 50Mbit
+tc class add dev eth0 parent 1:0 classid 1:1 hfsc ls m2 2Mbit ul m2 2Mbit
 .fi
 
 \&... so "internet" tree under 1:1 and "router itself" as 1:999
diff --git a/man/man8/.gitignore b/man/man8/.gitignore
new file mode 100644
index 0000000..4f1a476
--- /dev/null
+++ b/man/man8/.gitignore
@@ -0,0 +1,5 @@
+# these pages are built
+ip-address.8
+ip-link.8
+ip-route.8
+
diff --git a/man/man8/Makefile b/man/man8/Makefile
index 6873a4b..152747a 100644
--- a/man/man8/Makefile
+++ b/man/man8/Makefile
@@ -1,13 +1,15 @@
 TARGETS = ip-address.8 ip-link.8 ip-route.8
 
 MAN8PAGES = $(TARGETS) ip.8 arpd.8 lnstat.8 routel.8 rtacct.8 rtmon.8 ss.8 \
-	tc-bfifo.8 tc-cbq-details.8 tc-cbq.8 tc-drr.8 tc-htb.8 \
-	tc-pfifo.8 tc-pfifo_fast.8 tc-prio.8 tc-red.8 tc-sfq.8 \
-	tc-tbf.8 tc.8 rtstat.8 ctstat.8 nstat.8 routef.8 \
-	tc-sfb.8 tc-netem.8 tc-choke.8 ip-tunnel.8 ip-rule.8 ip-ntable.8 \
-	ip-monitor.8 tc-stab.8 tc-hfsc.8 ip-xfrm.8 ip-netns.8 \
-	ip-neighbour.8 ip-mroute.8 ip-maddress.8 ip-addrlabel.8
-
+	tc.8 tc-bfifo.8 tc-cbq.8 tc-cbq-details.8 tc-choke.8 tc-codel.8 \
+	tc-drr.8 tc-ematch.8 tc-fq_codel.8 tc-hfsc.8 tc-htb.8 tc-pie.8 \
+	tc-mqprio.8 tc-netem.8 tc-pfifo.8 tc-pfifo_fast.8 tc-prio.8 tc-red.8 \
+	tc-sfb.8 tc-sfq.8 tc-stab.8 tc-tbf.8 \
+	bridge.8 rtstat.8 ctstat.8 nstat.8 routef.8 \
+	ip-addrlabel.8 ip-fou.8 ip-gue.8 ip-l2tp.8 \
+	ip-maddress.8 ip-monitor.8 ip-mroute.8 ip-neighbour.8 \
+	ip-netns.8 ip-ntable.8 ip-rule.8 ip-tunnel.8 ip-xfrm.8 \
+	ip-tcp_metrics.8 ip-netconf.8 ip-token.8
 
 all: $(TARGETS)
 
diff --git a/man/man8/arpd.8 b/man/man8/arpd.8
index a14044b..5050a98 100644
--- a/man/man8/arpd.8
+++ b/man/man8/arpd.8
@@ -4,12 +4,12 @@
 arpd \- userspace arp daemon.
 
 .SH SYNOPSIS
-Usage: arpd [ -lkh? ] [ -a N ] [ -b dbase ] [ -B number ] [ -f file ] [-p interval ] [ -n time ] [ -R rate ] [ interfaces ]
+Usage: arpd [ -lkh? ] [ -a N ] [ -b dbase ] [ -B number ] [ -f file ] [-p interval ] [ -n time ] [ -R rate ] [ <INTERFACES> ]
 
 .SH DESCRIPTION
 The
 .B arpd
-daemon collects gratuitous ARP information, saving it on local disk and feeding it to kernel on demand to avoid redundant broadcasting due to limited size of kernel ARP cache.
+daemon collects gratuitous ARP information, saving it on local disk and feeding it to the kernel on demand to avoid redundant broadcasting due to limited size of the kernel ARP cache.
 
 .SH OPTIONS
 .TP
@@ -17,41 +17,41 @@
 Print help
 .TP
 -l
-Dump arpd database to stdout and exit. Output consists of three columns: interface index, IP address and MAC address. Negative entries for dead hosts are also shown, in this case MAC address is replaced by word FAILED followed by colon and time when the fact that host is dead was proven the last time.
+Dump the arpd database to stdout and exit. The output consists of three columns: the interface index, the IP address of the interface, and the MAC address of the interface. Negative entries for dead hosts are also shown, in this case the MAC address is replaced by the word FAILED followed by a colon and the most recent time when the fact that the host is dead was proven.
 .TP
 -f <FILE>
-Read and load arpd database from FILE in text format similar dumped by option -l. Exit after load, probably listing resulting database, if option -l is also given. If FILE is -, stdin is read to get ARP table.
+Read and load an arpd database from FILE in a text format similar to that dumped by option -l. Exit after load, possibly listing resulting database, if option -l is also given. If FILE is -, stdin is read to get the ARP table.
 .TP
 -b <DATABASE>
-location of database file. Default location is /var/lib/arpd/arpd.db
+the location of the database file. The default location is /var/lib/arpd/arpd.db
 .TP
 -a <NUMBER>
-arpd not only passively listens ARP on wire, but also send brodcast queries itself. NUMBER is number of such queries to make before destination is considered as dead. When arpd is started as kernel helper (i.e. with app_solicit enabled in sysctl or even with option -k) without this option and still did not learn enough information, you can observe 1 second gaps in service. Not fatal, but not good.
+With this option, arpd not only passively listens for ARP packets on the interface, but also sends broadcast queries itself. NUMBER is the number of such queries to make before a destination is considered dead. When arpd is started as kernel helper (i.e. with app_solicit enabled in sysctl or even with option -k) without this option and still did not learn enough information, you can observe 1 second gaps in service. Not fatal, but not good.
 .TP
 -k
-Suppress sending broadcast queries by kernel. It takes sense together with option -a.
+Suppress sending broadcast queries by the kernel. This option only makes sense together with option -a.
 .TP
 -n <TIME>
-Timeout of negative cache. When resolution fails arpd suppresses further attempts to resolve for this period. It makes sense only together with option -k This timeout should not be too much longer than boot time of a typical host not supporting gratuitous ARP. Default value is 60 seconds.
+Specifies the timeout of the negative cache. When resolution fails, arpd suppresses further attempts to resolve for this period. This option only makes sense together with option '-k'. This timeout should not be too much longer than the boot time of a typical host not supporting gratuitous ARP. Default value is 60 seconds.
 .TP
 -p <TIME>
-Time to wait in seconds between polling attempts to the kernel ARP table. TIME may be a floating point number.  The default value is 30.
+The time to wait in seconds between polling attempts to the kernel ARP table. TIME may be a floating point number. The default value is 30.
 .TP
 -R <RATE>
 Maximal steady rate of broadcasts sent by arpd in packets per second. Default value is 1.
 .TP
 -B <NUMBER>
-Number of broadcasts sent by <tt/arpd/ back to back. Default value is 3. Together with option <tt/-R/ this option allows to police broadcasting not to exceed B+R*T over any interval of time T.
+The number of broadcasts sent by arpd back to back. Default value is 3. Together with the -R option, this option ensures that the number of ARP queries that are broadcast does not exceed B+R*T over any interval of time T.
 .P
-<INTERFACE> is the name of networking interface to watch. If no interfaces given, arpd monitors all the interfaces. In this case arpd does not adjust sysctl parameters, it is supposed user does this himself after arpd is started.
+<INTERFACES> is a list of names of networking interfaces to watch. If no interfaces are given, arpd monitors all the interfaces. In this case arpd does not adjust sysctl parameters, it is assumed that the user does this himself after arpd is started.
 .P
-Signals
-.br
-arpd exits gracefully syncing database and restoring adjusted sysctl parameters, when receives SIGINT or SIGTERM. SIGHUP syncs database to disk. SIGUSR1 sends some statistics to syslog. Effect of another signals is undefined, they may corrupt database and leave sysctl praameters in an unpredictable state.
+.SH SIGNALS
+.TP
+When arpd receives a SIGINT or SIGTERM signal, it exits gracefully, syncing the database and restoring adjusted sysctl parameters. On a SIGHUP it syncs the database to disk. With SIGUSR1 it sends some statistics to syslog. The effect of any other signals is undefined. In particular, they may corrupt the database and leave the sysctl parameters in an unpredictable state.
 .P
-Note
-.br
-In order for arpd to be able to serve as ARP resolver, kernel must be compiled with the option CONFIG_ARPD and, in the case when interface list in not given on command line, variable app_solicit on interfaces of interest should be in /proc/sys/net/ipv4/neigh/*. If this is not made arpd still collects gratuitous ARP information in its database.
+.SH NOTE
+.TP
+In order for arpd to be able to serve as ARP resolver, the kernel must be compiled with the option CONFIG_ARPD and, in the case when interface list in not given on command line, variable app_solicit on interfaces of interest should be in /proc/sys/net/ipv4/neigh/*. If this is not made arpd still collects gratuitous ARP information in its database.
 .SH EXAMPLES
 .TP
 arpd -b /var/tmp/arpd.db
@@ -64,6 +64,6 @@
 Enable kernel helper, leaving leading role to kernel.
 .TP
 arpd -b /var/tmp/arpd.db -a 3 -k eth0 eth1
-Completely replace kernel resolution on interfaces eth0 and eth1. In this case kernel still does unicast probing to validate entries, but all the broadcast activity is suppressed and made under authority of arpd.
+Completely replace kernel resolution on interfaces eth0 and eth1. In this case the kernel still does unicast probing to validate entries, but all the broadcast activity is suppressed and made under authority of arpd.
 .PP
-This is mode which arpd is supposed to work normally. It is not default just to prevent occasional enabling of too aggressive mode occasionally.
+This is the mode in which arpd normally is supposed to work. It is not the default to prevent occasional enabling of too aggressive a mode.
diff --git a/man/man8/bridge.8 b/man/man8/bridge.8
new file mode 100644
index 0000000..4135d01
--- /dev/null
+++ b/man/man8/bridge.8
@@ -0,0 +1,558 @@
+.TH BRIDGE 8 "1 August 2012" "iproute2" "Linux"
+.SH NAME
+bridge \- show / manipulate bridge addresses and devices
+.SH SYNOPSIS
+
+.ad l
+.in +8
+.ti -8
+.B bridge
+.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OBJECT " := { "
+.BR link " | " fdb " | " mdb " | " vlan " | " monitor " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-s\fR[\fItatistics\fR] |
+\fB\-n\fR[\fIetns\fR] name }
+
+.ti -8
+.BR "bridge link set"
+.B  dev
+.IR DEV
+.IR " [ "
+.B  cost
+.IR COST " ] [ "
+.B  priority
+.IR PRIO " ]  [ "
+.B  state
+.IR STATE "] ["
+.BR guard " { " on " | " off " } ] [ "
+.BR hairpin " { " on " | " off " } ] [ "
+.BR fastleave " { " on " | " off " } ] [ "
+.BR root_block " { " on " | " off " } ] [ "
+.BR learning " { " on " | " off " } ] [ "
+.BR learning_sync " { " on " | " off " } ] [ "
+.BR flood " { " on " | " off " } ] [ "
+.BR hwmode " { " vepa " | " veb " } ] [ "
+.BR self " ]  [ " master " ] "
+
+.ti -8
+.BR "bridge link" " [ " show " ] [ "
+.B  dev
+.IR DEV " ]"
+
+.ti -8
+.BR "bridge fdb" " { " add " | " append " | " del " } "
+.I LLADDR
+.B  dev
+.IR DEV " { "
+.BR local " | " temp " } { "
+.BR self " } { " router " } [ "
+.B  dst
+.IR IPADDR " ] [ "
+.B vni
+.IR VNI " ] ["
+.B port
+.IR PORT " ] ["
+.B via
+.IR DEVICE " ]"
+
+.ti -8
+.BR "bridge fdb" " [ " show " ] [ "
+.B  dev
+.IR DEV " ]"
+
+.ti -8
+.BR "bridge mdb" " { " add " | " del " } "
+.B  dev
+.IR DEV
+.B port
+.IR PORT
+.B grp
+.IR GROUP " [ "
+.BR permanent " | " temp " ]"
+
+.ti -8
+.BR "bridge mdb show " [ "
+.B  dev
+.IR DEV " ]"
+
+.ti -8
+.BR "bridge vlan" " { " add " | " del " } "
+.B  dev
+.IR DEV
+.B  vid 
+.IR VID " [ "
+.BR  pvid " ] [ " untagged " ]  [ "
+.BR  self " ]  [ " master " ] "
+
+.ti -8
+.BR "bridge vlan" " [ " show " ] [ "
+.B  dev
+.IR DEV " ]"
+
+.ti -8
+.BR "bridge monitor" " [ " all " | " neigh " | " link " | " mdb " ]"
+
+.SH OPTIONS
+
+.TP
+.BR "\-V" , " -Version"
+print the version of the
+.B bridge
+utility and exit.
+
+.TP
+.BR "\-s" , " \-stats", " \-statistics"
+output more information. If this option
+is given multiple times, the amount of information increases.
+As a rule, the information is statistics or some time values.
+
+.TP
+.BR "\-n" , " \-net" , " \-netns " <NETNS>
+switches
+.B bridge
+to the specified network namespace
+.IR NETNS .
+Actually it just simplifies executing of:
+
+.B ip netns exec
+.IR NETNS
+.B bridge
+.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+to
+
+.B bridge
+.RI "-n[etns] " NETNS " [ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+
+.SH BRIDGE - COMMAND SYNTAX
+
+.SS
+.I OBJECT
+
+.TP
+.B link
+- Bridge port.
+
+.TP
+.B fdb 
+- Forwarding Database entry.
+
+.TP
+.B mdb
+- Multicast group database entry.
+
+.TP
+.B vlan
+- VLAN filter list.
+
+.SS
+.I COMMAND
+
+Specifies the action to perform on the object.
+The set of possible actions depends on the object type.
+As a rule, it is possible to
+.BR "add" , " delete"
+and
+.B show
+(or
+.B list
+) objects, but some objects do not allow all of these operations
+or have some additional commands. The
+.B help
+command is available for all objects. It prints
+out a list of available commands and argument syntax conventions.
+.sp
+If no command is given, some default command is assumed.
+Usually it is
+.B list
+or, if the objects of this class cannot be listed,
+.BR "help" .
+
+.SH bridge link - bridge port
+
+.B link
+objects correspond to the port devices of the bridge.
+
+.P
+The corresponding commands set and display port status and bridge specific
+attributes.
+
+.SS bridge link set - set bridge specific attributes on a port
+
+.TP
+.BI dev " NAME "
+interface name of the bridge port
+
+.TP
+.BI cost " COST "
+the STP path cost of the specified port.
+
+.TP
+.BI priority " PRIO "
+the STP port priority. The priority value is an unsigned 8-bit quantity
+(number between 0 and 255). This metric is used in the designated port an
+droot port selectio algorithms.
+
+.TP
+.BI state " STATE "
+the operation state of the port. This is primarily used by user space STP/RSTP
+implementation. One may enter a lowercased port state name, or one of the
+numbers below. Negative inputs are ignored, and unrecognized names return an
+error.
+
+.B 0
+- port is DISABLED. Make this port completely inactive.
+.sp
+
+.B 1
+- STP LISTENING state. Only valid if STP is enabled on the brige. In this
+state the port for list for STP BPDUs and drop all other traffic.
+.sp
+
+.B 2
+- STP LEARNING state. Only valid if STP is enabled on the bridge. In this
+state the port will accept traffic only for the purpose of updating MAC
+adress tables.
+.sp
+
+.B 3
+- STP FORWARDING state. Port is fully active.
+.sp
+
+.B 4
+- STP BLOCKING state. Only valid if STP is enabled on the bridge. This state
+is used during the STP election process. In this state, port will only process
+STP BPDUs.
+.sp
+
+.TP
+.BR "guard on " or " guard off "
+Controls whether STP BPUDs will be processed by the bridge port. By default,
+the flag is turned off allowed BPDU processing. Turning this flag on will
+cause the port to stop processing STP BPDUs.
+
+.TP
+.BR "hairpin on " or " hairpin off "
+Controls whether traffic may be send back out of the port on which it was
+received. By default, this flag is turned off and the bridge will not forward
+traffic back out of the receiving port.
+
+.TP
+.BR "fastleave on " or " fastleave off "
+This flag allows the bridge to immediately stop multicast traffic on a port
+that receives IGMP Leave message. It is only used with IGMP snooping is
+enabled on the bridge. By default the flag is off.
+
+.TP
+.BR "root_block on " or " root_block off "
+Controls whether a given port is allowed to become root port or not. Only used
+when STP is enabled on the bridge. By default the flag is off.
+
+.TP
+.BR "learning on " or " learning off "
+Controls whether a given port will learn MAC addresses from received traffic or
+not. If learning if off, the bridge will end up flooding any traffic for which
+it has no FDB entry. By default this flag is on.
+
+.TP
+.BR "learning_sync on " or " learning_sync off "
+Controls whether a given port will sync MAC addresses learned on device port to
+bridge FDB.
+
+.TP
+.BR "flooding on " or " flooding off "
+Controls whether a given port will flood unicast traffic for which there is no FDB entry. By default this flag is on.
+
+.TP
+.BI hwmode
+Some network interface cards support HW bridge functionality and they may be
+configured in different modes. Currently support modes are:
+
+.B vepa
+- Data sent between HW ports is sent on the wire to the external
+switch.
+
+.B veb
+- bridging happens in hardware.
+
+.TP
+.BI self
+link setting is configured on specified physical device
+
+.TP
+.BI master
+link setting is configured on the software bridge (default)
+
+
+.SS bridge link show - list bridge port configuration.
+
+This command displays the current bridge port configuration and flags.
+
+.SH bridge fdb - forwarding database management
+
+.B fdb
+objects contain known Ethernet addresses on a  link.
+
+.P
+The corresponding commands display fdb entries, add new entries,
+append entries,
+and delete old ones.
+
+.SS bridge fdb add - add a new fdb entry
+
+This command creates a new fdb entry.
+
+.TP
+.BI "LLADDR"
+the Ethernet MAC address.
+
+.TP
+.BI dev " DEV"
+the interface to which this address is associated.
+
+.B self
+- the address is associated with a software fdb (default)
+.sp
+
+.B router
+- the destination address is associated with a router.
+Valid if the referenced device is a VXLAN type device and has
+route shortcircuit enabled.
+.sp
+
+.in -8
+The next command line parameters apply only
+when the specified device
+.I DEV
+is of type VXLAN.
+.TP
+.BI dst " IPADDR"
+the IP address of the destination
+VXLAN tunnel endpoint where the Ethernet MAC ADDRESS resides.
+
+.TP
+.BI vni " VNI"
+the VXLAN VNI Network Identifier (or VXLAN Segment ID)
+to use to connect to the remote VXLAN tunnel endpoint.
+If omitted the value specified at vxlan device creation
+will be used.
+
+.TP
+.BI port " PORT"
+the UDP destination PORT number to use to connect to the
+remote VXLAN tunnel endpoint.
+If omitted the default value is used.
+
+.TP
+.BI via " DEVICE"
+device name of the outgoing interface for the
+VXLAN device driver to reach the
+remote VXLAN tunnel endpoint. 
+
+.SS bridge fdb append - append a forwarding database entry
+This command adds a new fdb entry with an already known
+.IR LLADDR .
+Valid only for multicast link layer addresses.
+The command adds support for broadcast and multicast
+Ethernet MAC addresses.
+The Ethernet MAC address is added multiple times into
+the forwarding database and the vxlan device driver
+sends a copy of the data packet to each entry found.
+
+.PP
+The arguments are the same as with
+.BR "bridge fdb add" ,
+
+.SS bridge fdb delete - delete a forwarding database entry
+This command removes an existing fdb entry.
+
+.PP
+The arguments are the same as with
+.BR "bridge fdb add" ,
+
+.SS bridge fdb show - list forwarding entries.
+
+This command displays the current forwarding table.
+
+.PP
+With the
+.B -statistics
+option, the command becomes verbose. It prints out the last updated
+and last used time for each entry.
+
+.SH bridge mdb - multicast group database management
+
+.B mdb
+objects contain known IP multicast group addresses on a link.
+
+.P
+The corresponding commands display mdb entries, add new entries,
+and delete old ones.
+
+.SS bridge mdb add - add a new multicast group database entry
+
+This command creates a new mdb entry.
+
+.TP
+.BI dev " DEV"
+the interface where this group address is associated.
+
+.TP
+.BI port " PORT"
+the port whose link is known to have members of this multicast group.
+
+.TP
+.BI grp " GROUP"
+the IP multicast group address whose members reside on the link connected to
+the port.
+
+.B permanent
+- the mdb entry is permanent
+.sp
+
+.B temp
+- the mdb entry is temporary (default)
+.sp
+
+.in -8
+.SS bridge mdb delete - delete a multicast group database entry
+This command removes an existing mdb entry.
+
+.PP
+The arguments are the same as with
+.BR "bridge mdb add" .
+
+.SS bridge mdb show - list multicast group database entries
+
+This command displays the current multicast group membership table. The table
+is populated by IGMP and MLD snooping in the bridge driver automatically. It
+can be altered by
+.B bridge mdb add
+and
+.B bridge mdb del
+commands manually too.
+
+.TP
+.BI dev " DEV"
+the interface only whose entries should be listed. Default is to list all
+bridge interfaces.
+
+.PP
+With the
+.B -details
+option, the command becomes verbose. It prints out the ports known to have
+a connected router.
+
+.SH bridge vlan - VLAN filter list
+
+.B vlan
+objects contain known VLAN IDs for a link.
+
+.P
+The corresponding commands display vlan filter entries, add new entries,
+and delete old ones.
+
+.SS bridge vlan add - add a new vlan filter entry
+
+This command creates a new vlan filter entry.
+
+.TP
+.BI dev " NAME"
+the interface with which this vlan is associated.
+
+.TP
+.BI vid " VID"
+the VLAN ID that identifies the vlan.
+
+.TP
+.BI pvid
+the vlan specified is to be considered a PVID at ingress.
+Any untagged frames will be assigned to this VLAN.
+
+.TP
+.BI untagged
+the vlan specified is to be treated as untagged on egress.
+
+.TP
+.BI self
+the vlan is configured on the specified physical device. Required if the
+device is the bridge device.
+
+.TP
+.BI master
+the vlan is configured on the software bridge (default).
+
+.SS bridge vlan delete - delete a forwarding database entry
+This command removes an existing fdb entry.
+
+.PP
+The arguments are the same as with
+.BR "bridge vlan add".
+The
+.BR "pvid " and " untagged"
+flags are ignored.
+
+.SS bridge vlan show - list vlan configuration.
+
+This command displays the current VLAN filter table.
+
+.SH bridge monitor - state monitoring
+
+The
+.B bridge
+utility can monitor the state of devices and  addresses
+continuously. This option has a slightly different format.
+Namely, the
+.B monitor
+command is the first in the command line and then the object list follows:
+
+.BR "bridge monitor" " [ " all " |"
+.IR OBJECT-LIST " ]"
+
+.I OBJECT-LIST
+is the list of object types that we want to monitor.
+It may contain
+.BR link ",  " fdb ", and " mdb "."
+If no
+.B file
+argument is given,
+.B bridge
+opens RTNETLINK, listens on it and dumps state changes in the format
+described in previous sections.
+
+.P
+If a file name is given, it does not listen on RTNETLINK,
+but opens the file containing RTNETLINK messages saved in binary format
+and dumps them. Such a history file can be generated with the
+
+
+.SH NOTES
+This command uses facilities added in Linux 3.0.
+
+Although the forwarding table is maintained on a per-bridge device basis
+the bridge device is not part of the syntax. This is a limitation of the
+underlying netlink neighbour message protocol. When displaying the
+forwarding table, entries for all bridges are displayed.
+Add/delete/modify commands determine the underlying bridge device
+based on the bridge to which the corresponding ethernet device is attached.
+
+
+.SH SEE ALSO
+.BR ip (8)
+.SH BUGS
+.RB "Please direct bugreports and patches to: " <netdev@vger.kernel.org>
+
+.SH AUTHOR
+Original Manpage by Stephen Hemminger
diff --git a/man/man8/ip-address.8 b/man/man8/ip-address.8
deleted file mode 100644
index 2eb0709..0000000
--- a/man/man8/ip-address.8
+++ /dev/null
@@ -1,280 +0,0 @@
-.TH "IP\-ADDRESS" 8 "04 March 2012" "iproute2" "Linux"
-.SH "NAME"
-ip-address \- protocol address management
-.SH "SYNOPSIS"
-.sp
-.ad l
-.in +8
-.ti -8
-.B ip
-.RI "[ " OPTIONS " ]"
-.B address
-.RI " { " COMMAND " | "
-.BR help " }"
-.sp
-
-.ti -8
-.BR "ip address" " { " add " | " change " | " replace " } "
-.IB IFADDR " dev " STRING
-.RI "[ " LIFETIME " ] [ " CONFFLAG-LIST " ]"
-
-.ti -8
-.BR "ip address del"
-.IB IFADDR " dev " STRING
-
-.ti -8
-.BR "ip address" " { " show " | " flush " } [ " dev
-.IR STRING " ] [ "
-.B  scope
-.IR SCOPE-ID " ] [ "
-.B  to
-.IR PREFIX " ] [ " FLAG-LIST " ] [ "
-.B  label
-.IR PATTERN " ]"
-
-.ti -8
-.IR IFADDR " := " PREFIX " | " ADDR
-.B  peer
-.IR PREFIX " [ "
-.B  broadcast
-.IR ADDR " ] [ "
-.B  anycast
-.IR ADDR " ] [ "
-.B  label
-.IR STRING " ] [ "
-.B  scope
-.IR SCOPE-ID " ]"
-
-.ti -8
-.IR SCOPE-ID " := "
-.RB "[ " host " | " link " | " global " | "
-.IR NUMBER " ]"
-
-.ti -8
-.IR FLAG-LIST " := [ "  FLAG-LIST " ] " FLAG
-
-.ti -8
-.IR FLAG " := "
-.RB "[ " permanent " | " dynamic " | " secondary " | " primary " | "\
-tentative " | " deprecated " | " dadfailed " | " temporary " | " CONFFLAG-LIST " ]"
-
-.ti -8
-.IR CONFFLAG-LIST " := [ "  CONFFLAG-LIST " ] " CONFFLAG
-
-.ti -8
-.IR CONFFLAG " := "
-.RB "[ " home " | " nodad " ]"
-
-.ti -8
-.IR LIFETIME " := [ "
-.BI valid_lft " LFT"
-.RB "| " preferred_lft
-.IR  LFT " ]"
-
-.ti -8
-.IR LFT " := [ "
-.BR forever " |"
-.IR SECONDS " ]"
-
-.SH "DESCRIPTION"
-The
-.B address
-is a protocol (IPv4 or IPv6) address attached
-to a network device.  Each device must have at least one address
-to use the corresponding protocol.  It is possible to have several
-different addresses attached to one device.  These addresses are not
-discriminated, so that the term
-.B alias
-is not quite appropriate for them and we do not use it in this document.
-.sp
-The
-.B ip address
-command displays addresses and their properties, adds new addresses
-and deletes old ones.
-
-.SS ip address add - add new protocol address.
-
-.TP
-.BI dev " NAME"
-the name of the device to add the address to.
-
-.TP
-.BI local " ADDRESS " (default)
-the address of the interface. The format of the address depends
-on the protocol. It is a dotted quad for IP and a sequence of
-hexadecimal halfwords separated by colons for IPv6. The
-.I ADDRESS
-may be followed by a slash and a decimal number which encodes
-the network prefix length.
-
-.TP
-.BI peer " ADDRESS"
-the address of the remote endpoint for pointopoint interfaces.
-Again, the
-.I ADDRESS
-may be followed by a slash and a decimal number, encoding the network
-prefix length. If a peer address is specified, the local address
-cannot have a prefix length. The network prefix is associated
-with the peer rather than with the local address.
-
-.TP
-.BI broadcast " ADDRESS"
-the broadcast address on the interface.
-.sp
-It is possible to use the special symbols
-.B '+'
-and
-.B '-'
-instead of the broadcast address. In this case, the broadcast address
-is derived by setting/resetting the host bits of the interface prefix.
-
-.TP
-.BI label " NAME"
-Each address may be tagged with a label string.
-In order to preserve compatibility with Linux-2.0 net aliases,
-this string must coincide with the name of the device or must be prefixed
-with the device name followed by colon.
-
-.TP
-.BI scope " SCOPE_VALUE"
-the scope of the area where this address is valid.
-The available scopes are listed in file
-.BR "/etc/iproute2/rt_scopes" .
-Predefined scope values are:
-
-.in +8
-.B global
-- the address is globally valid.
-.sp
-.B link
-- the address is link local, i.e. it is valid only on this device.
-.sp
-.B host
-- the address is valid only inside this host.
-.in -8
-
-.TP
-.BI valid_lft " LFT"
-(IPv6 only) the valid lifetime of this address; see section 5.5.4 of
-RFC 4862. Defaults to
-.BR "forever" .
-
-.TP
-.BI preferred_lft " LFT"
-(IPv6 only) the preferred lifetime of this address; see section 5.5.4
-of RFC 4862. Defaults to
-.BR "forever" .
-
-.TP
-.B home
-(IPv6 only) designates this address the "home address" as defined in
-RFC 6275.
-
-.TP
-.B nodad
-(IPv6 only) do not perform Duplicate Address Detection (RFC 4862) when
-adding this address.
-
-.SS ip address delete - delete protocol address
-.B Arguments:
-coincide with the arguments of
-.B ip addr add.
-The device name is a required argument. The rest are optional.
-If no arguments are given, the first address is deleted.
-
-.SS ip address show - look at protocol addresses
-
-.TP
-.BI dev " NAME " (default)
-name of device.
-
-.TP
-.BI scope " SCOPE_VAL"
-only list addresses with this scope.
-
-.TP
-.BI to " PREFIX"
-only list addresses matching this prefix.
-
-.TP
-.BI label " PATTERN"
-only list addresses with labels matching the
-.IR "PATTERN" .
-.I PATTERN
-is a usual shell style pattern.
-
-.TP
-.BR dynamic " and " permanent
-(IPv6 only) only list addresses installed due to stateless
-address configuration or only list permanent (not dynamic)
-addresses.
-
-.TP
-.B tentative
-(IPv6 only) only list addresses which have not yet passed duplicate
-address detection.
-
-.TP
-.B deprecated
-(IPv6 only) only list deprecated addresses.
-
-.TP
-.B dadfailed
-(IPv6 only) only list addresses which have failed duplicate
-address detection.
-
-.TP
-.B temporary
-(IPv6 only) only list temporary addresses.
-
-.TP
-.BR primary " and " secondary
-only list primary (or secondary) addresses.
-
-.SS ip address flush - flush protocol addresses
-This command flushes the protocol addresses selected by some criteria.
-
-.PP
-This command has the same arguments as
-.B show.
-The difference is that it does not run when no arguments are given.
-
-.PP
-.B Warning:
-This command and other
-.B flush
-commands are unforgiving. They will cruelly purge all the addresses.
-
-.PP
-With the
-.B -statistics
-option, the command becomes verbose. It prints out the number of deleted
-addresses and the number of rounds made to flush the address list.
-If this option is given twice,
-.B ip address flush
-also dumps all the deleted addresses in the format described in the
-previous subsection.
-
-.SH "EXAMPLES"
-.PP
-ip address show dev eth0
-.RS 4
-Shows the addresses assigned to network interface eth0
-.RE
-.PP
-ip addr add 2001:0db8:85a3::0370:7334/64 dev eth1
-.RS 4
-Adds an IPv6 address to network interface eth1
-.RE
-.PP
-ip addr flush dev eth4
-.RS 4
-Removes all addresses from device eth4
-.RE
-
-.SH SEE ALSO
-.br
-.BR ip (8)
-
-.SH AUTHOR
-Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-address.8.in b/man/man8/ip-address.8.in
index 63bf9cb..6e46af8 100644
--- a/man/man8/ip-address.8.in
+++ b/man/man8/ip-address.8.in
@@ -50,8 +50,9 @@
 
 .ti -8
 .IR FLAG " := "
-.RB "[ " permanent " | " dynamic " | " secondary " | " primary " | "\
-tentative " | " deprecated " | " dadfailed " | " temporary " ]"
+.RB "[ " permanent " | " dynamic " | " secondary " | " primary " | \
+[ - ] " tentative " | [ - ] " deprecated " | [ - ] " dadfailed " | "\
+temporary " ]"
 
 .SH "DESCRIPTION"
 The
@@ -79,7 +80,7 @@
 .BI local " ADDRESS " (default)
 the address of the interface. The format of the address depends
 on the protocol. It is a dotted quad for IP and a sequence of
-hexadecimal halfwords separated by colons for IPv6.  The
+hexadecimal halfwords separated by colons for IPv6. The
 .I ADDRESS
 may be followed by a slash and a decimal number which encodes
 the network prefix length.
@@ -90,8 +91,8 @@
 Again, the
 .I ADDRESS
 may be followed by a slash and a decimal number, encoding the network
-prefix length.  If a peer address is specified, the local address
-cannot have a prefix length.  The network prefix is associated
+prefix length. If a peer address is specified, the local address
+cannot have a prefix length. The network prefix is associated
 with the peer rather than with the local address.
 
 .TP
@@ -102,7 +103,7 @@
 .B '+'
 and
 .B '-'
-instead of the broadcast address.  In this case, the broadcast address
+instead of the broadcast address. In this case, the broadcast address
 is derived by setting/resetting the host bits of the interface prefix.
 
 .TP
@@ -138,7 +139,7 @@
 .B Arguments:
 coincide with the arguments of
 .B ip addr add.
-The device name is a required argument.  The rest are optional.
+The device name is a required argument. The rest are optional.
 If no arguments are given, the first address is deleted.
 
 .SS ip address show - look at protocol addresses
@@ -163,6 +164,10 @@
 is a usual shell style pattern.
 
 .TP
+.B up
+only list running interfaces.
+
+.TP
 .BR dynamic " and " permanent
 (IPv6 only) only list addresses installed due to stateless
 address configuration or only list permanent (not dynamic)
@@ -174,15 +179,29 @@
 address detection.
 
 .TP
+.B -tentative
+(IPv6 only) only list addresses which are not in the process of
+duplicate address detection currently.
+
+.TP
 .B deprecated
 (IPv6 only) only list deprecated addresses.
 
 .TP
+.B -deprecated
+(IPv6 only) only list addresses not being deprecated.
+
+.TP
 .B dadfailed
 (IPv6 only) only list addresses which have failed duplicate
 address detection.
 
 .TP
+.B -dadfailed
+(IPv6 only) only list addresses which have not failed duplicate
+address detection.
+
+.TP
 .B temporary
 (IPv6 only) only list temporary addresses.
 
@@ -202,14 +221,14 @@
 .B Warning:
 This command (and other
 .B flush
-commands described below) is pretty dangerous.  If you make a mistake,
+commands described below) is pretty dangerous. If you make a mistake,
 it will not forgive it, but will cruelly purge all the addresses.
 
 .PP
 With the
 .B -statistics
 option, the command becomes verbose. It prints out the number of deleted
-addresses and the number of rounds made to flush the address list.  If
+addresses and the number of rounds made to flush the address list. If
 this option is given twice,
 .B ip address flush
 also dumps all the deleted addresses in the format described in the
diff --git a/man/man8/ip-addrlabel.8 b/man/man8/ip-addrlabel.8
index 3252bd2..5fc18fe 100644
--- a/man/man8/ip-addrlabel.8
+++ b/man/man8/ip-addrlabel.8
@@ -34,12 +34,12 @@
 .BR "ip addrlabel" " { " list " | " flush " }"
 
 .SH "DESCRIPTION"
-IPv6 address label is used for address selection
-described in RFC 3484.  Precedence is managed by userspace,
-and only label is stored in kernel.
+IPv6 address labels are used for address selection;
+they are described in RFC 3484. Precedence is managed by userspace,
+and only the label itself is stored in the kernel.
 
 .SS ip addrlabel add - add an address label
-the command adds an address label entry to the kernel.
+add an address label entry to the kernel.
 .TP
 .BI prefix " PREFIX"
 .TP
@@ -50,15 +50,15 @@
 the label for the prefix.
 0xffffffff is reserved.
 .SS ip addrlabel del - delete an address label
-the command deletes an address label entry in the kernel.
+delete an address label entry from the kernel.
 .B Arguments:
 coincide with the arguments of
 .B ip addrlabel add
-but label is not required.
+but the label is not required.
 .SS ip addrlabel list - list address labels
-the command show contents of address labels.
+list the current address label entries in the kernel.
 .SS ip addrlabel flush - flush address labels
-the command flushes the contents of address labels and it does not restore default settings.
+flush all address labels in the kernel. This does not restore any default settings.
 
 .SH SEE ALSO
 .br
diff --git a/man/man8/ip-fou.8 b/man/man8/ip-fou.8
new file mode 100644
index 0000000..0fa22ee
--- /dev/null
+++ b/man/man8/ip-fou.8
@@ -0,0 +1,76 @@
+.TH IP\-FOU 8 "2 Nov 2014" "iproute2" "Linux"
+.SH "NAME"
+ip-fou \- Foo-over-UDP receive port configuration
+.P
+ip-gue \- Generic UDP Encapsulation receive port configuration
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B fou
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+.ti -8
+.BR "ip fou add"
+.B port
+.IR PORT
+.RB "{ "
+.B gue
+.RI "|"
+.B ipproto
+.IR PROTO
+.RB " }"
+.br
+.ti -8
+.BR "ip fou del"
+.B port
+.IR PORT
+.SH DESCRIPTION
+The
+.B ip fou
+commands are used to create and delete receive ports for Foo-over-UDP
+(FOU) as well as Generic UDP Encapsulation (GUE).
+.PP
+Foo-over-UDP allows encapsulating packets of an IP protocol directly
+over UDP. The receiver infers the protocol of a packet received on
+a FOU UDP port to be the protocol configured for the port.
+.PP
+Generic UDP Encapsulation (GUE) encapsulates packets of an IP protocol
+within UDP and an encapsulation header. The encapsulation header contains the
+IP protocol number for the encapsulated packet.
+.PP
+When creating a FOU or GUE receive port, the port number is specified in
+.I PORT
+argument. If FOU is used, the IP protocol number associated with the port is specified in
+.I PROTO
+argument.
+.PP
+A FOU or GUE receive port is deleted by specifying
+.I PORT
+in the delete command.
+.SH EXAMPLES
+.PP
+.SS Configure a FOU receive port for GRE bound to 7777
+.nf
+# ip fou add port 8888 ipproto 47
+.PP
+.SS Configure a FOU receive port for IPIP bound to 8888
+.nf
+# ip fou add port 8888 ipproto 4
+.PP
+.SS Configure a GUE receive port bound to 9999
+.nf
+# ip fou add port 9999 gue
+.PP
+.SS Delete the GUE receive port bound to 9999
+.nf
+# ip fou del port 9999
+.SH SEE ALSO
+.br
+.BR ip (8)
+.SH AUTHOR
+Tom Herbert <therbert@google.com>
diff --git a/man/man8/ip-gue.8 b/man/man8/ip-gue.8
new file mode 100644
index 0000000..4d2914c
--- /dev/null
+++ b/man/man8/ip-gue.8
@@ -0,0 +1 @@
+.so man8/ip-fou.8
diff --git a/man/man8/ip-l2tp.8 b/man/man8/ip-l2tp.8
index 18a83d4..1738035 100644
--- a/man/man8/ip-l2tp.8
+++ b/man/man8/ip-l2tp.8
@@ -52,6 +52,8 @@
 .IR HEXSTR
 .RB " ]"
 .br
+.RB "[ " l2spec_type " { " none " | " default " } ]"
+.br
 .RB "[ " offset
 .IR OFFSET
 .RB " ] [ " peer_offset
@@ -106,7 +108,7 @@
 manually created by issuing commands at a local system and at a remote
 peer.
 .PP
-L2TPv3 is suitable for Layer-2 tunnelling. Static tunnels are useful
+L2TPv3 is suitable for Layer-2 tunneling. Static tunnels are useful
 to establish network links across IP networks when the tunnels are
 fixed. L2TPv3 tunnels can carry data of more than one session. Each
 session is identified by a session_id and its parent tunnel's
@@ -239,6 +241,12 @@
 the peer. It tells the local system what cookie value to expect to
 find in received L2TP packets. Default is to use no cookie.
 .TP
+.BI l2spec_type " L2SPECTYPE"
+set the layer2specific header type of the session.
+.br
+Valid values are:
+.BR none ", " udp "."
+.TP
 .BI offset " OFFSET"
 sets the byte offset from the L2TP header where user data starts in
 transmitted L2TP data packets. This is hardly ever used. If set, the
@@ -311,9 +319,9 @@
 .PP
 .nf
 site-A:# ip link set l2tpeth0 up mtu 1446
-site-A:# brctl addbr br0
-site-A:# brctl addif br0 l2tpeth0
-site-A:# brctl addif br0 eth0
+site-A:# ip link add br0 type bridge
+site-A:# ip link set l2tpeth0 master br0
+site-A:# ip link set eth0 master br0
 site-A:# ip link set br0 up
 .fi
 .PP
@@ -323,9 +331,9 @@
 .PP
 .nf
 site-A:# ip link set l2tpeth0 up mtu 1446
-site-A:# brctl addbr brvlan5
-site-A:# brctl addif brvlan5 l2tpeth0.5
-site-A:# brctl addif brvlan5 eth1.5
+site-A:# ip link add brvlan5 type bridge
+site-A:# ip link set l2tpeth0.5 master brvlan5
+site-A:# ip link set eth1.5 master brvlan5
 site-A:# ip link set brvlan5 up
 .fi
 .PP
@@ -348,17 +356,17 @@
 transmitted. In such cases, it is important that frames leaving the
 tunnel are reassembled back into a single frame before being
 forwarded on. To do so, enable netfilter connection tracking
-(conntrack) or manually load the Linux netfilter degrag modules at
+(conntrack) or manually load the Linux netfilter defrag modules at
 each tunnel endpoint.
 .PP
 .nf
-site-A:# modprobe nf_degrag_ipv4
+site-A:# modprobe nf_defrag_ipv4
 
-site-B:# modprobe nf_degrag_ipv4
+site-B:# modprobe nf_defrag_ipv4
 .fi
 .PP
-If L2TP is being used over IPv6, use the IPv6 degrag module.
-.SH INTEROPABILITY
+If L2TP is being used over IPv6, use the IPv6 defrag module.
+.SH INTEROPERABILITY
 .PP
 Unmanaged (static) L2TPv3 tunnels are supported by some network
 equipment equipment vendors such as Cisco.
@@ -368,9 +376,14 @@
 link failures in order to automate tearing down and reestablishing
 dynamic tunnels. If a non-Linux peer supports Hello messages in
 unmanaged tunnels, it must be turned off to interoperate with Linux.
+.PP
+Linux defaults to use the Default Layer2SpecificHeader type as defined
+in the L2TPv3 protocol specification, RFC3931. This setting must be
+consistent with that configured at the peer. Some vendor
+implementations (e.g. Cisco) default to use a Layer2SpecificHeader
+type of None.
 .SH SEE ALSO
 .br
-.BR brctl (8)
 .BR ip (8)
 .SH AUTHOR
 James Chapman <jchapman@katalix.com>
diff --git a/man/man8/ip-link.8 b/man/man8/ip-link.8
deleted file mode 100644
index 24d2ec7..0000000
--- a/man/man8/ip-link.8
+++ /dev/null
@@ -1,394 +0,0 @@
-.TH IP\-LINK 8 "20 Dec 2011" "iproute2" "Linux"
-.SH "NAME"
-ip-link \- network device configuration
-.SH "SYNOPSIS"
-.sp
-.ad l
-.in +8
-.ti -8
-.B ip
-.RI "[ " OPTIONS " ]"
-.B link
-.RI  " { " COMMAND " | "
-.BR help " }"
-.sp
-
-.ti -8
-.IR OPTIONS " := { "
-\fB\-V\fR[\fIersion\fR] |
-\fB\-s\fR[\fItatistics\fR] |
-\fB\-r\fR[\fIesolve\fR] |
-\fB\-f\fR[\fIamily\fR] {
-.BR inet " | " inet6 " | " ipx " | " dnet " | " link " } | "
-\fB\-o\fR[\fIneline\fR] }
-
-.ti -8
-.BI "ip link add"
-.RB "[ " link
-.IR DEVICE " ]"
-.RB "[ " name " ]"
-.I NAME
-.br
-.RB "[ " txqueuelen 
-.IR PACKETS " ]"
-.br
-.RB "[ " address
-.IR LLADDR " ]"
-.RB "[ " broadcast
-.IR LLADDR " ]"
-.br
-.RB "[ " mtu
-.IR MTU " ]"
-.br
-.BR type " TYPE"
-.RI "[ " ARGS " ]"
-
-.ti -8
-.IR TYPE " := [ "
-.BR vlan " | " veth " | " vcan " | " dummy " | " ifb " | " macvlan " | " can " | " bridge " ]"
-
-.ti -8
-.BI "ip link delete " DEVICE
-.BI type " TYPE"
-.RI "[ " ARGS " ]"
-
-.ti -8
-.BR "ip link set " {
-.IR DEVICE " | "
-.BI "group " GROUP
-.RB "} { " up " | " down " | " arp " { " on " | " off " } |"
-.br
-.BR promisc " { " on " | " off " } |"
-.br
-.BR allmulticast " { " on " | " off " } |"
-.br
-.BR dynamic " { " on " | " off " } |"
-.br
-.BR multicast " { " on " | " off " } |"
-.br
-.B  txqueuelen
-.IR PACKETS " |"
-.br
-.B  name
-.IR NEWNAME " |"
-.br
-.B  address
-.IR LLADDR " |"
-.B  broadcast
-.IR LLADDR " |"
-.br
-.B  mtu
-.IR MTU " |"
-.br
-.B  netns
-.IR PID " |"
-.br
-.B  netns
-.IR NETNSNAME " |"
-.br
-.B alias
-.IR NAME  " |"
-.br
-.B vf
-.IR NUM " ["
-.B  mac
-.IR LLADDR " ] ["
-.B vlan
-.IR VLANID " [ "
-.B qos
-.IR VLAN-QOS " ] ] ["
-.B rate
-.IR TXRATE " ] ["
-.B spoofchk { on | off }
-] |
-.br
-.B mode
-.IR LINKMODE " |"
-.br
-.B state
-.IR LINKSTATE " |"
-.br
-.B master
-.IR DEVICE
-.br
-.B nomaster
-.BR " }"
-
-
-.ti -8
-.B ip link show
-.RI "[ " DEVICE " | "
-.B group
-.IR GROUP " ]"
-
-.SH "DESCRIPTION"
-.SS ip link add - add virtual link
-
-.TP
-.BI link " DEVICE "
-specifies the physical device to act operate on.
-
-.I NAME
-specifies the name of the new virtual device.
-
-.I TYPE
-specifies the type of the new device.
-.sp
-Link types:
-
-.in +8
-.B vlan
-- 802.1q tagged virtual LAN interface
-.sp
-.B veth
-- Virtual ethernet interface
-.sp
-.B vcan
-- Virtual Local CAN interface
-.sp
-.B dummy
-- Dummy network interface
-.sp
-.B ifb
-- Intermediate Functional Block device
-.sp
-.B macvlan
-- virtual interface base on link layer address (MAC)
-.sp
-.B can
-- Controller Area Network interface
-.sp
-.B bridge
-- Ethernet Bridge device
-.in -8
-
-.SS ip link delete - delete virtual link
-.I DEVICE
-specifies the virtual  device to act operate on.
-.I TYPE
-specifies the type of the device.
-
-
-.TP
-.BI dev " DEVICE "
-specifies the physical device to act operate on.
-
-.SS ip link set - change device attributes
-
-.TP
-.BI dev " DEVICE "
-.I DEVICE
-specifies network device to operate on. When configuring SR-IOV Virtual Fuction
-(VF) devices, this keyword should specify the associated Physical Function (PF)
-device.
-
-.TP
-.BI group " GROUP "
-.I GROUP
-has a dual role: If both group and dev are present, then move the device to the
-specified group.  If only a group is specified, then the command operates on
-all devices in that group.
-
-.TP
-.BR up " and " down
-change the state of the device to
-.B UP
-or
-.BR "DOWN" .
-
-.TP
-.BR "arp on " or " arp off"
-change the
-.B NOARP
-flag on the device.
-
-.TP
-.BR "multicast on " or " multicast off"
-change the
-.B MULTICAST
-flag on the device.
-
-.TP
-.BR "dynamic on " or " dynamic off"
-change the
-.B DYNAMIC
-flag on the device.
-
-.TP
-.BI name " NAME"
-change the name of the device.  This operation is not
-recommended if the device is running or has some addresses
-already configured.
-
-.TP
-.BI txqueuelen " NUMBER"
-.TP
-.BI txqlen " NUMBER"
-change the transmit queue length of the device.
-
-.TP
-.BI mtu " NUMBER"
-change the
-.I MTU
-of the device.
-
-.TP
-.BI address " LLADDRESS"
-change the station address of the interface.
-
-.TP
-.BI broadcast " LLADDRESS"
-.TP
-.BI brd " LLADDRESS"
-.TP
-.BI peer " LLADDRESS"
-change the link layer broadcast address or the peer address when
-the interface is
-.IR "POINTOPOINT" .
-
-.TP
-.BI netns " PID"
-move the device to the network namespace associated with the process
-.IR "PID".
-
-.TP
-.BI netns " NETNSNAME"
-move the device to the network namespace associated with name
-.IR "NETNSNAME".
-
-.TP
-.BI mode " LINKMODE"
-allows setting link mode which determines which RFC2863 operational state
-the device will transistion to when it is brought up. Setting
-.I dormant
-mode changes the behaviour so that device goes into DORMANT state instead
-of UP when driver is ready.
-
-.TP
-.BI state " LINKSTATE"
-allows setting the operational link state. The values (defined in RFC2863)
-are: UP, DOWN, TESTING, UNKNOWN, DORMANT, NOTPRESENT, LOWERLAYERDOWN.
-.TP
-.BI alias " NAME"
-give the device a symbolic name for easy reference.
-
-.TP
-.BI group " GROUP"
-specify the group the device belongs to.
-The available groups are listed in file
-.BR "/etc/iproute2/group" .
-
-.TP
-.BI vf " NUM"
-specify a Virtual Function device to be configured. The associated PF device
-must be specified using the
-.B dev
-parameter.
-
-.in +8
-.BI mac " LLADDRESS"
-- change the station address for the specified VF. The
-.B vf
-parameter must be specified.
-
-.sp
-.BI vlan " VLANID"
-- change the assigned VLAN for the specified VF. When specified, all traffic
-sent from the VF will be tagged with the specified VLAN ID. Incoming traffic
-will be filtered for the specified VLAN ID, and will have all VLAN tags
-stripped before being passed to the VF. Setting this parameter to 0 disables
-VLAN tagging and filtering. The
-.B vf
-parameter must be specified.
-
-.sp
-.BI qos " VLAN-QOS"
-- assign VLAN QOS (priority) bits for the VLAN tag. When specified, all VLAN
-tags transmitted by the VF will include the specified priority bits in the
-VLAN tag. If not specified, the value is assumed to be 0. Both the
-.B vf
-and
-.B vlan
-parameters must be specified. Setting both
-.B vlan
-and
-.B qos
-as 0 disables VLAN tagging and filtering for the VF.
-
-.sp
-.BI rate " TXRATE"
-- change the allowed transmit bandwidth, in Mbps, for the specified VF.
-Setting this parameter to 0 disables rate limiting. The
-.B vf
-parameter must be specified.
-
-.sp
-.BI spoofchk " on|off"
-- turn packet spoof checking on or off for the specified VF.
-.in -8
-
-.TP
-.BI master " DEVICE"
-set master device of the device (enslave device).
-
-.TP
-.BI nomaster
-unset master device of the device (release device).
-
-.PP
-.B Warning:
-If multiple parameter changes are requested,
-.B ip
-aborts immediately after any of the changes have failed.
-This is the only case when
-.B ip
-can move the system to an unpredictable state.  The solution
-is to avoid changing several parameters with one
-.B ip link set
-call.
-
-.SS  ip link show - display device attributes
-
-.TP
-.BI dev " NAME " (default)
-.I NAME
-specifies the network device to show.
-If this argument is omitted all devices in the default group are listed.
-
-.TP
-.BI group " GROUP "
-.I GROUP
-specifies what group of devices to show.
-
-.TP
-.B up
-only display running interfaces.
-
-.SH "EXAMPLES"
-.PP
-ip link show
-.RS 4
-Shows the state of all network interfaces on the system.
-.RE
-.PP
-ip link set dev ppp0 mtu 1400
-.RS 4
-Change the MTU the ppp0 device.
-.RE
-.PP
-ip link add link eth0 name eth0.10 type vlan id 10
-.RS 4
-Creates a new vlan device eth0.10 on device eth0.
-.RE
-.PP
-ip link delete dev eth0.10
-.RS 4
-Removes vlan device.
-.RE
-
-.SH SEE ALSO
-.br
-.BR ip (8)
-
-.SH AUTHOR
-Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in
index 9386cc6..5ad372c 100644
--- a/man/man8/ip-link.8.in
+++ b/man/man8/ip-link.8.in
@@ -1,4 +1,4 @@
-.TH IP\-LINK 8 "20 Dec 2011" "iproute2" "Linux"
+.TH IP\-LINK 8 "13 Dec 2012" "iproute2" "Linux"
 .SH "NAME"
 ip-link \- network device configuration
 .SH "SYNOPSIS"
@@ -16,6 +16,7 @@
 .ti -8
 .IR OPTIONS " := { "
 \fB\-V\fR[\fIersion\fR] |
+\fB\-h\fR[\fIuman-readable\fR] |
 \fB\-s\fR[\fItatistics\fR] |
 \fB\-r\fR[\fIesolve\fR] |
 \fB\-f\fR[\fIamily\fR] {
@@ -39,13 +40,42 @@
 .br
 .RB "[ " mtu
 .IR MTU " ]"
+.RB "[ " index
+.IR IDX " ]"
+.br
+.RB "[ " numtxqueues
+.IR QUEUE_COUNT " ]"
+.RB "[ " numrxqueues
+.IR QUEUE_COUNT " ]"
 .br
 .BR type " TYPE"
 .RI "[ " ARGS " ]"
 
 .ti -8
 .IR TYPE " := [ "
-.BR vlan " | " veth " | " vcan " | " dummy " | " ifb " | " macvlan " | " can " | " bridge " ]"
+.BR bridge " | "
+.BR bond " | "
+.BR can " | "
+.BR dummy " | "
+.BR hsr " | "
+.BR ifb " | "
+.BR ipoib " |"
+.BR macvlan  " | "
+.BR macvtap  " | "
+.BR vcan " | "
+.BR veth " | "
+.BR vlan " | "
+.BR vxlan " |"
+.BR ip6tnl " |"
+.BR ipip " |"
+.BR sit " |"
+.BR gre " |"
+.BR gretap " |"
+.BR ip6gre " |"
+.BR ip6gretap " |"
+.BR vti " |"
+.BR nlmon " |"
+.BR ipvlan " ]"
 
 .ti -8
 .BI "ip link delete " DEVICE
@@ -99,7 +129,12 @@
 .IR VLAN-QOS " ] ] ["
 .B rate
 .IR TXRATE " ] ["
-.B spoofchk { on | off }
+.B max_tx_rate
+.IR TXRATE " ] ["
+.B min_tx_rate
+.IR TXRATE " ] ["
+.B spoofchk { on | off } ] [
+.B state { auto | enable | disable}
 ] |
 .br
 .B master
@@ -113,7 +148,16 @@
 .B ip link show
 .RI "[ " DEVICE " | "
 .B group
-.IR GROUP " ]"
+.IR GROUP " | "
+.BR up " | "
+.B master
+.IR DEVICE " | "
+.B type
+.IR TYPE " ]"
+
+.ti -8
+.B ip link help
+.RI "[ " TYPE " ]"
 
 .SH "DESCRIPTION"
 .SS ip link add - add virtual link
@@ -131,31 +175,418 @@
 Link types:
 
 .in +8
-.B vlan
-- 802.1q tagged virtual LAN interface
+.B bridge
+- Ethernet Bridge device
 .sp
-.B veth
-- Virtual ethernet interface
-.sp
-.B vcan
-- Virtual Local CAN interface
+.B bond
+- Bonding device
 .sp
 .B dummy
 - Dummy network interface
 .sp
+.B hsr
+- High-availability Seamless Redundancy device
+.sp
 .B ifb
 - Intermediate Functional Block device
 .sp
+.B ipoib
+- IP over Infiniband device
+.sp
 .B macvlan
-- virtual interface base on link layer address (MAC)
+- Virtual interface base on link layer address (MAC)
 .sp
-.B can
-- Controller Area Network interface
+.B macvtap
+- Virtual interface based on link layer address (MAC) and TAP.
 .sp
-.B bridge
-- Ethernet Bridge device
+.B vcan
+- Virtual Controller Area Network interface
+.sp
+.B veth
+- Virtual ethernet interface
+.sp
+.BR vlan
+- 802.1q tagged virtual LAN interface
+.sp
+.BR vxlan
+- Virtual eXtended LAN
+.sp
+.BR ip6tnl
+- Virtual tunnel interface IPv4|IPv6 over IPv6
+.sp
+.BR ipip
+- Virtual tunnel interface IPv4 over IPv4
+.sp
+.BR sit
+- Virtual tunnel interface IPv6 over IPv4
+.sp
+.BR gre
+- Virtual tunnel interface GRE over IPv4
+.sp
+.BR gretap
+- Virtual L2 tunnel interface GRE over IPv4
+.sp
+.BR ip6gre
+- Virtual tunnel interface GRE over IPv6
+.sp
+.BR ip6gretap
+- Virtual L2 tunnel interface GRE over IPv6
+.sp
+.BR vti
+- Virtual tunnel interface
+.sp
+.BR nlmon
+- Netlink monitoring device
+.sp
+.BR ipvlan
+- Interface for L3 (IPv6/IPv4) based VLANs
 .in -8
 
+.TP
+.BI numtxqueues " QUEUE_COUNT "
+specifies the number of transmit queues for new device.
+
+.TP
+.BI numrxqueues " QUEUE_COUNT "
+specifies the number of receive queues for new device.
+
+.TP
+.BI index " IDX "
+specifies the desired index of the new virtual device. The link creation fails, if the index is busy.
+
+.TP
+VXLAN Type Support
+For a link of type
+.I VXLAN
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE
+.BI type " vxlan " id " ID"
+.R " [ "
+.BI dev " PHYS_DEV "
+.RB " ] [ { " group " | " remote " } "
+.I IPADDR
+.R " ] [ "
+.BI local " IPADDR "
+.R " ] [ "
+.BI ttl " TTL "
+.R " ] [ "
+.BI tos " TOS "
+.R " ] [ "
+.BI port " MIN MAX "
+.R " ] [ "
+.I "[no]learning "
+.R " ] [ "
+.I "[no]proxy "
+.R " ] [ "
+.I "[no]rsc "
+.R " ] [ "
+.I "[no]l2miss "
+.R " ] [ "
+.I "[no]l3miss "
+.R " ] [ "
+.BI ageing " SECONDS "
+.R " ] [ "
+.BI maxaddress " NUMBER "
+.R " ] [ "
+.B gbp
+.R " ]"
+
+.in +8
+.sp
+.BI  id " VNI "
+- specifies the VXLAN Network Identifer (or VXLAN Segment
+Identifier) to use.
+
+.BI dev " PHYS_DEV"
+- specifies the physical device to use for tunnel endpoint communication.
+
+.sp
+.BI group " IPADDR"
+- specifies the multicast IP address to join.
+This parameter cannot be specified with the
+.B remote
+parameter.
+
+.sp
+.BI remote " IPADDR"
+- specifies the unicast destination IP address to use in outgoing packets
+when the destination link layer address is not known in the VXLAN device
+forwarding database. This parameter cannot be specified with the
+.B group
+parameter.
+
+.sp
+.BI local " IPADDR"
+- specifies the source IP address to use in outgoing packets.
+
+.sp
+.BI ttl " TTL"
+- specifies the TTL value to use in outgoing packets.
+
+.sp
+.BI tos " TOS"
+- specifies the TOS value to use in outgoing packets.
+
+.sp
+.BI port " MIN MAX"
+- specifies the range of port numbers to use as UDP
+source ports to communicate to the remote VXLAN tunnel endpoint.
+
+.sp
+.I [no]learning
+- specifies if unknown source link layer addresses and IP addresses
+are entered into the VXLAN device forwarding database.
+
+.sp
+.I [no]rsc
+- specifies if route short circuit is turned on.
+
+.sp
+.I [no]proxy
+- specifies ARP proxy is turned on.
+
+.sp
+.I [no]l2miss
+- specifies if netlink LLADDR miss notifications are generated.
+
+.sp
+.I [no]l3miss
+- specifies if netlink IP ADDR miss notifications are generated.
+
+.sp
+.BI ageing " SECONDS"
+- specifies the lifetime in seconds of FDB entries learnt by the kernel.
+
+.sp
+.BI maxaddress " NUMBER"
+- specifies the maximum number of FDB entries.
+
+.sp
+.B gbp
+- enables the Group Policy extension (VXLAN-GBP).
+
+.in +4
+Allows to transport group policy context across VXLAN network peers.
+If enabled, includes the mark of a packet in the VXLAN header for outgoing
+packets and fills the packet mark based on the information found in the
+VXLAN header for incomming packets.
+
+Format of upper 16 bits of packet mark (flags);
+
+.in +2
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+.br
+|-|-|-|-|-|-|-|-|-|D|-|-|A|-|-|-|
+.br
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+.B D :=
+Don't Learn bit. When set, this bit indicates that the egress
+VTEP MUST NOT learn the source address of the encapsulated frame.
+
+.B A :=
+Indicates that the group policy has already been applied to
+this packet. Policies MUST NOT be applied by devices when the A bit is set.
+.in -2
+
+Format of lower 16 bits of packet mark (policy ID):
+
+.in +2
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+.br
+|        Group Policy ID        |
+.br
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+.in -2
+
+Example:
+  iptables -A OUTPUT [...] -j MARK --set-mark 0x800FF
+
+.in -4
+
+.in -8
+
+.TP
+GRE, IPIP, SIT Type Support
+For a link of types
+.I GRE/IPIP/SIT
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE
+.BR type " { gre | ipip | sit } "
+.BI " remote " ADDR " local " ADDR
+.R " [ "
+.BR encap " { fou | gue | none } "
+.R " ] [ "
+.BI "encap-sport { " PORT " | auto } "
+.R " ] [ "
+.BI "encap-dport " PORT
+.R " ] [ "
+.I " [no]encap-csum "
+.R " ] [ "
+.I " [no]encap-remcsum "
+.R " ]"
+
+.in +8
+.sp
+.BI  remote " ADDR "
+- specifies the remote address of the tunnel.
+
+.sp
+.BI  local " ADDR "
+- specifies the fixed local address for tunneled packets.
+It must be an address on another interface on this host.
+
+.sp
+.BR encap " { fou | gue | none } "
+- specifies type of secondary UDP encapsulation. "fou" indicates
+Foo-Over-UDP, "gue" indicates Generic UDP Encapsulation.
+
+.sp
+.BI "encap-sport { " PORT " | auto } "
+- specifies the source port in UDP encapsulation.
+.IR PORT
+indicates the port by number, "auto"
+indicates that the port number should be chosen automatically
+(the kernel picks a flow based on the flow hash of the
+encapsulated packet).
+
+.sp
+.I [no]encap-csum
+- specifies if UDP checksums are enabled in the secondary
+encapsulation.
+
+.sp
+.I [no]encap-remcsum
+- specifies if Remote Checksum Offload is enabled. This is only
+applicable for Generic UDP Encapsulation.
+
+.in -8
+
+.TP
+IP6GRE/IP6GRETAP Type Support
+For a link of type
+.I IP6GRE/IP6GRETAP
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE
+.BI type " { ip6gre | ip6gretap }  " remote " ADDR " local " ADDR
+.R " [ "
+.I "[i|o]seq]"
+.R " ] [ "
+.I "[i|o]key" KEY
+.R " ] [ "
+.I " [i|o]csum "
+.R " ] [ "
+.BI hoplimit " TTL "
+.R " ] [ "
+.BI encaplimit " ELIM "
+.R " ] [ "
+.BI tclass " TCLASS "
+.R " ] [ "
+.BI flowlabel " FLOWLABEL "
+.R " ] [ "
+.BI "dscp inherit"
+.R " ] [ "
+.BI dev " PHYS_DEV "
+.R " ]"
+
+.in +8
+.sp
+.BI  remote " ADDR "
+- specifies the remote IPv6 address of the tunnel.
+
+.sp
+.BI  local " ADDR "
+- specifies the fixed local IPv6 address for tunneled packets.
+It must be an address on another interface on this host.
+
+.sp
+.BI  [i|o]seq
+- serialize packets.
+The
+.B oseq
+flag enables sequencing of outgoing packets.
+The
+.B iseq
+flag requires that all input packets are serialized.
+
+.sp
+.BI  [i|o]key " KEY"
+- use keyed GRE with key
+.IR KEY ". "KEY
+is either a number or an IPv4 address-like dotted quad.
+The
+.B key
+parameter specifies the same key to use in both directions.
+The
+.BR ikey " and " okey
+parameters specify different keys for input and output.
+
+.sp
+.BI  [i|o]csum
+- generate/require checksums for tunneled packets.
+The
+.B ocsum
+flag calculates checksums for outgoing packets.
+The
+.B icsum
+flag requires that all input packets have the correct
+checksum. The
+.B csum
+flag is equivalent to the combination
+.BR "icsum ocsum" .
+
+.sp
+.BI  hoplimit " TTL"
+- specifies Hop Limit value to use in outgoing packets.
+
+.sp
+.BI  encaplimit " ELIM"
+- specifies a fixed encapsulation limit. Default is 4.
+
+.sp
+.BI  flowlabel " FLOWLABEL"
+- specifies a fixed flowlabel.
+
+.sp
+.BI  tclass " TCLASS"
+- specifies the traffic class field on
+tunneled packets, which can be specified as either a two-digit
+hex value (e.g. c0) or a predefined string (e.g. internet).
+The value
+.B inherit
+causes the field to be copied from the original IP header. The
+values
+.BI "inherit/" STRING
+or
+.BI "inherit/" 00 ".." ff
+will set the field to
+.I STRING
+or
+.IR 00 ".." ff
+when tunneling non-IP packets. The default value is 00.
+
+.in -8
+
+.TP
+IPoIB Type Support
+For a link of type
+.I IPoIB
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE " name " NAME
+.BI type " ipoib [ " pkey " PKEY ] [" mode " MODE " ]
+
+.in +8
+.sp
+.BI  pkey " PKEY "
+- specifies the IB P-Key to use.
+
+.BI  mode " MODE "
+- specifies the mode (datagram or connected) to use.
+
 .SS ip link delete - delete virtual link
 .I DEVICE
 specifies the virtual  device to act operate on.
@@ -172,7 +603,7 @@
 .TP
 .BI dev " DEVICE "
 .I DEVICE
-specifies network device to operate on. When configuring SR-IOV Virtual Fuction
+specifies network device to operate on. When configuring SR-IOV Virtual Function
 (VF) devices, this keyword should specify the associated Physical Function (PF)
 device.
 
@@ -180,7 +611,7 @@
 .BI group " GROUP "
 .I GROUP
 has a dual role: If both group and dev are present, then move the device to the
-specified group.  If only a group is specified, then the command operates on
+specified group. If only a group is specified, then the command operates on
 all devices in that group.
 
 .TP
@@ -206,11 +637,13 @@
 .BR "dynamic on " or " dynamic off"
 change the
 .B DYNAMIC
-flag on the device.
+flag on the device. Indicates that address can change when interface goes down (currently
+.B NOT
+used by the Linux).
 
 .TP
 .BI name " NAME"
-change the name of the device.  This operation is not
+change the name of the device. This operation is not
 recommended if the device is running or has some addresses
 already configured.
 
@@ -241,14 +674,29 @@
 .IR "POINTOPOINT" .
 
 .TP
-.BI netns " PID"
-move the device to the network namespace associated with the process
-.IR "PID".
-
-.TP
-.BI netns " NETNSNAME"
+.BI netns " NETNSNAME " \fR| " PID"
 move the device to the network namespace associated with name
-.IR "NETNSNAME".
+.IR "NETNSNAME " or
+.RI process " PID".
+
+Some devices are not allowed to change network namespace: loopback, bridge,
+ppp, wireless. These are network namespace local devices. In such case
+.B ip
+tool will return "Invalid argument" error. It is possible to find out if device is local
+to a single network namespace by checking
+.B netns-local
+flag in the output of the
+.BR ethtool ":"
+
+.in +8
+.B ethtool -k
+.I DEVICE
+.in -8
+
+To change network namespace for wireless devices the
+.B iw
+tool can be used. But it allows to change network namespace only for physical devices and by process
+.IR PID .
 
 .TP
 .BI alias " NAME"
@@ -299,14 +747,36 @@
 
 .sp
 .BI rate " TXRATE"
-- change the allowed transmit bandwidth, in Mbps, for the specified VF.
-Setting this parameter to 0 disables rate limiting. The
+-- change the allowed transmit bandwidth, in Mbps, for the specified VF.
+Setting this parameter to 0 disables rate limiting.
+.B vf
+parameter must be specified.
+Please use new API
+.B "max_tx_rate"
+option instead.
+
+.sp
+.BI max_tx_rate " TXRATE"
+- change the allowed maximum transmit bandwidth, in Mbps, for the specified VF.
+.B vf
+parameter must be specified.
+
+.sp
+.BI min_tx_rate " TXRATE"
+- change the allowed minimum transmit bandwidth, in Mbps, for the specified VF.
+Minimum TXRATE should be always <= Maximum TXRATE.
 .B vf
 parameter must be specified.
 
 .sp
 .BI spoofchk " on|off"
 - turn packet spoof checking on or off for the specified VF.
+.sp
+.BI state " auto|enable|disable"
+- set the virtual link state as seen by the specified VF. Setting to auto means a
+reflection of the PF link state, enable lets the VF to communicate with other VFs on
+this host even if the PF link state is down, disable causes the HW to drop any packets
+sent by the VF.
 .in -8
 
 .TP
@@ -324,7 +794,7 @@
 aborts immediately after any of the changes have failed.
 This is the only case when
 .B ip
-can move the system to an unpredictable state.  The solution
+can move the system to an unpredictable state. The solution
 is to avoid changing several parameters with one
 .B ip link set
 call.
@@ -346,6 +816,49 @@
 .B up
 only display running interfaces.
 
+.TP
+.BI master " DEVICE "
+.I DEVICE
+specifies the master device which enslaves devices to show.
+
+.TP
+.BI type " TYPE "
+.I TYPE
+specifies the type of devices to show.
+
+.TP
+The show command has additional formatting options:
+
+.RS
+.TP
+.BR "\-s" , " \-stats", " \-statistics"
+output more statistics about packet usage.
+
+.TP
+.BR "\-d", " \-details"
+output more detailed information.
+
+.TP
+.BR "\-h", " \-human", " \-human-readble"
+output statistics with human readable values number followed by suffix
+
+.TP
+.BR "\-iec"
+print human readable rates in IEC units (ie. 1K = 1024).
+.RE
+
+.SS  ip link help - display help
+
+.PP
+.I "TYPE"
+specifies which help of link type to dislpay.
+
+.SS
+.I GROUP
+may be a number or a string from the file
+.B /etc/iproute2/group
+which can be manually filled.
+
 .SH "EXAMPLES"
 .PP
 ip link show
@@ -353,6 +866,21 @@
 Shows the state of all network interfaces on the system.
 .RE
 .PP
+ip link show type bridge
+.RS 4
+Shows the bridge devices.
+.RE
+.PP
+ip link show type vlan
+.RS 4
+Shows the vlan devices.
+.RE
+.PP
+ip link show master br0
+.RS 4
+Shows devices enslaved by br0
+.RE
+.PP
 ip link set dev ppp0 mtu 1400
 .RS 4
 Change the MTU the ppp0 device.
@@ -368,9 +896,24 @@
 Removes vlan device.
 .RE
 
+ip link help gre
+.RS 4
+Display help for the gre link type.
+.RE
+.PP
+ip link add name tun1 type ipip remote 192.168.1.1
+local 192.168.1.2 ttl 225 encap gue encap-sport auto
+encap-dport 5555 encap-csum encap-remcsum
+.RS 4
+Creates an IPIP that is encapsulated with Generic UDP Encapsulation,
+and the outer UDP checksum and remote checksum offload are enabled.
+
+.RE
+
 .SH SEE ALSO
 .br
-.BR ip (8)
+.BR ip (8),
+.BR ip-netns (8)
 
 .SH AUTHOR
 Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-maddress.8 b/man/man8/ip-maddress.8
index afae551..f3432bb 100644
--- a/man/man8/ip-maddress.8
+++ b/man/man8/ip-maddress.8
@@ -15,11 +15,11 @@
 .ti -8
 
 .BR "ip maddress" " [ " add " | " del " ]"
-.IB MULTIADDR " dev " STRING
+.IB MULTIADDR " dev " NAME
 
 .ti -8
 .BR "ip maddress show" " [ " dev
-.IR STRING " ]"
+.IR NAME " ]"
 
 .SH DESCRIPTION
 .B maddress
@@ -31,20 +31,25 @@
 .BI dev " NAME " (default)
 the device name.
 
-.SS ip maddress add - add a multicast address
-.SS ip maddress delete - delete a multicast address
-these commands attach/detach a static link layer multicast address
+.TP
+.B ip maddress add - add a multicast address
+.TP
+.B ip maddress delete - delete a multicast address
+.sp
+These commands attach/detach a static link-layer multicast address
 to listen on the interface.
 Note that it is impossible to join protocol multicast groups
-statically.  This command only manages link layer addresses.
+statically. This command only manages link-layer addresses.
 
+.RS
 .TP
 .BI address " LLADDRESS " (default)
-the link layer multicast address.
+the link-layer multicast address.
 
 .TP
 .BI dev " NAME"
 the device to join/leave this multicast address.
+.RE
 
 .SH SEE ALSO
 .br
diff --git a/man/man8/ip-monitor.8 b/man/man8/ip-monitor.8
index 351a744..1de0ca9 100644
--- a/man/man8/ip-monitor.8
+++ b/man/man8/ip-monitor.8
@@ -1,4 +1,4 @@
-.TH IP\-MONITOR 8 "20 Dec 2011" "iproute2" "Linux"
+.TH IP\-MONITOR 8 "13 Dec 2012" "iproute2" "Linux"
 .SH "NAME"
 ip-monitor, rtmon \- state monitoring
 .SH "SYNOPSIS"
@@ -6,26 +6,50 @@
 .ad l
 .in +8
 .ti -8
-.BR "ip monitor" " [ " all " |"
-.IR LISTofOBJECTS " ]"
+.BR "ip " " [ "
+.IR ip-OPTIONS " ]"
+.BR  "monitor" " [ " all " |"
+.IR OBJECT-LIST " ] ["
+.BI file " FILENAME "
+] [
+.BI dev " DEVICE "
+]
 .sp
 
+.SH OPTIONS
+
+.TP
+.BR "\-t" , " \-timestamp"
+Prints timestamp before the event message on the separated line in format:
+    Timestamp: <Day> <Month> <DD> <hh:mm:ss> <YYYY> <usecs> usec
+    <EVENT>
+
+.TP
+.BR "\-ts" , " \-tshort"
+Prints short timestamp before the event message on the same line in format:
+    [<YYYY>-<MM>-<DD>T<hh:mm:ss>.<ms>] <EVENT>
+
 .SH DESCRIPTION
 The
 .B ip
 utility can monitor the state of devices, addresses
-and routes continuously.  This option has a slightly different format.
+and routes continuously. This option has a slightly different format.
 Namely, the
 .B monitor
 command is the first in the command line and then the object list follows:
 
 .BR "ip monitor" " [ " all " |"
-.IR LISTofOBJECTS " ]"
+.IR OBJECT-LIST " ] ["
+.BI file " FILENAME "
+] [
+.BI dev " DEVICE "
+]
 
 .I OBJECT-LIST
 is the list of object types that we want to monitor.
 It may contain
-.BR link ", " address " and " route "."
+.BR link ", " address ", " route ", " mroute ", " prefix ", "
+.BR neigh ", " netconf " and " rule "."
 If no
 .B file
 argument is given,
@@ -34,11 +58,14 @@
 described in previous sections.
 
 .P
-If a file name is given, it does not listen on RTNETLINK,
-but opens the file containing RTNETLINK messages saved in binary format
-and dumps them.  Such a history file can be generated with the
+If the
+.BI file
+option is given, the program does not listen on RTNETLINK,
+but opens the given file, and dumps its contents. The file
+should contain RTNETLINK messages saved in binary format.
+Such a file can be generated with the
 .B rtmon
-utility.  This utility has a command line syntax similar to
+utility. This utility has a command line syntax similar to
 .BR "ip monitor" .
 Ideally,
 .B rtmon
@@ -53,12 +80,17 @@
 later.
 
 .P
-Certainly, it is possible to start
+Nevertheless, it is possible to start
 .B rtmon
 at any time.
 It prepends the history with the state snapshot dumped at the moment
 of starting.
 
+.P
+If the
+.BI dev
+option is given, the program prints only events related to this device.
+
 .SH SEE ALSO
 .br
 .BR ip (8)
diff --git a/man/man8/ip-mroute.8 b/man/man8/ip-mroute.8
index 98aab88..e89b6b2 100644
--- a/man/man8/ip-mroute.8
+++ b/man/man8/ip-mroute.8
@@ -1,4 +1,4 @@
-.TH IP\-MROUTE 8 "20 Dec 2011" "iproute2" "Linux"
+.TH IP\-MROUTE 8 "13 Dec 2012" "iproute2" "Linux"
 .SH "NAME"
 ip-mroute \- multicast routing cache management
 .SH "SYNOPSIS"
@@ -6,16 +6,19 @@
 .ad l
 .in +8
 .ti -8
-.BR "ip mroute show" " ["
+.BR "ip " " [ ip-OPTIONS ] " "mroute show" " [ [ "
+.BR " to " " ] "
 .IR PREFIX " ] [ "
 .B  from
 .IR PREFIX " ] [ "
 .B  iif
-.IR DEVICE " ]"
+.IR DEVICE " ] [ "
+.B table
+.IR TABLE_ID " ] "
 
 .SH DESCRIPTION
 .B mroute
-objects are multicast routing cache entries created by a user level
+objects are multicast routing cache entries created by a user-level
 mrouting daemon (f.e.
 .B pimd
 or
@@ -25,7 +28,7 @@
 Due to the limitations of the current interface to the multicast routing
 engine, it is impossible to change
 .B mroute
-objects administratively, so we may only display them.  This limitation
+objects administratively, so we can only display them. This limitation
 will be removed in the future.
 
 .SS ip mroute show - list mroute cache entries
@@ -42,6 +45,11 @@
 .BI from " PREFIX"
 the prefix selecting the IP source addresses of the multicast route.
 
+.TP
+.BI table " TABLE_ID"
+the table id selecting the multicast table. It can be
+.BR local ", " main ", " default ", " all " or a number."
+
 .SH SEE ALSO
 .br
 .BR ip (8)
diff --git a/man/man8/ip-neighbour.8 b/man/man8/ip-neighbour.8
index 34980c5..b0fc0dd 100644
--- a/man/man8/ip-neighbour.8
+++ b/man/man8/ip-neighbour.8
@@ -40,16 +40,23 @@
 objects that establish bindings between protocol addresses and
 link layer addresses for hosts sharing the same link.
 Neighbour entries are organized into tables. The IPv4 neighbour table
-is known by another name - the ARP table.
+is also known by another name - the ARP table.
 
 .P
 The corresponding commands display neighbour bindings
 and their properties, add new neighbour entries and delete old ones.
 
-.SS ip neighbour add - add a new neighbour entry
-.SS ip neighbour change - change an existing entry
-.SS ip neighbour replace - add a new entry or change an existing one
-
+.TP
+ip neighbour add
+add a new neighbour entry
+.TP
+ip neighbour change
+change an existing entry
+.TP
+ip neighbour replace
+add a new entry or change an existing one
+.RS
+.PP
 These commands create new neighbour records or update existing ones.
 
 .TP
@@ -74,33 +81,31 @@
 is an abbreviation for 'Neighbour Unreachability Detection'.
 The state can take one of the following values:
 
-.in +8
+.TP
 .B permanent
-- the neighbour entry is valid forever and can be only
+the neighbour entry is valid forever and can be only
 be removed administratively.
-.sp
-
+.TP
 .B noarp
-- the neighbour entry is valid. No attempts to validate
+the neighbour entry is valid. No attempts to validate
 this entry will be made but it can be removed when its lifetime expires.
-.sp
-
+.TP
 .B reachable
-- the neighbour entry is valid until the reachability
+the neighbour entry is valid until the reachability
 timeout expires.
-.sp
-
+.TP
 .B stale
-- the neighbour entry is valid but suspicious.
+the neighbour entry is valid but suspicious.
 This option to
 .B ip neigh
 does not change the neighbour state if it was valid and the address
 is not changed by this command.
-.in -8
+.RE
 
-.SS ip neighbour delete - delete a neighbour entry
-This command invalidates a neighbour entry.
-
+.TP
+ip neighbour delete
+delete a neighbour entry
+.RS
 .PP
 The arguments are the same as with
 .BR "ip neigh add" ,
@@ -119,11 +124,12 @@
 on a
 .B NOARP
 interface or if the address is multicast or broadcast.
+.RE
 
-.SS ip neighbour show - list neighbour entries
-
-This commands displays neighbour tables.
-
+.TP
+ip neighbour show
+list neighbour entries
+.RS
 .TP
 .BI to " ADDRESS " (default)
 the prefix selecting the neighbours to list.
@@ -146,19 +152,19 @@
 .I NUD_STATE
 takes values listed below or the special value
 .B all
-which means all states.  This option may occur more than once.
+which means all states. This option may occur more than once.
 If this option is absent,
 .B ip
 lists all entries except for
 .B none
 and
 .BR "noarp" .
+.RE
 
-.SS ip neighbour flush - flush neighbour entries
-This command flushes neighbour tables, selecting
-entries to flush by some criteria.
-
-.PP
+.TP
+ip neighbour flush
+flush neighbour entries
+.RS
 This command has the same arguments as
 .B show.
 The differences are that it does not run when no arguments are given,
@@ -170,12 +176,13 @@
 .PP
 With the
 .B -statistics
-option, the command becomes verbose.  It prints out the number of
+option, the command becomes verbose. It prints out the number of
 deleted neighbours and the number of rounds made to flush the
-neighbour table.  If the option is given
+neighbour table. If the option is given
 twice,
 .B ip neigh flush
 also dumps all the deleted neighbours.
+.RE
 
 .SH EXAMPLES
 .PP
diff --git a/man/man8/ip-netconf.8 b/man/man8/ip-netconf.8
new file mode 100644
index 0000000..2718258
--- /dev/null
+++ b/man/man8/ip-netconf.8
@@ -0,0 +1,36 @@
+.TH IP\-NETCONF 8 "13 Dec 2012" "iproute2" "Linux"
+.SH "NAME"
+ip-netconf \- network configuration monitoring
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.BR "ip " " [ ip-OPTIONS ] " "netconf show" " [ "
+.B dev
+.IR NAME " ]"
+
+.SH DESCRIPTION
+The
+.B ip netconf
+utility can monitor IPv4 and IPv6 parameters (see
+.BR "/proc/sys/net/ipv[4|6]/conf/[all|DEV]/" ")"
+like forwarding, rp_filter
+or mc_forwarding status.
+
+If no interface is specified, the entry
+.B all
+is displayed.
+
+.SS ip netconf show - display network parameters
+
+.TP
+.BI dev " NAME"
+the name of the device to display network parameters for.
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Nicolas Dichtel <nicolas.dichtel@6wind.com>
diff --git a/man/man8/ip-netns.8 b/man/man8/ip-netns.8
index 7fe9eb9..80a4ad1 100644
--- a/man/man8/ip-netns.8
+++ b/man/man8/ip-netns.8
@@ -1,4 +1,4 @@
-.TH IP\-NETNS 8 "20 Dec 2011" "iproute2" "Linux"
+.TH IP\-NETNS 8 "16 Jan 2013" "iproute2" "Linux"
 .SH NAME
 ip-netns \- process network namespace management
 .SH SYNOPSIS
@@ -16,29 +16,51 @@
 .BR "ip netns" " { " list " } "
 
 .ti -8
-.BR "ip netns" " { " add " | " delete " } "
+.B ip netns add
 .I NETNSNAME
 
 .ti -8
-.BR "ip netns exec "
-.I NETNSNAME command ...
+.B ip [-all] netns del
+.RI "[ " NETNSNAME " ]"
+
+.ti -8
+.BR "ip netns" " { " set " } "
+.I NETNSNAME NETNSID
+
+.ti -8
+.BR "ip netns identify"
+.RI "[ " PID " ]"
+
+.ti -8
+.BR "ip netns pids"
+.I NETNSNAME
+
+.ti -8
+.BR "ip [-all] netns exec "
+.RI "[ " NETNSNAME " ] " command ...
+
+.ti -8
+.BR "ip netns monitor"
 
 .SH DESCRIPTION
 A network namespace is logically another copy of the network stack,
-with it's own routes, firewall rules, and network devices.
+with its own routes, firewall rules, and network devices.
+
+By default a process inherits its network namespace from its parent. Initially all
+the processes share the same default network namespace from the init process.
 
 By convention a named network namespace is an object at
 .BR "/var/run/netns/" NAME
-that can be opened.  The file descriptor resulting from opening
+that can be opened. The file descriptor resulting from opening
 .BR "/var/run/netns/" NAME
-refers to the specified network namespace.  Holding that file
-descriptor open keeps the network namespace alive.  The file
+refers to the specified network namespace. Holding that file
+descriptor open keeps the network namespace alive. The file
 descriptor can be used with the
 .B setns(2)
 system call to change the network namespace associated with a task.
 
-The convention for network namespace aware applications is to look
-for global network configuration files first in
+For applications that are aware of network namespaces, the convention
+is to look for global network configuration files first in
 .BR "/etc/netns/" NAME "/"
 then in
 .BR "/etc/".
@@ -53,12 +75,125 @@
 bind mounting all of the per network namespace configure files into
 their traditional location in /etc.
 
-.SS ip netns list - show all of the named network namespaces
-.SS ip netns add NAME - create a new named network namespace
-.SS ip netns delete NAME - delete the name of a network namespace
-.SS ip netns exec NAME cmd ... - Run cmd in the named network namespace
+.TP
+.B ip netns list - show all of the named network namespaces
+.sp
+This command displays all of the network namespaces in /var/run/netns
+
+.TP
+.B ip netns add NAME - create a new named network namespace
+.sp
+If NAME is available in /var/run/netns/ this command creates a new
+network namespace and assigns NAME.
+
+.TP
+.B ip [-all] netns delete [ NAME ] - delete the name of a network namespace(s)
+.sp
+If NAME is present in /var/run/netns it is umounted and the mount
+point is removed. If this is the last user of the network namespace the
+network namespace will be freed and all physical devices will be moved to the
+default one, otherwise the network namespace persists until it has no more
+users. ip netns delete may fail if the mount point is in use in another mount
+namespace.
+
+If
+.B -all
+option was specified then all the network namespace names will be removed.
+
+It is possible to lose the physical device when it was moved to netns and
+then this netns was deleted with a running process:
+
+.RS 10
+$ ip netns add net0
+.RE
+.RS 10
+$ ip link set dev eth0 netns net0
+.RE
+.RS 10
+$ ip netns exec net0 SOME_PROCESS_IN_BACKGROUND
+.RE
+.RS 10
+$ ip netns del net0
+.RE
+
+.RS
+and eth0 will appear in the default netns only after SOME_PROCESS_IN_BACKGROUND
+will exit or will be killed. To prevent this the processes running in net0
+should be killed before deleting the netns:
+
+.RE
+.RS 10
+$ ip netns pids net0 | xargs kill
+.RE
+.RS 10
+$ ip netns del net0
+.RE
+
+.TP
+.B ip netns set NAME NETNSID - assign an id to a peer network namespace
+.sp
+This command assigns a id to a peer network namespace. This id is valid
+only in the current network namespace.
+This id will be used by the kernel in some netlink messages. If no id is
+assigned when the kernel needs it, it will be automatically assigned by
+the kernel.
+Once it is assigned, it's not possible to change it.
+
+.TP
+.B ip netns identify [PID] - Report network namespaces names for process
+.sp
+This command walks through /var/run/netns and finds all the network
+namespace names for network namespace of the specified process, if PID is
+not specified then the current process will be used.
+
+.TP
+.B ip netns pids NAME - Report processes in the named network namespace
+.sp
+This command walks through proc and finds all of the process who have
+the named network namespace as their primary network namespace.
+
+.TP
+.B ip [-all] netns exec [ NAME ] cmd ... - Run cmd in the named network namespace
+.sp
+This command allows applications that are network namespace unaware
+to be run in something other than the default network namespace with
+all of the configuration for the specified network namespace appearing
+in the customary global locations. A network namespace and bind mounts
+are used to move files from their network namespace specific location
+to their default locations without affecting other processes.
+
+If
+.B -all
+option was specified then
+.B cmd
+will be executed synchronously on the each named network namespace even if
+.B cmd
+fails on some of them. Network namespace name is printed on each
+.B cmd
+executing.
+
+.TP
+.B ip netns monitor - Report as network namespace names are added and deleted
+.sp
+This command watches network namespace name addition and deletion events
+and prints a line for each event it sees.
 
 .SH EXAMPLES
+.PP
+ip netns list
+.RS
+Shows the list of current named network namespaces
+.RE
+.PP
+ip netns add vpn
+.RS
+Creates a network namespace and names it vpn
+.RE
+.PP
+ip netns exec vpn ip link set lo up
+.RS
+Bring up the loopback interface in the vpn network namespace.
+.RE
 
 .SH SEE ALSO
 .br
diff --git a/man/man8/ip-route.8 b/man/man8/ip-route.8
deleted file mode 100644
index 91c1a0b..0000000
--- a/man/man8/ip-route.8
+++ /dev/null
@@ -1,744 +0,0 @@
-.TH IP\-ROUTE 8 "20 Dec 2011" "iproute2" "Linux"
-.SH "NAME"
-ip-route \- routing table management
-.SH "SYNOPSIS"
-.sp
-.ad l
-.in +8
-.ti -8
-.B ip
-.RI "[ " OPTIONS " ]" 
-.B route 
-.RI " { " COMMAND " | "
-.BR help " }"
-.sp
-.ti -8
-
-.ti -8
-.BR "ip route" " { "
-.BR list " | " flush " } "
-.I  SELECTOR
-
-.ti -8
-.BR "ip route save"
-.I SELECTOR
-
-.ti -8
-.BR "ip route restore"
-
-.ti -8
-.B  ip route get
-.IR ADDRESS " [ "
-.BI from " ADDRESS " iif " STRING"
-.RB " ] [ " oif
-.IR STRING " ] [ "
-.B  tos
-.IR TOS " ]"
-
-.ti -8
-.BR "ip route" " { " add " | " del " | " change " | " append " | "\
-replace " } "
-.I  ROUTE
-
-.ti -8
-.IR SELECTOR " := "
-.RB "[ " root
-.IR PREFIX " ] [ "
-.B  match
-.IR PREFIX " ] [ "
-.B  exact
-.IR PREFIX " ] [ "
-.B  table
-.IR TABLE_ID " ] [ "
-.B  proto
-.IR RTPROTO " ] [ "
-.B  type
-.IR TYPE " ] [ "
-.B  scope
-.IR SCOPE " ]"
-
-.ti -8
-.IR ROUTE " := " NODE_SPEC " [ " INFO_SPEC " ]"
-
-.ti -8
-.IR NODE_SPEC " := [ " TYPE " ] " PREFIX " ["
-.B  tos
-.IR TOS " ] [ "
-.B  table
-.IR TABLE_ID " ] [ "
-.B  proto
-.IR RTPROTO " ] [ "
-.B  scope
-.IR SCOPE " ] [ "
-.B  metric
-.IR METRIC " ]"
-
-.ti -8
-.IR INFO_SPEC " := " "NH OPTIONS FLAGS" " ["
-.B  nexthop
-.IR NH " ] ..."
-
-.ti -8
-.IR NH " := [ "
-.B  via
-.IR ADDRESS " ] [ "
-.B  dev
-.IR STRING " ] [ "
-.B  weight
-.IR NUMBER " ] " NHFLAGS
-
-.ti -8
-.IR OPTIONS " := " FLAGS " [ "
-.B  mtu
-.IR NUMBER " ] [ "
-.B  advmss
-.IR NUMBER " ] [ "
-.B  rtt
-.IR TIME " ] [ "
-.B  rttvar
-.IR TIME " ] [ "
-.B  window
-.IR NUMBER " ] [ "
-.B  cwnd
-.IR NUMBER " ] [ "
-.B  ssthresh
-.IR REALM " ] [ "
-.B  realms
-.IR REALM " ] [ "
-.B  rto_min
-.IR TIME " ] [ "
-.B  initcwnd
-.IR NUMBER " ] [ "
-.B  initrwnd
-.IR NUMBER " ]"
-
-.ti -8
-.IR TYPE " := [ "
-.BR unicast " | " local " | " broadcast " | " multicast " | "\
-throw " | " unreachable " | " prohibit " | " blackhole " | " nat " ]"
-
-.ti -8
-.IR TABLE_ID " := [ "
-.BR local "| " main " | " default " | " all " |"
-.IR NUMBER " ]"
-
-.ti -8
-.IR SCOPE " := [ "
-.BR host " | " link " | " global " |"
-.IR NUMBER " ]"
-
-.ti -8
-.IR NHFLAGS " := [ "
-.BR onlink " | " pervasive " ]"
-
-.ti -8
-.IR RTPROTO " := [ "
-.BR kernel " | " boot " | " static " |"
-.IR NUMBER " ]"
-
-
-.SH DESCRIPTION
-.B ip route
-is used to manipulate entries in the kernel routing tables.
-.sp
-.B Route types:
-
-.in +8
-.B unicast
-- the route entry describes real paths to the destinations covered
-by the route prefix.
-
-.sp
-.B unreachable
-- these destinations are unreachable.  Packets are discarded and the
-ICMP message
-.I host unreachable
-is generated.
-The local senders get an
-.I EHOSTUNREACH
-error.
-
-.sp
-.B blackhole
-- these destinations are unreachable.  Packets are discarded silently.
-The local senders get an
-.I EINVAL
-error.
-
-.sp
-.B prohibit
-- these destinations are unreachable.  Packets are discarded and the
-ICMP message
-.I communication administratively prohibited
-is generated.  The local senders get an
-.I EACCES
-error.
-
-.sp
-.B local
-- the destinations are assigned to this host.  The packets are looped
-back and delivered locally.
-
-.sp
-.B broadcast
-- the destinations are broadcast addresses.  The packets are sent as
-link broadcasts.
-
-.sp
-.B throw
-- a special control route used together with policy rules. If such a
-route is selected, lookup in this table is terminated pretending that
-no route was found.  Without policy routing it is equivalent to the
-absence of the route in the routing table.  The packets are dropped
-and the ICMP message
-.I net unreachable
-is generated.  The local senders get an
-.I ENETUNREACH
-error.
-
-.sp
-.B nat
-- a special NAT route.  Destinations covered by the prefix
-are considered to be dummy (or external) addresses which require translation
-to real (or internal) ones before forwarding.  The addresses to translate to
-are selected with the attribute
-.B Warning:
-Route NAT is no longer supported in Linux 2.6.
-
-
-.BR "via" .
-.sp
-.B anycast
-.RI "- " "not implemented"
-the destinations are
-.I anycast
-addresses assigned to this host.  They are mainly equivalent
-to
-.B local
-with one difference: such addresses are invalid when used
-as the source address of any packet.
-
-.sp
-.B multicast
-- a special type used for multicast routing.  It is not present in
-normal routing tables.
-.in -8
-
-.P
-.B Route tables:
-Linux-2.x can pack routes into several routing tables identified 
-by a number in the range from 1 to 2^31 or by name from the file
-.B /etc/iproute2/rt_tables
-By default all normal routes are inserted into the
-.B main
-table (ID 254) and the kernel only uses this table when calculating routes.
-Values (0, 253, 254, and 255) are reserved for built-in use.
-
-.sp
-Actually, one other table always exists, which is invisible but
-even more important.  It is the
-.B local
-table (ID 255).  This table
-consists of routes for local and broadcast addresses.  The kernel maintains
-this table automatically and the administrator usually need not modify it
-or even look at it.
-
-The multiple routing tables enter the game when
-.I policy routing
-is used.
-
-.SS ip route add - add new route
-.SS ip route change - change route
-.SS ip route replace - change or add new one
-
-.TP
-.BI to " TYPE PREFIX " (default)
-the destination prefix of the route.  If
-.I TYPE
-is omitted,
-.B ip
-assumes type
-.BR "unicast" .
-Other values of
-.I TYPE
-are listed above.
-.I PREFIX
-is an IP or IPv6 address optionally followed by a slash and the
-prefix length.  If the length of the prefix is missing,
-.B ip
-assumes a full-length host route.  There is also a special
-.I PREFIX
-.B default
-- which is equivalent to IP
-.B 0/0
-or to IPv6
-.BR "::/0" .
-
-.TP
-.BI tos " TOS"
-.TP
-.BI dsfield " TOS"
-the Type Of Service (TOS) key.  This key has no associated mask and
-the longest match is understood as: First, compare the TOS
-of the route and of the packet.  If they are not equal, then the packet
-may still match a route with a zero TOS.
-.I TOS
-is either an 8 bit hexadecimal number or an identifier
-from
-.BR "/etc/iproute2/rt_dsfield" .
-
-.TP
-.BI metric " NUMBER"
-.TP
-.BI preference " NUMBER"
-the preference value of the route.
-.I NUMBER
-is an arbitrary 32bit number.
-
-.TP
-.BI table " TABLEID"
-the table to add this route to.
-.I TABLEID
-may be a number or a string from the file
-.BR "/etc/iproute2/rt_tables" .
-If this parameter is omitted,
-.B ip
-assumes the
-.B main
-table, with the exception of
-.BR local " , " broadcast " and " nat
-routes, which are put into the
-.B local
-table by default.
-
-.TP
-.BI dev " NAME"
-the output device name.
-
-.TP
-.BI via " ADDRESS"
-the address of the nexthop router.  Actually, the sense of this field
-depends on the route type.  For normal
-.B unicast
-routes it is either the true next hop router or, if it is a direct
-route installed in BSD compatibility mode, it can be a local address
-of the interface.  For NAT routes it is the first address of the block
-of translated IP destinations.
-
-.TP
-.BI src " ADDRESS"
-the source address to prefer when sending to the destinations
-covered by the route prefix.
-
-.TP
-.BI realm " REALMID"
-the realm to which this route is assigned.
-.I REALMID
-may be a number or a string from the file
-.BR "/etc/iproute2/rt_realms" .
-
-.TP
-.BI mtu " MTU"
-.TP
-.BI "mtu lock" " MTU"
-the MTU along the path to the destination.  If the modifier
-.B lock
-is not used, the MTU may be updated by the kernel due to
-Path MTU Discovery.  If the modifier
-.B lock
-is used, no path MTU discovery will be tried, all packets
-will be sent without the DF bit in IPv4 case or fragmented
-to MTU for IPv6.
-
-.TP
-.BI window " NUMBER"
-the maximal window for TCP to advertise to these destinations,
-measured in bytes.  It limits maximal data bursts that our TCP
-peers are allowed to send to us.
-
-.TP
-.BI rtt " TIME"
-the initial RTT ('Round Trip Time') estimate. If no suffix is
-specified the units are raw values passed directly to the
-routing code to maintain compatibility with previous releases.
-Otherwise if a suffix of s, sec or secs is used to specify
-seconds and ms, msec or msecs to specify milliseconds.
-
-
-.TP
-.BI rttvar " TIME " "(2.3.15+ only)"
-the initial RTT variance estimate. Values are specified as with
-.BI rtt
-above.
-
-.TP
-.BI rto_min " TIME " "(2.6.23+ only)"
-the minimum TCP Retransmission TimeOut to use when communicating with this
-destination.  Values are specified as with
-.BI rtt
-above.
-
-.TP
-.BI ssthresh " NUMBER " "(2.3.15+ only)"
-an estimate for the initial slow start threshold.
-
-.TP
-.BI cwnd " NUMBER " "(2.3.15+ only)"
-the clamp for congestion window.  It is ignored if the
-.B lock
-flag is not used.
-
-.TP
-.BI initcwnd " NUMBER " "(2.5.70+ only)"
-the initial congestion window size for connections to this destination.
-Actual window size is this value multiplied by the MSS
-(``Maximal Segment Size'') for same connection. The default is
-zero, meaning to use the values specified in RFC2414.
-
-.TP
-.BI initrwnd " NUMBER " "(2.6.33+ only)"
-the initial receive window size for connections to this destination.
-Actual window size is this value multiplied by the MSS of the connection.
-The default value is zero, meaning to use Slow Start value.
-
-.TP
-.BI advmss " NUMBER " "(2.3.15+ only)"
-the MSS ('Maximal Segment Size') to advertise to these
-destinations when establishing TCP connections.  If it is not given,
-Linux uses a default value calculated from the first hop device MTU.
-(If the path to these destination is asymmetric, this guess may be wrong.)
-
-.TP
-.BI reordering " NUMBER " "(2.3.15+ only)"
-Maximal reordering on the path to this destination.
-If it is not given, Linux uses the value selected with
-.B sysctl
-variable
-.BR "net/ipv4/tcp_reordering" .
-
-.TP
-.BI nexthop " NEXTHOP"
-the nexthop of a multipath route.
-.I NEXTHOP
-is a complex value with its own syntax similar to the top level
-argument lists:
-
-.in +8
-.BI via " ADDRESS"
-- is the nexthop router.
-.sp
-
-.BI dev " NAME"
-- is the output device.
-.sp
-
-.BI weight " NUMBER"
-- is a weight for this element of a multipath
-route reflecting its relative bandwidth or quality.
-.in -8
-
-.TP
-.BI scope " SCOPE_VAL"
-the scope of the destinations covered by the route prefix.
-.I SCOPE_VAL
-may be a number or a string from the file
-.BR "/etc/iproute2/rt_scopes" .
-If this parameter is omitted,
-.B ip
-assumes scope
-.B global
-for all gatewayed
-.B unicast
-routes, scope
-.B link
-for direct
-.BR unicast " and " broadcast
-routes and scope
-.BR host " for " local
-routes.
-
-.TP
-.BI protocol " RTPROTO"
-the routing protocol identifier of this route.
-.I RTPROTO
-may be a number or a string from the file
-.BR "/etc/iproute2/rt_protos" .
-If the routing protocol ID is not given,
-.B ip assumes protocol
-.B boot
-(i.e. it assumes the route was added by someone who doesn't
-understand what they are doing).  Several protocol values have
-a fixed interpretation.
-Namely:
-
-.in +8
-.B redirect
-- the route was installed due to an ICMP redirect.
-.sp
-
-.B kernel
-- the route was installed by the kernel during autoconfiguration.
-.sp
-
-.B boot
-- the route was installed during the bootup sequence.
-If a routing daemon starts, it will purge all of them.
-.sp
-
-.B static
-- the route was installed by the administrator
-to override dynamic routing. Routing daemon will respect them
-and, probably, even advertise them to its peers.
-.sp
-
-.B ra
-- the route was installed by Router Discovery protocol.
-.in -8
-
-.sp
-The rest of the values are not reserved and the administrator is free
-to assign (or not to assign) protocol tags.
-
-.TP
-.B onlink
-pretend that the nexthop is directly attached to this link,
-even if it does not match any interface prefix.
-
-.SS ip route delete - delete route
-
-.B ip route del
-has the same arguments as
-.BR "ip route add" ,
-but their semantics are a bit different.
-
-Key values
-.RB "(" to ", " tos ", " preference " and " table ")"
-select the route to delete.  If optional attributes are present,
-.B ip
-verifies that they coincide with the attributes of the route to delete.
-If no route with the given key and attributes was found,
-.B ip route del
-fails.
-
-.SS ip route show - list routes
-the command displays the contents of the routing tables or the route(s)
-selected by some criteria.
-
-.TP
-.BI to " SELECTOR " (default)
-only select routes from the given range of destinations.
-.I SELECTOR
-consists of an optional modifier
-.RB "(" root ", " match " or " exact ")"
-and a prefix.
-.BI root " PREFIX"
-selects routes with prefixes not shorter than
-.IR PREFIX "."
-F.e.
-.BI root " 0/0"
-selects the entire routing table.
-.BI match " PREFIX"
-selects routes with prefixes not longer than
-.IR PREFIX "."
-F.e.
-.BI match " 10.0/16"
-selects
-.IR 10.0/16 ","
-.IR 10/8 " and " 0/0 ,
-but it does not select
-.IR 10.1/16 " and " 10.0.0/24 .
-And
-.BI exact " PREFIX"
-(or just
-.IR PREFIX ")"
-selects routes with this exact prefix. If neither of these options
-are present,
-.B ip
-assumes
-.BI root " 0/0"
-i.e. it lists the entire table.
-
-.TP
-.BI tos " TOS"
-.BI dsfield " TOS"
-only select routes with the given TOS.
-
-.TP
-.BI table " TABLEID"
-show the routes from this table(s).  The default setting is to show
-.BR table main "."
-.I TABLEID
-may either be the ID of a real table or one of the special values:
-.sp
-.in +8
-.B all
-- list all of the tables.
-.sp
-.B cache
-- dump the routing cache.
-.in -8
-
-.TP
-.B cloned
-.TP
-.B cached
-list cloned routes i.e. routes which were dynamically forked from
-other routes because some route attribute (f.e. MTU) was updated.
-Actually, it is equivalent to
-.BR "table cache" "."
-
-.TP
-.BI from " SELECTOR"
-the same syntax as for
-.BR to ","
-but it binds the source address range rather than destinations.
-Note that the
-.B from
-option only works with cloned routes.
-
-.TP
-.BI protocol " RTPROTO"
-only list routes of this protocol.
-
-.TP
-.BI scope " SCOPE_VAL"
-only list routes with this scope.
-
-.TP
-.BI type " TYPE"
-only list routes of this type.
-
-.TP
-.BI dev " NAME"
-only list routes going via this device.
-
-.TP
-.BI via " PREFIX"
-only list routes going via the nexthop routers selected by
-.IR PREFIX "."
-
-.TP
-.BI src " PREFIX"
-only list routes with preferred source addresses selected
-by
-.IR PREFIX "."
-
-.TP
-.BI realm " REALMID"
-.TP
-.BI realms " FROMREALM/TOREALM"
-only list routes with these realms.
-
-.SS ip route flush - flush routing tables
-this command flushes routes selected by some criteria.
-
-.sp
-The arguments have the same syntax and semantics as the arguments of
-.BR "ip route show" ,
-but routing tables are not listed but purged.  The only difference is
-the default action:
-.B show
-dumps all the IP main routing table but
-.B flush
-prints the helper page.
-
-.sp
-With the
-.B -statistics
-option, the command becomes verbose. It prints out the number of
-deleted routes and the number of rounds made to flush the routing
-table. If the option is given
-twice,
-.B ip route flush
-also dumps all the deleted routes in the format described in the
-previous subsection.
-
-.SS ip route get - get a single route
-this command gets a single route to a destination and prints its
-contents exactly as the kernel sees it.
-
-.TP
-.BI to " ADDRESS " (default)
-the destination address.
-
-.TP
-.BI from " ADDRESS"
-the source address.
-
-.TP
-.BI tos " TOS"
-.TP
-.BI dsfield " TOS"
-the Type Of Service.
-
-.TP
-.BI iif " NAME"
-the device from which this packet is expected to arrive.
-
-.TP
-.BI oif " NAME"
-force the output device on which this packet will be routed.
-
-.TP
-.B connected
-if no source address
-.RB "(option " from ")"
-was given, relookup the route with the source set to the preferred
-address received from the first lookup.
-If policy routing is used, it may be a different route.
-
-.P
-Note that this operation is not equivalent to
-.BR "ip route show" .
-.B show
-shows existing routes.
-.B get
-resolves them and creates new clones if necessary.  Essentially,
-.B get
-is equivalent to sending a packet along this path.
-If the
-.B iif
-argument is not given, the kernel creates a route
-to output packets towards the requested destination.
-This is equivalent to pinging the destination
-with a subsequent
-.BR "ip route ls cache" ,
-however, no packets are actually sent.  With the
-.B iif
-argument, the kernel pretends that a packet arrived from this interface
-and searches for a path to forward the packet.
-
-.SS ip route save - save routing table information to stdout
-this command behaves like
-.BR "ip route show"
-except that the output is raw data suitable for passing to
-.BR "ip route restore" .
-
-.SS ip route restore - restore routing table information from stdin
-this command expects to read a data stream as returned from
-.BR "ip route save" .
-It will attempt to restore the routing table information exactly as
-it was at the time of the save, so any translation of information
-in the stream (such as device indexes) must be done first.  Any existing
-routes are left unchanged.  Any routes specified in the data stream that
-already exist in the table will be ignored.
-
-.SH EXAMPLES
-.PP
-ip ro
-.RS 4
-Show all route entries in the kernel.
-.RE
-.PP
-ip route add default via 192.168.1.1 dev eth0
-.RS 4
-Adds a default route (for all addresses) via the local gateway 192.168.1.1 that can
-be reached on device eth0.
-.RE
-
-.SH SEE ALSO
-.br
-.BR ip (8)
-
-.SH AUTHOR
-Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index 0ca6107..d53cc76 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1,4 +1,4 @@
-.TH IP\-ROUTE 8 "20 Dec 2011" "iproute2" "Linux"
+.TH IP\-ROUTE 8 "13 Dec 2012" "iproute2" "Linux"
 .SH "NAME"
 ip-route \- routing table management
 .SH "SYNOPSIS"
@@ -7,7 +7,7 @@
 .in +8
 .ti -8
 .B ip
-.RI "[ " OPTIONS " ]"
+.RI "[ " ip-OPTIONS " ]"
 .B route
 .RI " { " COMMAND " | "
 .BR help " }"
@@ -97,6 +97,8 @@
 .IR TIME " ] [ "
 .B  rttvar
 .IR TIME " ] [ "
+.B  reordering
+.IR NUMBER " ] [ "
 .B  window
 .IR NUMBER " ] [ "
 .B  cwnd
@@ -110,7 +112,13 @@
 .B  initcwnd
 .IR NUMBER " ] [ "
 .B  initrwnd
-.IR NUMBER " ]"
+.IR NUMBER " ] [ "
+.B  features
+.IR FEATURES " ] [ "
+.B  quickack
+.IR BOOL " ] [ "
+.B  congctl
+.IR NAME " ]"
 
 .ti -8
 .IR TYPE " := [ "
@@ -136,6 +144,10 @@
 .BR kernel " | " boot " | " static " |"
 .IR NUMBER " ]"
 
+.ti -8
+.IR FEATURES " := [ "
+.BR ecn " | ]"
+
 
 .SH DESCRIPTION
 .B ip route
@@ -150,7 +162,7 @@
 
 .sp
 .B unreachable
-- these destinations are unreachable.  Packets are discarded and the
+- these destinations are unreachable. Packets are discarded and the
 ICMP message
 .I host unreachable
 is generated.
@@ -160,59 +172,58 @@
 
 .sp
 .B blackhole
-- these destinations are unreachable.  Packets are discarded silently.
+- these destinations are unreachable. Packets are discarded silently.
 The local senders get an
 .I EINVAL
 error.
 
 .sp
 .B prohibit
-- these destinations are unreachable.  Packets are discarded and the
+- these destinations are unreachable. Packets are discarded and the
 ICMP message
 .I communication administratively prohibited
-is generated.  The local senders get an
+is generated. The local senders get an
 .I EACCES
 error.
 
 .sp
 .B local
-- the destinations are assigned to this host.  The packets are looped
+- the destinations are assigned to this host. The packets are looped
 back and delivered locally.
 
 .sp
 .B broadcast
-- the destinations are broadcast addresses.  The packets are sent as
+- the destinations are broadcast addresses. The packets are sent as
 link broadcasts.
 
 .sp
 .B throw
 - a special control route used together with policy rules. If such a
 route is selected, lookup in this table is terminated pretending that
-no route was found.  Without policy routing it is equivalent to the
-absence of the route in the routing table.  The packets are dropped
+no route was found. Without policy routing it is equivalent to the
+absence of the route in the routing table. The packets are dropped
 and the ICMP message
 .I net unreachable
-is generated.  The local senders get an
+is generated. The local senders get an
 .I ENETUNREACH
 error.
 
 .sp
 .B nat
-- a special NAT route.  Destinations covered by the prefix
+- a special NAT route. Destinations covered by the prefix
 are considered to be dummy (or external) addresses which require translation
-to real (or internal) ones before forwarding.  The addresses to translate to
+to real (or internal) ones before forwarding. The addresses to translate to
 are selected with the attribute
+.BR "via" .
 .B Warning:
 Route NAT is no longer supported in Linux 2.6.
 
-
-.BR "via" .
 .sp
 .B anycast
 .RI "- " "not implemented"
 the destinations are
 .I anycast
-addresses assigned to this host.  They are mainly equivalent
+addresses assigned to this host. They are mainly equivalent
 to
 .B local
 with one difference: such addresses are invalid when used
@@ -220,7 +231,7 @@
 
 .sp
 .B multicast
-- a special type used for multicast routing.  It is not present in
+- a special type used for multicast routing. It is not present in
 normal routing tables.
 .in -8
 
@@ -236,10 +247,10 @@
 
 .sp
 Actually, one other table always exists, which is invisible but
-even more important.  It is the
+even more important. It is the
 .B local
-table (ID 255).  This table
-consists of routes for local and broadcast addresses.  The kernel maintains
+table (ID 255). This table
+consists of routes for local and broadcast addresses. The kernel maintains
 this table automatically and the administrator usually need not modify it
 or even look at it.
 
@@ -247,13 +258,19 @@
 .I policy routing
 is used.
 
-.SS ip route add - add new route
-.SS ip route change - change route
-.SS ip route replace - change or add new one
-
+.TP
+ip route add
+add new route
+.TP
+ip route change
+change route
+.TP
+ip route replace
+change or add new one
+.RS
 .TP
 .BI to " TYPE PREFIX " (default)
-the destination prefix of the route.  If
+the destination prefix of the route. If
 .I TYPE
 is omitted,
 .B ip
@@ -264,9 +281,9 @@
 are listed above.
 .I PREFIX
 is an IP or IPv6 address optionally followed by a slash and the
-prefix length.  If the length of the prefix is missing,
+prefix length. If the length of the prefix is missing,
 .B ip
-assumes a full-length host route.  There is also a special
+assumes a full-length host route. There is also a special
 .I PREFIX
 .B default
 - which is equivalent to IP
@@ -278,9 +295,9 @@
 .BI tos " TOS"
 .TP
 .BI dsfield " TOS"
-the Type Of Service (TOS) key.  This key has no associated mask and
+the Type Of Service (TOS) key. This key has no associated mask and
 the longest match is understood as: First, compare the TOS
-of the route and of the packet.  If they are not equal, then the packet
+of the route and of the packet. If they are not equal, then the packet
 may still match a route with a zero TOS.
 .I TOS
 is either an 8 bit hexadecimal number or an identifier
@@ -306,7 +323,7 @@
 assumes the
 .B main
 table, with the exception of
-.BR local " , " broadcast " and " nat
+.BR local ", " broadcast " and " nat
 routes, which are put into the
 .B local
 table by default.
@@ -317,12 +334,12 @@
 
 .TP
 .BI via " ADDRESS"
-the address of the nexthop router.  Actually, the sense of this field
-depends on the route type.  For normal
+the address of the nexthop router. Actually, the sense of this field
+depends on the route type. For normal
 .B unicast
 routes it is either the true next hop router or, if it is a direct
 route installed in BSD compatibility mode, it can be a local address
-of the interface.  For NAT routes it is the first address of the block
+of the interface. For NAT routes it is the first address of the block
 of translated IP destinations.
 
 .TP
@@ -341,10 +358,10 @@
 .BI mtu " MTU"
 .TP
 .BI "mtu lock" " MTU"
-the MTU along the path to the destination.  If the modifier
+the MTU along the path to the destination. If the modifier
 .B lock
 is not used, the MTU may be updated by the kernel due to
-Path MTU Discovery.  If the modifier
+Path MTU Discovery. If the modifier
 .B lock
 is used, no path MTU discovery will be tried, all packets
 will be sent without the DF bit in IPv4 case or fragmented
@@ -353,7 +370,7 @@
 .TP
 .BI window " NUMBER"
 the maximal window for TCP to advertise to these destinations,
-measured in bytes.  It limits maximal data bursts that our TCP
+measured in bytes. It limits maximal data bursts that our TCP
 peers are allowed to send to us.
 
 .TP
@@ -374,7 +391,7 @@
 .TP
 .BI rto_min " TIME " "(2.6.23+ only)"
 the minimum TCP Retransmission TimeOut to use when communicating with this
-destination.  Values are specified as with
+destination. Values are specified as with
 .BI rtt
 above.
 
@@ -384,7 +401,7 @@
 
 .TP
 .BI cwnd " NUMBER " "(2.3.15+ only)"
-the clamp for congestion window.  It is ignored if the
+the clamp for congestion window. It is ignored if the
 .B lock
 flag is not used.
 
@@ -402,9 +419,40 @@
 The default value is zero, meaning to use Slow Start value.
 
 .TP
+.BI features " FEATURES " (3.18+ only)
+Enable or disable per-route features. Only available feature at this
+time is
+.B ecn
+to enable explicit congestion notification when initiating connections to the
+given destination network.
+When responding to a connection request from the given network, ecn will
+also be used even if the
+.B net.ipv4.tcp_ecn
+sysctl is set to 0.
+
+.TP
+.BI quickack " BOOL " "(3.11+ only)"
+Enable or disable quick ack for connections to this destination.
+
+.TP
+.BI congctl " NAME " "(3.20+ only)"
+.TP
+.BI "congctl lock" " NAME " "(3.20+ only)"
+Sets a specific TCP congestion control algorithm only for a given destination.
+If not specified, Linux keeps the current global default TCP congestion control
+algorithm, or the one set from the application. If the modifier
+.B lock
+is not used, an application may nevertheless overwrite the suggested congestion
+control algorithm for that destination. If the modifier
+.B lock
+is used, then an application is not allowed to overwrite the specified congestion
+control algorithm for that destination, thus it will be enforced/guaranteed to
+use the proposed algorithm.
+
+.TP
 .BI advmss " NUMBER " "(2.3.15+ only)"
 the MSS ('Maximal Segment Size') to advertise to these
-destinations when establishing TCP connections.  If it is not given,
+destinations when establishing TCP connections. If it is not given,
 Linux uses a default value calculated from the first hop device MTU.
 (If the path to these destination is asymmetric, this guess may be wrong.)
 
@@ -467,7 +515,7 @@
 .B ip assumes protocol
 .B boot
 (i.e. it assumes the route was added by someone who doesn't
-understand what they are doing).  Several protocol values have
+understand what they are doing). Several protocol values have
 a fixed interpretation.
 Namely:
 
@@ -503,9 +551,12 @@
 .B onlink
 pretend that the nexthop is directly attached to this link,
 even if it does not match any interface prefix.
+.RE
 
-.SS ip route delete - delete route
-
+.TP
+ip route delete
+delete route
+.RS
 .B ip route del
 has the same arguments as
 .BR "ip route add" ,
@@ -513,14 +564,18 @@
 
 Key values
 .RB "(" to ", " tos ", " preference " and " table ")"
-select the route to delete.  If optional attributes are present,
+select the route to delete. If optional attributes are present,
 .B ip
 verifies that they coincide with the attributes of the route to delete.
 If no route with the given key and attributes was found,
 .B ip route del
 fails.
+.RE
 
-.SS ip route show - list routes
+.TP
+ip route show
+list routes
+.RS
 the command displays the contents of the routing tables or the route(s)
 selected by some criteria.
 
@@ -560,13 +615,14 @@
 
 .TP
 .BI tos " TOS"
+.TP
 .BI dsfield " TOS"
 only select routes with the given TOS.
 
 .TP
 .BI table " TABLEID"
-show the routes from this table(s).  The default setting is to show
-.BR table main "."
+show the routes from this table(s). The default setting is to show table
+.BR main "."
 .I TABLEID
 may either be the ID of a real table or one of the special values:
 .sp
@@ -628,14 +684,18 @@
 .TP
 .BI realms " FROMREALM/TOREALM"
 only list routes with these realms.
+.RE
 
-.SS ip route flush - flush routing tables
+.TP
+ip route flush
+flush routing tables
+.RS
 this command flushes routes selected by some criteria.
 
 .sp
 The arguments have the same syntax and semantics as the arguments of
 .BR "ip route show" ,
-but routing tables are not listed but purged.  The only difference is
+but routing tables are not listed but purged. The only difference is
 the default action:
 .B show
 dumps all the IP main routing table but
@@ -652,8 +712,12 @@
 .B ip route flush
 also dumps all the deleted routes in the format described in the
 previous subsection.
+.RE
 
-.SS ip route get - get a single route
+.TP
+ip route get
+get a single route
+.RS
 this command gets a single route to a destination and prints its
 contents exactly as the kernel sees it.
 
@@ -693,7 +757,7 @@
 .B show
 shows existing routes.
 .B get
-resolves them and creates new clones if necessary.  Essentially,
+resolves them and creates new clones if necessary. Essentially,
 .B get
 is equivalent to sending a packet along this path.
 If the
@@ -703,25 +767,34 @@
 This is equivalent to pinging the destination
 with a subsequent
 .BR "ip route ls cache" ,
-however, no packets are actually sent.  With the
+however, no packets are actually sent. With the
 .B iif
 argument, the kernel pretends that a packet arrived from this interface
 and searches for a path to forward the packet.
+.RE
 
-.SS ip route save - save routing table information to stdout
-this command behaves like
+.TP
+ip route save
+save routing table information to stdout
+.RS
+This command behaves like
 .BR "ip route show"
 except that the output is raw data suitable for passing to
 .BR "ip route restore" .
+.RE
 
-.SS ip route restore - restore routing table information from stdin
-this command expects to read a data stream as returned from
+.TP
+ip route restore
+restore routing table information from stdin
+.RS
+This command expects to read a data stream as returned from
 .BR "ip route save" .
 It will attempt to restore the routing table information exactly as
 it was at the time of the save, so any translation of information
-in the stream (such as device indexes) must be done first.  Any existing
-routes are left unchanged.  Any routes specified in the data stream that
+in the stream (such as device indexes) must be done first. Any existing
+routes are left unchanged. Any routes specified in the data stream that
 already exist in the table will be ignored.
+.RE
 
 .SH EXAMPLES
 .PP
diff --git a/man/man8/ip-rule.8 b/man/man8/ip-rule.8
index 0f62a53..dd925be 100644
--- a/man/man8/ip-rule.8
+++ b/man/man8/ip-rule.8
@@ -43,6 +43,14 @@
 .IR ADDRESS " ] [ "
 .BR prohibit " | " reject " | " unreachable " ] [ " realms
 .RI "[" SRCREALM "/]" DSTREALM " ]"
+.I  SUPPRESSOR
+
+.ti -8
+.IR SUPPRESSOR " := [ "
+.B  suppress_prefixlength
+.IR NUMBER " ] [ "
+.B  suppress_ifgroup
+.IR GROUP " ]"
 
 .ti -8
 .IR TABLE_ID " := [ "
@@ -75,16 +83,16 @@
 .B selector
 and an
 .B action predicate.
-The RPDB is scanned in the order of increasing priority. The selector
+The RPDB is scanned in order of decreasing priority. The selector
 of each rule is applied to {source address, destination address, incoming
 interface, tos, fwmark} and, if the selector matches the packet,
-the action is performed.  The action predicate may return with success.
+the action is performed. The action predicate may return with success.
 In this case, it will either give a route or failure indication
 and the RPDB lookup is terminated. Otherwise, the RPDB program
-continues on the next rule.
+continues with the next rule.
 
 .P
-Semantically, natural action is to select the nexthop and the output device.
+Semantically, the natural action is to select the nexthop and the output device.
 
 .P
 At startup time the kernel configures the default RPDB consisting of three
@@ -123,24 +131,24 @@
 (ID 253).
 The
 .B default
-table is empty.  It is reserved for some post-processing if no previous
+table is empty. It is reserved for some post-processing if no previous
 default rules selected the packet.
 This rule may also be deleted.
 
 .P
 Each RPDB entry has additional
-attributes.  F.e. each rule has a pointer to some routing
-table.  NAT and masquerading rules have an attribute to select new IP
-address to translate/masquerade.  Besides that, rules have some
+attributes. F.e. each rule has a pointer to some routing
+table. NAT and masquerading rules have an attribute to select new IP
+address to translate/masquerade. Besides that, rules have some
 optional attributes, which routes have, namely
 .BR "realms" .
-These values do not override those contained in the routing tables.  They
+These values do not override those contained in the routing tables. They
 are only used if the route did not select any attributes.
 
 .sp
 The RPDB may contain rules of the following types:
 
-.in +8
+.RS
 .B unicast
 - the rule prescribes to return the route found
 in the routing table referenced by the rule.
@@ -158,14 +166,16 @@
 .B nat
 - the rule prescribes to translate the source address
 of the IP packet into some other value.
-.in -8
-
-.SS ip rule add - insert a new rule
-.SS ip rule delete - delete a rule
+.RE
 
 .TP
+.B ip rule add - insert a new rule
+.TP
+.B ip rule delete - delete a rule
+.RS
+.TP
 .BI type " TYPE " (default)
-the type of this rule.  The list of valid types was given in the previous
+the type of this rule. The list of valid types was given in the previous
 subsection.
 
 .TP
@@ -178,14 +188,14 @@
 
 .TP
 .BI iif " NAME"
-select the incoming device to match.  If the interface is loopback,
-the rule only matches packets originating from this host.  This means
+select the incoming device to match. If the interface is loopback,
+the rule only matches packets originating from this host. This means
 that you may create separate routing tables for forwarded and local
 packets and, hence, completely segregate them.
 
 .TP
 .BI oif " NAME"
-select the outgoing device to match.  The outgoing interface is only
+select the outgoing device to match. The outgoing interface is only
 available for packets originating from local sockets that are bound to
 a device.
 
@@ -203,7 +213,7 @@
 
 .TP
 .BI priority " PREFERENCE"
-the priority of this rule.  Each rule should have an explicitly
+the priority of this rule. Each rule should have an explicitly
 set
 .I unique
 priority value.
@@ -215,9 +225,18 @@
 It is also possible to use lookup instead of table.
 
 .TP
+.BI suppress_prefixlength " NUMBER"
+reject routing decisions that have a prefix length of NUMBER or less.
+
+.TP
+.BI suppress_ifgroup " GROUP"
+reject routing decisions that use a device belonging to the interface
+group GROUP.
+
+.TP
 .BI realms " FROM/TO"
 Realms to select if the rule matched and the routing table lookup
-succeeded.  Realm
+succeeded. Realm
 .I TO
 is only used if the route did not select any realm.
 
@@ -234,14 +253,15 @@
 
 .B Warning:
 Changes to the RPDB made with these commands do not become active
-immediately.  It is assumed that after a script finishes a batch of
+immediately. It is assumed that after a script finishes a batch of
 updates, it flushes the routing cache with
 .BR "ip route flush cache" .
-
-.SS ip rule flush - also dumps all the deleted rules.
+.RE
+.TP
+.B ip rule flush - also dumps all the deleted rules.
 This command has no arguments.
-
-.SS ip rule show - list rules
+.TP
+.B ip rule show - list rules
 This command has no arguments.
 The options list or lst are synonyms with show.
 
diff --git a/man/man8/ip-tcp_metrics.8 b/man/man8/ip-tcp_metrics.8
new file mode 100644
index 0000000..5d2dac8
--- /dev/null
+++ b/man/man8/ip-tcp_metrics.8
@@ -0,0 +1,143 @@
+.TH "IP\-TCP_METRICS" 8 "23 Aug 2012" "iproute2" "Linux"
+.SH "NAME"
+ip-tcp_metrics \- management for TCP Metrics
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B tcp_metrics
+.RI "{ " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.BR "ip tcp_metrics" " { " show " | " flush " }
+.IR SELECTOR
+
+.ti -8
+.BR "ip tcp_metrics delete " [ " address " ]
+.IR ADDRESS
+
+.ti -8
+.IR SELECTOR " := "
+.RB "[ [ " address " ] "
+.IR PREFIX " ]"
+
+.SH "DESCRIPTION"
+.B ip tcp_metrics
+is used to manipulate entries in the kernel that keep TCP information
+for IPv4 and IPv6 destinations. The entries are created when
+TCP sockets want to share information for destinations and are
+stored in a cache keyed by the destination address. The saved
+information may include values for metrics (initially obtained from
+routes), recent TSVAL for TIME-WAIT recycling purposes, state for the
+Fast Open feature, etc.
+For performance reasons the cache can not grow above configured limit
+and the older entries are replaced with fresh information, sometimes
+reclaimed and used for new destinations. The kernel never removes
+entries, they can be flushed only with this tool.
+
+.SS ip tcp_metrics show - show cached entries
+
+.TP
+.BI address " PREFIX " (default)
+IPv4/IPv6 prefix or address. If no prefix is provided all entries are shown.
+
+.LP
+The output may contain the following information:
+
+.BI age " <S.MMM>" sec
+- time after the entry was created, reset or updated with metrics
+from sockets. The entry is reset and refreshed on use with metrics from
+route if the metrics are not updated in last hour. Not all cached values
+reset the age on update.
+
+.BI cwnd " <N>"
+- CWND metric value
+
+.BI fo_cookie " <HEX-STRING>"
+- Cookie value received in SYN-ACK to be used by Fast Open for next SYNs
+
+.BI fo_mss " <N>"
+- MSS value received in SYN-ACK to be used by Fast Open for next SYNs
+
+.BI fo_syn_drops " <N>/<S.MMM>" "sec ago"
+- Number of drops of initial outgoing Fast Open SYNs with data
+detected by monitoring the received SYN-ACK after SYN retransmission.
+The seconds show the time after last SYN drop and together with
+the drop count can be used to disable Fast Open for some time.
+
+.BI reordering " <N>"
+- Reordering metric value
+
+.BI rtt " <N>" us
+- RTT metric value
+
+.BI rttvar " <N>" us
+- RTTVAR metric value
+
+.BI ssthresh " <SSTHRESH>"
+- SSTHRESH metric value
+
+.BI tw_ts " <TSVAL>/<SEC>" "sec ago"
+- recent TSVAL and the seconds after saving it into TIME-WAIT socket
+
+.SS ip tcp_metrics delete - delete single entry
+
+.TP
+.BI address " ADDRESS " (default)
+IPv4/IPv6 address. The address is a required argument.
+
+.SS ip tcp_metrics flush - flush entries
+This command flushes the entries selected by some criteria.
+
+.PP
+This command has the same arguments as
+.B show.
+
+.SH "EXAMPLES"
+.PP
+ip tcp_metrics show address 192.168.0.0/24
+.RS 4
+Shows the entries for destinations from subnet
+.RE
+.PP
+ip tcp_metrics show 192.168.0.0/24
+.RS 4
+The same but address keyword is optional
+.RE
+.PP
+ip tcp_metrics
+.RS 4
+Show all is the default action
+.RE
+.PP
+ip tcp_metrics delete 192.168.0.1
+.RS 4
+Removes the entry for 192.168.0.1 from cache.
+.RE
+.PP
+ip tcp_metrics flush 192.168.0.0/24
+.RS 4
+Removes entries for destinations from subnet
+.RE
+.PP
+ip tcp_metrics flush all
+.RS 4
+Removes all entries from cache
+.RE
+.PP
+ip -6 tcp_metrics flush all
+.RS 4
+Removes all IPv6 entries from cache keeping the IPv4 entries.
+.RE
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Julian Anastasov <ja@ssi.bg>
diff --git a/man/man8/ip-token.8 b/man/man8/ip-token.8
new file mode 100644
index 0000000..35a3d1e
--- /dev/null
+++ b/man/man8/ip-token.8
@@ -0,0 +1,66 @@
+.TH IP\-TOKEN 8 "28 Mar 2013" "iproute2" "Linux"
+.SH "NAME"
+ip-token \- tokenized interface identifier support
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip token
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.BR "ip token" " { " set " } "
+.IR TOKEN
+.B dev
+.IR DEV
+
+.ti -8
+.BR "ip token" " { " get " } "
+.B dev
+.IR DEV
+
+.ti -8
+.BR "ip token" " { " list " }"
+
+.SH "DESCRIPTION"
+IPv6 tokenized interface identifier support is used for assigning well-known
+host-part addresses to nodes whilst still obtaining a global network prefix
+from Router advertisements. The primary target for tokenized identifiers are
+server platforms where addresses are usually manually configured, rather than
+using DHCPv6 or SLAAC. By using tokenized identifiers, hosts can still
+determine their network prefix by use of SLAAC, but more readily be
+automatically renumbered should their network prefix change [1]. Tokenized
+IPv6 Identifiers are described in the draft
+[1]: <draft-chown-6man-tokenised-ipv6-identifiers-02>.
+
+.SS ip token set - set an interface token
+set the interface token to the kernel. Once a token is set, it cannot be
+removed from the interface, only overwritten.
+.TP
+.I TOKEN
+the interface identifier token address.
+.TP
+.BI dev " DEV"
+the networking interface.
+
+.SS ip token get - get the interface token from the kernel
+show a tokenized interface identifier of a particular networking device.
+.B Arguments:
+coincide with the arguments of
+.B ip token set
+but the
+.I TOKEN
+must be left out.
+.SS ip token list - list all interface tokens
+list all tokenized interface identifiers for the networking interfaces from
+the kernel.
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Manpage by Daniel Borkmann
diff --git a/man/man8/ip-tunnel.8 b/man/man8/ip-tunnel.8
index 37ba542..c97c28c 100644
--- a/man/man8/ip-tunnel.8
+++ b/man/man8/ip-tunnel.8
@@ -47,19 +47,22 @@
 .RB "[ [" no "]" pmtudisc " ]"
 .RB "[ " dev
 .IR PHYS_DEV " ]"
-.RB "[ " "dscp inherit" " ]"
 
 .ti -8
 .IR MODE " := "
-.RB " { " ipip " | " gre " | " sit " | " isatap " | " ip6ip6 " | " ipip6 " | " any " }"
+.RB " { " ipip " | " gre " | " sit " | " isatap " | " ip6ip6 " | " ipip6 " | " ip6gre " | " any " }"
 
 .ti -8
 .IR ADDR " := { " IP_ADDRESS " |"
 .BR any " }"
 
 .ti -8
-.IR TOS " := { " NUMBER " |"
-.BR inherit " }"
+.IR TOS " := { " STRING " | " 00 ".." ff " |"
+.BR inherit " |"
+.BI "inherit/" STRING
+.RB "|"
+.BI "inherit/" 00 ".." ff
+.RB "}"
 
 .ti -8
 .IR ELIM " := {"
@@ -81,14 +84,20 @@
 .B tunnel
 objects are tunnels, encapsulating packets in IP packets and then
 sending them over the IP infrastructure.
-The encapulating (or outer) address family is specified by the
+The encapsulating (or outer) address family is specified by the
 .B -f
-option.  The default is IPv4.
+option. The default is IPv4.
 
-.SS ip tunnel add - add a new tunnel
-.SS ip tunnel change - change an existing tunnel
-.SS ip tunnel delete - destroy a tunnel
-
+.TP
+.B ip tunnel add
+add a new tunnel
+.TP
+.B ip tunnel change
+change an existing tunnel
+.TP
+.B ip tunnel delete
+destroy a tunnel
+.RS
 .TP
 .BI name " NAME " (default)
 select the tunnel device name.
@@ -101,7 +110,7 @@
 .BR ipip ", " sit ", " isatap " and " gre "."
 .br
 Modes for IPv6 encapsulation available:
-.BR ip6ip6 ", " ipip6 " and " any "."
+.BR ip6ip6 ", " ipip6 ", " ip6gre ", and " any "."
 
 .TP
 .BI remote " ADDRESS"
@@ -132,11 +141,21 @@
 .BI dsfield " T"
 .TP
 .BI tclass " T"
-set a fixed TOS (or traffic class in IPv6)
-.I T
-on tunneled packets.
-The default value is:
-.BR "inherit" .
+set the type of service (IPv4) or traffic class (IPv6) field on
+tunneled packets, which can be specified as either a two-digit
+hex value (e.g. c0) or a predefined string (e.g. internet).
+The value
+.B inherit
+causes the field to be copied from the original IP header. The
+values
+.BI "inherit/" STRING
+or
+.BI "inherit/" 00 ".." ff
+will set the field to
+.I STRING
+or
+.IR 00 ".." ff
+when tunneling non-IP packets. The default value is 00.
 
 .TP
 .BI dev " NAME"
@@ -149,8 +168,8 @@
 .TP
 .B nopmtudisc
 disable Path MTU Discovery on this tunnel.
-It is enabled by default.  Note that a fixed ttl is incompatible
-with this option: tunnelling with a fixed ttl always makes pmtu
+It is enabled by default. Note that a fixed ttl is incompatible
+with this option: tunneling with a fixed ttl always makes pmtu
 discovery.
 
 .TP
@@ -180,7 +199,7 @@
 The
 .B icsum
 flag requires that all input packets have the correct
-checksum.  The
+checksum. The
 .B csum
 flag is equivalent to the combination
 .BR "icsum ocsum" .
@@ -202,22 +221,20 @@
 .B It isn't work. Don't use it.
 
 .TP
-.BR "dscp inherit"
-.RB ( " only IPv6 tunnels " )
-Inherit DS field between inner and outer header.
-
-.TP
 .BI encaplim " ELIM"
 .RB ( " only IPv6 tunnels " )
-set a fixed encapsulation limit.  Default is 4.
+set a fixed encapsulation limit. Default is 4.
 
 .TP
 .BI flowlabel " FLOWLABEL"
 .RB ( " only IPv6 tunnels " )
 set a fixed flowlabel.
+.RE
 
-.SS ip tunnel prl - potential router list (ISATAP only)
-
+.TP
+.B ip tunnel prl
+potential router list (ISATAP only)
+.RS
 .TP
 .BI dev " NAME"
 mandatory device name.
@@ -230,8 +247,11 @@
 .BI prl-delete " ADDR"
 .RB "Add or delete " ADDR
 as a potential router or default router.
+.RE
 
-.SS ip tunnel show - list tunnels
+.TP
+.B ip tunnel show
+list tunnels
 This command has no arguments.
 
 .SH SEE ALSO
diff --git a/man/man8/ip-xfrm.8 b/man/man8/ip-xfrm.8
index f359773..c9d2a2e 100644
--- a/man/man8/ip-xfrm.8
+++ b/man/man8/ip-xfrm.8
@@ -43,6 +43,10 @@
 .IR SEQ " ]"
 .RB "[ " replay-oseq
 .IR SEQ " ]"
+.RB "[ " replay-seq-hi
+.IR SEQ " ]"
+.RB "[ " replay-oseq-hi
+.IR SEQ " ]"
 .RB "[ " flag
 .IR FLAG-LIST " ]"
 .RB "[ " sel
@@ -117,25 +121,29 @@
 
 .ti -8
 .IR ALGO " :="
-.RB "{ " enc " | " auth " | " comp " } " 
-.IR ALGO-NAME " " ALGO-KEY " |"
-.br
-.B  aead
-.IR ALGO-NAME " " ALGO-KEY " " ALGO-ICV-LEN  " |"
+.RB "{ " enc " | " auth " } " 
+.IR ALGO-NAME " " ALGO-KEYMAT " |"
 .br
 .B auth-trunc
-.IR ALGO-NAME " " ALGO-KEY " " ALGO-TRUNC-LEN
+.IR ALGO-NAME " " ALGO-KEYMAT " " ALGO-TRUNC-LEN " |"
+.br
+.B aead
+.IR ALGO-NAME " " ALGO-KEYMAT " " ALGO-ICV-LEN " |"
+.br
+.B comp
+.IR ALGO-NAME
 
 .ti -8
 .IR MODE " := "
-.BR transport " | " tunnel " | " ro " | " in_trigger " | " beet
+.BR transport " | " tunnel " | " beet " | " ro " | " in_trigger
 
 .ti -8
 .IR FLAG-LIST " := [ " FLAG-LIST " ] " FLAG
 
 .ti -8
 .IR FLAG " :="
-.BR noecn " | " decap-dscp " | " nopmtudisc " | " wildrecv " | " icmp " | " af-unspec " | " align4
+.BR noecn " | " decap-dscp " | " nopmtudisc " | " wildrecv " | " icmp " | "
+.BR af-unspec " | " align4 " | " esn
 
 .ti -8
 .IR SELECTOR " :="
@@ -342,7 +350,7 @@
 
 .ti -8
 .IR MODE " := "
-.BR transport " | " tunnel " | " ro " | " in_trigger " | " beet
+.BR transport " | " tunnel " | " beet " | " ro " | " in_trigger
 
 .ti -8
 .IR LEVEL " :="
@@ -366,23 +374,19 @@
 object operating on the Security Policy Database). It is also used for
 the IP Payload Compression Protocol and features of Mobile IPv6.
 
-.SS ip xfrm state add - add new state into xfrm
-
-.SS ip xfrm state update - update existing state in xfrm
-
-.SS ip xfrm state allocspi - allocate an SPI value
-
-.SS ip xfrm state delete - delete existing state in xfrm
-
-.SS ip xfrm state get - get existing state in xfrm
-
-.SS ip xfrm state deleteall - delete all existing state in xfrm
-
-.SS ip xfrm state list - print out the list of existing state in xfrm
-
-.SS ip xfrm state flush - flush all state in xfrm
-
-.SS ip xfrm state count - count all existing state in xfrm
+.TS
+l l.
+ip xfrm state add	add new state into xfrm
+ip xfrm state update	update existing state in xfrm
+ip xfrm state allocspi	allocate an SPI value
+ip xfrm state delete	delete existing state in xfrm
+ip xfrm state get	get existing state in xfrm
+ip xfrm state deleteall	delete all existing state in xfrm
+ip xfrm state list	print out the list of existing state in xfrm
+ip xfrm state flush	flush all state in xfrm
+ip xfrm state count	count all existing state in xfrm
+ip xfrm monitor 	state monitoring for xfrm objects
+.TE
 
 .TP
 .IR ID
@@ -390,6 +394,8 @@
 .RI "transform protocol " XFRM-PROTO ","
 and/or Security Parameter Index
 .IR SPI "."
+(For IP Payload Compression, the Compression Parameter Index or CPI is used for
+.IR SPI ".)"
 
 .TP
 .I XFRM-PROTO
@@ -402,43 +408,74 @@
 
 .TP
 .I ALGO-LIST
-specifies one or more algorithms
-.IR ALGO
-to use. Algorithm types include
+contains one or more algorithms to use. Each algorithm
+.I ALGO
+is specified by:
+.RS
+.IP \[bu]
+the algorithm type:
 .RB "encryption (" enc "),"
-.RB "authentication (" auth "),"
-.RB "authentication with a specified truncation length (" auth-trunc "),"
-.RB "authenticated encryption with associated data (" aead "), and"
-.RB "compression (" comp ")."
-For each algorithm used, the algorithm type, the algorithm name
-.IR ALGO-NAME ","
-and the key
-.I ALGO-KEY
-must be specified. For
-.BR aead ","
+.RB "authentication (" auth " or " auth-trunc "),"
+.RB "authenticated encryption with associated data (" aead "), or"
+.RB "compression (" comp ")"
+.IP \[bu]
+the algorithm name
+.IR ALGO-NAME
+(see below)
+.IP \[bu]
+.RB "(for all except " comp ")"
+the keying material
+.IR ALGO-KEYMAT ","
+which may include both a key and a salt or nonce value; refer to the
+corresponding RFC
+.IP \[bu]
+.RB "(for " auth-trunc " only)"
+the truncation length
+.I ALGO-TRUNC-LEN
+in bits
+.IP \[bu]
+.RB "(for " aead " only)"
 the Integrity Check Value length
 .I ALGO-ICV-LEN
-must additionally be specified.
-For
-.BR auth-trunc ","
-the signature truncation length
-.I ALGO-TRUNC-LEN
-must additionally be specified.
+in bits
+.RE
+
+.nh
+.RS
+Encryption algorithms include
+.BR ecb(cipher_null) ", " cbc(des) ", " cbc(des3_ede) ", " cbc(cast5) ","
+.BR cbc(blowfish) ", " cbc(aes) ", " cbc(serpent) ", " cbc(camellia) ","
+.BR cbc(twofish) ", and " rfc3686(ctr(aes)) "."
+
+Authentication algorithms include
+.BR digest_null ", " hmac(md5) ", " hmac(sha1) ", " hmac(sha256) ","
+.BR hmac(sha384) ", " hmac(sha512) ", " hmac(rmd610) ", and " xcbc(aes) "."
+
+Authenticated encryption with associated data (AEAD) algorithms include
+.BR rfc4106(gcm(aes)) ", " rfc4309(ccm(aes)) ", and " rfc4543(gcm(aes)) "."
+
+Compression algorithms include
+.BR deflate ", " lzs ", and " lzjh "."
+.RE
+.hy
 
 .TP
 .I MODE
-specifies a mode of operation:
-.RB "IPsec transport mode (" transport "), "
-.RB "IPsec tunnel mode (" tunnel "), "
-.RB "Mobile IPv6 route optimization mode (" ro "), "
-.RB "Mobile IPv6 inbound trigger mode (" in_trigger "), or "
-.RB "IPsec ESP Bound End-to-End Tunnel Mode (" beet ")."
+specifies a mode of operation for the transform protocol. IPsec and IP Payload
+Compression modes are
+.BR transport ", " tunnel ","
+and (for IPsec ESP only) Bound End-to-End Tunnel
+.RB "(" beet ")."
+Mobile IPv6 modes are route optimization
+.RB "(" ro ")"
+and inbound trigger
+.RB "(" in_trigger ")."
 
 .TP
 .I FLAG-LIST
 contains one or more of the following optional flags:
 .BR noecn ", " decap-dscp ", " nopmtudisc ", " wildrecv ", " icmp ", "
-.BR af-unspec ", or " align4 "."
+.BR af-unspec ", " align4 ", or " esn "."
 
 .TP
 .IR SELECTOR
@@ -470,22 +507,18 @@
 .BR espinudp " or " espinudp-nonike ","
 .RI "using source port " SPORT ", destination port "  DPORT
 .RI ", and original address " OADDR "."
-
-.SS ip xfrm policy add - add a new policy
-
-.SS ip xfrm policy update - update an existing policy
-
-.SS ip xfrm policy delete - delete an existing policy
-
-.SS ip xfrm policy get - get an existing policy
-
-.SS ip xfrm policy deleteall - delete all existing xfrm policies
-
-.SS ip xfrm policy list - print out the list of xfrm policies
-
-.SS ip xfrm policy flush - flush policies
-
-.SS ip xfrm policy count - count existing policies
+.sp
+.TS
+l l.
+ip xfrm policy add	add a new policy
+ip xfrm policy update	update an existing policy
+ip xfrm policy delete	delete an existing policy
+ip xfrm policy get	get an existing policy
+ip xfrm policy deleteall	delete all existing xfrm policies
+ip xfrm policy list	print out the list of xfrm policies
+ip xfrm policy flush	flush policies
+ip xfrm policy count	count existing policies
+.TE
 
 .TP
 .IR SELECTOR
@@ -550,6 +583,8 @@
 .RI "transform protocol " XFRM-PROTO ","
 and/or Security Parameter Index
 .IR SPI "."
+(For IP Payload Compression, the Compression Parameter Index or CPI is used for
+.IR SPI ".)"
 
 .TP
 .I XFRM-PROTO
@@ -562,20 +597,22 @@
 
 .TP
 .I MODE
-specifies a mode of operation:
-.RB "IPsec transport mode (" transport "), "
-.RB "IPsec tunnel mode (" tunnel "), "
-.RB "Mobile IPv6 route optimization mode (" ro "), "
-.RB "Mobile IPv6 inbound trigger mode (" in_trigger "), or "
-.RB "IPsec ESP Bound End-to-End Tunnel Mode (" beet ")."
+specifies a mode of operation for the transform protocol. IPsec and IP Payload
+Compression modes are
+.BR transport ", " tunnel ","
+and (for IPsec ESP only) Bound End-to-End Tunnel
+.RB "(" beet ")."
+Mobile IPv6 modes are route optimization
+.RB "(" ro ")"
+and inbound trigger
+.RB "(" in_trigger ")."
 
 .TP
 .I LEVEL
 can be
 .BR required " (default) or " use "."
 
-.SS ip xfrm monitor - state monitoring for xfrm objects
 The xfrm objects to monitor can be optionally specified.
 
 .SH AUTHOR
-Manpage by David Ward
+Manpage revised by David Ward <david.ward@ll.mit.edu>
diff --git a/man/man8/ip.8 b/man/man8/ip.8
index ede3d12..4cd71de 100644
--- a/man/man8/ip.8
+++ b/man/man8/ip.8
@@ -12,10 +12,16 @@
 .sp
 
 .ti -8
+.B ip 
+.RB "[ " -force " ] "
+.BI "-batch " filename
+.sp
+
+.ti -8
 .IR OBJECT " := { "
 .BR link " | " addr " | " addrlabel " | " route " | " rule " | " neigh " | "\
  ntable " | " tunnel " | " tuntap " | " maddr " | "  mroute " | " mrule " | "\
- monitor " | " xfrm " | " netns " | "  l2tp " }"
+ monitor " | " xfrm " | " netns " | "  l2tp " | "  tcp_metrics " }"
 .sp
 
 .ti -8
@@ -25,36 +31,53 @@
 \fB\-r\fR[\fIesolve\fR] |
 \fB\-f\fR[\fIamily\fR] {
 .BR inet " | " inet6 " | " ipx " | " dnet " | " link " } | "
-\fB\-o\fR[\fIneline\fR] }
+\fB\-o\fR[\fIneline\fR] |
+\fB\-n\fR[\fIetns\fR] name |
+\fB\-a\fR[\fIll\fR] }
+
 
 .SH OPTIONS
 
 .TP
 .BR "\-V" , " -Version"
-print the version of the
+Print the version of the
 .B ip
 utility and exit.
 
 .TP
-.BR "\-s" , " \-stats", " \-statistics"
-output more information.  If the option
+.BR "\-b", " \-batch " <FILENAME>
+Read commands from provided file or standard input and invoke them.
+First failure will cause termination of ip.
+
+.TP
+.BR "\-force"
+Don't terminate ip on errors in batch mode.
+If there were any errors during execution of the commands, the application return code will be non zero.
+
+.TP
+.BR "\-s" , " \-stats" , " \-statistics"
+Output more information. If the option
 appears twice or more, the amount of information increases.
 As a rule, the information is statistics or some time values.
 
 .TP
-.BR "\-l" , " \-loops"
+.BR "\-d" , " \-details"
+Output more detailed information.
+
+.TP
+.BR "\-l" , " \-loops " <COUNT>
 Specify maximum number of loops the 'ip addr flush' logic
-will attempt before giving up.  The default is 10.
+will attempt before giving up. The default is 10.
 Zero (0) means loop until all addresses are removed.
 
 .TP
-.BR "\-f" , " \-family"
-followed by protocol family identifier:
-.BR "inet" , " inet6"
+.BR "\-f" , " \-family " <FAMILY>
+Specifies the protocol family to use. The protocol family identifier can be one of
+.BR "inet" , " inet6" , " bridge" , " ipx" , " dnet"
 or
-.B link
-,enforce the protocol family to use.  If the option is not present,
-the protocol family is guessed from other arguments.  If the rest
+.BR link .
+If this option is not present,
+the protocol family is guessed from other arguments. If the rest
 of the command line does not give enough information to guess the
 family,
 .B ip
@@ -77,6 +100,21 @@
 .BR "\-family inet6" .
 
 .TP
+.B \-B
+shortcut for
+.BR "\-family bridge" .
+
+.TP
+.B \-D
+shortcut for
+.BR "\-family decnet" .
+
+.TP
+.B \-I
+shortcut for
+.BR "\-family ipx" .
+
+.TP
 .B \-0
 shortcut for
 .BR "\-family link" .
@@ -85,11 +123,11 @@
 .BR "\-o" , " \-oneline"
 output each record on a single line, replacing line feeds
 with the
-.B '\e\'
+.B '\e'
 character. This is convenient when you want to count records
 with
 .BR wc (1)
- or to
+or to
 .BR grep (1)
 the output.
 
@@ -98,6 +136,30 @@
 use the system's name resolver to print DNS names instead of
 host addresses.
 
+.TP
+.BR "\-n" , " \-net" , " \-netns " <NETNS>
+switches
+.B ip
+to the specified network namespace
+.IR NETNS .
+Actually it just simplifies executing of:
+
+.B ip netns exec
+.IR NETNS
+.B ip
+.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+to
+
+.B ip
+.RI "-n[etns] " NETNS " [ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+.TP
+.BR "\-a" , " \-all"
+executes specified command over all objects, it depends if command supports this option.
+
 .SH IP - COMMAND SYNTAX
 
 .SS
@@ -156,6 +218,10 @@
 - rule in routing policy database.
 
 .TP
+.B tcp_metrics/tcpmetrics
+- manage TCP Metrics
+
+.TP
 .B tunnel
 - tunnel over IP.
 
@@ -169,9 +235,9 @@
 
 .PP
 The names of all objects may be written in full or
-abbreviated form, f.e.
+abbreviated form, for exampe
 .B address
-is abbreviated as
+can be abbreviated as
 .B addr
 or just
 .B a.
@@ -188,9 +254,9 @@
 (or
 .B list
 ) objects, but some objects do not allow all of these operations
-or have some additional commands.  The
+or have some additional commands. The
 .B help
-command is available for all objects.  It prints
+command is available for all objects. It prints
 out a list of available commands and argument syntax conventions.
 .sp
 If no command is given, some default command is assumed.
@@ -215,12 +281,13 @@
 .BR ip-ntable (8),
 .BR ip-route (8),
 .BR ip-rule (8),
+.BR ip-tcp_metrics (8),
 .BR ip-tunnel (8),
 .BR ip-xfrm (8)
 .br
 .RB "IP Command reference " ip-cref.ps
 .SH REPORTING BUGS
-Report bug to the Network Developers mailing list
+Report any bugs to the Network Developers mailing list
 .B <netdev@vger.kernel.org>
 where the development and maintenance is primarily done.
 You do not have to be subscribed to the list to send a message there.
diff --git a/man/man8/lnstat.8 b/man/man8/lnstat.8
index 315a8e5..699ddf4 100644
--- a/man/man8/lnstat.8
+++ b/man/man8/lnstat.8
@@ -13,8 +13,6 @@
 In addition to routing cache statistics, it supports any kind of statistics the linux kernel
 exports via a file in /proc/net/stat/.
 .SH OPTIONS
-These programs follow the usual GNU command line syntax, with long
-options starting with two dashes (`-').
 lnstat supports the following options.
 .TP
 .B \-h, \-\-help
@@ -35,6 +33,9 @@
 .B \-i, \-\-interval <intv>
 Set interval to 'intv' seconds.
 .TP
+.B \-j, \-\-json
+Display results in JSON format
+.TP
 .B \-k, \-\-keys k,k,k,...
 Display only keys specified.
 .TP
diff --git a/man/man8/rtacct.8 b/man/man8/rtacct.8
index fb9afe8..c3ab03d 100644
--- a/man/man8/rtacct.8
+++ b/man/man8/rtacct.8
@@ -15,33 +15,35 @@
 are simple tools to monitor kernel snmp counters and network interface statistics.
 
 .SH OPTIONS
-.TP
--h -?
+.B \-h, \-\-help
 Print help
 .TP
--v -V
+.B \-V, \-\-version
 Print version
 .TP
--z
+.B \-z, \-\-zero
 Dump zero counters too. By default they are not shown.
 .TP
--r
+.B \-r, \-\-reset
 Reset history.
 .TP
--n
+.B \-n, \-\-nooutput
 Do not display anything, only update history.
 .TP
--a
+.B \-a, \-\-ignore
 Dump absolute values of counters. The default is to calculate increments since the previous use.
 .TP
--s
+.B \-s, \-\-noupdate
 Do not update history, so that the next time you will see counters including values accumulated to the moment of this measurement too.
+.B \-j, \-\-json
+Display results in JSON format.
 .TP
--d <INTERVAL>
+.B \-d, \-\-interval <INTERVAL>
 Run in daemon mode collecting statistics. <INTERVAL> is interval between measurements in seconds.
 .TP
--t <INTERVAL>
+
 Time interval to average rates. Default value is 60 seconds.
+.TP
 
 .SH SEE ALSO
 lnstat(8)
diff --git a/man/man8/rtmon.8 b/man/man8/rtmon.8
index c9359d8..0538752 100644
--- a/man/man8/rtmon.8
+++ b/man/man8/rtmon.8
@@ -34,7 +34,7 @@
 .TP
 .B file FILE [ all | LISTofOBJECTS ]
 Log output to FILE. LISTofOBJECTS is the list of object types that we
-want to monitor.  It may contain 'link', 'address', 'route'
+want to monitor. It may contain 'link', 'address', 'route'
 and 'all'. 'link' specifies the network device, 'address' the protocol
 (IP or IPv6) address on a device, 'route' the routing table entry
 and 'all' does what the name says.
diff --git a/man/man8/ss.8 b/man/man8/ss.8
index 0b9a8c4..b7fbaef 100644
--- a/man/man8/ss.8
+++ b/man/man8/ss.8
@@ -15,10 +15,6 @@
 When no option is used ss displays a list of 
 open non-listening TCP sockets that have established connection.
 .TP
-These programs follow the usual GNU command line syntax, with long
-options starting with two dashes (`-').
-A summary of options is included below.
-.TP
 .B \-h, \-\-help
 Show summary of options.
 .TP
@@ -57,6 +53,43 @@
 summary from various sources. It is useful when amount of sockets is so huge
 that parsing /proc/net/tcp is painful.
 .TP
+.B \-Z, \-\-context
+As the
+.B \-p
+option but also shows process security context.
+.sp
+For
+.BR netlink (7)
+sockets the initiating process context is displayed as follows:
+.RS
+.RS
+.IP "1." 4
+If valid pid show the process context.
+.IP "2." 4
+If destination is kernel (pid = 0) show kernel initial context.
+.IP "3." 4
+If a unique identifier has been allocated by the kernel or netlink user,
+show context as "unavailable". This will generally indicate that a
+process has more than one netlink socket active.
+.RE
+.RE
+.TP
+.B \-z, \-\-contexts
+As the
+.B \-Z
+option but also shows the socket context. The socket context is
+taken from the associated inode and is not the actual socket
+context held by the kernel. Sockets are typically labeled with the
+context of the creating process, however the context shown will reflect
+any policy role, type and/or range transition rules applied,
+and is therefore a useful reference.
+.TP
+.B \-N NSNAME, \-\-net=NSNAME
+Switch to the specified network namespace name.
+.TP
+.B \-b, \-\-bpf
+Show socket BPF filters (only administrators are allowed to get these information).
+.TP
 .B \-4, \-\-ipv4
 Display only IP version 4 sockets (alias for -f inet).
 .TP
@@ -88,7 +121,7 @@
 .B \-A QUERY, \-\-query=QUERY, \-\-socket=QUERY
 List of socket tables to dump, separated by commas. The following identifiers
 are understood: all, inet, tcp, udp, raw, unix, packet, netlink, unix_dgram,
-unix_stream, packet_raw, packet_dgram.
+unix_stream, unix_seqpacket, packet_raw, packet_dgram.
 .TP
 .B \-D FILE, \-\-diag=FILE
 Do not display anything, just dump raw information about TCP sockets to FILE after applying filters. If FILE is - stdout is used.
@@ -97,13 +130,49 @@
 Read filter information from FILE.
 Each line of FILE is interpreted like single command line option. If FILE is - stdin is used.
 .TP
-.B FILTER := [ state TCP-STATE ] [ EXPRESSION ]
+.B FILTER := [ state STATE-FILTER ] [ EXPRESSION ]
 Please take a look at the official documentation (Debian package iproute-doc) for details regarding filters.
+
+.SH STATE-FILTER
+
+.B STATE-FILTER
+allows to construct arbitrary set of states to match. Its syntax is sequence of keywords state and exclude followed by identifier of state.
+.TP
+Available identifiers are:
+
+All standard TCP states:
+.BR established ", " syn-sent ", " syn-recv ", " fin-wait-1 ", " fin-wait-2 ", " time-wait ", " closed ", " close-wait ", " last-ack ", "
+.BR  listen " and " closing.
+
+.B all
+- for all the states
+
+.B connected
+- all the states except for
+.BR listen " and " closed
+
+.B synchronized
+- all the
+.B connected
+states except for
+.B syn-sent
+
+.B bucket
+- states, which are maintained as minisockets, i.e.
+.BR time-wait " and " syn-recv
+
+.B big
+- opposite to
+.B bucket
+
 .SH USAGE EXAMPLES
 .TP
 .B ss -t -a
 Display all TCP sockets.
 .TP
+.B ss -t -a -Z
+Display all TCP sockets with process SELinux security contexts.
+.TP
 .B ss -u -a
 Display all UDP sockets.
 .TP
@@ -117,10 +186,14 @@
 List all the tcp sockets in state FIN-WAIT-1 for our apache to network 193.233.7/24 and look at their timers.
 .SH SEE ALSO
 .BR ip (8),
-.BR /usr/share/doc/iproute-doc/ss.html " (package iproute­doc)"
+.BR /usr/share/doc/iproute-doc/ss.html " (package iproute­doc)",
+.br
+.BR RFC " 793 "
+- https://tools.ietf.org/rfc/rfc793.txt (TCP states) 
+
 .SH AUTHOR
 .I ss 
-was written by Alexey Kuznetosv, <kuznet@ms2.inr.ac.ru>.
+was written by Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>.
 .PP
 This manual page was written by Michael Prokop <mika@grml.org>
 for the Debian project (but may be used by others).
diff --git a/man/man8/tc-bfifo.8 b/man/man8/tc-bfifo.8
index 8dda4bb..f04090c 100644
--- a/man/man8/tc-bfifo.8
+++ b/man/man8/tc-bfifo.8
@@ -40,8 +40,12 @@
 .BR ifconfig (8)
 or
 .BR ip (8).
+The range for this parameter is [0, UINT32_MAX].
 
 For bfifo, it defaults to the txqueuelen multiplied by the interface MTU.
+The range for this parameter is [0, UINT32_MAX] bytes.
+
+Note: The link layer header was considered when counting packets length.
 
 .SH OUTPUT
 The output of 
diff --git a/man/man8/tc-cbq-details.8 b/man/man8/tc-cbq-details.8
index 09badb9..ddaf3ca 100644
--- a/man/man8/tc-cbq-details.8
+++ b/man/man8/tc-cbq-details.8
@@ -58,8 +58,8 @@
 
 .SH DESCRIPTION
 Class Based Queueing is a classful qdisc that implements a rich
-linksharing hierarchy of classes.  It contains shaping elements as
-well as prioritizing capabilities.  Shaping is performed using link
+linksharing hierarchy of classes. It contains shaping elements as
+well as prioritizing capabilities. Shaping is performed using link
 idle time calculations based on the timing of dequeue events and 
 underlying link bandwidth.
 
@@ -273,7 +273,7 @@
 .TP
 ewma log
 When CBQ needs to measure the average idle time, it does so using an 
-Exponentially Weighted Moving Average which smoothes out measurements into
+Exponentially Weighted Moving Average which smooths out measurements into
 a moving average. The EWMA LOG determines how much smoothing occurs. Defaults 
 to 5. Lower values imply greater sensitivity. Must be between 0 and 31.
 .P
diff --git a/man/man8/tc-cbq.8 b/man/man8/tc-cbq.8
index 79fb93b..b900e1c 100644
--- a/man/man8/tc-cbq.8
+++ b/man/man8/tc-cbq.8
@@ -60,8 +60,8 @@
 
 .SH DESCRIPTION
 Class Based Queueing is a classful qdisc that implements a rich
-linksharing hierarchy of classes.  It contains shaping elements as
-well as prioritizing capabilities.  Shaping is performed using link
+linksharing hierarchy of classes. It contains shaping elements as
+well as prioritizing capabilities. Shaping is performed using link
 idle time calculations based on the timing of dequeue events and 
 underlying link bandwidth.
 
@@ -184,7 +184,7 @@
 .TP
 ewma log
 When CBQ needs to measure the average idle time, it does so using an 
-Exponentially Weighted Moving Average which smoothes out measurements into
+Exponentially Weighted Moving Average which smooths out measurements into
 a moving average. The EWMA LOG determines how much smoothing occurs. Lower 
 values imply greater sensitivity. Must be between 0 and 31. Defaults 
 to 5.
diff --git a/man/man8/tc-choke.8 b/man/man8/tc-choke.8
index 620c7f6..1916a3d 100644
--- a/man/man8/tc-choke.8
+++ b/man/man8/tc-choke.8
@@ -22,19 +22,19 @@
 
 CHOKe (CHOose and Keep for responsive flows, CHOose and Kill for unresponsive flows)
 is a classless qdisc designed to both identify and penalize flows that monopolize the
-queue.  CHOKe is a variation of RED, and the configuration is similar to RED.
+queue. CHOKe is a variation of RED, and the configuration is similar to RED.
 
 .SH ALGORITHM
 Once the queue hits a certain average length, a random packet is drawn from the
-queue.  If both the to-be-queued and the drawn packet belong to the same flow,
-both packets are dropped.  Otherwise, if the queue length is still below the maximum length,
+queue. If both the to-be-queued and the drawn packet belong to the same flow,
+both packets are dropped. Otherwise, if the queue length is still below the maximum length,
 the new packet has a configurable chance of being marked (which may mean dropped).
 If the queue length exceeds
-.B max
-, the new packet will always be marked (or dropped).
+.BR max ,
+the new packet will always be marked (or dropped).
 If the queue length exceeds
-.B limit
-, the new packet is always dropped.
+.BR limit ,
+the new packet is always dropped.
 
 The marking probability computation is the same as used by the RED qdisc.
 
diff --git a/man/man8/tc-codel.8 b/man/man8/tc-codel.8
new file mode 100644
index 0000000..a0e50a4
--- /dev/null
+++ b/man/man8/tc-codel.8
@@ -0,0 +1,114 @@
+.TH CoDel 8 "23 May 2012" "iproute2" "Linux"
+.SH NAME
+CoDel \- Controlled-Delay Active Queue Management algorithm
+.SH SYNOPSIS
+.B tc qdisc ... codel
+[
+.B limit
+PACKETS ] [
+.B target
+TIME ] [
+.B interval
+TIME ] [
+.B ecn
+|
+.B noecn
+]
+
+.SH DESCRIPTION
+CoDel (pronounced "coddle") is an adaptive "no-knobs" active queue management
+algorithm (AQM) scheme that was developed to address the shortcomings of
+RED and its variants. It was developed with the following goals
+in mind:
+ o It should be parameterless.
+ o It should keep delays low while permitting bursts of traffic.
+ o It should control delay.
+ o It should adapt dynamically to changing link rates with no impact on
+utilization.
+ o It should be simple and efficient and should scale from simple to
+complex routers.
+
+.SH ALGORITHM
+CoDel comes with three major innovations. Instead of using queue size or queue
+average, it uses the local minimum queue as a measure of the standing/persistent queue.
+Second, it uses a single state-tracking variable of the minimum delay to see where it
+is relative to the standing queue delay. Third, instead of measuring queue size
+in bytes or packets, it is measured in packet-sojourn time in the queue.
+
+CoDel measures the minimum local queue delay (i.e. standing queue delay) and
+compares it to the value of the given acceptable queue delay
+.B target.
+As long as the minimum queue delay is less than
+.B target
+or the buffer contains fewer than MTU worth of bytes, packets are not dropped.
+Codel enters a dropping mode when the minimum queue delay has exceeded
+.B target
+for a time greater than
+.B interval.
+In this mode, packets are dropped at different drop times which is set by a
+control law. The control law ensures that the packet drops cause a linear change
+in the throughput. Once the minimum delay goes below
+.B target,
+packets are no longer dropped.
+
+Additional details can be found in the paper cited below.
+
+.SH PARAMETERS
+.SS limit
+hard limit on the real queue size. When this limit is reached, incoming packets
+are dropped. If the value is lowered, packets are dropped so that the new limit is
+met. Default is 1000 packets.
+
+.SS target
+is the acceptable minimum standing/persistent queue delay. This minimum delay
+is identified by tracking the local minimum queue delay that packets experience.
+Default and recommended value is 5ms.
+
+.SS interval
+is used to ensure that the measured minimum delay does not become too stale. The
+minimum delay must be experienced in the last epoch of length
+.B interval.
+It should be set on the order of the worst-case RTT through the bottleneck to
+give endpoints sufficient time to react. Default value is 100ms.
+
+.SS ecn | noecn
+can be used to mark packets instead of dropping them. If
+.B ecn
+has been enabled,
+.B noecn
+can be used to turn it off and vice-a-versa. By default,
+.B ecn
+is turned off.
+
+.SH EXAMPLES
+ # tc qdisc add dev eth0 root codel
+ # tc -s qdisc show
+   qdisc codel 801b: dev eth0 root refcnt 2 limit 1000p target 5.0ms
+interval 100.0ms
+    Sent 245801662 bytes 275853 pkt (dropped 0, overlimits 0 requeues 24)
+    backlog 0b 0p requeues 24
+     count 0 lastcount 0 ldelay 2us drop_next 0us
+     maxpacket 7306 ecn_mark 0 drop_overlimit 0
+
+ # tc qdisc add dev eth0 root codel limit 100 target 4ms interval 30ms ecn
+ # tc -s qdisc show
+   qdisc codel 801c: dev eth0 root refcnt 2 limit 100p target 4.0ms
+interval 30.0ms ecn
+    Sent 237573074 bytes 268561 pkt (dropped 0, overlimits 0 requeues 5)
+    backlog 0b 0p requeues 5
+     count 0 lastcount 0 ldelay 76us drop_next 0us
+     maxpacket 2962 ecn_mark 0 drop_overlimit 0
+
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-red (8)
+
+.SH SOURCES
+o   Kathleen Nichols and Van Jacobson, "Controlling Queue Delay", ACM Queue,
+http://queue.acm.org/detail.cfm?id=2209336
+
+.SH AUTHORS
+CoDel was implemented by Eric Dumazet and David Taht. This manpage was written
+by Vijay Subramanian. Please reports corrections to the Linux Networking
+mailing list <netdev@vger.kernel.org>.
diff --git a/man/man8/tc-drr.8 b/man/man8/tc-drr.8
index 16a8ec0..f550a35 100644
--- a/man/man8/tc-drr.8
+++ b/man/man8/tc-drr.8
@@ -23,9 +23,9 @@
 .B quantum.
 
 DRR maintains an (internal) ''active'' list of classes whose qdiscs are
-non-empty.  This list is used for dequeuing.  A packet is dequeued from
+non-empty. This list is used for dequeuing. A packet is dequeued from
 the class at the head of the list if the packet size is smaller or equal
-to the deficit counter.  If the counter is too small, it is increased by
+to the deficit counter. If the counter is too small, it is increased by
 .B quantum
 and the scheduler moves on to the next class in the active list.
 
@@ -34,7 +34,7 @@
 .TP
 quantum
 Amount of bytes a flow is allowed to dequeue before the scheduler moves to
-the next class.  Defaults to the MTU of the interface. The minimum value is 1.
+the next class. Defaults to the MTU of the interface. The minimum value is 1.
 
 .SH EXAMPLE & USAGE
 
@@ -45,6 +45,7 @@
 Adding two classes:
 .P
 # tc class add dev eth0 parent 1: classid 1:1 drr
+.br
 # tc class add dev eth0 parent 1: classid 1:2 drr
 .P
 You also need to add at least one filter to classify packets.
@@ -53,9 +54,9 @@
 .P
 
 Like SFQ, DRR is only useful when it owns the queue \-\- it is a pure scheduler and does
-not delay packets.  Attaching non-work-conserving qdiscs like tbf to it does not make
+not delay packets. Attaching non-work-conserving qdiscs like tbf to it does not make
 sense \-\- other qdiscs in the active list will also become inactive until the dequeue
-operation succeeds.  Embed DRR within another qdisc like HTB or HFSC to ensure it owns the queue.
+operation succeeds. Embed DRR within another qdisc like HTB or HFSC to ensure it owns the queue.
 .P
 You can mimic SFQ behavior by assigning packets to the attached classes using the
 flow filter:
@@ -64,9 +65,9 @@
 
 .B for i in .. 1024;do
 .br
-.B \ttc class add dev ..  classid $handle:$(print %x $i)
+.B "\ttc class add dev .. classid $handle:$(print %x $i)"
 .br
-.B \ttc qdisc add dev .. fifo limit 16
+.B "\ttc qdisc add dev .. fifo limit 16"
 .br
 .B done
 
diff --git a/man/man8/tc-ematch.8 b/man/man8/tc-ematch.8
new file mode 100644
index 0000000..b9bf70c
--- /dev/null
+++ b/man/man8/tc-ematch.8
@@ -0,0 +1,132 @@
+.TH ematch 8 "6 August 2012" iproute2 Linux
+.
+.SH NAME
+ematch \- extended matches for use with "basic" or "flow" filters
+.
+.SH SYNOPSIS
+.sp
+.ad l
+.B "tc filter add .. basic match"
+.RI EXPR
+.B .. flowid ..
+.sp
+
+.IR EXPR " := " TERM " [ { "
+.B and | or
+}
+.IR EXPR
+]
+
+.IR TERM " := [ " \fBnot " ] { " MATCH " | '(' " EXPR " ')' } "
+
+.IR MATCH " := " module " '(' " ARGS " ')' "
+
+.IR ARGS " := " ARG1 " " ARG2 " ..
+
+.SH MATCHES
+
+.SS cmp
+Simple comparison ematch: arithmetic compare of packet data to a given value.
+
+.IR cmp "( " ALIGN " at " OFFSET " [ " ATTRS " ] { " eq " | " lt " | " gt " } " VALUE " )
+
+.IR ALIGN " := { " u8 " | " u16 " | " u32 " } "
+
+.IR ATTRS " := [ layer " LAYER " ] [ mask " MASK " ] [ trans ]
+
+.IR LAYER " := { " link " | " network " | " transport " | " 0..2 " }
+
+.SS meta
+Metadata ematch
+
+.IR meta "( " OBJECT " { " eq " | " lt " |" gt " } " OBJECT " )
+
+.IR OBJECT " := { " META_ID " |  " VALUE " }
+
+.IR META_ID " := " id " [ shift " SHIFT " ] [ mask " MASK " ]
+
+.TP
+meta attributes:
+
+\fBrandom\fP 32 bit random value
+
+\fBloadavg_1\fP Load average in last 5 minutes
+
+\fBnf_mark\fP Netfilter mark
+
+\fBvlan\fP Vlan tag
+
+\fBsk_rcvbuf\fP Receive buffer size
+
+\fBsk_snd_queue\fP Send queue length
+
+.PP
+A full list of meta attributes can be obtained via
+
+# tc filter add dev eth1 basic match 'meta(list)'
+
+.SS nbyte
+match packet data byte sequence
+
+.IR nbyte "( " NEEDLE  " at " OFFSET " [ layer " LAYER " ] )
+
+.IR NEEDLE  " := { " string " | " c-escape-sequence "  } "
+
+.IR OFFSET  " := " int
+
+.IR LAYER " := { " link " | " network " | " transport " | " 0..2 " }
+
+.SS u32
+u32 ematch
+
+.IR u32 "( " ALIGN " " VALUE " " MASK " at [ nexthdr+ ] " OFFSET " )
+
+.IR ALIGN " := { " u8 " | " u16 " | " u32 " }
+
+.SS ipset
+test packet against ipset membership
+
+.IR ipset "( " SETNAME " " FLAGS " )
+
+.IR SETNAME " := " string
+
+.IR FLAGS " := { " FLAG " [, " FLAGS "] }
+
+The flag options are the same as those used by the iptables "set" match.
+
+When using the ipset ematch with the "ip_set_hash:net,iface" set type,
+the interface can be queried using "src,dst (source ip address, outgoing interface) or
+"src,src" (source ip address, incoming interface) syntax.
+
+.SH CAVEATS
+
+The ematch syntax uses '(' and ')' to group expressions. All braces need to be
+escaped properly to prevent shell commandline from interpreting these directly.
+
+When using the ipset ematch with the "ifb" device, the outgoing device will be the
+ifb device itself, e.g. "ifb0".
+The original interface (i.e. the device the packet arrived on) is treated as the incoming interface.
+
+.SH EXAMPLE & USAGE
+
+# tc filter add .. basic match ...
+
+# 'cmp(u16 at 3 layer 2 mask 0xff00 gt 20)'
+
+# 'meta(nfmark gt 24)' and 'meta(tcindex mask 0xf0 eq 0xf0)'
+
+# 'nbyte("ababa" at 12 layer 1)'
+
+# 'u32(u16 0x1122 0xffff at nexthdr+4)'
+
+Check if packet source ip address is member of set named \fBbulk\fP:
+
+# 'ipset(bulk src)'
+
+Check if packet source ip and the interface the packet arrived on is member of "hash:net,iface" set named \fBinteractive\fP:
+
+# 'ipset(interactive src,src)'
+
+.SH "AUTHOR"
+
+The extended match infrastructure was added by Thomas Graf.
diff --git a/man/man8/tc-fq_codel.8 b/man/man8/tc-fq_codel.8
new file mode 100644
index 0000000..a80389a
--- /dev/null
+++ b/man/man8/tc-fq_codel.8
@@ -0,0 +1,108 @@
+.TH FQ_CoDel 8 "4 June 2012" "iproute2" "Linux"
+.SH NAME
+CoDel \- Fair Queuing (FQ) with Controlled Delay (CoDel)
+.SH SYNOPSIS
+.B tc qdisc ... fq_codel
+[
+.B limit
+PACKETS ] [
+.B flows
+NUMBER ] [
+.B target
+TIME ] [
+.B interval
+TIME ] [
+.B quantum
+BYTES ] [
+.B ecn
+|
+.B noecn
+]
+
+.SH DESCRIPTION
+FQ_Codel (Fair Queuing Controlled Delay) is queuing discipline that combines Fair
+Queuing with the CoDel AQM scheme. FQ_Codel uses a stochastic model to classify
+incoming packets into different flows and is used to provide a fair share of the
+bandwidth to all the flows using the queue. Each such flow is managed by the
+CoDel queuing discipline. Reordering within a flow is avoided since Codel
+internally uses a FIFO queue.
+
+.SH PARAMETERS
+.SS limit
+has the same semantics as
+.B codel
+and is the hard limit on the real queue size.
+When this limit is reached, incoming packets are dropped. Default is 10240
+packets.
+
+.SS flows
+is the number of flows into which the incoming packets are classified. Due to
+the stochastic nature of hashing, multiple flows may end up being hashed into
+the same slot. Newer flows have priority over older ones. This parameter can be
+set only at load time since memory has to be allocated for the hash table.
+Default value is 1024.
+
+.SS target
+has the same semantics as
+.B codel
+and is the acceptable minimum
+standing/persistent queue delay. This minimum delay is identified by tracking
+the local minimum queue delay that packets experience. Default value is 5ms.
+
+.SS interval
+has the same semantics as
+.B codel
+and is used to ensure that the measured minimum delay does not become too stale.
+The minimum delay must be experienced in the last epoch of length .B interval.
+It should be set on the order of the worst-case RTT through the bottleneck to
+give endpoints sufficient time to react. Default value is 100ms.
+
+.SS quantum
+is the number of bytes used as 'deficit' in the fair queuing algorithm. Default
+is set to 1514 bytes which corresponds to the Ethernet MTU plus the hardware
+header length of 14 bytes.
+
+.SS ecn | noecn
+has the same semantics as
+.B codel
+and can be used to mark packets instead of dropping them. If
+.B ecn
+has been enabled,
+.B noecn
+can be used to turn it off and vice-a-versa. Unlike
+.B codel, ecn
+is turned on by default.
+
+.SH EXAMPLES
+#tc qdisc add   dev eth0 root fq_codel
+.br
+#tc -s qdisc show
+.br
+qdisc fq_codel 8002: dev eth0 root refcnt 2 limit 10240p flows 1024 quantum 1514
+ target 5.0ms interval 100.0ms ecn
+   Sent 428514 bytes 2269 pkt (dropped 0, overlimits 0 requeues 0)
+   backlog 0b 0p requeues 0
+    maxpacket 256 drop_overlimit 0 new_flow_count 0 ecn_mark 0
+    new_flows_len 0 old_flows_len 0
+
+#tc qdisc add dev eth0 root fq_codel limit 2000 target 3ms interval 40ms noecn
+.br
+#tc -s qdisc show
+.br
+qdisc fq_codel 8003: dev eth0 root refcnt 2 limit 2000p flows 1024 quantum 1514
+target 3.0ms interval 40.0ms
+ Sent 2588985006 bytes 1783629 pkt (dropped 0, overlimits 0 requeues 34869)
+ backlog 0b 0p requeues 34869
+  maxpacket 65226 drop_overlimit 0 new_flow_count 73 ecn_mark 0
+  new_flows_len 1 old_flows_len 3
+
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-codel (8),
+.BR tc-red (8)
+
+.SH AUTHORS
+FQ_CoDel was implemented by Eric Dumazet. This manpage was written
+by Vijay Subramanian. Please report corrections to the Linux Networking
+mailing list <netdev@vger.kernel.org>.
diff --git a/man/man8/tc-hfsc.8 b/man/man8/tc-hfsc.8
index c5ff331..5444118 100644
--- a/man/man8/tc-hfsc.8
+++ b/man/man8/tc-hfsc.8
@@ -32,7 +32,7 @@
 section of \fBtc\fR(8).
 .
 .SH DESCRIPTION (qdisc)
-HFSC qdisc has only one optional parameter \- \fBdefault\fR.  CLASSID specifies
+HFSC qdisc has only one optional parameter \- \fBdefault\fR. CLASSID specifies
 the minor part of the default classid, where packets not classified by other
 means (e.g. u32 filter, CLASSIFY target of iptables) will be enqueued. If
 \fBdefault\fR is not specified, unclassified packets will be dropped.
diff --git a/man/man8/tc-mqprio.8 b/man/man8/tc-mqprio.8
new file mode 100644
index 0000000..da3bf08
--- /dev/null
+++ b/man/man8/tc-mqprio.8
@@ -0,0 +1,112 @@
+.TH MQPRIO 8 "24 Sept 2013" "iproute2" "Linux"
+.SH NAME
+MQPRIO \- Multiqueue Priority Qdisc (Offloaded Hardware QOS)
+.SH SYNOPSIS
+.B tc qdisc ... dev
+dev
+.B  ( parent
+classid
+.B | root) [ handle
+major:
+.B ] mqprio [ numtc
+tcs
+.B ] [ map
+P0 P1 P2...
+.B ] [ queues
+count1@offset1 count2@offset2 ...
+.B ] [ hw
+1|0
+.B ]
+
+.SH DESCRIPTION
+The MQPRIO qdisc is a simple queuing discipline that allows mapping
+traffic flows to hardware queue ranges using priorities and a configurable
+priority to traffic class mapping. A traffic class in this context is
+a set of contiguous qdisc classes which map 1:1 to a set of hardware
+exposed queues.
+
+By default the qdisc allocates a pfifo qdisc (packet limited first in, first
+out queue) per TX queue exposed by the lower layer device. Other queuing
+disciplines may be added subsequently. Packets are enqueued using the
+.B map
+parameter and hashed across the indicated queues in the
+.B offset
+and
+.B count.
+By default these parameters are configured by the hardware
+driver to match the hardware QOS structures.
+
+Enabled hardware can provide hardware QOS with the ability to steer
+traffic flows to designated traffic classes provided by this qdisc.
+Configuring the hardware based QOS mechanism is outside the scope of
+this qdisc. Tools such as
+.B lldpad
+and
+.B ethtool
+exist to provide this functionality. Also further qdiscs may be added
+to the classes of MQPRIO to create more complex configurations.
+
+.SH ALGORITHM
+On creation with 'tc qdisc add', eight traffic classes are created mapping
+priorities 0..7 to traffic classes 0..7 and priorities greater than 7 to
+traffic class 0. This requires base driver support and the creation will
+fail on devices that do not support hardware QOS schemes.
+
+These defaults can be overridden using the qdisc parameters. Providing
+the 'hw 0' flag allows software to run without hardware coordination.
+
+If hardware coordination is being used and arguments are provided that
+the hardware can not support then an error is returned. For many users
+hardware defaults should work reasonably well.
+
+As one specific example numerous Ethernet cards support the 802.1Q
+link strict priority transmission selection algorithm (TSA). MQPRIO
+enabled hardware in conjunction with the classification methods below
+can provide hardware offloaded support for this TSA.
+
+.SH CLASSIFICATION
+Multiple methods are available to set the SKB priority which MQPRIO
+uses to select which traffic class to enqueue the packet.
+.TP
+From user space
+A process with sufficient privileges can encode the destination class
+directly with SO_PRIORITY, see
+.BR socket(7).
+.TP
+with iptables/nftables
+An iptables/nftables rule can be created to match traffic flows and
+set the priority.
+.BR iptables(8)
+.TP
+with net_prio cgroups
+The net_prio cgroup can be used to set the priority of all sockets
+belong to an application. See kernel and cgroup documentation for details.
+
+.SH QDISC PARAMETERS
+.TP
+num_tc
+Number of traffic classes to use upto 16 classes supported.
+
+.TP
+map
+The priority to traffic class map. Maps priorities 0..15 to a specified
+traffic class.
+
+.TP
+queues
+Provide count and offset of queue range for each traffic class. In the
+format,
+.B count@offset.
+Queue ranges for each traffic classes cannot overlap and must be a
+contiguous range of queues.
+
+.TP
+hw
+Set to
+.B 1
+to use hardware QOS defaults. Set to
+.B 0
+to override hardware defaults with user specified values.
+
+.SH AUTHORS
+John Fastabend, <john.r.fastabend@intel.com>
diff --git a/man/man8/tc-netem.8 b/man/man8/tc-netem.8
index 39f8454..53c4de9 100644
--- a/man/man8/tc-netem.8
+++ b/man/man8/tc-netem.8
@@ -30,8 +30,8 @@
 .IR p13 " [ " p31 " [ " p32 " [ " p23 " [ " p14 "]]]] |"
 .br
 .RB "               " gemodel
-.IR p " [ " r " [ " 1-h " [ " 1-k " ]]]"
-.BR " }"
+.IR p " [ " r " [ " 1-h " [ " 1-k " ]]] } "
+.RB  " [ " ecn " ] "
 
 .IR CORRUPT " := "
 .B corrupt
@@ -102,6 +102,10 @@
 the good states, 1-h is the loss probability in the bad state and 1-k is the
 loss probability in the good state.
 
+.SS ecn
+can be used optionally to mark packets instead of dropping them. A loss model
+has to be used for this to be enabled.
+
 .SS corrupt
 allows the emulation of random noise introducing an error in a random position
 for a chosen percent of packets. It is also possible to add a correlation
@@ -147,7 +151,7 @@
 the cellsize. Cellsize can be used to simulate link layer schemes. ATM for
 example has an payload cellsize of 48 bytes and 5 byte per cell header. If a
 packet is 50 byte then ATM must use two cells: 2 * 48 bytes payload including 2
-* 5 byte header, thus consume 106 byte on the wire.  The last optional value
+* 5 byte header, thus consume 106 byte on the wire. The last optional value
 .I CELLOVERHEAD 
 can be used to specify per cell overhead - for our ATM example 5.
 .I CELLOVERHEAD
diff --git a/man/man8/tc-pie.8 b/man/man8/tc-pie.8
new file mode 100644
index 0000000..278293b
--- /dev/null
+++ b/man/man8/tc-pie.8
@@ -0,0 +1,131 @@
+.TH PIE 8 "16 January 2014" "iproute2" "Linux"
+.SH NAME
+PIE \- Proportional Integral controller-Enhanced AQM algorithm
+.SH SYNOPSIS
+.B tc qdisc ... pie
+[
+.B limit
+PACKETS ] [
+.B target
+TIME ] [
+.B tupdate
+TIME ] [
+.B alpha
+int ] [
+.B beta
+int ] [
+.B ecn
+|
+.B noecn
+] [
+.B bytemode
+|
+.B nobytemode
+]
+
+.SH DESCRIPTION
+Proportional Integral controller-Enhanced (PIE) is a control theoretic active
+queue management scheme. It is based on the proportional integral controller but
+aims to control delay. The main design goals are
+ o Low latency control
+ o High link utilization
+ o Simple implementation
+ o Guaranteed stability and fast responsiveness
+
+.SH ALGORITHM
+PIE is designed to control delay effectively. First, an average dequeue rate is
+estimated based on the standing queue. The rate is used to calculate the current
+delay. Then, on a periodic basis, the delay is used to calculate the dropping
+probabilty. Finally, on arrival, a packet is dropped (or marked) based on this
+probability.
+
+PIE makes adjustments to the probability based on the trend of the delay i.e.
+whether it is going up or down.The delay converges quickly to the target value
+specified.
+
+alpha and beta are statically chosen parameters chosen to control the drop probability
+growth and are determined through control theoretic approaches. alpha determines how
+the deviation between the current and target latency changes probability. beta exerts
+additional adjustments depending on the latency trend.
+
+The drop probabilty is used to mark packets in ecn mode. However, as in RED,
+beyond 10% packets are dropped based on this probability. The bytemode is used
+to drop packets proportional to the packet size.
+
+Additional details can be found in the paper cited below.
+
+.SH PARAMETERS
+.SS limit
+limit on the queue size in packets. Incoming packets are dropped when this limit
+is reached. Default is 1000 packets.
+
+.SS target
+is the expected queue delay. The default target delay is 20ms.
+
+.SS tupdate
+is the frequency at which the system drop probability is calculated. The default is 30ms.
+
+.SS alpha
+.SS beta
+alpha and beta are parameters chosen to control the drop probability. These
+should be in the range between 0 and 32.
+
+.SS ecn | noecn
+is used to mark packets instead of dropping
+.B ecn
+to turn on ecn mode,
+.B noecn
+to turn off ecn mode. By default,
+.B ecn
+is turned off.
+
+.SS bytemode | nobytemode
+is used to scale drop probability proportional to packet size
+.B bytemode
+to turn on bytemode,
+.B nobytemode
+to turn off bytemode. By default,
+.B bytemode
+is turned off.
+
+.SH EXAMPLES
+ # tc qdisc add dev eth0 root pie
+ # tc -s qdisc show
+   qdisc pie 8034: dev eth0 root refcnt 2 limit 200p target 19000us tupdate 29000us alpha 2 beta 20
+   Sent 7443524 bytes 7204 pkt (dropped 900, overlimits 0 requeues 0)
+   backlog 38998b 37p requeues 0
+   prob 0.123384 delay 25000us avg_dq_rate 1464840
+   pkts_in 7241 overlimit 900 dropped 0 maxq 186 ecn_mark 0
+
+ # tc qdisc add dev eth0 root pie limit 100 target 20ms tupdate 30ms ecn
+ # tc -s qdisc show
+   qdisc pie 8036: dev eth0 root refcnt 2 limit 200p target 19000 tupdate 29000 alpha 2 beta 20 ecn
+   Sent 2491922 bytes 2507 pkt (dropped 214, overlimits 0 requeues 0)
+   backlog 33728b 32p requeues 0
+   prob 0.102262 delay 24000us avg_dq_rate 1464840
+   pkts_in 2468 overlimit 214 dropped 0 maxq 192 ecn_mark 71
+
+
+ # tc qdisc add dev eth0 root pie limit 100 target 50ms tupdate 30ms bytemode
+ # tc -s qdisc show
+   qdisc pie 8036: dev eth0 root refcnt 2 limit 200p target 19000 tupdate 29000 alpha 2 beta 20 ecn
+   Sent 2491922 bytes 2507 pkt (dropped 214, overlimits 0 requeues 0)
+   backlog 33728b 32p requeues 0
+   prob 0.102262 delay 24000us avg_dq_rate 1464840
+   pkts_in 2468 overlimit 214 dropped 0 maxq 192 ecn_mark 71
+
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-codel (8)
+.BR tc-red (8)
+
+.SH SOURCES
+ o IETF draft submission is at http://tools.ietf.org/html/draft-pan-tsvwg-pie-00
+ o IEEE  Conference on High Performance Switching and Routing 2013 : "PIE: A
+Lightweight Control Scheme to Address the Bufferbloat Problem"
+
+.SH AUTHORS
+PIE was implemented by Vijay Subramanian and Mythili Prabhu, also the authors of
+this man page. Please report bugs and corrections to the Linux networking
+development mailing list at <netdev@vger.kernel.org>.
diff --git a/man/man8/tc-prio.8 b/man/man8/tc-prio.8
index 1625fcc..99a4a26 100644
--- a/man/man8/tc-prio.8
+++ b/man/man8/tc-prio.8
@@ -11,7 +11,7 @@
 .B ] prio [ bands 
 bands
 .B ] [ priomap
-band,band,band... 
+band band band...
 .B ] [ estimator 
 interval timeconstant
 .B ]
@@ -106,7 +106,7 @@
 TOS     Bits  Means                    Linux Priority    Band
 ------------------------------------------------------------
 0x0     0     Normal Service           0 Best Effort     1
-0x2     1     Minimize Monetary Cost   1 Filler          2
+0x2     1     Minimize Monetary Cost   0 Best Effort     1
 0x4     2     Maximize Reliability     0 Best Effort     1
 0x6     3     mmc+mr                   0 Best Effort     1
 0x8     4     Maximize Throughput      2 Bulk            2
@@ -134,7 +134,7 @@
 The last column shows the result of the default priomap. On the command line,
 the default priomap looks like this:
 
-    1, 2, 2, 2, 1, 2, 0, 0 , 1, 1, 1, 1, 1, 1, 1, 1
+    1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
 
 This means that priority 4, for example, gets mapped to band number 1.
 The priomap also allows you to list higher priorities (> 7) which do not
diff --git a/man/man8/tc-red.8 b/man/man8/tc-red.8
index f410d15..d001c49 100644
--- a/man/man8/tc-red.8
+++ b/man/man8/tc-red.8
@@ -112,7 +112,7 @@
 As mentioned before, RED can either 'mark' or 'drop'. Explicit Congestion
 Notification allows RED to notify remote hosts that their rate exceeds the
 amount of bandwidth available. Non-ECN capable hosts can only be notified by
-dropping a packet.  If this parameter is specified, packets which indicate
+dropping a packet. If this parameter is specified, packets which indicate
 that their hosts honor ECN will only be marked and not dropped, unless the
 queue size hits
 .B limit
@@ -156,7 +156,7 @@
 .SH AUTHORS
 Alexey N. Kuznetsov, <kuznet@ms2.inr.ac.ru>,  Alexey Makarenko
 <makar@phoenix.kharkov.ua>, J Hadi Salim <hadi@nortelnetworks.com>,
-Eric Dumazet <eric.dumazet@gmail.com>.  
+Eric Dumazet <eric.dumazet@gmail.com>. 
 This manpage maintained by bert hubert <ahu@ds9a.nl>
 
 
diff --git a/man/man8/tc-sfb.8 b/man/man8/tc-sfb.8
index e7634d2..aad19e1 100644
--- a/man/man8/tc-sfb.8
+++ b/man/man8/tc-sfb.8
@@ -35,7 +35,7 @@
 The
 .B BLUE
 algorithm maintains a probability which is used to mark or drop packets
-that are to be queued.  If the queue overflows, the mark/drop probability
+that are to be queued. If the queue overflows, the mark/drop probability
 is increased. If the queue becomes empty, the probability is decreased. The
 .B Stochastic Fair Blue
 (SFB) algorithm is designed to protect TCP flows against non-responsive flows.
@@ -44,8 +44,8 @@
 Each flow is mapped into a bin of each level using a per-level hash value.
 
 Every bin maintains a marking probability, which gets increased or decreased
-based on bin occupancy.  If the number of packets exceeds the size of that
-bin, the marking probability is increased.  If the number drops to zero, it
+based on bin occupancy. If the number of packets exceeds the size of that
+bin, the marking probability is increased. If the number drops to zero, it
 is decreased.
 
 The marking probability is based on the minimum value of all bins a flow is
@@ -63,7 +63,7 @@
 being non-responsive.
 
 The probability of a responsive flow to be misidentified is dependent on
-the number of non-responsive flows, M.  It is (1 - (1 - (1 / 16.0)) ** M) **8,
+the number of non-responsive flows, M. It is (1 - (1 - (1 / 16.0)) ** M) **8,
 so for example with 10 non-responsive flows approximately 0.2% of responsive flows
 will be misidentified.
 
@@ -89,7 +89,7 @@
 4.4 of the SFB reference.
 While one set is used to manage the queue, a second set is warmed up:
 Whenever a flow is then determined to be non-responsive, the marking
-probabilities in the second set are updated.  When the rehashing
+probabilities in the second set are updated. When the rehashing
 happens, these bins will be used to manage the queue and all non-responsive
 flows can be rate-limited immediately.
 This value determines how much time has to pass before the 2nd set
@@ -100,19 +100,19 @@
 .TP
 limit
 Hard limit on the real (not average) total queue size in packets.
-Further packets are dropped.  Defaults to the transmit queue length of the
+Further packets are dropped. Defaults to the transmit queue length of the
 device the qdisc is attached to.
 .TP
 max
 Maximum length of a buckets queue, in packets, before packets start being
-dropped.  Should be sightly larger than
+dropped. Should be sightly larger than
 .B target
 , but should not be set to values exceeding 1.5 times that of
 .B target .
 Defaults to 25.
 .TP
 target
-The desired average bin length.  If the bin queue length reaches this value,
+The desired average bin length. If the bin queue length reaches this value,
 the marking probability is increased by
 .B increment.
 The default value depends on the
@@ -123,11 +123,11 @@
 .TP
 increment
 A value used to increase the marking probability when the queue appears
-to be over-used.  Must be between 0 and 1.0.  Defaults to 0.00050.
+to be over-used. Must be between 0 and 1.0. Defaults to 0.00050.
 .TP
 decrement
 Value used to decrease the marking probability when the queue is found
-to be empty.  Must be between 0 and 1.0.
+to be empty. Must be between 0 and 1.0.
 Defaults to 0.00005.
 .TP
 penalty_rate
@@ -153,7 +153,7 @@
 ratedrop
 The number of packets dropped because of rate-limiting.
 If this value is high, there are many non-reactive flows being
-sent through sfb.  In such cases, it might be better to
+sent through sfb. In such cases, it might be better to
 embed sfb within a classful qdisc to better control such
 flows using a different, shaping qdisc.
 .TP
@@ -163,7 +163,7 @@
 flows.
 .TP
 queuedrop
-The number of packets dropped due to reaching limit.  This should normally be 0.
+The number of packets dropped due to reaching limit. This should normally be 0.
 .TP
 marked
 The number of packets marked with ECN.
@@ -172,7 +172,7 @@
 The length of the current longest per-flow (virtual) queue.
 .TP
 maxprob
-The maximum per-flow drop probability.  1 means that some
+The maximum per-flow drop probability. 1 means that some
 flows have been detected as non-reactive.
 
 .SH NOTES
diff --git a/man/man8/tc-sfq.8 b/man/man8/tc-sfq.8
index 5a651ff..9afb5b2 100644
--- a/man/man8/tc-sfq.8
+++ b/man/man8/tc-sfq.8
@@ -150,7 +150,7 @@
 RED can either 'mark' or 'drop'. Explicit Congestion
 Notification allows RED to notify remote hosts that their rate exceeds the
 amount of bandwidth available. Non-ECN capable hosts can only be notified by
-dropping a packet.  If this parameter is specified, packets which indicate
+dropping a packet. If this parameter is specified, packets which indicate
 that their hosts honor ECN will only be marked and not dropped, unless the
 queue size hits
 .B depth
diff --git a/man/man8/tc-stab.8 b/man/man8/tc-stab.8
index 522ea00..02caa7d 100644
--- a/man/man8/tc-stab.8
+++ b/man/man8/tc-stab.8
@@ -5,15 +5,15 @@
 .
 .SH SYNOPSIS
 .nf
-tc qdisc add ... stab \\
+tc qdisc add ... stab
 .RS 4
-[ \fBmtu\fR BYTES ] [ \fBtsize\fR SLOTS ] \\
-[ \fBmpu\fR BYTES ] [ \fBoverhead\fR BYTES ] [ \fBlinklayer\fR TYPE ] ...
+[ \fBmtu\fR BYTES ] [ \fBtsize\fR SLOTS ]
+[ \fBmpu\fR BYTES ] [ \fBoverhead\fR BYTES ]
+[ \fBlinklayer\fR { adsl | atm | ethernet } ] ...
 .RE
-
-TYPE := adsl | atm | ethernet
 .fi
 
+.SH OPTIONS
 For the description of BYTES \- please refer to the \fBUNITS\fR
 section of \fBtc\fR(8).
 
@@ -31,27 +31,27 @@
 per\-packet size overhead (can be negative) used in computations
 .IP \fBlinklayer\fR
 .br
-required linklayer adaptation.
+required linklayer specification.
 .PP
 .
 .SH DESCRIPTION
 .
-Size tables allow manipulation of packet size, as seen by whole scheduler
+Size tables allow manipulation of packet sizes, as seen by the whole scheduler
 framework (of course, the actual packet size remains the same). Adjusted packet
 size is calculated only once \- when a qdisc enqueues the packet. Initial root
 enqueue initializes it to the real packet's size.
 
-Each qdisc can use different size table, but the adjusted size is stored in
-area shared by whole qdisc hierarchy attached to the interface. The effect is,
-that if you have such setup, the last qdisc with a stab in a chain "wins". For
+Each qdisc can use a different size table, but the adjusted size is stored in
+an area shared by whole qdisc hierarchy attached to the interface. The effect is
+that if you have such a setup, the last qdisc with a stab in a chain "wins". For
 example, consider HFSC with simple pfifo attached to one of its leaf classes.
 If that pfifo qdisc has stab defined, it will override lengths calculated
-during HFSC's enqueue, and in turn, whenever HFSC tries to dequeue a packet, it
-will use potentially invalid size in its calculations. Normal setups will
+during HFSC's enqueue; and in turn, whenever HFSC tries to dequeue a packet, it
+will use a potentially invalid size in its calculations. Normal setups will
 usually include stab defined only on root qdisc, but further overriding gives
 extra flexibility for less usual setups.
 
-Initial size table is calculated by \fBtc\fR tool using \fBmtu\fR and
+The initial size table is calculated by \fBtc\fR tool using \fBmtu\fR and
 \fBtsize\fR parameters. The algorithm sets each slot's size to the smallest
 power of 2 value, so the whole \fBmtu\fR is covered by the size table. Neither
 \fBtsize\fR, nor \fBmtu\fR have to be power of 2 value, so the size
@@ -65,12 +65,12 @@
 Stab calculation is also safe for an unusual case, when a size assigned to a
 slot would be larger than 2^16\-1 (you will lose the accuracy though).
 
-During kernel part of packet size adjustment, \fBoverhead\fR will be added to
-original size, and then slot will be calculated. If the size would cause
-overflow, more than 1 slot will be used to get the final size. It of course
+During the kernel part of packet size adjustment, \fBoverhead\fR will be added
+to original size, and then slot will be calculated. If the size would cause
+overflow, more than 1 slot will be used to get the final size. This of course
 will affect accuracy, but it's only a guard against unusual situations.
 
-Currently there're two methods of creating values stored in the size table \-
+Currently there are two methods of creating values stored in the size table \-
 ethernet and atm (adsl):
 
 .IP ethernet 4
@@ -78,7 +78,7 @@
 This is basically 1\-1 mapping, so following our example from above
 (disregarding \fBmpu\fR for a moment) slot 0 would have 8, slot 1 would have 16
 and so on, up to slot 127 with 2048. Note, that \fBmpu\fR\~>\~0 must be
-specified, and slots that would get less than specified by \fBmpu\fR, will get
+specified, and slots that would get less than specified by \fBmpu\fR will get
 \fBmpu\fR instead. If you don't specify \fBmpu\fR, the size table will not be
 created at all (it wouldn't make any difference), although any \fBoverhead\fR
 value will be respected during calculations.
@@ -88,7 +88,7 @@
 for payload. Also all the cells must be fully utilized, thus the last one is
 padded if/as necessary.
 
-When size table is calculated, adjusted size that fits properly into lowest
+When the size table is calculated, adjusted size that fits properly into lowest
 amount of cells is assigned to a slot. For example, a 100 byte long packet
 requires three 48\-byte payloads, so the final size would require 3 ATM cells
 \- 159 bytes.
@@ -118,22 +118,22 @@
 IPoA \- 8 (ATM \- 8)
 .RE
 .fi
-\p There're few important things regarding the above overheads:
+There are a few important things regarding the above overheads:
 .
 .IP \(bu 4
 IPoA in LLC case requires SNAP, instead of LLC\-NLPID (see rfc2684) \- this is
-the reason, why it actually takes more space than PPPoA.
+the reason why it actually takes more space than PPPoA.
 .IP \(bu
-In rare cases, FCS might be preserved on protocols that include ethernet frame
-(Bridged and PPPoE). In such situation, any ethernet specific padding
-guaranteeing 64 bytes long frame size has to be included as well (see rfc2684).
+In rare cases, FCS might be preserved on protocols that include Ethernet frames
+(Bridged and PPPoE). In such situation, any Ethernet specific padding
+guaranteeing 64 bytes long frame size has to be included as well (see RFC2684).
 In the other words, it also guarantees that any packet you send will take
 minimum 2 atm cells. You should set \fBmpu\fR accordingly for that.
 .IP \(bu
-When size table is consulted, and you're shaping traffic for the sake of
-another modem/router, ethernet header (without padding) will already be added
+When the size table is consulted, and you're shaping traffic for the sake of
+another modem/router, an Ethernet header (without padding) will already be added
 to initial packet's length. You should compensate for that by subtracting 14
-from the above overheads in such case. If you're shaping directly on the router
+from the above overheads in this case. If you're shaping directly on the router
 (for example, with speedtouch usb modem) using ppp daemon, you're using raw ip
 interface without underlying layer2, so nothing will be added.
 
@@ -141,10 +141,10 @@
 .
 .SH "ETHERNET CARDS CONSIDERATIONS"
 .
-It's often forgotten, that modern network cards (even cheap ones on desktop
+It's often forgotten that modern network cards (even cheap ones on desktop
 motherboards) and/or their drivers often support different offloading
-mechanisms. In context of traffic shaping, 'tso' and 'gso' might cause
-undesirable effects, due to massive tcp segments being considered during
+mechanisms. In the context of traffic shaping, 'tso' and 'gso' might cause
+undesirable effects, due to massive TCP segments being considered during
 traffic shaping (including stab calculations). For slow uplink interfaces,
 it's good to use \fBethtool\fR to turn off offloading features.
 .
diff --git a/man/man8/tc-tbf.8 b/man/man8/tc-tbf.8
index 3abb238..fc2c837 100644
--- a/man/man8/tc-tbf.8
+++ b/man/man8/tc-tbf.8
@@ -21,31 +21,27 @@
 burst is also known as buffer and maxburst. mtu is also known as minburst.
 .SH DESCRIPTION
 
-The Token Bucket Filter is a classless queueing discipline available for
+The Token Bucket Filter is a classful queueing discipline available for
 traffic control with the 
 .BR tc (8)
 command.
 
 TBF is a pure shaper and never schedules traffic. It is non-work-conserving and may throttle
 itself, although packets are available, to ensure that the configured rate is not exceeded. 
-On all platforms except for Alpha,
-it is able to shape up to 1mbit/s of normal traffic with ideal minimal burstiness, 
-sending out  data exactly at the configured rates. 
+It is able to shape up to 1mbit/s of normal traffic with ideal minimal burstiness, 
+sending out data exactly at the configured rates.
 
 Much higher rates are possible but at the cost of losing the minimal burstiness. In that
 case, data is on average dequeued at the configured rate but may be sent much faster at millisecond 
 timescales. Because of further queues living in network adaptors, this is often not a problem.
 
-Kernels with a higher 'HZ' can achieve higher rates with perfect burstiness. On Alpha, HZ is ten
-times higher, leading to a 10mbit/s limit to perfection. These calculations hold for packets of on 
-average 1000 bytes.
-
 .SH ALGORITHM
 As the name implies, traffic is filtered based on the expenditure of 
 .B tokens.
-Tokens roughly correspond to bytes, with the additional constraint that each packet consumes
-some tokens, no matter how small it is. This reflects the fact that even a zero-sized packet occupies
-the link for some time.
+Tokens roughly correspond to bytes, with the additional constraint
+that each packet consumes some tokens, no matter how small it is. This
+reflects the fact that even a zero-sized packet occupies the link for
+some time.
 
 On creation, the TBF is stocked with tokens which correspond to the amount of traffic that can be burst 
 in one go. Tokens arrive at a steady rate, until the bucket is full.
@@ -106,8 +102,9 @@
 
 .TP
 peakrate
-Maximum depletion rate of the bucket. Limited to 1mbit/s on Intel, 10mbit/s on Alpha. The peakrate does 
-not need to be set, it is only necessary if perfect millisecond timescale shaping is required.
+Maximum depletion rate of the bucket. The peakrate does not
+need to be set, it is only necessary if perfect millisecond timescale
+shaping is required.
 
 .TP
 mtu/minburst
@@ -124,9 +121,17 @@
 a 5kilobyte buffer, with a pre-bucket queue size limit calculated so the TBF causes
 at most 70ms of latency, with perfect peakrate behaviour, issue:
 .P
-# tc qdisc add dev eth0 root tbf rate 0.5mbit \\
+# tc qdisc add dev eth0 handle 10: root tbf rate 0.5mbit \\
   burst 5kb latency 70ms peakrate 1mbit       \\
   minburst 1540
+.P
+To attach an inner qdisc, for example sfq, issue:
+.P
+# tc qdisc add dev eth0 parent 10:1 handle 100: sfq
+.P
+Without inner qdisc TBF queue acts as bfifo. If the inner qdisc is changed
+the limit/latency is not effective anymore.
+.P
 
 .SH SEE ALSO
 .BR tc (8)
diff --git a/man/man8/tc.8 b/man/man8/tc.8
index fc8095e..434fe6c 100644
--- a/man/man8/tc.8
+++ b/man/man8/tc.8
@@ -2,29 +2,35 @@
 .SH NAME
 tc \- show / manipulate traffic control settings
 .SH SYNOPSIS
-.B tc qdisc [ add | change | replace | link ] dev 
-DEV 
-.B 
-[ parent 
-qdisc-id 
-.B | root ] 
-.B [ handle 
+.B tc
+.RI "[ " OPTIONS " ]"
+.B qdisc [ add | change | replace | link | delete ] dev
+DEV
+.B
+[ parent
+qdisc-id
+.B | root ]
+.B [ handle
 qdisc-id ] qdisc
 [ qdisc specific parameters ]
 .P
 
-.B tc class [ add | change | replace ] dev
+.B tc
+.RI "[ " OPTIONS " ]"
+.B class [ add | change | replace | delete ] dev
 DEV
-.B parent 
-qdisc-id 
-.B [ classid 
+.B parent
+qdisc-id
+.B [ classid
 class-id ] qdisc
 [ qdisc specific parameters ]
 .P
 
-.B tc filter [ add | change | replace ] dev
+.B tc
+.RI "[ " OPTIONS " ]"
+.B filter [ add | change | replace | delete ] dev
 DEV
-.B  [ parent
+.B [ parent
 qdisc-id
 .B | root ] protocol
 protocol
@@ -35,39 +41,52 @@
 flow-id
 
 .B tc
+.RI "[ " OPTIONS " ]"
 .RI "[ " FORMAT " ]"
-.B qdisc show [ dev 
-DEV 
-.B  ]
+.B qdisc show [ dev
+DEV
+.B ]
 .P
-.B tc 
+.B tc
+.RI "[ " OPTIONS " ]"
 .RI "[ " FORMAT " ]"
-.B class show dev 
-DEV 
+.B class show dev
+DEV
 .P
-.B tc filter show dev 
-DEV 
+.B tc
+.RI "[ " OPTIONS " ]"
+.B filter show dev
+DEV
 
-.ti -8
+.P
+.ti 8
+.IR OPTIONS " := {"
+\fB[ -force ] -b\fR[\fIatch\fR] \fB[ filename ] \fR|
+\fB[ \fB-n\fR[\fIetns\fR] name \fB] \fR|
+\fB[ \fB-nm \fR| \fB-nam\fR[\fIes\fR] \fB] \fR|
+\fB[ \fR{ \fB-cf \fR| \fB-c\fR[\fIonf\fR] \fR} \fB[ filename ] \fB] \fR}
+
+.ti 8
 .IR FORMAT " := {"
 \fB\-s\fR[\fItatistics\fR] |
 \fB\-d\fR[\fIetails\fR] |
 \fB\-r\fR[\fIaw\fR] |
 \fB\-p\fR[\fIretty\fR] |
-\fB\i\fR[\fIec\fR] }
+\fB\-i\fR[\fIec\fR] |
+\fB\-g\fR[\fIraph\fR] }
 
 .SH DESCRIPTION
 .B Tc
-is used to configure Traffic Control in the Linux kernel. Traffic Control consists 
+is used to configure Traffic Control in the Linux kernel. Traffic Control consists
 of the following:
 
-.TP 
+.TP
 SHAPING
-When traffic is shaped, its rate of transmission is under control. Shaping may 
-be more than lowering the available bandwidth - it is also used to smooth out 
+When traffic is shaped, its rate of transmission is under control. Shaping may
+be more than lowering the available bandwidth - it is also used to smooth out
 bursts in traffic for better network behaviour. Shaping occurs on egress.
 
-.TP 
+.TP
 SCHEDULING
 By scheduling the transmission of packets it is possible to improve interactivity
 for traffic that needs it while still guaranteeing bandwidth to bulk transfers. Reordering
@@ -75,39 +94,39 @@
 
 .TP
 POLICING
-Where shaping deals with transmission of traffic, policing pertains to traffic
+Whereas shaping deals with transmission of traffic, policing pertains to traffic
 arriving. Policing thus occurs on ingress.
 
 .TP
 DROPPING
-Traffic exceeding a set bandwidth may also be dropped forthwith, both on 
+Traffic exceeding a set bandwidth may also be dropped forthwith, both on
 ingress and on egress.
 
 .P
-Processing of traffic is controlled by three kinds of objects: qdiscs, 
-classes and filters. 
+Processing of traffic is controlled by three kinds of objects: qdiscs,
+classes and filters.
 
 .SH QDISCS
-.B qdisc 
-is short for 'queueing discipline' and it is elementary to 
-understanding traffic control. Whenever the kernel needs to send a 
-packet to an interface, it is 
+.B qdisc
+is short for 'queueing discipline' and it is elementary to
+understanding traffic control. Whenever the kernel needs to send a
+packet to an interface, it is
 .B enqueued
 to the qdisc configured for that interface. Immediately afterwards, the kernel
 tries to get as many packets as possible from the qdisc, for giving them
 to the network adaptor driver.
 
-A simple QDISC is the 'pfifo' one, which does no processing at all and is a pure 
+A simple QDISC is the 'pfifo' one, which does no processing at all and is a pure
 First In, First Out queue. It does however store traffic when the network interface
 can't handle it momentarily.
 
 .SH CLASSES
-Some qdiscs can contain classes, which contain further qdiscs - traffic may 
+Some qdiscs can contain classes, which contain further qdiscs - traffic may
 then be enqueued in any of the inner qdiscs, which are within the
 .B classes.
-When the kernel tries to dequeue a packet from such a 
+When the kernel tries to dequeue a packet from such a
 .B classful qdisc
-it can come from any of the classes. A qdisc may for example prioritize 
+it can come from any of the classes. A qdisc may for example prioritize
 certain kinds of traffic by trying to dequeue from certain classes
 before others.
 
@@ -117,45 +136,45 @@
 is used by a classful qdisc to determine in which class a packet will
 be enqueued. Whenever traffic arrives at a class with subclasses, it needs
 to be classified. Various methods may be employed to do so, one of these
-are the filters. All filters attached to the class are called, until one of 
-them returns with a verdict. If no verdict was made, other criteria may be 
+are the filters. All filters attached to the class are called, until one of
+them returns with a verdict. If no verdict was made, other criteria may be
 available. This differs per qdisc.
 
-It is important to notice that filters reside 
+It is important to notice that filters reside
 .B within
 qdiscs - they are not masters of what happens.
 
 .SH CLASSLESS QDISCS
 The classless qdiscs are:
-.TP 
+.TP
 [p|b]fifo
-Simplest usable qdisc, pure First In, First Out behaviour. Limited in 
+Simplest usable qdisc, pure First In, First Out behaviour. Limited in
 packets or in bytes.
 .TP
 pfifo_fast
 Standard qdisc for 'Advanced Router' enabled kernels. Consists of a three-band
-queue which honors Type of Service flags, as well as the priority that may be 
+queue which honors Type of Service flags, as well as the priority that may be
 assigned to a packet.
 .TP
 red
 Random Early Detection simulates physical congestion by randomly dropping
 packets when nearing configured bandwidth allocation. Well suited to very
 large bandwidth applications.
-.TP 
+.TP
 sfq
 Stochastic Fairness Queueing reorders queued traffic so each 'session'
 gets to send a packet in turn.
 .TP
 tbf
 The Token Bucket Filter is suited for slowing traffic down to a precisely
-configured rate. Scales well to large bandwidths. 
+configured rate. Scales well to large bandwidths.
 .SH CONFIGURING CLASSLESS QDISCS
-In the absence of classful qdiscs, classless qdiscs can only be attached at 
+In the absence of classful qdiscs, classless qdiscs can only be attached at
 the root of a device. Full syntax:
 .P
-.B tc qdisc add dev 
-DEV 
-.B root 
+.B tc qdisc add dev
+DEV
+.B root
 QDISC QDISC-PARAMETERS
 
 To remove, issue
@@ -164,7 +183,7 @@
 DEV
 .B root
 
-The  
+The
 .B pfifo_fast
 qdisc is the automatic default in the absence of a configured qdisc.
 
@@ -172,98 +191,115 @@
 The classful qdiscs are:
 .TP
 CBQ
-Class Based Queueing implements a rich linksharing hierarchy of classes. 
+Class Based Queueing implements a rich linksharing hierarchy of classes.
 It contains shaping elements as well as prioritizing capabilities. Shaping is
 performed using link idle time calculations based on average packet size and
 underlying link bandwidth. The latter may be ill-defined for some interfaces.
 .TP
 HTB
-The Hierarchy Token Bucket implements a rich linksharing hierarchy of 
+The Hierarchy Token Bucket implements a rich linksharing hierarchy of
 classes with an emphasis on conforming to existing practices. HTB facilitates
 guaranteeing bandwidth to classes, while also allowing specification of upper
 limits to inter-class sharing. It contains shaping elements, based on TBF and
-can prioritize classes.	
-.TP 
+can prioritize classes.
+.TP
 PRIO
-The PRIO qdisc is a non-shaping container for a configurable number of 
-classes which are dequeued in order. This allows for easy prioritization 
-of traffic, where lower classes are only able to send if higher ones have 
-no packets available. To facilitate configuration, Type Of Service bits are 
+The PRIO qdisc is a non-shaping container for a configurable number of
+classes which are dequeued in order. This allows for easy prioritization
+of traffic, where lower classes are only able to send if higher ones have
+no packets available. To facilitate configuration, Type Of Service bits are
 honored by default.
 .SH THEORY OF OPERATION
-Classes form a tree, where each class has a single parent. 
+Classes form a tree, where each class has a single parent.
 A class may have multiple children. Some qdiscs allow for runtime addition
-of classes (CBQ, HTB) while others (PRIO) are created with a static number of 
+of classes (CBQ, HTB) while others (PRIO) are created with a static number of
 children.
 
-Qdiscs which allow dynamic addition of classes can have zero or more 
-subclasses to which traffic may be enqueued. 
+Qdiscs which allow dynamic addition of classes can have zero or more
+subclasses to which traffic may be enqueued.
 
 Furthermore, each class contains a
 .B leaf qdisc
-which by default has 
-.B pfifo 
-behaviour though another qdisc can be attached in place. This qdisc may again 
-contain classes, but each class can have only one leaf qdisc. 
+which by default has
+.B pfifo
+behaviour, although another qdisc can be attached in place. This qdisc may again
+contain classes, but each class can have only one leaf qdisc.
 
-When a packet enters a classful qdisc it can be 
+When a packet enters a classful qdisc it can be
 .B classified
-to one of the classes within. Three criteria are available, although not all 
+to one of the classes within. Three criteria are available, although not all
 qdiscs will use all three:
-.TP 
+.TP
 tc filters
-If tc filters are attached to a class, they are consulted first 
-for relevant instructions. Filters can match on all fields of a packet header, 
-as well as on the firewall mark applied by ipchains or iptables. 
+If tc filters are attached to a class, they are consulted first
+for relevant instructions. Filters can match on all fields of a packet header,
+as well as on the firewall mark applied by ipchains or iptables.
 .TP
 Type of Service
 Some qdiscs have built in rules for classifying packets based on the TOS field.
 .TP
 skb->priority
-Userspace programs can encode a class-id in the 'skb->priority' field using 
+Userspace programs can encode a class-id in the 'skb->priority' field using
 the SO_PRIORITY option.
 .P
 Each node within the tree can have its own filters but higher level filters
 may also point directly to lower classes.
 
-If classification did not succeed, packets are enqueued to the leaf qdisc 
+If classification did not succeed, packets are enqueued to the leaf qdisc
 attached to that class. Check qdisc specific manpages for details, however.
 
 .SH NAMING
 All qdiscs, classes and filters have IDs, which can either be specified
-or be automatically assigned. 
+or be automatically assigned.
 
-IDs consist of a major number and a minor number, separated by a colon.
+IDs consist of a
+.BR major " number and a " minor
+number, separated by a colon -
+.BR major ":" minor "."
+Both
+.BR major " and " minor
+are hexadecimal numbers and are limited to 16 bits. There are two special
+values: root is signified by
+.BR major " and " minor
+of all ones, and unspecified is all zeros.
 
-.TP 
+.TP
 QDISCS
-A qdisc, which potentially can have children, 
-gets assigned a major number, called a 'handle', leaving the minor 
-number namespace available for classes. The handle is expressed as '10:'. 
-It is customary to explicitly assign a handle to qdiscs expected to have 
-children.
+A qdisc, which potentially can have children, gets assigned a
+.B major
+number, called a 'handle', leaving the
+.B minor
+number namespace available for classes. The handle is expressed as '10:'.
+It is customary to explicitly assign a handle to qdiscs expected to have children.
 
-.TP 
+.TP
 CLASSES
-Classes residing under a qdisc share their qdisc major number, but each have
-a separate minor number called a 'classid' that has no relation to their 
-parent classes, only to their parent qdisc. The same naming custom as for 
+Classes residing under a qdisc share their qdisc
+.B major
+number, but each have a separate
+.B minor
+number called a 'classid' that has no relation to their
+parent classes, only to their parent qdisc. The same naming custom as for
 qdiscs applies.
 
-.TP 
+.TP
 FILTERS
 Filters have a three part ID, which is only needed when using a hashed
 filter hierarchy.
-.SH UNITS
-All parameters accept a floating point number, possibly followed by a unit.
-.P
-Bandwidths or rates can be specified in:
-.TP 
-kbps
-Kilobytes per second
+
+.SH PARAMETERS
+The following parameters are widely used in TC. For other parameters,
+see the man pages for individual qdiscs.
+
 .TP
-mbps
-Megabytes per second
+RATES
+Bandwidths or rates.
+These parameters accept a floating point number, possibly followed by
+a unit (both SI and IEC units supported).
+.RS
+.TP
+bit or a bare number
+Bits per second
 .TP
 kbit
 Kilobits per second
@@ -271,27 +307,41 @@
 mbit
 Megabits per second
 .TP
-bps or a bare number
+gbit
+Gigabits per second
+.TP
+tbit
+Terabits per second
+.TP
+bps
 Bytes per second
+.TP
+kbps
+Kilobytes per second
+.TP
+mbps
+Megabytes per second
+.TP
+gbps
+Gigabytes per second
+.TP
+tbps
+Terabytes per second
+
 .P
-Amounts of data can be specified in:
-.TP
-kb or k
-Kilobytes
-.TP
-mb or m
-Megabytes
-.TP
-mbit
-Megabits
-.TP
-kbit
-Kilobits
-.TP
-b or a bare number
-Bytes.
+To specify in IEC units, replace the SI prefix (k-, m-, g-, t-) with
+IEC prefix (ki-, mi-, gi- and ti-) respectively.
+
 .P
-Lengths of time can be specified in:
+TC store rates as a 32-bit unsigned integer in bps internally,
+so we can specify a max rate of 4294967295 bps.
+.RE
+
+.TP
+TIMES
+Length of time. Can be specified as a floating point number
+followed by an optional unit:
+.RS
 .TP
 s, sec or secs
 Whole seconds
@@ -302,13 +352,58 @@
 us, usec, usecs or a bare number
 Microseconds.
 
+.P
+TC defined its own time unit (equal to microsecond) and stores
+time values as 32-bit unsigned integer, thus we can specify a max time value
+of 4294967295 usecs.
+.RE
+
+.TP
+SIZES
+Amounts of data. Can be specified as a floating point number
+followed by an optional unit:
+.RS
+.TP
+b or a bare number
+Bytes.
+.TP
+kbit
+Kilobits
+.TP
+kb or k
+Kilobytes
+.TP
+mbit
+Megabits
+.TP
+mb or m
+Megabytes
+.TP
+gbit
+Gigabits
+.TP
+gb or g
+Gigabytes
+
+.P
+TC stores sizes internally as 32-bit unsigned integer in byte,
+so we can specify a max size of 4294967295 bytes.
+.RE
+
+.TP
+VALUES
+Other values without a unit.
+These parameters are interpreted as decimal by default, but you can
+indicate TC to interpret them as octal and hexadecimal by adding a '0'
+or '0x' prefix respectively.
+
 .SH TC COMMANDS
 The following commands are available for qdiscs, classes and filter:
 .TP
 add
-Add a qdisc, class or filter to a node. For all entities, a 
+Add a qdisc, class or filter to a node. For all entities, a
 .B parent
-must be passed, either by passing its ID or by attaching directly to the root of a device. 
+must be passed, either by passing its ID or by attaching directly to the root of a device.
 When creating a qdisc or a filter, it can be named with the
 .B handle
 parameter. A class is named with the
@@ -316,16 +411,16 @@
 parameter.
 
 .TP
-remove
-A qdisc can be removed by specifying its handle, which may also be 'root'. All subclasses and their leaf qdiscs 
+delete
+A qdisc can be deleted by specifying its handle, which may also be 'root'. All subclasses and their leaf qdiscs
 are automatically deleted, as well as any filters attached to them.
 
 .TP
 change
 Some entities can be modified 'in place'. Shares the syntax of 'add', with the exception
-that the handle cannot be changed and neither can the parent. In other words, 
+that the handle cannot be changed and neither can the parent. In other words,
 .B
-change 
+change
 cannot move a node.
 
 .TP
@@ -335,9 +430,46 @@
 
 .TP
 link
-Only available for qdiscs and performs a replace where the node 
+Only available for qdiscs and performs a replace where the node
 must exist already.
 
+.SH OPTIONS
+
+.TP
+.BR "\-b", " \-b filename", " \-batch", " \-batch filename"
+read commands from provided file or standard input and invoke them.
+First failure will cause termination of tc.
+
+.TP
+.BR "\-force"
+don't terminate tc on errors in batch mode.
+If there were any errors during execution of the commands, the application return code will be non zero.
+
+.TP
+.BR "\-n" , " \-net" , " \-netns " <NETNS>
+switches
+.B tc
+to the specified network namespace
+.IR NETNS .
+Actually it just simplifies executing of:
+
+.B ip netns exec
+.IR NETNS
+.B tc
+.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+to
+
+.B tc
+.RI "-n[etns] " NETNS " [ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+.TP
+.BR "\-cf" , " \-conf " <FILENAME>
+specifies path to the config file. This option is used in conjuction with other options (e.g.
+.BR -nm ")."
+
 .SH FORMAT
 The show command has additional formatting options:
 
@@ -361,25 +493,88 @@
 .BR "\-iec"
 print rates in IEC units (ie. 1K = 1024).
 
+.TP
+.BR "\-g", " \-graph"
+shows classes as ASCII graph. Prints generic stats info under each class if
+.BR "-s"
+option was specified. Classes can be filtered only by
+.BR "dev"
+option.
+
+.TP
+.BR "\-nm" , " \-name"
+resolve class name from
+.B /etc/iproute2/tc_cls
+file or from file specified by
+.B -cf
+option. This file is just a mapping of
+.B classid
+to class name:
+
+.RS 10
+# Here is comment
+.RE
+.RS 10
+1:40   voip # Here is another comment
+.RE
+.RS 10
+1:50   web
+.RE
+.RS 10
+1:60   ftp
+.RE
+.RS 10
+1:2    home
+.RE
+
+.RS
+.B tc
+will not fail if
+.B -nm
+was specified without
+.B -cf
+option but
+.B /etc/iproute2/tc_cls
+file does not exist, which makes it possible to pass
+.B -nm
+option for creating
+.B tc
+alias.
+.RE
+
+.SH "EXAMPLES"
+.PP
+tc -g class show dev eth0
+.RS 4
+Shows classes as ASCII graph on eth0 interface.
+.RE
+.PP
+tc -g -s class show dev eth0
+.RS 4
+Shows classes as ASCII graph with stats info under each class.
 
 .SH HISTORY
 .B tc
 was written by Alexey N. Kuznetsov and added in Linux 2.2.
 .SH SEE ALSO
+.BR tc-bfifo (8),
 .BR tc-cbq (8),
 .BR tc-choke (8),
+.BR tc-codel (8),
 .BR tc-drr (8),
-.BR tc-htb (8),
-.BR tc-hfsc (8),
+.BR tc-ematch (8),
+.BR tc-fq_codel (8),
 .BR tc-hfsc (7),
+.BR tc-hfsc (8),
+.BR tc-htb (8),
+.BR tc-mqprio (8),
+.BR tc-pfifo (8),
+.BR tc-pfifo_fast (8),
+.BR tc-red (8),
 .BR tc-sfb (8),
 .BR tc-sfq (8),
-.BR tc-red (8),
-.BR tc-tbf (8),
-.BR tc-pfifo (8),
-.BR tc-bfifo (8),
-.BR tc-pfifo_fast (8),
 .BR tc-stab (8),
+.BR tc-tbf (8),
 .br
 .RB "User documentation at " http://lartc.org/ ", but please direct bugreports and patches to: " <netdev@vger.kernel.org>
 
diff --git a/misc/Makefile b/misc/Makefile
index a59ff87..b7ecba9 100644
--- a/misc/Makefile
+++ b/misc/Makefile
@@ -5,6 +5,15 @@
 
 include ../Config
 
+ifeq ($(HAVE_SELINUX),y)
+	LDLIBS += $(shell pkg-config --libs libselinux)
+	CFLAGS += $(shell pkg-config --cflags libselinux) -DHAVE_SELINUX
+endif
+
+ifeq ($(IP_CONFIG_SETNS),y)
+	CFLAGS += -DHAVE_SETNS
+endif
+
 all: $(TARGETS)
 
 ss: $(SSOBJ)
diff --git a/misc/arpd.c b/misc/arpd.c
index dd1de80..7919eb8 100644
--- a/misc/arpd.c
+++ b/misc/arpd.c
@@ -36,6 +36,7 @@
 
 #include "libnetlink.h"
 #include "utils.h"
+#include "rt_names.h"
 
 int resolve_hosts;
 
@@ -92,7 +93,7 @@
 int broadcast_burst = 3000;
 int poll_timeout = 30000;
 
-void usage(void)
+static void usage(void)
 {
 	fprintf(stderr,
 		"Usage: arpd [ -lkh? ] [ -a N ] [ -b dbase ] [ -B number ]"
@@ -100,7 +101,7 @@
 	exit(1);
 }
 
-int handle_if(int ifindex)
+static int handle_if(int ifindex)
 {
 	int i;
 
@@ -115,7 +116,7 @@
 
 int sysctl_adjusted;
 
-void do_sysctl_adjustments(void)
+static void do_sysctl_adjustments(void)
 {
 	int i;
 
@@ -148,7 +149,7 @@
 	sysctl_adjusted = 1;
 }
 
-void undo_sysctl_adjustments(void)
+static void undo_sysctl_adjustments(void)
 {
 	int i;
 
@@ -178,7 +179,7 @@
 }
 
 
-int send_probe(int ifindex, __u32 addr)
+static int send_probe(int ifindex, __u32 addr)
 {
 	struct ifreq ifr;
 	struct sockaddr_in dst;
@@ -238,7 +239,7 @@
 
 /* Be very tough on sending probes: 1 per second with burst of 3. */
 
-int queue_active_probe(int ifindex, __u32 addr)
+static int queue_active_probe(int ifindex, __u32 addr)
 {
 	static struct timeval prev;
 	static int buckets;
@@ -262,7 +263,7 @@
 	return -1;
 }
 
-int respond_to_kernel(int ifindex, __u32 addr, char *lla, int llalen)
+static int respond_to_kernel(int ifindex, __u32 addr, char *lla, int llalen)
 {
 	struct {
 		struct nlmsghdr 	n;
@@ -286,7 +287,7 @@
 	return rtnl_send(&rth, &req, req.n.nlmsg_len) <= 0;
 }
 
-void prepare_neg_entry(__u8 *ndata, __u32 stamp)
+static void prepare_neg_entry(__u8 *ndata, __u32 stamp)
 {
 	ndata[0] = 0xFF;
 	ndata[1] = 0;
@@ -297,7 +298,7 @@
 }
 
 
-int do_one_request(struct nlmsghdr *n)
+static int do_one_request(struct nlmsghdr *n)
 {
 	struct ndmsg *ndm = NLMSG_DATA(n);
 	int len = n->nlmsg_len;
@@ -426,12 +427,16 @@
 	return 0;
 }
 
-void load_initial_table(void)
+static void load_initial_table(void)
 {
-	rtnl_wilddump_request(&rth, AF_INET, RTM_GETNEIGH);
+	if (rtnl_wilddump_request(&rth, AF_INET, RTM_GETNEIGH) < 0) {
+		perror("dump request failed");
+		exit(1);
+	}
+
 }
 
-void get_kern_msg(void)
+static void get_kern_msg(void)
 {
 	int status;
 	struct nlmsghdr *h;
@@ -477,7 +482,7 @@
 }
 
 /* Receive gratuitous ARP messages and store them, that's all. */
-void get_arp_pkt(void)
+static void get_arp_pkt(void)
 {
 	unsigned char buf[1024];
 	struct sockaddr_ll sll;
@@ -532,7 +537,7 @@
 	dbase->put(dbase, &dbkey, &dbdat, 0);
 }
 
-void catch_signal(int sig, void (*handler)(int))
+static void catch_signal(int sig, void (*handler)(int))
 {
 	struct sigaction sa;
 
@@ -548,21 +553,21 @@
 sigjmp_buf env;
 volatile int in_poll;
 
-void sig_exit(int signo)
+static void sig_exit(int signo)
 {
 	do_exit = 1;
 	if (in_poll)
 		siglongjmp(env, 1);
 }
 
-void sig_sync(int signo)
+static void sig_sync(int signo)
 {
 	do_sync = 1;
 	if (in_poll)
 		siglongjmp(env, 1);
 }
 
-void sig_stats(int signo)
+static void sig_stats(int signo)
 {
 	do_sync = 1;
 	do_stats = 1;
@@ -570,7 +575,7 @@
 		siglongjmp(env, 1);
 }
 
-void send_stats(void)
+static void send_stats(void)
 {
 	syslog(LOG_INFO, "arp_rcv: n%lu c%lu app_rcv: tot %lu hits %lu bad %lu neg %lu sup %lu",
 	       stats.arp_new, stats.arp_change,
@@ -717,8 +722,7 @@
 				goto do_abort;
 			}
 
-			dbdat.data = hexstring_a2n(macbuf, b1, 6);
-			if (dbdat.data == NULL)
+			if (ll_addr_a2n((char *) b1, 6, macbuf) != 6)
 				goto do_abort;
 			dbdat.size = 6;
 
@@ -743,7 +747,7 @@
 					printf("%-8d %-15s %s\n",
 					       key->iface,
 					       inet_ntoa(*(struct in_addr*)&key->addr),
-					       hexstring_n2a(dbdat.data, 6, b1, 18));
+					       ll_addr_n2a(dbdat.data, 6, ARPHRD_ETHER, b1, 18));
 				} else {
 					printf("%-8d %-15s FAILED: %dsec ago\n",
 					       key->iface,
diff --git a/misc/ifstat.c b/misc/ifstat.c
index e7fbaa8..ab2cbc7 100644
--- a/misc/ifstat.c
+++ b/misc/ifstat.c
@@ -38,6 +38,7 @@
 int reset_history = 0;
 int ignore_history = 0;
 int no_output = 0;
+int json_output = 0;
 int no_update = 0;
 int scan_interval = 0;
 int time_constant = 0;
@@ -61,6 +62,32 @@
 	__u32			ival[MAXS];
 };
 
+static const char *stats[MAXS] = {
+	"rx_packets",
+	"tx_packets",
+	"rx_bytes",
+	"tx_bytes",
+	"rx_errors",
+	"tx_errors",
+	"rx_dropped",
+	"tx_dropped",
+	"multicast",
+	"collisions",
+	"rx_length_errors",
+	"rx_over_errors",
+	"rx_crc_errors",
+	"rx_frame_errors",
+	"rx_fifo_errors",
+	"rx_missed_errors",
+	"tx_aborted_errors",
+	"tx_carrier_errors",
+	"tx_fifo_errors",
+	"tx_heartbeat_errors",
+	"tx_window_errors",
+	"rx_compressed",
+	"tx_compressed"
+};
+
 struct ifstat_ent *kern_db;
 struct ifstat_ent *hist_db;
 
@@ -115,7 +142,7 @@
 	return 0;
 }
 
-void load_info(void)
+static void load_info(void)
 {
 	struct ifstat_ent *db, *n;
 	struct rtnl_handle rth;
@@ -146,7 +173,7 @@
 	}
 }
 
-void load_raw_table(FILE *fp)
+static void load_raw_table(FILE *fp)
 {
 	char buf[4096];
 	struct ifstat_ent *db = NULL;
@@ -209,11 +236,16 @@
 	}
 }
 
-void dump_raw_db(FILE *fp, int to_hist)
+static void dump_raw_db(FILE *fp, int to_hist)
 {
 	struct ifstat_ent *n, *h;
+	const char *eol = "\n";
+
 	h = hist_db;
-	fprintf(fp, "#%s\n", info_source);
+	if (json_output)
+		fprintf(fp, "{ \"%s\":{", info_source);
+	else
+		fprintf(fp, "#%s\n", info_source);
 
 	for (n=kern_db; n; n=n->next) {
 		int i;
@@ -232,10 +264,22 @@
 				}
 			}
 		}
-		fprintf(fp, "%d %s ", n->ifindex, n->name);
-		for (i=0; i<MAXS; i++)
-			fprintf(fp, "%llu %u ", vals[i], (unsigned)rates[i]);
-		fprintf(fp, "\n");
+
+		if (json_output) {
+			fprintf(fp, "%s   \"%s\":{",
+				eol, n->name);
+			eol = ",\n";
+			for (i=0; i<MAXS && stats[i]; i++)
+				fprintf(fp, " \"%s\":%llu",
+					stats[i], vals[i]);
+			fprintf(fp, "}");
+		} else {
+			fprintf(fp, "%d %s ", n->ifindex, n->name);
+			for (i=0; i<MAXS; i++)
+				fprintf(fp, "%llu %u ", vals[i],
+					(unsigned)rates[i]);
+			fprintf(fp, "\n");
+		}
 	}
 }
 
@@ -244,9 +288,11 @@
 static const unsigned long long mega = 1000000;
 static const unsigned long long kilo = 1000;
 
-void format_rate(FILE *fp, unsigned long long *vals, double *rates, int i)
+static void format_rate(FILE *fp, const unsigned long long *vals,
+			const double *rates, int i)
 {
 	char temp[64];
+
 	if (vals[i] > giga)
 		fprintf(fp, "%7lluM ", vals[i]/mega);
 	else if (vals[i] > mega)
@@ -264,7 +310,7 @@
 		fprintf(fp, "%-6u ", (unsigned)rates[i]);
 }
 
-void format_pair(FILE *fp, unsigned long long *vals, int i, int k)
+static void format_pair(FILE *fp, const unsigned long long *vals, int i, int k)
 {
 	char temp[64];
 	if (vals[i] > giga)
@@ -284,7 +330,7 @@
 		fprintf(fp, "%-6u ", (unsigned)vals[k]);
 }
 
-void print_head(FILE *fp)
+static void print_head(FILE *fp)
 {
 	fprintf(fp, "#%s\n", info_source);
 	fprintf(fp, "%-15s ", "Interface");
@@ -327,9 +373,27 @@
 	}
 }
 
-void print_one_if(FILE *fp, struct ifstat_ent *n, unsigned long long *vals)
+static void print_one_json(FILE *fp, const struct ifstat_ent *n,
+			   const unsigned long long *vals)
+{
+	int i, m;
+	const char *sep = " ";
+
+	m = show_errors ? 20 : 10;
+	fprintf(fp, "    \"%s\":{", n->name);
+	for (i=0; i < m && stats[i]; i++) {
+		fprintf(fp, "%s\"%s\":%llu",
+			sep, stats[i], vals[i]);
+		sep = ", ";
+	}
+	fprintf(fp, " }");
+}
+
+static void print_one_if(FILE *fp, const struct ifstat_ent *n,
+			 const unsigned long long *vals)
 {
 	int i;
+
 	fprintf(fp, "%-15s ", n->name);
 	for (i=0; i<4; i++)
 		format_rate(fp, vals, n->rate, i);
@@ -373,27 +437,42 @@
 	}
 }
 
-
-void dump_kern_db(FILE *fp)
+static void dump_kern_db(FILE *fp)
 {
 	struct ifstat_ent *n;
+	const char *eol = "\n";
 
-	print_head(fp);
+	if (json_output)
+		fprintf(fp, "{ \"%s\": {", info_source);
+	else
+		print_head(fp);
 
 	for (n=kern_db; n; n=n->next) {
 		if (!match(n->name))
 			continue;
-		print_one_if(fp, n, n->val);
+
+		if (json_output) {
+			fprintf(fp, "%s", eol);
+			eol = ",\n";
+			print_one_json(fp, n, n->val);
+		} else
+			print_one_if(fp, n, n->val);
 	}
+	if (json_output)
+		fprintf(fp, "\n} }\n");
 }
 
 
-void dump_incr_db(FILE *fp)
+static void dump_incr_db(FILE *fp)
 {
 	struct ifstat_ent *n, *h;
-	h = hist_db;
+	const char *eol = "\n";
 
-	print_head(fp);
+	h = hist_db;
+	if (json_output)
+		fprintf(fp, "{ \"%s\":{", info_source);
+	else
+		print_head(fp);
 
 	for (n=kern_db; n; n=n->next) {
 		int i;
@@ -412,18 +491,26 @@
 		}
 		if (!match(n->name))
 			continue;
-		print_one_if(fp, n, vals);
+
+		if (json_output) {
+			fprintf(fp, "%s", eol);
+			eol = ",\n";
+			print_one_json(fp, n, n->val);
+		} else
+			print_one_if(fp, n, vals);
 	}
+	if (json_output)
+		fprintf(fp, "\n} }\n");
 }
 
 
 static int children;
 
-void sigchild(int signo)
+static void sigchild(int signo)
 {
 }
 
-void update_db(int interval)
+static void update_db(int interval)
 {
 	struct ifstat_ent *n, *h;
 
@@ -482,7 +569,7 @@
 #define T_DIFF(a,b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
 
 
-void server_loop(int fd)
+static void server_loop(int fd)
 {
 	struct timeval snaptime = { 0 };
 	struct pollfd p;
@@ -534,7 +621,7 @@
 	}
 }
 
-int verify_forging(int fd)
+static int verify_forging(int fd)
 {
 	struct ucred cred;
 	socklen_t olen = sizeof(cred);
@@ -557,9 +644,10 @@
 "   -a, --ignore	ignore history\n"
 "   -d, --scan=SECS	sample every statistics every SECS\n"
 "   -e, --errors	show errors\n"
+"   -j, --json          format output in JSON\n"
 "   -n, --nooutput	do history only\n"
 "   -r, --reset		reset history\n"
-"   -s, --noupdate	don;t update history\n"
+"   -s, --noupdate	don\'t update history\n"
 "   -t, --interval=SECS	report average over the last SECS\n"
 "   -V, --version	output version information\n"
 "   -z, --zeros		show entries with zero activity\n");
@@ -573,6 +661,7 @@
 	{ "scan", 1, 0, 'd'},
 	{ "errors", 0, 0, 'e' },
 	{ "nooutput", 0, 0, 'n' },
+	{ "json", 0, 0, 'j' },
 	{ "reset", 0, 0, 'r' },
 	{ "noupdate", 0, 0, 's' },
 	{ "interval", 1, 0, 't' },
@@ -589,7 +678,7 @@
 	int ch;
 	int fd;
 
-	while ((ch = getopt_long(argc, argv, "hvVzrnasd:t:eK",
+	while ((ch = getopt_long(argc, argv, "hjvVzrnasd:t:e",
 			longopts, NULL)) != EOF) {
 		switch(ch) {
 		case 'z':
@@ -610,6 +699,9 @@
 		case 'e':
 			show_errors = 1;
 			break;
+		case 'j':
+			json_output = 1;
+			break;
 		case 'd':
 			scan_interval = atoi(optarg) * 1000;
 			if (scan_interval <= 0) {
@@ -757,11 +849,14 @@
 		else
 			dump_incr_db(stdout);
 	}
+
 	if (!no_update) {
 		ftruncate(fileno(hist_fp), 0);
 		rewind(hist_fp);
+
+		json_output = 0;
 		dump_raw_db(hist_fp, 1);
-		fflush(hist_fp);
+		fclose(hist_fp);
 	}
 	exit(0);
 }
diff --git a/misc/lnstat.c b/misc/lnstat.c
index bd19cc1..32b5cbe 100644
--- a/misc/lnstat.c
+++ b/misc/lnstat.c
@@ -20,7 +20,7 @@
 #define MAX_FIELDS		128
 
 /* Maximum number of header lines */
-#define HDR_LINES 		10
+#define HDR_LINES		10
 
 /* default field width if none specified */
 #define FIELD_WIDTH_DEFAULT	8
@@ -41,7 +41,8 @@
 static struct option opts[] = {
 	{ "version", 0, NULL, 'V' },
 	{ "count", 1, NULL, 'c' },
-	{ "dump", 1, NULL, 'd' },
+	{ "dump", 0, NULL, 'd' },
+	{ "json", 0, NULL, 'j' },
 	{ "file", 1, NULL, 'f' },
 	{ "help", 0, NULL, 'h' },
 	{ "interval", 1, NULL, 'i' },
@@ -63,6 +64,8 @@
 			"Print <count> number of intervals\n");
 	fprintf(stderr, "\t-d --dump\t\t"
 			"Dump list of available files/keys\n");
+	fprintf(stderr, "\t-j --json\t\t"
+			"Display in JSON format\n");
 	fprintf(stderr, "\t-f --file <file>\tStatistics file to use\n");
 	fprintf(stderr, "\t-h --help\t\tThis help message\n");
 	fprintf(stderr, "\t-i --interval <intv>\t"
@@ -94,16 +97,37 @@
 	int i;
 
 	for (i = 0; i < fp->num; i++) {
-		struct lnstat_field *lf = fp->params[i].lf;
-		char formatbuf[255];
+		const struct lnstat_field *lf = fp->params[i].lf;
 
-		snprintf(formatbuf, sizeof(formatbuf)-1, "%%%ulu|",
-			 fp->params[i].print.width);
-		fprintf(of, formatbuf, lf->result);
+		fprintf(of, "%*lu|", fp->params[i].print.width, lf->result);
 	}
 	fputc('\n', of);
 }
 
+static void print_json(FILE *of, const struct lnstat_file *lnstat_files,
+		       const struct field_params *fp)
+{
+	int i;
+	const char *sep;
+	const char *base = NULL;
+
+	fputs("{\n", of);
+	for (i = 0; i < fp->num; i++) {
+		const struct lnstat_field *lf = fp->params[i].lf;
+
+		if (!base || lf->file->basename != base) {
+			if (base) fputs("},\n", of);
+			base = lf->file->basename;
+			sep = "\n\t";
+			fprintf(of, "    \"%s\":{", base);
+		}
+		fprintf(of, "%s\"%s\":%lu", sep,
+			lf->name, lf->result);
+		sep = ",\n\t";
+	}
+	fputs("}\n}\n", of);
+}
+
 /* find lnstat_field according to user specification */
 static int map_field_params(struct lnstat_file *lnstat_files,
 			    struct field_params *fps, int interval)
@@ -121,7 +145,7 @@
 				if (!fps->params[j].print.width)
 					fps->params[j].print.width =
 							FIELD_WIDTH_DEFAULT;
-				
+
 				if (++j >= MAX_FIELDS - 1) {
 					fprintf(stderr,
 						"WARN: MAX_FIELDS (%d) reached,"
@@ -166,26 +190,25 @@
 
 	for (i = 0; i < HDR_LINES; i++) {
 		th.hdr[i] = malloc(HDR_LINE_LENGTH);
-		memset(th.hdr[i], 0, sizeof(th.hdr[i]));
+		memset(th.hdr[i], 0, HDR_LINE_LENGTH);
 	}
 
 	for (i = 0; i < fps->num; i++) {
 		char *cname, *fname = fps->params[i].lf->name;
-		char fmt[12];
 		unsigned int width = fps->params[i].print.width;
 
-		snprintf(fmt, sizeof(fmt)-1, "%%%u.%us|", width, width);
-
-		snprintf(th.hdr[0]+ofs, width+2, fmt,
+		snprintf(th.hdr[0]+ofs, width+2, "%*.*s|", width, width,
 			 fps->params[i].lf->file->basename);
 
 		cname = fname;
 		for (h = 1; h < HDR_LINES; h++) {
 			if (cname - fname >= strlen(fname))
-				snprintf(th.hdr[h]+ofs, width+2, fmt, "");
+				snprintf(th.hdr[h]+ofs, width+2,
+					 "%*.*s|", width, width, "");
 			else {
 				th.num_lines = h+1;
-				snprintf(th.hdr[h]+ofs, width+2, fmt, cname);
+				snprintf(th.hdr[h]+ofs, width+2,
+					 "%*.*s|", width, width, cname);
 			}
 			cname += width;
 		}
@@ -218,15 +241,16 @@
 {
 	struct lnstat_file *lnstat_files;
 	const char *basename;
-	int c;
+	int i, c;
 	int interval = DEFAULT_INTERVAL;
 	int hdr = 2;
 	enum {
 		MODE_DUMP,
+		MODE_JSON,
 		MODE_NORMAL,
 	} mode = MODE_NORMAL;
-
 	unsigned long count = 1;
+	struct table_hdr *header;
 	static struct field_params fp;
 	int num_req_files = 0;
 	char *req_files[LNSTAT_MAX_FILES];
@@ -248,70 +272,73 @@
 		num_req_files = 1;
 	}
 
-	while ((c = getopt_long(argc, argv,"Vc:df:h?i:k:s:w:",
+	while ((c = getopt_long(argc, argv,"Vc:djf:h?i:k:s:w:",
 				opts, NULL)) != -1) {
-		int i, len = 0;
+		int len = 0;
 		char *tmp, *tok;
 
 		switch (c) {
-			case 'c':
-				count = strtoul(optarg, NULL, 0);
+		case 'c':
+			count = strtoul(optarg, NULL, 0);
+			break;
+		case 'd':
+			mode = MODE_DUMP;
+			break;
+		case 'j':
+			mode = MODE_JSON;
+			break;
+		case 'f':
+			req_files[num_req_files++] = strdup(optarg);
+			break;
+		case '?':
+		case 'h':
+			usage(argv[0], 0);
+			break;
+		case 'i':
+			sscanf(optarg, "%u", &interval);
+			break;
+		case 'k':
+			tmp = strdup(optarg);
+			if (!tmp)
 				break;
-			case 'd':
-				mode = MODE_DUMP;
-				break;
-			case 'f':
-				req_files[num_req_files++] = strdup(optarg);
-				break;
-			case '?':
-			case 'h':
-				usage(argv[0], 0);
-				break;
-			case 'i':
-				sscanf(optarg, "%u", &interval);
-				break;
-			case 'k':
-				tmp = strdup(optarg);
-				if (!tmp)
+			for (tok = strtok(tmp, ",");
+			     tok;
+			     tok = strtok(NULL, ",")) {
+				if (fp.num >= MAX_FIELDS) {
+					fprintf(stderr,
+						"WARN: too many keys"
+						" requested: (%d max)\n",
+						MAX_FIELDS);
 					break;
-				for (tok = strtok(tmp, ",");
-				     tok;
-				     tok = strtok(NULL, ",")) {
-					if (fp.num >= MAX_FIELDS) {
-						fprintf(stderr, 
-							"WARN: too many keys"
-							" requested: (%d max)\n",
-							MAX_FIELDS);
-						break;
-					}
-					fp.params[fp.num++].name = tok;
 				}
+				fp.params[fp.num++].name = tok;
+			}
+			break;
+		case 's':
+			sscanf(optarg, "%u", &hdr);
+			break;
+		case 'w':
+			tmp = strdup(optarg);
+			if (!tmp)
 				break;
-			case 's':
-				sscanf(optarg, "%u", &hdr);
-				break;
-			case 'w':
-				tmp = strdup(optarg);
-				if (!tmp)
-					break;
-				i = 0;
-				for (tok = strtok(tmp, ",");
-				     tok;
-				     tok = strtok(NULL, ",")) {
-					len  = strtoul(tok, NULL, 0);
-					if (len > FIELD_WIDTH_MAX)
-						len = FIELD_WIDTH_MAX;
+			i = 0;
+			for (tok = strtok(tmp, ",");
+			     tok;
+			     tok = strtok(NULL, ",")) {
+				len  = strtoul(tok, NULL, 0);
+				if (len > FIELD_WIDTH_MAX)
+					len = FIELD_WIDTH_MAX;
+				fp.params[i].print.width = len;
+				i++;
+			}
+			if (i == 1) {
+				for (i = 0; i < MAX_FIELDS; i++)
 					fp.params[i].print.width = len;
-					i++;
-				}
-				if (i == 1) {
-					for (i = 0; i < MAX_FIELDS; i++)
-						fp.params[i].print.width = len;
-				}
-				break;
-			default:
-				usage(argv[0], 1);
-				break;
+			}
+			break;
+		default:
+			usage(argv[0], 1);
+			break;
 		}
 	}
 
@@ -319,13 +346,12 @@
 				       (const char **) req_files);
 
 	switch (mode) {
-		int i;
-		struct table_hdr *header;
 	case MODE_DUMP:
 		lnstat_dump(stderr, lnstat_files);
 		break;
-	case MODE_NORMAL:
 
+	case MODE_NORMAL:
+	case MODE_JSON:
 		if (!map_field_params(lnstat_files, &fp, interval))
 			exit(1);
 
@@ -334,16 +360,23 @@
 			exit(1);
 
 		if (interval < 1 )
-			interval=1;
+			interval = 1;
 
 		for (i = 0; i < count; i++) {
-			if  ((hdr > 1 && (! (i % 20))) || (hdr == 1 && i == 0))
-				print_hdr(stdout, header);
 			lnstat_update(lnstat_files);
-			print_line(stdout, lnstat_files, &fp);
+			if (mode == MODE_JSON)
+				print_json(stdout, lnstat_files, &fp);
+			else {
+				if  ((hdr > 1 &&
+				      (! (i % 20))) || (hdr == 1 && i == 0))
+					print_hdr(stdout, header);
+				print_line(stdout, lnstat_files, &fp);
+			}
 			fflush(stdout);
-			sleep(interval);
+			if (i < count - 1)
+				sleep(interval);
 		}
+		break;
 	}
 
 	return 1;
diff --git a/misc/lnstat.h b/misc/lnstat.h
index 06774ab..83dad97 100644
--- a/misc/lnstat.h
+++ b/misc/lnstat.h
@@ -2,6 +2,7 @@
 #define _LNSTAT_H
 
 #include <limits.h>
+#include <sys/select.h>
 
 #define LNSTAT_VERSION "0.02 041002"
 
diff --git a/misc/nstat.c b/misc/nstat.c
index 2f06ffd..c2cb056 100644
--- a/misc/nstat.c
+++ b/misc/nstat.c
@@ -26,6 +26,7 @@
 #include <sys/stat.h>
 #include <signal.h>
 #include <math.h>
+#include <getopt.h>
 
 #include <SNAPSHOT.h>
 
@@ -33,6 +34,7 @@
 int reset_history = 0;
 int ignore_history = 0;
 int no_output = 0;
+int json_output = 0;
 int no_update = 0;
 int scan_interval = 0;
 int time_constant = 0;
@@ -55,17 +57,17 @@
 	return open(p, O_RDONLY);
 }
 
-int net_netstat_open(void)
+static int net_netstat_open(void)
 {
 	return generic_proc_open("PROC_NET_NETSTAT", "net/netstat");
 }
 
-int net_snmp_open(void)
+static int net_snmp_open(void)
 {
 	return generic_proc_open("PROC_NET_SNMP", "net/snmp");
 }
 
-int net_snmp6_open(void)
+static int net_snmp6_open(void)
 {
 	return generic_proc_open("PROC_NET_SNMP6", "net/snmp6");
 }
@@ -75,20 +77,19 @@
 	struct nstat_ent *next;
 	char		 *id;
 	unsigned long long val;
-	unsigned long	   ival;
 	double		   rate;
 };
 
 struct nstat_ent *kern_db;
 struct nstat_ent *hist_db;
 
-char *useless_numbers[] = {
-"IpForwarding", "IpDefaultTTL",
-"TcpRtoAlgorithm", "TcpRtoMin", "TcpRtoMax",
-"TcpMaxConn", "TcpCurrEstab"
+static const char *useless_numbers[] = {
+	"IpForwarding", "IpDefaultTTL",
+	"TcpRtoAlgorithm", "TcpRtoMin", "TcpRtoMax",
+	"TcpMaxConn", "TcpCurrEstab"
 };
 
-int useless_number(char *id)
+static int useless_number(const char *id)
 {
 	int i;
 	for (i=0; i<sizeof(useless_numbers)/sizeof(*useless_numbers); i++)
@@ -97,7 +98,7 @@
 	return 0;
 }
 
-int match(char *id)
+static int match(const char *id)
 {
 	int i;
 
@@ -111,7 +112,7 @@
 	return 0;
 }
 
-void load_good_table(FILE *fp)
+static void load_good_table(FILE *fp)
 {
 	char buf[4096];
 	struct nstat_ent *db = NULL;
@@ -141,7 +142,6 @@
 		if ((n = malloc(sizeof(*n))) == NULL)
 			abort();
 		n->id = strdup(idbuf);
-		n->ival = (unsigned long)val;
 		n->val = val;
 		n->rate = rate;
 		n->next = db;
@@ -156,8 +156,17 @@
 	}
 }
 
+static int count_spaces(const char *line)
+{
+	int count = 0;
+	char c;
 
-void load_ugly_table(FILE *fp)
+	while ((c = *line++) != 0)
+		count += c == ' ' || c == '\n';
+	return count;
+}
+
+static void load_ugly_table(FILE *fp)
 {
 	char buf[4096];
 	struct nstat_ent *db = NULL;
@@ -167,10 +176,12 @@
 		char idbuf[sizeof(buf)];
 		int  off;
 		char *p;
+		int count1, count2, skip = 0;
 
 		p = strchr(buf, ':');
 		if (!p)
 			abort();
+		count1 = count_spaces(buf);
 		*p = 0;
 		idbuf[0] = 0;
 		strncat(idbuf, buf, sizeof(idbuf) - 1);
@@ -199,17 +210,19 @@
 		n = db;
 		if (fgets(buf, sizeof(buf), fp) == NULL)
 			abort();
+		count2 = count_spaces(buf);
+		if (count2 > count1)
+			skip = count2 - count1;
 		do {
 			p = strrchr(buf, ' ');
 			if (!p)
 				abort();
 			*p = 0;
-			if (sscanf(p+1, "%lu", &n->ival) != 1)
+			if (sscanf(p+1, "%llu", &n->val) != 1)
 				abort();
-			n->val = n->ival;
 			/* Trick to skip "dummy" trailing ICMP MIB in 2.4 */
-			if (strcmp(idbuf, "IcmpOutAddrMaskReps") == 0)
-				idbuf[5] = 0;
+			if (skip)
+				skip--;
 			else
 				n = n->next;
 		} while (p > buf + off + 2);
@@ -228,7 +241,7 @@
 	}
 }
 
-void load_snmp(void)
+static void load_snmp(void)
 {
 	FILE *fp = fdopen(net_snmp_open(), "r");
 	if (fp) {
@@ -237,7 +250,7 @@
 	}
 }
 
-void load_snmp6(void)
+static void load_snmp6(void)
 {
 	FILE *fp = fdopen(net_snmp6_open(), "r");
 	if (fp) {
@@ -246,7 +259,7 @@
 	}
 }
 
-void load_netstat(void)
+static void load_netstat(void)
 {
 	FILE *fp = fdopen(net_netstat_open(), "r");
 	if (fp) {
@@ -255,11 +268,18 @@
 	}
 }
 
-void dump_kern_db(FILE *fp, int to_hist)
+
+static void dump_kern_db(FILE *fp, int to_hist)
 {
 	struct nstat_ent *n, *h;
+	const char *eol = "\n";
+
 	h = hist_db;
-	fprintf(fp, "#%s\n", info_source);
+	if (json_output)
+		fprintf(fp, "{ \"%s\":{", info_source);
+	else
+		fprintf(fp, "#%s\n", info_source);
+
 	for (n=kern_db; n; n=n->next) {
 		unsigned long long val = n->val;
 		if (!dump_zeros && !val && !n->rate)
@@ -276,15 +296,29 @@
 				}
 			}
 		}
-		fprintf(fp, "%-32s%-16llu%6.1f\n", n->id, val, n->rate);
+
+		if (json_output) {
+			fprintf(fp, "%s    \"%s\":%llu",
+				eol, n->id, val);
+			eol = ",\n";
+		} else
+			fprintf(fp, "%-32s%-16llu%6.1f\n", n->id, val, n->rate);
 	}
+	if (json_output)
+		fprintf(fp, "\n} }\n");
 }
 
-void dump_incr_db(FILE *fp)
+static void dump_incr_db(FILE *fp)
 {
 	struct nstat_ent *n, *h;
+	const char *eol = "\n";
+
 	h = hist_db;
-	fprintf(fp, "#%s\n", info_source);
+	if (json_output)
+		fprintf(fp, "{ \"%s\":{", info_source);
+	else
+		fprintf(fp, "#%s\n", info_source);
+
 	for (n=kern_db; n; n=n->next) {
 		int ovfl = 0;
 		unsigned long long val = n->val;
@@ -304,18 +338,26 @@
 			continue;
 		if (!match(n->id))
 			continue;
-		fprintf(fp, "%-32s%-16llu%6.1f%s\n", n->id, val,
-			n->rate, ovfl?" (overflow)":"");
+
+		if (json_output) {
+			fprintf(fp, "%s    \"%s\":%llu",
+				eol, n->id, val);
+			eol = ",\n";
+		} else
+			fprintf(fp, "%-32s%-16llu%6.1f%s\n", n->id, val,
+				n->rate, ovfl?" (overflow)":"");
 	}
+	if (json_output)
+		fprintf(fp, "\n} }\n");
 }
 
 static int children;
 
-void sigchild(int signo)
+static void sigchild(int signo)
 {
 }
 
-void update_db(int interval)
+static void update_db(int interval)
 {
 	struct nstat_ent *n, *h;
 
@@ -334,10 +376,10 @@
 		for (h1 = h; h1; h1 = h1->next) {
 			if (strcmp(h1->id, n->id) == 0) {
 				double sample;
-				unsigned long incr = h1->ival - n->ival;
-				n->val += incr;
-				n->ival = h1->ival;
-				sample = (double)(incr*1000)/interval;
+				unsigned long long incr = h1->val - n->val;
+
+				n->val = h1->val;
+				sample = (double)incr * 1000.0 / interval;
 				if (interval >= scan_interval) {
 					n->rate += W*(sample-n->rate);
 				} else if (interval >= 1000) {
@@ -367,7 +409,7 @@
 #define T_DIFF(a,b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
 
 
-void server_loop(int fd)
+static void server_loop(int fd)
 {
 	struct timeval snaptime = { 0 };
 	struct pollfd p;
@@ -419,7 +461,7 @@
 	}
 }
 
-int verify_forging(int fd)
+static int verify_forging(int fd)
 {
 	struct ucred cred;
 	socklen_t olen = sizeof(cred);
@@ -437,11 +479,33 @@
 static void usage(void)
 {
 	fprintf(stderr,
-"Usage: nstat [ -h?vVzrnasd:t: ] [ PATTERN [ PATTERN ] ]\n"
-		);
+"Usage: nstat [OPTION] [ PATTERN [ PATTERN ] ]\n"
+"   -h, --help		this message\n"
+"   -a, --ignore	ignore history\n"
+"   -d, --scan=SECS	sample every statistics every SECS\n"
+"   -j, --json          format output in JSON\n"
+"   -n, --nooutput	do history only\n"
+"   -r, --reset		reset history\n"
+"   -s, --noupdate	don\'t update history\n"
+"   -t, --interval=SECS	report average over the last SECS\n"
+"   -V, --version	output version information\n"
+"   -z, --zeros		show entries with zero activity\n");
 	exit(-1);
 }
 
+static const struct option longopts[] = {
+	{ "help", 0, 0, 'h' },
+	{ "ignore",  0,  0, 'a' },
+	{ "scan", 1, 0, 'd'},
+	{ "nooutput", 0, 0, 'n' },
+	{ "json", 0, 0, 'j' },
+	{ "reset", 0, 0, 'r' },
+	{ "noupdate", 0, 0, 's' },
+	{ "interval", 1, 0, 't' },
+	{ "version", 0, 0, 'V' },
+	{ "zeros", 0, 0, 'z' },
+	{ 0 }
+};
 
 int main(int argc, char *argv[])
 {
@@ -451,7 +515,8 @@
 	int ch;
 	int fd;
 
-	while ((ch = getopt(argc, argv, "h?vVzrnasd:t:")) != EOF) {
+	while ((ch = getopt_long(argc, argv, "h?vVzrnasd:t:j",
+				 longopts, NULL)) != EOF) {
 		switch(ch) {
 		case 'z':
 			dump_zeros = 1;
@@ -478,6 +543,9 @@
 				exit(-1);
 			}
 			break;
+		case 'j':
+			json_output = 1;
+			break;
 		case 'v':
 		case 'V':
 			printf("nstat utility, iproute2-ss%s\n", SNAPSHOT);
@@ -614,8 +682,10 @@
 	if (!no_update) {
 		ftruncate(fileno(hist_fp), 0);
 		rewind(hist_fp);
+
+		json_output = 0;
 		dump_kern_db(hist_fp, 1);
-		fflush(hist_fp);
+		fclose(hist_fp);
 	}
 	exit(0);
 }
diff --git a/misc/rtacct.c b/misc/rtacct.c
index 49168bd..bb8c90f 100644
--- a/misc/rtacct.c
+++ b/misc/rtacct.c
@@ -55,12 +55,12 @@
 	return open(p, O_RDONLY);
 }
 
-int net_rtacct_open(void)
+static int net_rtacct_open(void)
 {
 	return generic_proc_open("PROC_NET_RTACCT", "net/rt_acct");
 }
 
-__u32 rmap[256/4];
+static __u32 rmap[256/4];
 
 struct rtacct_data
 {
@@ -71,12 +71,12 @@
 	char			signature[128];
 };
 
-struct rtacct_data kern_db_static;
+static struct rtacct_data kern_db_static;
 
-struct rtacct_data *kern_db = &kern_db_static;
-struct rtacct_data *hist_db;
+static struct rtacct_data *kern_db = &kern_db_static;
+static struct rtacct_data *hist_db;
 
-void nread(int fd, char *buf, int tot)
+static void nread(int fd, char *buf, int tot)
 {
 	int count = 0;
 
@@ -93,8 +93,7 @@
 	}
 }
 
-
-__u32 *read_kern_table(__u32 *tbl)
+static __u32 *read_kern_table(__u32 *tbl)
 {
 	static __u32 *tbl_ptr;
 	int fd;
@@ -130,7 +129,7 @@
 	return tbl;
 }
 
-void format_rate(FILE *fp, double rate)
+static void format_rate(FILE *fp, double rate)
 {
 	char temp[64];
 
@@ -144,7 +143,7 @@
 		fprintf(fp, " %-10u", (unsigned)rate);
 }
 
-void format_count(FILE *fp, unsigned long long val)
+static void format_count(FILE *fp, unsigned long long val)
 {
 	if (val > 1024*1024*1024)
 		fprintf(fp, " %10lluM", val/(1024*1024));
@@ -154,7 +153,7 @@
 		fprintf(fp, " %10llu", val);
 }
 
-void dump_abs_db(FILE *fp)
+static void dump_abs_db(FILE *fp)
 {
 	int realm;
 	char b1[16];
@@ -216,7 +215,7 @@
 }
 
 
-void dump_incr_db(FILE *fp)
+static void dump_incr_db(FILE *fp)
 {
 	int k, realm;
 	char b1[16];
@@ -293,13 +292,13 @@
 
 static int children;
 
-void sigchild(int signo)
+static void sigchild(int signo)
 {
 }
 
 /* Server side only: read kernel data, update tables, calculate rates. */
 
-void update_db(int interval)
+static void update_db(int interval)
 {
 	int i;
 	__u32 *ival;
@@ -331,7 +330,7 @@
 	}
 }
 
-void send_db(int fd)
+static void send_db(int fd)
 {
 	int tot = 0;
 
@@ -351,7 +350,7 @@
 #define T_DIFF(a,b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
 
 
-void pad_kern_table(struct rtacct_data *dat, __u32 *ival)
+static void pad_kern_table(struct rtacct_data *dat, __u32 *ival)
 {
 	int i;
 	memset(dat->rate, 0, sizeof(dat->rate));
@@ -361,7 +360,7 @@
 		dat->val[i] = ival[i];
 }
 
-void server_loop(int fd)
+static void server_loop(int fd)
 {
 	struct timeval snaptime = { 0 };
 	struct pollfd p;
@@ -410,7 +409,7 @@
 	}
 }
 
-int verify_forging(int fd)
+static int verify_forging(int fd)
 {
 	struct ucred cred;
 	socklen_t olen = sizeof(cred);
diff --git a/misc/ss.c b/misc/ss.c
index cf529ef..954a30b 100644
--- a/misc/ss.c
+++ b/misc/ss.c
@@ -22,21 +22,69 @@
 #include <errno.h>
 #include <netdb.h>
 #include <arpa/inet.h>
-#include <resolv.h>
 #include <dirent.h>
 #include <fnmatch.h>
 #include <getopt.h>
+#include <stdbool.h>
 
 #include "utils.h"
 #include "rt_names.h"
 #include "ll_map.h"
 #include "libnetlink.h"
+#include "namespace.h"
 #include "SNAPSHOT.h"
 
-#include <netinet/tcp.h>
+#include <linux/tcp.h>
 #include <linux/sock_diag.h>
 #include <linux/inet_diag.h>
 #include <linux/unix_diag.h>
+#include <linux/netdevice.h>	/* for MAX_ADDR_LEN */
+#include <linux/filter.h>
+#include <linux/packet_diag.h>
+#include <linux/netlink_diag.h>
+
+#define MAGIC_SEQ 123456
+
+#define DIAG_REQUEST(_req, _r)						    \
+	struct {							    \
+		struct nlmsghdr nlh;					    \
+		_r;							    \
+	} _req = {							    \
+		.nlh = {						    \
+			.nlmsg_type = SOCK_DIAG_BY_FAMILY,		    \
+			.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST,\
+			.nlmsg_seq = MAGIC_SEQ,				    \
+			.nlmsg_len = sizeof(_req),			    \
+		},							    \
+	}
+
+#if HAVE_SELINUX
+#include <selinux/selinux.h>
+#else
+/* Stubs for SELinux functions */
+static int is_selinux_enabled(void)
+{
+	return -1;
+}
+
+static int getpidcon(pid_t pid, char **context)
+{
+	*context = NULL;
+	return -1;
+}
+
+static int getfilecon(char *path, char **context)
+{
+	*context = NULL;
+	return -1;
+}
+
+static int security_get_initial_context(char *name,  char **context)
+{
+	*context = NULL;
+	return -1;
+}
+#endif
 
 int resolve_hosts = 0;
 int resolve_services = 1;
@@ -46,6 +94,11 @@
 int show_users = 0;
 int show_mem = 0;
 int show_tcpinfo = 0;
+int show_bpf = 0;
+int show_proc_ctx = 0;
+int show_sock_ctx = 0;
+/* If show_users & show_proc_ctx only do user_ent_hash_build() once */
+int user_ent_hash_build_init = 0;
 
 int netid_width;
 int state_width;
@@ -67,6 +120,7 @@
 	RAW_DB,
 	UNIX_DG_DB,
 	UNIX_ST_DB,
+	UNIX_SQ_DB,
 	PACKET_DG_DB,
 	PACKET_R_DB,
 	NETLINK_DB,
@@ -74,8 +128,9 @@
 };
 
 #define PACKET_DBM ((1<<PACKET_DG_DB)|(1<<PACKET_R_DB))
-#define UNIX_DBM ((1<<UNIX_DG_DB)|(1<<UNIX_ST_DB))
+#define UNIX_DBM ((1<<UNIX_DG_DB)|(1<<UNIX_ST_DB)|(1<<UNIX_SQ_DB))
 #define ALL_DB ((1<<MAX_DB)-1)
+#define INET_DBM ((1<<TCP_DB)|(1<<UDP_DB)|(1<<DCCP_DB)|(1<<RAW_DB))
 
 enum {
 	SS_UNKNOWN,
@@ -93,7 +148,8 @@
 	SS_MAX
 };
 
-#define SS_ALL ((1<<SS_MAX)-1)
+#define SS_ALL ((1 << SS_MAX) - 1)
+#define SS_CONN (SS_ALL & ~((1<<SS_LISTEN)|(1<<SS_CLOSE)|(1<<SS_TIME_WAIT)|(1<<SS_SYN_RECV)))
 
 #include "ssfilter.h"
 
@@ -105,13 +161,127 @@
 	struct ssfilter *f;
 };
 
-struct filter default_filter = {
-	.dbs	=  (1<<TCP_DB),
-	.states = SS_ALL & ~((1<<SS_LISTEN)|(1<<SS_CLOSE)|(1<<SS_TIME_WAIT)|(1<<SS_SYN_RECV)),
-	.families= (1<<AF_INET)|(1<<AF_INET6),
+static const struct filter default_dbs[MAX_DB] = {
+	[TCP_DB] = {
+		.states   = SS_CONN,
+		.families = (1 << AF_INET) | (1 << AF_INET6),
+	},
+	[DCCP_DB] = {
+		.states   = SS_CONN,
+		.families = (1 << AF_INET) | (1 << AF_INET6),
+	},
+	[UDP_DB] = {
+		.states   = (1 << SS_ESTABLISHED),
+		.families = (1 << AF_INET) | (1 << AF_INET6),
+	},
+	[RAW_DB] = {
+		.states   = (1 << SS_ESTABLISHED),
+		.families = (1 << AF_INET) | (1 << AF_INET6),
+	},
+	[UNIX_DG_DB] = {
+		.states   = (1 << SS_CLOSE),
+		.families = (1 << AF_UNIX),
+	},
+	[UNIX_ST_DB] = {
+		.states   = SS_CONN,
+		.families = (1 << AF_UNIX),
+	},
+	[UNIX_SQ_DB] = {
+		.states   = SS_CONN,
+		.families = (1 << AF_UNIX),
+	},
+	[PACKET_DG_DB] = {
+		.states   = (1 << SS_CLOSE),
+		.families = (1 << AF_PACKET),
+	},
+	[PACKET_R_DB] = {
+		.states   = (1 << SS_CLOSE),
+		.families = (1 << AF_PACKET),
+	},
+	[NETLINK_DB] = {
+		.states   = (1 << SS_CLOSE),
+		.families = (1 << AF_NETLINK),
+	},
 };
 
-struct filter current_filter;
+static const struct filter default_afs[AF_MAX] = {
+	[AF_INET] = {
+		.dbs    = INET_DBM,
+		.states = SS_CONN,
+	},
+	[AF_INET6] = {
+		.dbs    = INET_DBM,
+		.states = SS_CONN,
+	},
+	[AF_UNIX] = {
+		.dbs    = UNIX_DBM,
+		.states = SS_CONN,
+	},
+	[AF_PACKET] = {
+		.dbs    = PACKET_DBM,
+		.states = (1 << SS_CLOSE),
+	},
+	[AF_NETLINK] = {
+		.dbs    = (1 << NETLINK_DB),
+		.states = (1 << SS_CLOSE),
+	},
+};
+
+static int do_default = 1;
+static struct filter current_filter;
+
+static void filter_db_set(struct filter *f, int db)
+{
+	f->states   |= default_dbs[db].states;
+	f->families |= default_dbs[db].families;
+	f->dbs	    |= 1 << db;
+	do_default   = 0;
+}
+
+static void filter_af_set(struct filter *f, int af)
+{
+	f->dbs		   |= default_afs[af].dbs;
+	f->states	   |= default_afs[af].states;
+	f->families	   |= 1 << af;
+	do_default	    = 0;
+	preferred_family    = af;
+}
+
+static int filter_af_get(struct filter *f, int af)
+{
+	return f->families & (1 << af);
+}
+
+static void filter_default_dbs(struct filter *f)
+{
+	filter_db_set(f, UDP_DB);
+	filter_db_set(f, DCCP_DB);
+	filter_db_set(f, TCP_DB);
+	filter_db_set(f, RAW_DB);
+	filter_db_set(f, UNIX_ST_DB);
+	filter_db_set(f, UNIX_DG_DB);
+	filter_db_set(f, UNIX_SQ_DB);
+	filter_db_set(f, PACKET_R_DB);
+	filter_db_set(f, PACKET_DG_DB);
+	filter_db_set(f, NETLINK_DB);
+}
+
+static void filter_merge(struct filter *af, struct filter *dbf, int states)
+{
+	if (af->families)
+		af->families = (af->families | dbf->families) & af->families;
+	else
+		af->families = dbf->families;
+
+	if (dbf->dbs)
+		af->dbs = (af->dbs | dbf->dbs) & dbf->dbs;
+
+	if (dbf->states)
+		af->states = (af->states | dbf->states) & dbf->states;
+
+	if (states)
+		af->states = (af->states | states) & states;
+}
 
 static FILE *generic_proc_open(const char *env, const char *name)
 {
@@ -202,7 +372,9 @@
 	unsigned int	ino;
 	int		pid;
 	int		fd;
-	char		process[0];
+	char		*process;
+	char		*process_ctx;
+	char		*socket_ctx;
 };
 
 #define USER_ENT_HASH_SIZE	256
@@ -215,26 +387,50 @@
 	return val & (USER_ENT_HASH_SIZE - 1);
 }
 
-static void user_ent_add(unsigned int ino, const char *process, int pid, int fd)
+static void user_ent_add(unsigned int ino, char *process,
+					int pid, int fd,
+					char *proc_ctx,
+					char *sock_ctx)
 {
 	struct user_ent *p, **pp;
-	int str_len;
 
-	str_len = strlen(process) + 1;
-	p = malloc(sizeof(struct user_ent) + str_len);
-	if (!p)
+	p = malloc(sizeof(struct user_ent));
+	if (!p) {
+		fprintf(stderr, "ss: failed to malloc buffer\n");
 		abort();
+	}
 	p->next = NULL;
 	p->ino = ino;
 	p->pid = pid;
 	p->fd = fd;
-	strcpy(p->process, process);
+	p->process = strdup(process);
+	p->process_ctx = strdup(proc_ctx);
+	p->socket_ctx = strdup(sock_ctx);
 
 	pp = &user_ent_hash[user_ent_hashfn(ino)];
 	p->next = *pp;
 	*pp = p;
 }
 
+static void user_ent_destroy(void)
+{
+	struct user_ent *p, *p_next;
+	int cnt = 0;
+
+	while (cnt != USER_ENT_HASH_SIZE) {
+		p = user_ent_hash[cnt];
+		while (p) {
+			free(p->process);
+			free(p->process_ctx);
+			free(p->socket_ctx);
+			p_next = p->next;
+			free(p);
+			p = p_next;
+		}
+		cnt++;
+	}
+}
+
 static void user_ent_hash_build(void)
 {
 	const char *root = getenv("PROC_ROOT") ? : "/proc/";
@@ -242,6 +438,15 @@
 	char name[1024];
 	int nameoff;
 	DIR *dir;
+	char *pid_context;
+	char *sock_context;
+	const char *no_ctx = "unavailable";
+
+	/* If show_users & show_proc_ctx set only do this once */
+	if (user_ent_hash_build_init != 0)
+		return;
+
+	user_ent_hash_build_init = 1;
 
 	strcpy(name, root);
 	if (strlen(name) == 0 || name[strlen(name)-1] != '/')
@@ -256,6 +461,7 @@
 	while ((d = readdir(dir)) != NULL) {
 		struct dirent *d1;
 		char process[16];
+		char *p;
 		int pid, pos;
 		DIR *dir1;
 		char crap;
@@ -263,12 +469,16 @@
 		if (sscanf(d->d_name, "%d%c", &pid, &crap) != 1)
 			continue;
 
+		if (getpidcon(pid, &pid_context) != 0)
+			pid_context = strdup(no_ctx);
+
 		sprintf(name + nameoff, "%d/fd/", pid);
 		pos = strlen(name);
 		if ((dir1 = opendir(name)) == NULL)
 			continue;
 
 		process[0] = '\0';
+		p = process;
 
 		while ((d1 = readdir(dir1)) != NULL) {
 			const char *pattern = "socket:[";
@@ -276,6 +486,7 @@
 			char lnk[64];
 			int fd;
 			ssize_t link_len;
+			char tmp[1024];
 
 			if (sscanf(d1->d_name, "%d%c", &fd, &crap) != 1)
 				continue;
@@ -292,55 +503,107 @@
 
 			sscanf(lnk, "socket:[%u]", &ino);
 
-			if (process[0] == '\0') {
-				char tmp[1024];
+			snprintf(tmp, sizeof(tmp), "%s/%d/fd/%s",
+					root, pid, d1->d_name);
+
+			if (getfilecon(tmp, &sock_context) <= 0)
+				sock_context = strdup(no_ctx);
+
+			if (*p == '\0') {
 				FILE *fp;
 
-				snprintf(tmp, sizeof(tmp), "%s/%d/stat", root, pid);
+				snprintf(tmp, sizeof(tmp), "%s/%d/stat",
+					root, pid);
 				if ((fp = fopen(tmp, "r")) != NULL) {
-					fscanf(fp, "%*d (%[^)])", process);
+					fscanf(fp, "%*d (%[^)])", p);
 					fclose(fp);
 				}
 			}
-
-			user_ent_add(ino, process, pid, fd);
+			user_ent_add(ino, p, pid, fd,
+					pid_context, sock_context);
+			free(sock_context);
 		}
+		free(pid_context);
 		closedir(dir1);
 	}
 	closedir(dir);
 }
 
-int find_users(unsigned ino, char *buf, int buflen)
+enum entry_types {
+	USERS,
+	PROC_CTX,
+	PROC_SOCK_CTX
+};
+
+#define ENTRY_BUF_SIZE 512
+static int find_entry(unsigned ino, char **buf, int type)
 {
 	struct user_ent *p;
 	int cnt = 0;
 	char *ptr;
+	char **new_buf = buf;
+	int len, new_buf_len;
+	int buf_used = 0;
+	int buf_len = 0;
 
 	if (!ino)
 		return 0;
 
 	p = user_ent_hash[user_ent_hashfn(ino)];
-	ptr = buf;
+	ptr = *buf = NULL;
 	while (p) {
 		if (p->ino != ino)
 			goto next;
 
-		if (ptr - buf >= buflen - 1)
-			break;
+		while (1) {
+			ptr = *buf + buf_used;
+			switch (type) {
+			case USERS:
+				len = snprintf(ptr, buf_len - buf_used,
+					"(\"%s\",pid=%d,fd=%d),",
+					p->process, p->pid, p->fd);
+				break;
+			case PROC_CTX:
+				len = snprintf(ptr, buf_len - buf_used,
+					"(\"%s\",pid=%d,proc_ctx=%s,fd=%d),",
+					p->process, p->pid,
+					p->process_ctx, p->fd);
+				break;
+			case PROC_SOCK_CTX:
+				len = snprintf(ptr, buf_len - buf_used,
+					"(\"%s\",pid=%d,proc_ctx=%s,fd=%d,sock_ctx=%s),",
+					p->process, p->pid,
+					p->process_ctx, p->fd,
+					p->socket_ctx);
+				break;
+			default:
+				fprintf(stderr, "ss: invalid type: %d\n", type);
+				abort();
+			}
 
-		snprintf(ptr, buflen - (ptr - buf),
-			 "(\"%s\",%d,%d),",
-			 p->process, p->pid, p->fd);
-		ptr += strlen(ptr);
+			if (len < 0 || len >= buf_len - buf_used) {
+				new_buf_len = buf_len + ENTRY_BUF_SIZE;
+				*new_buf = realloc(*buf, new_buf_len);
+				if (!new_buf) {
+					fprintf(stderr, "ss: failed to malloc buffer\n");
+					abort();
+				}
+				**buf = **new_buf;
+				buf_len = new_buf_len;
+				continue;
+			} else {
+				buf_used += len;
+				break;
+			}
+		}
 		cnt++;
-
-	next:
+next:
 		p = p->next;
 	}
-
-	if (ptr != buf)
+	if (buf_used) {
+		ptr = *buf + buf_used;
 		ptr[-1] = '\0';
-
+	}
 	return cnt;
 }
 
@@ -355,7 +618,7 @@
 	int skbs;
 };
 
-struct slabstat slabstat;
+static struct slabstat slabstat;
 
 static const char *slabstat_ids[] =
 {
@@ -366,11 +629,15 @@
 	"skbuff_head_cache",
 };
 
-int get_slabstat(struct slabstat *s)
+static int get_slabstat(struct slabstat *s)
 {
 	char buf[256];
 	FILE *fp;
 	int cnt;
+	static int slabstat_valid;
+
+	if (slabstat_valid)
+		return 0;
 
 	memset(s, 0, sizeof(*s));
 
@@ -394,59 +661,159 @@
 			break;
 	}
 
+	slabstat_valid = 1;
+
 	fclose(fp);
 	return 0;
 }
 
+static inline void sock_addr_set_str(inet_prefix *prefix, char **ptr)
+{
+    memcpy(prefix->data, ptr, sizeof(char *));
+}
+
+static inline char *sock_addr_get_str(const inet_prefix *prefix)
+{
+    char *tmp ;
+    memcpy(&tmp, prefix->data, sizeof(char *));
+    return tmp;
+}
+
+static unsigned long long cookie_sk_get(const uint32_t *cookie)
+{
+	return (((unsigned long long)cookie[1] << 31) << 1) | cookie[0];
+}
+
 static const char *sstate_name[] = {
 	"UNKNOWN",
-	[TCP_ESTABLISHED] = "ESTAB",
-	[TCP_SYN_SENT] = "SYN-SENT",
-	[TCP_SYN_RECV] = "SYN-RECV",
-	[TCP_FIN_WAIT1] = "FIN-WAIT-1",
-	[TCP_FIN_WAIT2] = "FIN-WAIT-2",
-	[TCP_TIME_WAIT] = "TIME-WAIT",
-	[TCP_CLOSE] = "UNCONN",
-	[TCP_CLOSE_WAIT] = "CLOSE-WAIT",
-	[TCP_LAST_ACK] = "LAST-ACK",
-	[TCP_LISTEN] = 	"LISTEN",
-	[TCP_CLOSING] = "CLOSING",
+	[SS_ESTABLISHED] = "ESTAB",
+	[SS_SYN_SENT] = "SYN-SENT",
+	[SS_SYN_RECV] = "SYN-RECV",
+	[SS_FIN_WAIT1] = "FIN-WAIT-1",
+	[SS_FIN_WAIT2] = "FIN-WAIT-2",
+	[SS_TIME_WAIT] = "TIME-WAIT",
+	[SS_CLOSE] = "UNCONN",
+	[SS_CLOSE_WAIT] = "CLOSE-WAIT",
+	[SS_LAST_ACK] = "LAST-ACK",
+	[SS_LISTEN] = 	"LISTEN",
+	[SS_CLOSING] = "CLOSING",
 };
 
 static const char *sstate_namel[] = {
 	"UNKNOWN",
-	[TCP_ESTABLISHED] = "established",
-	[TCP_SYN_SENT] = "syn-sent",
-	[TCP_SYN_RECV] = "syn-recv",
-	[TCP_FIN_WAIT1] = "fin-wait-1",
-	[TCP_FIN_WAIT2] = "fin-wait-2",
-	[TCP_TIME_WAIT] = "time-wait",
-	[TCP_CLOSE] = "unconnected",
-	[TCP_CLOSE_WAIT] = "close-wait",
-	[TCP_LAST_ACK] = "last-ack",
-	[TCP_LISTEN] = 	"listening",
-	[TCP_CLOSING] = "closing",
+	[SS_ESTABLISHED] = "established",
+	[SS_SYN_SENT] = "syn-sent",
+	[SS_SYN_RECV] = "syn-recv",
+	[SS_FIN_WAIT1] = "fin-wait-1",
+	[SS_FIN_WAIT2] = "fin-wait-2",
+	[SS_TIME_WAIT] = "time-wait",
+	[SS_CLOSE] = "unconnected",
+	[SS_CLOSE_WAIT] = "close-wait",
+	[SS_LAST_ACK] = "last-ack",
+	[SS_LISTEN] = 	"listening",
+	[SS_CLOSING] = "closing",
+};
+
+struct sockstat
+{
+	struct sockstat	   *next;
+	unsigned int	    type;
+	uint16_t	    prot;
+	inet_prefix	    local;
+	inet_prefix	    remote;
+	int		    lport;
+	int		    rport;
+	int		    state;
+	int		    rq, wq;
+	unsigned	    ino;
+	unsigned	    uid;
+	int		    refcnt;
+	unsigned int	    iface;
+	unsigned long long  sk;
+};
+
+struct dctcpstat
+{
+	unsigned int	ce_state;
+	unsigned int	alpha;
+	unsigned int	ab_ecn;
+	unsigned int	ab_tot;
+	bool		enabled;
 };
 
 struct tcpstat
 {
-	inet_prefix	local;
-	inet_prefix	remote;
-	int		lport;
-	int		rport;
-	int		state;
-	int		rq, wq;
-	int		timer;
-	int		timeout;
-	int		retrs;
-	unsigned	ino;
-	int		probes;
-	unsigned	uid;
-	int		refcnt;
-	unsigned long long sk;
-	int		rto, ato, qack, cwnd, ssthresh;
+	struct sockstat	    ss;
+	int		    timer;
+	int		    timeout;
+	int		    probes;
+	char		    *cong_alg;
+	double		    rto, ato, rtt, rttvar;
+	int		    qack, cwnd, ssthresh, backoff;
+	double		    send_bps;
+	int		    snd_wscale;
+	int		    rcv_wscale;
+	int		    mss;
+	unsigned int	    lastsnd;
+	unsigned int	    lastrcv;
+	unsigned int	    lastack;
+	double		    pacing_rate;
+	double		    pacing_rate_max;
+	unsigned int	    unacked;
+	unsigned int	    retrans;
+	unsigned int	    retrans_total;
+	unsigned int	    lost;
+	unsigned int	    sacked;
+	unsigned int	    fackets;
+	unsigned int	    reordering;
+	double		    rcv_rtt;
+	int		    rcv_space;
+	bool		    has_ts_opt;
+	bool		    has_sack_opt;
+	bool		    has_ecn_opt;
+	bool		    has_ecnseen_opt;
+	bool		    has_fastopen_opt;
+	bool		    has_wscale_opt;
+	struct dctcpstat    *dctcp;
 };
 
+static void sock_state_print(struct sockstat *s, const char *sock_name)
+{
+	if (netid_width)
+		printf("%-*s ", netid_width, sock_name);
+	if (state_width)
+		printf("%-*s ", state_width, sstate_name[s->state]);
+
+	printf("%-6d %-6d ", s->rq, s->wq);
+}
+
+static void sock_details_print(struct sockstat *s)
+{
+	if (s->uid)
+		printf(" uid:%u", s->uid);
+
+	printf(" ino:%u", s->ino);
+	printf(" sk:%llx", s->sk);
+}
+
+static void sock_addr_print_width(int addr_len, const char *addr, char *delim,
+		int port_len, const char *port, const char *ifname)
+{
+	if (ifname) {
+		printf("%*s%%%s%s%-*s ", addr_len, addr, ifname, delim,
+				port_len, port);
+	}
+	else {
+		printf("%*s%s%-*s ", addr_len, addr, delim, port_len, port);
+	}
+}
+
+static void sock_addr_print(const char *addr, char *delim, const char *port,
+		const char *ifname)
+{
+	sock_addr_print_width(addr_width, addr, delim, serv_width, port, ifname);
+}
+
 static const char *tmr_name[] = {
 	"off",
 	"on",
@@ -456,7 +823,7 @@
 	"unknown"
 };
 
-const char *print_ms_timer(int timeout)
+static const char *print_ms_timer(int timeout)
 {
 	static char buf[64];
 	int secs, msecs, minutes;
@@ -483,12 +850,6 @@
 	return buf;
 }
 
-const char *print_hz_timer(int timeout)
-{
-	int hz = get_user_hz();
-	return print_ms_timer(((timeout*1000) + hz-1)/hz);
-}
-
 struct scache
 {
 	struct scache *next;
@@ -499,7 +860,7 @@
 
 struct scache *rlist;
 
-void init_service_resolver(void)
+static void init_service_resolver(void)
 {
 	char buf[128];
 	FILE *fp = popen("/usr/sbin/rpcinfo -p 2>/dev/null", "r");
@@ -556,7 +917,7 @@
 }
 
 
-const char *__resolve_service(int port)
+static const char *__resolve_service(int port)
 {
 	struct scache *c;
 
@@ -581,7 +942,7 @@
 }
 
 
-const char *resolve_service(int port)
+static const char *resolve_service(int port)
 {
 	static char buf[128];
 	static struct scache cache[256];
@@ -635,13 +996,12 @@
 	return buf;
 }
 
-void formatted_print(const inet_prefix *a, int port)
+static void inet_addr_print(const inet_prefix *a, int port, unsigned int ifindex)
 {
 	char buf[1024];
 	const char *ap = buf;
-	int est_len;
-
-	est_len = addr_width;
+	int est_len = addr_width;
+	const char *ifname = NULL;
 
 	if (a->family == AF_INET) {
 		if (a->data[0] == 0) {
@@ -658,7 +1018,14 @@
 		else
 			est_len = addr_width + ((est_len-addr_width+3)/4)*4;
 	}
-	printf("%*s:%-*s ", est_len, ap, serv_width, resolve_service(port));
+
+	if (ifindex) {
+		ifname   = ll_index_to_name(ifindex);
+		est_len -= strlen(ifname) + 1;  /* +1 for percent char */
+	}
+
+	sock_addr_print_width(est_len, ap, ":", serv_width, resolve_service(port),
+			ifname);
 }
 
 struct aafilter
@@ -668,7 +1035,8 @@
 	struct aafilter *next;
 };
 
-int inet2_addr_match(const inet_prefix *a, const inet_prefix *p, int plen)
+static int inet2_addr_match(const inet_prefix *a, const inet_prefix *p,
+			    int plen)
 {
 	if (!inet_addr_match(a, p, plen))
 		return 0;
@@ -687,11 +1055,11 @@
 	return 1;
 }
 
-int unix_match(const inet_prefix *a, const inet_prefix *p)
+static int unix_match(const inet_prefix *a, const inet_prefix *p)
 {
-	char *addr, *pattern;
-	memcpy(&addr, a->data, sizeof(addr));
-	memcpy(&pattern, p->data, sizeof(pattern));
+	char *addr = sock_addr_get_str(a);
+	char *pattern = sock_addr_get_str(p);
+
 	if (pattern == NULL)
 		return 1;
 	if (addr == NULL)
@@ -699,7 +1067,7 @@
 	return !fnmatch(pattern, addr, 0);
 }
 
-int run_ssfilter(struct ssfilter *f, struct tcpstat *s)
+static int run_ssfilter(struct ssfilter *f, struct sockstat *s)
 {
 	switch (f->type) {
 		case SSF_S_AUTO:
@@ -707,8 +1075,7 @@
                 static int low, high=65535;
 
 		if (s->local.family == AF_UNIX) {
-			char *p;
-			memcpy(&p, s->local.data, sizeof(p));
+			char *p = sock_addr_get_str(&s->local);
 			return p == NULL || (p[0] == '@' && strlen(p) == 6 &&
 					     strspn(p+1, "0123456789abcdef") == 5);
 		}
@@ -889,7 +1256,8 @@
 
 		case SSF_AND:
 	{
-		char *a1, *a2, *a, l1, l2;
+		char *a1, *a2, *a;
+		int l1, l2;
 		l1 = ssfilter_bytecompile(f->pred, &a1);
 		l2 = ssfilter_bytecompile(f->post, &a2);
 		if (!(a = malloc(l1+l2))) abort();
@@ -902,7 +1270,8 @@
 	}
 		case SSF_OR:
 	{
-		char *a1, *a2, *a, l1, l2;
+		char *a1, *a2, *a;
+		int l1, l2;
 		l1 = ssfilter_bytecompile(f->pred, &a1);
 		l2 = ssfilter_bytecompile(f->post, &a2);
 		if (!(a = malloc(l1+l2+4))) abort();
@@ -915,7 +1284,8 @@
 	}
 		case SSF_NOT:
 	{
-		char *a1, *a, l1;
+		char *a1, *a;
+		int l1;
 		l1 = ssfilter_bytecompile(f->pred, &a1);
 		if (!(a = malloc(l1+4))) abort();
 		memcpy(a, a1, l1);
@@ -988,7 +1358,9 @@
 static void xll_init(void)
 {
 	struct rtnl_handle rth;
-	rtnl_open(&rth, 0);
+	if (rtnl_open(&rth, 0) < 0)
+		exit(1);
+
 	ll_init_map(&rth);
 	rtnl_close(&rth);
 	xll_initted = 1;
@@ -1008,15 +1380,13 @@
 	return ll_name_to_index(dev);
 }
 
-void *parse_hostcond(char *addr)
+void *parse_hostcond(char *addr, bool is_port)
 {
 	char *port = NULL;
-	struct aafilter a;
+	struct aafilter a = { .port = -1 };
 	struct aafilter *res;
 	int fam = preferred_family;
-
-	memset(&a, 0, sizeof(a));
-	a.port = -1;
+	struct filter *f = &current_filter;
 
 	if (fam == AF_UNIX || strncmp(addr, "unix:", 5) == 0) {
 		char *p;
@@ -1025,7 +1395,8 @@
 			addr+=5;
 		p = strdup(addr);
 		a.addr.bitlen = 8*strlen(p);
-		memcpy(a.addr.data, &p, sizeof(p));
+		sock_addr_set_str(&a.addr, &p);
+		fam = AF_UNIX;
 		goto out;
 	}
 
@@ -1051,6 +1422,7 @@
 				return NULL;
 			a.addr.data[0] = ntohs(tmp);
 		}
+		fam = AF_PACKET;
 		goto out;
 	}
 
@@ -1073,26 +1445,21 @@
 		}
 		if (addr[0] && strcmp(addr, "*")) {
 			a.addr.bitlen = 32;
-			if (get_u32(a.addr.data, addr, 0)) {
-				if (strcmp(addr, "rtnl") == 0)
-					a.addr.data[0] = 0;
-				else if (strcmp(addr, "fw") == 0)
-					a.addr.data[0] = 3;
-				else if (strcmp(addr, "tcpdiag") == 0)
-					a.addr.data[0] = 4;
-				else
-					return NULL;
-			}
+			if (nl_proto_a2n(&a.addr.data[0], addr) == -1)
+				return NULL;
 		}
+		fam = AF_NETLINK;
 		goto out;
 	}
 
-	if (strncmp(addr, "inet:", 5) == 0) {
-		addr += 5;
+	if (fam == AF_INET || !strncmp(addr, "inet:", 5)) {
 		fam = AF_INET;
-	} else if (strncmp(addr, "inet6:", 6) == 0) {
-		addr += 6;
+		if (!strncmp(addr, "inet:", 5))
+			addr += 5;
+	} else if (fam == AF_INET6 || !strncmp(addr, "inet6:", 6)) {
 		fam = AF_INET6;
+		if (!strncmp(addr, "inet6:", 6))
+			addr += 6;
 	}
 
 	/* URL-like literal [] */
@@ -1106,10 +1473,14 @@
 	} else {
 		port = strrchr(strchr(addr, '/') ? : addr, ':');
 	}
+
+	if (is_port)
+		port = addr;
+
 	if (port && *port) {
-		if (*port != ':')
-			return NULL;
-		*port++ = 0;
+		if (*port == ':')
+			*port++ = 0;
+
 		if (*port && *port != '*') {
 			if (get_integer(&a.port, port, 0)) {
 				struct servent *se1 = NULL;
@@ -1150,7 +1521,7 @@
 			}
 		}
 	}
-	if (addr && *addr && *addr != '*') {
+	if (!is_port && addr && *addr && *addr != '*') {
 		if (get_prefix_1(&a.addr, addr, fam)) {
 			if (get_dns_host(&a, addr, fam)) {
 				fprintf(stderr, "Error: an inet prefix is expected rather than \"%s\".\n", addr);
@@ -1159,133 +1530,274 @@
 		}
 	}
 
-	out:
+out:
+	if (fam != AF_UNSPEC) {
+		f->families = 0;
+		filter_af_set(f, fam);
+		filter_merge(f, f, 0);
+	}
+
 	res = malloc(sizeof(*res));
 	if (res)
 		memcpy(res, &a, sizeof(a));
 	return res;
 }
 
-static int tcp_show_line(char *line, const struct filter *f, int family)
+static char *proto_name(int protocol)
 {
-	struct tcpstat s;
-	char *loc, *rem, *data;
-	char opt[256];
-	int n;
+	switch (protocol) {
+	case IPPROTO_UDP:
+		return "udp";
+	case IPPROTO_TCP:
+		return "tcp";
+	case IPPROTO_DCCP:
+		return "dccp";
+	}
+
+	return "???";
+}
+
+static void inet_stats_print(struct sockstat *s, int protocol)
+{
+	char *buf = NULL;
+
+	sock_state_print(s, proto_name(protocol));
+
+	inet_addr_print(&s->local, s->lport, s->iface);
+	inet_addr_print(&s->remote, s->rport, 0);
+
+	if (show_proc_ctx || show_sock_ctx) {
+		if (find_entry(s->ino, &buf,
+				(show_proc_ctx & show_sock_ctx) ?
+				PROC_SOCK_CTX : PROC_CTX) > 0) {
+			printf(" users:(%s)", buf);
+			free(buf);
+		}
+	} else if (show_users) {
+		if (find_entry(s->ino, &buf, USERS) > 0) {
+			printf(" users:(%s)", buf);
+			free(buf);
+		}
+	}
+}
+
+static int proc_parse_inet_addr(char *loc, char *rem, int family, struct
+		sockstat *s)
+{
+	s->local.family = s->remote.family = family;
+	if (family == AF_INET) {
+		sscanf(loc, "%x:%x", s->local.data, (unsigned*)&s->lport);
+		sscanf(rem, "%x:%x", s->remote.data, (unsigned*)&s->rport);
+		s->local.bytelen = s->remote.bytelen = 4;
+		return 0;
+	} else {
+		sscanf(loc, "%08x%08x%08x%08x:%x",
+		       s->local.data,
+		       s->local.data + 1,
+		       s->local.data + 2,
+		       s->local.data + 3,
+		       &s->lport);
+		sscanf(rem, "%08x%08x%08x%08x:%x",
+		       s->remote.data,
+		       s->remote.data + 1,
+		       s->remote.data + 2,
+		       s->remote.data + 3,
+		       &s->rport);
+		s->local.bytelen = s->remote.bytelen = 16;
+		return 0;
+	}
+	return -1;
+}
+
+static int proc_inet_split_line(char *line, char **loc, char **rem, char **data)
+{
 	char *p;
 
 	if ((p = strchr(line, ':')) == NULL)
 		return -1;
-	loc = p+2;
 
-	if ((p = strchr(loc, ':')) == NULL)
+	*loc = p+2;
+	if ((p = strchr(*loc, ':')) == NULL)
 		return -1;
-	p[5] = 0;
-	rem = p+6;
 
-	if ((p = strchr(rem, ':')) == NULL)
+	p[5] = 0;
+	*rem = p+6;
+	if ((p = strchr(*rem, ':')) == NULL)
 		return -1;
+
 	p[5] = 0;
-	data = p+6;
+	*data = p+6;
+	return 0;
+}
 
-	do {
-		int state = (data[1] >= 'A') ? (data[1] - 'A' + 10) : (data[1] - '0');
+static char *sprint_bw(char *buf, double bw)
+{
+	if (bw > 1000000.)
+		sprintf(buf,"%.1fM", bw / 1000000.);
+	else if (bw > 1000.)
+		sprintf(buf,"%.1fK", bw / 1000.);
+	else
+		sprintf(buf, "%g", bw);
 
-		if (!(f->states & (1<<state)))
-			return 0;
-	} while (0);
+	return buf;
+}
 
-	s.local.family = s.remote.family = family;
-	if (family == AF_INET) {
-		sscanf(loc, "%x:%x", s.local.data, (unsigned*)&s.lport);
-		sscanf(rem, "%x:%x", s.remote.data, (unsigned*)&s.rport);
-		s.local.bytelen = s.remote.bytelen = 4;
-	} else {
-		sscanf(loc, "%08x%08x%08x%08x:%x",
-		       s.local.data,
-		       s.local.data+1,
-		       s.local.data+2,
-		       s.local.data+3,
-		       &s.lport);
-		sscanf(rem, "%08x%08x%08x%08x:%x",
-		       s.remote.data,
-		       s.remote.data+1,
-		       s.remote.data+2,
-		       s.remote.data+3,
-		       &s.rport);
-		s.local.bytelen = s.remote.bytelen = 16;
+static void tcp_stats_print(struct tcpstat *s)
+{
+	char b1[64];
+
+	if (s->has_ts_opt)
+		printf(" ts");
+	if (s->has_sack_opt)
+		printf(" sack");
+	if (s->has_ecn_opt)
+		printf(" ecn");
+	if (s->has_ecnseen_opt)
+		printf(" ecnseen");
+	if (s->has_fastopen_opt)
+		printf(" fastopen");
+	if (s->cong_alg)
+		printf(" %s", s->cong_alg);
+	if (s->has_wscale_opt)
+		printf(" wscale:%d,%d", s->snd_wscale, s->rcv_wscale);
+	if (s->rto)
+		printf(" rto:%g", s->rto);
+	if (s->backoff)
+		printf(" backoff:%u", s->backoff);
+	if (s->rtt)
+		printf(" rtt:%g/%g", s->rtt, s->rttvar);
+	if (s->ato)
+		printf(" ato:%g", s->ato);
+
+	if (s->qack)
+		printf(" qack:%d", s->qack);
+	if (s->qack & 1)
+		printf(" bidir");
+
+	if (s->mss)
+		printf(" mss:%d", s->mss);
+	if (s->cwnd && s->cwnd != 2)
+		printf(" cwnd:%d", s->cwnd);
+	if (s->ssthresh)
+		printf(" ssthresh:%d", s->ssthresh);
+
+	if (s->dctcp && s->dctcp->enabled) {
+		struct dctcpstat *dctcp = s->dctcp;
+
+		printf("dctcp:(ce_state:%u,alpha:%u,ab_ecn:%u,ab_tot:%u)",
+				dctcp->ce_state, dctcp->alpha, dctcp->ab_ecn,
+				dctcp->ab_tot);
+	} else if (s->dctcp) {
+		printf("dctcp:fallback_mode");
 	}
 
-	if (f->f && run_ssfilter(f->f, &s) == 0)
+	if (s->send_bps)
+		printf(" send %sbps", sprint_bw(b1, s->send_bps));
+	if (s->lastsnd)
+		printf(" lastsnd:%u", s->lastsnd);
+	if (s->lastrcv)
+		printf(" lastrcv:%u", s->lastrcv);
+	if (s->lastack)
+		printf(" lastack:%u", s->lastack);
+
+	if (s->pacing_rate) {
+		printf(" pacing_rate %sbps", sprint_bw(b1, s->pacing_rate));
+		if (s->pacing_rate_max)
+				printf("/%sbps", sprint_bw(b1,
+							s->pacing_rate_max));
+	}
+
+	if (s->unacked)
+		printf(" unacked:%u", s->unacked);
+	if (s->retrans || s->retrans_total)
+		printf(" retrans:%u/%u", s->retrans, s->retrans_total);
+	if (s->lost)
+		printf(" lost:%u", s->lost);
+	if (s->sacked && s->ss.state != SS_LISTEN)
+		printf(" sacked:%u", s->sacked);
+	if (s->fackets)
+		printf(" fackets:%u", s->fackets);
+	if (s->reordering != 3)
+		printf(" reordering:%d", s->reordering);
+	if (s->rcv_rtt)
+		printf(" rcv_rtt:%g", s->rcv_rtt);
+	if (s->rcv_space)
+		printf(" rcv_space:%d", s->rcv_space);
+}
+
+static void tcp_timer_print(struct tcpstat *s)
+{
+	if (s->timer) {
+		if (s->timer > 4)
+			s->timer = 5;
+		printf(" timer:(%s,%s,%d)",
+				tmr_name[s->timer],
+				print_ms_timer(s->timeout),
+				s->retrans);
+	}
+}
+
+static int tcp_show_line(char *line, const struct filter *f, int family)
+{
+	int rto = 0, ato = 0;
+	struct tcpstat s = {};
+	char *loc, *rem, *data;
+	char opt[256];
+	int n;
+	int hz = get_user_hz();
+
+	if (proc_inet_split_line(line, &loc, &rem, &data))
+		return -1;
+
+	int state = (data[1] >= 'A') ? (data[1] - 'A' + 10) : (data[1] - '0');
+	if (!(f->states & (1 << state)))
+		return 0;
+
+	proc_parse_inet_addr(loc, rem, family, &s.ss);
+
+	if (f->f && run_ssfilter(f->f, &s.ss) == 0)
 		return 0;
 
 	opt[0] = 0;
 	n = sscanf(data, "%x %x:%x %x:%x %x %d %d %u %d %llx %d %d %d %d %d %[^\n]\n",
-		   &s.state, &s.wq, &s.rq,
-		   &s.timer, &s.timeout, &s.retrs, &s.uid, &s.probes, &s.ino,
-		   &s.refcnt, &s.sk, &s.rto, &s.ato, &s.qack,
-		   &s.cwnd, &s.ssthresh, opt);
+		   &s.ss.state, &s.ss.wq, &s.ss.rq,
+		   &s.timer, &s.timeout, &s.retrans, &s.ss.uid, &s.probes,
+		   &s.ss.ino, &s.ss.refcnt, &s.ss.sk, &rto, &ato, &s.qack, &s.cwnd,
+		   &s.ssthresh, opt);
 
 	if (n < 17)
 		opt[0] = 0;
 
 	if (n < 12) {
-		s.rto = 0;
+		rto = 0;
 		s.cwnd = 2;
 		s.ssthresh = -1;
-		s.ato = s.qack = 0;
+		ato = s.qack = 0;
 	}
 
-	if (netid_width)
-		printf("%-*s ", netid_width, "tcp");
-	if (state_width)
-		printf("%-*s ", state_width, sstate_name[s.state]);
+	s.retrans   = s.timer != 1 ? s.probes : s.retrans;
+	s.timeout   = (s.timeout * 1000 + hz - 1) / hz;
+	s.ato	    = (double)ato / hz;
+	s.qack	   /= 2;
+	s.rto	    = (double)rto;
+	s.ssthresh  = s.ssthresh == -1 ? 0 : s.ssthresh;
+	s.rto	    = s.rto != 3 * hz  ? s.rto / hz : 0;
 
-	printf("%-6d %-6d ", s.rq, s.wq);
+	inet_stats_print(&s.ss, IPPROTO_TCP);
 
-	formatted_print(&s.local, s.lport);
-	formatted_print(&s.remote, s.rport);
+	if (show_options)
+		tcp_timer_print(&s);
 
-	if (show_options) {
-		if (s.timer) {
-			if (s.timer > 4)
-				s.timer = 5;
-			printf(" timer:(%s,%s,%d)",
-			       tmr_name[s.timer],
-			       print_hz_timer(s.timeout),
-			       s.timer != 1 ? s.probes : s.retrs);
-		}
-	}
-	if (show_tcpinfo) {
-		int hz = get_user_hz();
-		if (s.rto && s.rto != 3*hz)
-			printf(" rto:%g", (double)s.rto/hz);
-		if (s.ato)
-			printf(" ato:%g", (double)s.ato/hz);
-		if (s.cwnd != 2)
-			printf(" cwnd:%d", s.cwnd);
-		if (s.ssthresh != -1)
-			printf(" ssthresh:%d", s.ssthresh);
-		if (s.qack/2)
-			printf(" qack:%d", s.qack/2);
-		if (s.qack&1)
-			printf(" bidir");
-	}
-	if (show_users) {
-		char ubuf[4096];
-		if (find_users(s.ino, ubuf, sizeof(ubuf)) > 0)
-			printf(" users:(%s)", ubuf);
-	}
 	if (show_details) {
-		if (s.uid)
-			printf(" uid:%u", (unsigned)s.uid);
-		printf(" ino:%u", s.ino);
-		printf(" sk:%llx", s.sk);
+		sock_details_print(&s.ss);
 		if (opt[0])
 			printf(" opt:\"%s\"", opt);
 	}
-	printf("\n");
 
+	if (show_tcpinfo)
+		tcp_stats_print(&s);
+
+	printf("\n");
 	return 0;
 }
 
@@ -1315,46 +1827,56 @@
 	return ferror(fp) ? -1 : 0;
 }
 
-static char *sprint_bw(char *buf, double bw)
+static void print_skmeminfo(struct rtattr *tb[], int attrtype)
 {
-	if (bw > 1000000.)
-		sprintf(buf,"%.1fM", bw / 1000000.);
-	else if (bw > 1000.)
-		sprintf(buf,"%.1fK", bw / 1000.);
-	else
-		sprintf(buf, "%g", bw);
+	const __u32 *skmeminfo;
 
-	return buf;
+	if (!tb[attrtype]) {
+		if (attrtype == INET_DIAG_SKMEMINFO) {
+			if (!tb[INET_DIAG_MEMINFO])
+				return;
+
+			const struct inet_diag_meminfo *minfo =
+				RTA_DATA(tb[INET_DIAG_MEMINFO]);
+
+			printf(" mem:(r%u,w%u,f%u,t%u)",
+					minfo->idiag_rmem,
+					minfo->idiag_wmem,
+					minfo->idiag_fmem,
+					minfo->idiag_tmem);
+		}
+		return;
+	}
+
+	skmeminfo = RTA_DATA(tb[attrtype]);
+
+	printf(" skmem:(r%u,rb%u,t%u,tb%u,f%u,w%u,o%u",
+	       skmeminfo[SK_MEMINFO_RMEM_ALLOC],
+	       skmeminfo[SK_MEMINFO_RCVBUF],
+	       skmeminfo[SK_MEMINFO_WMEM_ALLOC],
+	       skmeminfo[SK_MEMINFO_SNDBUF],
+	       skmeminfo[SK_MEMINFO_FWD_ALLOC],
+	       skmeminfo[SK_MEMINFO_WMEM_QUEUED],
+	       skmeminfo[SK_MEMINFO_OPTMEM]);
+
+	if (RTA_PAYLOAD(tb[attrtype]) >=
+		(SK_MEMINFO_BACKLOG + 1) * sizeof(__u32))
+		printf(",bl%u", skmeminfo[SK_MEMINFO_BACKLOG]);
+
+	printf(")");
 }
 
-static void tcp_show_info(const struct nlmsghdr *nlh, struct inet_diag_msg *r)
+#define TCPI_HAS_OPT(info, opt) !!(info->tcpi_options & (opt))
+
+static void tcp_show_info(const struct nlmsghdr *nlh, struct inet_diag_msg *r,
+		struct rtattr *tb[])
 {
-	struct rtattr * tb[INET_DIAG_MAX+1];
-	char b1[64];
 	double rtt = 0;
+	struct tcpstat s = {};
 
-	parse_rtattr(tb, INET_DIAG_MAX, (struct rtattr*)(r+1),
-		     nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+	s.ss.state = r->idiag_state;
 
-	if (tb[INET_DIAG_SKMEMINFO]) {
-		const __u32 *skmeminfo =  RTA_DATA(tb[INET_DIAG_SKMEMINFO]);
-		printf(" skmem:(r%u,rb%u,t%u,tb%u,f%u,w%u,o%u)",
-			skmeminfo[SK_MEMINFO_RMEM_ALLOC],
-			skmeminfo[SK_MEMINFO_RCVBUF],
-			skmeminfo[SK_MEMINFO_WMEM_ALLOC],
-			skmeminfo[SK_MEMINFO_SNDBUF],
-			skmeminfo[SK_MEMINFO_FWD_ALLOC],
-			skmeminfo[SK_MEMINFO_WMEM_QUEUED],
-			skmeminfo[SK_MEMINFO_OPTMEM]);
-	}else if (tb[INET_DIAG_MEMINFO]) {
-		const struct inet_diag_meminfo *minfo
-			= RTA_DATA(tb[INET_DIAG_MEMINFO]);
-		printf(" mem:(r%u,w%u,f%u,t%u)",
-		       minfo->idiag_rmem,
-		       minfo->idiag_wmem,
-		       minfo->idiag_fmem,
-		       minfo->idiag_tmem);
-	}
+	print_skmeminfo(tb, INET_DIAG_SKMEMINFO);
 
 	if (tb[INET_DIAG_INFO]) {
 		struct tcp_info *info;
@@ -1369,33 +1891,49 @@
 			info = RTA_DATA(tb[INET_DIAG_INFO]);
 
 		if (show_options) {
-			if (info->tcpi_options & TCPI_OPT_TIMESTAMPS)
-				printf(" ts");
-			if (info->tcpi_options & TCPI_OPT_SACK)
-				printf(" sack");
-			if (info->tcpi_options & TCPI_OPT_ECN)
-				printf(" ecn");
-			if (info->tcpi_options & TCPI_OPT_ECN_SEEN)
-				printf(" ecnseen");
+			s.has_ts_opt	   = TCPI_HAS_OPT(info, TCPI_OPT_TIMESTAMPS);
+			s.has_sack_opt	   = TCPI_HAS_OPT(info, TCPI_OPT_SACK);
+			s.has_ecn_opt	   = TCPI_HAS_OPT(info, TCPI_OPT_ECN);
+			s.has_ecnseen_opt  = TCPI_HAS_OPT(info, TCPI_OPT_ECN_SEEN);
+			s.has_fastopen_opt = TCPI_HAS_OPT(info, TCPI_OPT_SYN_DATA);
 		}
 
-		if (tb[INET_DIAG_CONG])
-			printf(" %s", rta_getattr_str(tb[INET_DIAG_CONG]));
+		if (tb[INET_DIAG_CONG]) {
+			const char *cong_attr = rta_getattr_str(tb[INET_DIAG_CONG]);
+			s.cong_alg = malloc(strlen(cong_attr + 1));
+			strcpy(s.cong_alg, cong_attr);
+		}
 
-		if (info->tcpi_options & TCPI_OPT_WSCALE)
-			printf(" wscale:%d,%d", info->tcpi_snd_wscale,
-			       info->tcpi_rcv_wscale);
+		if (TCPI_HAS_OPT(info, TCPI_OPT_WSCALE)) {
+			s.has_wscale_opt  = true;
+			s.snd_wscale	  = info->tcpi_snd_wscale;
+			s.rcv_wscale	  = info->tcpi_rcv_wscale;
+		}
+
 		if (info->tcpi_rto && info->tcpi_rto != 3000000)
-			printf(" rto:%g", (double)info->tcpi_rto/1000);
-		if (info->tcpi_rtt)
-			printf(" rtt:%g/%g", (double)info->tcpi_rtt/1000,
-			       (double)info->tcpi_rttvar/1000);
-		if (info->tcpi_ato)
-			printf(" ato:%g", (double)info->tcpi_ato/1000);
-		if (info->tcpi_snd_cwnd != 2)
-			printf(" cwnd:%d", info->tcpi_snd_cwnd);
+			s.rto = (double)info->tcpi_rto / 1000;
+
+		s.backoff	 = info->tcpi_backoff;
+		s.rtt		 = (double)info->tcpi_rtt / 1000;
+		s.rttvar	 = (double)info->tcpi_rttvar / 1000;
+		s.ato		 = (double)info->tcpi_ato / 1000;
+		s.mss		 = info->tcpi_snd_mss;
+		s.rcv_space	 = info->tcpi_rcv_space;
+		s.rcv_rtt	 = (double)info->tcpi_rcv_rtt / 1000;
+		s.lastsnd	 = info->tcpi_last_data_sent;
+		s.lastrcv	 = info->tcpi_last_data_recv;
+		s.lastack	 = info->tcpi_last_ack_recv;
+		s.unacked	 = info->tcpi_unacked;
+		s.retrans	 = info->tcpi_retrans;
+		s.retrans_total  = info->tcpi_total_retrans;
+		s.lost		 = info->tcpi_lost;
+		s.sacked	 = info->tcpi_sacked;
+		s.reordering	 = info->tcpi_reordering;
+		s.rcv_space	 = info->tcpi_rcv_space;
+		s.cwnd		 = info->tcpi_snd_cwnd;
+
 		if (info->tcpi_snd_ssthresh < 0xFFFF)
-			printf(" ssthresh:%d", info->tcpi_snd_ssthresh);
+			s.ssthresh = info->tcpi_snd_ssthresh;
 
 		rtt = (double) info->tcpi_rtt;
 		if (tb[INET_DIAG_VEGASINFO]) {
@@ -1403,92 +1941,109 @@
 				= RTA_DATA(tb[INET_DIAG_VEGASINFO]);
 
 			if (vinfo->tcpv_enabled &&
-			    vinfo->tcpv_rtt && vinfo->tcpv_rtt != 0x7fffffff)
+					vinfo->tcpv_rtt && vinfo->tcpv_rtt != 0x7fffffff)
 				rtt =  vinfo->tcpv_rtt;
 		}
 
-		if (rtt > 0 && info->tcpi_snd_mss && info->tcpi_snd_cwnd) {
-			printf(" send %sbps",
-			       sprint_bw(b1, (double) info->tcpi_snd_cwnd *
-					 (double) info->tcpi_snd_mss * 8000000.
-					 / rtt));
+		if (tb[INET_DIAG_DCTCPINFO]) {
+			struct dctcpstat *dctcp = malloc(sizeof(struct
+						dctcpstat));
+
+			const struct tcp_dctcp_info *dinfo
+				= RTA_DATA(tb[INET_DIAG_DCTCPINFO]);
+
+			dctcp->enabled	= !!dinfo->dctcp_enabled;
+			dctcp->ce_state = dinfo->dctcp_ce_state;
+			dctcp->alpha	= dinfo->dctcp_alpha;
+			dctcp->ab_ecn	= dinfo->dctcp_ab_ecn;
+			dctcp->ab_tot	= dinfo->dctcp_ab_tot;
+			s.dctcp		= dctcp;
 		}
 
-		if (info->tcpi_rcv_rtt)
-			printf(" rcv_rtt:%g", (double) info->tcpi_rcv_rtt/1000);
-		if (info->tcpi_rcv_space)
-			printf(" rcv_space:%d", info->tcpi_rcv_space);
+		if (rtt > 0 && info->tcpi_snd_mss && info->tcpi_snd_cwnd) {
+			s.send_bps = (double) info->tcpi_snd_cwnd *
+				(double)info->tcpi_snd_mss * 8000000. / rtt;
+		}
 
+		if (info->tcpi_pacing_rate &&
+				info->tcpi_pacing_rate != ~0ULL) {
+			s.pacing_rate = info->tcpi_pacing_rate * 8.;
+
+			if (info->tcpi_max_pacing_rate &&
+					info->tcpi_max_pacing_rate != ~0ULL)
+				s.pacing_rate_max = info->tcpi_max_pacing_rate * 8.;
+		}
+		tcp_stats_print(&s);
+		if (s.dctcp)
+			free(s.dctcp);
+		if (s.cong_alg)
+			free(s.cong_alg);
 	}
 }
 
-static int tcp_show_sock(struct nlmsghdr *nlh, struct filter *f)
+static int inet_show_sock(struct nlmsghdr *nlh, struct filter *f, int protocol)
 {
+	struct rtattr * tb[INET_DIAG_MAX+1];
 	struct inet_diag_msg *r = NLMSG_DATA(nlh);
-	struct tcpstat s;
+	struct sockstat s = {};
 
-	s.state = r->idiag_state;
-	s.local.family = s.remote.family = r->idiag_family;
-	s.lport = ntohs(r->id.idiag_sport);
-	s.rport = ntohs(r->id.idiag_dport);
+	parse_rtattr(tb, INET_DIAG_MAX, (struct rtattr*)(r+1),
+		     nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+	s.state		= r->idiag_state;
+	s.local.family  = s.remote.family = r->idiag_family;
+	s.lport		= ntohs(r->id.idiag_sport);
+	s.rport		= ntohs(r->id.idiag_dport);
+	s.wq		= r->idiag_wqueue;
+	s.rq		= r->idiag_rqueue;
+	s.ino		= r->idiag_inode;
+	s.uid		= r->idiag_uid;
+	s.iface		= r->id.idiag_if;
+	s.sk		= cookie_sk_get(&r->id.idiag_cookie[0]);
+
 	if (s.local.family == AF_INET) {
 		s.local.bytelen = s.remote.bytelen = 4;
 	} else {
 		s.local.bytelen = s.remote.bytelen = 16;
 	}
+
 	memcpy(s.local.data, r->id.idiag_src, s.local.bytelen);
 	memcpy(s.remote.data, r->id.idiag_dst, s.local.bytelen);
 
 	if (f && f->f && run_ssfilter(f->f, &s) == 0)
 		return 0;
 
-	if (netid_width)
-		printf("%-*s ", netid_width, "tcp");
-	if (state_width)
-		printf("%-*s ", state_width, sstate_name[s.state]);
-
-	printf("%-6d %-6d ", r->idiag_rqueue, r->idiag_wqueue);
-
-	formatted_print(&s.local, s.lport);
-	formatted_print(&s.remote, s.rport);
+	inet_stats_print(&s, protocol);
 
 	if (show_options) {
-		if (r->idiag_timer) {
-			if (r->idiag_timer > 4)
-				r->idiag_timer = 5;
-			printf(" timer:(%s,%s,%d)",
-			       tmr_name[r->idiag_timer],
-			       print_ms_timer(r->idiag_expires),
-			       r->idiag_retrans);
+		struct tcpstat t = {};
+
+		t.timer = r->idiag_timer;
+		t.timeout = r->idiag_expires;
+		t.retrans = r->idiag_retrans;
+		tcp_timer_print(&t);
+	}
+
+	if (show_details) {
+		sock_details_print(&s);
+		if (tb[INET_DIAG_SHUTDOWN]) {
+			unsigned char mask;
+			mask = *(__u8 *)RTA_DATA(tb[INET_DIAG_SHUTDOWN]);
+			printf(" %c-%c", mask & 1 ? '-' : '<', mask & 2 ? '-' : '>');
 		}
 	}
-	if (show_users) {
-		char ubuf[4096];
-		if (find_users(r->idiag_inode, ubuf, sizeof(ubuf)) > 0)
-			printf(" users:(%s)", ubuf);
-	}
-	if (show_details) {
-		if (r->idiag_uid)
-			printf(" uid:%u", (unsigned)r->idiag_uid);
-		printf(" ino:%u", r->idiag_inode);
-		printf(" sk:");
-		if (r->id.idiag_cookie[1] != 0)
-			printf("%08x", r->id.idiag_cookie[1]);
- 		printf("%08x", r->id.idiag_cookie[0]);
-	}
+
 	if (show_mem || show_tcpinfo) {
 		printf("\n\t");
-		tcp_show_info(nlh, r);
+		tcp_show_info(nlh, r, tb);
 	}
 
 	printf("\n");
-
 	return 0;
 }
 
-static int tcp_show_netlink(struct filter *f, FILE *dump_fp, int socktype)
+static int tcpdiag_send(int fd, int protocol, struct filter *f)
 {
-	int fd;
 	struct sockaddr_nl nladdr;
 	struct {
 		struct nlmsghdr nlh;
@@ -1498,20 +2053,22 @@
 	int	bclen;
 	struct msghdr msg;
 	struct rtattr rta;
-	char	buf[8192];
 	struct iovec iov[3];
 
-	if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG)) < 0)
+	if (protocol == IPPROTO_UDP)
 		return -1;
 
 	memset(&nladdr, 0, sizeof(nladdr));
 	nladdr.nl_family = AF_NETLINK;
 
 	req.nlh.nlmsg_len = sizeof(req);
-	req.nlh.nlmsg_type = socktype;
+	if (protocol == IPPROTO_TCP)
+		req.nlh.nlmsg_type = TCPDIAG_GETSOCK;
+	else
+		req.nlh.nlmsg_type = DCCPDIAG_GETSOCK;
 	req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
 	req.nlh.nlmsg_pid = 0;
-	req.nlh.nlmsg_seq = 123456;
+	req.nlh.nlmsg_seq = MAGIC_SEQ;
 	memset(&req.r, 0, sizeof(req.r));
 	req.r.idiag_family = AF_INET;
 	req.r.idiag_states = f->states;
@@ -1551,99 +2108,125 @@
 		return -1;
 	}
 
+	return 0;
+}
+
+static int sockdiag_send(int family, int fd, int protocol, struct filter *f)
+{
+	struct sockaddr_nl nladdr;
+	DIAG_REQUEST(req, struct inet_diag_req_v2 r);
+	char    *bc = NULL;
+	int	bclen;
+	struct msghdr msg;
+	struct rtattr rta;
+	struct iovec iov[3];
+
+	if (family == PF_UNSPEC)
+		return tcpdiag_send(fd, protocol, f);
+
+	memset(&nladdr, 0, sizeof(nladdr));
+	nladdr.nl_family = AF_NETLINK;
+
+	memset(&req.r, 0, sizeof(req.r));
+	req.r.sdiag_family = family;
+	req.r.sdiag_protocol = protocol;
+	req.r.idiag_states = f->states;
+	if (show_mem) {
+		req.r.idiag_ext |= (1<<(INET_DIAG_MEMINFO-1));
+		req.r.idiag_ext |= (1<<(INET_DIAG_SKMEMINFO-1));
+	}
+
+	if (show_tcpinfo) {
+		req.r.idiag_ext |= (1<<(INET_DIAG_INFO-1));
+		req.r.idiag_ext |= (1<<(INET_DIAG_VEGASINFO-1));
+		req.r.idiag_ext |= (1<<(INET_DIAG_CONG-1));
+	}
+
 	iov[0] = (struct iovec){
-		.iov_base = buf,
-		.iov_len = sizeof(buf)
+		.iov_base = &req,
+		.iov_len = sizeof(req)
+	};
+	if (f->f) {
+		bclen = ssfilter_bytecompile(f->f, &bc);
+		rta.rta_type = INET_DIAG_REQ_BYTECODE;
+		rta.rta_len = RTA_LENGTH(bclen);
+		iov[1] = (struct iovec){ &rta, sizeof(rta) };
+		iov[2] = (struct iovec){ bc, bclen };
+		req.nlh.nlmsg_len += RTA_LENGTH(bclen);
+	}
+
+	msg = (struct msghdr) {
+		.msg_name = (void*)&nladdr,
+		.msg_namelen = sizeof(nladdr),
+		.msg_iov = iov,
+		.msg_iovlen = f->f ? 3 : 1,
 	};
 
-	while (1) {
-		int status;
-		struct nlmsghdr *h;
-
-		msg = (struct msghdr) {
-			(void*)&nladdr, sizeof(nladdr),
-			iov,	1,
-			NULL,	0,
-			0
-		};
-
-		status = recvmsg(fd, &msg, 0);
-
-		if (status < 0) {
-			if (errno == EINTR)
-				continue;
-			perror("OVERRUN");
-			continue;
-		}
-		if (status == 0) {
-			fprintf(stderr, "EOF on netlink\n");
-			close(fd);
-			return 0;
-		}
-
-		if (dump_fp)
-			fwrite(buf, 1, NLMSG_ALIGN(status), dump_fp);
-
-		h = (struct nlmsghdr*)buf;
-		while (NLMSG_OK(h, status)) {
-			int err;
-			struct inet_diag_msg *r = NLMSG_DATA(h);
-
-			if (/*h->nlmsg_pid != rth->local.nl_pid ||*/
-			    h->nlmsg_seq != 123456)
-				goto skip_it;
-
-			if (h->nlmsg_type == NLMSG_DONE) {
-				close(fd);
-				return 0;
-			}
-			if (h->nlmsg_type == NLMSG_ERROR) {
-				struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h);
-				if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
-					fprintf(stderr, "ERROR truncated\n");
-				} else {
-					errno = -err->error;
-					if (errno == EOPNOTSUPP) {
-						close(fd);
-						return -1;
-					}
-					perror("TCPDIAG answers");
-				}
-				close(fd);
-				return 0;
-			}
-			if (!dump_fp) {
-				if (!(f->families & (1<<r->idiag_family))) {
-					h = NLMSG_NEXT(h, status);
-					continue;
-				}
-				err = tcp_show_sock(h, NULL);
-				if (err < 0) {
-					close(fd);
-					return err;
-				}
-			}
-
-skip_it:
-			h = NLMSG_NEXT(h, status);
-		}
-		if (msg.msg_flags & MSG_TRUNC) {
-			fprintf(stderr, "Message truncated\n");
-			continue;
-		}
-		if (status) {
-			fprintf(stderr, "!!!Remnant of size %d\n", status);
-			exit(1);
-		}
+	if (sendmsg(fd, &msg, 0) < 0) {
+		close(fd);
+		return -1;
 	}
-	close(fd);
+
 	return 0;
 }
 
+struct inet_diag_arg {
+	struct filter *f;
+	int protocol;
+};
+
+static int show_one_inet_sock(const struct sockaddr_nl *addr,
+		struct nlmsghdr *h, void *arg)
+{
+	int err;
+	struct inet_diag_arg *diag_arg = arg;
+	struct inet_diag_msg *r = NLMSG_DATA(h);
+
+	if (!(diag_arg->f->families & (1 << r->idiag_family)))
+		return 0;
+	if ((err = inet_show_sock(h, NULL, diag_arg->protocol)) < 0)
+		return err;
+
+	return 0;
+}
+
+static int inet_show_netlink(struct filter *f, FILE *dump_fp, int protocol)
+{
+	int err = 0;
+	struct rtnl_handle rth;
+	int family = PF_INET;
+	struct inet_diag_arg arg = { .f = f, .protocol = protocol };
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_SOCK_DIAG))
+		return -1;
+	rth.dump = MAGIC_SEQ;
+	rth.dump_fp = dump_fp;
+
+again:
+	if ((err = sockdiag_send(family, rth.fd, protocol, f)))
+		goto Exit;
+
+	if ((err = rtnl_dump_filter(&rth, show_one_inet_sock, &arg))) {
+		if (family != PF_UNSPEC) {
+			family = PF_UNSPEC;
+			goto again;
+		}
+		goto Exit;
+	}
+	if (family == PF_INET) {
+		family = PF_INET6;
+		goto again;
+	}
+
+Exit:
+	rtnl_close(&rth);
+	return err;
+}
+
 static int tcp_show_netlink_file(struct filter *f)
 {
 	FILE	*fp;
-	char	buf[8192];
+	char	buf[16384];
 
 	if ((fp = fopen(getenv("TCPDIAG_FILE"), "r")) == NULL) {
 		perror("fopen($TCPDIAG_FILE)");
@@ -1690,7 +2273,7 @@
 			return -1;
 		}
 
-		err = tcp_show_sock(h, f);
+		err = inet_show_sock(h, f, IPPROTO_TCP);
 		if (err < 0)
 			return err;
 	}
@@ -1702,13 +2285,16 @@
 	char *buf = NULL;
 	int bufsize = 64*1024;
 
+	if (!filter_af_get(f, AF_INET) && !filter_af_get(f, AF_INET6))
+		return 0;
+
 	dg_proto = TCP_PROTO;
 
 	if (getenv("TCPDIAG_FILE"))
 		return tcp_show_netlink_file(f);
 
 	if (!getenv("PROC_NET_TCP") && !getenv("PROC_ROOT")
-	    && tcp_show_netlink(f, NULL, socktype) == 0)
+	    && inet_show_netlink(f, NULL, socktype) == 0)
 		return 0;
 
 	/* Sigh... We have to parse /proc/net/tcp... */
@@ -1721,6 +2307,8 @@
 	 * it is able to give us some memory for snapshot.
 	 */
 	if (1) {
+		get_slabstat(&slabstat);
+
 		int guess = slabstat.socks+slabstat.tcp_syns;
 		if (f->states&(1<<SS_TIME_WAIT))
 			guess += slabstat.tcp_tws;
@@ -1774,55 +2362,21 @@
 }
 
 
-int dgram_show_line(char *line, const struct filter *f, int family)
+static int dgram_show_line(char *line, const struct filter *f, int family)
 {
-	struct tcpstat s;
+	struct sockstat s = {};
 	char *loc, *rem, *data;
 	char opt[256];
 	int n;
-	char *p;
 
-	if ((p = strchr(line, ':')) == NULL)
+	if (proc_inet_split_line(line, &loc, &rem, &data))
 		return -1;
-	loc = p+2;
 
-	if ((p = strchr(loc, ':')) == NULL)
-		return -1;
-	p[5] = 0;
-	rem = p+6;
+	int state = (data[1] >= 'A') ? (data[1] - 'A' + 10) : (data[1] - '0');
+	if (!(f->states & (1 << state)))
+		return 0;
 
-	if ((p = strchr(rem, ':')) == NULL)
-		return -1;
-	p[5] = 0;
-	data = p+6;
-
-	do {
-		int state = (data[1] >= 'A') ? (data[1] - 'A' + 10) : (data[1] - '0');
-
-		if (!(f->states & (1<<state)))
-			return 0;
-	} while (0);
-
-	s.local.family = s.remote.family = family;
-	if (family == AF_INET) {
-		sscanf(loc, "%x:%x", s.local.data, (unsigned*)&s.lport);
-		sscanf(rem, "%x:%x", s.remote.data, (unsigned*)&s.rport);
-		s.local.bytelen = s.remote.bytelen = 4;
-	} else {
-		sscanf(loc, "%08x%08x%08x%08x:%x",
-		       s.local.data,
-		       s.local.data+1,
-		       s.local.data+2,
-		       s.local.data+3,
-		       &s.lport);
-		sscanf(rem, "%08x%08x%08x%08x:%x",
-		       s.remote.data,
-		       s.remote.data+1,
-		       s.remote.data+2,
-		       s.remote.data+3,
-		       &s.rport);
-		s.local.bytelen = s.remote.bytelen = 16;
-	}
+	proc_parse_inet_addr(loc, rem, family, &s);
 
 	if (f->f && run_ssfilter(f->f, &s) == 0)
 		return 0;
@@ -1836,42 +2390,28 @@
 	if (n < 9)
 		opt[0] = 0;
 
-	if (netid_width)
-		printf("%-*s ", netid_width, dg_proto);
-	if (state_width)
-		printf("%-*s ", state_width, sstate_name[s.state]);
+	inet_stats_print(&s, IPPROTO_UDP);
 
-	printf("%-6d %-6d ", s.rq, s.wq);
+	if (show_details && opt[0])
+		printf(" opt:\"%s\"", opt);
 
-	formatted_print(&s.local, s.lport);
-	formatted_print(&s.remote, s.rport);
-
-	if (show_users) {
-		char ubuf[4096];
-		if (find_users(s.ino, ubuf, sizeof(ubuf)) > 0)
-			printf(" users:(%s)", ubuf);
-	}
-
-	if (show_details) {
-		if (s.uid)
-			printf(" uid=%u", (unsigned)s.uid);
-		printf(" ino=%u", s.ino);
-		printf(" sk=%llx", s.sk);
-		if (opt[0])
-			printf(" opt:\"%s\"", opt);
-	}
 	printf("\n");
-
 	return 0;
 }
 
-
-int udp_show(struct filter *f)
+static int udp_show(struct filter *f)
 {
 	FILE *fp = NULL;
 
+	if (!filter_af_get(f, AF_INET) && !filter_af_get(f, AF_INET6))
+		return 0;
+
 	dg_proto = UDP_PROTO;
 
+	if (!getenv("PROC_NET_UDP") && !getenv("PROC_ROOT")
+	    && inet_show_netlink(f, NULL, IPPROTO_UDP) == 0)
+		return 0;
+
 	if (f->families&(1<<AF_INET)) {
 		if ((fp = net_udp_open()) == NULL)
 			goto outerr;
@@ -1898,10 +2438,13 @@
 	} while (0);
 }
 
-int raw_show(struct filter *f)
+static int raw_show(struct filter *f)
 {
 	FILE *fp = NULL;
 
+	if (!filter_af_get(f, AF_INET) && !filter_af_get(f, AF_INET6))
+		return 0;
+
 	dg_proto = RAW_PROTO;
 
 	if (f->families&(1<<AF_INET)) {
@@ -1930,258 +2473,238 @@
 	} while (0);
 }
 
-
-struct unixstat
-{
-	struct unixstat *next;
-	int ino;
-	int peer;
-	int rq;
-	int wq;
-	int state;
-	int type;
-	char *name;
-};
-
-
-
 int unix_state_map[] = { SS_CLOSE, SS_SYN_SENT,
 			 SS_ESTABLISHED, SS_CLOSING };
 
+#define MAX_UNIX_REMEMBER (1024*1024/sizeof(struct sockstat))
 
-#define MAX_UNIX_REMEMBER (1024*1024/sizeof(struct unixstat))
-
-void unix_list_free(struct unixstat *list)
+static void unix_list_free(struct sockstat *list)
 {
 	while (list) {
-		struct unixstat *s = list;
+		struct sockstat *s = list;
+		char *name = sock_addr_get_str(&s->local);
+
 		list = list->next;
-		if (s->name)
-			free(s->name);
+
+		if (name)
+			free(name);
 		free(s);
 	}
 }
 
-void unix_list_print(struct unixstat *list, struct filter *f)
+static const char *unix_netid_name(int type)
 {
-	struct unixstat *s;
-	char *peer;
+	const char *netid;
+
+	switch (type) {
+	case SOCK_STREAM:
+		netid = "u_str";
+		break;
+	case SOCK_SEQPACKET:
+		netid = "u_seq";
+		break;
+	case SOCK_DGRAM:
+	default:
+		netid = "u_dgr";
+		break;
+	}
+	return netid;
+}
+
+static bool unix_type_skip(struct sockstat *s, struct filter *f)
+{
+	if (s->type == SOCK_STREAM && !(f->dbs&(1<<UNIX_ST_DB)))
+		return true;
+	if (s->type == SOCK_DGRAM && !(f->dbs&(1<<UNIX_DG_DB)))
+		return true;
+	if (s->type == SOCK_SEQPACKET && !(f->dbs&(1<<UNIX_SQ_DB)))
+		return true;
+	return false;
+}
+
+static bool unix_use_proc(void)
+{
+	return getenv("PROC_NET_UNIX") || getenv("PROC_ROOT");
+}
+
+static void unix_stats_print(struct sockstat *list, struct filter *f)
+{
+	struct sockstat *s;
+	char *local, *peer;
+	char *ctx_buf = NULL;
+	bool use_proc = unix_use_proc();
+	char port_name[30] = {};
 
 	for (s = list; s; s = s->next) {
-		if (!(f->states & (1<<s->state)))
+		if (!(f->states & (1 << s->state)))
 			continue;
-		if (s->type == SOCK_STREAM && !(f->dbs&(1<<UNIX_ST_DB)))
-			continue;
-		if (s->type == SOCK_DGRAM && !(f->dbs&(1<<UNIX_DG_DB)))
+		if (unix_type_skip(s, f))
 			continue;
 
-		peer = "*";
-		if (s->peer) {
-			struct unixstat *p;
+		local = sock_addr_get_str(&s->local);
+		peer  = "*";
+
+		if (s->rport && use_proc) {
+			struct sockstat *p;
+
 			for (p = list; p; p = p->next) {
-				if (s->peer == p->ino)
+				if (s->rport == p->lport)
 					break;
 			}
+
 			if (!p) {
 				peer = "?";
 			} else {
-				peer = p->name ? : "*";
+				peer = sock_addr_get_str(&p->local);
+				peer = peer ? : "*";
 			}
 		}
 
-		if (f->f) {
-			struct tcpstat tst;
-			tst.local.family = AF_UNIX;
-			tst.remote.family = AF_UNIX;
-			memcpy(tst.local.data, &s->name, sizeof(s->name));
+		if (use_proc && f->f) {
 			if (strcmp(peer, "*") == 0)
-				memset(tst.remote.data, 0, sizeof(peer));
+				memset(s->remote.data, 0, sizeof(char *));
 			else
-				memcpy(tst.remote.data, &peer, sizeof(peer));
-			if (run_ssfilter(f->f, &tst) == 0)
+				sock_addr_set_str(&s->remote, &peer);
+
+			if (run_ssfilter(f->f, s) == 0)
 				continue;
 		}
 
-		if (netid_width)
-			printf("%-*s ", netid_width,
-			       s->type == SOCK_STREAM ? "u_str" : "u_dgr");
-		if (state_width)
-			printf("%-*s ", state_width, sstate_name[s->state]);
-		printf("%-6d %-6d ", s->rq, s->wq);
-		printf("%*s %-*d %*s %-*d",
-		       addr_width, s->name ? : "*", serv_width, s->ino,
-		       addr_width, peer, serv_width, s->peer);
-		if (show_users) {
-			char ubuf[4096];
-			if (find_users(s->ino, ubuf, sizeof(ubuf)) > 0)
-				printf(" users:(%s)", ubuf);
+		sock_state_print(s, unix_netid_name(s->type));
+
+		sock_addr_print(local ?: "*", " ",
+				int_to_str(s->lport, port_name), NULL);
+		sock_addr_print(peer, " ", int_to_str(s->rport, port_name),
+				NULL);
+
+		if (show_proc_ctx || show_sock_ctx) {
+			if (find_entry(s->ino, &ctx_buf,
+					(show_proc_ctx & show_sock_ctx) ?
+					PROC_SOCK_CTX : PROC_CTX) > 0) {
+				printf(" users:(%s)", ctx_buf);
+				free(ctx_buf);
+			}
+		} else if (show_users) {
+			if (find_entry(s->ino, &ctx_buf, USERS) > 0) {
+				printf(" users:(%s)", ctx_buf);
+				free(ctx_buf);
+			}
 		}
 		printf("\n");
 	}
 }
 
-static int unix_show_sock(struct nlmsghdr *nlh, struct filter *f)
+static int unix_show_sock(const struct sockaddr_nl *addr, struct nlmsghdr *nlh,
+		void *arg)
 {
+	struct filter *f = (struct filter *)arg;
 	struct unix_diag_msg *r = NLMSG_DATA(nlh);
 	struct rtattr *tb[UNIX_DIAG_MAX+1];
-	char name[128];
-	int peer_ino;
-	int rqlen;
+	char *name = NULL;
+	struct sockstat stat = {};
 
 	parse_rtattr(tb, UNIX_DIAG_MAX, (struct rtattr*)(r+1),
 		     nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
 
-	if (netid_width)
-		printf("%-*s ", netid_width,
-				r->udiag_type == SOCK_STREAM ? "u_str" : "u_dgr");
-	if (state_width)
-		printf("%-*s ", state_width, sstate_name[r->udiag_state]);
+	stat.type  = r->udiag_type;
+	stat.state = r->udiag_state;
+	stat.ino   = stat.lport = r->udiag_ino;
+	stat.local.family = stat.remote.family = AF_UNIX;
 
-	if (tb[UNIX_DIAG_RQLEN])
-		rqlen = *(int *)RTA_DATA(tb[UNIX_DIAG_RQLEN]);
-	else
-		rqlen = 0;
+	if (unix_type_skip(&stat, f))
+		return 0;
 
-	printf("%-6d %-6d ", rqlen, 0);
-
+	if (tb[UNIX_DIAG_RQLEN]) {
+		struct unix_diag_rqlen *rql = RTA_DATA(tb[UNIX_DIAG_RQLEN]);
+		stat.rq = rql->udiag_rqueue;
+		stat.wq = rql->udiag_wqueue;
+	}
 	if (tb[UNIX_DIAG_NAME]) {
 		int len = RTA_PAYLOAD(tb[UNIX_DIAG_NAME]);
 
+		name = malloc(len + 1);
 		memcpy(name, RTA_DATA(tb[UNIX_DIAG_NAME]), len);
 		name[len] = '\0';
 		if (name[0] == '\0')
 			name[0] = '@';
-	} else
-		sprintf(name, "*");
-
-	if (tb[UNIX_DIAG_PEER])
-		peer_ino = *(int *)RTA_DATA(tb[UNIX_DIAG_PEER]);
-	else
-		peer_ino = 0;
-
-	printf("%*s %-*d %*s %-*d",
-			addr_width, name,
-			serv_width, r->udiag_ino,
-			addr_width, "*", /* FIXME */
-			serv_width, peer_ino);
-
-	if (show_users) {
-		char ubuf[4096];
-		if (find_users(r->udiag_ino, ubuf, sizeof(ubuf)) > 0)
-			printf(" users:(%s)", ubuf);
+		sock_addr_set_str(&stat.local, &name);
 	}
+	if (tb[UNIX_DIAG_PEER])
+		stat.rport = rta_getattr_u32(tb[UNIX_DIAG_PEER]);
 
-	printf("\n");
+	if (f->f && run_ssfilter(f->f, &stat) == 0)
+		return 0;
 
+	unix_stats_print(&stat, f);
+
+	if (show_mem) {
+		printf("\t");
+		print_skmeminfo(tb, UNIX_DIAG_MEMINFO);
+	}
+	if (show_details) {
+		if (tb[UNIX_DIAG_SHUTDOWN]) {
+			unsigned char mask;
+			mask = *(__u8 *)RTA_DATA(tb[UNIX_DIAG_SHUTDOWN]);
+			printf(" %c-%c", mask & 1 ? '-' : '<', mask & 2 ? '-' : '>');
+		}
+	}
+	if (show_mem || show_details)
+		printf("\n");
+
+	if (name)
+		free(name);
 	return 0;
 }
 
-static int unix_show_netlink(struct filter *f, FILE *dump_fp)
+static int handle_netlink_request(struct filter *f, struct nlmsghdr *req,
+		size_t size, rtnl_filter_t show_one_sock)
 {
-	int fd;
-	struct {
-		struct nlmsghdr nlh;
-		struct unix_diag_req r;
-	} req;
-	char	buf[8192];
+	int ret = -1;
+	struct rtnl_handle rth;
 
-	if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG)) < 0)
+	if (rtnl_open_byproto(&rth, 0, NETLINK_SOCK_DIAG))
 		return -1;
 
-	memset(&req, 0, sizeof(req));
-	req.nlh.nlmsg_len = sizeof(req);
-	req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
-	req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
-	req.nlh.nlmsg_seq = 123456;
+	rth.dump = MAGIC_SEQ;
+
+	if (rtnl_send(&rth, req, size) < 0)
+		goto Exit;
+
+	if (rtnl_dump_filter(&rth, show_one_sock, f))
+		goto Exit;
+
+	ret = 0;
+Exit:
+	rtnl_close(&rth);
+	return ret;
+}
+
+static int unix_show_netlink(struct filter *f)
+{
+	DIAG_REQUEST(req, struct unix_diag_req r);
 
 	req.r.sdiag_family = AF_UNIX;
 	req.r.udiag_states = f->states;
 	req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN;
+	if (show_mem)
+		req.r.udiag_show |= UDIAG_SHOW_MEMINFO;
 
-	if (send(fd, &req, sizeof(req), 0) < 0) {
-		close(fd);
-		return -1;
-	}
-
-	while (1) {
-		ssize_t status;
-		struct nlmsghdr *h;
-		struct sockaddr_nl nladdr;
-		socklen_t slen = sizeof(nladdr);
-
-		status = recvfrom(fd, buf, sizeof(buf), 0,
-				  (struct sockaddr *) &nladdr, &slen);
-		if (status < 0) {
-			if (errno == EINTR)
-				continue;
-			perror("OVERRUN");
-			continue;
-		}
-		if (status == 0) {
-			fprintf(stderr, "EOF on netlink\n");
-			goto close_it;
-		}
-
-		if (dump_fp)
-			fwrite(buf, 1, NLMSG_ALIGN(status), dump_fp);
-
-		h = (struct nlmsghdr*)buf;
-		while (NLMSG_OK(h, status)) {
-			int err;
-
-			if (/*h->nlmsg_pid != rth->local.nl_pid ||*/
-			    h->nlmsg_seq != 123456)
-				goto skip_it;
-
-			if (h->nlmsg_type == NLMSG_DONE)
-				goto close_it;
-
-			if (h->nlmsg_type == NLMSG_ERROR) {
-				struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h);
-				if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
-					fprintf(stderr, "ERROR truncated\n");
-				} else {
-					errno = -err->error;
-					if (errno != ENOENT)
-						fprintf(stderr, "UDIAG answers %d\n", errno);
-				}
-				close(fd);
-				return -1;
-			}
-			if (!dump_fp) {
-				err = unix_show_sock(h, f);
-				if (err < 0) {
-					close(fd);
-					return err;
-				}
-			}
-
-skip_it:
-			h = NLMSG_NEXT(h, status);
-		}
-
-		if (status) {
-			fprintf(stderr, "!!!Remnant of size %zd\n", status);
-			exit(1);
-		}
-	}
-
-close_it:
-	close(fd);
-	return 0;
+	return handle_netlink_request(f, &req.nlh, sizeof(req), unix_show_sock);
 }
 
-int unix_show(struct filter *f)
+static int unix_show(struct filter *f)
 {
 	FILE *fp;
 	char buf[256];
 	char name[128];
 	int  newformat = 0;
 	int  cnt;
-	struct unixstat *list = NULL;
+	struct sockstat *list = NULL;
 
-	if (!getenv("PROC_NET_UNIX") && !getenv("PROC_ROOT")
-	    && unix_show_netlink(f, NULL) == 0)
+	if (!filter_af_get(f, AF_UNIX))
+		return 0;
+
+	if (!unix_use_proc() && unix_show_netlink(f) == 0)
 		return 0;
 
 	if ((fp = net_unix_open()) == NULL)
@@ -2193,30 +2716,30 @@
 	cnt = 0;
 
 	while (fgets(buf, sizeof(buf)-1, fp)) {
-		struct unixstat *u, **insp;
+		struct sockstat *u, **insp;
 		int flags;
 
 		if (!(u = malloc(sizeof(*u))))
 			break;
-		u->name = NULL;
 
 		if (sscanf(buf, "%x: %x %x %x %x %x %d %s",
-			   &u->peer, &u->rq, &u->wq, &flags, &u->type,
+			   &u->rport, &u->rq, &u->wq, &flags, &u->type,
 			   &u->state, &u->ino, name) < 8)
 			name[0] = 0;
 
-		if (flags&(1<<16)) {
+		u->lport = u->ino;
+		u->local.family = u->remote.family = AF_UNIX;
+
+		if (flags & (1 << 16)) {
 			u->state = SS_LISTEN;
 		} else {
 			u->state = unix_state_map[u->state-1];
-			if (u->type == SOCK_DGRAM &&
-			    u->state == SS_CLOSE &&
-			    u->peer)
+			if (u->type == SOCK_DGRAM && u->state == SS_CLOSE && u->rport)
 				u->state = SS_ESTABLISHED;
 		}
 
 		if (!newformat) {
-			u->peer = 0;
+			u->rport = 0;
 			u->rq = 0;
 			u->wq = 0;
 		}
@@ -2233,12 +2756,11 @@
 		*insp = u;
 
 		if (name[0]) {
-			if ((u->name = malloc(strlen(name)+1)) == NULL)
-				break;
-			strcpy(u->name, name);
+			char *tmp = strdup(name);
+			sock_addr_set_str(&u->local, &tmp);
 		}
 		if (++cnt > MAX_UNIX_REMEMBER) {
-			unix_list_print(list, f);
+			unix_stats_print(list, f);
 			unix_list_free(list);
 			list = NULL;
 			cnt = 0;
@@ -2246,7 +2768,7 @@
 	}
 	fclose(fp);
 	if (list) {
-		unix_list_print(list, f);
+		unix_stats_print(list, f);
 		unix_list_free(list);
 		list = NULL;
 		cnt = 0;
@@ -2255,85 +2777,318 @@
 	return 0;
 }
 
+static int packet_stats_print(struct sockstat *s, const struct filter *f)
+{
+	char *buf = NULL;
+	const char *addr, *port;
+	char ll_name[16];
 
-int packet_show(struct filter *f)
+	if (f->f) {
+		s->local.family = AF_PACKET;
+		s->remote.family = AF_PACKET;
+		s->local.data[0] = s->prot;
+		if (run_ssfilter(f->f, s) == 0)
+			return 1;
+	}
+
+	sock_state_print(s, s->type == SOCK_RAW ? "p_raw" : "p_dgr");
+
+	if (s->prot == 3)
+		addr = "*";
+	else
+		addr = ll_proto_n2a(htons(s->prot), ll_name, sizeof(ll_name));
+
+	if (s->iface == 0)
+		port = "*";
+	else
+		port = xll_index_to_name(s->iface);
+
+	sock_addr_print(addr, ":", port, NULL);
+	sock_addr_print("", "*", "", NULL);
+
+	if (show_proc_ctx || show_sock_ctx) {
+		if (find_entry(s->ino, &buf,
+					(show_proc_ctx & show_sock_ctx) ?
+					PROC_SOCK_CTX : PROC_CTX) > 0) {
+			printf(" users:(%s)", buf);
+			free(buf);
+		}
+	} else if (show_users) {
+		if (find_entry(s->ino, &buf, USERS) > 0) {
+			printf(" users:(%s)", buf);
+			free(buf);
+		}
+	}
+
+	if (show_details)
+		sock_details_print(s);
+
+	return 0;
+}
+
+static int packet_show_sock(const struct sockaddr_nl *addr,
+		struct nlmsghdr *nlh, void *arg)
+{
+	const struct filter *f = arg;
+	struct packet_diag_msg *r = NLMSG_DATA(nlh);
+	struct rtattr *tb[PACKET_DIAG_MAX+1];
+	struct sockstat stat = {};
+
+	parse_rtattr(tb, PACKET_DIAG_MAX, (struct rtattr*)(r+1),
+		     nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+	/* use /proc/net/packet if all info are not available */
+	if (!tb[PACKET_DIAG_MEMINFO])
+		return -1;
+
+	stat.type   = r->pdiag_type;
+	stat.prot   = r->pdiag_num;
+	stat.ino    = r->pdiag_ino;
+	stat.state  = SS_CLOSE;
+	stat.sk	    = cookie_sk_get(&r->pdiag_cookie[0]);
+
+	if (tb[PACKET_DIAG_MEMINFO]) {
+		__u32 *skmeminfo = RTA_DATA(tb[PACKET_DIAG_MEMINFO]);
+		stat.rq = skmeminfo[SK_MEMINFO_RMEM_ALLOC];
+	}
+
+	if (tb[PACKET_DIAG_INFO]) {
+		struct packet_diag_info *pinfo = RTA_DATA(tb[PACKET_DIAG_INFO]);
+		stat.lport = stat.iface = pinfo->pdi_index;
+	}
+
+	if (tb[PACKET_DIAG_UID])
+		stat.uid = *(__u32 *)RTA_DATA(tb[PACKET_DIAG_UID]);
+
+	if (packet_stats_print(&stat, f))
+		return 0;
+
+	if (show_bpf && tb[PACKET_DIAG_FILTER]) {
+		struct sock_filter *fil =
+		       RTA_DATA(tb[PACKET_DIAG_FILTER]);
+		int num = RTA_PAYLOAD(tb[PACKET_DIAG_FILTER]) /
+			  sizeof(struct sock_filter);
+
+		printf("\n\tbpf filter (%d): ", num);
+		while (num) {
+			printf(" 0x%02x %u %u %u,",
+			      fil->code, fil->jt, fil->jf, fil->k);
+			num--;
+			fil++;
+		}
+	}
+	printf("\n");
+	return 0;
+}
+
+static int packet_show_netlink(struct filter *f)
+{
+	DIAG_REQUEST(req, struct packet_diag_req r);
+
+	req.r.sdiag_family = AF_PACKET;
+	req.r.pdiag_show = PACKET_SHOW_INFO | PACKET_SHOW_MEMINFO | PACKET_SHOW_FILTER;
+
+	return handle_netlink_request(f, &req.nlh, sizeof(req), packet_show_sock);
+}
+
+static int packet_show_line(char *buf, const struct filter *f, int fam)
+{
+	unsigned long long sk;
+	struct sockstat stat = {};
+	int type, prot, iface, state, rq, uid, ino;
+
+	sscanf(buf, "%llx %*d %d %x %d %d %u %u %u",
+			&sk,
+			&type, &prot, &iface, &state,
+			&rq, &uid, &ino);
+
+	if (stat.type == SOCK_RAW && !(f->dbs&(1<<PACKET_R_DB)))
+		return 0;
+	if (stat.type == SOCK_DGRAM && !(f->dbs&(1<<PACKET_DG_DB)))
+		return 0;
+
+	stat.type  = type;
+	stat.prot  = prot;
+	stat.lport = stat.iface = iface;
+	stat.state = state;
+	stat.rq    = rq;
+	stat.uid   = uid;
+	stat.ino   = ino;
+	stat.state = SS_CLOSE;
+
+	if (packet_stats_print(&stat, f))
+		return 0;
+
+	printf("\n");
+	return 0;
+}
+
+static int packet_show(struct filter *f)
 {
 	FILE *fp;
-	char buf[256];
-	int type;
-	int prot;
-	int iface;
-	int state;
-	int rq;
-	int uid;
-	int ino;
-	unsigned long long sk;
 
-	if (!(f->states & (1<<SS_CLOSE)))
+	if (!filter_af_get(f, AF_PACKET) || !(f->states & (1 << SS_CLOSE)))
+		return 0;
+
+	if (!getenv("PROC_NET_PACKET") && !getenv("PROC_ROOT") &&
+			packet_show_netlink(f) == 0)
 		return 0;
 
 	if ((fp = net_packet_open()) == NULL)
 		return -1;
-	fgets(buf, sizeof(buf)-1, fp);
+	if (generic_record_read(fp, packet_show_line, f, AF_PACKET))
+		return -1;
 
-	while (fgets(buf, sizeof(buf)-1, fp)) {
-		sscanf(buf, "%llx %*d %d %x %d %d %u %u %u",
-		       &sk,
-		       &type, &prot, &iface, &state,
-		       &rq, &uid, &ino);
+	return 0;
+}
 
-		if (type == SOCK_RAW && !(f->dbs&(1<<PACKET_R_DB)))
-			continue;
-		if (type == SOCK_DGRAM && !(f->dbs&(1<<PACKET_DG_DB)))
-			continue;
-		if (f->f) {
-			struct tcpstat tst;
-			tst.local.family = AF_PACKET;
-			tst.remote.family = AF_PACKET;
-			tst.rport = 0;
-			tst.lport = iface;
-			tst.local.data[0] = prot;
-			tst.remote.data[0] = 0;
-			if (run_ssfilter(f->f, &tst) == 0)
-				continue;
+static int netlink_show_one(struct filter *f,
+				int prot, int pid, unsigned groups,
+				int state, int dst_pid, unsigned dst_group,
+				int rq, int wq,
+				unsigned long long sk, unsigned long long cb)
+{
+	struct sockstat st;
+	SPRINT_BUF(prot_buf) = {};
+	const char *prot_name;
+	char procname[64] = {};
+
+	st.state = SS_CLOSE;
+	st.rq	 = rq;
+	st.wq	 = wq;
+
+	if (f->f) {
+		st.local.family = AF_NETLINK;
+		st.remote.family = AF_NETLINK;
+		st.rport = -1;
+		st.lport = pid;
+		st.local.data[0] = prot;
+		if (run_ssfilter(f->f, &st) == 0)
+			return 1;
+	}
+
+	sock_state_print(&st, "nl");
+
+	if (resolve_services)
+		prot_name = nl_proto_n2a(prot, prot_buf, sizeof(prot_buf));
+	else
+		prot_name = int_to_str(prot, prot_buf);
+
+	if (pid == -1) {
+		procname[0] = '*';
+	} else if (resolve_services) {
+		int done = 0;
+		if (!pid) {
+			done = 1;
+			strncpy(procname, "kernel", 6);
+		} else if (pid > 0) {
+			FILE *fp;
+			sprintf(procname, "%s/%d/stat",
+				getenv("PROC_ROOT") ? : "/proc", pid);
+			if ((fp = fopen(procname, "r")) != NULL) {
+				if (fscanf(fp, "%*d (%[^)])", procname) == 1) {
+					sprintf(procname+strlen(procname), "/%d", pid);
+					done = 1;
+				}
+				fclose(fp);
+			}
 		}
+		if (!done)
+			int_to_str(pid, procname);
+	} else {
+		int_to_str(pid, procname);
+	}
 
-		if (netid_width)
-			printf("%-*s ", netid_width,
-			       type == SOCK_RAW ? "p_raw" : "p_dgr");
-		if (state_width)
-			printf("%-*s ", state_width, "UNCONN");
-		printf("%-6d %-6d ", rq, 0);
-		if (prot == 3) {
-			printf("%*s:", addr_width, "*");
+	sock_addr_print(prot_name, ":", procname, NULL);
+
+	if (state == NETLINK_CONNECTED) {
+		char dst_group_buf[30];
+		char dst_pid_buf[30];
+		sock_addr_print(int_to_str(dst_group, dst_group_buf), ":",
+				int_to_str(dst_pid, dst_pid_buf), NULL);
+	} else {
+		sock_addr_print("", "*", "", NULL);
+	}
+
+	char *pid_context = NULL;
+	if (show_proc_ctx) {
+		/* The pid value will either be:
+		 *   0 if destination kernel - show kernel initial context.
+		 *   A valid process pid - use getpidcon.
+		 *   A unique value allocated by the kernel or netlink user
+		 *   to the process - show context as "not available".
+		 */
+		if (!pid)
+			security_get_initial_context("kernel", &pid_context);
+		else if (pid > 0)
+			getpidcon(pid, &pid_context);
+
+		if (pid_context != NULL) {
+			printf("proc_ctx=%-*s ", serv_width, pid_context);
+			free(pid_context);
 		} else {
-			char tb[16];
-			printf("%*s:", addr_width,
-			       ll_proto_n2a(htons(prot), tb, sizeof(tb)));
+			printf("proc_ctx=%-*s ", serv_width, "unavailable");
 		}
-		if (iface == 0) {
-			printf("%-*s ", serv_width, "*");
-		} else {
-			printf("%-*s ", serv_width, xll_index_to_name(iface));
-		}
-		printf("%*s*%-*s",
-		       addr_width, "", serv_width, "");
+	}
 
-		if (show_users) {
-			char ubuf[4096];
-			if (find_users(ino, ubuf, sizeof(ubuf)) > 0)
-				printf(" users:(%s)", ubuf);
-		}
-		if (show_details) {
-			printf(" ino=%u uid=%u sk=%llx", ino, uid, sk);
-		}
+	if (show_details) {
+		printf(" sk=%llx cb=%llx groups=0x%08x", sk, cb, groups);
+	}
+	printf("\n");
+
+	return 0;
+}
+
+static int netlink_show_sock(const struct sockaddr_nl *addr,
+		struct nlmsghdr *nlh, void *arg)
+{
+	struct filter *f = (struct filter *)arg;
+	struct netlink_diag_msg *r = NLMSG_DATA(nlh);
+	struct rtattr *tb[NETLINK_DIAG_MAX+1];
+	int rq = 0, wq = 0;
+	unsigned long groups = 0;
+
+	parse_rtattr(tb, NETLINK_DIAG_MAX, (struct rtattr*)(r+1),
+		     nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+	if (tb[NETLINK_DIAG_GROUPS] && RTA_PAYLOAD(tb[NETLINK_DIAG_GROUPS]))
+		groups = *(unsigned long *) RTA_DATA(tb[NETLINK_DIAG_GROUPS]);
+
+	if (tb[NETLINK_DIAG_MEMINFO]) {
+		const __u32 *skmeminfo;
+		skmeminfo = RTA_DATA(tb[NETLINK_DIAG_MEMINFO]);
+
+		rq = skmeminfo[SK_MEMINFO_RMEM_ALLOC];
+		wq = skmeminfo[SK_MEMINFO_WMEM_ALLOC];
+	}
+
+	if (netlink_show_one(f, r->ndiag_protocol, r->ndiag_portid, groups,
+			 r->ndiag_state, r->ndiag_dst_portid, r->ndiag_dst_group,
+			 rq, wq, 0, 0)) {
+		return 0;
+	}
+
+	if (show_mem) {
+		printf("\t");
+		print_skmeminfo(tb, NETLINK_DIAG_MEMINFO);
 		printf("\n");
 	}
 
 	return 0;
 }
 
-int netlink_show(struct filter *f)
+static int netlink_show_netlink(struct filter *f)
+{
+	DIAG_REQUEST(req, struct netlink_diag_req r);
+
+	req.r.sdiag_family = AF_NETLINK;
+	req.r.sdiag_protocol = NDIAG_PROTO_ALL;
+	req.r.ndiag_show = NDIAG_SHOW_GROUPS | NDIAG_SHOW_MEMINFO;
+
+	return handle_netlink_request(f, &req.nlh, sizeof(req), netlink_show_sock);
+}
+
+static int netlink_show(struct filter *f)
 {
 	FILE *fp;
 	char buf[256];
@@ -2342,7 +3097,11 @@
 	int rq, wq, rc;
 	unsigned long long sk, cb;
 
-	if (!(f->states & (1<<SS_CLOSE)))
+	if (!filter_af_get(f, AF_NETLINK) || !(f->states & (1 << SS_CLOSE)))
+		return 0;
+
+	if (!getenv("PROC_NET_NETLINK") && !getenv("PROC_ROOT") &&
+		netlink_show_netlink(f) == 0)
 		return 0;
 
 	if ((fp = net_netlink_open()) == NULL)
@@ -2354,64 +3113,7 @@
 		       &sk,
 		       &prot, &pid, &groups, &rq, &wq, &cb, &rc);
 
-		if (f->f) {
-			struct tcpstat tst;
-			tst.local.family = AF_NETLINK;
-			tst.remote.family = AF_NETLINK;
-			tst.rport = -1;
-			tst.lport = pid;
-			tst.local.data[0] = prot;
-			tst.remote.data[0] = 0;
-			if (run_ssfilter(f->f, &tst) == 0)
-				continue;
-		}
-
-		if (netid_width)
-			printf("%-*s ", netid_width, "nl");
-		if (state_width)
-			printf("%-*s ", state_width, "UNCONN");
-		printf("%-6d %-6d ", rq, wq);
-		if (resolve_services && prot == 0)
-			printf("%*s:", addr_width, "rtnl");
-		else if (resolve_services && prot == 3)
-			printf("%*s:", addr_width, "fw");
-		else if (resolve_services && prot == 4)
-			printf("%*s:", addr_width, "tcpdiag");
-		else
-			printf("%*d:", addr_width, prot);
-		if (pid == -1) {
-			printf("%-*s ", serv_width, "*");
-		} else if (resolve_services) {
-			int done = 0;
-			if (!pid) {
-				done = 1;
-				printf("%-*s ", serv_width, "kernel");
-			} else if (pid > 0) {
-				char procname[64];
-				FILE *fp;
-				sprintf(procname, "%s/%d/stat",
-					getenv("PROC_ROOT") ? : "/proc", pid);
-				if ((fp = fopen(procname, "r")) != NULL) {
-					if (fscanf(fp, "%*d (%[^)])", procname) == 1) {
-						sprintf(procname+strlen(procname), "/%d", pid);
-						printf("%-*s ", serv_width, procname);
-						done = 1;
-					}
-					fclose(fp);
-				}
-			}
-			if (!done)
-				printf("%-*d ", serv_width, pid);
-		} else {
-			printf("%-*d ", serv_width, pid);
-		}
-		printf("%*s*%-*s",
-		       addr_width, "", serv_width, "");
-
-		if (show_details) {
-			printf(" sk=%llx cb=%llx groups=0x%08x", sk, cb, groups);
-		}
-		printf("\n");
+		netlink_show_one(f, prot, pid, groups, 0, 0, 0, rq, wq, sk, cb);
 	}
 
 	return 0;
@@ -2422,7 +3124,7 @@
 	int tcp_estab;
 };
 
-int get_snmp_int(char *proto, char *key, int *result)
+static int get_snmp_int(char *proto, char *key, int *result)
 {
 	char buf[1024];
 	FILE *fp;
@@ -2469,7 +3171,7 @@
 
 /* Get stats from sockstat */
 
-struct sockstat
+struct ssummary
 {
 	int socks;
 	int tcp_mem;
@@ -2488,7 +3190,7 @@
 	int frag6_mem;
 };
 
-static void get_sockstat_line(char *line, struct sockstat *s)
+static void get_sockstat_line(char *line, struct ssummary *s)
 {
 	char id[256], rem[256];
 
@@ -2517,7 +3219,7 @@
 		       &s->tcp_orphans, &s->tcp_tws, &s->tcp_total, &s->tcp_mem);
 }
 
-int get_sockstat(struct sockstat *s)
+static int get_sockstat(struct ssummary *s)
 {
 	char buf[256];
 	FILE *fp;
@@ -2539,9 +3241,9 @@
 	return 0;
 }
 
-int print_summary(void)
+static int print_summary(void)
 {
-	struct sockstat s;
+	struct ssummary s;
 	struct snmpstat sn;
 
 	if (get_sockstat(&s) < 0)
@@ -2549,6 +3251,8 @@
 	if (get_snmp_int("Tcp:", "CurrEstab", &sn.tcp_estab) < 0)
 		perror("ss: get_snmpstat");
 
+	get_slabstat(&slabstat);
+
 	printf("Total: %d (kernel %d)\n", s.socks, slabstat.socks);
 
 	printf("TCP:   %d (estab %d, closed %d, orphaned %d, synrecv %d, timewait %d/%d), ports %d\n",
@@ -2584,35 +3288,45 @@
 	fprintf(dest,
 "Usage: ss [ OPTIONS ]\n"
 "       ss [ OPTIONS ] [ FILTER ]\n"
-"   -h, --help		this message\n"
-"   -V, --version	output version information\n"
-"   -n, --numeric	don't resolve service names\n"
+"   -h, --help          this message\n"
+"   -V, --version       output version information\n"
+"   -n, --numeric       don't resolve service names\n"
 "   -r, --resolve       resolve host names\n"
-"   -a, --all		display all sockets\n"
-"   -l, --listening	display listening sockets\n"
+"   -a, --all           display all sockets\n"
+"   -l, --listening     display listening sockets\n"
 "   -o, --options       show timer information\n"
 "   -e, --extended      show detailed socket information\n"
 "   -m, --memory        show socket memory usage\n"
-"   -p, --processes	show process using socket\n"
-"   -i, --info		show internal TCP information\n"
-"   -s, --summary	show socket usage summary\n"
+"   -p, --processes     show process using socket\n"
+"   -i, --info          show internal TCP information\n"
+"   -s, --summary       show socket usage summary\n"
+"   -b, --bpf           show bpf filter socket information\n"
+"   -Z, --context       display process SELinux security contexts\n"
+"   -z, --contexts      display process and socket SELinux security contexts\n"
+"   -N, --net           switch to the specified network namespace name\n"
 "\n"
 "   -4, --ipv4          display only IP version 4 sockets\n"
 "   -6, --ipv6          display only IP version 6 sockets\n"
-"   -0, --packet	display PACKET sockets\n"
-"   -t, --tcp		display only TCP sockets\n"
-"   -u, --udp		display only UDP sockets\n"
-"   -d, --dccp		display only DCCP sockets\n"
-"   -w, --raw		display only RAW sockets\n"
-"   -x, --unix		display only Unix domain sockets\n"
+"   -0, --packet        display PACKET sockets\n"
+"   -t, --tcp           display only TCP sockets\n"
+"   -u, --udp           display only UDP sockets\n"
+"   -d, --dccp          display only DCCP sockets\n"
+"   -w, --raw           display only RAW sockets\n"
+"   -x, --unix          display only Unix domain sockets\n"
 "   -f, --family=FAMILY display sockets of type FAMILY\n"
 "\n"
 "   -A, --query=QUERY, --socket=QUERY\n"
-"       QUERY := {all|inet|tcp|udp|raw|unix|packet|netlink}[,QUERY]\n"
+"       QUERY := {all|inet|tcp|udp|raw|unix|unix_dgram|unix_stream|unix_seqpacket|packet|netlink}[,QUERY]\n"
 "\n"
 "   -D, --diag=FILE     Dump raw information about TCP sockets to FILE\n"
 "   -F, --filter=FILE   read filter information from FILE\n"
-"       FILTER := [ state TCP-STATE ] [ EXPRESSION ]\n"
+"       FILTER := [ state STATE-FILTER ] [ EXPRESSION ]\n"
+"       STATE-FILTER := {all|connected|synchronized|bucket|big|TCP-STATES}\n"
+"         TCP-STATES := {established|syn-sent|syn-recv|fin-wait-{1,2}|time-wait|closed|close-wait|last-ack|listen|closing}\n"
+"          connected := {established|syn-sent|syn-recv|fin-wait-{1,2}|time-wait|close-wait|last-ack|closing}\n"
+"       synchronized := {established|syn-recv|fin-wait-{1,2}|time-wait|close-wait|last-ack|closing}\n"
+"             bucket := {syn-recv|time-wait}\n"
+"                big := {established|syn-sent|fin-wait-{1,2}|closed|close-wait|last-ack|listen|closing}\n"
 		);
 }
 
@@ -2631,7 +3345,7 @@
 }
 
 
-int scan_state(const char *state)
+static int scan_state(const char *state)
 {
 	int i;
 	if (strcasecmp(state, "close") == 0 ||
@@ -2655,7 +3369,9 @@
 		if (strcasecmp(state, sstate_namel[i]) == 0)
 			return (1<<i);
 	}
-	return 0;
+
+	fprintf(stderr, "ss: wrong state name: %s\n", state);
+	exit(-1);
 }
 
 static const struct option long_opts[] = {
@@ -2666,6 +3382,7 @@
 	{ "memory", 0, 0, 'm' },
 	{ "info", 0, 0, 'i' },
 	{ "processes", 0, 0, 'p' },
+	{ "bpf", 0, 0, 'b' },
 	{ "dccp", 0, 0, 'd' },
 	{ "tcp", 0, 0, 't' },
 	{ "udp", 0, 0, 'u' },
@@ -2684,25 +3401,25 @@
 	{ "filter", 1, 0, 'F' },
 	{ "version", 0, 0, 'V' },
 	{ "help", 0, 0, 'h' },
+	{ "context", 0, 0, 'Z' },
+	{ "contexts", 0, 0, 'z' },
+	{ "net", 1, 0, 'N' },
 	{ 0 }
 
 };
 
 int main(int argc, char *argv[])
 {
-	int do_default = 1;
 	int saw_states = 0;
 	int saw_query = 0;
 	int do_summary = 0;
 	const char *dump_tcpdiag = NULL;
 	FILE *filter_fp = NULL;
 	int ch;
+	struct filter dbs_filter = {};
+	int state_filter = 0;
 
-	memset(&current_filter, 0, sizeof(current_filter));
-
-	current_filter.states = default_filter.states;
-
-	while ((ch = getopt_long(argc, argv, "dhaletuwxnro460spf:miA:D:F:vV",
+	while ((ch = getopt_long(argc, argv, "dhaletuwxnro460spbf:miA:D:F:vVzZN:",
 				 long_opts, NULL)) != EOF) {
 		switch(ch) {
 		case 'n':
@@ -2728,56 +3445,56 @@
 			show_users++;
 			user_ent_hash_build();
 			break;
+		case 'b':
+			show_options = 1;
+			show_bpf++;
+			break;
 		case 'd':
-			current_filter.dbs |= (1<<DCCP_DB);
-			do_default = 0;
+			filter_db_set(&dbs_filter, DCCP_DB);
 			break;
 		case 't':
-			current_filter.dbs |= (1<<TCP_DB);
-			do_default = 0;
+			filter_db_set(&dbs_filter, TCP_DB);
 			break;
 		case 'u':
-			current_filter.dbs |= (1<<UDP_DB);
-			do_default = 0;
+			filter_db_set(&dbs_filter, UDP_DB);
 			break;
 		case 'w':
-			current_filter.dbs |= (1<<RAW_DB);
-			do_default = 0;
+			filter_db_set(&dbs_filter, RAW_DB);
 			break;
 		case 'x':
-			current_filter.dbs |= UNIX_DBM;
-			do_default = 0;
+			filter_af_set(&current_filter, AF_UNIX);
 			break;
 		case 'a':
-			current_filter.states = SS_ALL;
+			state_filter = SS_ALL;
 			break;
 		case 'l':
-			current_filter.states = (1<<SS_LISTEN) | (1<<SS_CLOSE);
+			state_filter = (1 << SS_LISTEN) | (1 << SS_CLOSE);
 			break;
 		case '4':
-			preferred_family = AF_INET;
+			filter_af_set(&current_filter, AF_INET);
 			break;
 		case '6':
-			preferred_family = AF_INET6;
+			filter_af_set(&current_filter, AF_INET6);
 			break;
 		case '0':
-			preferred_family = AF_PACKET;
+			filter_af_set(&current_filter, AF_PACKET);
 			break;
 		case 'f':
 			if (strcmp(optarg, "inet") == 0)
-				preferred_family = AF_INET;
+				filter_af_set(&current_filter, AF_INET);
 			else if (strcmp(optarg, "inet6") == 0)
-				preferred_family = AF_INET6;
+				filter_af_set(&current_filter, AF_INET6);
 			else if (strcmp(optarg, "link") == 0)
-				preferred_family = AF_PACKET;
+				filter_af_set(&current_filter, AF_PACKET);
 			else if (strcmp(optarg, "unix") == 0)
-				preferred_family = AF_UNIX;
+				filter_af_set(&current_filter, AF_UNIX);
 			else if (strcmp(optarg, "netlink") == 0)
-				preferred_family = AF_NETLINK;
+				filter_af_set(&current_filter, AF_NETLINK);
 			else if (strcmp(optarg, "help") == 0)
 				help();
 			else {
-				fprintf(stderr, "ss: \"%s\" is invalid family\n", optarg);
+				fprintf(stderr, "ss: \"%s\" is invalid family\n",
+						optarg);
 				usage();
 			}
 			break;
@@ -2794,35 +3511,44 @@
 				if ((p1 = strchr(p, ',')) != NULL)
 					*p1 = 0;
 				if (strcmp(p, "all") == 0) {
-					current_filter.dbs = ALL_DB;
+					filter_default_dbs(&dbs_filter);
 				} else if (strcmp(p, "inet") == 0) {
-					current_filter.dbs |= (1<<TCP_DB)|(1<<DCCP_DB)|(1<<UDP_DB)|(1<<RAW_DB);
+					filter_db_set(&dbs_filter, UDP_DB);
+					filter_db_set(&dbs_filter, DCCP_DB);
+					filter_db_set(&dbs_filter, TCP_DB);
+					filter_db_set(&dbs_filter, RAW_DB);
 				} else if (strcmp(p, "udp") == 0) {
-					current_filter.dbs |= (1<<UDP_DB);
+					filter_db_set(&dbs_filter, UDP_DB);
 				} else if (strcmp(p, "dccp") == 0) {
-					current_filter.dbs |= (1<<DCCP_DB);
+					filter_db_set(&dbs_filter, DCCP_DB);
 				} else if (strcmp(p, "tcp") == 0) {
-					current_filter.dbs |= (1<<TCP_DB);
+					filter_db_set(&dbs_filter, TCP_DB);
 				} else if (strcmp(p, "raw") == 0) {
-					current_filter.dbs |= (1<<RAW_DB);
+					filter_db_set(&dbs_filter, RAW_DB);
 				} else if (strcmp(p, "unix") == 0) {
-					current_filter.dbs |= UNIX_DBM;
+					filter_db_set(&dbs_filter, UNIX_ST_DB);
+					filter_db_set(&dbs_filter, UNIX_DG_DB);
+					filter_db_set(&dbs_filter, UNIX_SQ_DB);
 				} else if (strcasecmp(p, "unix_stream") == 0 ||
 					   strcmp(p, "u_str") == 0) {
-					current_filter.dbs |= (1<<UNIX_ST_DB);
+					filter_db_set(&dbs_filter, UNIX_ST_DB);
 				} else if (strcasecmp(p, "unix_dgram") == 0 ||
 					   strcmp(p, "u_dgr") == 0) {
-					current_filter.dbs |= (1<<UNIX_DG_DB);
+					filter_db_set(&dbs_filter, UNIX_DG_DB);
+				} else if (strcasecmp(p, "unix_seqpacket") == 0 ||
+					   strcmp(p, "u_seq") == 0) {
+					filter_db_set(&dbs_filter, UNIX_SQ_DB);
 				} else if (strcmp(p, "packet") == 0) {
-					current_filter.dbs |= PACKET_DBM;
+					filter_db_set(&dbs_filter, PACKET_R_DB);
+					filter_db_set(&dbs_filter, PACKET_DG_DB);
 				} else if (strcmp(p, "packet_raw") == 0 ||
 					   strcmp(p, "p_raw") == 0) {
-					current_filter.dbs |= (1<<PACKET_R_DB);
+					filter_db_set(&dbs_filter, PACKET_R_DB);
 				} else if (strcmp(p, "packet_dgram") == 0 ||
 					   strcmp(p, "p_dgr") == 0) {
-					current_filter.dbs |= (1<<PACKET_DG_DB);
+					filter_db_set(&dbs_filter, PACKET_DG_DB);
 				} else if (strcmp(p, "netlink") == 0) {
-					current_filter.dbs |= (1<<NETLINK_DB);
+					filter_db_set(&dbs_filter, NETLINK_DB);
 				} else {
 					fprintf(stderr, "ss: \"%s\" is illegal socket table id\n", p);
 					usage();
@@ -2855,6 +3581,20 @@
 		case 'V':
 			printf("ss utility, iproute2-ss%s\n", SNAPSHOT);
 			exit(0);
+		case 'z':
+			show_sock_ctx++;
+		case 'Z':
+			if (is_selinux_enabled() <= 0) {
+				fprintf(stderr, "ss: SELinux is not enabled.\n");
+				exit(1);
+			}
+			show_proc_ctx++;
+			user_ent_hash_build();
+			break;
+		case 'N':
+			if (netns_switch(optarg))
+				exit(1);
+			break;
 		case 'h':
 		case '?':
 			help();
@@ -2866,65 +3606,12 @@
 	argc -= optind;
 	argv += optind;
 
-	get_slabstat(&slabstat);
-
 	if (do_summary) {
 		print_summary();
 		if (do_default && argc == 0)
 			exit(0);
 	}
 
-	if (do_default)
-		current_filter.dbs = default_filter.dbs;
-
-	if (preferred_family == AF_UNSPEC) {
-		if (!(current_filter.dbs&~UNIX_DBM))
-			preferred_family = AF_UNIX;
-		else if (!(current_filter.dbs&~PACKET_DBM))
-			preferred_family = AF_PACKET;
-		else if (!(current_filter.dbs&~(1<<NETLINK_DB)))
-			preferred_family = AF_NETLINK;
-	}
-
-	if (preferred_family != AF_UNSPEC) {
-		int mask2;
-		if (preferred_family == AF_INET ||
-		    preferred_family == AF_INET6) {
-			mask2= current_filter.dbs;
-		} else if (preferred_family == AF_PACKET) {
-			mask2 = PACKET_DBM;
-		} else if (preferred_family == AF_UNIX) {
-			mask2 = UNIX_DBM;
-		} else if (preferred_family == AF_NETLINK) {
-			mask2 = (1<<NETLINK_DB);
-		} else {
-			mask2 = 0;
-		}
-
-		if (do_default)
-			current_filter.dbs = mask2;
-		else
-			current_filter.dbs &= mask2;
-		current_filter.families = (1<<preferred_family);
-	} else {
-		if (!do_default)
-			current_filter.families = ~0;
-		else
-			current_filter.families = default_filter.families;
-	}
-	if (current_filter.dbs == 0) {
-		fprintf(stderr, "ss: no socket tables to show with such filter.\n");
-		exit(0);
-	}
-	if (current_filter.families == 0) {
-		fprintf(stderr, "ss: no families to show with such filter.\n");
-		exit(0);
-	}
-
-	if (resolve_services && resolve_hosts &&
-	    (current_filter.dbs&(UNIX_DBM|(1<<TCP_DB)|(1<<UDP_DB)|(1<<DCCP_DB))))
-		init_service_resolver();
-
 	/* Now parse filter... */
 	if (argc == 0 && filter_fp) {
 		if (ssfilter_parse(&current_filter.f, 0, NULL, filter_fp))
@@ -2935,24 +3622,43 @@
 		if (strcmp(*argv, "state") == 0) {
 			NEXT_ARG();
 			if (!saw_states)
-				current_filter.states = 0;
-			current_filter.states |= scan_state(*argv);
+				state_filter = 0;
+			state_filter |= scan_state(*argv);
 			saw_states = 1;
 		} else if (strcmp(*argv, "exclude") == 0 ||
 			   strcmp(*argv, "excl") == 0) {
 			NEXT_ARG();
 			if (!saw_states)
-				current_filter.states = SS_ALL;
-			current_filter.states &= ~scan_state(*argv);
+				state_filter = SS_ALL;
+			state_filter &= ~scan_state(*argv);
 			saw_states = 1;
 		} else {
-			if (ssfilter_parse(&current_filter.f, argc, argv, filter_fp))
-				usage();
 			break;
 		}
 		argc--; argv++;
 	}
 
+	if (do_default) {
+		state_filter = state_filter ? state_filter : SS_CONN;
+		filter_default_dbs(&current_filter);
+		filter_merge(&current_filter, &current_filter, state_filter);
+	} else {
+		filter_merge(&current_filter, &dbs_filter, state_filter);
+	}
+
+	if (resolve_services && resolve_hosts &&
+	    (current_filter.dbs&(UNIX_DBM|(1<<TCP_DB)|(1<<UDP_DB)|(1<<DCCP_DB))))
+		init_service_resolver();
+
+
+	if (current_filter.dbs == 0) {
+		fprintf(stderr, "ss: no socket tables to show with such filter.\n");
+		exit(0);
+	}
+	if (current_filter.families == 0) {
+		fprintf(stderr, "ss: no families to show with such filter.\n");
+		exit(0);
+	}
 	if (current_filter.states == 0) {
 		fprintf(stderr, "ss: no socket states to show with such filter.\n");
 		exit(0);
@@ -2971,11 +3677,14 @@
 				exit(-1);
 			}
 		}
-		tcp_show_netlink(&current_filter, dump_fp, TCPDIAG_GETSOCK);
+		inet_show_netlink(&current_filter, dump_fp, IPPROTO_TCP);
 		fflush(dump_fp);
 		exit(0);
 	}
 
+	if (ssfilter_parse(&current_filter.f, argc, argv, filter_fp))
+		usage();
+
 	netid_width = 0;
 	if (current_filter.dbs&(current_filter.dbs-1))
 		netid_width = 5;
@@ -3022,6 +3731,10 @@
 		printf("%-*s ", state_width, "State");
 	printf("%-6s %-6s ", "Recv-Q", "Send-Q");
 
+	/* Make enough space for the local/remote port field */
+	addr_width -= 13;
+	serv_width += 13;
+
 	printf("%*s:%-*s %*s:%-*s\n",
 	       addr_width, "Local Address", serv_width, "Port",
 	       addr_width, "Peer Address", serv_width, "Port");
@@ -3039,8 +3752,12 @@
 	if (current_filter.dbs & (1<<UDP_DB))
 		udp_show(&current_filter);
 	if (current_filter.dbs & (1<<TCP_DB))
-		tcp_show(&current_filter, TCPDIAG_GETSOCK);
+		tcp_show(&current_filter, IPPROTO_TCP);
 	if (current_filter.dbs & (1<<DCCP_DB))
-		tcp_show(&current_filter, DCCPDIAG_GETSOCK);
+		tcp_show(&current_filter, IPPROTO_DCCP);
+
+	if (show_users || show_proc_ctx || show_sock_ctx)
+		user_ent_destroy();
+
 	return 0;
 }
diff --git a/misc/ssfilter.h b/misc/ssfilter.h
index 00b92e3..b20092b 100644
--- a/misc/ssfilter.h
+++ b/misc/ssfilter.h
@@ -9,6 +9,8 @@
 #define SSF_S_LE  8
 #define SSF_S_AUTO  9
 
+#include <stdbool.h>
+
 struct ssfilter
 {
 	int type;
@@ -17,5 +19,5 @@
 };
 
 int ssfilter_parse(struct ssfilter **f, int argc, char **argv, FILE *fp);
-void *parse_hostcond(char*);
+void *parse_hostcond(char *addr, bool is_port);
 
diff --git a/misc/ssfilter.y b/misc/ssfilter.y
index 2e9d962..a258d04 100644
--- a/misc/ssfilter.y
+++ b/misc/ssfilter.y
@@ -25,6 +25,7 @@
 static int		yy_argc;
 static FILE		*yy_fp;
 static ssfilter_t	*yy_ret;
+static int tok_type = -1;
 
 static int yylex(void);
 
@@ -220,14 +221,22 @@
 		return '(';
 	if (strcmp(curtok, ")") == 0)
 		return ')';
-	if (strcmp(curtok, "dst") == 0)
+	if (strcmp(curtok, "dst") == 0) {
+		tok_type = DCOND;
 		return DCOND;
-	if (strcmp(curtok, "src") == 0)
+	}
+	if (strcmp(curtok, "src") == 0) {
+                tok_type = SCOND;
 		return SCOND;
-	if (strcmp(curtok, "dport") == 0)
+        }
+	if (strcmp(curtok, "dport") == 0) {
+		tok_type = DPORT;
 		return DPORT;
-	if (strcmp(curtok, "sport") == 0)
+	}
+	if (strcmp(curtok, "sport") == 0) {
+		tok_type = SPORT;
 		return SPORT;
+	}
 	if (strcmp(curtok, ">=") == 0 ||
 	    strcmp(curtok, "ge") == 0 ||
 	    strcmp(curtok, "geq") == 0)
@@ -250,9 +259,11 @@
 	if (strcmp(curtok, "<") == 0 ||
 	    strcmp(curtok, "lt") == 0)
 		return '<';
-	if (strcmp(curtok, "autobound") == 0)
+	if (strcmp(curtok, "autobound") == 0) {
+		tok_type = AUTOBOUND;
 		return AUTOBOUND;
-	yylval = (void*)parse_hostcond(curtok);
+	}
+	yylval = (void*)parse_hostcond(curtok, tok_type == SPORT || tok_type == DPORT);
 	if (yylval == NULL) {
 		fprintf(stderr, "Cannot parse dst/src address.\n");
 		exit(1);
diff --git a/tc/Android.mk b/tc/Android.mk
index f137d3e..1e8b3be 100644
--- a/tc/Android.mk
+++ b/tc/Android.mk
@@ -1,8 +1,6 @@
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-# clang cannot compile 'variable length array in structure' in ipxfrm.c
-LOCAL_CLANG := false
 LOCAL_SRC_FILES :=  tc.c tc_qdisc.c q_cbq.c tc_util.c tc_class.c tc_core.c m_action.c \
                     m_estimator.c tc_filter.c tc_monitor.c tc_stab.c tc_cbq.c \
                     tc_estimator.c f_u32.c m_police.c q_ingress.c m_mirred.c q_htb.c
diff --git a/tc/Makefile b/tc/Makefile
index be8cd5a..d831a15 100644
--- a/tc/Makefile
+++ b/tc/Makefile
@@ -1,8 +1,13 @@
 TCOBJ= tc.o tc_qdisc.o tc_class.o tc_filter.o tc_util.o \
-       tc_monitor.o m_police.o m_estimator.o m_action.o \
+       tc_monitor.o tc_bpf.o m_police.o m_estimator.o m_action.o \
        m_ematch.o emp_ematch.yacc.o emp_ematch.lex.o
 
 include ../Config
+
+ifeq ($(IP_CONFIG_SETNS),y)
+	CFLAGS += -DHAVE_SETNS
+endif
+
 SHARED_LIBS ?= y
 
 TCMODULES :=
@@ -22,6 +27,7 @@
 TCMODULES += f_route.o
 TCMODULES += f_fw.o
 TCMODULES += f_basic.o
+TCMODULES += f_bpf.o
 TCMODULES += f_flow.o
 TCMODULES += f_cgroup.o
 TCMODULES += q_dsmark.o
@@ -38,6 +44,9 @@
 TCMODULES += m_pedit.o
 TCMODULES += m_skbedit.o
 TCMODULES += m_csum.o
+TCMODULES += m_simple.o
+TCMODULES += m_vlan.o
+TCMODULES += m_bpf.o
 TCMODULES += p_ip.o
 TCMODULES += p_icmp.o
 TCMODULES += p_tcp.o
@@ -45,8 +54,20 @@
 TCMODULES += em_nbyte.o
 TCMODULES += em_cmp.o
 TCMODULES += em_u32.o
+TCMODULES += em_canid.o
 TCMODULES += em_meta.o
 TCMODULES += q_mqprio.o
+TCMODULES += q_codel.o
+TCMODULES += q_fq_codel.o
+TCMODULES += q_fq.o
+TCMODULES += q_pie.o
+TCMODULES += q_hhf.o
+
+ifeq ($(TC_CONFIG_IPSET), y)
+  ifeq ($(TC_CONFIG_XT), y)
+    TCMODULES += em_ipset.o
+  endif
+endif
 
 TCSO :=
 ifeq ($(TC_CONFIG_ATM),y)
@@ -126,10 +147,12 @@
 	$(CC) $(CFLAGS) $(LDFLAGS) -shared -fpic -o q_atm.so q_atm.c -latm
 
 m_xt.so: m_xt.c
-	$(CC) $(CFLAGS) $(LDFLAGS) -shared -fpic -o m_xt.so m_xt.c $$(pkg-config xtables --cflags --libs)
+	$(CC) $(CFLAGS) $(LDFLAGS) -shared -fpic -o m_xt.so m_xt.c $$($(PKG_CONFIG) xtables --cflags --libs)
 
 m_xt_old.so: m_xt_old.c
-	$(CC) $(CFLAGS) $(LDFLAGS) -shared -fpic -o m_xt_old.so m_xt_old.c $$(pkg-config xtables --cflags --libs)
+	$(CC) $(CFLAGS) $(LDFLAGS) -shared -fpic -o m_xt_old.so m_xt_old.c $$($(PKG_CONFIG) xtables --cflags --libs)
+
+em_ipset.o: CFLAGS += $$($(PKG_CONFIG) xtables --cflags)
 
 %.yacc.c: %.y
 	$(YACC) $(YACCFLAGS) -o $@ $<
diff --git a/tc/em_canid.c b/tc/em_canid.c
new file mode 100644
index 0000000..16f6ed5
--- /dev/null
+++ b/tc/em_canid.c
@@ -0,0 +1,191 @@
+/*
+ * em_canid.c  Ematch rule to match CAN frames according to their CAN identifiers
+ *
+ *             This program is free software; you can distribute 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.
+ *
+ * Idea:       Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
+ * Copyright:  (c) 2011 Czech Technical University in Prague
+ *             (c) 2011 Volkswagen Group Research
+ * Authors:    Michal Sojka <sojkam1@fel.cvut.cz>
+ *             Pavel Pisa <pisa@cmp.felk.cvut.cz>
+ *             Rostislav Lisovy <lisovy@gmail.cz>
+ * Funded by:  Volkswagen Group Research
+ *
+ * Documentation: http://rtime.felk.cvut.cz/can/socketcan-qdisc-final.pdf
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/can.h>
+#include <inttypes.h>
+#include "m_ematch.h"
+
+#define EM_CANID_RULES_MAX 400 /* Main reason for this number is Nelink
+	message size limit equal to Single memory page size. When dump()
+	is invoked, there are even some ematch related headers sent from
+	kernel to userspace together with em_canid configuration --
+	400*sizeof(struct can_filter) should fit without any problems */
+
+extern struct ematch_util canid_ematch_util;
+struct rules {
+	struct can_filter *rules_raw;
+	int rules_capacity;	/* Size of array allocated for rules_raw */
+	int rules_cnt;		/* Actual number of rules stored in rules_raw */
+};
+
+static void canid_print_usage(FILE *fd)
+{
+	fprintf(fd,
+		"Usage: canid(IDLIST)\n" \
+		"where: IDLIST := IDSPEC [ IDLIST ]\n" \
+		"       IDSPEC := { ’sff’ CANID | ’eff’ CANID }\n" \
+		"       CANID := ID[:MASK]\n" \
+		"       ID, MASK := hexadecimal number (i.e. 0x123)\n" \
+		"Example: canid(sff 0x123 sff 0x124 sff 0x125:0xf)\n");
+}
+
+static int canid_parse_rule(struct rules *rules, struct bstr *a, int iseff)
+{
+	unsigned int can_id = 0;
+	unsigned int can_mask = 0;
+
+	if (sscanf(a->data, "%"SCNx32 ":" "%"SCNx32, &can_id, &can_mask) != 2) {
+		if (sscanf(a->data, "%"SCNx32, &can_id) != 1) {
+			return -1;
+		} else {
+			can_mask = (iseff) ? CAN_EFF_MASK : CAN_SFF_MASK;
+		}
+	}
+
+	/* Stretch rules array up to EM_CANID_RULES_MAX if necessary */
+	if (rules->rules_cnt == rules->rules_capacity) {
+		if (rules->rules_capacity <= EM_CANID_RULES_MAX/2) {
+			rules->rules_capacity *= 2;
+			rules->rules_raw = realloc(rules->rules_raw,
+				sizeof(struct can_filter) * rules->rules_capacity);
+		} else {
+			return -2;
+		}
+	}
+
+	rules->rules_raw[rules->rules_cnt].can_id =
+		can_id | ((iseff) ? CAN_EFF_FLAG : 0);
+	rules->rules_raw[rules->rules_cnt].can_mask =
+		can_mask | CAN_EFF_FLAG;
+
+	rules->rules_cnt++;
+
+	return 0;
+}
+
+static int canid_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+			  struct bstr *args)
+{
+	int iseff = 0;
+	int ret = 0;
+	struct rules rules = {
+		.rules_capacity = 25, /* Denominator of EM_CANID_RULES_MAX
+			Will be multiplied by 2 to calculate the size for realloc() */
+		.rules_cnt = 0
+	};
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+	em_parse_error(EINVAL, args, CARG, &canid_ematch_util, FMT, ##ARGS)
+
+	if (args == NULL)
+		return PARSE_ERR(args, "canid: missing arguments");
+
+	rules.rules_raw = malloc(sizeof(struct can_filter) * rules.rules_capacity);
+	memset(rules.rules_raw, 0, sizeof(struct can_filter) * rules.rules_capacity);
+
+	do {
+		if (!bstrcmp(args, "sff")) {
+			iseff = 0;
+		} else if (!bstrcmp(args, "eff")) {
+			iseff = 1;
+		} else {
+			ret = PARSE_ERR(args, "canid: invalid key");
+			goto exit;
+		}
+
+		args = bstr_next(args);
+		if (args == NULL) {
+			ret = PARSE_ERR(args, "canid: missing argument");
+			goto exit;
+		}
+
+		ret = canid_parse_rule(&rules, args, iseff);
+		if (ret == -1) {
+			ret = PARSE_ERR(args, "canid: Improperly formed CAN ID & mask\n");
+			goto exit;
+		} else if (ret == -2) {
+			ret = PARSE_ERR(args, "canid: Too many arguments on input\n");
+			goto exit;
+		}
+	} while ((args = bstr_next(args)) != NULL);
+
+	addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+	addraw_l(n, MAX_MSG, rules.rules_raw,
+		sizeof(struct can_filter) * rules.rules_cnt);
+
+#undef PARSE_ERR
+exit:
+	free(rules.rules_raw);
+	return ret;
+}
+
+static int canid_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+			  int data_len)
+{
+	struct can_filter *conf = data; /* Array with rules */
+	int rules_count;
+	int i;
+
+	rules_count = data_len / sizeof(struct can_filter);
+
+	for (i = 0; i < rules_count; i++) {
+		struct can_filter *pcfltr = &conf[i];
+
+		if (pcfltr->can_id & CAN_EFF_FLAG) {
+			if (pcfltr->can_mask == (CAN_EFF_FLAG | CAN_EFF_MASK))
+				fprintf(fd, "eff 0x%"PRIX32,
+						pcfltr->can_id & CAN_EFF_MASK);
+			else
+				fprintf(fd, "eff 0x%"PRIX32":0x%"PRIX32,
+						pcfltr->can_id & CAN_EFF_MASK,
+						pcfltr->can_mask & CAN_EFF_MASK);
+		} else {
+			if (pcfltr->can_mask == (CAN_EFF_FLAG | CAN_SFF_MASK))
+				fprintf(fd, "sff 0x%"PRIX32,
+						pcfltr->can_id & CAN_SFF_MASK);
+			else
+				fprintf(fd, "sff 0x%"PRIX32":0x%"PRIX32,
+						pcfltr->can_id & CAN_SFF_MASK,
+						pcfltr->can_mask & CAN_SFF_MASK);
+		}
+
+		if ((i + 1) < rules_count)
+			fprintf(fd, " ");
+	}
+
+	return 0;
+}
+
+struct ematch_util canid_ematch_util = {
+	.kind = "canid",
+	.kind_num = TCF_EM_CANID,
+	.parse_eopt = canid_parse_eopt,
+	.print_eopt = canid_print_eopt,
+	.print_usage = canid_print_usage
+};
diff --git a/tc/em_ipset.c b/tc/em_ipset.c
new file mode 100644
index 0000000..a2d0d15
--- /dev/null
+++ b/tc/em_ipset.c
@@ -0,0 +1,265 @@
+/*
+ * em_ipset.c		IPset Ematch
+ *
+ * (C) 2012 Florian Westphal <fw@strlen.de>
+ *
+ * Parts taken from iptables libxt_set.h:
+ * Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ *                         Patrick Schaaf <bof@bof.de>
+ *                         Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 2003-2010 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <errno.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+#include <xtables.h>
+#include <linux/netfilter/ipset/ip_set.h>
+
+#ifndef IPSET_INVALID_ID
+typedef __u16 ip_set_id_t;
+
+enum ip_set_dim {
+	IPSET_DIM_ZERO = 0,
+	IPSET_DIM_ONE,
+	IPSET_DIM_TWO,
+	IPSET_DIM_THREE,
+	IPSET_DIM_MAX = 6,
+};
+#endif /* IPSET_INVALID_ID */
+
+#include <linux/netfilter/xt_set.h>
+#include "m_ematch.h"
+
+#ifndef IPSET_INVALID_ID
+#define IPSET_INVALID_ID	65535
+#define SO_IP_SET		83
+
+union ip_set_name_index {
+	char name[IPSET_MAXNAMELEN];
+	__u16 index;
+};
+
+#define IP_SET_OP_GET_BYNAME	0x00000006	/* Get set index by name */
+struct ip_set_req_get_set {
+	unsigned op;
+	unsigned version;
+	union ip_set_name_index set;
+};
+
+#define IP_SET_OP_GET_BYINDEX	0x00000007	/* Get set name by index */
+/* Uses ip_set_req_get_set */
+
+#define IP_SET_OP_VERSION	0x00000100	/* Ask kernel version */
+struct ip_set_req_version {
+	unsigned op;
+	unsigned version;
+};
+#endif /* IPSET_INVALID_ID */
+
+extern struct ematch_util ipset_ematch_util;
+
+static int get_version(unsigned *version)
+{
+	int res, sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+	struct ip_set_req_version req_version;
+	socklen_t size = sizeof(req_version);
+
+	if (sockfd < 0) {
+		fputs("Can't open socket to ipset.\n", stderr);
+		return -1;
+	}
+
+	req_version.op = IP_SET_OP_VERSION;
+	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req_version, &size);
+	if (res != 0) {
+		perror("xt_set getsockopt");
+		return -1;
+	}
+
+	*version = req_version.version;
+	return sockfd;
+}
+
+static int do_getsockopt(struct ip_set_req_get_set *req)
+{
+	int sockfd, res;
+	socklen_t size = sizeof(struct ip_set_req_get_set);
+	sockfd = get_version(&req->version);
+	if (sockfd < 0)
+		return -1;
+	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, req, &size);
+	if (res != 0)
+		perror("Problem when communicating with ipset");
+	close(sockfd);
+	if (res != 0)
+		return -1;
+
+	if (size != sizeof(struct ip_set_req_get_set)) {
+		fprintf(stderr,
+			"Incorrect return size from kernel during ipset lookup, "
+			"(want %zu, got %zu)\n",
+			sizeof(struct ip_set_req_get_set), (size_t)size);
+		return -1;
+	}
+
+	return res;
+}
+
+static int
+get_set_byid(char *setname, unsigned int idx)
+{
+	struct ip_set_req_get_set req;
+	int res;
+
+	req.op = IP_SET_OP_GET_BYINDEX;
+	req.set.index = idx;
+	res = do_getsockopt(&req);
+	if (res != 0)
+		return -1;
+	if (req.set.name[0] == '\0') {
+		fprintf(stderr,
+			"Set with index %i in kernel doesn't exist.\n", idx);
+		return -1;
+	}
+
+	strncpy(setname, req.set.name, IPSET_MAXNAMELEN);
+	return 0;
+}
+
+static int
+get_set_byname(const char *setname, struct xt_set_info *info)
+{
+	struct ip_set_req_get_set req;
+	int res;
+
+	req.op = IP_SET_OP_GET_BYNAME;
+	strncpy(req.set.name, setname, IPSET_MAXNAMELEN);
+	req.set.name[IPSET_MAXNAMELEN - 1] = '\0';
+	res = do_getsockopt(&req);
+	if (res != 0)
+		return -1;
+	if (req.set.index == IPSET_INVALID_ID)
+		return -1;
+	info->index = req.set.index;
+	return 0;
+}
+
+static int
+parse_dirs(const char *opt_arg, struct xt_set_info *info)
+{
+        char *saved = strdup(opt_arg);
+        char *ptr, *tmp = saved;
+
+	if (!tmp) {
+		perror("strdup");
+		return -1;
+	}
+
+        while (info->dim < IPSET_DIM_MAX && tmp != NULL) {
+                info->dim++;
+                ptr = strsep(&tmp, ",");
+                if (strncmp(ptr, "src", 3) == 0)
+                        info->flags |= (1 << info->dim);
+                else if (strncmp(ptr, "dst", 3) != 0) {
+                        fputs("You must specify (the comma separated list of) 'src' or 'dst'\n", stderr);
+			free(saved);
+			return -1;
+		}
+        }
+
+        if (tmp)
+                fprintf(stderr, "Can't be more src/dst options than %u", IPSET_DIM_MAX);
+        free(saved);
+	return tmp ? -1 : 0;
+}
+
+static void ipset_print_usage(FILE *fd)
+{
+	fprintf(fd,
+	    "Usage: ipset(SETNAME FLAGS)\n" \
+	    "where: SETNAME:= string\n" \
+	    "       FLAGS  := { FLAG[,FLAGS] }\n" \
+	    "       FLAG   := { src | dst }\n" \
+	    "\n" \
+	    "Example: 'ipset(bulk src,dst)'\n");
+}
+
+static int ipset_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+			    struct bstr *args)
+{
+	struct xt_set_info set_info;
+	int ret;
+
+	memset(&set_info, 0, sizeof(set_info));
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+	em_parse_error(EINVAL, args, CARG, &ipset_ematch_util, FMT ,##ARGS)
+
+	if (args == NULL)
+		return PARSE_ERR(args, "ipset: missing set name");
+
+	if (args->len >= IPSET_MAXNAMELEN)
+		return PARSE_ERR(args, "ipset: set name too long (max %u)", IPSET_MAXNAMELEN - 1);
+	ret = get_set_byname(args->data, &set_info);
+	if (ret < 0)
+		return PARSE_ERR(args, "ipset: unknown set name '%s'", args->data);
+
+	if (args->next == NULL)
+		return PARSE_ERR(args, "ipset: missing set flags");
+
+	args = bstr_next(args);
+	if (parse_dirs(args->data, &set_info))
+		return PARSE_ERR(args, "ipset: error parsing set flags");
+
+	if (args->next) {
+		args = bstr_next(args);
+		return PARSE_ERR(args, "ipset: unknown parameter");
+	}
+
+	addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+	addraw_l(n, MAX_MSG, &set_info, sizeof(set_info));
+
+#undef PARSE_ERR
+	return 0;
+}
+
+static int ipset_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+			    int data_len)
+{
+	int i;
+        char setname[IPSET_MAXNAMELEN];
+	const struct xt_set_info *set_info = data;
+
+	if (data_len != sizeof(*set_info)) {
+		fprintf(stderr, "xt_set_info struct size mismatch\n");
+		return -1;
+	}
+
+        if (get_set_byid(setname, set_info->index))
+		return -1;
+	fputs(setname, fd);
+	for (i = 1; i <= set_info->dim; i++) {
+		fprintf(fd, "%s%s", i == 1 ? " " : ",", set_info->flags & (1 << i) ? "src" : "dst");
+	}
+
+	return 0;
+}
+
+struct ematch_util ipset_ematch_util = {
+	.kind = "ipset",
+	.kind_num = TCF_EM_IPSET,
+	.parse_eopt = ipset_parse_eopt,
+	.print_eopt = ipset_print_eopt,
+	.print_usage = ipset_print_usage
+};
diff --git a/tc/em_meta.c b/tc/em_meta.c
index fad6b12..b64f333 100644
--- a/tc/em_meta.c
+++ b/tc/em_meta.c
@@ -32,7 +32,7 @@
 	    "where: OBJECT  := { META_ID | VALUE }\n" \
 	    "       META_ID := id [ shift SHIFT ] [ mask MASK ]\n" \
 	    "\n" \
-	    "Example: meta(nfmark gt 24)\n" \
+	    "Example: meta(nf_mark gt 24)\n" \
 	    "         meta(indev shift 1 eq \"ppp\")\n" \
 	    "         meta(tcindex mask 0xf0 eq 0xf0)\n" \
 	    "\n" \
diff --git a/tc/emp_ematch.y b/tc/emp_ematch.y
index 7043a80..bc08da2 100644
--- a/tc/emp_ematch.y
+++ b/tc/emp_ematch.y
@@ -9,7 +9,7 @@
 %locations
 %token-table
 %error-verbose
-%name-prefix="ematch_"
+%name-prefix "ematch_"
 
 %union {
 	unsigned int i;
diff --git a/tc/f_basic.c b/tc/f_basic.c
index 67d26ec..1c33ca3 100644
--- a/tc/f_basic.c
+++ b/tc/f_basic.c
@@ -27,11 +27,12 @@
 
 static void explain(void)
 {
-	fprintf(stderr, "Usage: ... basic [ match EMATCH_TREE ] [ police POLICE_SPEC ]\n");
+	fprintf(stderr, "Usage: ... basic [ match EMATCH_TREE ] \n");
 	fprintf(stderr, "                 [ action ACTION_SPEC ] [ classid CLASSID ]\n");
 	fprintf(stderr, "\n");
 	fprintf(stderr, "Where: SELECTOR := SAMPLE SAMPLE ...\n");
 	fprintf(stderr, "       FILTERID := X:Y:Z\n");
+	fprintf(stderr, "       ACTION_SPEC := ... look at individual actions\n");
 	fprintf(stderr, "\nNOTE: CLASSID is parsed as hexadecimal input.\n");
 }
 
diff --git a/tc/f_bpf.c b/tc/f_bpf.c
new file mode 100644
index 0000000..e2af94e
--- /dev/null
+++ b/tc/f_bpf.c
@@ -0,0 +1,176 @@
+/*
+ * f_bpf.c	BPF-based Classifier
+ *
+ *		This program is free software; you can distribute 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.
+ *
+ * Authors:	Daniel Borkmann <dborkman@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <limits.h>
+#include <linux/filter.h>
+#include <linux/if.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_bpf.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... bpf ...\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, " [inline]:     run bytecode BPF_BYTECODE\n");
+	fprintf(stderr, " [from file]:  run bytecode-file FILE\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, "               [ action ACTION_SPEC ]\n");
+	fprintf(stderr, "               [ classid CLASSID ]\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, "Where BPF_BYTECODE := \'s,c t f k,c t f k,c t f k,...\'\n");
+	fprintf(stderr, "      c,t,f,k and s are decimals; s denotes number of 4-tuples\n");
+	fprintf(stderr, "Where FILE points to a file containing the BPF_BYTECODE string\n");
+	fprintf(stderr, "\nACTION_SPEC := ... look at individual actions\n");
+	fprintf(stderr, "NOTE: CLASSID is parsed as hexadecimal input.\n");
+}
+
+static int bpf_parse_opt(struct filter_util *qu, char *handle,
+			 int argc, char **argv, struct nlmsghdr *n)
+{
+	struct tcmsg *t = NLMSG_DATA(n);
+	struct rtattr *tail;
+	long h = 0;
+
+	if (argc == 0)
+		return 0;
+
+	if (handle) {
+		h = strtol(handle, NULL, 0);
+		if (h == LONG_MIN || h == LONG_MAX) {
+			fprintf(stderr, "Illegal handle \"%s\", must be "
+				"numeric.\n", handle);
+			return -1;
+		}
+	}
+
+	t->tcm_handle = h;
+
+	tail = (struct rtattr*)(((void*)n)+NLMSG_ALIGN(n->nlmsg_len));
+	addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);
+
+	while (argc > 0) {
+		if (matches(*argv, "run") == 0) {
+			bool from_file;
+			struct sock_filter bpf_ops[BPF_MAXINSNS];
+			__u16 bpf_len;
+			int ret;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "bytecode-file") == 0) {
+				from_file = true;
+			} else if (strcmp(*argv, "bytecode") == 0) {
+				from_file = false;
+			} else {
+				fprintf(stderr, "What is \"%s\"?\n", *argv);
+				explain();
+				return -1;
+			}
+			NEXT_ARG();
+			ret = bpf_parse_ops(argc, argv, bpf_ops, from_file);
+			if (ret < 0) {
+				fprintf(stderr, "Illegal \"bytecode\"\n");
+				return -1;
+			}
+			bpf_len = ret;
+			addattr16(n, MAX_MSG, TCA_BPF_OPS_LEN, bpf_len);
+			addattr_l(n, MAX_MSG, TCA_BPF_OPS, &bpf_ops,
+				  bpf_len * sizeof(struct sock_filter));
+		} else if (matches(*argv, "classid") == 0 ||
+			   strcmp(*argv, "flowid") == 0) {
+			unsigned handle;
+			NEXT_ARG();
+			if (get_tc_classid(&handle, *argv)) {
+				fprintf(stderr, "Illegal \"classid\"\n");
+				return -1;
+			}
+			addattr_l(n, MAX_MSG, TCA_BPF_CLASSID, &handle, 4);
+		} else if (matches(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (parse_action(&argc, &argv, TCA_BPF_ACT, n)) {
+				fprintf(stderr, "Illegal \"action\"\n");
+				return -1;
+			}
+			continue;
+		} else if (matches(*argv, "police") == 0) {
+			NEXT_ARG();
+			if (parse_police(&argc, &argv, TCA_BPF_POLICE, n)) {
+				fprintf(stderr, "Illegal \"police\"\n");
+				return -1;
+			}
+			continue;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	tail->rta_len = (((void*)n)+n->nlmsg_len) - (void*)tail;
+	return 0;
+}
+
+static int bpf_print_opt(struct filter_util *qu, FILE *f,
+			 struct rtattr *opt, __u32 handle)
+{
+	struct rtattr *tb[TCA_BPF_MAX + 1];
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_BPF_MAX, opt);
+
+	if (handle)
+		fprintf(f, "handle 0x%x ", handle);
+
+	if (tb[TCA_BPF_CLASSID]) {
+		SPRINT_BUF(b1);
+		fprintf(f, "flowid %s ",
+			sprint_tc_classid(rta_getattr_u32(tb[TCA_BPF_CLASSID]), b1));
+	}
+
+	if (tb[TCA_BPF_OPS] && tb[TCA_BPF_OPS_LEN])
+		bpf_print_ops(f, tb[TCA_BPF_OPS],
+			      rta_getattr_u16(tb[TCA_BPF_OPS_LEN]));
+
+	if (tb[TCA_BPF_POLICE]) {
+		fprintf(f, "\n");
+		tc_print_police(f, tb[TCA_BPF_POLICE]);
+	}
+
+	if (tb[TCA_BPF_ACT]) {
+		tc_print_action(f, tb[TCA_BPF_ACT]);
+	}
+
+	return 0;
+}
+
+struct filter_util bpf_filter_util = {
+	.id = "bpf",
+	.parse_fopt = bpf_parse_opt,
+	.print_fopt = bpf_print_opt,
+};
diff --git a/tc/f_cgroup.c b/tc/f_cgroup.c
index 4a4026e..53f7406 100644
--- a/tc/f_cgroup.c
+++ b/tc/f_cgroup.c
@@ -18,8 +18,8 @@
 
 static void explain(void)
 {
-	fprintf(stderr, "Usage: ... cgroup [ match EMATCH_TREE ] [ police POLICE_SPEC ]\n");
-	fprintf(stderr, "                 [ action ACTION_SPEC ]\n");
+	fprintf(stderr, "Usage: ... cgroup [ match EMATCH_TREE ]\n");
+	fprintf(stderr, "                  [ action ACTION_SPEC ]\n");
 }
 
 static int cgroup_parse_opt(struct filter_util *qu, char *handle,
diff --git a/tc/f_flow.c b/tc/f_flow.c
index 7d4bb7a..f398f55 100644
--- a/tc/f_flow.c
+++ b/tc/f_flow.c
@@ -1,10 +1,10 @@
 /*
  * f_flow.c		Flow filter
  *
- * 		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 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.
  *
  * Authors:	Patrick McHardy <kaber@trash.net>
  */
@@ -27,7 +27,7 @@
 " [hashing mode]: hash keys KEY-LIST ... [ perturb SECS ]\n"
 "\n"
 "                 [ divisor NUM ] [ baseclass ID ] [ match EMATCH_TREE ]\n"
-"                 [ police POLICE_SPEC ] [ action ACTION_SPEC ]\n"
+"                 [ action ACTION_SPEC ]\n"
 "\n"
 "KEY-LIST := [ KEY-LIST , ] KEY\n"
 "KEY      := [ src | dst | proto | proto-src | proto-dst | iif | priority | \n"
diff --git a/tc/f_fw.c b/tc/f_fw.c
index 161e2f7..165f489 100644
--- a/tc/f_fw.c
+++ b/tc/f_fw.c
@@ -25,8 +25,8 @@
 
 static void explain(void)
 {
-	fprintf(stderr, "Usage: ... fw [ classid CLASSID ] [ police POLICE_SPEC ]\n");
-	fprintf(stderr, "       POLICE_SPEC := ... look at TBF\n");
+	fprintf(stderr, "Usage: ... fw [ classid CLASSID ] [ action ACTION_SPEC ]\n");
+	fprintf(stderr, "       ACTION_SPEC := ... look at individual actions\n");
 	fprintf(stderr, "       CLASSID := X:Y\n");
 	fprintf(stderr, "\nNOTE: CLASSID is parsed as hexadecimal input.\n");
 }
diff --git a/tc/f_route.c b/tc/f_route.c
index 649e0ec..23c4ecc 100644
--- a/tc/f_route.c
+++ b/tc/f_route.c
@@ -28,8 +28,8 @@
 static void explain(void)
 {
 	fprintf(stderr, "Usage: ... route [ from REALM | fromif TAG ] [ to REALM ]\n");
-	fprintf(stderr, "                [ flowid CLASSID ] [ police POLICE_SPEC ]\n");
-	fprintf(stderr, "       POLICE_SPEC := ... look at TBF\n");
+	fprintf(stderr, "                [ flowid CLASSID ] [ action ACTION_SPEC ]]\n");
+	fprintf(stderr, "       ACTION_SPEC := ... look at individual actions\n");
 	fprintf(stderr, "       CLASSID := X:Y\n");
 	fprintf(stderr, "\nNOTE: CLASSID is parsed as hexadecimal input.\n");
 }
@@ -105,6 +105,13 @@
 				return -1;
 			}
 			continue;
+		} else if (matches(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (parse_action(&argc, &argv, TCA_ROUTE4_ACT, n)) {
+				fprintf(stderr, "Illegal \"action\"\n");
+				return -1;
+			}
+			continue;
 		} else if (matches(*argv, "order") == 0) {
 			NEXT_ARG();
 			if (get_u32(&order, *argv, 0)) {
@@ -158,6 +165,8 @@
 		fprintf(f, "fromif %s", ll_index_to_name(*(int*)RTA_DATA(tb[TCA_ROUTE4_IIF])));
 	if (tb[TCA_ROUTE4_POLICE])
 		tc_print_police(f, tb[TCA_ROUTE4_POLICE]);
+	if (tb[TCA_ROUTE4_ACT])
+		tc_print_action(f, tb[TCA_ROUTE4_ACT]);
 	return 0;
 }
 
diff --git a/tc/f_rsvp.c b/tc/f_rsvp.c
index 8eaf85d..cb7b8fb 100644
--- a/tc/f_rsvp.c
+++ b/tc/f_rsvp.c
@@ -28,16 +28,16 @@
 {
 	fprintf(stderr, "Usage: ... rsvp ipproto PROTOCOL session DST[/PORT | GPI ]\n");
 	fprintf(stderr, "                [ sender SRC[/PORT | GPI ]\n");
-	fprintf(stderr, "                [ classid CLASSID ] [ police POLICE_SPEC ]\n");
+	fprintf(stderr, "                [ classid CLASSID ] [ action ACTION_SPEC ]\n");
 	fprintf(stderr, "                [ tunnelid ID ] [ tunnel ID skip NUMBER ]\n");
 	fprintf(stderr, "Where: GPI := { flowlabel NUMBER | spi/ah SPI | spi/esp SPI |\n");
 	fprintf(stderr, "                u{8|16|32} NUMBER mask MASK at OFFSET}\n");
-	fprintf(stderr, "       POLICE_SPEC := ... look at TBF\n");
+	fprintf(stderr, "       ACTION_SPEC := ... look at individual actions\n");
 	fprintf(stderr, "       FILTERID := X:Y\n");
 	fprintf(stderr, "\nNOTE: CLASSID is parsed as hexadecimal input.\n");
 }
 
-int get_addr_and_pi(int *argc_p, char ***argv_p, inet_prefix * addr,
+static int get_addr_and_pi(int *argc_p, char ***argv_p, inet_prefix * addr,
 		    struct tc_rsvp_pinfo *pinfo, int dir, int family)
 {
 	int argc = *argc_p;
@@ -261,6 +261,13 @@
 			}
 			pinfo.tunnelhdr = tid;
 			pinfo_ok++;
+		} else if (matches(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (parse_action(&argc, &argv, TCA_RSVP_ACT, n)) {
+				fprintf(stderr, "Illegal \"action\"\n");
+				return -1;
+			}
+			continue;
 		} else if (matches(*argv, "police") == 0) {
 			NEXT_ARG();
 			if (parse_police(&argc, &argv, TCA_RSVP_POLICE, n)) {
@@ -385,6 +392,10 @@
 		SPRINT_BUF(b2);
 		fprintf(f, "sender [NONE]%s ", sprint_spi(&pinfo->spi, 0, b2));
 	}
+
+	if (tb[TCA_RSVP_ACT]) {
+		tc_print_action(f, tb[TCA_RSVP_ACT]);
+	}
 	if (tb[TCA_RSVP_POLICE])
 		tc_print_police(f, tb[TCA_RSVP_POLICE]);
 	return 0;
diff --git a/tc/f_tcindex.c b/tc/f_tcindex.c
index 590c0a6..b1847c8 100644
--- a/tc/f_tcindex.c
+++ b/tc/f_tcindex.c
@@ -21,7 +21,7 @@
 	    " [ shift SHIFT ]\n");
 	fprintf(stderr,"                    [ pass_on | fall_through ]\n");
 	fprintf(stderr,"                    [ classid CLASSID ] "
-	    "[ police POLICE_SPEC ]\n");
+	    "[ action ACTION_SPEC ]\n");
 }
 
 static int tcindex_parse_opt(struct filter_util *qu, char *handle, int argc,
@@ -106,6 +106,14 @@
 			}
 			continue;
 		}
+		else if (!strcmp(*argv,"action")) {
+			NEXT_ARG();
+			if (parse_police(&argc, &argv, TCA_TCINDEX_ACT, n)) {
+				fprintf(stderr, "Illegal \"action\"\n");
+				return -1;
+			}
+			continue;
+		}
 		else {
 			explain();
 			return -1;
@@ -171,6 +179,10 @@
 		fprintf(f, "\n");
 		tc_print_police(f, tb[TCA_TCINDEX_POLICE]);
 	}
+	if (tb[TCA_TCINDEX_ACT]) {
+		fprintf(f, "\n");
+		tc_print_police(f, tb[TCA_TCINDEX_ACT]);
+	}
 	return 0;
 }
 
diff --git a/tc/f_u32.c b/tc/f_u32.c
index 975c0b5..cb63869 100644
--- a/tc/f_u32.c
+++ b/tc/f_u32.c
@@ -32,7 +32,7 @@
 {
 	fprintf(stderr, "Usage: ... u32 [ match SELECTOR ... ] [ link HTID ]"
 		" [ classid CLASSID ]\n");
-	fprintf(stderr, "               [ police POLICE_SPEC ]"
+	fprintf(stderr, "               [ action ACTION_SPEC ]"
 		" [ offset OFFSET_SPEC ]\n");
 	fprintf(stderr, "               [ ht HTID ] [ hashkey HASHKEY_SPEC ]\n");
 	fprintf(stderr, "               [ sample SAMPLE ]\n");
@@ -45,7 +45,7 @@
 	fprintf(stderr, "\nNOTE: CLASSID is parsed at hexadecimal input.\n");
 }
 
-int get_u32_handle(__u32 *handle, const char *str)
+static int get_u32_handle(__u32 *handle, const char *str)
 {
 	__u32 htid=0, hash=0, nodeid=0;
 	char *tmp = strchr(str, ':');
@@ -80,7 +80,7 @@
 	return 0;
 }
 
-char * sprint_u32_handle(__u32 handle, char *buf)
+static char * sprint_u32_handle(__u32 handle, char *buf)
 {
 	int bsize = SPRINT_BSIZE-1;
 	__u32 htid = TC_U32_HTID(handle);
@@ -194,7 +194,7 @@
 }
 
 
-int parse_at(int *argc_p, char ***argv_p, int *off, int *offmask)
+static int parse_at(int *argc_p, char ***argv_p, int *off, int *offmask)
 {
 	int argc = *argc_p;
 	char **argv = *argv_p;
@@ -513,7 +513,7 @@
 		res = pack_key16(sel, 0, 0x3FFF, 6, 0);
 	} else if (strcmp(*argv, "firstfrag") == 0) {
 		argc--; argv++;
-		res = pack_key16(sel, 0, 0x1FFF, 6, 0);
+		res = pack_key16(sel, 0x2000, 0x3FFF, 6, 0);
 	} else if (strcmp(*argv, "df") == 0) {
 		argc--; argv++;
 		res = pack_key16(sel, 0x4000, 0x4000, 6, 0);
@@ -531,7 +531,7 @@
 		res = parse_u8(&argc, &argv, sel, 20, 0);
 	} else if (strcmp(*argv, "icmp_code") == 0) {
 		NEXT_ARG();
-		res = parse_u8(&argc, &argv, sel, 20, 1);
+		res = parse_u8(&argc, &argv, sel, 21, 0);
 	} else
 		return -1;
 
@@ -539,7 +539,7 @@
 	*argv_p = argv;
 	return res;
 }
-				
+
 static int parse_ip6(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
 {
 	int res = -1;
@@ -732,7 +732,7 @@
 	} else if (matches(*argv, "ether") == 0) {
 		NEXT_ARG();
 		res = parse_ether(&argc, &argv, sel);
-	} else 
+	} else
 		return -1;
 
 	*argc_p = argc;
@@ -847,7 +847,7 @@
 	case 16: {
 			int bits = mask2bits(key->mask);
 			if (bits >= 0) {
-				fprintf(f, "\n  %s %s/%d", 
+				fprintf(f, "\n  %s %s/%d",
 					key->off == 12 ? "match IP src" : "match IP dst",
 					inet_ntop(AF_INET, &key->val,
 						  abuf, sizeof(abuf)),
@@ -903,7 +903,7 @@
 	case 16: {
 			int bits = mask2bits(key->mask);
 			if (bits >= 0) {
-				fprintf(f, "\n  %s %s/%d", 
+				fprintf(f, "\n  %s %s/%d",
 					key->off == 12 ? "match IP src" : "match IP dst",
 					inet_ntop(AF_INET, &key->val,
 						  abuf, sizeof(abuf)),
@@ -936,7 +936,7 @@
 
 static void print_raw(FILE *f, const struct tc_u32_key *key)
 {
-	fprintf(f, "\n  match %08x/%08x at %s%d", 
+	fprintf(f, "\n  match %08x/%08x at %s%d",
 		(unsigned int)ntohl(key->val),
 		(unsigned int)ntohl(key->mask),
 		key->offmask ? "nexthdr+" : "",
@@ -1152,7 +1152,7 @@
 	/* We dont necessarily need class/flowids */
 	if (terminal_ok)
 		sel.sel.flags |= TC_U32_TERMINAL;
-	
+
 	if (order) {
 		if (TC_U32_NODE(t->tcm_handle) && order != TC_U32_NODE(t->tcm_handle)) {
 			fprintf(stderr, "\"order\" contradicts \"handle\"\n");
@@ -1164,7 +1164,7 @@
 	if (htid)
 		addattr_l(n, MAX_MSG, TCA_U32_HASH, &htid, 4);
 	if (sel_ok)
-		addattr_l(n, MAX_MSG, TCA_U32_SEL, &sel, 
+		addattr_l(n, MAX_MSG, TCA_U32_SEL, &sel,
 			  sizeof(sel.sel)+sel.sel.nkeys*sizeof(struct tc_u32_key));
 	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
 	return 0;
diff --git a/tc/m_action.c b/tc/m_action.c
index b1dd26d..63db8fe 100644
--- a/tc/m_action.c
+++ b/tc/m_action.c
@@ -37,10 +37,9 @@
 #ifdef CONFIG_GACT
 int gact_ld = 0 ; //fuckin backward compatibility
 #endif
-int batch_c = 0;
 int tab_flush = 0;
 
-void act_usage(void)
+static void act_usage(void)
 {
 	/*XXX: In the near future add a action->print_help to improve
 	 * usability
@@ -88,7 +87,7 @@
 	return -1;
 }
 
-struct action_util *get_action_kind(char *str)
+static struct action_util *get_action_kind(char *str)
 {
 #ifdef ANDROID
 	if (!strcmp(str, "mirred")) {
@@ -151,12 +150,13 @@
 	return a;
 }
 
-int
+static int
 new_cmd(char **argv)
 {
 	if ((matches(*argv, "change") == 0) ||
 		(matches(*argv, "replace") == 0)||
 		(matches(*argv, "delete") == 0)||
+		(matches(*argv, "get") == 0)||
 		(matches(*argv, "add") == 0))
 			return 1;
 
@@ -198,6 +198,10 @@
 			}
 #endif
 			continue;
+		} else if (strcmp(*argv, "flowid") == 0) {
+			break;
+		} else if (strcmp(*argv, "classid") == 0) {
+			break;
 		} else if (strcmp(*argv, "help") == 0) {
 			return -1;
 		} else if (new_cmd(argv)) {
@@ -254,7 +258,7 @@
 	return -1;
 }
 
-int
+static int
 tc_print_one_action(FILE * f, struct rtattr *arg)
 {
 
@@ -266,6 +270,7 @@
 		return -1;
 
 	parse_rtattr_nested(tb, TCA_ACT_MAX, arg);
+
 	if (tb[TCA_ACT_KIND] == NULL) {
 		fprintf(stderr, "NULL Action!\n");
 		return -1;
@@ -276,14 +281,7 @@
 	if (NULL == a)
 		return err;
 
-	if (tab_flush) {
-		fprintf(f," %s \n", a->id);
-		tab_flush = 0;
-		return 0;
-	}
-
-	err = a->print_aopt(a,f,tb[TCA_ACT_OPTIONS]);
-
+	err = a->print_aopt(a, f, tb[TCA_ACT_OPTIONS]);
 
 	if (0 > err)
 		return err;
@@ -297,8 +295,34 @@
 	return 0;
 }
 
+static int
+tc_print_action_flush(FILE *f, const struct rtattr *arg)
+{
+
+	struct rtattr *tb[TCA_MAX + 1];
+	int err = 0;
+	struct action_util *a = NULL;
+	__u32 *delete_count = 0;
+
+	parse_rtattr_nested(tb, TCA_MAX, arg);
+
+	if (tb[TCA_KIND] == NULL) {
+		fprintf(stderr, "NULL Action!\n");
+		return -1;
+	}
+
+	a = get_action_kind(RTA_DATA(tb[TCA_KIND]));
+	if (NULL == a)
+		return err;
+
+	delete_count = RTA_DATA(tb[TCA_FCNT]);
+	fprintf(f," %s (%d entries)\n", a->id, *delete_count);
+	tab_flush = 0;
+	return 0;
+}
+
 int
-tc_print_action(FILE * f, const struct rtattr *arg)
+tc_print_action(FILE *f, const struct rtattr *arg)
 {
 
 	int i;
@@ -309,14 +333,12 @@
 
 	parse_rtattr_nested(tb, TCA_ACT_MAX_PRIO, arg);
 
-	if (tab_flush && NULL != tb[0]  && NULL == tb[1]) {
-		int ret = tc_print_one_action(f, tb[0]);
-		return ret;
-	}
+	if (tab_flush && NULL != tb[0]  && NULL == tb[1])
+		return tc_print_action_flush(f, tb[0]);
 
 	for (i = 0; i < TCA_ACT_MAX_PRIO; i++) {
 		if (tb[i]) {
-			fprintf(f, "\n\taction order %d: ", i + batch_c);
+			fprintf(f, "\n\taction order %d: ", i);
 			if (0 > tc_print_one_action(f, tb[i])) {
 				fprintf(f, "Error printing action\n");
 			}
@@ -324,7 +346,6 @@
 
 	}
 
-	batch_c+=TCA_ACT_MAX_PRIO ;
 	return 0;
 }
 
@@ -368,7 +389,7 @@
 	return 0;
 }
 
-int tc_action_gd(int cmd, unsigned flags, int *argc_p, char ***argv_p)
+static int tc_action_gd(int cmd, unsigned flags, int *argc_p, char ***argv_p)
 {
 	char k[16];
 	struct action_util *a = NULL;
@@ -480,7 +501,7 @@
 	return ret;
 }
 
-int tc_action_modify(int cmd, unsigned flags, int *argc_p, char ***argv_p)
+static int tc_action_modify(int cmd, unsigned flags, int *argc_p, char ***argv_p)
 {
 	int argc = *argc_p;
 	char **argv = *argv_p;
@@ -520,7 +541,7 @@
 	return ret;
 }
 
-int tc_act_list_or_flush(int argc, char **argv, int event)
+static int tc_act_list_or_flush(int argc, char **argv, int event)
 {
 	int ret = 0, prio = 0, msg_size = 0;
 	char k[16];
diff --git a/tc/m_bpf.c b/tc/m_bpf.c
new file mode 100644
index 0000000..bc6cc47
--- /dev/null
+++ b/tc/m_bpf.c
@@ -0,0 +1,186 @@
+/*
+ * m_bpf.c	BFP based action module
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <linux/tc_act/tc_bpf.h>
+
+#include "utils.h"
+#include "rt_names.h"
+#include "tc_util.h"
+#include "tc_bpf.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... bpf ...\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, " [inline]:     run bytecode BPF_BYTECODE\n");
+	fprintf(stderr, " [from file]:  run bytecode-file FILE\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, "Where BPF_BYTECODE := \'s,c t f k,c t f k,c t f k,...\'\n");
+	fprintf(stderr, "      c,t,f,k and s are decimals; s denotes number of 4-tuples\n");
+	fprintf(stderr, "Where FILE points to a file containing the BPF_BYTECODE string\n");
+}
+
+static void usage(void)
+{
+	explain();
+	exit(-1);
+}
+
+static int parse_bpf(struct action_util *a, int *argc_p, char ***argv_p,
+		     int tca_id, struct nlmsghdr *n)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	struct rtattr *tail;
+	struct tc_act_bpf parm = { 0 };
+	struct sock_filter bpf_ops[BPF_MAXINSNS];
+	__u16 bpf_len = 0;
+
+	if (matches(*argv, "bpf") != 0)
+		return -1;
+
+	NEXT_ARG();
+
+	while (argc > 0) {
+		if (matches(*argv, "run") == 0) {
+			bool from_file;
+			int ret;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "bytecode-file") == 0) {
+				from_file = true;
+			} else if (strcmp(*argv, "bytecode") == 0) {
+				from_file = false;
+			} else {
+				fprintf(stderr, "unexpected \"%s\"\n", *argv);
+				explain();
+				return -1;
+			}
+			NEXT_ARG();
+			ret = bpf_parse_ops(argc, argv, bpf_ops, from_file);
+			if (ret < 0) {
+				fprintf(stderr, "Illegal \"bytecode\"\n");
+				return -1;
+			}
+			bpf_len = ret;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			break;
+		}
+		argc--;
+		argv++;
+	}
+
+	parm.action = TC_ACT_PIPE;
+	if (argc) {
+		if (matches(*argv, "reclassify") == 0) {
+			parm.action = TC_ACT_RECLASSIFY;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "pipe") == 0) {
+			parm.action = TC_ACT_PIPE;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "drop") == 0 ||
+			   matches(*argv, "shot") == 0) {
+			parm.action = TC_ACT_SHOT;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "continue") == 0) {
+			parm.action = TC_ACT_UNSPEC;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "pass") == 0) {
+			parm.action = TC_ACT_OK;
+			argc--;
+			argv++;
+		}
+	}
+
+	if (argc) {
+		if (matches(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&parm.index, *argv, 10)) {
+				fprintf(stderr, "bpf: Illegal \"index\"\n");
+				return -1;
+			}
+			argc--;
+			argv++;
+		}
+	}
+
+	if (!bpf_len) {
+		fprintf(stderr, "bpf: Bytecode needs to be passed\n");
+		explain();
+		return -1;
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	addattr_l(n, MAX_MSG, TCA_ACT_BPF_PARMS, &parm, sizeof(parm));
+	addattr16(n, MAX_MSG, TCA_ACT_BPF_OPS_LEN, bpf_len);
+	addattr_l(n, MAX_MSG, TCA_ACT_BPF_OPS, &bpf_ops,
+		  bpf_len * sizeof(struct sock_filter));
+	tail->rta_len = (char *)NLMSG_TAIL(n) - (char *)tail;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+static int print_bpf(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+	struct rtattr *tb[TCA_ACT_BPF_MAX + 1];
+	struct tc_act_bpf *parm;
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, TCA_ACT_BPF_MAX, arg);
+
+	if (!tb[TCA_ACT_BPF_PARMS]) {
+		fprintf(f, "[NULL bpf parameters]");
+		return -1;
+	}
+	parm = RTA_DATA(tb[TCA_ACT_BPF_PARMS]);
+
+	fprintf(f, " bpf ");
+
+	if (tb[TCA_ACT_BPF_OPS] && tb[TCA_ACT_BPF_OPS_LEN])
+		bpf_print_ops(f, tb[TCA_ACT_BPF_OPS],
+			      rta_getattr_u16(tb[TCA_ACT_BPF_OPS_LEN]));
+
+	fprintf(f, "\n\tindex %d ref %d bind %d", parm->index, parm->refcnt,
+		parm->bindcnt);
+
+	if (show_stats) {
+		if (tb[TCA_ACT_BPF_TM]) {
+			struct tcf_t *tm = RTA_DATA(tb[TCA_ACT_BPF_TM]);
+			print_tm(f, tm);
+		}
+	}
+
+	fprintf(f, "\n ");
+
+	return 0;
+}
+
+struct action_util bpf_action_util = {
+	.id = "bpf",
+	.parse_aopt = parse_bpf,
+	.print_aopt = print_bpf,
+};
diff --git a/tc/m_ematch.h b/tc/m_ematch.h
index 5036e9b..81456aa 100644
--- a/tc/m_ematch.h
+++ b/tc/m_ematch.h
@@ -4,6 +4,7 @@
 #include <ctype.h>
 #include <stdlib.h>
 #include <string.h>
+#include <limits.h>
 
 #include "utils.h"
 #include "tc_util.h"
diff --git a/tc/m_estimator.c b/tc/m_estimator.c
index a9e5dbc..3dc8624 100644
--- a/tc/m_estimator.c
+++ b/tc/m_estimator.c
@@ -22,6 +22,7 @@
 
 #include "utils.h"
 #include "tc_util.h"
+#include "tc_common.h"
 
 static void est_help(void);
 
@@ -31,7 +32,6 @@
 	fprintf(stderr, "  INTERVAL is interval between measurements\n");
 	fprintf(stderr, "  TIME-CONST is averaging time constant\n");
 	fprintf(stderr, "Example: ... est 1sec 8sec\n");
-	return;
 }
 
 int parse_estimator(int *p_argc, char ***p_argv, struct tc_estimator *est)
diff --git a/tc/m_gact.c b/tc/m_gact.c
index 9f07851..94bd5e7 100644
--- a/tc/m_gact.c
+++ b/tc/m_gact.c
@@ -68,7 +68,7 @@
 	exit(-1);
 }
 
-int
+static int
 get_act(char ***argv_p)
 {
 	char **argv = *argv_p;
@@ -89,8 +89,9 @@
 	}
 }
 
-int
-parse_gact(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+static int
+parse_gact(struct action_util *a, int *argc_p, char ***argv_p,
+	   int tca_id, struct nlmsghdr *n)
 {
 	int argc = *argc_p;
 	char **argv = *argv_p;
@@ -202,7 +203,7 @@
 	return 0;
 }
 
-int
+static int
 print_gact(struct action_util *au,FILE * f, struct rtattr *arg)
 {
 	SPRINT_BUF(b1);
diff --git a/tc/m_ipt.c b/tc/m_ipt.c
index dc2dedc..e5c4897 100644
--- a/tc/m_ipt.c
+++ b/tc/m_ipt.c
@@ -1,6 +1,6 @@
 /*
  * m_ipt.c	iptables based targets
- * 		utilities mostly ripped from iptables <duh, its the linux way>
+ *		utilities mostly ripped from iptables <duh, its the linux way>
  *
  *		This program is free software; you can distribute it and/or
  *		modify it under the terms of the GNU General Public License
@@ -415,7 +415,7 @@
 	}
 
 	if (argc <= 2) {
-		fprintf(stderr,"bad arguements to ipt %d vs %d \n", argc, rargc);
+		fprintf(stderr,"bad arguments to ipt %d vs %d \n", argc, rargc);
 		return -1;
 	}
 
diff --git a/tc/m_mirred.c b/tc/m_mirred.c
index 0d771bc..dc231d7 100644
--- a/tc/m_mirred.c
+++ b/tc/m_mirred.c
@@ -45,7 +45,7 @@
 	exit(-1);
 }
 
-char *mirred_n2a(int action)
+static const char *mirred_n2a(int action)
 {
 	switch (action) {
 	case TCA_EGRESS_REDIR:
@@ -61,8 +61,9 @@
 	}
 }
 
-int
-parse_egress(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+static int
+parse_egress(struct action_util *a, int *argc_p, char ***argv_p,
+	     int tca_id, struct nlmsghdr *n)
 {
 
 	int argc = *argc_p;
@@ -104,7 +105,7 @@
 			} else if (!mirror && matches(*argv, "mirror") == 0) {
 				mirror=1;
 				if (redir) {
-					fprintf(stderr, "Cant have both mirror and redir\n");
+					fprintf(stderr, "Can't have both mirror and redir\n");
 					return -1;
 				}
 				p.eaction = TCA_EGRESS_MIRROR;
@@ -113,7 +114,7 @@
 			} else if (!redir && matches(*argv, "redirect") == 0) {
 				redir=1;
 				if (mirror) {
-					fprintf(stderr, "Cant have both mirror and redir\n");
+					fprintf(stderr, "Can't have both mirror and redir\n");
 					return -1;
 				}
 				p.eaction = TCA_EGRESS_REDIR;
@@ -205,22 +206,23 @@
 }
 
 
-int
-parse_mirred(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+static int
+parse_mirred(struct action_util *a, int *argc_p, char ***argv_p,
+	     int tca_id, struct nlmsghdr *n)
 {
 
 	int argc = *argc_p;
 	char **argv = *argv_p;
 
 	if (argc < 0) {
-		fprintf(stderr,"mirred bad arguement count %d\n", argc);
+		fprintf(stderr,"mirred bad argument count %d\n", argc);
 		return -1;
 	}
 
 	if (matches(*argv, "mirred") == 0) {
 		NEXT_ARG();
 	} else {
-		fprintf(stderr,"mirred bad arguement %s\n", *argv);
+		fprintf(stderr,"mirred bad argument %s\n", *argv);
 		return -1;
 	}
 
@@ -245,7 +247,7 @@
 
 }
 
-int
+static int
 print_mirred(struct action_util *au,FILE * f, struct rtattr *arg)
 {
 	struct tc_mirred *p;
diff --git a/tc/m_nat.c b/tc/m_nat.c
index 01ec032..d502a81 100644
--- a/tc/m_nat.c
+++ b/tc/m_nat.c
@@ -146,7 +146,7 @@
 		if (matches(*argv, "index") == 0) {
 			NEXT_ARG();
 			if (get_u32(&sel.index, *argv, 10)) {
-				fprintf(stderr, "Pedit: Illegal \"index\"\n");
+				fprintf(stderr, "Nat: Illegal \"index\"\n");
 				return -1;
 			}
 			argc--;
diff --git a/tc/m_pedit.c b/tc/m_pedit.c
index 7499846..dfe9b2e 100644
--- a/tc/m_pedit.c
+++ b/tc/m_pedit.c
@@ -9,9 +9,9 @@
  * Authors:  J Hadi Salim (hadi@cyberus.ca)
  *
  * TODO:
- * 	1) Big endian broken in some spots
- * 	2) A lot of this stuff was added on the fly; get a big double-double
- * 	and clean it up at some point.
+ *	1) Big endian broken in some spots
+ *	2) A lot of this stuff was added on the fly; get a big double-double
+ *	and clean it up at some point.
  *
  */
 
@@ -30,7 +30,7 @@
 #include "m_pedit.h"
 
 static struct m_pedit_util *pedit_list;
-int pedit_debug = 1;
+static int pedit_debug;
 
 static void
 explain(void)
@@ -73,8 +73,7 @@
 
 }
 
-struct m_pedit_util
-*get_pedit_kind(char *str)
+static struct m_pedit_util *get_pedit_kind(const char *str)
 {
 	static void *pBODY;
 	void *dlh;
@@ -411,7 +410,7 @@
 	return res;
 }
 
-int
+static int
 parse_munge(int *argc_p, char ***argv_p,struct tc_pedit_sel *sel)
 {
 	struct tc_pedit_key tkey;
diff --git a/tc/m_police.c b/tc/m_police.c
index c3869b6..915f1a5 100644
--- a/tc/m_police.c
+++ b/tc/m_police.c
@@ -38,7 +38,6 @@
 	fprintf(stderr, "                [ peakrate BPS ] [ avrate BPS ] [ overhead BYTES ]\n");
 	fprintf(stderr, "                [ linklayer TYPE ] [ ACTIONTERM ]\n");
 
-	fprintf(stderr, "Old Syntax ACTIONTERM := action <EXCEEDACT>[/NOTEXCEEDACT] \n");
 	fprintf(stderr, "New Syntax ACTIONTERM := conform-exceed <EXCEEDACT>[/NOTEXCEEDACT] \n");
 	fprintf(stderr, "Where: *EXCEEDACT := pipe | ok | reclassify | drop | continue \n");
 	fprintf(stderr, "Where:  pipe is only valid for new syntax \n");
@@ -50,7 +49,7 @@
 	fprintf(stderr, "Illegal \"%s\"\n", arg);
 }
 
-char *police_action_n2a(int action, char *buf, int len)
+static const char *police_action_n2a(int action, char *buf, int len)
 {
 	switch (action) {
 	case -1:
@@ -72,7 +71,7 @@
 	}
 }
 
-int police_action_a2n(char *arg, int *result)
+static int police_action_a2n(const char *arg, int *result)
 {
 	int res;
 
@@ -100,7 +99,7 @@
 }
 
 
-int get_police_result(int *action, int *result, char *arg)
+static int get_police_result(int *action, int *result, char *arg)
 {
 	char *p = strchr(arg, '/');
 
@@ -230,8 +229,7 @@
 			p.action = TC_POLICE_OK;
 		} else if (matches(*argv, "pipe") == 0) {
 			p.action = TC_POLICE_PIPE;
-		} else if (strcmp(*argv, "action") == 0 ||
-			   strcmp(*argv, "conform-exceed") == 0) {
+		} else if (strcmp(*argv, "conform-exceed") == 0) {
 			NEXT_ARG();
 			if (get_police_result(&p.action, &presult, *argv)) {
 				fprintf(stderr, "Illegal \"action\"\n");
@@ -322,9 +320,11 @@
 print_police(struct action_util *a, FILE *f, struct rtattr *arg)
 {
 	SPRINT_BUF(b1);
+	SPRINT_BUF(b2);
 	struct tc_police *p;
 	struct rtattr *tb[TCA_POLICE_MAX+1];
 	unsigned buffer;
+	unsigned int linklayer;
 
 	if (arg == NULL)
 		return 0;
@@ -360,6 +360,9 @@
 	} else
 		fprintf(f, " ");
 	fprintf(f, "overhead %ub ", p->rate.overhead);
+	linklayer = (p->rate.linklayer & TC_LINKLAYER_MASK);
+	if (linklayer > TC_LINKLAYER_ETHERNET || show_details)
+		fprintf(f, "linklayer %s ", sprint_linklayer(linklayer, b2));
 	fprintf(f, "\nref %d bind %d\n",p->refcnt, p->bindcnt);
 
 	return 0;
diff --git a/tc/m_simple.c b/tc/m_simple.c
new file mode 100644
index 0000000..866552f
--- /dev/null
+++ b/tc/m_simple.c
@@ -0,0 +1,202 @@
+/*
+ * m_simple.c	simple action
+ *
+ *		This program is free software; you can distribute 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.
+ *
+ * Authors:	J Hadi Salim <jhs@mojatatu.com>
+ *
+ * Pedagogical example. Adds a string that will be printed every time
+ * the simple instance is hit.
+ * Use this as a skeleton action and keep modifying it to meet your needs.
+ * Look at linux/tc_act/tc_defact.h for the different components ids and
+ * definitions used in  this actions
+ *
+ * example use, yell "Incoming ICMP!" every time you see an incoming ICMP on
+ * eth0. Steps are:
+ * 1) Add an ingress qdisc point to eth0
+ * 2) Start a chain on ingress of eth0 that first matches ICMP then invokes
+ *    the simple action to shout.
+ * 3) display stats and show that no packet has been seen by the action
+ * 4) Send one ping packet to google (expect to receive a response back)
+ * 5) grep the logs to see the logged message
+ * 6) display stats again and observe increment by 1
+ *
+  hadi@noma1:$ tc qdisc add dev eth0 ingress
+  hadi@noma1:$tc filter add dev eth0 parent ffff: protocol ip prio 5 \
+	 u32 match ip protocol 1 0xff flowid 1:1 action simple "Incoming ICMP"
+
+  hadi@noma1:$ sudo tc -s filter ls  dev eth0 parent ffff:
+   filter protocol ip pref 5 u32
+   filter protocol ip pref 5 u32 fh 800: ht divisor 1
+   filter protocol ip pref 5 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1
+     match 00010000/00ff0000 at 8
+	action order 1: Simple <Incoming ICMP>
+	 index 4 ref 1 bind 1 installed 29 sec used 29 sec
+	 Action statistics:
+		Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
+		backlog 0b 0p requeues 0
+
+
+  hadi@noma1$ ping -c 1 www.google.ca
+  PING www.google.ca (74.125.225.120) 56(84) bytes of data.
+  64 bytes from ord08s08-in-f24.1e100.net (74.125.225.120): icmp_req=1 ttl=53 time=31.3 ms
+
+  --- www.google.ca ping statistics ---
+  1 packets transmitted, 1 received, 0% packet loss, time 0ms
+  rtt min/avg/max/mdev = 31.316/31.316/31.316/0.000 ms
+
+  hadi@noma1$ dmesg | grep simple
+  [135354.473951] simple: Incoming ICMP_1
+
+  hadi@noma1$ sudo tc/tc -s filter ls  dev eth0 parent ffff:
+  filter protocol ip pref 5 u32
+  filter protocol ip pref 5 u32 fh 800: ht divisor 1
+  filter protocol ip pref 5 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1
+    match 00010000/00ff0000 at 8
+	action order 1: Simple <Incoming ICMP>
+	 index 4 ref 1 bind 1 installed 206 sec used 67 sec
+	Action statistics:
+	Sent 84 bytes 1 pkt (dropped 0, overlimits 0 requeues 0)
+	backlog 0b 0p requeues 0
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_defact.h>
+
+#ifndef SIMP_MAX_DATA
+#define SIMP_MAX_DATA   32
+#endif
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... simple STRING\n"
+		"STRING being an arbitrary string\n"
+		"example: \"simple blah\"\n");
+}
+
+static void usage(void)
+{
+	explain();
+	exit(-1);
+}
+
+static int
+parse_simple(struct action_util *a, int *argc_p, char ***argv_p, int tca_id,
+	     struct nlmsghdr *n)
+{
+	struct tc_defact sel = {};
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int ok = 0;
+	struct rtattr *tail;
+	char *simpdata = NULL;
+
+
+	while (argc > 0) {
+		if (matches(*argv, "simple") == 0) {
+			NEXT_ARG();
+			simpdata = *argv;
+			ok = 1;
+			argc--;
+			argv++;
+			break;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			break;
+		}
+
+	}
+
+	if (!ok) {
+		explain();
+		return -1;
+	}
+
+	if (argc) {
+		if (matches(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&sel.index, *argv, 10)) {
+				fprintf(stderr, "simple: Illegal \"index\"\n");
+				return -1;
+			}
+			argc--;
+			argv++;
+		}
+	}
+
+	if (strlen(simpdata) > (SIMP_MAX_DATA - 1)) {
+		fprintf(stderr, "simple: Illegal string len %ld <%s> \n",
+			strlen(simpdata), simpdata);
+		return -1;
+	}
+
+	sel.action = TC_ACT_PIPE;
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	addattr_l(n, MAX_MSG, TCA_DEF_PARMS, &sel, sizeof(sel));
+	addattr_l(n, MAX_MSG, TCA_DEF_DATA, simpdata, SIMP_MAX_DATA);
+	tail->rta_len = (char *)NLMSG_TAIL(n) - (char *)tail;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+static int print_simple(struct action_util *au, FILE * f, struct rtattr *arg)
+{
+	struct tc_defact *sel;
+	struct rtattr *tb[TCA_DEF_MAX + 1];
+	char *simpdata;
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, TCA_DEF_MAX, arg);
+
+	if (tb[TCA_DEF_PARMS] == NULL) {
+		fprintf(f, "[NULL simple parameters]");
+		return -1;
+	}
+	sel = RTA_DATA(tb[TCA_DEF_PARMS]);
+
+	if (tb[TCA_DEF_DATA] == NULL) {
+		fprintf(f, "[missing simple string]");
+		return -1;
+	}
+
+	simpdata = RTA_DATA(tb[TCA_DEF_DATA]);
+
+	fprintf(f, "Simple <%s>\n", simpdata);
+	fprintf(f, "\t index %d ref %d bind %d", sel->index,
+		sel->refcnt, sel->bindcnt);
+
+	if (show_stats) {
+		if (tb[TCA_DEF_TM]) {
+			struct tcf_t *tm = RTA_DATA(tb[TCA_DEF_TM]);
+			print_tm(f, tm);
+		}
+	}
+	fprintf(f, "\n");
+
+	return 0;
+}
+
+struct action_util simple_action_util = {
+	.id = "simple",
+	.parse_aopt = parse_simple,
+	.print_aopt = print_simple,
+};
diff --git a/tc/m_skbedit.c b/tc/m_skbedit.c
index c7eef44..36323a9 100644
--- a/tc/m_skbedit.c
+++ b/tc/m_skbedit.c
@@ -12,9 +12,8 @@
  * 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, write to the Free Software Foundation, Inc., 59 Temple
- * Place - Suite 330, Boston, MA 02111-1307 USA.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  *
  * Authors:	Alexander Duyck <alexander.h.duyck@intel.com>
  *
@@ -31,7 +30,7 @@
 static void
 explain(void)
 {
- 	fprintf(stderr, "Usage: ... skbedit <[QM] [PM] [MM]>\n"
+	fprintf(stderr, "Usage: ... skbedit <[QM] [PM] [MM]>\n"
 		"QM = queue_mapping QUEUE_MAPPING\n"
 		"PM = priority PRIORITY \n"
 		"MM = mark MARK \n"
@@ -100,6 +99,7 @@
 		argv++;
 	}
 
+	sel.action = TC_ACT_PIPE;
 	if (argc) {
 		if (matches(*argv, "reclassify") == 0) {
 			sel.action = TC_ACT_RECLASSIFY;
@@ -165,6 +165,7 @@
 	__u32 *priority;
 	__u32 *mark;
 	__u16 *queue_mapping;
+	struct tc_skbedit *p = NULL;
 
 	if (arg == NULL)
 		return -1;
@@ -175,6 +176,7 @@
 		fprintf(f, "[NULL skbedit parameters]");
 		return -1;
 	}
+	p = RTA_DATA(tb[TCA_SKBEDIT_PARMS]);
 
 	fprintf(f, " skbedit");
 
@@ -191,6 +193,8 @@
 		fprintf(f, " mark %d", *mark);
 	}
 
+	fprintf(f, "\n\t index %d ref %d bind %d", p->index, p->refcnt, p->bindcnt);
+
 	if (show_stats) {
 		if (tb[TCA_SKBEDIT_TM]) {
 			struct tcf_t *tm = RTA_DATA(tb[TCA_SKBEDIT_TM]);
@@ -198,6 +202,8 @@
 		}
 	}
 
+	fprintf(f, "\n ");
+
 	return 0;
 }
 
diff --git a/tc/m_vlan.c b/tc/m_vlan.c
new file mode 100644
index 0000000..32db5ed
--- /dev/null
+++ b/tc/m_vlan.c
@@ -0,0 +1,227 @@
+/*
+ * m_vlan.c		vlan manipulation module
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <linux/if_ether.h>
+#include "utils.h"
+#include "rt_names.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_vlan.h>
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: vlan pop\n");
+	fprintf(stderr, "       vlan push [ protocol VLANPROTO ] id VLANID\n");
+	fprintf(stderr, "       VLANPROTO is one of 802.1Q or 802.1AD\n");
+	fprintf(stderr, "            with default: 802.1Q\n");
+}
+
+static void usage(void)
+{
+	explain();
+	exit(-1);
+}
+
+static int parse_vlan(struct action_util *a, int *argc_p, char ***argv_p,
+		      int tca_id, struct nlmsghdr *n)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	struct rtattr *tail;
+	int action = 0;
+	__u16 id;
+	int id_set = 0;
+	__u16 proto;
+	int proto_set = 0;
+	struct tc_vlan parm = { 0 };
+
+	if (matches(*argv, "vlan") != 0)
+		return -1;
+
+	NEXT_ARG();
+
+	while (argc > 0) {
+		if (matches(*argv, "pop") == 0) {
+			if (action) {
+				fprintf(stderr, "unexpected \"%s\" - action already specified\n",
+					*argv);
+				explain();
+				return -1;
+			}
+			action = TCA_VLAN_ACT_POP;
+		} else if (matches(*argv, "push") == 0) {
+			if (action) {
+				fprintf(stderr, "unexpected \"%s\" - action already specified\n",
+					*argv);
+				explain();
+				return -1;
+			}
+			action = TCA_VLAN_ACT_PUSH;
+		} else if (matches(*argv, "id") == 0) {
+			if (action != TCA_VLAN_ACT_PUSH) {
+				fprintf(stderr, "\"%s\" is only valid for push\n",
+					*argv);
+				explain();
+				return -1;
+			}
+			NEXT_ARG();
+			if (get_u16(&id, *argv, 0))
+				invarg("id is invalid", *argv);
+			id_set = 1;
+		} else if (matches(*argv, "protocol") == 0) {
+			if (action != TCA_VLAN_ACT_PUSH) {
+				fprintf(stderr, "\"%s\" is only valid for push\n",
+					*argv);
+				explain();
+				return -1;
+			}
+			NEXT_ARG();
+			if (ll_proto_a2n(&proto, *argv))
+				invarg("protocol is invalid", *argv);
+			proto_set = 1;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			break;
+		}
+		argc--;
+		argv++;
+	}
+
+	parm.action = TC_ACT_PIPE;
+	if (argc) {
+		if (matches(*argv, "reclassify") == 0) {
+			parm.action = TC_ACT_RECLASSIFY;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "pipe") == 0) {
+			parm.action = TC_ACT_PIPE;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "drop") == 0 ||
+			   matches(*argv, "shot") == 0) {
+			parm.action = TC_ACT_SHOT;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "continue") == 0) {
+			parm.action = TC_ACT_UNSPEC;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "pass") == 0) {
+			parm.action = TC_ACT_OK;
+			argc--;
+			argv++;
+		}
+	}
+
+	if (argc) {
+		if (matches(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&parm.index, *argv, 10)) {
+				fprintf(stderr, "vlan: Illegal \"index\"\n");
+				return -1;
+			}
+			argc--;
+			argv++;
+		}
+	}
+
+	if (action == TCA_VLAN_ACT_PUSH && !id_set) {
+		fprintf(stderr, "id needs to be set for push\n");
+		explain();
+		return -1;
+	}
+
+	parm.v_action = action;
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	addattr_l(n, MAX_MSG, TCA_VLAN_PARMS, &parm, sizeof(parm));
+	if (id_set)
+		addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_ID, &id, 2);
+	if (proto_set) {
+		if (proto != htons(ETH_P_8021Q) &&
+		    proto != htons(ETH_P_8021AD)) {
+			fprintf(stderr, "protocol not supported\n");
+			explain();
+			return -1;
+		}
+
+		addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_PROTOCOL, &proto, 2);
+	}
+	tail->rta_len = (char *)NLMSG_TAIL(n) - (char *)tail;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+static int print_vlan(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+	SPRINT_BUF(b1);
+	struct rtattr *tb[TCA_VLAN_MAX + 1];
+	__u16 val;
+	struct tc_vlan *parm;
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, TCA_VLAN_MAX, arg);
+
+	if (!tb[TCA_VLAN_PARMS]) {
+		fprintf(f, "[NULL vlan parameters]");
+		return -1;
+	}
+	parm = RTA_DATA(tb[TCA_VLAN_PARMS]);
+
+	fprintf(f, " vlan");
+
+	switch(parm->v_action) {
+	case TCA_VLAN_ACT_POP:
+		fprintf(f, " pop");
+		break;
+	case TCA_VLAN_ACT_PUSH:
+		fprintf(f, " push");
+		if (tb[TCA_VLAN_PUSH_VLAN_ID]) {
+			val = rta_getattr_u16(tb[TCA_VLAN_PUSH_VLAN_ID]);
+			fprintf(f, " id %u", val);
+		}
+		if (tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]) {
+			fprintf(f, " protocol %s",
+				ll_proto_n2a(rta_getattr_u16(tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]),
+					     b1, sizeof(b1)));
+		}
+		break;
+	}
+	fprintf(f, " %s", action_n2a(parm->action, b1, sizeof (b1)));
+
+	fprintf(f, "\n\t index %d ref %d bind %d", parm->index, parm->refcnt,
+		parm->bindcnt);
+
+	if (show_stats) {
+		if (tb[TCA_VLAN_TM]) {
+			struct tcf_t *tm = RTA_DATA(tb[TCA_VLAN_TM]);
+			print_tm(f, tm);
+		}
+	}
+
+	fprintf(f, "\n ");
+
+	return 0;
+}
+
+struct action_util vlan_action_util = {
+	.id = "vlan",
+	.parse_aopt = parse_vlan,
+	.print_aopt = print_vlan,
+};
diff --git a/tc/m_xt.c b/tc/m_xt.c
index bcc4d75..bf603fc 100644
--- a/tc/m_xt.c
+++ b/tc/m_xt.c
@@ -1,6 +1,6 @@
 /*
  * m_xt.c	xtables based targets
- * 		utilities mostly ripped from iptables <duh, its the linux way>
+ *		utilities mostly ripped from iptables <duh, its the linux way>
  *
  *		This program is free software; you can distribute it and/or
  *		modify it under the terms of the GNU General Public License
@@ -38,9 +38,13 @@
 #       define XT_LIB_DIR "/lib/xtables"
 #endif
 
+#ifndef __ALIGN_KERNEL
+#define __ALIGN_KERNEL(x, a)		__ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
+#define __ALIGN_KERNEL_MASK(x, mask)	(((x) + (mask)) & ~(mask))
+#endif
+
 #ifndef ALIGN
-#define ALIGN(x,a)		__ALIGN_MASK(x,(typeof(x))(a)-1)
-#define __ALIGN_MASK(x,mask)	(((x)+(mask))&~(mask))
+#define ALIGN(x,a)	__ALIGN_KERNEL((x), (a))
 #endif
 
 static const char *tname = "mangle";
@@ -76,7 +80,7 @@
 /*
  * we may need to check for version mismatch
 */
-int
+static int
 build_st(struct xtables_target *target, struct xt_entry_target *t)
 {
 
@@ -98,7 +102,7 @@
 
 }
 
-inline void set_lib_dir(void)
+static void set_lib_dir(void)
 {
 
 	lib_dir = getenv("XTABLES_LIBDIR");
@@ -118,6 +122,7 @@
 	struct xtables_target *m = NULL;
 	struct ipt_entry fw;
 	struct rtattr *tail;
+
 	int c;
 	int rargc = *argc_p;
 	char **argv = *argv_p;
@@ -126,6 +131,7 @@
 	int size = 0;
 	int iok = 0, ok = 0;
 	__u32 hook = 0, index = 0;
+	struct option *opts = NULL;
 
 	xtables_init_all(&tcipt_globals, NFPROTO_IPV4);
 	set_lib_dir();
@@ -141,7 +147,7 @@
 	}
 
 	if (argc <= 2) {
-		fprintf(stderr,"bad arguements to ipt %d vs %d \n", argc, rargc);
+		fprintf(stderr,"bad arguments to ipt %d vs %d \n", argc, rargc);
 		return -1;
 	}
 
@@ -158,14 +164,21 @@
 					printf(" %s error \n", m->name);
 					return -1;
 				}
-				tcipt_globals.opts =
-				    xtables_merge_options(
 #if (XTABLES_VERSION_CODE >= 6)
-				        tcipt_globals.orig_opts,
+			opts = xtables_options_xfrm(tcipt_globals.orig_opts,
+						    tcipt_globals.opts,
+						    m->x6_options,
+						    &m->option_offset);
+#else
+			opts = xtables_merge_options(tcipt_globals.opts,
+						     m->extra_opts,
+						     &m->option_offset);
 #endif
-				        tcipt_globals.opts,
-				        m->extra_opts,
-				        &m->option_offset);
+			if (opts == NULL) {
+				fprintf(stderr, " failed to find additional options for target %s\n\n", optarg);
+				return -1;
+			} else
+				tcipt_globals.opts = opts;
 			} else {
 				fprintf(stderr," failed to find target %s\n\n", optarg);
 				return -1;
@@ -175,17 +188,21 @@
 
 		default:
 			memset(&fw, 0, sizeof (fw));
-			if (m) {
-				m->parse(c - m->option_offset, argv, 0,
-					 &m->tflags, NULL, &m->t);
+#if (XTABLES_VERSION_CODE >= 6)
+		if (m != NULL && m->x6_parse != NULL ) {
+			xtables_option_tpcall(c, argv, 0 , m, NULL);
+#else
+		if (m != NULL && m->parse != NULL ) {
+			m->parse(c - m->option_offset, argv, 0, &m->tflags,
+				 NULL, &m->t);
+#endif
 			} else {
-				fprintf(stderr," failed to find target %s\n\n", optarg);
+				fprintf(stderr,"failed to find target %s\n\n", optarg);
 				return -1;
 
 			}
 			ok++;
 			break;
-
 		}
 	}
 
@@ -208,8 +225,13 @@
 	}
 
 	/* check that we passed the correct parameters to the target */
+#if (XTABLES_VERSION_CODE >= 6)
+	if (m)
+		xtables_option_tfcall(m);
+#else
 	if (m && m->final_check)
 		m->final_check(m->tflags);
+#endif
 
 	{
 		struct tcmsg *t = NLMSG_DATA(n);
@@ -271,11 +293,15 @@
 {
 	struct rtattr *tb[TCA_IPT_MAX + 1];
 	struct xt_entry_target *t = NULL;
+	struct option *opts = NULL;
 
 	if (arg == NULL)
 		return -1;
 
-	xtables_init_all(&tcipt_globals, NFPROTO_IPV4);
+	/* copy tcipt_globals because .opts will be modified by iptables */
+	struct xtables_globals tmp_tcipt_globals = tcipt_globals;
+
+	xtables_init_all(&tmp_tcipt_globals, NFPROTO_IPV4);
 	set_lib_dir();
 
 	parse_rtattr_nested(tb, TCA_IPT_MAX, arg);
@@ -309,14 +335,21 @@
 				return -1;
 			}
 
-			tcipt_globals.opts =
-			    xtables_merge_options(
 #if (XTABLES_VERSION_CODE >= 6)
-				                  tcipt_globals.orig_opts,
+		opts = xtables_options_xfrm(tmp_tcipt_globals.orig_opts,
+					    tmp_tcipt_globals.opts,
+					    m->x6_options,
+					    &m->option_offset);
+#else
+		opts = xtables_merge_options(tmp_tcipt_globals.opts,
+					     m->extra_opts,
+					     &m->option_offset);
 #endif
-				                  tcipt_globals.opts,
-			                          m->extra_opts,
-			                          &m->option_offset);
+	if (opts == NULL) {
+		fprintf(stderr, " failed to find additional options for target %s\n\n", optarg);
+		return -1;
+	} else
+		tmp_tcipt_globals.opts = opts;
 		} else {
 			fprintf(stderr, " failed to find target %s\n\n",
 				t->u.user.name);
@@ -355,4 +388,3 @@
         .parse_aopt = parse_ipt,
         .print_aopt = print_ipt,
 };
-
diff --git a/tc/m_xt_old.c b/tc/m_xt_old.c
index 554e4ed..0ea0b4a 100644
--- a/tc/m_xt_old.c
+++ b/tc/m_xt_old.c
@@ -1,6 +1,6 @@
 /*
  * m_xt.c	xtables based targets
- * 		utilities mostly ripped from iptables <duh, its the linux way>
+ *		utilities mostly ripped from iptables <duh, its the linux way>
  *
  *		This program is free software; you can distribute it and/or
  *		modify it under the terms of the GNU General Public License
@@ -232,7 +232,7 @@
 	}
 
 	if (argc <= 2) {
-		fprintf(stderr,"bad arguements to ipt %d vs %d \n", argc, rargc);
+		fprintf(stderr,"bad arguments to ipt %d vs %d \n", argc, rargc);
 		return -1;
 	}
 
diff --git a/tc/q_cbq.c b/tc/q_cbq.c
index 3c5e72c..d76600c 100644
--- a/tc/q_cbq.c
+++ b/tc/q_cbq.c
@@ -442,7 +442,9 @@
 	struct tc_cbq_wrropt *wrr = NULL;
 	struct tc_cbq_fopt *fopt = NULL;
 	struct tc_cbq_ovl *ovl = NULL;
+	unsigned int linklayer;
 	SPRINT_BUF(b1);
+	SPRINT_BUF(b2);
 
 	if (opt == NULL)
 		return 0;
@@ -486,6 +488,9 @@
 		char buf[64];
 		print_rate(buf, sizeof(buf), r->rate);
 		fprintf(f, "rate %s ", buf);
+		linklayer = (r->linklayer & TC_LINKLAYER_MASK);
+		if (linklayer > TC_LINKLAYER_ETHERNET || show_details)
+			fprintf(f, "linklayer %s ", sprint_linklayer(linklayer, b2));
 		if (show_details) {
 			fprintf(f, "cell %ub ", 1<<r->cell_log);
 			if (r->mpu)
diff --git a/tc/q_choke.c b/tc/q_choke.c
index 6fbcadf..bd9ceb8 100644
--- a/tc/q_choke.c
+++ b/tc/q_choke.c
@@ -108,11 +108,11 @@
 		return -1;
 	}
 
-	/* Compute default min/max thresholds based on 
+	/* Compute default min/max thresholds based on
 	   Sally Floyd's recommendations:
 	   http://www.icir.org/floyd/REDparameters.txt
 	*/
-	if (!opt.qth_max) 
+	if (!opt.qth_max)
 		opt.qth_max = opt.limit / 4;
 	if (!opt.qth_min)
 		opt.qth_min = opt.qth_max / 3;
diff --git a/tc/q_codel.c b/tc/q_codel.c
new file mode 100644
index 0000000..dc4b3f6
--- /dev/null
+++ b/tc/q_codel.c
@@ -0,0 +1,188 @@
+/*
+ * Codel - The Controlled-Delay Active Queue Management algorithm
+ *
+ *  Copyright (C) 2011-2012 Kathleen Nichols <nichols@pollere.com>
+ *  Copyright (C) 2011-2012 Van Jacobson <van@pollere.com>
+ *  Copyright (C) 2012 Michael D. Taht <dave.taht@bufferbloat.net>
+ *  Copyright (C) 2012 Eric Dumazet <edumazet@google.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the authors may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, provided that this notice is retained in full, this
+ * software may be distributed under the terms of the GNU General
+ * Public License ("GPL") version 2, in which case the provisions of the
+ * GPL apply INSTEAD OF those given above.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... codel [ limit PACKETS ] [ target TIME]\n");
+	fprintf(stderr, "                 [ interval TIME ] [ ecn | noecn ]\n");
+}
+
+static int codel_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+			   struct nlmsghdr *n)
+{
+	unsigned limit = 0;
+	unsigned target = 0;
+	unsigned interval = 0;
+	int ecn = -1;
+	struct rtattr *tail;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&limit, *argv, 0)) {
+				fprintf(stderr, "Illegal \"limit\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "target") == 0) {
+			NEXT_ARG();
+			if (get_time(&target, *argv)) {
+				fprintf(stderr, "Illegal \"target\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "interval") == 0) {
+			NEXT_ARG();
+			if (get_time(&interval, *argv)) {
+				fprintf(stderr, "Illegal \"interval\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "ecn") == 0) {
+			ecn = 1;
+		} else if (strcmp(*argv, "noecn") == 0) {
+			ecn = 0;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	if (limit)
+		addattr_l(n, 1024, TCA_CODEL_LIMIT, &limit, sizeof(limit));
+	if (interval)
+		addattr_l(n, 1024, TCA_CODEL_INTERVAL, &interval, sizeof(interval));
+	if (target)
+		addattr_l(n, 1024, TCA_CODEL_TARGET, &target, sizeof(target));
+	if (ecn != -1)
+		addattr_l(n, 1024, TCA_CODEL_ECN, &ecn, sizeof(ecn));
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int codel_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_CODEL_MAX + 1];
+	unsigned limit;
+	unsigned interval;
+	unsigned target;
+	unsigned ecn;
+	SPRINT_BUF(b1);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_CODEL_MAX, opt);
+
+	if (tb[TCA_CODEL_LIMIT] &&
+	    RTA_PAYLOAD(tb[TCA_CODEL_LIMIT]) >= sizeof(__u32)) {
+		limit = rta_getattr_u32(tb[TCA_CODEL_LIMIT]);
+		fprintf(f, "limit %up ", limit);
+	}
+	if (tb[TCA_CODEL_TARGET] &&
+	    RTA_PAYLOAD(tb[TCA_CODEL_TARGET]) >= sizeof(__u32)) {
+		target = rta_getattr_u32(tb[TCA_CODEL_TARGET]);
+		fprintf(f, "target %s ", sprint_time(target, b1));
+	}
+	if (tb[TCA_CODEL_INTERVAL] &&
+	    RTA_PAYLOAD(tb[TCA_CODEL_INTERVAL]) >= sizeof(__u32)) {
+		interval = rta_getattr_u32(tb[TCA_CODEL_INTERVAL]);
+		fprintf(f, "interval %s ", sprint_time(interval, b1));
+	}
+	if (tb[TCA_CODEL_ECN] &&
+	    RTA_PAYLOAD(tb[TCA_CODEL_ECN]) >= sizeof(__u32)) {
+		ecn = rta_getattr_u32(tb[TCA_CODEL_ECN]);
+		if (ecn)
+			fprintf(f, "ecn ");
+	}
+
+	return 0;
+}
+
+static int codel_print_xstats(struct qdisc_util *qu, FILE *f,
+			      struct rtattr *xstats)
+{
+	struct tc_codel_xstats *st;
+	SPRINT_BUF(b1);
+
+	if (xstats == NULL)
+		return 0;
+
+	if (RTA_PAYLOAD(xstats) < sizeof(*st))
+		return -1;
+
+	st = RTA_DATA(xstats);
+	fprintf(f, "  count %u lastcount %u ldelay %s",
+		st->count, st->lastcount, sprint_time(st->ldelay, b1));
+	if (st->dropping)
+		fprintf(f, " dropping");
+	if (st->drop_next < 0)
+		fprintf(f, " drop_next -%s", sprint_time(-st->drop_next, b1));
+	else
+		fprintf(f, " drop_next %s", sprint_time(st->drop_next, b1));
+	fprintf(f, "\n  maxpacket %u ecn_mark %u drop_overlimit %u",
+		st->maxpacket, st->ecn_mark, st->drop_overlimit);
+	return 0;
+
+}
+
+struct qdisc_util codel_qdisc_util = {
+	.id		= "codel",
+	.parse_qopt	= codel_parse_opt,
+	.print_qopt	= codel_print_opt,
+	.print_xstats	= codel_print_xstats,
+};
diff --git a/tc/q_fifo.c b/tc/q_fifo.c
index 6242a98..c9ab123 100644
--- a/tc/q_fifo.c
+++ b/tc/q_fifo.c
@@ -38,7 +38,7 @@
 		if (strcmp(*argv, "limit") == 0) {
 			NEXT_ARG();
 			if (get_size(&opt.limit, *argv)) {
-				fprintf(stderr, "Illegal \"limit\"\n");
+				fprintf(stderr, "%s: Illegal value for \"limit\": \"%s\"\n", qu->id, *argv);
 				return -1;
 			}
 			ok++;
@@ -46,7 +46,7 @@
 			explain();
 			return -1;
 		} else {
-			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			fprintf(stderr, "%s: unknown parameter \"%s\"\n", qu->id, *argv);
 			explain();
 			return -1;
 		}
diff --git a/tc/q_fq.c b/tc/q_fq.c
new file mode 100644
index 0000000..e7288c2
--- /dev/null
+++ b/tc/q_fq.c
@@ -0,0 +1,292 @@
+/*
+ * Fair Queue
+ *
+ *  Copyright (C) 2013 Eric Dumazet <edumazet@google.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the authors may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, provided that this notice is retained in full, this
+ * software may be distributed under the terms of the GNU General
+ * Public License ("GPL") version 2, in which case the provisions of the
+ * GPL apply INSTEAD OF those given above.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... fq [ limit PACKETS ] [ flow_limit PACKETS ]\n");
+	fprintf(stderr, "              [ quantum BYTES ] [ initial_quantum BYTES ]\n");
+	fprintf(stderr, "              [ maxrate RATE  ] [ buckets NUMBER ]\n");
+	fprintf(stderr, "              [ [no]pacing ]\n");
+}
+
+static unsigned int ilog2(unsigned int val)
+{
+	unsigned int res = 0;
+
+	val--;
+	while (val) {
+		res++;
+		val >>= 1;
+	}
+	return res;
+}
+
+static int fq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+			struct nlmsghdr *n)
+{
+	unsigned int plimit;
+	unsigned int flow_plimit;
+	unsigned int quantum;
+	unsigned int initial_quantum;
+	unsigned int buckets = 0;
+	unsigned int maxrate;
+	unsigned int defrate;
+	bool set_plimit = false;
+	bool set_flow_plimit = false;
+	bool set_quantum = false;
+	bool set_initial_quantum = false;
+	bool set_maxrate = false;
+	bool set_defrate = false;
+	int pacing = -1;
+	struct rtattr *tail;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&plimit, *argv, 0)) {
+				fprintf(stderr, "Illegal \"limit\"\n");
+				return -1;
+			}
+			set_plimit = true;
+		} else if (strcmp(*argv, "flow_limit") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&flow_plimit, *argv, 0)) {
+				fprintf(stderr, "Illegal \"flow_limit\"\n");
+				return -1;
+			}
+			set_flow_plimit = true;
+		} else if (strcmp(*argv, "buckets") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&buckets, *argv, 0)) {
+				fprintf(stderr, "Illegal \"buckets\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "maxrate") == 0) {
+			NEXT_ARG();
+			if (get_rate(&maxrate, *argv)) {
+				fprintf(stderr, "Illegal \"maxrate\"\n");
+				return -1;
+			}
+			set_maxrate = true;
+		} else if (strcmp(*argv, "defrate") == 0) {
+			NEXT_ARG();
+			if (get_rate(&defrate, *argv)) {
+				fprintf(stderr, "Illegal \"defrate\"\n");
+				return -1;
+			}
+			set_defrate = true;
+		} else if (strcmp(*argv, "quantum") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&quantum, *argv, 0)) {
+				fprintf(stderr, "Illegal \"quantum\"\n");
+				return -1;
+			}
+			set_quantum = true;
+		} else if (strcmp(*argv, "initial_quantum") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&initial_quantum, *argv, 0)) {
+				fprintf(stderr, "Illegal \"initial_quantum\"\n");
+				return -1;
+			}
+			set_initial_quantum = true;
+		} else if (strcmp(*argv, "pacing") == 0) {
+			pacing = 1;
+		} else if (strcmp(*argv, "nopacing") == 0) {
+			pacing = 0;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	if (buckets) {
+		unsigned int log = ilog2(buckets);
+
+		addattr_l(n, 1024, TCA_FQ_BUCKETS_LOG,
+			  &log, sizeof(log));
+	}
+	if (set_plimit)
+		addattr_l(n, 1024, TCA_FQ_PLIMIT,
+			  &plimit, sizeof(plimit));
+	if (set_flow_plimit)
+		addattr_l(n, 1024, TCA_FQ_FLOW_PLIMIT,
+			  &flow_plimit, sizeof(flow_plimit));
+	if (set_quantum)
+		addattr_l(n, 1024, TCA_FQ_QUANTUM, &quantum, sizeof(quantum));
+	if (set_initial_quantum)
+		addattr_l(n, 1024, TCA_FQ_INITIAL_QUANTUM,
+			  &initial_quantum, sizeof(initial_quantum));
+	if (pacing != -1)
+		addattr_l(n, 1024, TCA_FQ_RATE_ENABLE,
+			  &pacing, sizeof(pacing));
+	if (set_maxrate)
+		addattr_l(n, 1024, TCA_FQ_FLOW_MAX_RATE,
+			  &maxrate, sizeof(maxrate));
+	if (set_defrate)
+		addattr_l(n, 1024, TCA_FQ_FLOW_DEFAULT_RATE,
+			  &defrate, sizeof(defrate));
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int fq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_FQ_MAX + 1];
+	unsigned int plimit, flow_plimit;
+	unsigned int buckets_log;
+	int pacing;
+	unsigned int rate, quantum;
+	SPRINT_BUF(b1);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_FQ_MAX, opt);
+
+	if (tb[TCA_FQ_PLIMIT] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_PLIMIT]) >= sizeof(__u32)) {
+		plimit = rta_getattr_u32(tb[TCA_FQ_PLIMIT]);
+		fprintf(f, "limit %up ", plimit);
+	}
+	if (tb[TCA_FQ_FLOW_PLIMIT] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_FLOW_PLIMIT]) >= sizeof(__u32)) {
+		flow_plimit = rta_getattr_u32(tb[TCA_FQ_FLOW_PLIMIT]);
+		fprintf(f, "flow_limit %up ", flow_plimit);
+	}
+	if (tb[TCA_FQ_BUCKETS_LOG] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_BUCKETS_LOG]) >= sizeof(__u32)) {
+		buckets_log = rta_getattr_u32(tb[TCA_FQ_BUCKETS_LOG]);
+		fprintf(f, "buckets %u ", 1U << buckets_log);
+	}
+	if (tb[TCA_FQ_RATE_ENABLE] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_RATE_ENABLE]) >= sizeof(int)) {
+		pacing = rta_getattr_u32(tb[TCA_FQ_RATE_ENABLE]);
+		if (pacing == 0)
+			fprintf(f, "nopacing ");
+	}
+	if (tb[TCA_FQ_QUANTUM] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_QUANTUM]) >= sizeof(__u32)) {
+		quantum = rta_getattr_u32(tb[TCA_FQ_QUANTUM]);
+		fprintf(f, "quantum %u ", quantum);
+	}
+	if (tb[TCA_FQ_INITIAL_QUANTUM] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_INITIAL_QUANTUM]) >= sizeof(__u32)) {
+		quantum = rta_getattr_u32(tb[TCA_FQ_INITIAL_QUANTUM]);
+		fprintf(f, "initial_quantum %u ", quantum);
+	}
+	if (tb[TCA_FQ_FLOW_MAX_RATE] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_FLOW_MAX_RATE]) >= sizeof(__u32)) {
+		rate = rta_getattr_u32(tb[TCA_FQ_FLOW_MAX_RATE]);
+
+		if (rate != ~0U)
+			fprintf(f, "maxrate %s ", sprint_rate(rate, b1));
+	}
+	if (tb[TCA_FQ_FLOW_DEFAULT_RATE] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_FLOW_DEFAULT_RATE]) >= sizeof(__u32)) {
+		rate = rta_getattr_u32(tb[TCA_FQ_FLOW_DEFAULT_RATE]);
+
+		if (rate != 0)
+			fprintf(f, "defrate %s ", sprint_rate(rate, b1));
+	}
+
+	return 0;
+}
+
+static int fq_print_xstats(struct qdisc_util *qu, FILE *f,
+			   struct rtattr *xstats)
+{
+	struct tc_fq_qd_stats *st;
+
+	if (xstats == NULL)
+		return 0;
+
+	if (RTA_PAYLOAD(xstats) < sizeof(*st))
+		return -1;
+
+	st = RTA_DATA(xstats);
+
+	fprintf(f, "  %u flows (%u inactive, %u throttled)",
+		st->flows, st->inactive_flows, st->throttled_flows);
+
+	if (st->time_next_delayed_flow > 0)
+		fprintf(f, ", next packet delay %llu ns", st->time_next_delayed_flow);
+
+	fprintf(f, "\n  %llu gc, %llu highprio",
+		st->gc_flows, st->highprio_packets);
+
+	if (st->tcp_retrans)
+		fprintf(f, ", %llu retrans", st->tcp_retrans);
+
+	fprintf(f, ", %llu throttled", st->throttled);
+
+	if (st->flows_plimit)
+		fprintf(f, ", %llu flows_plimit", st->flows_plimit);
+
+	if (st->pkts_too_long || st->allocation_errors)
+		fprintf(f, "\n  %llu too long pkts, %llu alloc errors\n",
+			st->pkts_too_long, st->allocation_errors);
+
+	return 0;
+}
+
+struct qdisc_util fq_qdisc_util = {
+	.id		= "fq",
+	.parse_qopt	= fq_parse_opt,
+	.print_qopt	= fq_print_opt,
+	.print_xstats	= fq_print_xstats,
+};
diff --git a/tc/q_fq_codel.c b/tc/q_fq_codel.c
new file mode 100644
index 0000000..1d3bfa2
--- /dev/null
+++ b/tc/q_fq_codel.c
@@ -0,0 +1,232 @@
+/*
+ * Fair Queue Codel
+ *
+ *  Copyright (C) 2012 Eric Dumazet <edumazet@google.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the authors may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, provided that this notice is retained in full, this
+ * software may be distributed under the terms of the GNU General
+ * Public License ("GPL") version 2, in which case the provisions of the
+ * GPL apply INSTEAD OF those given above.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... fq_codel [ limit PACKETS ] [ flows NUMBER ]\n");
+	fprintf(stderr, "                    [ target TIME] [ interval TIME ]\n");
+	fprintf(stderr, "                    [ quantum BYTES ] [ [no]ecn ]\n");
+}
+
+static int fq_codel_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+			      struct nlmsghdr *n)
+{
+	unsigned limit = 0;
+	unsigned flows = 0;
+	unsigned target = 0;
+	unsigned interval = 0;
+	unsigned quantum = 0;
+	int ecn = -1;
+	struct rtattr *tail;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&limit, *argv, 0)) {
+				fprintf(stderr, "Illegal \"limit\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "flows") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&flows, *argv, 0)) {
+				fprintf(stderr, "Illegal \"flows\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "quantum") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&quantum, *argv, 0)) {
+				fprintf(stderr, "Illegal \"quantum\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "target") == 0) {
+			NEXT_ARG();
+			if (get_time(&target, *argv)) {
+				fprintf(stderr, "Illegal \"target\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "interval") == 0) {
+			NEXT_ARG();
+			if (get_time(&interval, *argv)) {
+				fprintf(stderr, "Illegal \"interval\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "ecn") == 0) {
+			ecn = 1;
+		} else if (strcmp(*argv, "noecn") == 0) {
+			ecn = 0;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	if (limit)
+		addattr_l(n, 1024, TCA_FQ_CODEL_LIMIT, &limit, sizeof(limit));
+	if (flows)
+		addattr_l(n, 1024, TCA_FQ_CODEL_FLOWS, &flows, sizeof(flows));
+	if (quantum)
+		addattr_l(n, 1024, TCA_FQ_CODEL_QUANTUM, &quantum, sizeof(quantum));
+	if (interval)
+		addattr_l(n, 1024, TCA_FQ_CODEL_INTERVAL, &interval, sizeof(interval));
+	if (target)
+		addattr_l(n, 1024, TCA_FQ_CODEL_TARGET, &target, sizeof(target));
+	if (ecn != -1)
+		addattr_l(n, 1024, TCA_FQ_CODEL_ECN, &ecn, sizeof(ecn));
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int fq_codel_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_FQ_CODEL_MAX + 1];
+	unsigned limit;
+	unsigned flows;
+	unsigned interval;
+	unsigned target;
+	unsigned ecn;
+	unsigned quantum;
+	SPRINT_BUF(b1);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_FQ_CODEL_MAX, opt);
+
+	if (tb[TCA_FQ_CODEL_LIMIT] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_CODEL_LIMIT]) >= sizeof(__u32)) {
+		limit = rta_getattr_u32(tb[TCA_FQ_CODEL_LIMIT]);
+		fprintf(f, "limit %up ", limit);
+	}
+	if (tb[TCA_FQ_CODEL_FLOWS] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_CODEL_FLOWS]) >= sizeof(__u32)) {
+		flows = rta_getattr_u32(tb[TCA_FQ_CODEL_FLOWS]);
+		fprintf(f, "flows %u ", flows);
+	}
+	if (tb[TCA_FQ_CODEL_QUANTUM] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_CODEL_QUANTUM]) >= sizeof(__u32)) {
+		quantum = rta_getattr_u32(tb[TCA_FQ_CODEL_QUANTUM]);
+		fprintf(f, "quantum %u ", quantum);
+	}
+	if (tb[TCA_FQ_CODEL_TARGET] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_CODEL_TARGET]) >= sizeof(__u32)) {
+		target = rta_getattr_u32(tb[TCA_FQ_CODEL_TARGET]);
+		fprintf(f, "target %s ", sprint_time(target, b1));
+	}
+	if (tb[TCA_FQ_CODEL_INTERVAL] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_CODEL_INTERVAL]) >= sizeof(__u32)) {
+		interval = rta_getattr_u32(tb[TCA_FQ_CODEL_INTERVAL]);
+		fprintf(f, "interval %s ", sprint_time(interval, b1));
+	}
+	if (tb[TCA_FQ_CODEL_ECN] &&
+	    RTA_PAYLOAD(tb[TCA_FQ_CODEL_ECN]) >= sizeof(__u32)) {
+		ecn = rta_getattr_u32(tb[TCA_FQ_CODEL_ECN]);
+		if (ecn)
+			fprintf(f, "ecn ");
+	}
+
+	return 0;
+}
+
+static int fq_codel_print_xstats(struct qdisc_util *qu, FILE *f,
+				 struct rtattr *xstats)
+{
+	struct tc_fq_codel_xstats *st;
+	SPRINT_BUF(b1);
+
+	if (xstats == NULL)
+		return 0;
+
+	if (RTA_PAYLOAD(xstats) < sizeof(*st))
+		return -1;
+
+	st = RTA_DATA(xstats);
+	if (st->type == TCA_FQ_CODEL_XSTATS_QDISC) {
+		fprintf(f, "  maxpacket %u drop_overlimit %u new_flow_count %u ecn_mark %u",
+			st->qdisc_stats.maxpacket,
+			st->qdisc_stats.drop_overlimit,
+			st->qdisc_stats.new_flow_count,
+			st->qdisc_stats.ecn_mark);
+		fprintf(f, "\n  new_flows_len %u old_flows_len %u",
+			st->qdisc_stats.new_flows_len,
+			st->qdisc_stats.old_flows_len);
+	}
+	if (st->type == TCA_FQ_CODEL_XSTATS_CLASS) {
+		fprintf(f, "  deficit %d count %u lastcount %u ldelay %s",
+			st->class_stats.deficit,
+			st->class_stats.count,
+			st->class_stats.lastcount,
+			sprint_time(st->class_stats.ldelay, b1));
+		if (st->class_stats.dropping) {
+			fprintf(f, " dropping");
+			if (st->class_stats.drop_next < 0)
+				fprintf(f, " drop_next -%s",
+					sprint_time(-st->class_stats.drop_next, b1));
+			else
+				fprintf(f, " drop_next %s",
+					sprint_time(st->class_stats.drop_next, b1));
+		}
+	}
+	return 0;
+
+}
+
+struct qdisc_util fq_codel_qdisc_util = {
+	.id		= "fq_codel",
+	.parse_qopt	= fq_codel_parse_opt,
+	.print_qopt	= fq_codel_print_opt,
+	.print_xstats	= fq_codel_print_xstats,
+};
diff --git a/tc/q_gred.c b/tc/q_gred.c
index a4df3a6..88bd094 100644
--- a/tc/q_gred.c
+++ b/tc/q_gred.c
@@ -47,7 +47,7 @@
 	    "[grio]\n");
 }
 
-static int init_gred(struct qdisc_util *qu, int argc, char **argv, 
+static int init_gred(struct qdisc_util *qu, int argc, char **argv,
 		     struct nlmsghdr *n)
 {
 
@@ -78,7 +78,7 @@
 				return -1;
 			}
 			if (def_dp < 0 || def_dp > dps) {
-				fprintf(stderr, 
+				fprintf(stderr,
 					"\"default DP\" must be less than %d\n",
 					opt.DPs);
 				return -1;
diff --git a/tc/q_hhf.c b/tc/q_hhf.c
new file mode 100644
index 0000000..06ec8a2
--- /dev/null
+++ b/tc/q_hhf.c
@@ -0,0 +1,199 @@
+/* q_hhf.c		Heavy-Hitter Filter (HHF)
+ *
+ * Copyright (C) 2013 Terry Lam <vtlam@google.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... hhf [ limit PACKETS ] [ quantum BYTES]\n");
+	fprintf(stderr, "               [ hh_limit NUMBER ]\n");
+	fprintf(stderr, "               [ reset_timeout TIME ]\n");
+	fprintf(stderr, "               [ admit_bytes BYTES ]\n");
+	fprintf(stderr, "               [ evict_timeout TIME ]\n");
+	fprintf(stderr, "               [ non_hh_weight NUMBER ]\n");
+}
+
+static int hhf_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	unsigned limit = 0;
+	unsigned quantum = 0;
+	unsigned hh_limit = 0;
+	unsigned reset_timeout = 0;
+	unsigned admit_bytes = 0;
+	unsigned evict_timeout = 0;
+	unsigned non_hh_weight = 0;
+	struct rtattr *tail;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&limit, *argv, 0)) {
+				fprintf(stderr, "Illegal \"limit\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "quantum") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&quantum, *argv, 0)) {
+				fprintf(stderr, "Illegal \"quantum\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "hh_limit") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&hh_limit, *argv, 0)) {
+				fprintf(stderr, "Illegal \"hh_limit\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "reset_timeout") == 0) {
+			NEXT_ARG();
+			if (get_time(&reset_timeout, *argv)) {
+				fprintf(stderr, "Illegal \"reset_timeout\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "admit_bytes") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&admit_bytes, *argv, 0)) {
+				fprintf(stderr, "Illegal \"admit_bytes\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "evict_timeout") == 0) {
+			NEXT_ARG();
+			if (get_time(&evict_timeout, *argv)) {
+				fprintf(stderr, "Illegal \"evict_timeout\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "non_hh_weight") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&non_hh_weight, *argv, 0)) {
+				fprintf(stderr, "Illegal \"non_hh_weight\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	if (limit)
+		addattr_l(n, 1024, TCA_HHF_BACKLOG_LIMIT, &limit,
+			  sizeof(limit));
+	if (quantum)
+		addattr_l(n, 1024, TCA_HHF_QUANTUM, &quantum, sizeof(quantum));
+	if (hh_limit)
+		addattr_l(n, 1024, TCA_HHF_HH_FLOWS_LIMIT, &hh_limit,
+			  sizeof(hh_limit));
+	if (reset_timeout)
+		addattr_l(n, 1024, TCA_HHF_RESET_TIMEOUT, &reset_timeout,
+			  sizeof(reset_timeout));
+	if (admit_bytes)
+		addattr_l(n, 1024, TCA_HHF_ADMIT_BYTES, &admit_bytes,
+			  sizeof(admit_bytes));
+	if (evict_timeout)
+		addattr_l(n, 1024, TCA_HHF_EVICT_TIMEOUT, &evict_timeout,
+			  sizeof(evict_timeout));
+	if (non_hh_weight)
+		addattr_l(n, 1024, TCA_HHF_NON_HH_WEIGHT, &non_hh_weight,
+			  sizeof(non_hh_weight));
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int hhf_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_HHF_MAX + 1];
+	unsigned limit;
+	unsigned quantum;
+	unsigned hh_limit;
+	unsigned reset_timeout;
+	unsigned admit_bytes;
+	unsigned evict_timeout;
+	unsigned non_hh_weight;
+	SPRINT_BUF(b1);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_HHF_MAX, opt);
+
+	if (tb[TCA_HHF_BACKLOG_LIMIT] &&
+	    RTA_PAYLOAD(tb[TCA_HHF_BACKLOG_LIMIT]) >= sizeof(__u32)) {
+		limit = rta_getattr_u32(tb[TCA_HHF_BACKLOG_LIMIT]);
+		fprintf(f, "limit %up ", limit);
+	}
+	if (tb[TCA_HHF_QUANTUM] &&
+	    RTA_PAYLOAD(tb[TCA_HHF_QUANTUM]) >= sizeof(__u32)) {
+		quantum = rta_getattr_u32(tb[TCA_HHF_QUANTUM]);
+		fprintf(f, "quantum %u ", quantum);
+	}
+	if (tb[TCA_HHF_HH_FLOWS_LIMIT] &&
+	    RTA_PAYLOAD(tb[TCA_HHF_HH_FLOWS_LIMIT]) >= sizeof(__u32)) {
+		hh_limit = rta_getattr_u32(tb[TCA_HHF_HH_FLOWS_LIMIT]);
+		fprintf(f, "hh_limit %u ", hh_limit);
+	}
+	if (tb[TCA_HHF_RESET_TIMEOUT] &&
+	    RTA_PAYLOAD(tb[TCA_HHF_RESET_TIMEOUT]) >= sizeof(__u32)) {
+		reset_timeout = rta_getattr_u32(tb[TCA_HHF_RESET_TIMEOUT]);
+		fprintf(f, "reset_timeout %s ", sprint_time(reset_timeout, b1));
+	}
+	if (tb[TCA_HHF_ADMIT_BYTES] &&
+	    RTA_PAYLOAD(tb[TCA_HHF_ADMIT_BYTES]) >= sizeof(__u32)) {
+		admit_bytes = rta_getattr_u32(tb[TCA_HHF_ADMIT_BYTES]);
+		fprintf(f, "admit_bytes %u ", admit_bytes);
+	}
+	if (tb[TCA_HHF_EVICT_TIMEOUT] &&
+	    RTA_PAYLOAD(tb[TCA_HHF_EVICT_TIMEOUT]) >= sizeof(__u32)) {
+		evict_timeout = rta_getattr_u32(tb[TCA_HHF_EVICT_TIMEOUT]);
+		fprintf(f, "evict_timeout %s ", sprint_time(evict_timeout, b1));
+	}
+	if (tb[TCA_HHF_NON_HH_WEIGHT] &&
+	    RTA_PAYLOAD(tb[TCA_HHF_NON_HH_WEIGHT]) >= sizeof(__u32)) {
+		non_hh_weight = rta_getattr_u32(tb[TCA_HHF_NON_HH_WEIGHT]);
+		fprintf(f, "non_hh_weight %u ", non_hh_weight);
+	}
+	return 0;
+}
+
+static int hhf_print_xstats(struct qdisc_util *qu, FILE *f,
+			    struct rtattr *xstats)
+{
+	struct tc_hhf_xstats *st;
+
+	if (xstats == NULL)
+		return 0;
+
+	if (RTA_PAYLOAD(xstats) < sizeof(*st))
+		return -1;
+
+	st = RTA_DATA(xstats);
+
+	fprintf(f, "  drop_overlimit %u hh_overlimit %u tot_hh %u cur_hh %u",
+		st->drop_overlimit, st->hh_overlimit,
+		st->hh_tot_count, st->hh_cur_count);
+	return 0;
+}
+
+struct qdisc_util hhf_qdisc_util = {
+	.id		= "hhf",
+	.parse_qopt	= hhf_parse_opt,
+	.print_qopt	= hhf_print_opt,
+	.print_xstats	= hhf_print_xstats,
+};
diff --git a/tc/q_htb.c b/tc/q_htb.c
index caa47c2..7075a4c 100644
--- a/tc/q_htb.c
+++ b/tc/q_htb.c
@@ -31,9 +31,11 @@
 static void explain(void)
 {
 	fprintf(stderr, "Usage: ... qdisc add ... htb [default N] [r2q N]\n"
+		"                      [direct_qlen P]\n"
 		" default  minor id of class to which unclassified packets are sent {0}\n"
 		" r2q      DRR quantums are computed as rate in Bps/r2q {10}\n"
 		" debug    string of 16 numbers each 0-3 {0}\n\n"
+		" direct_qlen  Limit of the direct queue {in packets}\n"
 		"... class add ... htb rate R1 [burst B1] [mpu B] [overhead O]\n"
 		"                      [prio P] [slot S] [pslot PS]\n"
 		"                      [ceil R2] [cburst B2] [mtu MTU] [quantum Q]\n"
@@ -60,6 +62,7 @@
 
 static int htb_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
 {
+	unsigned int direct_qlen = ~0U;
 	struct tc_htb_glob opt;
 	struct rtattr *tail;
 	unsigned i; char *p;
@@ -69,21 +72,26 @@
 
 	while (argc > 0) {
 		if (matches(*argv, "r2q") == 0) {
-		    NEXT_ARG();
-		    if (get_u32(&opt.rate2quantum, *argv, 10)) {
-			explain1("r2q"); return -1;
-		    }
+			NEXT_ARG();
+			if (get_u32(&opt.rate2quantum, *argv, 10)) {
+				explain1("r2q"); return -1;
+			}
 		} else if (matches(*argv, "default") == 0) {
-		    NEXT_ARG();
-		    if (get_u32(&opt.defcls, *argv, 16)) {
-			explain1("default"); return -1;
-		    }
+			NEXT_ARG();
+			if (get_u32(&opt.defcls, *argv, 16)) {
+				explain1("default"); return -1;
+			}
 		} else if (matches(*argv, "debug") == 0) {
-		    NEXT_ARG(); p = *argv;
-		    for (i=0; i<16; i++,p++) {
-			if (*p<'0' || *p>'3') break;
-			opt.debug |= (*p-'0')<<(2*i);
-		    }
+			NEXT_ARG(); p = *argv;
+			for (i=0; i<16; i++,p++) {
+				if (*p<'0' || *p>'3') break;
+				opt.debug |= (*p-'0')<<(2*i);
+			}
+		} else if (matches(*argv, "direct_qlen") == 0) {
+			NEXT_ARG();
+			if (get_u32(&direct_qlen, *argv, 10)) {
+				explain1("direct_qlen"); return -1;
+			}
 		} else {
 			fprintf(stderr, "What is \"%s\"?\n", *argv);
 			explain();
@@ -94,6 +102,9 @@
 	tail = NLMSG_TAIL(n);
 	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
 	addattr_l(n, 2024, TCA_HTB_INIT, &opt, NLMSG_ALIGN(sizeof(opt)));
+	if (direct_qlen != ~0U)
+		addattr_l(n, 2024, TCA_HTB_DIRECT_QLEN,
+			  &direct_qlen, sizeof(direct_qlen));
 	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
 	return 0;
 }
@@ -110,6 +121,7 @@
 	unsigned short overhead = 0;
 	unsigned int linklayer  = LINKLAYER_ETHERNET; /* Assume ethernet */
 	struct rtattr *tail;
+	__u64 ceil64 = 0, rate64 = 0;
 
 	memset(&opt, 0, sizeof(opt)); mtu = 1600; /* eth packet len */
 
@@ -146,8 +158,8 @@
 				explain1("quantum"); return -1;
 			}
 		} else if (matches(*argv, "burst") == 0 ||
-			strcmp(*argv, "buffer") == 0 ||
-			strcmp(*argv, "maxburst") == 0) {
+			   strcmp(*argv, "buffer") == 0 ||
+			   strcmp(*argv, "maxburst") == 0) {
 			NEXT_ARG();
 			if (get_size_and_cell(&buffer, &cell_log, *argv) < 0) {
 				explain1("buffer");
@@ -155,8 +167,8 @@
 			}
 			ok++;
 		} else if (matches(*argv, "cburst") == 0 ||
-			strcmp(*argv, "cbuffer") == 0 ||
-			strcmp(*argv, "cmaxburst") == 0) {
+			   strcmp(*argv, "cbuffer") == 0 ||
+			   strcmp(*argv, "cmaxburst") == 0) {
 			NEXT_ARG();
 			if (get_size_and_cell(&cbuffer, &ccell_log, *argv) < 0) {
 				explain1("cbuffer");
@@ -165,22 +177,22 @@
 			ok++;
 		} else if (strcmp(*argv, "ceil") == 0) {
 			NEXT_ARG();
-			if (opt.ceil.rate) {
+			if (ceil64) {
 				fprintf(stderr, "Double \"ceil\" spec\n");
 				return -1;
 			}
-			if (get_rate(&opt.ceil.rate, *argv)) {
+			if (get_rate64(&ceil64, *argv)) {
 				explain1("ceil");
 				return -1;
 			}
 			ok++;
 		} else if (strcmp(*argv, "rate") == 0) {
 			NEXT_ARG();
-			if (opt.rate.rate) {
+			if (rate64) {
 				fprintf(stderr, "Double \"rate\" spec\n");
 				return -1;
 			}
-			if (get_rate(&opt.rate.rate, *argv)) {
+			if (get_rate64(&rate64, *argv)) {
 				explain1("rate");
 				return -1;
 			}
@@ -196,20 +208,26 @@
 		argc--; argv++;
 	}
 
-/*	if (!ok)
+	/*	if (!ok)
 		return 0;*/
 
-	if (opt.rate.rate == 0) {
+	if (!rate64) {
 		fprintf(stderr, "\"rate\" is required.\n");
 		return -1;
 	}
 	/* if ceil params are missing, use the same as rate */
-	if (!opt.ceil.rate) opt.ceil = opt.rate;
+	if (!ceil64)
+		ceil64 = rate64;
+
+	opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64;
+	opt.ceil.rate = (ceil64 >= (1ULL << 32)) ? ~0U : ceil64;
 
 	/* compute minimal allowed burst from rate; mtu is added here to make
 	   sute that buffer is larger than mtu and to have some safeguard space */
-	if (!buffer) buffer = opt.rate.rate / get_hz() + mtu;
-	if (!cbuffer) cbuffer = opt.ceil.rate / get_hz() + mtu;
+	if (!buffer)
+		buffer = rate64 / get_hz() + mtu;
+	if (!cbuffer)
+		cbuffer = ceil64 / get_hz() + mtu;
 
 	opt.ceil.overhead = overhead;
 	opt.rate.overhead = overhead;
@@ -221,16 +239,23 @@
 		fprintf(stderr, "htb: failed to calculate rate table.\n");
 		return -1;
 	}
-	opt.buffer = tc_calc_xmittime(opt.rate.rate, buffer);
+	opt.buffer = tc_calc_xmittime(rate64, buffer);
 
 	if (tc_calc_rtable(&opt.ceil, ctab, ccell_log, mtu, linklayer) < 0) {
 		fprintf(stderr, "htb: failed to calculate ceil rate table.\n");
 		return -1;
 	}
-	opt.cbuffer = tc_calc_xmittime(opt.ceil.rate, cbuffer);
+	opt.cbuffer = tc_calc_xmittime(ceil64, cbuffer);
 
 	tail = NLMSG_TAIL(n);
 	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+
+	if (rate64 >= (1ULL << 32))
+		addattr_l(n, 1124, TCA_HTB_RATE64, &rate64, sizeof(rate64));
+
+	if (ceil64 >= (1ULL << 32))
+		addattr_l(n, 1224, TCA_HTB_CEIL64, &ceil64, sizeof(ceil64));
+
 	addattr_l(n, 2024, TCA_HTB_PARMS, &opt, sizeof(opt));
 	addattr_l(n, 3024, TCA_HTB_RTAB, rtab, 1024);
 	addattr_l(n, 4024, TCA_HTB_CTAB, ctab, 1024);
@@ -240,62 +265,88 @@
 
 static int htb_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
 {
-	struct rtattr *tb[TCA_HTB_RTAB+1];
+	struct rtattr *tb[TCA_HTB_MAX + 1];
 	struct tc_htb_opt *hopt;
 	struct tc_htb_glob *gopt;
 	double buffer,cbuffer;
+	unsigned int linklayer;
+	__u64 rate64, ceil64;
 	SPRINT_BUF(b1);
 	SPRINT_BUF(b2);
 	SPRINT_BUF(b3);
+	SPRINT_BUF(b4);
 
 	if (opt == NULL)
 		return 0;
 
-	parse_rtattr_nested(tb, TCA_HTB_RTAB, opt);
+	parse_rtattr_nested(tb, TCA_HTB_MAX, opt);
 
 	if (tb[TCA_HTB_PARMS]) {
-
-	    hopt = RTA_DATA(tb[TCA_HTB_PARMS]);
-	    if (RTA_PAYLOAD(tb[TCA_HTB_PARMS])  < sizeof(*hopt)) return -1;
+		hopt = RTA_DATA(tb[TCA_HTB_PARMS]);
+		if (RTA_PAYLOAD(tb[TCA_HTB_PARMS])  < sizeof(*hopt)) return -1;
 
 		if (!hopt->level) {
 			fprintf(f, "prio %d ", (int)hopt->prio);
 			if (show_details)
 				fprintf(f, "quantum %d ", (int)hopt->quantum);
 		}
-	    fprintf(f, "rate %s ", sprint_rate(hopt->rate.rate, b1));
-	    buffer = tc_calc_xmitsize(hopt->rate.rate, hopt->buffer);
-	    fprintf(f, "ceil %s ", sprint_rate(hopt->ceil.rate, b1));
-	    cbuffer = tc_calc_xmitsize(hopt->ceil.rate, hopt->cbuffer);
-	    if (show_details) {
-		fprintf(f, "burst %s/%u mpu %s overhead %s ",
-			sprint_size(buffer, b1),
-			1<<hopt->rate.cell_log,
-			sprint_size(hopt->rate.mpu&0xFF, b2),
-			sprint_size((hopt->rate.mpu>>8)&0xFF, b3));
-		fprintf(f, "cburst %s/%u mpu %s overhead %s ",
-			sprint_size(cbuffer, b1),
-			1<<hopt->ceil.cell_log,
-			sprint_size(hopt->ceil.mpu&0xFF, b2),
-			sprint_size((hopt->ceil.mpu>>8)&0xFF, b3));
-		fprintf(f, "level %d ", (int)hopt->level);
-	    } else {
-		fprintf(f, "burst %s ", sprint_size(buffer, b1));
-		fprintf(f, "cburst %s ", sprint_size(cbuffer, b1));
-	    }
-	    if (show_raw)
-		fprintf(f, "buffer [%08x] cbuffer [%08x] ",
-			hopt->buffer,hopt->cbuffer);
+
+		rate64 = hopt->rate.rate;
+		if (tb[TCA_HTB_RATE64] &&
+		    RTA_PAYLOAD(tb[TCA_HTB_RATE64]) >= sizeof(rate64)) {
+			rate64 = rta_getattr_u64(tb[TCA_HTB_RATE64]);
+		}
+
+		ceil64 = hopt->ceil.rate;
+		if (tb[TCA_HTB_CEIL64] &&
+		    RTA_PAYLOAD(tb[TCA_HTB_CEIL64]) >= sizeof(ceil64))
+			ceil64 = rta_getattr_u64(tb[TCA_HTB_CEIL64]);
+
+		fprintf(f, "rate %s ", sprint_rate(rate64, b1));
+		if (hopt->rate.overhead)
+			fprintf(f, "overhead %u ", hopt->rate.overhead);
+		buffer = tc_calc_xmitsize(rate64, hopt->buffer);
+
+		fprintf(f, "ceil %s ", sprint_rate(ceil64, b1));
+		cbuffer = tc_calc_xmitsize(ceil64, hopt->cbuffer);
+		linklayer = (hopt->rate.linklayer & TC_LINKLAYER_MASK);
+		if (linklayer > TC_LINKLAYER_ETHERNET || show_details)
+			fprintf(f, "linklayer %s ", sprint_linklayer(linklayer, b4));
+		if (show_details) {
+			fprintf(f, "burst %s/%u mpu %s overhead %s ",
+				sprint_size(buffer, b1),
+				1<<hopt->rate.cell_log,
+				sprint_size(hopt->rate.mpu&0xFF, b2),
+				sprint_size((hopt->rate.mpu>>8)&0xFF, b3));
+			fprintf(f, "cburst %s/%u mpu %s overhead %s ",
+				sprint_size(cbuffer, b1),
+				1<<hopt->ceil.cell_log,
+				sprint_size(hopt->ceil.mpu&0xFF, b2),
+				sprint_size((hopt->ceil.mpu>>8)&0xFF, b3));
+			fprintf(f, "level %d ", (int)hopt->level);
+		} else {
+			fprintf(f, "burst %s ", sprint_size(buffer, b1));
+			fprintf(f, "cburst %s ", sprint_size(cbuffer, b1));
+		}
+		if (show_raw)
+			fprintf(f, "buffer [%08x] cbuffer [%08x] ",
+				hopt->buffer,hopt->cbuffer);
 	}
 	if (tb[TCA_HTB_INIT]) {
-	    gopt = RTA_DATA(tb[TCA_HTB_INIT]);
-	    if (RTA_PAYLOAD(tb[TCA_HTB_INIT])  < sizeof(*gopt)) return -1;
+		gopt = RTA_DATA(tb[TCA_HTB_INIT]);
+		if (RTA_PAYLOAD(tb[TCA_HTB_INIT])  < sizeof(*gopt)) return -1;
 
-	    fprintf(f, "r2q %d default %x direct_packets_stat %u",
-		    gopt->rate2quantum,gopt->defcls,gopt->direct_pkts);
+		fprintf(f, "r2q %d default %x direct_packets_stat %u",
+			gopt->rate2quantum,gopt->defcls,gopt->direct_pkts);
 		if (show_details)
 			fprintf(f," ver %d.%d",gopt->version >> 16,gopt->version & 0xffff);
 	}
+	if (tb[TCA_HTB_DIRECT_QLEN] &&
+	    RTA_PAYLOAD(tb[TCA_HTB_DIRECT_QLEN]) >= sizeof(__u32)) {
+		__u32 direct_qlen = rta_getattr_u32(tb[TCA_HTB_DIRECT_QLEN]);
+
+		fprintf(f, " direct_qlen %u", direct_qlen);
+	}
 	return 0;
 }
 
@@ -323,13 +374,3 @@
 	.parse_copt	= htb_parse_class_opt,
 	.print_copt	= htb_print_opt,
 };
-
-/* for testing of old one */
-struct qdisc_util htb2_qdisc_util = {
-	.id		=  "htb2",
-	.parse_qopt	= htb_parse_opt,
-	.print_qopt	= htb_print_opt,
-	.print_xstats 	= htb_print_xstats,
-	.parse_copt	= htb_parse_class_opt,
-	.print_copt	= htb_print_opt,
-};
diff --git a/tc/q_mqprio.c b/tc/q_mqprio.c
index b2d8c12..fa1022b 100644
--- a/tc/q_mqprio.c
+++ b/tc/q_mqprio.c
@@ -104,7 +104,7 @@
 	return 0;
 }
 
-int mqprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+static int mqprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
 {
 	int i;
 	struct tc_mqprio_qopt *qopt;
diff --git a/tc/q_multiq.c b/tc/q_multiq.c
index 7ff9e74..f4f41f7 100644
--- a/tc/q_multiq.c
+++ b/tc/q_multiq.c
@@ -12,14 +12,13 @@
  * 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, write to the Free Software Foundation, Inc., 59 Temple
- * Place - Suite 330, Boston, MA 02111-1307 USA.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
  *
  * Author: Alexander Duyck <alexander.h.duyck@intel.com>
  *
  * Original Authors:	PJ Waskiewicz, <peter.p.waskiewicz.jr@intel.com> (RR)
- * 			Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> (from PRIO)
+ *			Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> (from PRIO)
  *
  */
 
@@ -61,7 +60,7 @@
 	return 0;
 }
 
-int multiq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+static int multiq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
 {
 	struct tc_multiq_qopt *qopt;
 
diff --git a/tc/q_netem.c b/tc/q_netem.c
index 360080c..cd990a0 100644
--- a/tc/q_netem.c
+++ b/tc/q_netem.c
@@ -38,6 +38,7 @@
 "                 [ loss random PERCENT [CORRELATION]]\n" \
 "                 [ loss state P13 [P31 [P32 [P23 P14]]]\n" \
 "                 [ loss gemodel PERCENT [R [1-H [1-K]]]\n" \
+"                 [ ecn ]\n" \
 "                 [ reorder PRECENT [CORRELATION] [ gap DISTANCE ]]\n" \
 "                 [ rate RATE [PACKETOVERHEAD] [CELLSIZE] [CELLOVERHEAD]]\n");
 }
@@ -86,12 +87,12 @@
 	return 0;
 }
 
-void print_percent(char *buf, int len, __u32 per)
+static void print_percent(char *buf, int len, __u32 per)
 {
 	snprintf(buf, len, "%g%%", 100. * (double) per / max_percent_value);
 }
 
-char * sprint_percent(__u32 per, char *buf)
+static char * sprint_percent(__u32 per, char *buf)
 {
 	print_percent(buf, SPRINT_BSIZE-1, per);
 	return buf;
@@ -146,6 +147,8 @@
 }
 
 #define NEXT_IS_NUMBER() (NEXT_ARG_OK() && isdigit(argv[1][0]))
+#define NEXT_IS_SIGNED_NUMBER() \
+	(NEXT_ARG_OK() && (isdigit(argv[1][0]) || argv[1][0] == '-'))
 
 /* Adjust for the fact that psched_ticks aren't always usecs
    (based on kernel PSCHED_CLOCK configuration */
@@ -180,6 +183,7 @@
 	__s16 *dist_data = NULL;
 	__u16 loss_type = NETEM_LOSS_UNSPEC;
 	int present[__TCA_NETEM_MAX];
+	__u64 rate64 = 0;
 
 	memset(&cor, 0, sizeof(cor));
 	memset(&reorder, 0, sizeof(reorder));
@@ -259,6 +263,7 @@
 				set_percent(&gimodel.p31, 1. - p13);
 				set_percent(&gimodel.p32, 0);
 				set_percent(&gimodel.p23, 1.);
+				set_percent(&gimodel.p14, 0);
 				loss_type = NETEM_LOSS_GI;
 
 				if (!NEXT_IS_NUMBER())
@@ -284,6 +289,13 @@
 					explain1("loss p23");
 					return -1;
 				}
+				if (!NEXT_IS_NUMBER())
+					continue;
+				NEXT_ARG();
+				if (get_percent(&gimodel.p14, *argv)) {
+					explain1("loss p14");
+					return -1;
+				}
 
 			} else if (!strcmp(*argv, "gemodel")) {
 				NEXT_ARG();
@@ -295,7 +307,7 @@
 				/* set defaults */
 				set_percent(&gemodel.r, 1.);
 				set_percent(&gemodel.h, 0);
-				set_percent(&gemodel.k1, 1.);
+				set_percent(&gemodel.k1, 0);
 				loss_type = NETEM_LOSS_GE;
 
 				if (!NEXT_IS_NUMBER())
@@ -313,6 +325,10 @@
 					explain1("loss gemodel h");
 					return -1;
 				}
+				/* netem option is "1-h" but kernel
+				 * expects "h".
+				 */
+				gemodel.h = max_percent_value - gemodel.h;
 
 				if (!NEXT_IS_NUMBER())
 					continue;
@@ -326,6 +342,8 @@
 					*argv);
 				return -1;
 			}
+		} else if (matches(*argv, "ecn") == 0) {
+				present[TCA_NETEM_ECN] = 1;
 		} else if (matches(*argv, "reorder") == 0) {
 			NEXT_ARG();
 			present[TCA_NETEM_REORDER] = 1;
@@ -386,11 +404,11 @@
 		} else if (matches(*argv, "rate") == 0) {
 			++present[TCA_NETEM_RATE];
 			NEXT_ARG();
-			if (get_rate(&rate.rate, *argv)) {
+			if (get_rate64(&rate64, *argv)) {
 				explain1("rate");
 				return -1;
 			}
-			if (NEXT_IS_NUMBER()) {
+			if (NEXT_IS_SIGNED_NUMBER()) {
 				NEXT_ARG();
 				if (get_s32(&rate.packet_overhead, *argv, 0)) {
 					explain1("rate");
@@ -404,7 +422,7 @@
 					return -1;
 				}
 			}
-			if (NEXT_IS_NUMBER()) {
+			if (NEXT_IS_SIGNED_NUMBER()) {
 				NEXT_ARG();
 				if (get_s32(&rate.cell_overhead, *argv, 0)) {
 					explain1("rate");
@@ -437,6 +455,14 @@
 		return -1;
 	}
 
+	if (present[TCA_NETEM_ECN]) {
+		if (opt.loss <= 0 && loss_type == NETEM_LOSS_UNSPEC) {
+			fprintf(stderr, "ecn requested without loss model\n");
+			explain();
+			return -1;
+		}
+	}
+
 	if (dist_data && (opt.latency == 0 || opt.jitter == 0)) {
 		fprintf(stderr, "distribution specified but no latency and jitter values\n");
 		explain();
@@ -454,6 +480,11 @@
 	    addattr_l(n, 1024, TCA_NETEM_REORDER, &reorder, sizeof(reorder)) < 0)
 		return -1;
 
+	if (present[TCA_NETEM_ECN] &&
+	    addattr_l(n, 1024, TCA_NETEM_ECN, &present[TCA_NETEM_ECN],
+		      sizeof(present[TCA_NETEM_ECN])) < 0)
+			return -1;
+
 	if (present[TCA_NETEM_CORRUPT] &&
 	    addattr_l(n, 1024, TCA_NETEM_CORRUPT, &corrupt, sizeof(corrupt)) < 0)
 		return -1;
@@ -474,13 +505,22 @@
 			fprintf(stderr, "loss in the weeds!\n");
 			return -1;
 		}
-		
+
 		addattr_nest_end(n, start);
 	}
 
-	if (present[TCA_NETEM_RATE] &&
-	    addattr_l(n, 1024, TCA_NETEM_RATE, &rate, sizeof(rate)) < 0)
-		return -1;
+	if (present[TCA_NETEM_RATE]) {
+		if (rate64 >= (1ULL << 32)) {
+			if (addattr_l(n, 1024,
+				      TCA_NETEM_RATE64, &rate64, sizeof(rate64)) < 0)
+				return -1;
+			rate.rate = ~0U;
+		} else {
+			rate.rate = rate64;
+		}
+		if (addattr_l(n, 1024, TCA_NETEM_RATE, &rate, sizeof(rate)) < 0)
+			return -1;
+	}
 
 	if (dist_data) {
 		if (addattr_l(n, MAX_DIST * sizeof(dist_data[0]),
@@ -500,9 +540,11 @@
 	const struct tc_netem_corrupt *corrupt = NULL;
 	const struct tc_netem_gimodel *gimodel = NULL;
 	const struct tc_netem_gemodel *gemodel = NULL;
+	int *ecn = NULL;
 	struct tc_netem_qopt qopt;
 	const struct tc_netem_rate *rate = NULL;
 	int len = RTA_PAYLOAD(opt) - sizeof(qopt);
+	__u64 rate64 = 0;
 	SPRINT_BUF(b1);
 
 	if (opt == NULL)
@@ -539,15 +581,25 @@
 
 			parse_rtattr_nested(lb, NETEM_LOSS_MAX, tb[TCA_NETEM_LOSS]);
 			if (lb[NETEM_LOSS_GI])
-				gemodel = RTA_DATA(lb[NETEM_LOSS_GI]);
+				gimodel = RTA_DATA(lb[NETEM_LOSS_GI]);
 			if (lb[NETEM_LOSS_GE])
 				gemodel = RTA_DATA(lb[NETEM_LOSS_GE]);
-		}			
+		}
 		if (tb[TCA_NETEM_RATE]) {
 			if (RTA_PAYLOAD(tb[TCA_NETEM_RATE]) < sizeof(*rate))
 				return -1;
 			rate = RTA_DATA(tb[TCA_NETEM_RATE]);
 		}
+		if (tb[TCA_NETEM_ECN]) {
+			if (RTA_PAYLOAD(tb[TCA_NETEM_ECN]) < sizeof(*ecn))
+				return -1;
+			ecn = RTA_DATA(tb[TCA_NETEM_ECN]);
+		}
+		if (tb[TCA_NETEM_RATE64]) {
+			if (RTA_PAYLOAD(tb[TCA_NETEM_RATE64]) < sizeof(rate64))
+				return -1;
+			rate64 = rta_getattr_u64(tb[TCA_NETEM_RATE64]);
+		}
 	}
 
 	fprintf(f, "limit %d", qopt.limit);
@@ -577,10 +629,11 @@
 	}
 
 	if (gemodel) {
-		fprintf(f, "loss gemodel p %s",
+		fprintf(f, " loss gemodel p %s",
 			sprint_percent(gemodel->p, b1));
 		fprintf(f, " r %s", sprint_percent(gemodel->r, b1));
-		fprintf(f, " 1-h %s", sprint_percent(gemodel->h, b1));
+		fprintf(f, " 1-h %s", sprint_percent(max_percent_value -
+						     gemodel->h, b1));
 		fprintf(f, " 1-k %s", sprint_percent(gemodel->k1, b1));
 	}
 
@@ -608,7 +661,10 @@
 	}
 
 	if (rate && rate->rate) {
-		fprintf(f, " rate %s", sprint_rate(rate->rate, b1));
+		if (rate64)
+			fprintf(f, " rate %s", sprint_rate(rate64, b1));
+		else
+			fprintf(f, " rate %s", sprint_rate(rate->rate, b1));
 		if (rate->packet_overhead)
 			fprintf(f, " packetoverhead %d", rate->packet_overhead);
 		if (rate->cell_size)
@@ -617,9 +673,13 @@
 			fprintf(f, " celloverhead %d", rate->cell_overhead);
 	}
 
+	if (ecn)
+		fprintf(f, " ecn ");
+
 	if (qopt.gap)
 		fprintf(f, " gap %lu", (unsigned long)qopt.gap);
 
+
 	return 0;
 }
 
diff --git a/tc/q_pie.c b/tc/q_pie.c
new file mode 100644
index 0000000..193b05d
--- /dev/null
+++ b/tc/q_pie.c
@@ -0,0 +1,218 @@
+/* Copyright (C) 2013 Cisco Systems, Inc, 2013.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Author: Vijay Subramanian <vijaynsu@cisco.com>
+ * Author: Mythili Prabhu <mysuryan@cisco.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <math.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... pie [ limit PACKETS ][ target TIME us]\n");
+	fprintf(stderr, "              [ tupdate TIME us][ alpha ALPHA ]");
+	fprintf(stderr, "[beta BETA ][bytemode | nobytemode][ecn | noecn ]\n");
+}
+
+#define ALPHA_MAX 32
+#define ALPHA_MIN 0
+#define BETA_MAX 32
+#define BETA_MIN 0
+
+static int pie_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	unsigned int limit   = 0;
+	unsigned int target  = 0;
+	unsigned int tupdate = 0;
+	unsigned int alpha   = 0;
+	unsigned int beta    = 0;
+	int ecn = -1;
+	int bytemode = -1;
+	struct rtattr *tail;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&limit, *argv, 0)) {
+				fprintf(stderr, "Illegal \"limit\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "target") == 0) {
+			NEXT_ARG();
+			if (get_time(&target, *argv)) {
+				fprintf(stderr, "Illegal \"target\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "tupdate") == 0) {
+			NEXT_ARG();
+			if (get_time(&tupdate, *argv)) {
+				fprintf(stderr, "Illegal \"tupdate\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "alpha") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&alpha, *argv, 0) ||
+			    (alpha > ALPHA_MAX) || (alpha < ALPHA_MIN)) {
+				fprintf(stderr, "Illegal \"alpha\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "beta") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&beta, *argv, 0) ||
+			    (beta > BETA_MAX) || (beta < BETA_MIN)) {
+				fprintf(stderr, "Illegal \"beta\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "ecn") == 0) {
+			ecn = 1;
+		} else if (strcmp(*argv, "noecn") == 0) {
+			ecn = 0;
+		} else if (strcmp(*argv, "bytemode") == 0) {
+			bytemode = 1;
+		} else if (strcmp(*argv, "nobytemode") == 0) {
+			bytemode = 0;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--;
+		argv++;
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	if (limit)
+		addattr_l(n, 1024, TCA_PIE_LIMIT, &limit, sizeof(limit));
+	if (tupdate)
+		addattr_l(n, 1024, TCA_PIE_TUPDATE, &tupdate, sizeof(tupdate));
+	if (target)
+		addattr_l(n, 1024, TCA_PIE_TARGET, &target, sizeof(target));
+	if (alpha)
+		addattr_l(n, 1024, TCA_PIE_ALPHA, &alpha, sizeof(alpha));
+	if (beta)
+		addattr_l(n, 1024, TCA_PIE_BETA, &beta, sizeof(beta));
+	if (ecn != -1)
+		addattr_l(n, 1024, TCA_PIE_ECN, &ecn, sizeof(ecn));
+	if (bytemode != -1)
+		addattr_l(n, 1024, TCA_PIE_BYTEMODE, &bytemode,
+			  sizeof(bytemode));
+
+	tail->rta_len = (void *)NLMSG_TAIL(n) - (void *)tail;
+	return 0;
+}
+
+static int pie_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_PIE_MAX + 1];
+	unsigned int limit;
+	unsigned int tupdate;
+	unsigned int target;
+	unsigned int alpha;
+	unsigned int beta;
+	unsigned ecn;
+	unsigned bytemode;
+	SPRINT_BUF(b1);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_PIE_MAX, opt);
+
+	if (tb[TCA_PIE_LIMIT] &&
+	    RTA_PAYLOAD(tb[TCA_PIE_LIMIT]) >= sizeof(__u32)) {
+		limit = rta_getattr_u32(tb[TCA_PIE_LIMIT]);
+		fprintf(f, "limit %up ", limit);
+	}
+	if (tb[TCA_PIE_TARGET] &&
+	    RTA_PAYLOAD(tb[TCA_PIE_TARGET]) >= sizeof(__u32)) {
+		target = rta_getattr_u32(tb[TCA_PIE_TARGET]);
+		fprintf(f, "target %s ", sprint_time(target, b1));
+	}
+	if (tb[TCA_PIE_TUPDATE] &&
+	    RTA_PAYLOAD(tb[TCA_PIE_TUPDATE]) >= sizeof(__u32)) {
+		tupdate = rta_getattr_u32(tb[TCA_PIE_TUPDATE]);
+		fprintf(f, "tupdate %s ", sprint_time(tupdate, b1));
+	}
+	if (tb[TCA_PIE_ALPHA] &&
+	    RTA_PAYLOAD(tb[TCA_PIE_ALPHA]) >= sizeof(__u32)) {
+		alpha = rta_getattr_u32(tb[TCA_PIE_ALPHA]);
+		fprintf(f, "alpha %u ", alpha);
+	}
+	if (tb[TCA_PIE_BETA] &&
+	    RTA_PAYLOAD(tb[TCA_PIE_BETA]) >= sizeof(__u32)) {
+		beta = rta_getattr_u32(tb[TCA_PIE_BETA]);
+		fprintf(f, "beta %u ", beta);
+	}
+
+	if (tb[TCA_PIE_ECN] && RTA_PAYLOAD(tb[TCA_PIE_ECN]) >= sizeof(__u32)) {
+		ecn = rta_getattr_u32(tb[TCA_PIE_ECN]);
+		if (ecn)
+			fprintf(f, "ecn ");
+	}
+
+	if (tb[TCA_PIE_BYTEMODE] &&
+	    RTA_PAYLOAD(tb[TCA_PIE_BYTEMODE]) >= sizeof(__u32)) {
+		bytemode = rta_getattr_u32(tb[TCA_PIE_BYTEMODE]);
+		if (bytemode)
+			fprintf(f, "bytemode ");
+	}
+
+	return 0;
+}
+
+static int pie_print_xstats(struct qdisc_util *qu, FILE *f,
+			    struct rtattr *xstats)
+{
+	struct tc_pie_xstats *st;
+
+	if (xstats == NULL)
+		return 0;
+
+	if (RTA_PAYLOAD(xstats) < sizeof(*st))
+		return -1;
+
+	st = RTA_DATA(xstats);
+	/*prob is returned as a fracion of maximum integer value */
+	fprintf(f, "prob %f delay %uus avg_dq_rate %u\n",
+		(double)st->prob / (double)0xffffffff, st->delay,
+		st->avg_dq_rate);
+	fprintf(f, "pkts_in %u overlimit %u dropped %u maxq %u ecn_mark %u\n",
+		st->packets_in, st->overlimit, st->dropped, st->maxq,
+		st->ecn_mark);
+	return 0;
+
+}
+
+struct qdisc_util pie_qdisc_util = {
+	.id = "pie",
+	.parse_qopt	= pie_parse_opt,
+	.print_qopt	= pie_print_opt,
+	.print_xstats	= pie_print_xstats,
+};
diff --git a/tc/q_prio.c b/tc/q_prio.c
index 79b4fd0..bacc702 100644
--- a/tc/q_prio.c
+++ b/tc/q_prio.c
@@ -67,7 +67,7 @@
 				fprintf(stderr, "Illegal \"priomap\" element\n");
 				return -1;
 			}
-			if (band > opt.bands) {
+			if (band >= opt.bands) {
 				fprintf(stderr, "\"priomap\" element is out of bands\n");
 				return -1;
 			}
diff --git a/tc/q_rr.c b/tc/q_rr.c
index 79072ad..e8a9165 100644
--- a/tc/q_rr.c
+++ b/tc/q_rr.c
@@ -88,7 +88,7 @@
 	return 0;
 }
 
-int rr_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+static int rr_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
 {
 	int i;
 	struct tc_prio_qopt *qopt;
diff --git a/tc/q_sfq.c b/tc/q_sfq.c
index 96f63ff..50846a9 100644
--- a/tc/q_sfq.c
+++ b/tc/q_sfq.c
@@ -159,11 +159,11 @@
 			fprintf(stderr, "Required parameter (redflowlimit) is missing\n");
 			return -1;
 		}
-		/* Compute default min/max thresholds based on 
+		/* Compute default min/max thresholds based on
 		   Sally Floyd's recommendations:
 		   http://www.icir.org/floyd/REDparameters.txt
 		*/
-		if (!opt.qth_max) 
+		if (!opt.qth_max)
 			opt.qth_max = opt.limit / 4;
 		if (!opt.qth_min)
 			opt.qth_min = opt.qth_max / 3;
diff --git a/tc/q_tbf.c b/tc/q_tbf.c
index dc556fe..2d56331 100644
--- a/tc/q_tbf.c
+++ b/tc/q_tbf.c
@@ -30,9 +30,9 @@
 	fprintf(stderr, "[ overhead BYTES ] [ linklayer TYPE ]\n");
 }
 
-static void explain1(char *arg)
+static void explain1(const char *arg, const char *val)
 {
-	fprintf(stderr, "Illegal \"%s\"\n", arg);
+	fprintf(stderr, "tbf: illegal value for \"%s\": \"%s\"\n", arg, val);
 }
 
 
@@ -47,140 +47,164 @@
 	unsigned short overhead=0;
 	unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
 	struct rtattr *tail;
+	__u64 rate64 = 0, prate64 = 0;
 
 	memset(&opt, 0, sizeof(opt));
 
 	while (argc > 0) {
 		if (matches(*argv, "limit") == 0) {
 			NEXT_ARG();
-			if (opt.limit || latency) {
-				fprintf(stderr, "Double \"limit/latency\" spec\n");
+			if (opt.limit) {
+				fprintf(stderr, "tbf: duplicate \"limit\" specification\n");
+				return -1;
+			}
+			if (latency) {
+				fprintf(stderr, "tbf: specifying both \"latency\" and \"limit\" is not allowed\n");
 				return -1;
 			}
 			if (get_size(&opt.limit, *argv)) {
-				explain1("limit");
+				explain1("limit", *argv);
 				return -1;
 			}
 			ok++;
 		} else if (matches(*argv, "latency") == 0) {
 			NEXT_ARG();
-			if (opt.limit || latency) {
-				fprintf(stderr, "Double \"limit/latency\" spec\n");
+			if (latency) {
+				fprintf(stderr, "tbf: duplicate \"latency\" specification\n");
+				return -1;
+			}
+			if (opt.limit) {
+				fprintf(stderr, "tbf: specifying both \"limit\" and \"/latency\" is not allowed\n");
 				return -1;
 			}
 			if (get_time(&latency, *argv)) {
-				explain1("latency");
+				explain1("latency", *argv);
 				return -1;
 			}
 			ok++;
 		} else if (matches(*argv, "burst") == 0 ||
 			strcmp(*argv, "buffer") == 0 ||
 			strcmp(*argv, "maxburst") == 0) {
+			const char *parm_name = *argv;
 			NEXT_ARG();
 			if (buffer) {
-				fprintf(stderr, "Double \"buffer/burst\" spec\n");
+				fprintf(stderr, "tbf: duplicate \"buffer/burst/maxburst\" specification\n");
 				return -1;
 			}
 			if (get_size_and_cell(&buffer, &Rcell_log, *argv) < 0) {
-				explain1("buffer");
+				explain1(parm_name, *argv);
 				return -1;
 			}
 			ok++;
 		} else if (strcmp(*argv, "mtu") == 0 ||
 			   strcmp(*argv, "minburst") == 0) {
+			const char *parm_name = *argv;
 			NEXT_ARG();
 			if (mtu) {
-				fprintf(stderr, "Double \"mtu/minburst\" spec\n");
+				fprintf(stderr, "tbf: duplicate \"mtu/minburst\" specification\n");
 				return -1;
 			}
 			if (get_size_and_cell(&mtu, &Pcell_log, *argv) < 0) {
-				explain1("mtu");
+				explain1(parm_name, *argv);
 				return -1;
 			}
 			ok++;
 		} else if (strcmp(*argv, "mpu") == 0) {
 			NEXT_ARG();
 			if (mpu) {
-				fprintf(stderr, "Double \"mpu\" spec\n");
+				fprintf(stderr, "tbf: duplicate \"mpu\" specification\n");
 				return -1;
 			}
 			if (get_size(&mpu, *argv)) {
-				explain1("mpu");
+				explain1("mpu", *argv);
 				return -1;
 			}
 			ok++;
 		} else if (strcmp(*argv, "rate") == 0) {
 			NEXT_ARG();
-			if (opt.rate.rate) {
-				fprintf(stderr, "Double \"rate\" spec\n");
+			if (rate64) {
+				fprintf(stderr, "tbf: duplicate \"rate\" specification\n");
 				return -1;
 			}
-			if (get_rate(&opt.rate.rate, *argv)) {
-				explain1("rate");
+			if (get_rate64(&rate64, *argv)) {
+				explain1("rate", *argv);
 				return -1;
 			}
 			ok++;
 		} else if (matches(*argv, "peakrate") == 0) {
 			NEXT_ARG();
-			if (opt.peakrate.rate) {
-				fprintf(stderr, "Double \"peakrate\" spec\n");
+			if (prate64) {
+				fprintf(stderr, "tbf: duplicate \"peakrate\" specification\n");
 				return -1;
 			}
-			if (get_rate(&opt.peakrate.rate, *argv)) {
-				explain1("peakrate");
+			if (get_rate64(&prate64, *argv)) {
+				explain1("peakrate", *argv);
 				return -1;
 			}
 			ok++;
 		} else if (matches(*argv, "overhead") == 0) {
 			NEXT_ARG();
 			if (overhead) {
-				fprintf(stderr, "Double \"overhead\" spec\n");
+				fprintf(stderr, "tbf: duplicate \"overhead\" specification\n");
 				return -1;
 			}
 			if (get_u16(&overhead, *argv, 10)) {
-				explain1("overhead"); return -1;
+				explain1("overhead", *argv); return -1;
 			}
 		} else if (matches(*argv, "linklayer") == 0) {
 			NEXT_ARG();
 			if (get_linklayer(&linklayer, *argv)) {
-				explain1("linklayer"); return -1;
+				explain1("linklayer", *argv); return -1;
 			}
 		} else if (strcmp(*argv, "help") == 0) {
 			explain();
 			return -1;
 		} else {
-			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			fprintf(stderr, "tbf: unknown parameter \"%s\"\n", *argv);
 			explain();
 			return -1;
 		}
 		argc--; argv++;
 	}
 
-	if (!ok) {
-		explain();
-		return -1;
-	}
+        int verdict = 0;
 
-	if (opt.rate.rate == 0 || !buffer) {
-		fprintf(stderr, "Both \"rate\" and \"burst\" are required.\n");
-		return -1;
+        /* Be nice to the user: try to emit all error messages in
+         * one go rather than reveal one more problem when a
+         * previous one has been fixed.
+         */
+	if (rate64 == 0) {
+		fprintf(stderr, "tbf: the \"rate\" parameter is mandatory.\n");
+		verdict = -1;
 	}
-	if (opt.peakrate.rate) {
+	if (!buffer) {
+		fprintf(stderr, "tbf: the \"burst\" parameter is mandatory.\n");
+		verdict = -1;
+	}
+	if (prate64) {
 		if (!mtu) {
-			fprintf(stderr, "\"mtu\" is required, if \"peakrate\" is requested.\n");
-			return -1;
+			fprintf(stderr, "tbf: when \"peakrate\" is specified, \"mtu\" must also be specified.\n");
+			verdict = -1;
 		}
 	}
 
 	if (opt.limit == 0 && latency == 0) {
-		fprintf(stderr, "Either \"limit\" or \"latency\" are required.\n");
-		return -1;
+		fprintf(stderr, "tbf: either \"limit\" or \"latency\" is required.\n");
+		verdict = -1;
 	}
 
+        if (verdict != 0) {
+                explain();
+                return verdict;
+        }
+
+	opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64;
+	opt.peakrate.rate = (prate64 >= (1ULL << 32)) ? ~0U : prate64;
+
 	if (opt.limit == 0) {
-		double lim = opt.rate.rate*(double)latency/TIME_UNITS_PER_SEC + buffer;
-		if (opt.peakrate.rate) {
-			double lim2 = opt.peakrate.rate*(double)latency/TIME_UNITS_PER_SEC + mtu;
+		double lim = rate64*(double)latency/TIME_UNITS_PER_SEC + buffer;
+		if (prate64) {
+			double lim2 = prate64*(double)latency/TIME_UNITS_PER_SEC + mtu;
 			if (lim2 < lim)
 				lim = lim2;
 		}
@@ -190,7 +214,7 @@
 	opt.rate.mpu      = mpu;
 	opt.rate.overhead = overhead;
 	if (tc_calc_rtable(&opt.rate, rtab, Rcell_log, mtu, linklayer) < 0) {
-		fprintf(stderr, "TBF: failed to calculate rate table.\n");
+		fprintf(stderr, "tbf: failed to calculate rate table.\n");
 		return -1;
 	}
 	opt.buffer = tc_calc_xmittime(opt.rate.rate, buffer);
@@ -199,7 +223,7 @@
 		opt.peakrate.mpu      = mpu;
 		opt.peakrate.overhead = overhead;
 		if (tc_calc_rtable(&opt.peakrate, ptab, Pcell_log, mtu, linklayer) < 0) {
-			fprintf(stderr, "TBF: failed to calculate peak rate table.\n");
+			fprintf(stderr, "tbf: failed to calculate peak rate table.\n");
 			return -1;
 		}
 		opt.mtu = tc_calc_xmittime(opt.peakrate.rate, mtu);
@@ -208,26 +232,36 @@
 	tail = NLMSG_TAIL(n);
 	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
 	addattr_l(n, 2024, TCA_TBF_PARMS, &opt, sizeof(opt));
+	addattr_l(n, 2124, TCA_TBF_BURST, &buffer, sizeof(buffer));
+	if (rate64 >= (1ULL << 32))
+		addattr_l(n, 2124, TCA_TBF_RATE64, &rate64, sizeof(rate64));
 	addattr_l(n, 3024, TCA_TBF_RTAB, rtab, 1024);
-	if (opt.peakrate.rate)
+	if (opt.peakrate.rate) {
+		if (prate64 >= (1ULL << 32))
+			addattr_l(n, 3124, TCA_TBF_PRATE64, &prate64, sizeof(prate64));
+		addattr_l(n, 3224, TCA_TBF_PBURST, &mtu, sizeof(mtu));
 		addattr_l(n, 4096, TCA_TBF_PTAB, ptab, 1024);
+	}
 	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
 	return 0;
 }
 
 static int tbf_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
 {
-	struct rtattr *tb[TCA_TBF_PTAB+1];
+	struct rtattr *tb[TCA_TBF_MAX+1];
 	struct tc_tbf_qopt *qopt;
+	unsigned int linklayer;
 	double buffer, mtu;
 	double latency;
+	__u64 rate64 = 0, prate64 = 0;
 	SPRINT_BUF(b1);
 	SPRINT_BUF(b2);
+	SPRINT_BUF(b3);
 
 	if (opt == NULL)
 		return 0;
 
-	parse_rtattr_nested(tb, TCA_TBF_PTAB, opt);
+	parse_rtattr_nested(tb, TCA_TBF_MAX, opt);
 
 	if (tb[TCA_TBF_PARMS] == NULL)
 		return -1;
@@ -235,8 +269,12 @@
 	qopt = RTA_DATA(tb[TCA_TBF_PARMS]);
 	if (RTA_PAYLOAD(tb[TCA_TBF_PARMS])  < sizeof(*qopt))
 		return -1;
-	fprintf(f, "rate %s ", sprint_rate(qopt->rate.rate, b1));
-	buffer = tc_calc_xmitsize(qopt->rate.rate, qopt->buffer);
+	rate64 = qopt->rate.rate;
+	if (tb[TCA_TBF_RATE64] &&
+	    RTA_PAYLOAD(tb[TCA_TBF_RATE64]) >= sizeof(rate64))
+		rate64 = rta_getattr_u64(tb[TCA_TBF_RATE64]);
+	fprintf(f, "rate %s ", sprint_rate(rate64, b1));
+	buffer = tc_calc_xmitsize(rate64, qopt->buffer);
 	if (show_details) {
 		fprintf(f, "burst %s/%u mpu %s ", sprint_size(buffer, b1),
 			1<<qopt->rate.cell_log, sprint_size(qopt->rate.mpu, b2));
@@ -245,10 +283,14 @@
 	}
 	if (show_raw)
 		fprintf(f, "[%08x] ", qopt->buffer);
-	if (qopt->peakrate.rate) {
-		fprintf(f, "peakrate %s ", sprint_rate(qopt->peakrate.rate, b1));
+	prate64 = qopt->peakrate.rate;
+	if (tb[TCA_TBF_PRATE64] &&
+	    RTA_PAYLOAD(tb[TCA_TBF_PRATE64]) >= sizeof(prate64))
+		prate64 = rta_getattr_u64(tb[TCA_TBF_PRATE64]);
+	if (prate64) {
+		fprintf(f, "peakrate %s ", sprint_rate(prate64, b1));
 		if (qopt->mtu || qopt->peakrate.mpu) {
-			mtu = tc_calc_xmitsize(qopt->peakrate.rate, qopt->mtu);
+			mtu = tc_calc_xmitsize(prate64, qopt->mtu);
 			if (show_details) {
 				fprintf(f, "mtu %s/%u mpu %s ", sprint_size(mtu, b1),
 					1<<qopt->peakrate.cell_log, sprint_size(qopt->peakrate.mpu, b2));
@@ -260,20 +302,23 @@
 		}
 	}
 
-	if (show_raw)
-		fprintf(f, "limit %s ", sprint_size(qopt->limit, b1));
-
-	latency = TIME_UNITS_PER_SEC*(qopt->limit/(double)qopt->rate.rate) - tc_core_tick2time(qopt->buffer);
-	if (qopt->peakrate.rate) {
-		double lat2 = TIME_UNITS_PER_SEC*(qopt->limit/(double)qopt->peakrate.rate) - tc_core_tick2time(qopt->mtu);
+	latency = TIME_UNITS_PER_SEC*(qopt->limit/(double)rate64) - tc_core_tick2time(qopt->buffer);
+	if (prate64) {
+		double lat2 = TIME_UNITS_PER_SEC*(qopt->limit/(double)prate64) - tc_core_tick2time(qopt->mtu);
 		if (lat2 > latency)
 			latency = lat2;
 	}
-	fprintf(f, "lat %s ", sprint_time(latency, b1));
+	if (latency >= 0.0)
+		fprintf(f, "lat %s ", sprint_time(latency, b1));
+	if (show_raw || latency < 0.0)
+		fprintf(f, "limit %s ", sprint_size(qopt->limit, b1));
 
 	if (qopt->rate.overhead) {
 		fprintf(f, "overhead %d", qopt->rate.overhead);
 	}
+	linklayer = (qopt->rate.linklayer & TC_LINKLAYER_MASK);
+	if (linklayer > TC_LINKLAYER_ETHERNET || show_details)
+		fprintf(f, "linklayer %s ", sprint_linklayer(linklayer, b3));
 
 	return 0;
 }
diff --git a/tc/static-syms.c b/tc/static-syms.c
index 1ed3a8a..0bc8074 100644
--- a/tc/static-syms.c
+++ b/tc/static-syms.c
@@ -1,4 +1,12 @@
+/*
+ * This file creates a dummy version of dynamic loading
+ * for environments where dynamic linking
+ * is not used or available.
+ */
+
 #include <string.h>
+#include "dlfcn.h"
+
 void *_dlsym(const char *sym)
 {
 #include "static-syms.h"
diff --git a/tc/tc.c b/tc/tc.c
index a261136..e2e75af 100644
--- a/tc/tc.c
+++ b/tc/tc.c
@@ -29,15 +29,22 @@
 #include "utils.h"
 #include "tc_util.h"
 #include "tc_common.h"
+#include "namespace.h"
 
 int show_stats = 0;
 int show_details = 0;
 int show_raw = 0;
 int show_pretty = 0;
+int show_graph = 0;
 
+int batch_mode = 0;
 int resolve_hosts = 0;
 int use_iec = 0;
 int force = 0;
+bool use_names = false;
+
+static char *conf_file;
+
 struct rtnl_handle rth;
 
 static void *BODY = NULL;	/* cached handle dlopen(NULL) */
@@ -215,7 +222,9 @@
 			"       tc [-force] -batch filename\n"
 #endif
 	                "where  OBJECT := { qdisc | class | filter | action | monitor }\n"
-	                "       OPTIONS := { -s[tatistics] | -d[etails] | -r[aw] | -p[retty] | -b[atch] [filename] }\n");
+	                "       OPTIONS := { -s[tatistics] | -d[etails] | -r[aw] | -p[retty] | -b[atch] [filename] | "
+			"-n[etns] name |\n"
+			"                    -nm | -nam[es] | { -cf | -conf } path }\n");
 }
 
 static int do_cmd(int argc, char **argv)
@@ -252,6 +261,7 @@
 	size_t len = 0;
 	int ret = 0;
 
+	batch_mode = 1;
 	if (name && strcmp(name, "-") != 0) {
 		if (freopen(name, "r", stdin) == NULL) {
 			fprintf(stderr, "Cannot open file \"%s\" for reading: %s\n",
@@ -291,12 +301,12 @@
 }
 #endif
 
+
 int main(int argc, char **argv)
 {
 	int ret;
 #ifndef ANDROID
-	int do_batching = 0;
-	char *batchfile = NULL;
+	char *batch_file = NULL;
 #endif
 
 	while (argc > 1) {
@@ -311,6 +321,8 @@
 			++show_raw;
 		} else if (matches(argv[1], "-pretty") == 0) {
 			++show_pretty;
+		} else if (matches(argv[1], "-graph") == 0) {
+			show_graph = 1;
 		} else if (matches(argv[1], "-Version") == 0) {
 			printf("tc utility, iproute2-ss%s\n", SNAPSHOT);
 			return 0;
@@ -322,21 +334,33 @@
 		} else if (matches(argv[1], "-force") == 0) {
 			++force;
 #ifndef ANDROID
-		} else 	if (matches(argv[1], "-batch") == 0) {
-			do_batching = 1;
-			if (argc > 2)
-				batchfile = argv[2];
+		} else if (matches(argv[1], "-batch") == 0) {
 			argc--;	argv++;
+			if (argc <= 1)
+				usage();
+			batch_file = argv[1];
 #endif
+		} else if (matches(argv[1], "-netns") == 0) {
+			NEXT_ARG();
+			if (netns_switch(argv[1]))
+				return -1;
+		} else if (matches(argv[1], "-names") == 0 ||
+				matches(argv[1], "-nm") == 0) {
+			use_names = true;
+		} else if (matches(argv[1], "-cf") == 0 ||
+				matches(argv[1], "-conf") == 0) {
+			NEXT_ARG();
+			conf_file = argv[1];
 		} else {
 			fprintf(stderr, "Option \"%s\" is unknown, try \"tc -help\".\n", argv[1]);
 			return -1;
 		}
 		argc--;	argv++;
 	}
+
 #ifndef ANDROID
-	if (do_batching)
-		return batch(batchfile);
+	if (batch_file)
+		return batch(batch_file);
 #endif
 
 	if (argc <= 1) {
@@ -350,8 +374,17 @@
 		exit(1);
 	}
 
+	if (use_names && cls_names_init(conf_file)) {
+		ret = -1;
+		goto Exit;
+	}
+
 	ret = do_cmd(argc-1, argv+1);
+Exit:
 	rtnl_close(&rth);
 
+	if (use_names)
+		cls_names_uninit();
+
 	return ret;
 }
diff --git a/tc/tc_bpf.c b/tc/tc_bpf.c
new file mode 100644
index 0000000..c6901d6
--- /dev/null
+++ b/tc/tc_bpf.c
@@ -0,0 +1,146 @@
+/*
+ * tc_bpf.c	BPF common code
+ *
+ *		This program is free software; you can distribute 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.
+ *
+ * Authors:	Daniel Borkmann <dborkman@redhat.com>
+ *		Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <linux/filter.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_bpf.h"
+
+int bpf_parse_string(char *arg, bool from_file, __u16 *bpf_len,
+		     char **bpf_string, bool *need_release,
+		     const char separator)
+{
+	char sp;
+
+	if (from_file) {
+		size_t tmp_len, op_len = sizeof("65535 255 255 4294967295,");
+		char *tmp_string;
+		FILE *fp;
+
+		tmp_len = sizeof("4096,") + BPF_MAXINSNS * op_len;
+		tmp_string = malloc(tmp_len);
+		if (tmp_string == NULL)
+			return -ENOMEM;
+
+		memset(tmp_string, 0, tmp_len);
+
+		fp = fopen(arg, "r");
+		if (fp == NULL) {
+			perror("Cannot fopen");
+			free(tmp_string);
+			return -ENOENT;
+		}
+
+		if (!fgets(tmp_string, tmp_len, fp)) {
+			free(tmp_string);
+			fclose(fp);
+			return -EIO;
+		}
+
+		fclose(fp);
+
+		*need_release = true;
+		*bpf_string = tmp_string;
+	} else {
+		*need_release = false;
+		*bpf_string = arg;
+	}
+
+	if (sscanf(*bpf_string, "%hu%c", bpf_len, &sp) != 2 ||
+	    sp != separator) {
+		if (*need_release)
+			free(*bpf_string);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int bpf_parse_ops(int argc, char **argv, struct sock_filter *bpf_ops,
+		  bool from_file)
+{
+	char *bpf_string, *token, separator = ',';
+	int ret = 0, i = 0;
+	bool need_release;
+	__u16 bpf_len = 0;
+
+	if (argc < 1)
+		return -EINVAL;
+	if (bpf_parse_string(argv[0], from_file, &bpf_len, &bpf_string,
+			     &need_release, separator))
+		return -EINVAL;
+	if (bpf_len == 0 || bpf_len > BPF_MAXINSNS) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	token = bpf_string;
+	while ((token = strchr(token, separator)) && (++token)[0]) {
+		if (i >= bpf_len) {
+			fprintf(stderr, "Real program length exceeds encoded "
+				"length parameter!\n");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (sscanf(token, "%hu %hhu %hhu %u,",
+			   &bpf_ops[i].code, &bpf_ops[i].jt,
+			   &bpf_ops[i].jf, &bpf_ops[i].k) != 4) {
+			fprintf(stderr, "Error at instruction %d!\n", i);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		i++;
+	}
+
+	if (i != bpf_len) {
+		fprintf(stderr, "Parsed program length is less than encoded"
+			"length parameter!\n");
+		ret = -EINVAL;
+		goto out;
+	}
+	ret = bpf_len;
+
+out:
+	if (need_release)
+		free(bpf_string);
+
+	return ret;
+}
+
+void bpf_print_ops(FILE *f, struct rtattr *bpf_ops, __u16 len)
+{
+	struct sock_filter *ops = (struct sock_filter *) RTA_DATA(bpf_ops);
+	int i;
+
+	if (len == 0)
+		return;
+
+	fprintf(f, "bytecode \'%u,", len);
+
+	for (i = 0; i < len - 1; i++)
+		fprintf(f, "%hu %hhu %hhu %u,", ops[i].code, ops[i].jt,
+			ops[i].jf, ops[i].k);
+
+	fprintf(f, "%hu %hhu %hhu %u\'\n", ops[i].code, ops[i].jt,
+		ops[i].jf, ops[i].k);
+}
diff --git a/tc/tc_bpf.h b/tc/tc_bpf.h
new file mode 100644
index 0000000..08cca92
--- /dev/null
+++ b/tc/tc_bpf.h
@@ -0,0 +1,28 @@
+/*
+ * tc_bpf.h	BPF common code
+ *
+ *		This program is free software; you can distribute 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.
+ *
+ * Authors:	Daniel Borkmann <dborkman@redhat.com>
+ *		Jiri Pirko <jiri@resnulli.us>
+ */
+
+#ifndef _TC_BPF_H_
+#define _TC_BPF_H_ 1
+
+#include <stdio.h>
+#include <linux/filter.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+int bpf_parse_string(char *arg, bool from_file, __u16 *bpf_len,
+		     char **bpf_string, bool *need_release,
+		     const char separator);
+int bpf_parse_ops(int argc, char **argv, struct sock_filter *bpf_ops,
+		  bool from_file);
+void bpf_print_ops(FILE *f, struct rtattr *bpf_ops, __u16 len);
+
+#endif
diff --git a/tc/tc_class.c b/tc/tc_class.c
index de18fd1..877048a 100644
--- a/tc/tc_class.c
+++ b/tc/tc_class.c
@@ -24,6 +24,21 @@
 #include "utils.h"
 #include "tc_util.h"
 #include "tc_common.h"
+#include "hlist.h"
+
+struct graph_node {
+	struct hlist_node hlist;
+	__u32 id;
+	__u32 parent_id;
+	struct graph_node *parent_node;
+	struct graph_node *right_node;
+	void *data;
+	int data_len;
+	int nodes_count;
+};
+
+static struct hlist_head cls_list = {};
+static struct hlist_head root_cls_list = {};
 
 static void usage(void);
 
@@ -40,7 +55,7 @@
 	return;
 }
 
-int tc_class_modify(int cmd, unsigned flags, int argc, char **argv)
+static int tc_class_modify(int cmd, unsigned flags, int argc, char **argv)
 {
 	struct {
 		struct nlmsghdr 	n;
@@ -74,12 +89,12 @@
 			if (req.t.tcm_handle)
 				duparg("classid", *argv);
 			if (get_tc_classid(&handle, *argv))
-				invarg(*argv, "invalid class ID");
+				invarg("invalid class ID", *argv);
 			req.t.tcm_handle = handle;
 		} else if (strcmp(*argv, "handle") == 0) {
 			fprintf(stderr, "Error: try \"classid\" instead of \"handle\"\n");
 			return -1;
- 		} else if (strcmp(*argv, "root") == 0) {
+		} else if (strcmp(*argv, "root") == 0) {
 			if (req.t.tcm_parent) {
 				fprintf(stderr, "Error: \"root\" is duplicate parent ID.\n");
 				return -1;
@@ -91,7 +106,7 @@
 			if (req.t.tcm_parent)
 				duparg("parent", *argv);
 			if (get_tc_classid(&handle, *argv))
-				invarg(*argv, "invalid parent ID");
+				invarg("invalid parent ID", *argv);
 			req.t.tcm_parent = handle;
 		} else if (matches(*argv, "estimator") == 0) {
 			if (parse_estimator(&argc, &argv, &est))
@@ -148,13 +163,152 @@
 __u32 filter_qdisc;
 __u32 filter_classid;
 
+static void graph_node_add(__u32 parent_id, __u32 id, void *data,
+		int len)
+{
+	struct graph_node *node = malloc(sizeof(struct graph_node));
+
+	memset(node, 0, sizeof(*node));
+	node->id         = id;
+	node->parent_id  = parent_id;
+
+	if (data && len) {
+		node->data       = malloc(len);
+		node->data_len   = len;
+		memcpy(node->data, data, len);
+	}
+
+	if (parent_id == TC_H_ROOT)
+		hlist_add_head(&node->hlist, &root_cls_list);
+	else
+		hlist_add_head(&node->hlist, &cls_list);
+}
+
+static void graph_indent(char *buf, struct graph_node *node, int is_newline,
+		int add_spaces)
+{
+	char spaces[100] = {0};
+
+	while (node && node->parent_node) {
+		node->parent_node->right_node = node;
+		node = node->parent_node;
+	}
+	while (node && node->right_node) {
+		if (node->hlist.next)
+			strcat(buf, "|    ");
+		else
+			strcat(buf, "     ");
+
+		node = node->right_node;
+	}
+
+	if (is_newline) {
+		if (node->hlist.next && node->nodes_count)
+			strcat(buf, "|    |");
+		else if (node->hlist.next)
+			strcat(buf, "|     ");
+		else if (node->nodes_count)
+			strcat(buf, "     |");
+		else if (!node->hlist.next)
+			strcat(buf, "      ");
+	}
+	if (add_spaces > 0) {
+		sprintf(spaces, "%-*s", add_spaces, "");
+		strcat(buf, spaces);
+	}
+}
+
+static void graph_cls_show(FILE *fp, char *buf, struct hlist_head *root_list,
+		int level)
+{
+	struct hlist_node *n, *tmp_cls;
+	char cls_id_str[256] = {};
+	struct rtattr *tb[TCA_MAX + 1] = {};
+	struct qdisc_util *q;
+	char str[100] = {};
+
+	hlist_for_each_safe(n, tmp_cls, root_list) {
+		struct hlist_node *c, *tmp_chld;
+		struct hlist_head children = {};
+		struct graph_node *cls = container_of(n, struct graph_node,
+				hlist);
+
+		hlist_for_each_safe(c, tmp_chld, &cls_list) {
+			struct graph_node *child = container_of(c,
+					struct graph_node, hlist);
+
+			if (cls->id == child->parent_id) {
+				hlist_del(c);
+				hlist_add_head(c, &children);
+				cls->nodes_count++;
+				child->parent_node = cls;
+			}
+		}
+
+		graph_indent(buf, cls, 0, 0);
+
+		print_tc_classid(cls_id_str, sizeof(cls_id_str), cls->id);
+		sprintf(str, "+---(%s)", cls_id_str);
+		strcat(buf, str);
+
+		parse_rtattr(tb, TCA_MAX, (struct rtattr *)cls->data,
+				cls->data_len);
+
+		if (tb[TCA_KIND] == NULL) {
+			strcat(buf, " [unknown qdisc kind] ");
+		} else {
+			const char *kind = rta_getattr_str(tb[TCA_KIND]);
+
+			sprintf(str, " %s ", kind);
+			strcat(buf, str);
+			fprintf(fp, "%s", buf);
+			buf[0] = '\0';
+
+			q = get_qdisc_kind(kind);
+			if (q && q->print_copt) {
+				q->print_copt(q, fp, tb[TCA_OPTIONS]);
+			}
+			if (q && show_stats) {
+				int cls_indent = strlen(q->id) - 2 +
+					strlen(cls_id_str);
+				struct rtattr *stats = NULL;
+
+				graph_indent(buf, cls, 1, cls_indent);
+
+				if (tb[TCA_STATS] || tb[TCA_STATS2]) {
+					fprintf(fp, "\n");
+					print_tcstats_attr(fp, tb, buf, &stats);
+					buf[0] = '\0';
+				}
+				if (cls->hlist.next || cls->nodes_count) {
+					strcat(buf, "\n");
+					graph_indent(buf, cls, 1, 0);
+				}
+			}
+		}
+		free(cls->data);
+		fprintf(fp, "%s\n", buf);
+		buf[0] = '\0';
+
+		graph_cls_show(fp, buf, &children, level + 1);
+		if (!cls->hlist.next) {
+			graph_indent(buf, cls, 0, 0);
+			strcat(buf, "\n");
+		}
+
+		fprintf(fp, "%s", buf);
+		buf[0] = '\0';
+		free(cls);
+	}
+}
+
 int print_class(const struct sockaddr_nl *who,
 		       struct nlmsghdr *n, void *arg)
 {
 	FILE *fp = (FILE*)arg;
 	struct tcmsg *t = NLMSG_DATA(n);
 	int len = n->nlmsg_len;
-	struct rtattr * tb[TCA_MAX+1];
+	struct rtattr *tb[TCA_MAX + 1] = {};
 	struct qdisc_util *q;
 	char abuf[256];
 
@@ -167,13 +321,18 @@
 		fprintf(stderr, "Wrong len %d\n", len);
 		return -1;
 	}
+
+	if (show_graph) {
+		graph_node_add(t->tcm_parent, t->tcm_handle, TCA_RTA(t), len);
+		return 0;
+	}
+
 	if (filter_qdisc && TC_H_MAJ(t->tcm_handle^filter_qdisc))
 		return 0;
 
 	if (filter_classid && t->tcm_handle != filter_classid)
 		return 0;
 
-	memset(tb, 0, sizeof(tb));
 	parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len);
 
 	if (tb[TCA_KIND] == NULL) {
@@ -232,15 +391,19 @@
 }
 
 
-int tc_class_list(int argc, char **argv)
+static int tc_class_list(int argc, char **argv)
 {
 	struct tcmsg t;
 	char d[16];
+	char buf[1024] = {0};
 
 	memset(&t, 0, sizeof(t));
 	t.tcm_family = AF_UNSPEC;
 	memset(d, 0, sizeof(d));
 
+	filter_qdisc = 0;
+	filter_classid = 0;
+
 	while (argc > 0) {
 		if (strcmp(*argv, "dev") == 0) {
 			NEXT_ARG();
@@ -252,13 +415,13 @@
 			if (filter_qdisc)
 				duparg("qdisc", *argv);
 			if (get_qdisc_handle(&filter_qdisc, *argv))
-				invarg(*argv, "invalid qdisc ID");
+				invarg("invalid qdisc ID", *argv);
 		} else if (strcmp(*argv, "classid") == 0) {
 			NEXT_ARG();
 			if (filter_classid)
 				duparg("classid", *argv);
 			if (get_tc_classid(&filter_classid, *argv))
-				invarg(*argv, "invalid class ID");
+				invarg("invalid class ID", *argv);
 		} else if (strcmp(*argv, "root") == 0) {
 			if (t.tcm_parent) {
 				fprintf(stderr, "Error: \"root\" is duplicate parent ID\n");
@@ -271,7 +434,7 @@
 				duparg("parent", *argv);
 			NEXT_ARG();
 			if (get_tc_classid(&handle, *argv))
-				invarg(*argv, "invalid parent ID");
+				invarg("invalid parent ID", *argv);
 			t.tcm_parent = handle;
 		} else if (matches(*argv, "help") == 0) {
 			usage();
@@ -283,7 +446,7 @@
 		argc--; argv++;
 	}
 
- 	ll_init_map(&rth);
+	ll_init_map(&rth);
 
 	if (d[0]) {
 		if ((t.tcm_ifindex = ll_name_to_index(d)) == 0) {
@@ -293,16 +456,19 @@
 		filter_ifindex = t.tcm_ifindex;
 	}
 
- 	if (rtnl_dump_request(&rth, RTM_GETTCLASS, &t, sizeof(t)) < 0) {
+	if (rtnl_dump_request(&rth, RTM_GETTCLASS, &t, sizeof(t)) < 0) {
 		perror("Cannot send dump request");
 		return 1;
 	}
 
- 	if (rtnl_dump_filter(&rth, print_class, stdout) < 0) {
+	if (rtnl_dump_filter(&rth, print_class, stdout) < 0) {
 		fprintf(stderr, "Dump terminated\n");
 		return 1;
 	}
 
+	if (show_graph)
+		graph_cls_show(stdout, &buf[0], &root_cls_list, 0);
+
 	return 0;
 }
 
diff --git a/tc/tc_common.h b/tc/tc_common.h
index 4f88856..96a0e20 100644
--- a/tc/tc_common.h
+++ b/tc/tc_common.h
@@ -19,3 +19,6 @@
 struct tc_sizespec;
 extern int parse_size_table(int *p_argc, char ***p_argv, struct tc_sizespec *s);
 extern int check_size_table_opts(struct tc_sizespec *s);
+
+extern int show_graph;
+extern bool use_names;
diff --git a/tc/tc_core.c b/tc/tc_core.c
index 9a0ff39..46eaefb 100644
--- a/tc/tc_core.c
+++ b/tc/tc_core.c
@@ -56,12 +56,12 @@
 	return ktime / clock_factor;
 }
 
-unsigned tc_calc_xmittime(unsigned rate, unsigned size)
+unsigned tc_calc_xmittime(__u64 rate, unsigned size)
 {
-	return tc_core_time2tick(TIME_UNITS_PER_SEC*((double)size/rate));
+	return tc_core_time2tick(TIME_UNITS_PER_SEC*((double)size/(double)rate));
 }
 
-unsigned tc_calc_xmitsize(unsigned rate, unsigned ticks)
+unsigned tc_calc_xmitsize(__u64 rate, unsigned ticks)
 {
 	return ((double)rate*tc_core_tick2time(ticks))/TIME_UNITS_PER_SEC;
 }
@@ -76,7 +76,7 @@
  * (as the table will always be aligned for 48 bytes).
  *  --Hawk, d.7/11-2004. <hawk@diku.dk>
  */
-unsigned tc_align_to_atm(unsigned size)
+static unsigned tc_align_to_atm(unsigned size)
 {
 	int linksize, cells;
 	cells = size / ATM_CELL_PAYLOAD;
@@ -87,7 +87,7 @@
 	return linksize;
 }
 
-unsigned tc_adjust_size(unsigned sz, unsigned mpu, enum link_layer linklayer)
+static unsigned tc_adjust_size(unsigned sz, unsigned mpu, enum link_layer linklayer)
 {
 	if (sz < mpu)
 		sz = mpu;
@@ -102,6 +102,21 @@
 	}
 }
 
+/* Notice, the rate table calculated here, have gotten replaced in the
+ * kernel and is no-longer used for lookups.
+ *
+ * This happened in kernel release v3.8 caused by kernel
+ *  - commit 56b765b79 ("htb: improved accuracy at high rates").
+ * This change unfortunately caused breakage of tc overhead and
+ * linklayer parameters.
+ *
+ * Kernel overhead handling got fixed in kernel v3.10 by
+ * - commit 01cb71d2d47 (net_sched: restore "overhead xxx" handling)
+ *
+ * Kernel linklayer handling got fixed in kernel v3.11 by
+ * - commit 8a8e3d84b17 (net_sched: restore "linklayer atm" handling)
+ */
+
 /*
    rtab[pkt_len>>cell_log] = pkt_xmit_time
  */
@@ -131,6 +146,7 @@
 
 	r->cell_align=-1; // Due to the sz calc
 	r->cell_log=cell_log;
+	r->linklayer = (linklayer & TC_LINKLAYER_MASK);
 	return cell_log;
 }
 
@@ -181,7 +197,7 @@
 	return 0;
 }
 
-int tc_core_init()
+int tc_core_init(void)
 {
 	FILE *fp;
 	__u32 clock_res;
diff --git a/tc/tc_core.h b/tc/tc_core.h
index 5a693ba..8a63b79 100644
--- a/tc/tc_core.h
+++ b/tc/tc_core.h
@@ -18,8 +18,8 @@
 unsigned tc_core_tick2time(unsigned tick);
 unsigned tc_core_time2ktime(unsigned time);
 unsigned tc_core_ktime2time(unsigned ktime);
-unsigned tc_calc_xmittime(unsigned rate, unsigned size);
-unsigned tc_calc_xmitsize(unsigned rate, unsigned ticks);
+unsigned tc_calc_xmittime(__u64 rate, unsigned size);
+unsigned tc_calc_xmitsize(__u64 rate, unsigned ticks);
 int tc_calc_rtable(struct tc_ratespec *r, __u32 *rtab,
 		   int cell_log, unsigned mtu, enum link_layer link_layer);
 int tc_calc_size_table(struct tc_sizespec *s, __u16 **stab);
diff --git a/tc/tc_filter.c b/tc/tc_filter.c
index 207302f..609fbe9 100644
--- a/tc/tc_filter.c
+++ b/tc/tc_filter.c
@@ -45,7 +45,7 @@
 }
 
 
-int tc_filter_modify(int cmd, unsigned flags, int argc, char **argv)
+static int tc_filter_modify(int cmd, unsigned flags, int argc, char **argv)
 {
 	struct {
 		struct nlmsghdr 	n;
@@ -93,7 +93,7 @@
 			if (req.t.tcm_parent)
 				duparg("parent", *argv);
 			if (get_tc_classid(&handle, *argv))
-				invarg(*argv, "Invalid parent ID");
+				invarg("Invalid parent ID", *argv);
 			req.t.tcm_parent = handle;
 		} else if (strcmp(*argv, "handle") == 0) {
 			NEXT_ARG();
@@ -105,15 +105,15 @@
 			NEXT_ARG();
 			if (prio)
 				duparg("priority", *argv);
-			if (get_u32(&prio, *argv, 0))
-				invarg(*argv, "invalid priority value");
+			if (get_u32(&prio, *argv, 0) || prio > 0xFFFF)
+				invarg("invalid priority value", *argv);
 		} else if (matches(*argv, "protocol") == 0) {
 			__u16 id;
 			NEXT_ARG();
 			if (protocol_set)
 				duparg("protocol", *argv);
 			if (ll_proto_a2n(&id, *argv))
-				invarg(*argv, "invalid protocol");
+				invarg("invalid protocol", *argv);
 			protocol = id;
 			protocol_set = 1;
 		} else if (matches(*argv, "estimator") == 0) {
@@ -159,7 +159,7 @@
 
 
 	if (d[0])  {
- 		ll_init_map(&rth);
+		ll_init_map(&rth);
 
 		if ((req.t.tcm_ifindex = ll_name_to_index(d)) == 0) {
 			fprintf(stderr, "Cannot find device \"%s\"\n", d);
@@ -260,7 +260,7 @@
 }
 
 
-int tc_filter_list(int argc, char **argv)
+static int tc_filter_list(int argc, char **argv)
 {
 	struct tcmsg t;
 	char d[16];
@@ -290,7 +290,7 @@
 			if (t.tcm_parent)
 				duparg("parent", *argv);
 			if (get_tc_classid(&handle, *argv))
-				invarg(*argv, "invalid parent ID");
+				invarg("invalid parent ID", *argv);
 			filter_parent = t.tcm_parent = handle;
 		} else if (strcmp(*argv, "handle") == 0) {
 			NEXT_ARG();
@@ -303,7 +303,7 @@
 			if (prio)
 				duparg("priority", *argv);
 			if (get_u32(&prio, *argv, 0))
-				invarg(*argv, "invalid preference");
+				invarg("invalid preference", *argv);
 			filter_prio = prio;
 		} else if (matches(*argv, "protocol") == 0) {
 			__u16 res;
@@ -311,7 +311,7 @@
 			if (protocol)
 				duparg("protocol", *argv);
 			if (ll_proto_a2n(&res, *argv))
-				invarg(*argv, "invalid protocol");
+				invarg("invalid protocol", *argv);
 			protocol = res;
 			filter_protocol = protocol;
 		} else if (matches(*argv, "help") == 0) {
@@ -326,7 +326,7 @@
 
 	t.tcm_info = TC_H_MAKE(prio<<16, protocol);
 
- 	ll_init_map(&rth);
+	ll_init_map(&rth);
 
 	if (d[0]) {
 		if ((t.tcm_ifindex = ll_name_to_index(d)) == 0) {
@@ -336,12 +336,12 @@
 		filter_ifindex = t.tcm_ifindex;
 	}
 
- 	if (rtnl_dump_request(&rth, RTM_GETTFILTER, &t, sizeof(t)) < 0) {
+	if (rtnl_dump_request(&rth, RTM_GETTFILTER, &t, sizeof(t)) < 0) {
 		perror("Cannot send dump request");
 		return 1;
 	}
 
- 	if (rtnl_dump_filter(&rth, print_filter, stdout) < 0) {
+	if (rtnl_dump_filter(&rth, print_filter, stdout) < 0) {
 		fprintf(stderr, "Dump terminated\n");
 		return 1;
 	}
diff --git a/tc/tc_monitor.c b/tc/tc_monitor.c
index bf58744..0efe034 100644
--- a/tc/tc_monitor.c
+++ b/tc/tc_monitor.c
@@ -35,7 +35,8 @@
 }
 
 
-int accept_tcmsg(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
+static int accept_tcmsg(const struct sockaddr_nl *who,
+			struct nlmsghdr *n, void *arg)
 {
 	FILE *fp = (FILE*)arg;
 
diff --git a/tc/tc_qdisc.c b/tc/tc_qdisc.c
index 3f932a7..c71937d 100644
--- a/tc/tc_qdisc.c
+++ b/tc/tc_qdisc.c
@@ -44,7 +44,7 @@
 	return -1;
 }
 
-int tc_qdisc_modify(int cmd, unsigned flags, int argc, char **argv)
+static int tc_qdisc_modify(int cmd, unsigned flags, int argc, char **argv)
 {
 	struct qdisc_util *q = NULL;
 	struct tc_estimator est;
@@ -83,7 +83,7 @@
 				duparg("handle", *argv);
 			NEXT_ARG();
 			if (get_qdisc_handle(&handle, *argv))
-				invarg(*argv, "invalid qdisc ID");
+				invarg("invalid qdisc ID", *argv);
 			req.t.tcm_handle = handle;
 		} else if (strcmp(*argv, "root") == 0) {
 			if (req.t.tcm_parent) {
@@ -111,7 +111,7 @@
 			if (req.t.tcm_parent)
 				duparg("parent", *argv);
 			if (get_tc_classid(&handle, *argv))
-				invarg(*argv, "invalid parent ID");
+				invarg("invalid parent ID", *argv);
 			req.t.tcm_parent = handle;
 		} else if (matches(*argv, "estimator") == 0) {
 			if (parse_estimator(&argc, &argv, &est))
@@ -138,12 +138,13 @@
 		addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
 
 	if (q) {
-		if (!q->parse_qopt) {
+		if (q->parse_qopt) {
+			if (q->parse_qopt(q, argc, argv, &req.n))
+				return 1;
+		} else if (argc) {
 			fprintf(stderr, "qdisc '%s' does not support option parsing\n", k);
 			return -1;
 		}
-		if (q->parse_qopt(q, argc, argv, &req.n))
-			return 1;
 	} else {
 		if (argc) {
 			if (matches(*argv, "help") == 0)
@@ -177,7 +178,7 @@
 	if (d[0])  {
 		int idx;
 
- 		ll_init_map(&rth);
+		ll_init_map(&rth);
 
 		if ((idx = ll_name_to_index(d)) == 0) {
 			fprintf(stderr, "Cannot find device \"%s\"\n", d);
@@ -277,7 +278,7 @@
 }
 
 
-int tc_qdisc_list(int argc, char **argv)
+static int tc_qdisc_list(int argc, char **argv)
 {
 	struct tcmsg t;
 	char d[16];
@@ -308,7 +309,7 @@
 		argc--; argv++;
 	}
 
- 	ll_init_map(&rth);
+	ll_init_map(&rth);
 
 	if (d[0]) {
 		if ((t.tcm_ifindex = ll_name_to_index(d)) == 0) {
@@ -318,12 +319,12 @@
 		filter_ifindex = t.tcm_ifindex;
 	}
 
- 	if (rtnl_dump_request(&rth, RTM_GETQDISC, &t, sizeof(t)) < 0) {
+	if (rtnl_dump_request(&rth, RTM_GETQDISC, &t, sizeof(t)) < 0) {
 		perror("Cannot send dump request");
 		return 1;
 	}
 
- 	if (rtnl_dump_filter(&rth, print_qdisc, stdout) < 0) {
+	if (rtnl_dump_filter(&rth, print_qdisc, stdout) < 0) {
 		fprintf(stderr, "Dump terminated\n");
 		return 1;
 	}
diff --git a/tc/tc_stab.c b/tc/tc_stab.c
index 47b4e5e..a8404f8 100644
--- a/tc/tc_stab.c
+++ b/tc/tc_stab.c
@@ -17,6 +17,7 @@
 #include <fcntl.h>
 #include <math.h>
 #include <sys/socket.h>
+#include <sys/param.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <string.h>
diff --git a/tc/tc_util.c b/tc/tc_util.c
index 926ed08..1d3153d 100644
--- a/tc/tc_util.c
+++ b/tc/tc_util.c
@@ -16,18 +16,52 @@
 #include <syslog.h>
 #include <fcntl.h>
 #include <sys/socket.h>
+#include <sys/param.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <string.h>
 #include <math.h>
+#include <errno.h>
 
 #include "utils.h"
+#include "names.h"
 #include "tc_util.h"
+#include "tc_common.h"
 
 #ifndef LIBDIR
 #define LIBDIR "/usr/lib"
 #endif
 
+static struct db_names *cls_names = NULL;
+
+#define NAMES_DB "/etc/iproute2/tc_cls"
+
+int cls_names_init(char *path)
+{
+	int ret;
+
+	cls_names = db_names_alloc();
+	if (!cls_names)
+		return -1;
+
+	ret = db_names_load(cls_names, path ?: NAMES_DB);
+	if (ret == -ENOENT && path) {
+		fprintf(stderr, "Can't open class names file: %s\n", path);
+		return -1;
+	}
+	if (ret) {
+		db_names_free(cls_names);
+		cls_names = NULL;
+	}
+
+	return 0;
+}
+
+void cls_names_uninit(void)
+{
+	db_names_free(cls_names);
+}
+
 const char *get_tc_lib(void)
 {
 	const char *lib_dir;
@@ -96,20 +130,34 @@
 
 int print_tc_classid(char *buf, int len, __u32 h)
 {
+	char handle[40] = {};
+
 	if (h == TC_H_ROOT)
-		sprintf(buf, "root");
+		sprintf(handle, "root");
 	else if (h == TC_H_UNSPEC)
-		snprintf(buf, len, "none");
+		snprintf(handle, len, "none");
 	else if (TC_H_MAJ(h) == 0)
-		snprintf(buf, len, ":%x", TC_H_MIN(h));
+		snprintf(handle, len, ":%x", TC_H_MIN(h));
 	else if (TC_H_MIN(h) == 0)
-		snprintf(buf, len, "%x:", TC_H_MAJ(h)>>16);
+		snprintf(handle, len, "%x:", TC_H_MAJ(h) >> 16);
 	else
-		snprintf(buf, len, "%x:%x", TC_H_MAJ(h)>>16, TC_H_MIN(h));
+		snprintf(handle, len, "%x:%x", TC_H_MAJ(h) >> 16, TC_H_MIN(h));
+
+	if (use_names) {
+		char clname[IDNAME_MAX] = {};
+
+		if (id_to_name(cls_names, h, clname))
+			snprintf(buf, len, "%s#%s", clname, handle);
+		else
+			snprintf(buf, len, "%s", handle);
+	} else {
+		snprintf(buf, len, "%s", handle);
+	}
+
 	return 0;
 }
 
-char * sprint_tc_classid(__u32 h, char *buf)
+char *sprint_tc_classid(__u32 h, char *buf)
 {
 	if (print_tc_classid(buf, SPRINT_BSIZE-1, h))
 		strcpy(buf, "???");
@@ -152,73 +200,71 @@
 	if (p == str)
 		return -1;
 
-	if (*p == '\0') {
-		*rate = bps / 8.;	/* assume bytes/sec */
-		return 0;
-	}
-
 	for (s = suffixes; s->name; ++s) {
 		if (strcasecmp(s->name, p) == 0) {
-			*rate = (bps * s->scale) / 8.;
-			return 0;
+			bps *= s->scale;
+			p += strlen(p);
+			break;
 		}
 	}
 
-	return -1;
-}
+	if (*p)
+		return -1; /* unknown suffix */
 
-int get_rate_and_cell(unsigned *rate, int *cell_log, char *str)
-{
-	char * slash = strchr(str, '/');
-
-	if (slash)
-		*slash = 0;
-
-	if (get_rate(rate, str))
+	bps /= 8; /* -> bytes per second */
+	*rate = bps;
+	/* detect if an overflow happened */
+	if (*rate != floor(bps))
 		return -1;
-
-	if (slash) {
-		int cell;
-		int i;
-
-		if (get_integer(&cell, slash+1, 0))
-			return -1;
-		*slash = '/';
-
-		for (i=0; i<32; i++) {
-			if ((1<<i) == cell) {
-				*cell_log = i;
-				return 0;
-			}
-		}
-		return -1;
-	}
 	return 0;
 }
 
-void print_rate(char *buf, int len, __u32 rate)
+int get_rate64(__u64 *rate, const char *str)
 {
-	double tmp = (double)rate*8;
-	extern int use_iec;
+	char *p;
+	double bps = strtod(str, &p);
+	const struct rate_suffix *s;
 
-	if (use_iec) {
-		if (tmp >= 1000.0*1024.0*1024.0)
-			snprintf(buf, len, "%.0fMibit", tmp/(1024.0*1024.0));
-		else if (tmp >= 1000.0*1024)
-			snprintf(buf, len, "%.0fKibit", tmp/1024);
-		else
-			snprintf(buf, len, "%.0fbit", tmp);
-	} else {
-		if (tmp >= 1000.0*1000000.0)
-			snprintf(buf, len, "%.0fMbit", tmp/1000000.0);
-		else if (tmp >= 1000.0 * 1000.0)
-			snprintf(buf, len, "%.0fKbit", tmp/1000.0);
-		else
-			snprintf(buf, len, "%.0fbit",  tmp);
+	if (p == str)
+		return -1;
+
+	for (s = suffixes; s->name; ++s) {
+		if (strcasecmp(s->name, p) == 0) {
+			bps *= s->scale;
+			p += strlen(p);
+			break;
+		}
 	}
+
+	if (*p)
+		return -1; /* unknown suffix */
+
+	bps /= 8; /* -> bytes per second */
+	*rate = bps;
+	return 0;
 }
 
-char * sprint_rate(__u32 rate, char *buf)
+void print_rate(char *buf, int len, __u64 rate)
+{
+	extern int use_iec;
+	unsigned long kilo = use_iec ? 1024 : 1000;
+	const char *str = use_iec ? "i" : "";
+	int i = 0;
+	static char *units[5] = {"", "K", "M", "G", "T"};
+
+	rate <<= 3; /* bytes/sec -> bits/sec */
+
+	for (i = 0; i < ARRAY_SIZE(units); i++)  {
+		if (rate < kilo)
+			break;
+		if (((rate % kilo) != 0) && rate < 1000*kilo)
+			break;
+		rate /= kilo;
+	}
+	snprintf(buf, len, "%.0f%s%sbit", (double)rate, units[i], str);
+}
+
+char * sprint_rate(__u64 rate, char *buf)
 {
 	print_rate(buf, SPRINT_BSIZE-1, rate);
 	return buf;
@@ -485,9 +531,19 @@
 			q.drops, q.overlimits, q.requeues);
 	}
 
-	if (tbs[TCA_STATS_RATE_EST]) {
+	if (tbs[TCA_STATS_RATE_EST64]) {
+		struct gnet_stats_rate_est64 re = {0};
+
+		memcpy(&re, RTA_DATA(tbs[TCA_STATS_RATE_EST64]),
+		       MIN(RTA_PAYLOAD(tbs[TCA_STATS_RATE_EST64]),
+			   sizeof(re)));
+		fprintf(fp, "\n%srate %s %llupps ",
+			prefix, sprint_rate(re.bps, b1), re.pps);
+	} else if (tbs[TCA_STATS_RATE_EST]) {
 		struct gnet_stats_rate_est re = {0};
-		memcpy(&re, RTA_DATA(tbs[TCA_STATS_RATE_EST]), MIN(RTA_PAYLOAD(tbs[TCA_STATS_RATE_EST]), sizeof(re)));
+
+		memcpy(&re, RTA_DATA(tbs[TCA_STATS_RATE_EST]),
+		       MIN(RTA_PAYLOAD(tbs[TCA_STATS_RATE_EST]), sizeof(re)));
 		fprintf(fp, "\n%srate %s %upps ",
 			prefix, sprint_rate(re.bps, b1), re.pps);
 	}
diff --git a/tc/tc_util.h b/tc/tc_util.h
index cefd241..1be1b50 100644
--- a/tc/tc_util.h
+++ b/tc/tc_util.h
@@ -7,10 +7,6 @@
 #include <linux/gen_stats.h>
 #include "tc_core.h"
 
-#ifndef MIN
-#define MIN(x,y)        ((x)<(y)?(x):(y))
-#endif
-
 /* This is the deprecated multiqueue interface */
 #ifndef TCA_PRIO_MAX
 enum
@@ -62,17 +58,18 @@
 
 extern int get_qdisc_handle(__u32 *h, const char *str);
 extern int get_rate(unsigned *rate, const char *str);
+extern int get_rate64(__u64 *rate, const char *str);
 extern int get_size(unsigned *size, const char *str);
 extern int get_size_and_cell(unsigned *size, int *cell_log, char *str);
 extern int get_time(unsigned *time, const char *str);
 extern int get_linklayer(unsigned *val, const char *arg);
 
-extern void print_rate(char *buf, int len, __u32 rate);
+extern void print_rate(char *buf, int len, __u64 rate);
 extern void print_size(char *buf, int len, __u32 size);
 extern void print_qdisc_handle(char *buf, int len, __u32 h);
 extern void print_time(char *buf, int len, __u32 time);
 extern void print_linklayer(char *buf, int len, unsigned linklayer);
-extern char * sprint_rate(__u32 rate, char *buf);
+extern char * sprint_rate(__u64 rate, char *buf);
 extern char * sprint_size(__u32 size, char *buf);
 extern char * sprint_qdisc_handle(__u32 h, char *buf);
 extern char * sprint_tc_classid(__u32 h, char *buf);
@@ -101,5 +98,9 @@
 extern int  tc_print_ipt(FILE *f, const struct rtattr *tb);
 extern int  parse_action(int *, char ***, int, struct nlmsghdr *);
 extern void print_tm(FILE *f, const struct tcf_t *tm);
+extern int prio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt);
+
+extern int cls_names_init(char *path);
+extern void cls_names_uninit(void);
 
 #endif
diff --git a/testsuite/Makefile b/testsuite/Makefile
index 2a4e0ba..a2c8a2d 100644
--- a/testsuite/Makefile
+++ b/testsuite/Makefile
@@ -1,11 +1,19 @@
 ## -- Config --
 DEV := lo
-PREFIX := sudo
+PREFIX := sudo -E
+RESULTS_DIR := results
 ## -- End Config --
 
-TESTS := $(patsubst tests/%,%,$(wildcard tests/*.t))
+rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
+
+TESTS := $(patsubst tests/%,%,$(call rwildcard,tests/,*.t))
+TESTS_DIR := $(dir $(TESTS))
+
 IPVERS := $(filter-out iproute2/Makefile,$(wildcard iproute2/*))
-KENV := $(shell cat /proc/config.gz | gunzip | grep ^CONFIG)
+
+ifneq (,$(wildcard /proc/config.gz))
+	KENV := $(shell cat /proc/config.gz | gunzip | grep ^CONFIG)
+endif
 
 .PHONY: compile listtests alltests configure $(TESTS)
 
@@ -23,23 +31,30 @@
 alltests: $(TESTS)
 
 clean:
-	@rm -rf results/*
+	@echo "Removing $(RESULTS_DIR) dir ..."
+	@rm -rf $(RESULTS_DIR)
 
 distclean: clean
 	echo "Entering iproute2" && cd iproute2 && $(MAKE) distclean && cd ..;
 
-$(TESTS):
+$(TESTS): clean
+	@mkdir -p $(RESULTS_DIR)
+	
+	@for d in $(TESTS_DIR); do \
+	    mkdir -p $(RESULTS_DIR)/$$d; \
+	done
+	
 	@for i in $(IPVERS); do \
 		o=`echo $$i | sed -e 's/iproute2\///'`; \
 		echo -n "Running $@ [$$o/`uname -r`]: "; \
 		TC="$$i/tc/tc" IP="$$i/ip/ip" DEV="$(DEV)" IPVER="$@" SNAME="$$i" \
-		ERRF="results/$@.$$o.err" $(KENV) $(PREFIX) tests/$@ > results/$@.$$o.out; \
+		ERRF="$(RESULTS_DIR)/$@.$$o.err" $(KENV) $(PREFIX) tests/$@ > $(RESULTS_DIR)/$@.$$o.out; \
 		if [ "$$?" = "127" ]; then \
 			echo "SKIPPED"; \
-		elif [ -e "results/$@.$$o.err" ]; then \
+		elif [ -e "$(RESULTS_DIR)/$@.$$o.err" ]; then \
 			echo "FAILED"; \
 		else \
 			echo "PASS"; \
 		fi; \
-		dmesg > results/$@.$$o.dmesg; \
+		dmesg > $(RESULTS_DIR)/$@.$$o.dmesg; \
 	done
diff --git a/testsuite/lib/generic.sh b/testsuite/lib/generic.sh
index cc48947..3473cc1 100644
--- a/testsuite/lib/generic.sh
+++ b/testsuite/lib/generic.sh
@@ -21,6 +21,11 @@
 	ts_cat "$@" | tee >> $ERRF
 }
 
+ts_skip()
+{
+    exit 127
+}
+
 ts_tc()
 {
 	SCRIPT=$1; shift
@@ -57,8 +62,9 @@
 	TMP_OUT=`mktemp /tmp/tc_testsuite.XXXXXX` || exit
 
 	$IP $@ 2> $TMP_ERR > $TMP_OUT
+        RET=$?
 
-	if [ -s $TMP_ERR ]; then
+	if [ -s $TMP_ERR ] || [ "$RET" != "0" ]; then
 		ts_err "${SCRIPT}: ${DESC} failed:"
 		ts_err "command: $IP $@"
 		ts_err "stderr output:"
@@ -86,3 +92,8 @@
 		return 1;
 	fi
 }
+
+rand_dev()
+{
+    echo "dev-$(tr -dc "[:alpha:]" < /dev/urandom | head -c 6)"
+}
diff --git a/testsuite/tests/ip/link/dev_wo_vf_rate.nl b/testsuite/tests/ip/link/dev_wo_vf_rate.nl
new file mode 100644
index 0000000..40fa87f
--- /dev/null
+++ b/testsuite/tests/ip/link/dev_wo_vf_rate.nl
Binary files differ
diff --git a/testsuite/tests/ip/link/new_link.t b/testsuite/tests/ip/link/new_link.t
new file mode 100755
index 0000000..549ff25
--- /dev/null
+++ b/testsuite/tests/ip/link/new_link.t
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+source lib/generic.sh
+
+ts_log "[Testing add/del virtual links]"
+
+NEW_DEV="$(rand_dev)"
+
+ts_ip "$0" "Add $NEW_DEV dummy interface"  link add dev $NEW_DEV type dummy
+ts_ip "$0" "Show $NEW_DEV dummy interface" link show dev $NEW_DEV
+ts_ip "$0" "Del $NEW_DEV dummy interface"  link del dev $NEW_DEV
diff --git a/testsuite/tests/ip/link/show_dev_wo_vf_rate.t b/testsuite/tests/ip/link/show_dev_wo_vf_rate.t
new file mode 100755
index 0000000..a600ba6
--- /dev/null
+++ b/testsuite/tests/ip/link/show_dev_wo_vf_rate.t
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+source lib/generic.sh
+
+NL_FILE="tests/ip/link/dev_wo_vf_rate.nl"
+ts_ip "$0" "Show VF devices w/o VF rate info" -d monitor file $NL_FILE
diff --git a/testsuite/tests/cbq.t b/testsuite/tests/tc/cbq.t
similarity index 100%
rename from testsuite/tests/cbq.t
rename to testsuite/tests/tc/cbq.t
diff --git a/testsuite/tests/cls-testbed.t b/testsuite/tests/tc/cls-testbed.t
similarity index 93%
rename from testsuite/tests/cls-testbed.t
rename to testsuite/tests/tc/cls-testbed.t
index efae2a5..2afc26f 100755
--- a/testsuite/tests/cls-testbed.t
+++ b/testsuite/tests/tc/cls-testbed.t
@@ -5,10 +5,15 @@
 
 QDISCS="cbq htb dsmark"
 
+if [ ! -d tests/cls ]; then
+    ts_log "tests/cls folder does not exist"
+    ts_skip
+fi
+
 for q in ${QDISCS}; do
 	ts_log "Preparing classifier testbed with qdisc $q"
 
-	for c in tests/cls/*.t; do
+	for c in tests/cls/*.c; do
 
 		case "$q" in
 		cbq)
diff --git a/testsuite/tests/dsmark.t b/testsuite/tests/tc/dsmark.t
similarity index 100%
rename from testsuite/tests/dsmark.t
rename to testsuite/tests/tc/dsmark.t
diff --git a/testsuite/tests/policer b/testsuite/tests/tc/policer.t
similarity index 100%
rename from testsuite/tests/policer
rename to testsuite/tests/tc/policer.t