Merge branch 'master' into net-next-3.11

Conflicts:
	tc/q_fq.c
diff --git a/bridge/monitor.c b/bridge/monitor.c
index e96fcaf..76e7d47 100644
--- a/bridge/monitor.c
+++ b/bridge/monitor.c
@@ -132,12 +132,15 @@
 
 	if (file) {
 		FILE *fp;
+		int err;
 		fp = fopen(file, "r");
 		if (fp == NULL) {
 			perror("Cannot fopen");
 			exit(-1);
 		}
-		return rtnl_from_file(fp, accept_msg, stdout);
+		err = rtnl_from_file(fp, accept_msg, stdout);
+		fclose(fp);
+		return err;
 	}
 
 	if (rtnl_open(&rth, groups) < 0)
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/utils.h b/include/utils.h
index 24ff19f..a3e310e 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -151,6 +151,7 @@
 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,
diff --git a/ip/Makefile b/ip/Makefile
index 48bd4a1..f10d22f 100644
--- a/ip/Makefile
+++ b/ip/Makefile
@@ -5,7 +5,7 @@
     iplink_vlan.o link_veth.o link_gre.o iplink_can.o \
     iplink_macvlan.o iplink_macvtap.o ipl2tp.o link_vti.o \
     iplink_vxlan.o tcp_metrics.o iplink_ipoib.o ipnetconf.o link_ip6tnl.o \
-    link_iptnl.o
+    link_iptnl.o link_gre6.o
 
 RTMONOBJ=rtmon.o
 
@@ -23,7 +23,6 @@
 
 ip: $(IPOBJ) $(LIBNETLINK)
 
-
 rtmon: $(RTMONOBJ)
 
 install: all
diff --git a/ip/ip6tunnel.c b/ip/ip6tunnel.c
index 216e982..463be42 100644
--- a/ip/ip6tunnel.c
+++ b/ip/ip6tunnel.c
@@ -48,11 +48,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 | 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");
@@ -62,10 +63,11 @@
 		DEFAULT_TNL_HOP_LIMIT);
 	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 +106,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.\n", _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];
@@ -124,6 +146,9 @@
 				 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;
@@ -202,6 +227,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();
@@ -212,7 +291,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;
@@ -230,7 +309,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;
@@ -244,8 +323,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) &&
@@ -263,7 +342,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;
@@ -287,7 +366,7 @@
 			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';
@@ -312,10 +391,12 @@
 			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)
@@ -346,7 +427,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);
@@ -369,28 +450,44 @@
 
 static int do_add(int cmd, int argc, char **argv)
 {
-	struct ip6_tnl_parm p;
+	struct ip6_tnl_parm2 p;
 
 	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);
+	switch (p.proto) {
+	case IPPROTO_IPIP:
+	case IPPROTO_IPV6:
+		return tnl_add_ioctl(cmd, "ip6tnl0", p.name, &p);
+	case IPPROTO_GRE:
+		return tnl_add_ioctl(cmd, "ip6gre0", p.name, &p);
+	default:
+		fprintf(stderr, "cannot determine tunnel mode (ip6ip6, ipip6 or gre)\n");
+	}
+	return -1;
 }
 
 static int do_del(int argc, char **argv)
 {
-	struct ip6_tnl_parm p;
+	struct ip6_tnl_parm2 p;
 
 	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);
+	switch (p.proto) {
+	case IPPROTO_IPIP:
+	case IPPROTO_IPV6:
+		return tnl_del_ioctl("ip6tnl0", p.name, &p);
+	case IPPROTO_GRE:
+		return tnl_del_ioctl("ip6gre0", p.name, &p);
+	default:
+		return tnl_del_ioctl(p.name, p.name, &p);
+	}
+	return -1;
 }
 
 int do_ip6tunnel(int argc, char **argv)
diff --git a/ip/iplink.c b/ip/iplink.c
index ada9d42..6cde731 100644
--- a/ip/iplink.c
+++ b/ip/iplink.c
@@ -84,8 +84,9 @@
 
 	if (iplink_have_newlink()) {
 		fprintf(stderr, "\n");
-		fprintf(stderr, "TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | can |\n");
-		fprintf(stderr, "          bridge | ipoib | ip6tnl | ipip | sit | vxlan }\n");
+		fprintf(stderr, "TYPE := { vlan | veth | vcan | dummy | ifb | macvlan | macvtap |\n");
+		fprintf(stderr, "          can | bridge | ipoib | ip6tnl | ipip | sit | vxlan |\n");
+		fprintf(stderr, "          gre | gretap | ip6gre | ip6gretap | vti }\n");
 	}
 	exit(-1);
 }
@@ -243,7 +244,7 @@
 			}
 			ivt.vf = vf;
 			addattr_l(&req->n, sizeof(*req), IFLA_VF_TX_RATE, &ivt, sizeof(ivt));
-		
+
 		} else if (matches(*argv, "spoofchk") == 0) {
 			struct ifla_vf_spoofchk ivs;
 			NEXT_ARG();
@@ -286,7 +287,6 @@
 	return 0;
 }
 
-
 int iplink_parse(int argc, char **argv, struct iplink_req *req,
 		char **name, char **type, char **link, char **dev, int *group)
 {
@@ -811,7 +811,6 @@
 	return 0;
 }
 
-
 static int do_set(int argc, char **argv)
 {
 	char *dev = NULL;
diff --git a/ip/iplink_vxlan.c b/ip/iplink_vxlan.c
index 4304b0d..aa551d8 100644
--- a/ip/iplink_vxlan.c
+++ b/ip/iplink_vxlan.c
@@ -43,6 +43,9 @@
 	__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;
@@ -66,21 +69,30 @@
 			vni_set = 1;
 		} else if (!matches(*argv, "group")) {
 			NEXT_ARG();
-			gaddr = get_addr32(*argv);
-
-			if (!IN_MULTICAST(ntohl(gaddr)))
+			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();
-			daddr = get_addr32(*argv);
-
-			if (IN_MULTICAST(ntohl(daddr)))
+			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"))
-				saddr = get_addr32(*argv);
-			if (IN_MULTICAST(ntohl(saddr)))
+			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();
@@ -167,7 +179,9 @@
 		fprintf(stderr, "vxlan: missing virtual network identifier\n");
 		return -1;
 	}
-	if (gaddr && daddr) {
+	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;
 	}
@@ -176,8 +190,16 @@
 		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);
@@ -229,6 +251,17 @@
 				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]) {
@@ -236,6 +269,12 @@
 		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] &&
diff --git a/ip/link_gre6.c b/ip/link_gre6.c
new file mode 100644
index 0000000..4c9c536
--- /dev/null
+++ b/ip/link_gre6.c
@@ -0,0 +1,398 @@
+/*
+ * 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 usage(void) __attribute__((noreturn));
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ip link { add | set | change | replace | del } NAME\n");
+	fprintf(stderr, "          type { ip6gre | ip6gretap } [ remote ADDR ] [ local ADDR ]\n");
+	fprintf(stderr, "          [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n");
+	fprintf(stderr, "          [ hoplimit TTL ] [ encaplimit ELIM ]\n");
+	fprintf(stderr, "          [ tclass TCLASS ] [ flowlabel FLOWLABEL ]\n");
+	fprintf(stderr, "          [ dscp inherit ] [ dev PHYS_DEV ]\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, "Where: NAME      := STRING\n");
+	fprintf(stderr, "       ADDR      := IPV6_ADDRESS\n");
+	fprintf(stderr, "       TTL       := { 0..255 } (default=%d)\n",
+		DEFAULT_TNL_HOP_LIMIT);
+	fprintf(stderr, "       KEY       := { DOTTED_QUAD | NUMBER }\n");
+	fprintf(stderr, "       ELIM      := { none | 0..255 }(default=%d)\n",
+		IPV6_DEFAULT_TNL_ENCAP_LIMIT);
+	fprintf(stderr, "       TCLASS    := { 0x0..0xff | inherit }\n");
+	fprintf(stderr, "       FLOWLABEL := { 0x0..0xfffff | inherit }\n");
+	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)
+				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);
+}
+
+struct link_util ip6gre_link_util = {
+	.id = "ip6gre",
+	.maxattr = IFLA_GRE_MAX,
+	.parse_opt = gre_parse_opt,
+	.print_opt = gre_print_opt,
+};
+
+struct link_util ip6gretap_link_util = {
+	.id = "ip6gretap",
+	.maxattr = IFLA_GRE_MAX,
+	.parse_opt = gre_parse_opt,
+	.print_opt = gre_print_opt,
+};
diff --git a/ip/xfrm_policy.c b/ip/xfrm_policy.c
index 36e33c9..a8d8b98 100644
--- a/ip/xfrm_policy.c
+++ b/ip/xfrm_policy.c
@@ -373,7 +373,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) {
diff --git a/ip/xfrm_state.c b/ip/xfrm_state.c
index 389942c..c4d2bf6 100644
--- a/ip/xfrm_state.c
+++ b/ip/xfrm_state.c
@@ -162,7 +162,7 @@
 			if (len > max)
 				invarg("ALGO-KEYMAT value makes buffer overflow\n", key);
 
-			strncpy(buf, key, len);
+			memcpy(buf, key, len);
 		}
 	}
 
@@ -528,7 +528,7 @@
 		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) {
diff --git a/lib/ll_types.c b/lib/ll_types.c
index 448892b..0c39183 100644
--- a/lib/ll_types.c
+++ b/lib/ll_types.c
@@ -103,6 +103,7 @@
 __PF(PHONET, phonet)
 __PF(PHONET_PIPE, phonet_pipe)
 __PF(CAIF, caif)
+__PF(IP6GRE, gre6)
 
 __PF(NONE, none)
 __PF(VOID,void)
diff --git a/lib/utils.c b/lib/utils.c
index dae1b51..4e9c719 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -868,3 +868,11 @@
 
 	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);
+}
diff --git a/man/man8/bridge.8 b/man/man8/bridge.8
index 66678b5..9a34804 100644
--- a/man/man8/bridge.8
+++ b/man/man8/bridge.8
@@ -13,7 +13,7 @@
 
 .ti -8
 .IR OBJECT " := { "
-.BR link " | " fdb " | " vlan " | " monitor " }"
+.BR link " | " fdb " | " mdb " | " vlan " | " monitor " }"
 .sp
 
 .ti -8
@@ -65,6 +65,21 @@
 .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
@@ -79,7 +94,7 @@
 .IR DEV " ]"
 
 .ti -8
-.BR "bridge monitor" " [ " all " | " neigh " | " link " ]"
+.BR "bridge monitor" " [ " all " | " neigh " | " link " | " mdb " ]"
 
 .SH OPTIONS
 
@@ -110,6 +125,10 @@
 - Forwarding Database entry.
 
 .TP
+.B mdb
+- Multicast group database entry.
+
+.TP
 .B vlan
 - VLAN filter list.
 
@@ -326,6 +345,69 @@
 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
@@ -395,7 +477,7 @@
 .I OBJECT-LIST
 is the list of object types that we want to monitor.
 It may contain
-.BR link ",  and " fdb "."
+.BR link ",  " fdb ", and " mdb "."
 If no
 .B file
 argument is given,
diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in
index 76f92dd..8b68c78 100644
--- a/man/man8/ip-link.8.in
+++ b/man/man8/ip-link.8.in
@@ -62,7 +62,11 @@
 .BR vxlan " |"
 .BR ip6tnl " |"
 .BR ipip " |"
-.BR sit " ]"
+.BR sit " |"
+.BR gre " |"
+.BR gretap " |"
+.BR ip6gre " |"
+.BR ip6gretap " ]"
 
 .ti -8
 .BI "ip link delete " DEVICE
@@ -186,6 +190,18 @@
 .sp
 .BR sit
 - Virtual tunnel interface IPv6 over IPv4
+.sp
+.BR gre
+- Virtual tunnel interface GRE over IPv4
+.sp
+.BR gretap
+- Virtual L2 tuunel interface GRE over IPv4
+.sp
+.BR ip6gre
+- Virtual tuunel interface GRE over IPv6
+.sp
+.BR ip6gretap
+- Virtual L2 tuunel interface GRE over IPv6
 .in -8
 
 .TP
@@ -292,6 +308,112 @@
 
 .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 and 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
+
 .SS ip link delete - delete virtual link
 .I DEVICE
 specifies the virtual  device to act operate on.
diff --git a/man/man8/ip-tunnel.8 b/man/man8/ip-tunnel.8
index 697e80d..fc2d6bd 100644
--- a/man/man8/ip-tunnel.8
+++ b/man/man8/ip-tunnel.8
@@ -50,7 +50,7 @@
 
 .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 " |"
@@ -110,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"
diff --git a/man/man8/lnstat.8 b/man/man8/lnstat.8
index a76940b..699ddf4 100644
--- a/man/man8/lnstat.8
+++ b/man/man8/lnstat.8
@@ -33,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/misc/ifstat.c b/misc/ifstat.c
index 6d0ad8c..a47c046 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;
 
@@ -212,8 +239,13 @@
 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,10 +288,11 @@
 static const unsigned long long mega = 1000000;
 static const unsigned long long kilo = 1000;
 
-static 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)
@@ -265,7 +310,7 @@
 		fprintf(fp, "%-6u ", (unsigned)rates[i]);
 }
 
-static 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)
@@ -328,10 +373,27 @@
 	}
 }
 
-static 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);
@@ -375,27 +437,42 @@
 	}
 }
 
-
 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");
 }
 
 
 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;
@@ -414,8 +491,16 @@
 		}
 		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");
 }
 
 
@@ -559,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");
@@ -575,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' },
@@ -591,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':
@@ -612,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) {
@@ -759,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 90198b7..d6415ef 100644
--- a/misc/lnstat.c
+++ b/misc/lnstat.c
@@ -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,7 +97,7 @@
 	int i;
 
 	for (i = 0; i < fp->num; i++) {
-		struct lnstat_field *lf = fp->params[i].lf;
+		const struct lnstat_field *lf = fp->params[i].lf;
 		char formatbuf[255];
 
 		snprintf(formatbuf, sizeof(formatbuf)-1, "%%%ulu|",
@@ -104,6 +107,30 @@
 	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)
@@ -218,15 +245,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 +276,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 +350,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 +364,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/nstat.c b/misc/nstat.c
index b685978..d3f8621 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;
@@ -255,11 +257,18 @@
 	}
 }
 
+
 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 +285,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");
 }
 
 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,9 +327,17 @@
 			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;
@@ -437,11 +468,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 +504,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 +532,9 @@
 				exit(-1);
 			}
 			break;
+		case 'j':
+			json_output = 1;
+			break;
 		case 'v':
 		case 'V':
 			printf("nstat utility, iproute2-ss%s\n", SNAPSHOT);
@@ -614,8 +671,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/tc/Makefile b/tc/Makefile
index 1eeabd8..f54a955 100644
--- a/tc/Makefile
+++ b/tc/Makefile
@@ -38,6 +38,7 @@
 TCMODULES += m_pedit.o
 TCMODULES += m_skbedit.o
 TCMODULES += m_csum.o
+TCMODULES += m_simple.o
 TCMODULES += p_ip.o
 TCMODULES += p_icmp.o
 TCMODULES += p_tcp.o
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_simple.c b/tc/m_simple.c
new file mode 100644
index 0000000..0224440
--- /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 everytime
+ * 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/q_fq.c b/tc/q_fq.c
index c0bcdb9..c1f658e 100644
--- a/tc/q_fq.c
+++ b/tc/q_fq.c
@@ -53,7 +53,7 @@
 	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"); 
+	fprintf(stderr, "              [ [no]pacing ]\n");
 }
 
 static unsigned int ilog2(unsigned int val)
diff --git a/tc/q_htb.c b/tc/q_htb.c
index 7b6f908..e108857 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"
@@ -108,6 +110,7 @@
 	unsigned mtu;
 	unsigned short mpu = 0;
 	unsigned short overhead = 0;
+	unsigned int direct_qlen = ~0U;
 	unsigned int linklayer  = LINKLAYER_ETHERNET; /* Assume ethernet */
 	struct rtattr *tail;
 
@@ -125,6 +128,11 @@
 			if (get_u32(&mtu, *argv, 10)) {
 				explain1("mtu"); return -1;
 			}
+		} else if (matches(*argv, "direct_qlen") == 0) {
+			NEXT_ARG();
+			if (get_u32(&direct_qlen, *argv, 10)) {
+				explain1("direct_qlen"); return -1;
+			}
 		} else if (matches(*argv, "mpu") == 0) {
 			NEXT_ARG();
 			if (get_u16(&mpu, *argv, 10)) {
@@ -230,6 +238,9 @@
 	opt.cbuffer = tc_calc_xmittime(opt.ceil.rate, cbuffer);
 
 	tail = NLMSG_TAIL(n);
+	if (direct_qlen != ~0U)
+		addattr_l(n, 1024, TCA_HTB_DIRECT_QLEN,
+			  &direct_qlen, sizeof(direct_qlen));
 	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
 	addattr_l(n, 2024, TCA_HTB_PARMS, &opt, sizeof(opt));
 	addattr_l(n, 3024, TCA_HTB_RTAB, rtab, 1024);
@@ -240,7 +251,7 @@
 
 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;
@@ -253,7 +264,7 @@
 	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]);
@@ -302,6 +313,12 @@
 		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;
 }
 
@@ -329,13 +346,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/tc_class.c b/tc/tc_class.c
index 6c0850d..e56bf07 100644
--- a/tc/tc_class.c
+++ b/tc/tc_class.c
@@ -241,6 +241,9 @@
 	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();
diff --git a/tc/tc_qdisc.c b/tc/tc_qdisc.c
index 3002a56..e304858 100644
--- a/tc/tc_qdisc.c
+++ b/tc/tc_qdisc.c
@@ -137,15 +137,16 @@
 	if (est.ewma_log)
 		addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
 
-	if (argc) {
-		if (q) {
-			if (!q->parse_qopt) {
-				fprintf(stderr, "qdisc '%s' does not support option parsing\n", k);
-				return -1;
-			}
+	if (q) {
+		if (q->parse_qopt) {
 			if (q->parse_qopt(q, argc, argv, &req.n))
 				return 1;
-		} else {
+		} else if (argc) {
+			fprintf(stderr, "qdisc '%s' does not support option parsing\n", k);
+			return -1;
+		}
+	} else {
+		if (argc) {
 			if (matches(*argv, "help") == 0)
 				usage();
 
diff --git a/tc/tc_util.c b/tc/tc_util.c
index 8114c97..be3ed07 100644
--- a/tc/tc_util.c
+++ b/tc/tc_util.c
@@ -171,20 +171,24 @@
 	return 0;
 }
 
-void print_rate(char *buf, int len, __u32 rate)
+void print_rate(char *buf, int len, __u64 rate)
 {
 	double tmp = (double)rate*8;
 	extern int use_iec;
 
 	if (use_iec) {
-		if (tmp >= 1000.0*1024.0*1024.0)
+		if (tmp >= 1000.0*1024.0*1024.0*1024.0)
+			snprintf(buf, len, "%.0fGibit", tmp/(1024.0*1024.0*1024.0));
+		else 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)
+		if (tmp >= 1000.0*1000000000.0)
+			snprintf(buf, len, "%.0fGbit", tmp/1000000000.0);
+		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);
@@ -193,7 +197,7 @@
 	}
 }
 
-char * sprint_rate(__u32 rate, char *buf)
+char * sprint_rate(__u64 rate, char *buf)
 {
 	print_rate(buf, SPRINT_BSIZE-1, rate);
 	return buf;
@@ -460,9 +464,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 4f54436..7c3709f 100644
--- a/tc/tc_util.h
+++ b/tc/tc_util.h
@@ -63,12 +63,12 @@
 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);