iproute2: Initial checkin of version iproute2-2.6.31

For http://b/issue?id=2576057

Change-Id: Ic2034b9512b4cbf7a2d66501cd9ef387355eba1d
Signed-off-by: San Mehat <san@google.com>
diff --git a/tc/.gitignore b/tc/.gitignore
new file mode 100644
index 0000000..e8e86c9
--- /dev/null
+++ b/tc/.gitignore
@@ -0,0 +1,5 @@
+*.yacc.c
+*.lex.c
+*.output
+*.yacc.h
+tc
diff --git a/tc/Makefile b/tc/Makefile
new file mode 100644
index 0000000..f3dd2b7
--- /dev/null
+++ b/tc/Makefile
@@ -0,0 +1,130 @@
+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 \
+       m_ematch.o emp_ematch.yacc.o emp_ematch.lex.o
+
+include ../Config
+SHARED_LIBS ?= y
+
+TCMODULES :=
+TCMODULES += q_fifo.o
+TCMODULES += q_sfq.o
+TCMODULES += q_red.o
+TCMODULES += q_prio.o
+TCMODULES += q_tbf.o
+TCMODULES += q_cbq.o
+TCMODULES += q_rr.o
+TCMODULES += q_multiq.o
+TCMODULES += q_netem.o
+TCMODULES += f_rsvp.o
+TCMODULES += f_u32.o
+TCMODULES += f_route.o
+TCMODULES += f_fw.o
+TCMODULES += f_basic.o
+TCMODULES += f_flow.o
+TCMODULES += f_cgroup.o
+TCMODULES += q_dsmark.o
+TCMODULES += q_gred.o
+TCMODULES += f_tcindex.o
+TCMODULES += q_ingress.o
+TCMODULES += q_hfsc.o
+TCMODULES += q_htb.o
+TCMODULES += q_drr.o
+TCMODULES += m_gact.o
+TCMODULES += m_mirred.o
+TCMODULES += m_nat.o
+TCMODULES += m_pedit.o
+TCMODULES += m_skbedit.o
+TCMODULES += p_ip.o
+TCMODULES += p_icmp.o
+TCMODULES += p_tcp.o
+TCMODULES += p_udp.o
+TCMODULES += em_nbyte.o
+TCMODULES += em_cmp.o
+TCMODULES += em_u32.o
+TCMODULES += em_meta.o
+
+
+ifeq ($(TC_CONFIG_XT),y)
+  TCMODULES += m_xt.o
+  LDLIBS += -lxtables
+else
+  ifeq ($(TC_CONFIG_XT_OLD),y)
+    TCMODULES += m_xt_old.o
+    LDLIBS += -lxtables
+  else
+    ifeq ($(TC_CONFIG_XT_OLD_H),y)
+	CFLAGS += -DTC_CONFIG_XT_H
+	TCMODULES += m_xt_old.o
+	LDLIBS += -lxtables
+    else
+      TCMODULES += m_ipt.o
+    endif
+  endif
+endif
+
+TCOBJ += $(TCMODULES)
+LDLIBS += -L. -ltc -lm
+
+ifeq ($(SHARED_LIBS),y)
+LDLIBS += -ldl
+LDFLAGS += -Wl,-export-dynamic
+endif
+
+TCLIB := tc_core.o
+TCLIB += tc_red.o
+TCLIB += tc_cbq.o
+TCLIB += tc_estimator.o
+TCLIB += tc_stab.o
+
+CFLAGS += -DCONFIG_GACT -DCONFIG_GACT_PROB
+
+TCSO :=
+ifeq ($(TC_CONFIG_ATM),y)
+  TCSO += q_atm.so
+endif
+
+YACC := bison
+LEX := flex
+
+%.so: %.c
+	$(CC) $(CFLAGS) -shared -fpic $< -o $@
+
+
+all: libtc.a tc $(TCSO)
+
+tc: $(TCOBJ) $(LIBNETLINK) $(LIBUTIL) $(TCLIB)
+
+libtc.a: $(TCLIB)
+	$(AR) rcs $@ $(TCLIB)
+
+install: all
+	mkdir -p $(DESTDIR)$(LIBDIR)/tc
+	install -m 0755 tc $(DESTDIR)$(SBINDIR)
+	for i in $(TCSO); \
+	do install -m 755 $$i $(DESTDIR)$(LIBDIR)/tc; \
+	done
+
+clean:
+	rm -f $(TCOBJ) $(TCLIB) libtc.a tc *.so emp_ematch.yacc.h; \
+	rm -f emp_ematch.yacc.output
+
+q_atm.so: q_atm.c
+	$(CC) $(CFLAGS) $(LDFLAGS) -shared -fpic -o q_atm.so q_atm.c -latm
+
+%.yacc.c: %.y
+	$(YACC) $(YACCFLAGS) -o $@ $<
+
+%.lex.c: %.l
+	$(LEX) $(LEXFLAGS) -o$@ $<
+
+ifneq ($(SHARED_LIBS),y)
+
+tc: static-syms.o
+static-syms.o: static-syms.h
+static-syms.h: $(wildcard *.c)
+	files="$^" ; \
+	for s in `grep -B 3 '\<dlsym' $$files | sed -n '/snprintf/{s:.*"\([^"]*\)".*:\1:;s:%s::;p}'` ; do \
+		sed -n '/'$$s'[^ ]* =/{s:.* \([^ ]*'$$s'[^ ]*\) .*:extern char \1[] __attribute__((weak)); if (!strcmp(sym, "\1")) return \1;:;p}' $$files ; \
+	done > $@
+
+endif
diff --git a/tc/README.last b/tc/README.last
new file mode 100644
index 0000000..9400438
--- /dev/null
+++ b/tc/README.last
@@ -0,0 +1,47 @@
+Kernel code and interface.
+--------------------------
+
+* Compile time switches
+
+There is only one, but very important, compile time switch.
+It is not settable by "make config", but should be selected
+manually and after a bit of thinking in <include/net/pkt_sched.h>
+
+PSCHED_CLOCK_SOURCE can take three values:
+
+	PSCHED_GETTIMEOFDAY
+	PSCHED_JIFFIES
+	PSCHED_CPU
+
+
+ PSCHED_GETTIMEOFDAY
+
+Default setting is the most conservative PSCHED_GETTIMEOFDAY.
+It is very slow both because of weird slowness of do_gettimeofday()
+and because it forces code to use unnatural "timeval" format,
+where microseconds and seconds fields are separate.
+Besides that, it will misbehave, when delays exceed 2 seconds
+(f.e. very slow links or classes bounded to small slice of bandwidth)
+To resume: as only you will get it working, select correct clock
+source and forget about PSCHED_GETTIMEOFDAY forever.
+
+
+ PSCHED_JIFFIES
+
+Clock is derived from jiffies. On architectures with HZ=100
+granularity of this clock is not enough to make reasonable
+bindings to real time. However, taking into account Linux
+architecture problems, which force us to use artificial
+integrated clock in any case, this switch is not so bad
+for schduling even on high speed networks, though policing
+is not reliable.
+
+
+ PSCHED_CPU
+
+It is available only for alpha and pentiums with correct
+CPU timestamp. It is the fastest way, use it when it is available,
+but remember: not all pentiums have this facility, and
+a lot of them have clock, broken by APM etc. etc.
+
+
diff --git a/tc/em_cmp.c b/tc/em_cmp.c
new file mode 100644
index 0000000..6addce0
--- /dev/null
+++ b/tc/em_cmp.c
@@ -0,0 +1,187 @@
+/*
+ * em_cmp.c		Simple comparison Ematch
+ *
+ *		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:	Thomas Graf <tgraf@suug.ch>
+ */
+
+#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 "m_ematch.h"
+#include <linux/tc_ematch/tc_em_cmp.h>
+
+extern struct ematch_util cmp_ematch_util;
+
+static void cmp_print_usage(FILE *fd)
+{
+	fprintf(fd,
+	    "Usage: cmp(ALIGN at OFFSET [ ATTRS ] { eq | lt | gt } VALUE)\n" \
+	    "where: ALIGN  := { u8 | u16 | u32 }\n" \
+	    "       ATTRS  := [ layer LAYER ] [ mask MASK ] [ trans ]\n" \
+	    "       LAYER  := { link | network | transport | 0..%d }\n" \
+	    "\n" \
+	    "Example: cmp(u16 at 3 layer 2 mask 0xff00 gt 20)\n",
+	    TCF_LAYER_MAX);
+}
+
+static int cmp_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+			  struct bstr *args)
+{
+	struct bstr *a;
+	int align, opnd = 0;
+	unsigned long offset = 0, layer = TCF_LAYER_NETWORK, mask = 0, value = 0;
+	int offset_present = 0, value_present = 0;
+	struct tcf_em_cmp cmp;
+
+	memset(&cmp, 0, sizeof(cmp));
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+	em_parse_error(EINVAL, args, CARG, &cmp_ematch_util, FMT ,##ARGS)
+
+	if (args == NULL)
+		return PARSE_ERR(args, "cmp: missing arguments");
+
+	if (!bstrcmp(args, "u8"))
+		align = TCF_EM_ALIGN_U8;
+	else if (!bstrcmp(args, "u16"))
+		align = TCF_EM_ALIGN_U16;
+	else if (!bstrcmp(args, "u32"))
+		align = TCF_EM_ALIGN_U32;
+	else
+		return PARSE_ERR(args, "cmp: invalid alignment");
+
+	for (a = bstr_next(args); a; a = bstr_next(a)) {
+		if (!bstrcmp(a, "at")) {
+			if (a->next == NULL)
+				return PARSE_ERR(a, "cmp: missing argument");
+			a = bstr_next(a);
+
+			offset = bstrtoul(a);
+			if (offset == ULONG_MAX)
+				return PARSE_ERR(a, "cmp: invalid offset, " \
+				    "must be numeric");
+
+			offset_present = 1;
+		} else if (!bstrcmp(a, "layer")) {
+			if (a->next == NULL)
+				return PARSE_ERR(a, "cmp: missing argument");
+			a = bstr_next(a);
+
+			layer = parse_layer(a);
+			if (layer == INT_MAX) {
+				layer = bstrtoul(a);
+				if (layer == ULONG_MAX)
+					return PARSE_ERR(a, "cmp: invalid " \
+					    "layer");
+			}
+
+			if (layer > TCF_LAYER_MAX)
+				return PARSE_ERR(a, "cmp: illegal layer, " \
+				    "must be in 0..%d", TCF_LAYER_MAX);
+		} else if (!bstrcmp(a, "mask")) {
+			if (a->next == NULL)
+				return PARSE_ERR(a, "cmp: missing argument");
+			a = bstr_next(a);
+
+			mask = bstrtoul(a);
+			if (mask == ULONG_MAX)
+				return PARSE_ERR(a, "cmp: invalid mask");
+		} else if (!bstrcmp(a, "trans")) {
+			cmp.flags |= TCF_EM_CMP_TRANS;
+		} else if (!bstrcmp(a, "eq") || !bstrcmp(a, "gt") ||
+		    !bstrcmp(a, "lt")) {
+
+			if (!bstrcmp(a, "eq"))
+				opnd = TCF_EM_OPND_EQ;
+			else if (!bstrcmp(a, "gt"))
+				opnd = TCF_EM_OPND_GT;
+			else if (!bstrcmp(a, "lt"))
+				opnd = TCF_EM_OPND_LT;
+
+			if (a->next == NULL)
+				return PARSE_ERR(a, "cmp: missing argument");
+			a = bstr_next(a);
+
+			value = bstrtoul(a);
+			if (value == ULONG_MAX)
+				return PARSE_ERR(a, "cmp: invalid value");
+
+			value_present = 1;
+		} else
+			return PARSE_ERR(a, "nbyte: unknown parameter");
+	}
+
+	if (offset_present == 0 || value_present == 0)
+		return PARSE_ERR(a, "cmp: offset and value required");
+
+	cmp.val = (__u32) value;
+	cmp.mask = (__u32) mask;
+	cmp.off = (__u16) offset;
+	cmp.align = (__u8) align;
+	cmp.layer = (__u8) layer;
+	cmp.opnd = (__u8) opnd;
+
+	addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+	addraw_l(n, MAX_MSG, &cmp, sizeof(cmp));
+
+#undef PARSE_ERR
+	return 0;
+}
+
+static int cmp_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+			  int data_len)
+{
+	struct tcf_em_cmp *cmp = data;
+
+	if (data_len < sizeof(*cmp)) {
+		fprintf(stderr, "CMP header size mismatch\n");
+		return -1;
+	}
+
+	if (cmp->align == TCF_EM_ALIGN_U8)
+		fprintf(fd, "u8 ");
+	else if (cmp->align == TCF_EM_ALIGN_U16)
+		fprintf(fd, "u16 ");
+	else if (cmp->align == TCF_EM_ALIGN_U16)
+		fprintf(fd, "u32 ");
+
+	fprintf(fd, "at %d layer %d ", cmp->off, cmp->layer);
+
+	if (cmp->mask)
+		fprintf(fd, "mask 0x%x ", cmp->mask);
+
+	if (cmp->flags & TCF_EM_CMP_TRANS)
+		fprintf(fd, "trans ");
+
+	if (cmp->opnd == TCF_EM_OPND_EQ)
+		fprintf(fd, "eq ");
+	else if (cmp->opnd == TCF_EM_OPND_LT)
+		fprintf(fd, "lt ");
+	else if (cmp->opnd == TCF_EM_OPND_GT)
+		fprintf(fd, "gt ");
+
+	fprintf(fd, "%d", cmp->val);
+
+	return 0;
+}
+
+struct ematch_util cmp_ematch_util = {
+	.kind = "cmp",
+	.kind_num = TCF_EM_CMP,
+	.parse_eopt = cmp_parse_eopt,
+	.print_eopt = cmp_print_eopt,
+	.print_usage = cmp_print_usage
+};
diff --git a/tc/em_meta.c b/tc/em_meta.c
new file mode 100644
index 0000000..033e29f
--- /dev/null
+++ b/tc/em_meta.c
@@ -0,0 +1,546 @@
+/*
+ * em_meta.c		Metadata Ematch
+ *
+ *		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:	Thomas Graf <tgraf@suug.ch>
+ */
+
+#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 "m_ematch.h"
+#include <linux/tc_ematch/tc_em_meta.h>
+
+extern struct ematch_util meta_ematch_util;
+
+static void meta_print_usage(FILE *fd)
+{
+	fprintf(fd,
+	    "Usage: meta(OBJECT { eq | lt | gt } OBJECT)\n" \
+	    "where: OBJECT  := { META_ID | VALUE }\n" \
+	    "       META_ID := id [ shift SHIFT ] [ mask MASK ]\n" \
+	    "\n" \
+	    "Example: meta(nfmark gt 24)\n" \
+	    "         meta(indev shift 1 eq \"ppp\")\n" \
+	    "         meta(tcindex mask 0xf0 eq 0xf0)\n" \
+	    "\n" \
+	    "For a list of meta identifiers, use meta(list).\n");
+}
+
+struct meta_entry {
+	int		id;
+	char *		kind;
+	char *		mask;
+	char *		desc;
+} meta_table[] = {
+#define TCF_META_ID_SECTION 0
+#define __A(id, name, mask, desc) { TCF_META_ID_##id, name, mask, desc }
+	__A(SECTION,		"Generic", "", ""),
+	__A(RANDOM,		"random",	"i",
+				"Random value (32 bit)"),
+	__A(LOADAVG_0,		"loadavg_1",	"i",
+				"Load average in last minute"),
+	__A(LOADAVG_1,		"loadavg_5",	"i",
+				"Load average in last 5 minutes"),
+	__A(LOADAVG_2,		"loadavg_15",	"i",
+				"Load average in last 15 minutes"),
+
+	__A(SECTION,		"Interfaces", "", ""),
+	__A(DEV,		"dev",		"iv",
+				"Device the packet is on"),
+	__A(SECTION,		"Packet attributes", "", ""),
+	__A(PRIORITY,		"priority",	"i",
+				"Priority of packet"),
+	__A(PROTOCOL,		"protocol",	"i",
+				"Link layer protocol"),
+	__A(PKTTYPE,		"pkt_type",	"i",
+				"Packet type (uni|multi|broad|...)cast"),
+	__A(PKTLEN,		"pkt_len",	"i",
+				"Length of packet"),
+	__A(DATALEN,		"data_len",	"i",
+				"Length of data in packet"),
+	__A(MACLEN,		"mac_len",	"i",
+				"Length of link layer header"),
+
+	__A(SECTION,		"Netfilter", "", ""),
+	__A(NFMARK,		"nf_mark",	"i",
+				"Netfilter mark"),
+	__A(NFMARK,		"fwmark",	"i",
+				"Alias for nf_mark"),
+
+	__A(SECTION,		"Traffic Control", "", ""),
+	__A(TCINDEX,		"tc_index",	"i",	"TC Index"),
+	__A(SECTION,		"Routing", "", ""),
+	__A(RTCLASSID,		"rt_classid",	"i",
+				"Routing ClassID (cls_route)"),
+	__A(RTIIF,		"rt_iif",	"i",
+				"Incoming interface index"),
+	__A(VLAN_TAG,		"vlan",		"i",	"Vlan tag"),
+
+	__A(SECTION,		"Sockets", "", ""),
+	__A(SK_FAMILY,		"sk_family",	"i",	"Address family"),
+	__A(SK_STATE,		"sk_state",	"i",	"State"),
+	__A(SK_REUSE,		"sk_reuse",	"i",	"Reuse Flag"),
+	__A(SK_BOUND_IF,	"sk_bind_if",	"iv",	"Bound interface"),
+	__A(SK_REFCNT,		"sk_refcnt",	"i",	"Reference counter"),
+	__A(SK_SHUTDOWN,	"sk_shutdown",	"i",	"Shutdown mask"),
+	__A(SK_PROTO,		"sk_proto",	"i",	"Protocol"),
+	__A(SK_TYPE,		"sk_type",	"i",	"Type"),
+	__A(SK_RCVBUF,		"sk_rcvbuf",	"i",	"Receive buffer size"),
+	__A(SK_RMEM_ALLOC,	"sk_rmem",	"i",	"RMEM"),
+	__A(SK_WMEM_ALLOC,	"sk_wmem",	"i",	"WMEM"),
+	__A(SK_OMEM_ALLOC,	"sk_omem",	"i",	"OMEM"),
+	__A(SK_WMEM_QUEUED,	"sk_wmem_queue","i",	"WMEM queue"),
+	__A(SK_SND_QLEN,	"sk_snd_queue",	"i",	"Send queue length"),
+	__A(SK_RCV_QLEN,	"sk_rcv_queue",	"i",	"Receive queue length"),
+	__A(SK_ERR_QLEN,	"sk_err_queue",	"i",	"Error queue length"),
+	__A(SK_FORWARD_ALLOCS,	"sk_fwd_alloc",	"i",	"Forward allocations"),
+	__A(SK_SNDBUF,		"sk_sndbuf",	"i",	"Send buffer size"),
+#undef __A
+};
+
+static inline int map_type(char k)
+{
+	switch (k) {
+		case 'i': return TCF_META_TYPE_INT;
+		case 'v': return TCF_META_TYPE_VAR;
+	}
+
+	fprintf(stderr, "BUG: Unknown map character '%c'\n", k);
+	return INT_MAX;
+}
+
+static struct meta_entry * lookup_meta_entry(struct bstr *kind)
+{
+	int i;
+
+	for (i = 0; i < (sizeof(meta_table)/sizeof(meta_table[0])); i++)
+		if (!bstrcmp(kind, meta_table[i].kind) &&
+		    meta_table[i].id != 0)
+			return &meta_table[i];
+
+	return NULL;
+}
+
+static struct meta_entry * lookup_meta_entry_byid(int id)
+{
+	int i;
+
+	for (i = 0; i < (sizeof(meta_table)/sizeof(meta_table[0])); i++)
+		if (meta_table[i].id == id)
+			return &meta_table[i];
+
+	return NULL;
+}
+
+static inline void dump_value(struct nlmsghdr *n, int tlv, unsigned long val,
+			      struct tcf_meta_val *hdr)
+{
+	__u32 t;
+
+	switch (TCF_META_TYPE(hdr->kind)) {
+		case TCF_META_TYPE_INT:
+			t = val;
+			addattr_l(n, MAX_MSG, tlv, &t, sizeof(t));
+			break;
+
+		case TCF_META_TYPE_VAR:
+			if (TCF_META_ID(hdr->kind) == TCF_META_ID_VALUE) {
+				struct bstr *a = (struct bstr *) val;
+				addattr_l(n, MAX_MSG, tlv, a->data, a->len);
+			}
+			break;
+	}
+}
+
+static inline int is_compatible(struct tcf_meta_val *what,
+				struct tcf_meta_val *needed)
+{
+	char *p;
+	struct meta_entry *entry;
+
+	entry = lookup_meta_entry_byid(TCF_META_ID(what->kind));
+
+	if (entry == NULL)
+		return 0;
+
+	for (p = entry->mask; p; p++)
+		if (map_type(*p) == TCF_META_TYPE(needed->kind))
+			return 1;
+
+	return 0;
+}
+
+static void list_meta_ids(FILE *fd)
+{
+	int i;
+
+	fprintf(fd,
+	    "--------------------------------------------------------\n" \
+	    "  ID               Type       Description\n" \
+	    "--------------------------------------------------------");
+
+	for (i = 0; i < (sizeof(meta_table)/sizeof(meta_table[0])); i++) {
+		if (meta_table[i].id == TCF_META_ID_SECTION) {
+			fprintf(fd, "\n%s:\n", meta_table[i].kind);
+		} else {
+			char *p = meta_table[i].mask;
+			char buf[64] = {0};
+
+			fprintf(fd, "  %-16s ", meta_table[i].kind);
+
+			while (*p) {
+				int type = map_type(*p);
+
+				switch (type) {
+					case TCF_META_TYPE_INT:
+						strcat(buf, "INT");
+						break;
+
+					case TCF_META_TYPE_VAR:
+						strcat(buf, "VAR");
+						break;
+				}
+
+				if (*(++p))
+					strcat(buf, ",");
+			}
+
+			fprintf(fd, "%-10s %s\n", buf, meta_table[i].desc);
+		}
+	}
+
+	fprintf(fd,
+	    "--------------------------------------------------------\n");
+}
+
+#undef TCF_META_ID_SECTION
+
+#define PARSE_FAILURE ((void *) (-1))
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+	em_parse_error(EINVAL, args, CARG, &meta_ematch_util, FMT ,##ARGS)
+
+static inline int can_adopt(struct tcf_meta_val *val)
+{
+	return !!TCF_META_ID(val->kind);
+}
+
+static inline int overwrite_type(struct tcf_meta_val *src,
+				 struct tcf_meta_val *dst)
+{
+	return (TCF_META_TYPE(dst->kind) << 12) | TCF_META_ID(src->kind);
+}
+
+
+static inline struct bstr *
+parse_object(struct bstr *args, struct bstr *arg, struct tcf_meta_val *obj,
+	     unsigned long *dst, struct tcf_meta_val *left)
+{
+	struct meta_entry *entry;
+	unsigned long num;
+	struct bstr *a;
+
+	if (arg->quoted) {
+		obj->kind = TCF_META_TYPE_VAR << 12;
+		obj->kind |= TCF_META_ID_VALUE;
+		*dst = (unsigned long) arg;
+		return bstr_next(arg);
+	}
+
+	num = bstrtoul(arg);
+	if (num != ULONG_MAX) {
+		obj->kind = TCF_META_TYPE_INT << 12;
+		obj->kind |= TCF_META_ID_VALUE;
+		*dst = (unsigned long) num;
+		return bstr_next(arg);
+	}
+
+	entry = lookup_meta_entry(arg);
+
+	if (entry == NULL) {
+		PARSE_ERR(arg, "meta: unknown meta id\n");
+		return PARSE_FAILURE;
+	}
+
+	obj->kind = entry->id | (map_type(entry->mask[0]) << 12);
+
+	if (left) {
+		struct tcf_meta_val *right = obj;
+
+		if (TCF_META_TYPE(right->kind) == TCF_META_TYPE(left->kind))
+			goto compatible;
+
+		if (can_adopt(left) && !can_adopt(right)) {
+			if (is_compatible(left, right))
+				left->kind = overwrite_type(left, right);
+			else
+				goto not_compatible;
+		} else if (can_adopt(right) && !can_adopt(left)) {
+			if (is_compatible(right, left))
+				right->kind = overwrite_type(right, left);
+			else
+				goto not_compatible;
+		} else if (can_adopt(left) && can_adopt(right)) {
+			if (is_compatible(left, right))
+				left->kind = overwrite_type(left, right);
+			else if (is_compatible(right, left))
+				right->kind = overwrite_type(right, left);
+			else
+				goto not_compatible;
+		} else
+			goto not_compatible;
+	}
+
+compatible:
+
+	a = bstr_next(arg);
+
+	while(a) {
+		if (!bstrcmp(a, "shift")) {
+			unsigned long shift;
+
+			if (a->next == NULL) {
+				PARSE_ERR(a, "meta: missing argument");
+				return PARSE_FAILURE;
+			}
+			a = bstr_next(a);
+
+			shift = bstrtoul(a);
+			if (shift == ULONG_MAX) {
+				PARSE_ERR(a, "meta: invalid shift, must " \
+				    "be numeric");
+				return PARSE_FAILURE;
+			}
+
+			obj->shift = (__u8) shift;
+			a = bstr_next(a);
+		} else if (!bstrcmp(a, "mask")) {
+			unsigned long mask;
+
+			if (a->next == NULL) {
+				PARSE_ERR(a, "meta: missing argument");
+				return PARSE_FAILURE;
+			}
+			a = bstr_next(a);
+
+			mask = bstrtoul(a);
+			if (mask == ULONG_MAX) {
+				PARSE_ERR(a, "meta: invalid mask, must be " \
+				    "numeric");
+				return PARSE_FAILURE;
+			}
+			*dst = (unsigned long) mask;
+			a = bstr_next(a);
+		} else
+			break;
+	}
+
+	return a;
+
+not_compatible:
+	PARSE_ERR(arg, "lvalue and rvalue are not compatible.");
+	return PARSE_FAILURE;
+}
+
+static int meta_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+			   struct bstr *args)
+{
+	int opnd;
+	struct bstr *a;
+	struct tcf_meta_hdr meta_hdr;
+	unsigned long lvalue = 0, rvalue = 0;
+
+	memset(&meta_hdr, 0, sizeof(meta_hdr));
+
+	if (args == NULL)
+		return PARSE_ERR(args, "meta: missing arguments");
+
+	if (!bstrcmp(args, "list")) {
+		list_meta_ids(stderr);
+		return -1;
+	}
+
+	a = parse_object(args, args, &meta_hdr.left, &lvalue, NULL);
+	if (a == PARSE_FAILURE)
+		return -1;
+	else if (a == NULL)
+		return PARSE_ERR(args, "meta: missing operand");
+
+	if (!bstrcmp(a, "eq"))
+		opnd = TCF_EM_OPND_EQ;
+	else if (!bstrcmp(a, "gt"))
+		opnd = TCF_EM_OPND_GT;
+	else if (!bstrcmp(a, "lt"))
+		opnd = TCF_EM_OPND_LT;
+	else
+		return PARSE_ERR(a, "meta: invalid operand");
+
+	meta_hdr.left.op = (__u8) opnd;
+
+	if (a->next == NULL)
+		return PARSE_ERR(args, "meta: missing rvalue");
+	a = bstr_next(a);
+
+	a = parse_object(args, a, &meta_hdr.right, &rvalue, &meta_hdr.left);
+	if (a == PARSE_FAILURE)
+		return -1;
+	else if (a != NULL)
+		return PARSE_ERR(a, "meta: unexpected trailer");
+
+
+	addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+
+	addattr_l(n, MAX_MSG, TCA_EM_META_HDR, &meta_hdr, sizeof(meta_hdr));
+
+	dump_value(n, TCA_EM_META_LVALUE, lvalue, &meta_hdr.left);
+	dump_value(n, TCA_EM_META_RVALUE, rvalue, &meta_hdr.right);
+
+	return 0;
+}
+#undef PARSE_ERR
+
+static inline void print_binary(FILE *fd, unsigned char *str, int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++)
+		if (!isprint(str[i]))
+			goto binary;
+
+	for (i = 0; i < len; i++)
+		fprintf(fd, "%c", str[i]);
+	return;
+
+binary:
+	for (i = 0; i < len; i++)
+		fprintf(fd, "%02x ", str[i]);
+
+	fprintf(fd, "\"");
+	for (i = 0; i < len; i++)
+		fprintf(fd, "%c", isprint(str[i]) ? str[i] : '.');
+	fprintf(fd, "\"");
+}
+
+static inline int print_value(FILE *fd, int type, struct rtattr *rta)
+{
+	if (rta == NULL) {
+		fprintf(stderr, "Missing value TLV\n");
+		return -1;
+	}
+
+	switch(type) {
+		case TCF_META_TYPE_INT:
+			if (RTA_PAYLOAD(rta) < sizeof(__u32)) {
+				fprintf(stderr, "meta int type value TLV " \
+				    "size mismatch.\n");
+				return -1;
+			}
+			fprintf(fd, "%d", *(__u32 *) RTA_DATA(rta));
+			break;
+
+		case TCF_META_TYPE_VAR:
+			print_binary(fd, RTA_DATA(rta), RTA_PAYLOAD(rta));
+			break;
+	}
+
+	return 0;
+}
+
+static int print_object(FILE *fd, struct tcf_meta_val *obj, struct rtattr *rta)
+{
+	int id = TCF_META_ID(obj->kind);
+	int type = TCF_META_TYPE(obj->kind);
+	struct meta_entry *entry;
+
+	if (id == TCF_META_ID_VALUE)
+		return print_value(fd, type, rta);
+
+	entry = lookup_meta_entry_byid(id);
+
+	if (entry == NULL)
+		fprintf(fd, "[unknown meta id %d]", id);
+	else
+		fprintf(fd, "%s", entry->kind);
+
+	if (obj->shift)
+		fprintf(fd, " shift %d", obj->shift);
+
+	switch (type) {
+		case TCF_META_TYPE_INT:
+			if (rta) {
+				if (RTA_PAYLOAD(rta) < sizeof(__u32))
+					goto size_mismatch;
+
+				fprintf(fd, " mask 0x%08x",
+				    *(__u32*) RTA_DATA(rta));
+			}
+			break;
+	}
+
+	return 0;
+
+size_mismatch:
+	fprintf(stderr, "meta int type mask TLV size mismatch\n");
+	return -1;
+}
+
+
+static int meta_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+			   int data_len)
+{
+	struct rtattr *tb[TCA_EM_META_MAX+1];
+	struct tcf_meta_hdr *meta_hdr;
+
+	if (parse_rtattr(tb, TCA_EM_META_MAX, data, data_len) < 0)
+		return -1;
+
+	if (tb[TCA_EM_META_HDR] == NULL) {
+		fprintf(stderr, "Missing meta header\n");
+		return -1;
+	}
+
+	if (RTA_PAYLOAD(tb[TCA_EM_META_HDR]) < sizeof(*meta_hdr)) {
+		fprintf(stderr, "Meta header size mismatch\n");
+		return -1;
+	}
+
+	meta_hdr = RTA_DATA(tb[TCA_EM_META_HDR]);
+
+	if (print_object(fd, &meta_hdr->left, tb[TCA_EM_META_LVALUE]) < 0)
+		return -1;
+
+	switch (meta_hdr->left.op) {
+		case TCF_EM_OPND_EQ:
+			fprintf(fd, " eq ");
+			break;
+		case TCF_EM_OPND_LT:
+			fprintf(fd, " lt ");
+			break;
+		case TCF_EM_OPND_GT:
+			fprintf(fd, " gt ");
+			break;
+	}
+
+	return print_object(fd, &meta_hdr->right, tb[TCA_EM_META_RVALUE]);
+}
+
+struct ematch_util meta_ematch_util = {
+	.kind = "meta",
+	.kind_num = TCF_EM_META,
+	.parse_eopt = meta_parse_eopt,
+	.print_eopt = meta_print_eopt,
+	.print_usage = meta_print_usage
+};
diff --git a/tc/em_nbyte.c b/tc/em_nbyte.c
new file mode 100644
index 0000000..87f3e9d
--- /dev/null
+++ b/tc/em_nbyte.c
@@ -0,0 +1,143 @@
+/*
+ * em_nbyte.c		N-Byte Ematch
+ *
+ *		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:	Thomas Graf <tgraf@suug.ch>
+ */
+
+#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 "m_ematch.h"
+#include <linux/tc_ematch/tc_em_nbyte.h>
+
+extern struct ematch_util nbyte_ematch_util;
+
+static void nbyte_print_usage(FILE *fd)
+{
+	fprintf(fd,
+	    "Usage: nbyte(NEEDLE at OFFSET [layer LAYER])\n" \
+	    "where: NEEDLE := { string | \"c-escape-sequence\" }\n" \
+	    "       OFFSET := int\n" \
+	    "       LAYER  := { link | network | transport | 0..%d }\n" \
+	    "\n" \
+	    "Example: nbyte(\"ababa\" at 12 layer 1)\n",
+	    TCF_LAYER_MAX);
+}
+
+static int nbyte_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+			    struct bstr *args)
+{
+	struct bstr *a;
+	struct bstr *needle = args;
+	unsigned long offset = 0, layer = TCF_LAYER_NETWORK;
+	int offset_present = 0;
+	struct tcf_em_nbyte nb;
+
+	memset(&nb, 0, sizeof(nb));
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+	em_parse_error(EINVAL, args, CARG, &nbyte_ematch_util, FMT ,##ARGS)
+
+	if (args == NULL)
+		return PARSE_ERR(args, "nbyte: missing arguments");
+
+	if (needle->len <= 0)
+		return PARSE_ERR(args, "nbyte: needle length is 0");
+
+	for (a = bstr_next(args); a; a = bstr_next(a)) {
+		if (!bstrcmp(a, "at")) {
+			if (a->next == NULL)
+				return PARSE_ERR(a, "nbyte: missing argument");
+			a = bstr_next(a);
+
+			offset = bstrtoul(a);
+			if (offset == ULONG_MAX)
+				return PARSE_ERR(a, "nbyte: invalid offset, " \
+				    "must be numeric");
+
+			offset_present = 1;
+		} else if (!bstrcmp(a, "layer")) {
+			if (a->next == NULL)
+				return PARSE_ERR(a, "nbyte: missing argument");
+			a = bstr_next(a);
+
+			layer = parse_layer(a);
+			if (layer == INT_MAX) {
+				layer = bstrtoul(a);
+				if (layer == ULONG_MAX)
+					return PARSE_ERR(a, "nbyte: invalid " \
+					    "layer");
+			}
+
+			if (layer > TCF_LAYER_MAX)
+				return PARSE_ERR(a, "nbyte: illegal layer, " \
+				    "must be in 0..%d", TCF_LAYER_MAX);
+		} else
+			return PARSE_ERR(a, "nbyte: unknown parameter");
+	}
+
+	if (offset_present == 0)
+		return PARSE_ERR(a, "nbyte: offset required");
+
+	nb.len = needle->len;
+	nb.layer = (__u8) layer;
+	nb.off = (__u16) offset;
+
+	addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+	addraw_l(n, MAX_MSG, &nb, sizeof(nb));
+	addraw_l(n, MAX_MSG, needle->data, needle->len);
+
+#undef PARSE_ERR
+	return 0;
+}
+
+static int nbyte_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+			    int data_len)
+{
+	int i;
+	struct tcf_em_nbyte *nb = data;
+	__u8 *needle;
+
+	if (data_len < sizeof(*nb)) {
+		fprintf(stderr, "NByte header size mismatch\n");
+		return -1;
+	}
+
+	if (data_len < sizeof(*nb) + nb->len) {
+		fprintf(stderr, "NByte payload size mismatch\n");
+		return -1;
+	}
+
+	needle = data + sizeof(*nb);
+
+	for (i = 0; i < nb->len; i++)
+		fprintf(fd, "%02x ", needle[i]);
+
+	fprintf(fd, "\"");
+	for (i = 0; i < nb->len; i++)
+		fprintf(fd, "%c", isprint(needle[i]) ? needle[i] : '.');
+	fprintf(fd, "\" at %d layer %d", nb->off, nb->layer);
+
+	return 0;
+}
+
+struct ematch_util nbyte_ematch_util = {
+	.kind = "nbyte",
+	.kind_num = TCF_EM_NBYTE,
+	.parse_eopt = nbyte_parse_eopt,
+	.print_eopt = nbyte_print_eopt,
+	.print_usage = nbyte_print_usage
+};
diff --git a/tc/em_u32.c b/tc/em_u32.c
new file mode 100644
index 0000000..21ed70f
--- /dev/null
+++ b/tc/em_u32.c
@@ -0,0 +1,177 @@
+/*
+ * em_u32.c		U32 Ematch
+ *
+ *		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:	Thomas Graf <tgraf@suug.ch>
+ */
+
+#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 "m_ematch.h"
+
+extern struct ematch_util u32_ematch_util;
+
+static void u32_print_usage(FILE *fd)
+{
+	fprintf(fd,
+	    "Usage: u32(ALIGN VALUE MASK at [ nexthdr+ ] OFFSET)\n" \
+	    "where: ALIGN  := { u8 | u16 | u32 }\n" \
+	    "\n" \
+	    "Example: u32(u16 0x1122 0xffff at nexthdr+4)\n");
+}
+
+static int u32_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+			  struct bstr *args)
+{
+	struct bstr *a;
+	int align, nh_len;
+	unsigned long key, mask, offmask = 0, offset;
+	struct tc_u32_key u_key;
+
+	memset(&u_key, 0, sizeof(u_key));
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+	em_parse_error(EINVAL, args, CARG, &u32_ematch_util, FMT ,##ARGS)
+
+	if (args == NULL)
+		return PARSE_ERR(args, "u32: missing arguments");
+
+	if (!bstrcmp(args, "u8"))
+		align = 1;
+	else if (!bstrcmp(args, "u16"))
+		align = 2;
+	else if (!bstrcmp(args, "u32"))
+		align = 4;
+	else
+		return PARSE_ERR(args, "u32: invalid alignment");
+
+	a = bstr_next(args);
+	if (a == NULL)
+		return PARSE_ERR(a, "u32: missing key");
+
+	key = bstrtoul(a);
+	if (key == ULONG_MAX)
+		return PARSE_ERR(a, "u32: invalid key, must be numeric");
+
+	a = bstr_next(a);
+	if (a == NULL)
+		return PARSE_ERR(a, "u32: missing mask");
+
+	mask = bstrtoul(a);
+	if (mask == ULONG_MAX)
+		return PARSE_ERR(a, "u32: invalid mask, must be numeric");
+
+	a = bstr_next(a);
+	if (a == NULL || bstrcmp(a, "at") != 0)
+		return PARSE_ERR(a, "u32: missing \"at\"");
+
+	a = bstr_next(a);
+	if (a == NULL)
+		return PARSE_ERR(a, "u32: missing offset");
+
+	nh_len = strlen("nexthdr+");
+	if (a->len > nh_len && !memcmp(a->data, "nexthdr+", nh_len)) {
+		char buf[a->len - nh_len + 1];
+		offmask = -1;
+		memcpy(buf, a->data + nh_len, a->len - nh_len);
+		offset = strtoul(buf, NULL, 0);
+	} else if (!bstrcmp(a, "nexthdr+")) {
+		a = bstr_next(a);
+		if (a == NULL)
+			return PARSE_ERR(a, "u32: missing offset");
+		offset = bstrtoul(a);
+	} else
+		offset = bstrtoul(a);
+
+	if (offset == ULONG_MAX)
+		return PARSE_ERR(a, "u32: invalid offset");
+
+	if (a->next)
+		return PARSE_ERR(a->next, "u32: unexpected trailer");
+
+	switch (align) {
+		case 1:
+			if (key > 0xFF)
+				return PARSE_ERR(a, "Illegal key (>0xFF)");
+			if (mask > 0xFF)
+				return PARSE_ERR(a, "Illegal mask (>0xFF)");
+
+			key <<= 24 - ((offset & 3) * 8);
+			mask <<= 24 - ((offset & 3) * 8);
+			offset &= ~3;
+			break;
+
+		case 2:
+			if (key > 0xFFFF)
+				return PARSE_ERR(a, "Illegal key (>0xFFFF)");
+			if (mask > 0xFFFF)
+				return PARSE_ERR(a, "Illegal mask (>0xFFFF)");
+
+			if ((offset & 3) == 0) {
+				key <<= 16;
+				mask <<= 16;
+			}
+			offset &= ~3;
+			break;
+	}
+
+	key = htonl(key);
+	mask = htonl(mask);
+
+	if (offset % 4)
+		return PARSE_ERR(a, "u32: invalid offset alignment, " \
+		    "must be aligned to 4.");
+
+	key &= mask;
+
+	u_key.mask = mask;
+	u_key.val = key;
+	u_key.off = offset;
+	u_key.offmask = offmask;
+
+	addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+	addraw_l(n, MAX_MSG, &u_key, sizeof(u_key));
+
+#undef PARSE_ERR
+	return 0;
+}
+
+static int u32_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+			  int data_len)
+{
+	struct tc_u32_key *u_key = data;
+
+	if (data_len < sizeof(*u_key)) {
+		fprintf(stderr, "U32 header size mismatch\n");
+		return -1;
+	}
+
+	fprintf(fd, "%08x/%08x at %s%d",
+	    (unsigned int) ntohl(u_key->val),
+	    (unsigned int) ntohl(u_key->mask),
+	    u_key->offmask ? "nexthdr+" : "",
+	    u_key->off);
+
+	return 0;
+}
+
+struct ematch_util u32_ematch_util = {
+	.kind = "u32",
+	.kind_num = TCF_EM_U32,
+	.parse_eopt = u32_parse_eopt,
+	.print_eopt = u32_print_eopt,
+	.print_usage = u32_print_usage
+};
diff --git a/tc/emp_ematch.l b/tc/emp_ematch.l
new file mode 100644
index 0000000..0184753
--- /dev/null
+++ b/tc/emp_ematch.l
@@ -0,0 +1,145 @@
+%{
+ #include "emp_ematch.yacc.h"
+ #include "m_ematch.h"
+
+ extern int ematch_argc;
+ extern char **ematch_argv;
+
+ #define yylval ematch_lval
+
+ #define NEXT_EM_ARG() do { ematch_argc--; ematch_argv++; } while(0);
+
+ #define YY_INPUT(buf, result, max_size)				\
+ {									\
+ next:									\
+ 	if (ematch_argc <= 0)						\
+		result = YY_NULL;					\
+	else if (**ematch_argv == '\0') {				\
+		NEXT_EM_ARG();						\
+		goto next;						\
+	} else {							\
+		if (max_size <= strlen(*ematch_argv) + 1) {		\
+			fprintf(stderr, "match argument too long.\n");	\
+			result = YY_NULL;				\
+		} else {						\
+			strcpy(buf, *ematch_argv);			\
+			result = strlen(*ematch_argv) + 1;		\
+			buf[result-1] = ' ';				\
+			buf[result] = '\0';				\
+			NEXT_EM_ARG();					\
+		}							\
+	}								\
+ }
+
+ static void __attribute__ ((unused)) yyunput (int c,char *buf_ptr  );
+ static void __attribute__ ((unused)) yy_push_state (int  new_state );
+ static void __attribute__ ((unused)) yy_pop_state  (void);
+ static int  __attribute__ ((unused)) yy_top_state (void );
+
+ static char *strbuf;
+ static unsigned int strbuf_size;
+ static unsigned int strbuf_index;
+
+ static void strbuf_enlarge(void)
+ {
+ 	strbuf_size += 512;
+ 	strbuf = realloc(strbuf, strbuf_size);
+ }
+
+ static void strbuf_append_char(char c)
+ {
+ 	while (strbuf_index >= strbuf_size)
+ 		strbuf_enlarge();
+ 	strbuf[strbuf_index++] = c;
+ }
+
+ static void strbuf_append_charp(char *s)
+ {
+ 	while (strbuf_index >= strbuf_size)
+ 		strbuf_enlarge();
+ 	memcpy(strbuf + strbuf_index, s, strlen(s));
+ 	strbuf_index += strlen(s);
+ }
+
+%}
+
+%x str
+
+%option 8bit stack warn noyywrap prefix="ematch_"
+%%
+[ \t\r\n]+
+
+\"					{
+						if (strbuf == NULL) {
+							strbuf_size = 512;
+							strbuf = calloc(1, strbuf_size);
+							if (strbuf == NULL)
+								return ERROR;
+						}
+						strbuf_index = 0;
+
+						BEGIN(str);
+					}
+
+<str>\"					{
+						BEGIN(INITIAL);
+						yylval.b = bstr_new(strbuf, strbuf_index);
+						yylval.b->quoted = 1;
+						return ATTRIBUTE;
+					}
+
+<str>\\[0-7]{1,3}			{ /* octal escape sequence */
+						int res;
+
+						sscanf(yytext + 1, "%o", &res);
+						if (res > 0xFF) {
+							fprintf(stderr, "error: octal escape sequence" \
+							" out of range\n");
+							return ERROR;
+						}
+						strbuf_append_char((unsigned char) res);
+					}
+
+<str>\\[0-9]+				{ /* catch wrong octal escape seq. */
+						fprintf(stderr, "error: invalid octale escape sequence\n");
+						return ERROR;
+					}
+
+<str>\\x[0-9a-fA-F]{1,2}		{
+						int res;
+
+						sscanf(yytext + 2, "%x", &res);
+
+						if (res > 0xFF) {
+							fprintf(stderr, "error: hexadecimal escape " \
+							"sequence out of range\n");
+							return ERROR;
+						}
+						strbuf_append_char((unsigned char) res);
+					}
+
+<str>\\n				strbuf_append_char('\n');
+<str>\\r				strbuf_append_char('\r');
+<str>\\t				strbuf_append_char('\t');
+<str>\\v				strbuf_append_char('\v');
+<str>\\b				strbuf_append_char('\b');
+<str>\\f				strbuf_append_char('\f');
+<str>\\a				strbuf_append_char('\a');
+
+<str>\\(.|\n)				strbuf_append_char(yytext[1]);
+<str>[^\\\n\"]+				strbuf_append_charp(yytext);
+
+[aA][nN][dD]				return AND;
+[oO][rR]				return OR;
+[nN][oO][tT]				return NOT;
+"("					|
+")"					{
+						return yylval.i = *yytext;
+					}
+[^ \t\r\n()]+				{
+						yylval.b = bstr_alloc(yytext);
+						if (yylval.b == NULL)
+							return ERROR;
+						return ATTRIBUTE;
+					}
+%%
diff --git a/tc/emp_ematch.y b/tc/emp_ematch.y
new file mode 100644
index 0000000..e8d1671
--- /dev/null
+++ b/tc/emp_ematch.y
@@ -0,0 +1,101 @@
+%{
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <malloc.h>
+ #include <string.h>
+ #include "m_ematch.h"
+%}
+
+%locations
+%token-table
+%error-verbose
+%name-prefix="ematch_"
+
+%union {
+	unsigned int i;
+	struct bstr *b;
+	struct ematch *e;
+}
+
+%{
+ extern int ematch_lex(void);
+ extern void yyerror(char *s);
+ extern struct ematch *ematch_root;
+ extern char *ematch_err;
+%}
+
+%token <i> ERROR
+%token <b> ATTRIBUTE
+%token <i> AND OR NOT
+%type <i> invert relation
+%type <e> match expr
+%type <b> args
+%right AND OR
+%start input
+%%
+input:
+	/* empty */
+	| expr
+		{ ematch_root = $1; }
+	| expr error
+		{
+			ematch_root = $1;
+			YYACCEPT;
+		}
+	;
+
+expr:
+	match
+		{ $$ = $1; }
+	| match relation expr
+		{
+			$1->relation = $2;
+			$1->next = $3;
+			$$ = $1;
+		}
+	;
+
+match:
+	invert ATTRIBUTE '(' args ')'
+		{
+			$2->next = $4;
+			$$ = new_ematch($2, $1);
+			if ($$ == NULL)
+				YYABORT;
+		}
+	| invert '(' expr ')'
+		{
+			$$ = new_ematch(NULL, $1);
+			if ($$ == NULL)
+				YYABORT;
+			$$->child = $3;
+		}
+	;
+
+args:
+	ATTRIBUTE
+		{ $$ = $1; }
+	| ATTRIBUTE args
+		{ $1->next = $2; }
+	;
+
+relation:
+	AND
+		{ $$ = TCF_EM_REL_AND; }
+	| OR
+		{ $$ = TCF_EM_REL_OR; }
+	;
+
+invert:
+	/* empty */
+		{ $$ = 0; }
+	| NOT
+		{ $$ = 1; }
+	;
+%%
+
+ void yyerror(char *s)
+ {
+	 ematch_err = strdup(s);
+ }
+
diff --git a/tc/f_basic.c b/tc/f_basic.c
new file mode 100644
index 0000000..ad41633
--- /dev/null
+++ b/tc/f_basic.c
@@ -0,0 +1,147 @@
+/*
+ * f_basic.c		Basic 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:	Thomas Graf <tgraf@suug.ch>
+ *
+ */
+
+#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 <linux/if.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "m_ematch.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... basic [ match EMATCH_TREE ] [ police POLICE_SPEC ]\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, "\nNOTE: CLASSID is parsed as hexadecimal input.\n");
+}
+
+static int basic_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, "match") == 0) {
+			NEXT_ARG();
+			if (parse_ematch(&argc, &argv, TCA_BASIC_EMATCHES, n)) {
+				fprintf(stderr, "Illegal \"ematch\"\n");
+				return -1;
+			}
+			continue;
+		} 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_BASIC_CLASSID, &handle, 4);
+		} else if (matches(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (parse_action(&argc, &argv, TCA_BASIC_ACT, n)) {
+				fprintf(stderr, "Illegal \"action\"\n");
+				return -1;
+			}
+			continue;
+
+		} else if (matches(*argv, "police") == 0) {
+			NEXT_ARG();
+			if (parse_police(&argc, &argv, TCA_BASIC_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 basic_print_opt(struct filter_util *qu, FILE *f,
+			   struct rtattr *opt, __u32 handle)
+{
+	struct rtattr *tb[TCA_BASIC_MAX+1];
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_BASIC_MAX, opt);
+
+	if (handle)
+		fprintf(f, "handle 0x%x ", handle);
+
+	if (tb[TCA_BASIC_CLASSID]) {
+		SPRINT_BUF(b1);
+		fprintf(f, "flowid %s ",
+			sprint_tc_classid(*(__u32*)RTA_DATA(tb[TCA_BASIC_CLASSID]), b1));
+	}
+
+	if (tb[TCA_BASIC_EMATCHES])
+		print_ematch(f, tb[TCA_BASIC_EMATCHES]);
+
+	if (tb[TCA_BASIC_POLICE]) {
+		fprintf(f, "\n");
+		tc_print_police(f, tb[TCA_BASIC_POLICE]);
+	}
+
+	if (tb[TCA_BASIC_ACT]) {
+		tc_print_action(f, tb[TCA_BASIC_ACT]);
+	}
+
+	return 0;
+}
+
+struct filter_util basic_filter_util = {
+	.id = "basic",
+	.parse_fopt = basic_parse_opt,
+	.print_fopt = basic_print_opt,
+};
diff --git a/tc/f_cgroup.c b/tc/f_cgroup.c
new file mode 100644
index 0000000..192e697
--- /dev/null
+++ b/tc/f_cgroup.c
@@ -0,0 +1,115 @@
+/*
+ * f_cgroup.c		Control Group 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:	Thomas Graf <tgraf@infradead.org>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "utils.h"
+#include "tc_util.h"
+#include "m_ematch.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... cgroup [ match EMATCH_TREE ] [ police POLICE_SPEC ]\n");
+	fprintf(stderr, "                 [ action ACTION_SPEC ]\n");
+}
+
+static int cgroup_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 (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, "match") == 0) {
+			NEXT_ARG();
+			if (parse_ematch(&argc, &argv, TCA_CGROUP_EMATCHES, n)) {
+				fprintf(stderr, "Illegal \"ematch\"\n");
+				return -1;
+			}
+			continue;
+		} else if (matches(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (parse_action(&argc, &argv, TCA_CGROUP_ACT, n)) {
+				fprintf(stderr, "Illegal \"action\"\n");
+				return -1;
+			}
+			continue;
+
+		} else if (matches(*argv, "police") == 0) {
+			NEXT_ARG();
+			if (parse_police(&argc, &argv, TCA_CGROUP_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 cgroup_print_opt(struct filter_util *qu, FILE *f,
+			   struct rtattr *opt, __u32 handle)
+{
+	struct rtattr *tb[TCA_CGROUP_MAX+1];
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_CGROUP_MAX, opt);
+
+	if (handle)
+		fprintf(f, "handle 0x%x ", handle);
+
+	if (tb[TCA_CGROUP_EMATCHES])
+		print_ematch(f, tb[TCA_CGROUP_EMATCHES]);
+
+	if (tb[TCA_CGROUP_POLICE]) {
+		fprintf(f, "\n");
+		tc_print_police(f, tb[TCA_CGROUP_POLICE]);
+	}
+
+	if (tb[TCA_CGROUP_ACT])
+		tc_print_action(f, tb[TCA_CGROUP_ACT]);
+
+	return 0;
+}
+
+struct filter_util cgroup_filter_util = {
+	.id = "cgroup",
+	.parse_fopt = cgroup_parse_opt,
+	.print_fopt = cgroup_print_opt,
+};
diff --git a/tc/f_flow.c b/tc/f_flow.c
new file mode 100644
index 0000000..84b45c9
--- /dev/null
+++ b/tc/f_flow.c
@@ -0,0 +1,360 @@
+/*
+ * 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.
+ *
+ * Authors:	Patrick McHardy <kaber@trash.net>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "m_ematch.h"
+
+static void explain(void)
+{
+	fprintf(stderr,
+"Usage: ... flow ...\n"
+"\n"
+" [mapping mode]: map key KEY [ OPS ] ...\n"
+" [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"
+"\n"
+"KEY-LIST := [ KEY-LIST , ] KEY\n"
+"KEY      := [ src | dst | proto | proto-src | proto-dst | iif | priority | \n"
+"              mark | nfct | nfct-src | nfct-dst | nfct-proto-src | \n"
+"              nfct-proto-dst | rt-classid | sk-uid | sk-gid |\n"
+"              vlan-tag ]\n"
+"OPS      := [ or NUM | and NUM | xor NUM | rshift NUM | addend NUM ]\n"
+"ID       := X:Y\n"
+	);
+}
+
+static const char *flow_keys[FLOW_KEY_MAX+1] = {
+	[FLOW_KEY_SRC]			= "src",
+	[FLOW_KEY_DST]			= "dst",
+	[FLOW_KEY_PROTO]		= "proto",
+	[FLOW_KEY_PROTO_SRC]		= "proto-src",
+	[FLOW_KEY_PROTO_DST]		= "proto-dst",
+	[FLOW_KEY_IIF]			= "iif",
+	[FLOW_KEY_PRIORITY]		= "priority",
+	[FLOW_KEY_MARK]			= "mark",
+	[FLOW_KEY_NFCT]			= "nfct",
+	[FLOW_KEY_NFCT_SRC]		= "nfct-src",
+	[FLOW_KEY_NFCT_DST]		= "nfct-dst",
+	[FLOW_KEY_NFCT_PROTO_SRC]	= "nfct-proto-src",
+	[FLOW_KEY_NFCT_PROTO_DST]	= "nfct-proto-dst",
+	[FLOW_KEY_RTCLASSID]		= "rt-classid",
+	[FLOW_KEY_SKUID]		= "sk-uid",
+	[FLOW_KEY_SKGID]		= "sk-gid",
+	[FLOW_KEY_VLAN_TAG]		= "vlan-tag",
+};
+
+static int flow_parse_keys(__u32 *keys, __u32 *nkeys, char *argv)
+{
+	char *s, *sep;
+	unsigned int i;
+
+	*keys = 0;
+	*nkeys = 0;
+	s = argv;
+	while (s != NULL) {
+		sep = strchr(s, ',');
+		if (sep)
+			*sep = '\0';
+
+		for (i = 0; i <= FLOW_KEY_MAX; i++) {
+			if (matches(s, flow_keys[i]) == 0) {
+				*keys |= 1 << i;
+				(*nkeys)++;
+				break;
+			}
+		}
+		if (i > FLOW_KEY_MAX) {
+			fprintf(stderr, "Unknown flow key \"%s\"\n", s);
+			return -1;
+		}
+		s = sep ? sep + 1 : NULL;
+	}
+	return 0;
+}
+
+static void transfer_bitop(__u32 *mask, __u32 *xor, __u32 m, __u32 x)
+{
+	*xor = x ^ (*xor & m);
+	*mask &= m;
+}
+
+static int get_addend(__u32 *addend, char *argv, __u32 keys)
+{
+	inet_prefix addr;
+	int sign = 0;
+	__u32 tmp;
+
+	if (*argv == '-') {
+		sign = 1;
+		argv++;
+	}
+
+	if (get_u32(&tmp, argv, 0) == 0)
+		goto out;
+
+	if (keys & (FLOW_KEY_SRC | FLOW_KEY_DST |
+		    FLOW_KEY_NFCT_SRC | FLOW_KEY_NFCT_DST) &&
+	    get_addr(&addr, argv, AF_UNSPEC) == 0) {
+		switch (addr.family) {
+		case AF_INET:
+			tmp = ntohl(addr.data[0]);
+			goto out;
+		case AF_INET6:
+			tmp = ntohl(addr.data[3]);
+			goto out;
+		}
+	}
+
+	return -1;
+out:
+	if (sign)
+		tmp = -tmp;
+	*addend = tmp;
+	return 0;
+}
+
+static int flow_parse_opt(struct filter_util *fu, char *handle,
+			  int argc, char **argv, struct nlmsghdr *n)
+{
+	struct tc_police tp;
+	struct tcmsg *t = NLMSG_DATA(n);
+	struct rtattr *tail;
+	__u32 mask = ~0U, xor = 0;
+	__u32 keys = 0, nkeys = 0;
+	__u32 mode = FLOW_MODE_MAP;
+	__u32 tmp;
+
+	memset(&tp, 0, sizeof(tp));
+
+	if (handle) {
+		if (get_u32(&t->tcm_handle, handle, 0)) {
+			fprintf(stderr, "Illegal \"handle\"\n");
+			return -1;
+		}
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 4096, TCA_OPTIONS, NULL, 0);
+
+	while (argc > 0) {
+		if (matches(*argv, "map") == 0) {
+			mode = FLOW_MODE_MAP;
+		} else if (matches(*argv, "hash") == 0) {
+			mode = FLOW_MODE_HASH;
+		} else if (matches(*argv, "keys") == 0) {
+			NEXT_ARG();
+			if (flow_parse_keys(&keys, &nkeys, *argv))
+				return -1;
+			addattr32(n, 4096, TCA_FLOW_KEYS, keys);
+		} else if (matches(*argv, "and") == 0) {
+			NEXT_ARG();
+			if (get_u32(&tmp, *argv, 0)) {
+				fprintf(stderr, "Illegal \"mask\"\n");
+				return -1;
+			}
+			transfer_bitop(&mask, &xor, tmp, 0);
+		} else if (matches(*argv, "or") == 0) {
+			NEXT_ARG();
+			if (get_u32(&tmp, *argv, 0)) {
+				fprintf(stderr, "Illegal \"or\"\n");
+				return -1;
+			}
+			transfer_bitop(&mask, &xor, ~tmp, tmp);
+		} else if (matches(*argv, "xor") == 0) {
+			NEXT_ARG();
+			if (get_u32(&tmp, *argv, 0)) {
+				fprintf(stderr, "Illegal \"xor\"\n");
+				return -1;
+			}
+			transfer_bitop(&mask, &xor, ~0, tmp);
+		} else if (matches(*argv, "rshift") == 0) {
+			NEXT_ARG();
+			if (get_u32(&tmp, *argv, 0)) {
+				fprintf(stderr, "Illegal \"rshift\"\n");
+				return -1;
+			}
+			addattr32(n, 4096, TCA_FLOW_RSHIFT, tmp);
+		} else if (matches(*argv, "addend") == 0) {
+			NEXT_ARG();
+			if (get_addend(&tmp, *argv, keys)) {
+				fprintf(stderr, "Illegal \"addend\"\n");
+				return -1;
+			}
+			addattr32(n, 4096, TCA_FLOW_ADDEND, tmp);
+		} else if (matches(*argv, "divisor") == 0) {
+			NEXT_ARG();
+			if (get_u32(&tmp, *argv, 0)) {
+				fprintf(stderr, "Illegal \"divisor\"\n");
+				return -1;
+			}
+			addattr32(n, 4096, TCA_FLOW_DIVISOR, tmp);
+		} else if (matches(*argv, "baseclass") == 0) {
+			NEXT_ARG();
+			if (get_tc_classid(&tmp, *argv) || TC_H_MIN(tmp) == 0) {
+				fprintf(stderr, "Illegal \"baseclass\"\n");
+				return -1;
+			}
+			addattr32(n, 4096, TCA_FLOW_BASECLASS, tmp);
+		} else if (matches(*argv, "perturb") == 0) {
+			NEXT_ARG();
+			if (get_u32(&tmp, *argv, 0)) {
+				fprintf(stderr, "Illegal \"perturb\"\n");
+				return -1;
+			}
+			addattr32(n, 4096, TCA_FLOW_PERTURB, tmp);
+		} else if (matches(*argv, "police") == 0) {
+			NEXT_ARG();
+			if (parse_police(&argc, &argv, TCA_FLOW_POLICE, n)) {
+				fprintf(stderr, "Illegal \"police\"\n");
+				return -1;
+			}
+			continue;
+		} else if (matches(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (parse_action(&argc, &argv, TCA_FLOW_ACT, n)) {
+				fprintf(stderr, "Illegal \"action\"\n");
+				return -1;
+			}
+			continue;
+		} else if (matches(*argv, "match") == 0) {
+			NEXT_ARG();
+			if (parse_ematch(&argc, &argv, TCA_FLOW_EMATCHES, n)) {
+				fprintf(stderr, "Illegal \"ematch\"\n");
+				return -1;
+			}
+			continue;
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argv++, argc--;
+	}
+
+	if (nkeys > 1 && mode != FLOW_MODE_HASH) {
+		fprintf(stderr, "Invalid mode \"map\" for multiple keys\n");
+		return -1;
+	}
+	addattr32(n, 4096, TCA_FLOW_MODE, mode);
+
+	if (mask != ~0 || xor != 0) {
+		addattr32(n, 4096, TCA_FLOW_MASK, mask);
+		addattr32(n, 4096, TCA_FLOW_XOR, xor);
+	}
+
+	tail->rta_len = (void *)NLMSG_TAIL(n) - (void *)tail;
+	return 0;
+}
+
+static int flow_print_opt(struct filter_util *fu, FILE *f, struct rtattr *opt,
+			  __u32 handle)
+{
+	struct rtattr *tb[TCA_FLOW_MAX+1];
+	SPRINT_BUF(b1);
+	unsigned int i;
+	__u32 mask = ~0, val = 0;
+
+	if (opt == NULL)
+		return -EINVAL;
+
+	parse_rtattr_nested(tb, TCA_FLOW_MAX, opt);
+
+	fprintf(f, "handle 0x%x ", handle);
+
+	if (tb[TCA_FLOW_MODE]) {
+		__u32 mode = *(__u32 *)RTA_DATA(tb[TCA_FLOW_MODE]);
+
+		switch (mode) {
+		case FLOW_MODE_MAP:
+			fprintf(f, "map ");
+			break;
+		case FLOW_MODE_HASH:
+			fprintf(f, "hash ");
+			break;
+		}
+	}
+
+	if (tb[TCA_FLOW_KEYS]) {
+		__u32 keymask = *(__u32 *)RTA_DATA(tb[TCA_FLOW_KEYS]);
+		char *sep = "";
+
+		fprintf(f, "keys ");
+		for (i = 0; i <= FLOW_KEY_MAX; i++) {
+			if (keymask & (1 << i)) {
+				fprintf(f, "%s%s", sep, flow_keys[i]);
+				sep = ",";
+			}
+		}
+		fprintf(f, " ");
+	}
+
+	if (tb[TCA_FLOW_MASK])
+		mask = *(__u32 *)RTA_DATA(tb[TCA_FLOW_MASK]);
+	if (tb[TCA_FLOW_XOR])
+		val = *(__u32 *)RTA_DATA(tb[TCA_FLOW_XOR]);
+
+	if (mask != ~0 || val != 0) {
+		__u32 or = (mask & val) ^ val;
+		__u32 xor = mask & val;
+
+		if (mask != ~0)
+			fprintf(f, "and 0x%.8x ", mask);
+		if (xor != 0)
+			fprintf(f, "xor 0x%.8x ", xor);
+		if (or != 0)
+			fprintf(f, "or 0x%.8x ", or);
+	}
+
+	if (tb[TCA_FLOW_RSHIFT])
+		fprintf(f, "rshift %u ",
+			*(__u32 *)RTA_DATA(tb[TCA_FLOW_RSHIFT]));
+	if (tb[TCA_FLOW_ADDEND])
+		fprintf(f, "addend 0x%x ",
+			*(__u32 *)RTA_DATA(tb[TCA_FLOW_ADDEND]));
+
+	if (tb[TCA_FLOW_DIVISOR])
+		fprintf(f, "divisor %u ",
+			*(__u32 *)RTA_DATA(tb[TCA_FLOW_DIVISOR]));
+	if (tb[TCA_FLOW_BASECLASS])
+		fprintf(f, "baseclass %s ",
+			sprint_tc_classid(*(__u32 *)RTA_DATA(tb[TCA_FLOW_BASECLASS]), b1));
+
+	if (tb[TCA_FLOW_PERTURB])
+		fprintf(f, "perturb %usec ",
+			*(__u32 *)RTA_DATA(tb[TCA_FLOW_PERTURB]));
+
+	if (tb[TCA_FLOW_EMATCHES])
+		print_ematch(f, tb[TCA_FLOW_EMATCHES]);
+	if (tb[TCA_FLOW_POLICE])
+		tc_print_police(f, tb[TCA_FLOW_POLICE]);
+	if (tb[TCA_FLOW_ACT]) {
+		fprintf(f, "\n");
+		tc_print_action(f, tb[TCA_FLOW_ACT]);
+	}
+	return 0;
+}
+
+struct filter_util flow_filter_util = {
+	.id		= "flow",
+	.parse_fopt	= flow_parse_opt,
+	.print_fopt	= flow_print_opt,
+};
diff --git a/tc/f_fw.c b/tc/f_fw.c
new file mode 100644
index 0000000..cc8ea2d
--- /dev/null
+++ b/tc/f_fw.c
@@ -0,0 +1,164 @@
+/*
+ * f_fw.c		FW 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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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 <linux/if.h> /* IFNAMSIZ */
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... fw [ classid CLASSID ] [ police POLICE_SPEC ]\n");
+	fprintf(stderr, "       POLICE_SPEC := ... look at TBF\n");
+	fprintf(stderr, "       CLASSID := X:Y\n");
+	fprintf(stderr, "\nNOTE: CLASSID is parsed as hexadecimal input.\n");
+}
+
+#define usage() return(-1)
+
+static int fw_parse_opt(struct filter_util *qu, char *handle, int argc, char **argv, struct nlmsghdr *n)
+{
+	struct tc_police tp;
+	struct tcmsg *t = NLMSG_DATA(n);
+	struct rtattr *tail;
+	__u32 mask = 0;
+	int mask_set = 0;
+
+	memset(&tp, 0, sizeof(tp));
+
+	if (handle) {
+		char *slash;
+		if ((slash = strchr(handle, '/')) != NULL)
+			*slash = '\0';
+		if (get_u32(&t->tcm_handle, handle, 0)) {
+			fprintf(stderr, "Illegal \"handle\"\n");
+			return -1;
+		}
+		if (slash) {
+			if (get_u32(&mask, slash+1, 0)) {
+				fprintf(stderr, "Illegal \"handle\" mask\n");
+				return -1;
+			}
+			mask_set = 1;
+		}
+	}
+
+	if (argc == 0)
+		return 0;
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 4096, TCA_OPTIONS, NULL, 0);
+
+	if (mask_set)
+		addattr32(n, MAX_MSG, TCA_FW_MASK, mask);
+
+	while (argc > 0) {
+		if (matches(*argv, "classid") == 0 ||
+		    matches(*argv, "flowid") == 0) {
+			unsigned handle;
+			NEXT_ARG();
+			if (get_tc_classid(&handle, *argv)) {
+				fprintf(stderr, "Illegal \"classid\"\n");
+				return -1;
+			}
+			addattr_l(n, 4096, TCA_FW_CLASSID, &handle, 4);
+		} else if (matches(*argv, "police") == 0) {
+			NEXT_ARG();
+			if (parse_police(&argc, &argv, TCA_FW_POLICE, n)) {
+				fprintf(stderr, "Illegal \"police\"\n");
+				return -1;
+			}
+			continue;
+		} else if (matches(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (parse_action(&argc, &argv, TCA_FW_ACT, n)) {
+				fprintf(stderr, "Illegal fw \"action\"\n");
+				return -1;
+			}
+			continue;
+		} else if (strcmp(*argv, "indev") == 0) {
+			char d[IFNAMSIZ+1];
+			memset(d, 0, sizeof (d));
+			argc--;
+			argv++;
+			if (argc < 1) {
+				fprintf(stderr, "Illegal indev\n");
+				return -1;
+			}
+			strncpy(d, *argv, sizeof (d) - 1);
+			addattr_l(n, MAX_MSG, TCA_FW_INDEV, d, strlen(d) + 1);
+		} 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 *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int fw_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 handle)
+{
+	struct rtattr *tb[TCA_FW_MAX+1];
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_FW_MAX, opt);
+
+	if (handle || tb[TCA_FW_MASK]) {
+		__u32 mark = 0, mask = 0;
+		if(handle)
+			mark = handle;
+		if(tb[TCA_FW_MASK] &&
+		    (mask = *(__u32*)RTA_DATA(tb[TCA_FW_MASK])) != 0xFFFFFFFF)
+			fprintf(f, "handle 0x%x/0x%x ", mark, mask);
+		else
+			fprintf(f, "handle 0x%x ", handle);
+	}
+
+	if (tb[TCA_FW_CLASSID]) {
+		SPRINT_BUF(b1);
+		fprintf(f, "classid %s ", sprint_tc_classid(*(__u32*)RTA_DATA(tb[TCA_FW_CLASSID]), b1));
+	}
+
+	if (tb[TCA_FW_POLICE])
+		tc_print_police(f, tb[TCA_FW_POLICE]);
+	if (tb[TCA_FW_INDEV]) {
+		struct rtattr *idev = tb[TCA_FW_INDEV];
+		fprintf(f, "input dev %s ",(char *)RTA_DATA(idev));
+	}
+
+	if (tb[TCA_FW_ACT]) {
+		fprintf(f, "\n");
+		tc_print_action(f, tb[TCA_FW_ACT]);
+	}
+	return 0;
+}
+
+struct filter_util fw_filter_util = {
+	.id = "fw",
+	.parse_fopt = fw_parse_opt,
+	.print_fopt = fw_print_opt,
+};
diff --git a/tc/f_route.c b/tc/f_route.c
new file mode 100644
index 0000000..67dd49c
--- /dev/null
+++ b/tc/f_route.c
@@ -0,0 +1,170 @@
+/*
+ * f_route.c		ROUTE 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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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 "rt_names.h"
+#include "tc_common.h"
+#include "tc_util.h"
+
+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, "       CLASSID := X:Y\n");
+	fprintf(stderr, "\nNOTE: CLASSID is parsed as hexadecimal input.\n");
+}
+
+#define usage() return(-1)
+
+static int route_parse_opt(struct filter_util *qu, char *handle, int argc, char **argv, struct nlmsghdr *n)
+{
+	struct tc_police tp;
+	struct tcmsg *t = NLMSG_DATA(n);
+	struct rtattr *tail;
+	__u32 fh = 0xFFFF8000;
+	__u32 order = 0;
+
+	memset(&tp, 0, sizeof(tp));
+
+	if (handle) {
+		if (get_u32(&t->tcm_handle, handle, 0)) {
+			fprintf(stderr, "Illegal \"handle\"\n");
+			return -1;
+		}
+	}
+
+	if (argc == 0)
+		return 0;
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 4096, TCA_OPTIONS, NULL, 0);
+
+	while (argc > 0) {
+		if (matches(*argv, "to") == 0) {
+			__u32 id;
+			NEXT_ARG();
+			if (rtnl_rtrealm_a2n(&id, *argv)) {
+				fprintf(stderr, "Illegal \"to\"\n");
+				return -1;
+			}
+			addattr_l(n, 4096, TCA_ROUTE4_TO, &id, 4);
+			fh &= ~0x80FF;
+			fh |= id&0xFF;
+		} else if (matches(*argv, "from") == 0) {
+			__u32 id;
+			NEXT_ARG();
+			if (rtnl_rtrealm_a2n(&id, *argv)) {
+				fprintf(stderr, "Illegal \"from\"\n");
+				return -1;
+			}
+			addattr_l(n, 4096, TCA_ROUTE4_FROM, &id, 4);
+			fh &= 0xFFFF;
+			fh |= id<<16;
+		} else if (matches(*argv, "fromif") == 0) {
+			__u32 id;
+			NEXT_ARG();
+			ll_init_map(&rth);
+			if ((id=ll_name_to_index(*argv)) <= 0) {
+				fprintf(stderr, "Illegal \"fromif\"\n");
+				return -1;
+			}
+			addattr_l(n, 4096, TCA_ROUTE4_IIF, &id, 4);
+			fh &= 0xFFFF;
+			fh |= (0x8000|id)<<16;
+		} 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, 4096, TCA_ROUTE4_CLASSID, &handle, 4);
+		} else if (matches(*argv, "police") == 0) {
+			NEXT_ARG();
+			if (parse_police(&argc, &argv, TCA_ROUTE4_POLICE, n)) {
+				fprintf(stderr, "Illegal \"police\"\n");
+				return -1;
+			}
+			continue;
+		} else if (matches(*argv, "order") == 0) {
+			NEXT_ARG();
+			if (get_u32(&order, *argv, 0)) {
+				fprintf(stderr, "Illegal \"order\"\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->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	if (order) {
+		fh &= ~0x7F00;
+		fh |= (order<<8)&0x7F00;
+	}
+	if (!t->tcm_handle)
+		t->tcm_handle = fh;
+	return 0;
+}
+
+static int route_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 handle)
+{
+	struct rtattr *tb[TCA_ROUTE4_MAX+1];
+	SPRINT_BUF(b1);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_ROUTE4_MAX, opt);
+
+	if (handle)
+		fprintf(f, "fh 0x%08x ", handle);
+	if (handle&0x7F00)
+		fprintf(f, "order %d ", (handle>>8)&0x7F);
+
+	if (tb[TCA_ROUTE4_CLASSID]) {
+		SPRINT_BUF(b1);
+		fprintf(f, "flowid %s ", sprint_tc_classid(*(__u32*)RTA_DATA(tb[TCA_ROUTE4_CLASSID]), b1));
+	}
+	if (tb[TCA_ROUTE4_TO])
+		fprintf(f, "to %s ", rtnl_rtrealm_n2a(*(__u32*)RTA_DATA(tb[TCA_ROUTE4_TO]), b1, sizeof(b1)));
+	if (tb[TCA_ROUTE4_FROM])
+		fprintf(f, "from %s ", rtnl_rtrealm_n2a(*(__u32*)RTA_DATA(tb[TCA_ROUTE4_FROM]), b1, sizeof(b1)));
+	if (tb[TCA_ROUTE4_IIF])
+		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]);
+	return 0;
+}
+
+struct filter_util route_filter_util = {
+	.id = "route",
+	.parse_fopt = route_parse_opt,
+	.print_fopt = route_print_opt,
+};
diff --git a/tc/f_rsvp.c b/tc/f_rsvp.c
new file mode 100644
index 0000000..7e1e6d9
--- /dev/null
+++ b/tc/f_rsvp.c
@@ -0,0 +1,405 @@
+/*
+ * q_rsvp.c		RSVP 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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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 "rt_names.h"
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+	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, "                [ 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, "       FILTERID := X:Y\n");
+	fprintf(stderr, "\nNOTE: CLASSID is parsed as hexadecimal input.\n");
+}
+
+#define usage() return(-1)
+
+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;
+	char **argv = *argv_p;
+	char *p = strchr(*argv, '/');
+	struct tc_rsvp_gpi *pi = dir ? &pinfo->dpi : &pinfo->spi;
+
+	if (p) {
+		__u16 tmp;
+
+		if (get_u16(&tmp, p+1, 0))
+			return -1;
+
+		if (dir == 0) {
+			/* Source port: u16 at offset 0 */
+			pi->key = htonl(((__u32)tmp)<<16);
+			pi->mask = htonl(0xFFFF0000);
+		} else {
+			/* Destination port: u16 at offset 2 */
+			pi->key = htonl(((__u32)tmp));
+			pi->mask = htonl(0x0000FFFF);
+		}
+		pi->offset = 0;
+		*p = 0;
+	}
+	if (get_addr_1(addr, *argv, family))
+		return -1;
+	if (p)
+		*p = '/';
+
+	argc--; argv++;
+
+	if (pi->mask || argc <= 0)
+		goto done;
+
+	if (strcmp(*argv, "spi/ah") == 0 ||
+	    strcmp(*argv, "gpi/ah") == 0) {
+		__u32 gpi;
+		NEXT_ARG();
+		if (get_u32(&gpi, *argv, 0))
+			return -1;
+		pi->mask = htonl(0xFFFFFFFF);
+		pi->key = htonl(gpi);
+		pi->offset = 4;
+		if (pinfo->protocol == 0)
+			pinfo->protocol = IPPROTO_AH;
+		argc--; argv++;
+	} else if (strcmp(*argv, "spi/esp") == 0 ||
+		   strcmp(*argv, "gpi/esp") == 0) {
+		__u32 gpi;
+		NEXT_ARG();
+		if (get_u32(&gpi, *argv, 0))
+			return -1;
+		pi->mask = htonl(0xFFFFFFFF);
+		pi->key = htonl(gpi);
+		pi->offset = 0;
+		if (pinfo->protocol == 0)
+			pinfo->protocol = IPPROTO_ESP;
+		argc--; argv++;
+	} else if (strcmp(*argv, "flowlabel") == 0) {
+		__u32 flabel;
+		NEXT_ARG();
+		if (get_u32(&flabel, *argv, 0))
+			return -1;
+		if (family != AF_INET6)
+			return -1;
+		pi->mask = htonl(0x000FFFFF);
+		pi->key = htonl(flabel) & pi->mask;
+		pi->offset = -40;
+		argc--; argv++;
+	} else if (strcmp(*argv, "u32") == 0 ||
+		   strcmp(*argv, "u16") == 0 ||
+		   strcmp(*argv, "u8") == 0) {
+		int sz = 1;
+		__u32 tmp;
+		__u32 mask = 0xff;
+		if (strcmp(*argv, "u32") == 0) {
+			sz = 4;
+			mask = 0xffff;
+		} else if (strcmp(*argv, "u16") == 0) {
+			mask = 0xffffffff;
+			sz = 2;
+		}
+		NEXT_ARG();
+		if (get_u32(&tmp, *argv, 0))
+			return -1;
+		argc--; argv++;
+		if (strcmp(*argv, "mask") == 0) {
+			NEXT_ARG();
+			if (get_u32(&mask, *argv, 16))
+				return -1;
+			argc--; argv++;
+		}
+		if (strcmp(*argv, "at") == 0) {
+			NEXT_ARG();
+			if (get_integer(&pi->offset, *argv, 0))
+				return -1;
+			argc--; argv++;
+		}
+		if (sz == 1) {
+			if ((pi->offset & 3) == 0) {
+				mask <<= 24;
+				tmp <<= 24;
+			} else if ((pi->offset & 3) == 1) {
+				mask <<= 16;
+				tmp <<= 16;
+			} else if ((pi->offset & 3) == 3) {
+				mask <<= 8;
+				tmp <<= 8;
+			}
+		} else if (sz == 2) {
+			if ((pi->offset & 3) == 0) {
+				mask <<= 16;
+				tmp <<= 16;
+			}
+		}
+		pi->offset &= ~3;
+		pi->mask = htonl(mask);
+		pi->key = htonl(tmp) & pi->mask;
+	}
+
+done:
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+
+static int rsvp_parse_opt(struct filter_util *qu, char *handle, int argc, char **argv, struct nlmsghdr *n)
+{
+	int family = strcmp(qu->id, "rsvp") == 0 ? AF_INET : AF_INET6;
+	struct tc_rsvp_pinfo pinfo;
+	struct tc_police tp;
+	struct tcmsg *t = NLMSG_DATA(n);
+	int pinfo_ok = 0;
+	struct rtattr *tail;
+
+	memset(&pinfo, 0, sizeof(pinfo));
+	memset(&tp, 0, sizeof(tp));
+
+	if (handle) {
+		if (get_u32(&t->tcm_handle, handle, 0)) {
+			fprintf(stderr, "Illegal \"handle\"\n");
+			return -1;
+		}
+	}
+
+	if (argc == 0)
+		return 0;
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 4096, TCA_OPTIONS, NULL, 0);
+
+	while (argc > 0) {
+		if (matches(*argv, "session") == 0) {
+			inet_prefix addr;
+			NEXT_ARG();
+			if (get_addr_and_pi(&argc, &argv, &addr, &pinfo, 1, family)) {
+				fprintf(stderr, "Illegal \"session\"\n");
+				return -1;
+			}
+			addattr_l(n, 4096, TCA_RSVP_DST, &addr.data, addr.bytelen);
+			if (pinfo.dpi.mask || pinfo.protocol)
+				pinfo_ok++;
+			continue;
+		} else if (matches(*argv, "sender") == 0 ||
+			   matches(*argv, "flowspec") == 0) {
+			inet_prefix addr;
+			NEXT_ARG();
+			if (get_addr_and_pi(&argc, &argv, &addr, &pinfo, 0, family)) {
+				fprintf(stderr, "Illegal \"sender\"\n");
+				return -1;
+			}
+			addattr_l(n, 4096, TCA_RSVP_SRC, &addr.data, addr.bytelen);
+			if (pinfo.spi.mask || pinfo.protocol)
+				pinfo_ok++;
+			continue;
+		} else if (matches("ipproto", *argv) == 0) {
+			int num;
+			NEXT_ARG();
+			num = inet_proto_a2n(*argv);
+			if (num < 0) {
+				fprintf(stderr, "Illegal \"ipproto\"\n");
+				return -1;
+			}
+			pinfo.protocol = num;
+			pinfo_ok++;
+		} 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, 4096, TCA_RSVP_CLASSID, &handle, 4);
+		} else if (strcmp(*argv, "tunnelid") == 0) {
+			unsigned tid;
+			NEXT_ARG();
+			if (get_unsigned(&tid, *argv, 0)) {
+				fprintf(stderr, "Illegal \"tunnelid\"\n");
+				return -1;
+			}
+			pinfo.tunnelid = tid;
+			pinfo_ok++;
+		} else if (strcmp(*argv, "tunnel") == 0) {
+			unsigned tid;
+			NEXT_ARG();
+			if (get_unsigned(&tid, *argv, 0)) {
+				fprintf(stderr, "Illegal \"tunnel\"\n");
+				return -1;
+			}
+			addattr_l(n, 4096, TCA_RSVP_CLASSID, &tid, 4);
+			NEXT_ARG();
+			if (strcmp(*argv, "skip") == 0) {
+				NEXT_ARG();
+			}
+			if (get_unsigned(&tid, *argv, 0)) {
+				fprintf(stderr, "Illegal \"skip\"\n");
+				return -1;
+			}
+			pinfo.tunnelhdr = tid;
+			pinfo_ok++;
+		} else if (matches(*argv, "police") == 0) {
+			NEXT_ARG();
+			if (parse_police(&argc, &argv, TCA_RSVP_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++;
+	}
+
+	if (pinfo_ok)
+		addattr_l(n, 4096, TCA_RSVP_PINFO, &pinfo, sizeof(pinfo));
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static char * sprint_spi(struct tc_rsvp_gpi *pi, int dir, char *buf)
+{
+	if (pi->offset == 0) {
+		if (dir && pi->mask == htonl(0xFFFF)) {
+			snprintf(buf, SPRINT_BSIZE-1, "/%d", htonl(pi->key));
+			return buf;
+		}
+		if (!dir && pi->mask == htonl(0xFFFF0000)) {
+			snprintf(buf, SPRINT_BSIZE-1, "/%d", htonl(pi->key)>>16);
+			return buf;
+		}
+		if (pi->mask == htonl(0xFFFFFFFF)) {
+			snprintf(buf, SPRINT_BSIZE-1, " spi/esp 0x%08x", htonl(pi->key));
+			return buf;
+		}
+	} else if (pi->offset == 4 && pi->mask == htonl(0xFFFFFFFF)) {
+		snprintf(buf, SPRINT_BSIZE-1, " spi/ah 0x%08x", htonl(pi->key));
+		return buf;
+	} else if (pi->offset == -40 && pi->mask == htonl(0x000FFFFF)) {
+		snprintf(buf, SPRINT_BSIZE-1, " flowlabel 0x%05x", htonl(pi->key));
+		return buf;
+	}
+	snprintf(buf, SPRINT_BSIZE-1, " u32 0x%08x mask %08x at %d",
+		 htonl(pi->key), htonl(pi->mask), pi->offset);
+	return buf;
+}
+
+static int rsvp_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 handle)
+{
+	int family = strcmp(qu->id, "rsvp") == 0 ? AF_INET : AF_INET6;
+	struct rtattr *tb[TCA_RSVP_MAX+1];
+	struct tc_rsvp_pinfo *pinfo = NULL;
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_RSVP_MAX, opt);
+
+	if (handle)
+		fprintf(f, "fh 0x%08x ", handle);
+
+	if (tb[TCA_RSVP_PINFO]) {
+		if (RTA_PAYLOAD(tb[TCA_RSVP_PINFO])  < sizeof(*pinfo))
+			return -1;
+
+		pinfo = RTA_DATA(tb[TCA_RSVP_PINFO]);
+	}
+
+	if (tb[TCA_RSVP_CLASSID]) {
+		SPRINT_BUF(b1);
+		if (!pinfo || pinfo->tunnelhdr == 0)
+			fprintf(f, "flowid %s ", sprint_tc_classid(*(__u32*)RTA_DATA(tb[TCA_RSVP_CLASSID]), b1));
+		else
+			fprintf(f, "tunnel %d skip %d ", *(__u32*)RTA_DATA(tb[TCA_RSVP_CLASSID]), pinfo->tunnelhdr);
+	} else if (pinfo && pinfo->tunnelhdr)
+		fprintf(f, "tunnel [BAD] skip %d ", pinfo->tunnelhdr);
+
+	if (tb[TCA_RSVP_DST]) {
+		char buf[128];
+		fprintf(f, "session ");
+		if (inet_ntop(family, RTA_DATA(tb[TCA_RSVP_DST]), buf, sizeof(buf)) == 0)
+			fprintf(f, " [INVALID DADDR] ");
+		else
+			fprintf(f, "%s", buf);
+		if (pinfo && pinfo->dpi.mask) {
+			SPRINT_BUF(b2);
+			fprintf(f, "%s ", sprint_spi(&pinfo->dpi, 1, b2));
+		} else
+			fprintf(f, " ");
+	} else {
+		if (pinfo && pinfo->dpi.mask) {
+			SPRINT_BUF(b2);
+			fprintf(f, "session [NONE]%s ", sprint_spi(&pinfo->dpi, 1, b2));
+		} else
+			fprintf(f, "session NONE ");
+	}
+
+	if (pinfo && pinfo->protocol) {
+		SPRINT_BUF(b1);
+		fprintf(f, "ipproto %s ", inet_proto_n2a(pinfo->protocol, b1, sizeof(b1)));
+	}
+	if (pinfo && pinfo->tunnelid)
+		fprintf(f, "tunnelid %d ", pinfo->tunnelid);
+	if (tb[TCA_RSVP_SRC]) {
+		char buf[128];
+		fprintf(f, "sender ");
+		if (inet_ntop(family, RTA_DATA(tb[TCA_RSVP_SRC]), buf, sizeof(buf)) == 0) {
+			fprintf(f, "[BAD]");
+		} else {
+			fprintf(f, " %s", buf);
+		}
+		if (pinfo && pinfo->spi.mask) {
+			SPRINT_BUF(b2);
+			fprintf(f, "%s ", sprint_spi(&pinfo->spi, 0, b2));
+		} else
+			fprintf(f, " ");
+	} else if (pinfo && pinfo->spi.mask) {
+		SPRINT_BUF(b2);
+		fprintf(f, "sender [NONE]%s ", sprint_spi(&pinfo->spi, 0, b2));
+	}
+	if (tb[TCA_RSVP_POLICE])
+		tc_print_police(f, tb[TCA_RSVP_POLICE]);
+	return 0;
+}
+
+struct filter_util rsvp_filter_util = {
+	.id = "rsvp",
+	.parse_fopt = rsvp_parse_opt,
+	.print_fopt = rsvp_print_opt,
+};
+
+struct filter_util rsvp6_filter_util = {
+	.id = "rsvp6",
+	.parse_fopt = rsvp_parse_opt,
+	.print_fopt = rsvp_print_opt,
+};
diff --git a/tc/f_tcindex.c b/tc/f_tcindex.c
new file mode 100644
index 0000000..39ac75a
--- /dev/null
+++ b/tc/f_tcindex.c
@@ -0,0 +1,185 @@
+/*
+ * f_tcindex.c		Traffic control index filter
+ *
+ * Written 1998,1999 by Werner Almesberger
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <string.h>
+#include <netinet/in.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+	fprintf(stderr," Usage: ... tcindex [ hash SIZE ] [ mask MASK ]"
+	    " [ shift SHIFT ]\n");
+	fprintf(stderr,"                    [ pass_on | fall_through ]\n");
+	fprintf(stderr,"                    [ classid CLASSID ] "
+	    "[ police POLICE_SPEC ]\n");
+}
+
+
+#define usage() return(-1)
+
+
+static int tcindex_parse_opt(struct filter_util *qu, char *handle, int argc,
+    char **argv, struct nlmsghdr *n)
+{
+	struct tcmsg *t = NLMSG_DATA(n);
+	struct rtattr *tail;
+	char *end;
+
+	if (handle) {
+		t->tcm_handle = strtoul(handle,&end,0);
+		if (*end) {
+			fprintf(stderr, "Illegal filter ID\n");
+			return -1;
+		}
+	}
+	if (!argc) return 0;
+	tail = NLMSG_TAIL(n);
+	addattr_l(n,4096,TCA_OPTIONS,NULL,0);
+	while (argc) {
+		if (!strcmp(*argv,"hash")) {
+			int hash;
+
+			NEXT_ARG();
+			hash = strtoul(*argv,&end,0);
+			if (*end || !hash || hash > 0x10000) {
+				explain();
+				return -1;
+			}
+			addattr_l(n,4096,TCA_TCINDEX_HASH,&hash,sizeof(hash));
+		}
+		else if (!strcmp(*argv,"mask")) {
+			__u16 mask;
+
+			NEXT_ARG();
+			mask = strtoul(*argv,&end,0);
+			if (*end) {
+				explain();
+				return -1;
+			}
+			addattr_l(n,4096,TCA_TCINDEX_MASK,&mask,sizeof(mask));
+		}
+		else if (!strcmp(*argv,"shift")) {
+			int shift;
+
+			NEXT_ARG();
+			shift = strtoul(*argv,&end,0);
+			if (*end) {
+				explain();
+				return -1;
+			}
+			addattr_l(n,4096,TCA_TCINDEX_SHIFT,&shift,
+			    sizeof(shift));
+		}
+		else if (!strcmp(*argv,"fall_through")) {
+			int value = 1;
+
+			addattr_l(n,4096,TCA_TCINDEX_FALL_THROUGH,&value,
+			    sizeof(value));
+		}
+		else if (!strcmp(*argv,"pass_on")) {
+			int value = 0;
+
+			addattr_l(n,4096,TCA_TCINDEX_FALL_THROUGH,&value,
+			    sizeof(value));
+		}
+		else if (!strcmp(*argv,"classid")) {
+			__u32 handle;
+
+			NEXT_ARG();
+			if (get_tc_classid(&handle,*argv)) {
+				fprintf(stderr, "Illegal \"classid\"\n");
+				return -1;
+			}
+			addattr_l(n, 4096, TCA_TCINDEX_CLASSID, &handle, 4);
+		}
+		else if (!strcmp(*argv,"police")) {
+			NEXT_ARG();
+			if (parse_police(&argc, &argv, TCA_TCINDEX_POLICE, n)) {
+				fprintf(stderr, "Illegal \"police\"\n");
+				return -1;
+			}
+			continue;
+		}
+		else {
+			explain();
+			return -1;
+		}
+		argc--;
+		argv++;
+	}
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+
+static int tcindex_print_opt(struct filter_util *qu, FILE *f,
+     struct rtattr *opt, __u32 handle)
+{
+	struct rtattr *tb[TCA_TCINDEX_MAX+1];
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_TCINDEX_MAX, opt);
+
+	if (handle != ~0) fprintf(f,"handle 0x%04x ",handle);
+	if (tb[TCA_TCINDEX_HASH]) {
+		__u16 hash;
+
+		if (RTA_PAYLOAD(tb[TCA_TCINDEX_HASH]) < sizeof(hash))
+			return -1;
+		hash = *(__u16 *) RTA_DATA(tb[TCA_TCINDEX_HASH]);
+		fprintf(f,"hash %d ",hash);
+	}
+	if (tb[TCA_TCINDEX_MASK]) {
+		__u16 mask;
+
+		if (RTA_PAYLOAD(tb[TCA_TCINDEX_MASK]) < sizeof(mask))
+			return -1;
+		mask = *(__u16 *) RTA_DATA(tb[TCA_TCINDEX_MASK]);
+		fprintf(f,"mask 0x%04x ",mask);
+	}
+	if (tb[TCA_TCINDEX_SHIFT]) {
+		int shift;
+
+		if (RTA_PAYLOAD(tb[TCA_TCINDEX_SHIFT]) < sizeof(shift))
+			return -1;
+		shift = *(int *) RTA_DATA(tb[TCA_TCINDEX_SHIFT]);
+		fprintf(f,"shift %d ",shift);
+	}
+	if (tb[TCA_TCINDEX_FALL_THROUGH]) {
+		int fall_through;
+
+		if (RTA_PAYLOAD(tb[TCA_TCINDEX_FALL_THROUGH]) <
+		    sizeof(fall_through))
+			return -1;
+		fall_through = *(int *) RTA_DATA(tb[TCA_TCINDEX_FALL_THROUGH]);
+		fprintf(f,fall_through ? "fall_through " : "pass_on ");
+	}
+	if (tb[TCA_TCINDEX_CLASSID]) {
+		SPRINT_BUF(b1);
+		fprintf(f, "classid %s ",sprint_tc_classid(*(__u32 *)
+		    RTA_DATA(tb[TCA_TCINDEX_CLASSID]), b1));
+	}
+	if (tb[TCA_TCINDEX_POLICE]) {
+		fprintf(f, "\n");
+		tc_print_police(f, tb[TCA_TCINDEX_POLICE]);
+	}
+	return 0;
+}
+
+struct filter_util tcindex_filter_util = {
+	.id = "tcindex",
+	.parse_fopt = tcindex_parse_opt,
+	.print_fopt = tcindex_print_opt,
+};
diff --git a/tc/f_u32.c b/tc/f_u32.c
new file mode 100644
index 0000000..cb3f67e
--- /dev/null
+++ b/tc/f_u32.c
@@ -0,0 +1,1261 @@
+/*
+ * q_u32.c		U32 filter.
+ *
+ *		This program is free software; you can u32istribute 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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *		Match mark added by Catalin(ux aka Dino) BOIE <catab at umbrella.ro> [5 nov 2004]
+ *
+ */
+
+#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 <linux/if.h>
+#include <linux/if_ether.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+extern int show_pretty;
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... u32 [ match SELECTOR ... ] [ link HTID ]"
+		" [ classid CLASSID ]\n");
+	fprintf(stderr, "               [ police POLICE_SPEC ]"
+		" [ offset OFFSET_SPEC ]\n");
+	fprintf(stderr, "               [ ht HTID ] [ hashkey HASHKEY_SPEC ]\n");
+	fprintf(stderr, "               [ sample SAMPLE ]\n");
+	fprintf(stderr, "or         u32 divisor DIVISOR\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, "Where: SELECTOR := SAMPLE SAMPLE ...\n");
+	fprintf(stderr, "       SAMPLE := { ip | ip6 | udp | tcp | icmp |"
+		" u{32|16|8} | mark } SAMPLE_ARGS [divisor DIVISOR]\n");
+	fprintf(stderr, "       FILTERID := X:Y:Z\n");
+	fprintf(stderr, "\nNOTE: CLASSID is parsed at hexadecimal input.\n");
+}
+
+#define usage() return(-1)
+
+int get_u32_handle(__u32 *handle, const char *str)
+{
+	__u32 htid=0, hash=0, nodeid=0;
+	char *tmp = strchr(str, ':');
+
+	if (tmp == NULL) {
+		if (memcmp("0x", str, 2) == 0)
+			return get_u32(handle, str, 16);
+		return -1;
+	}
+	htid = strtoul(str, &tmp, 16);
+	if (tmp == str && *str != ':' && *str != 0)
+		return -1;
+	if (htid>=0x1000)
+		return -1;
+	if (*tmp) {
+		str = tmp+1;
+		hash = strtoul(str, &tmp, 16);
+		if (tmp == str && *str != ':' && *str != 0)
+			return -1;
+		if (hash>=0x100)
+			return -1;
+		if (*tmp) {
+			str = tmp+1;
+			nodeid = strtoul(str, &tmp, 16);
+			if (tmp == str && *str != 0)
+				return -1;
+			if (nodeid>=0x1000)
+				return -1;
+		}
+	}
+	*handle = (htid<<20)|(hash<<12)|nodeid;
+	return 0;
+}
+
+char * sprint_u32_handle(__u32 handle, char *buf)
+{
+	int bsize = SPRINT_BSIZE-1;
+	__u32 htid = TC_U32_HTID(handle);
+	__u32 hash = TC_U32_HASH(handle);
+	__u32 nodeid = TC_U32_NODE(handle);
+	char *b = buf;
+
+	if (handle == 0) {
+		snprintf(b, bsize, "none");
+		return b;
+	}
+	if (htid) {
+		int l = snprintf(b, bsize, "%x:", htid>>20);
+		bsize -= l;
+		b += l;
+	}
+	if (nodeid|hash) {
+		if (hash) {
+			int l = snprintf(b, bsize, "%x", hash);
+			bsize -= l;
+			b += l;
+		}
+		if (nodeid) {
+			int l = snprintf(b, bsize, ":%x", nodeid);
+			bsize -= l;
+			b += l;
+		}
+	}
+	if (show_raw)
+		snprintf(b, bsize, "[%08x] ", handle);
+	return buf;
+}
+
+static int pack_key(struct tc_u32_sel *sel, __u32 key, __u32 mask,
+		    int off, int offmask)
+{
+	int i;
+	int hwm = sel->nkeys;
+
+	key &= mask;
+
+	for (i=0; i<hwm; i++) {
+		if (sel->keys[i].off == off && sel->keys[i].offmask == offmask) {
+			__u32 intersect = mask&sel->keys[i].mask;
+
+			if ((key^sel->keys[i].val) & intersect)
+				return -1;
+			sel->keys[i].val |= key;
+			sel->keys[i].mask |= mask;
+			return 0;
+		}
+	}
+
+	if (hwm >= 128)
+		return -1;
+	if (off % 4)
+		return -1;
+	sel->keys[hwm].val = key;
+	sel->keys[hwm].mask = mask;
+	sel->keys[hwm].off = off;
+	sel->keys[hwm].offmask = offmask;
+	sel->nkeys++;
+	return 0;
+}
+
+static int pack_key32(struct tc_u32_sel *sel, __u32 key, __u32 mask,
+		      int off, int offmask)
+{
+	key = htonl(key);
+	mask = htonl(mask);
+	return pack_key(sel, key, mask, off, offmask);
+}
+
+static int pack_key16(struct tc_u32_sel *sel, __u32 key, __u32 mask,
+		      int off, int offmask)
+{
+	if (key > 0xFFFF || mask > 0xFFFF)
+		return -1;
+
+	if ((off & 3) == 0) {
+		key <<= 16;
+		mask <<= 16;
+	}
+	off &= ~3;
+	key = htonl(key);
+	mask = htonl(mask);
+
+	return pack_key(sel, key, mask, off, offmask);
+}
+
+static int pack_key8(struct tc_u32_sel *sel, __u32 key, __u32 mask, int off, int offmask)
+{
+	if (key > 0xFF || mask > 0xFF)
+		return -1;
+
+	if ((off & 3) == 0) {
+		key <<= 24;
+		mask <<= 24;
+	} else if ((off & 3) == 1) {
+		key <<= 16;
+		mask <<= 16;
+	} else if ((off & 3) == 2) {
+		key <<= 8;
+		mask <<= 8;
+	}
+	off &= ~3;
+	key = htonl(key);
+	mask = htonl(mask);
+
+	return pack_key(sel, key, mask, off, offmask);
+}
+
+
+int parse_at(int *argc_p, char ***argv_p, int *off, int *offmask)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	char *p = *argv;
+
+	if (argc <= 0)
+		return -1;
+
+	if (strlen(p) > strlen("nexthdr+") &&
+	    memcmp(p, "nexthdr+", strlen("nexthdr+")) == 0) {
+		*offmask = -1;
+		p += strlen("nexthdr+");
+	} else if (matches(*argv, "nexthdr+") == 0) {
+		NEXT_ARG();
+		*offmask = -1;
+		p = *argv;
+	}
+
+	if (get_integer(off, p, 0))
+		return -1;
+	argc--; argv++;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+
+static int parse_u32(int *argc_p, char ***argv_p, struct tc_u32_sel *sel,
+		     int off, int offmask)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	__u32 key;
+	__u32 mask;
+
+	if (argc < 2)
+		return -1;
+
+	if (get_u32(&key, *argv, 0))
+		return -1;
+	argc--; argv++;
+
+	if (get_u32(&mask, *argv, 16))
+		return -1;
+	argc--; argv++;
+
+	if (argc > 0 && strcmp(argv[0], "at") == 0) {
+		NEXT_ARG();
+		if (parse_at(&argc, &argv, &off, &offmask))
+			return -1;
+	}
+
+	res = pack_key32(sel, key, mask, off, offmask);
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int parse_u16(int *argc_p, char ***argv_p, struct tc_u32_sel *sel,
+		     int off, int offmask)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	__u32 key;
+	__u32 mask;
+
+	if (argc < 2)
+		return -1;
+
+	if (get_u32(&key, *argv, 0))
+		return -1;
+	argc--; argv++;
+
+	if (get_u32(&mask, *argv, 16))
+		return -1;
+	argc--; argv++;
+
+	if (argc > 0 && strcmp(argv[0], "at") == 0) {
+		NEXT_ARG();
+		if (parse_at(&argc, &argv, &off, &offmask))
+			return -1;
+	}
+	res = pack_key16(sel, key, mask, off, offmask);
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int parse_u8(int *argc_p, char ***argv_p, struct tc_u32_sel *sel,
+		    int off, int offmask)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	__u32 key;
+	__u32 mask;
+
+	if (argc < 2)
+		return -1;
+
+	if (get_u32(&key, *argv, 0))
+		return -1;
+	argc--; argv++;
+
+	if (get_u32(&mask, *argv, 16))
+		return -1;
+	argc--; argv++;
+
+	if (key > 0xFF || mask > 0xFF)
+		return -1;
+
+	if (argc > 0 && strcmp(argv[0], "at") == 0) {
+		NEXT_ARG();
+		if (parse_at(&argc, &argv, &off, &offmask))
+			return -1;
+	}
+
+	res = pack_key8(sel, key, mask, off, offmask);
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int parse_ip_addr(int *argc_p, char ***argv_p, struct tc_u32_sel *sel,
+			 int off)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	inet_prefix addr;
+	__u32 mask;
+	int offmask = 0;
+
+	if (argc < 1)
+		return -1;
+
+	if (get_prefix_1(&addr, *argv, AF_INET))
+		return -1;
+	argc--; argv++;
+
+	if (argc > 0 && strcmp(argv[0], "at") == 0) {
+		NEXT_ARG();
+		if (parse_at(&argc, &argv, &off, &offmask))
+			return -1;
+	}
+
+	mask = 0;
+	if (addr.bitlen)
+		mask = htonl(0xFFFFFFFF<<(32-addr.bitlen));
+	if (pack_key(sel, addr.data[0], mask, off, offmask) < 0)
+		return -1;
+	res = 0;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int parse_ip6_addr(int *argc_p, char ***argv_p,
+			  struct tc_u32_sel *sel, int off)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int plen = 128;
+	int i;
+	inet_prefix addr;
+	int offmask = 0;
+
+	if (argc < 1)
+		return -1;
+
+	if (get_prefix_1(&addr, *argv, AF_INET6))
+		return -1;
+	argc--; argv++;
+
+	if (argc > 0 && strcmp(argv[0], "at") == 0) {
+		NEXT_ARG();
+		if (parse_at(&argc, &argv, &off, &offmask))
+			return -1;
+	}
+
+	plen = addr.bitlen;
+	for (i=0; i<plen; i+=32) {
+//		if (((i+31)&~0x1F)<=plen) {
+		if (i + 31 <= plen) {
+			res = pack_key(sel, addr.data[i/32],
+				       0xFFFFFFFF, off+4*(i/32), offmask);
+			if (res < 0)
+				return -1;
+		} else if (i < plen) {
+			__u32 mask = htonl(0xFFFFFFFF << (32 - (plen -i )));
+			res = pack_key(sel, addr.data[i/32],
+				       mask, off+4*(i/32), offmask);
+			if (res < 0)
+				return -1;
+		}
+	}
+	res = 0;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int parse_ether_addr(int *argc_p, char ***argv_p,
+			    struct tc_u32_sel *sel, int off)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	__u8 addr[6];
+	int offmask = 0;
+	__u32 key;
+	int i;
+
+	if (argc < 1)
+		return -1;
+
+	if (sscanf(*argv, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+		   addr + 0, addr + 1, addr + 2,
+		   addr + 3, addr + 4, addr + 5) != 6) {
+		fprintf(stderr, "parse_ether_addr: improperly formed address '%s'\n",
+			*argv);
+		return -1;
+	}
+
+	argc--; argv++;
+	if (argc > 0 && strcmp(argv[0], "at") == 0) {
+		NEXT_ARG();
+		if (parse_at(&argc, &argv, &off, &offmask))
+			return -1;
+	}
+
+	for (i = 0; i < 6; i += 2) {
+		key = *(__u16 *) (addr + i);
+		
+		res = pack_key16(sel, key, 0xFFFF, off + i, offmask);
+		if (res < 0)
+			return -1;
+	}
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int parse_ip(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc < 2)
+		return -1;
+
+	if (strcmp(*argv, "src") == 0) {
+		NEXT_ARG();
+		res = parse_ip_addr(&argc, &argv, sel, 12);
+	} else if (strcmp(*argv, "dst") == 0) {
+		NEXT_ARG();
+		res = parse_ip_addr(&argc, &argv, sel, 16);
+	} else if (strcmp(*argv, "tos") == 0 ||
+	    matches(*argv, "dsfield") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 1, 0);
+	} else if (strcmp(*argv, "ihl") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 0, 0);
+	} else if (strcmp(*argv, "protocol") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 9, 0);
+	} else if (matches(*argv, "precedence") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 1, 0);
+	} else if (strcmp(*argv, "nofrag") == 0) {
+		argc--; argv++;
+		res = pack_key16(sel, 0, 0x3FFF, 6, 0);
+	} else if (strcmp(*argv, "firstfrag") == 0) {
+		argc--; argv++;
+		res = pack_key16(sel, 0, 0x1FFF, 6, 0);
+	} else if (strcmp(*argv, "df") == 0) {
+		argc--; argv++;
+		res = pack_key16(sel, 0x4000, 0x4000, 6, 0);
+	} else if (strcmp(*argv, "mf") == 0) {
+		argc--; argv++;
+		res = pack_key16(sel, 0x2000, 0x2000, 6, 0);
+	} else if (strcmp(*argv, "dport") == 0) {
+		NEXT_ARG();
+		res = parse_u16(&argc, &argv, sel, 22, 0);
+	} else if (strcmp(*argv, "sport") == 0) {
+		NEXT_ARG();
+		res = parse_u16(&argc, &argv, sel, 20, 0);
+	} else if (strcmp(*argv, "icmp_type") == 0) {
+		NEXT_ARG();
+		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);
+	} else
+		return -1;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+				
+static int parse_ip6(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc < 2)
+		return -1;
+
+	if (strcmp(*argv, "src") == 0) {
+		NEXT_ARG();
+		res = parse_ip6_addr(&argc, &argv, sel, 8);
+	} else if (strcmp(*argv, "dst") == 0) {
+		NEXT_ARG();
+		res = parse_ip6_addr(&argc, &argv, sel, 24);
+	} else if (strcmp(*argv, "priority") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 4, 0);
+	} else if (strcmp(*argv, "protocol") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 6, 0);
+	} else if (strcmp(*argv, "flowlabel") == 0) {
+		NEXT_ARG();
+		res = parse_u32(&argc, &argv, sel, 0, 0);
+	} else if (strcmp(*argv, "dport") == 0) {
+		NEXT_ARG();
+		res = parse_u16(&argc, &argv, sel, 42, 0);
+	} else if (strcmp(*argv, "sport") == 0) {
+		NEXT_ARG();
+		res = parse_u16(&argc, &argv, sel, 40, 0);
+	} else if (strcmp(*argv, "icmp_type") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 40, 0);
+	} else if (strcmp(*argv, "icmp_code") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 41, 1);
+	} else
+		return -1;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int parse_ether(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc < 2)
+		return -1;
+
+	if (strcmp(*argv, "src") == 0) {
+		NEXT_ARG();
+		res = parse_ether_addr(&argc, &argv, sel, -8);
+	} else if (strcmp(*argv, "dst") == 0) {
+		NEXT_ARG();
+		res = parse_ether_addr(&argc, &argv, sel, -14);
+	} else {
+		fprintf(stderr, "Unknown match: ether %s\n", *argv);
+		return -1;
+	}
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+#define parse_tcp parse_udp
+static int parse_udp(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc < 2)
+		return -1;
+
+	if (strcmp(*argv, "src") == 0) {
+		NEXT_ARG();
+		res = parse_u16(&argc, &argv, sel, 0, -1);
+	} else if (strcmp(*argv, "dst") == 0) {
+		NEXT_ARG();
+		res = parse_u16(&argc, &argv, sel, 2, -1);
+	} else
+		return -1;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+
+static int parse_icmp(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc < 2)
+		return -1;
+
+	if (strcmp(*argv, "type") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 0, -1);
+	} else if (strcmp(*argv, "code") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 1, -1);
+	} else
+		return -1;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int parse_mark(int *argc_p, char ***argv_p, struct nlmsghdr *n)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	struct tc_u32_mark mark;
+
+	if (argc <= 1)
+		return -1;
+
+	if (get_u32(&mark.val, *argv, 0)) {
+		fprintf(stderr, "Illegal \"mark\" value\n");
+		return -1;
+	}
+	NEXT_ARG();
+
+	if (get_u32(&mark.mask, *argv, 0)) {
+		fprintf(stderr, "Illegal \"mark\" mask\n");
+		return -1;
+	}
+	NEXT_ARG();
+
+	if ((mark.val & mark.mask) != mark.val) {
+		fprintf(stderr, "Illegal \"mark\" (impossible combination)\n");
+		return -1;
+	}
+
+	addattr_l(n, MAX_MSG, TCA_U32_MARK, &mark, sizeof(mark));
+	res = 0;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int parse_selector(int *argc_p, char ***argv_p,
+			  struct tc_u32_sel *sel, struct nlmsghdr *n)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int res = -1;
+
+	if (argc <= 0)
+		return -1;
+
+	if (matches(*argv, "u32") == 0) {
+		NEXT_ARG();
+		res = parse_u32(&argc, &argv, sel, 0, 0);
+	} else if (matches(*argv, "u16") == 0) {
+		NEXT_ARG();
+		res = parse_u16(&argc, &argv, sel, 0, 0);
+	} else if (matches(*argv, "u8") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, sel, 0, 0);
+	} else if (matches(*argv, "ip") == 0) {
+		NEXT_ARG();
+		res = parse_ip(&argc, &argv, sel);
+	} else 	if (matches(*argv, "ip6") == 0) {
+		NEXT_ARG();
+		res = parse_ip6(&argc, &argv, sel);
+	} else if (matches(*argv, "udp") == 0) {
+		NEXT_ARG();
+		res = parse_udp(&argc, &argv, sel);
+	} else if (matches(*argv, "tcp") == 0) {
+		NEXT_ARG();
+		res = parse_tcp(&argc, &argv, sel);
+	} else if (matches(*argv, "icmp") == 0) {
+		NEXT_ARG();
+		res = parse_icmp(&argc, &argv, sel);
+	} else if (matches(*argv, "mark") == 0) {
+		NEXT_ARG();
+		res = parse_mark(&argc, &argv, n);
+	} else if (matches(*argv, "ether") == 0) {
+		NEXT_ARG();
+		res = parse_ether(&argc, &argv, sel);
+	} else 
+		return -1;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int parse_offset(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	while (argc > 0) {
+		if (matches(*argv, "plus") == 0) {
+			int off;
+			NEXT_ARG();
+			if (get_integer(&off, *argv, 0))
+				return -1;
+			sel->off = off;
+			sel->flags |= TC_U32_OFFSET;
+		} else if (matches(*argv, "at") == 0) {
+			int off;
+			NEXT_ARG();
+			if (get_integer(&off, *argv, 0))
+				return -1;
+			sel->offoff = off;
+			if (off%2) {
+				fprintf(stderr, "offset \"at\" must be even\n");
+				return -1;
+			}
+			sel->flags |= TC_U32_VAROFFSET;
+		} else if (matches(*argv, "mask") == 0) {
+			__u16 mask;
+			NEXT_ARG();
+			if (get_u16(&mask, *argv, 16))
+				return -1;
+			sel->offmask = htons(mask);
+			sel->flags |= TC_U32_VAROFFSET;
+		} else if (matches(*argv, "shift") == 0) {
+			int shift;
+			NEXT_ARG();
+			if (get_integer(&shift, *argv, 0))
+				return -1;
+			sel->offshift = shift;
+			sel->flags |= TC_U32_VAROFFSET;
+		} else if (matches(*argv, "eat") == 0) {
+			sel->flags |= TC_U32_EAT;
+		} else {
+			break;
+		}
+		argc--; argv++;
+	}
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+static int parse_hashkey(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	while (argc > 0) {
+		if (matches(*argv, "mask") == 0) {
+			__u32 mask;
+			NEXT_ARG();
+			if (get_u32(&mask, *argv, 16))
+				return -1;
+			sel->hmask = htonl(mask);
+		} else if (matches(*argv, "at") == 0) {
+			int num;
+			NEXT_ARG();
+			if (get_integer(&num, *argv, 0))
+				return -1;
+			if (num%4)
+				return -1;
+			sel->hoff = num;
+		} else {
+			break;
+		}
+		argc--; argv++;
+	}
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+static void print_ipv4(FILE *f, const struct tc_u32_key *key)
+{
+	char abuf[256];
+
+	switch (key->off) {
+	case 0:
+		switch (ntohl(key->mask)) {
+		case 0x0f000000:
+			fprintf(f, "\n  match IP ihl %u", ntohl(key->val) >> 24);
+			return;
+		case 0x00ff0000:
+			fprintf(f, "\n  match IP dsfield %#x", ntohl(key->val) >> 16);
+			return;
+		}
+		break;
+	case 8:
+		if (ntohl(key->mask) == 0x00ff0000) {
+			fprintf(f, "\n  match IP protocol %d", ntohl(key->val) >> 16);
+			return;
+		}
+		break;
+	case 12:
+	case 16: {
+			int bits = mask2bits(key->mask);
+			if (bits >= 0) {
+				fprintf(f, "\n  %s %s/%d", 
+					key->off == 12 ? "match IP src" : "match IP dst",
+					inet_ntop(AF_INET, &key->val,
+						  abuf, sizeof(abuf)),
+					bits);
+				return;
+			}
+		}
+		break;
+
+	case 20:
+		switch (ntohl(key->mask)) {
+		case 0x0000ffff:
+			fprintf(f, "\n  match sport %u",
+				ntohl(key->val) & 0xffff);
+			return;
+		case 0xffff0000:
+			fprintf(f, "\n  match dport %u",
+				ntohl(key->val) >> 16);
+			return;
+		case 0xffffffff:
+			fprintf(f, "\n  match sport %u, match dport %u",
+				ntohl(key->val) & 0xffff,
+				ntohl(key->val) >> 16);
+
+			return;
+		}
+		/* XXX: Default print_raw */
+	}
+}
+
+static void print_ipv6(FILE *f, const struct tc_u32_key *key)
+{
+	char abuf[256];
+
+	switch (key->off) {
+	case 0:
+		switch (ntohl(key->mask)) {
+		case 0x0f000000:
+			fprintf(f, "\n  match IP ihl %u", ntohl(key->val) >> 24);
+			return;
+		case 0x00ff0000:
+			fprintf(f, "\n  match IP dsfield %#x", ntohl(key->val) >> 16);
+			return;
+		}
+		break;
+	case 8:
+		if (ntohl(key->mask) == 0x00ff0000) {
+			fprintf(f, "\n  match IP protocol %d", ntohl(key->val) >> 16);
+			return;
+		}
+		break;
+	case 12:
+	case 16: {
+			int bits = mask2bits(key->mask);
+			if (bits >= 0) {
+				fprintf(f, "\n  %s %s/%d", 
+					key->off == 12 ? "match IP src" : "match IP dst",
+					inet_ntop(AF_INET, &key->val,
+						  abuf, sizeof(abuf)),
+					bits);
+				return;
+			}
+		}
+		break;
+
+	case 20:
+		switch (ntohl(key->mask)) {
+		case 0x0000ffff:
+			fprintf(f, "\n  match sport %u",
+				ntohl(key->val) & 0xffff);
+			return;
+		case 0xffff0000:
+			fprintf(f, "\n  match dport %u",
+				ntohl(key->val) >> 16);
+			return;
+		case 0xffffffff:
+			fprintf(f, "\n  match sport %u, match dport %u",
+				ntohl(key->val) & 0xffff,
+				ntohl(key->val) >> 16);
+
+			return;
+		}
+		/* XXX: Default print_raw */
+	}
+}
+
+static void print_raw(FILE *f, const struct tc_u32_key *key)
+{
+	fprintf(f, "\n  match %08x/%08x at %s%d", 
+		(unsigned int)ntohl(key->val),
+		(unsigned int)ntohl(key->mask),
+		key->offmask ? "nexthdr+" : "",
+		key->off);
+}
+
+static const struct {
+	__u16 proto;
+	__u16 pad;
+	void (*pprinter)(FILE *f, const struct tc_u32_key *key);
+} u32_pprinters[] = {
+	{0, 	   0, print_raw},
+	{ETH_P_IP, 0, print_ipv4},
+	{ETH_P_IPV6, 0, print_ipv6},
+};
+
+static void show_keys(FILE *f, const struct tc_u32_key *key)
+{
+	int i = 0;
+
+	if (!show_pretty)
+		goto show_k;
+
+	for (i = 0; i < sizeof(u32_pprinters) / sizeof(u32_pprinters[0]); i++) {
+		if (u32_pprinters[i].proto == ntohs(f_proto)) {
+show_k:
+			u32_pprinters[i].pprinter(f, key);
+			return;
+		}
+	}
+
+	i = 0;
+	goto show_k;
+}
+
+static int u32_parse_opt(struct filter_util *qu, char *handle,
+			 int argc, char **argv, struct nlmsghdr *n)
+{
+	struct {
+		struct tc_u32_sel sel;
+		struct tc_u32_key keys[128];
+	} sel;
+	struct tcmsg *t = NLMSG_DATA(n);
+	struct rtattr *tail;
+	int sel_ok = 0, terminal_ok = 0;
+	int sample_ok = 0;
+	__u32 htid = 0;
+	__u32 order = 0;
+
+	memset(&sel, 0, sizeof(sel));
+
+	if (handle && get_u32_handle(&t->tcm_handle, handle)) {
+		fprintf(stderr, "Illegal filter ID\n");
+		return -1;
+	}
+
+	if (argc == 0)
+		return 0;
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);
+
+	while (argc > 0) {
+		if (matches(*argv, "match") == 0) {
+			NEXT_ARG();
+			if (parse_selector(&argc, &argv, &sel.sel, n)) {
+				fprintf(stderr, "Illegal \"match\"\n");
+				return -1;
+			}
+			sel_ok++;
+			continue;
+		} else if (matches(*argv, "offset") == 0) {
+			NEXT_ARG();
+			if (parse_offset(&argc, &argv, &sel.sel)) {
+				fprintf(stderr, "Illegal \"offset\"\n");
+				return -1;
+			}
+			continue;
+		} else if (matches(*argv, "hashkey") == 0) {
+			NEXT_ARG();
+			if (parse_hashkey(&argc, &argv, &sel.sel)) {
+				fprintf(stderr, "Illegal \"hashkey\"\n");
+				return -1;
+			}
+			continue;
+		} 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_U32_CLASSID, &handle, 4);
+			sel.sel.flags |= TC_U32_TERMINAL;
+		} else if (matches(*argv, "divisor") == 0) {
+			unsigned divisor;
+			NEXT_ARG();
+			if (get_unsigned(&divisor, *argv, 0) ||
+			    divisor == 0 ||
+			    divisor > 0x100 || ((divisor - 1) & divisor)) {
+				fprintf(stderr, "Illegal \"divisor\"\n");
+				return -1;
+			}
+			addattr_l(n, MAX_MSG, TCA_U32_DIVISOR, &divisor, 4);
+		} else if (matches(*argv, "order") == 0) {
+			NEXT_ARG();
+			if (get_u32(&order, *argv, 0)) {
+				fprintf(stderr, "Illegal \"order\"\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "link") == 0) {
+			unsigned handle;
+			NEXT_ARG();
+			if (get_u32_handle(&handle, *argv)) {
+				fprintf(stderr, "Illegal \"link\"\n");
+				return -1;
+			}
+			if (handle && TC_U32_NODE(handle)) {
+				fprintf(stderr, "\"link\" must be a hash table.\n");
+				return -1;
+			}
+			addattr_l(n, MAX_MSG, TCA_U32_LINK, &handle, 4);
+		} else if (strcmp(*argv, "ht") == 0) {
+			unsigned handle;
+			NEXT_ARG();
+			if (get_u32_handle(&handle, *argv)) {
+				fprintf(stderr, "Illegal \"ht\"\n");
+				return -1;
+			}
+			if (handle && TC_U32_NODE(handle)) {
+				fprintf(stderr, "\"ht\" must be a hash table.\n");
+				return -1;
+			}
+			if (sample_ok)
+				htid = (htid&0xFF000)|(handle&0xFFF00000);
+			else
+				htid = (handle&0xFFFFF000);
+		} else if (strcmp(*argv, "sample") == 0) {
+			__u32 hash;
+			unsigned divisor = 0x100;
+
+			struct {
+				struct tc_u32_sel sel;
+				struct tc_u32_key keys[4];
+			} sel2;
+			memset(&sel2, 0, sizeof(sel2));
+			NEXT_ARG();
+			if (parse_selector(&argc, &argv, &sel2.sel, n)) {
+				fprintf(stderr, "Illegal \"sample\"\n");
+				return -1;
+			}
+			if (sel2.sel.nkeys != 1) {
+				fprintf(stderr, "\"sample\" must contain"
+					" exactly ONE key.\n");
+				return -1;
+			}
+			if (*argv != 0 && strcmp(*argv, "divisor") == 0) {
+				NEXT_ARG();
+				if (get_unsigned(&divisor, *argv, 0) || divisor == 0 ||
+				    divisor > 0x100 || ((divisor - 1) & divisor)) {
+					fprintf(stderr, "Illegal sample \"divisor\"\n");
+					return -1;
+				}
+				NEXT_ARG();
+			}
+			hash = sel2.sel.keys[0].val&sel2.sel.keys[0].mask;
+			hash ^= hash>>16;
+			hash ^= hash>>8;
+			htid = ((hash%divisor)<<12)|(htid&0xFFF00000);
+			sample_ok = 1;
+			continue;
+		} else if (strcmp(*argv, "indev") == 0) {
+			char ind[IFNAMSIZ + 1];
+			memset(ind, 0, sizeof (ind));
+			argc--;
+			argv++;
+			if (argc < 1) {
+				fprintf(stderr, "Illegal indev\n");
+				return -1;
+			}
+			strncpy(ind, *argv, sizeof (ind) - 1);
+			addattr_l(n, MAX_MSG, TCA_U32_INDEV, ind, strlen(ind) + 1);
+
+		} else if (matches(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (parse_action(&argc, &argv, TCA_U32_ACT, n)) {
+				fprintf(stderr, "Illegal \"action\"\n");
+				return -1;
+			}
+			terminal_ok++;
+			continue;
+
+		} else if (matches(*argv, "police") == 0) {
+			NEXT_ARG();
+			if (parse_police(&argc, &argv, TCA_U32_POLICE, n)) {
+				fprintf(stderr, "Illegal \"police\"\n");
+				return -1;
+			}
+			terminal_ok++;
+			continue;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	/* 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");
+			return -1;
+		}
+		t->tcm_handle |= order;
+	}
+
+	if (htid)
+		addattr_l(n, MAX_MSG, TCA_U32_HASH, &htid, 4);
+	if (sel_ok)
+		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;
+}
+
+static int u32_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt,
+			 __u32 handle)
+{
+	struct rtattr *tb[TCA_U32_MAX+1];
+	struct tc_u32_sel *sel = NULL;
+	struct tc_u32_pcnt *pf = NULL;
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_U32_MAX, opt);
+
+	if (handle) {
+		SPRINT_BUF(b1);
+		fprintf(f, "fh %s ", sprint_u32_handle(handle, b1));
+	}
+	if (TC_U32_NODE(handle)) {
+		fprintf(f, "order %d ", TC_U32_NODE(handle));
+	}
+
+	if (tb[TCA_U32_SEL]) {
+		if (RTA_PAYLOAD(tb[TCA_U32_SEL])  < sizeof(*sel))
+			return -1;
+
+		sel = RTA_DATA(tb[TCA_U32_SEL]);
+	}
+
+	if (tb[TCA_U32_DIVISOR]) {
+		fprintf(f, "ht divisor %d ", *(__u32*)RTA_DATA(tb[TCA_U32_DIVISOR]));
+	} else if (tb[TCA_U32_HASH]) {
+		__u32 htid = *(__u32*)RTA_DATA(tb[TCA_U32_HASH]);
+		fprintf(f, "key ht %x bkt %x ", TC_U32_USERHTID(htid),
+			TC_U32_HASH(htid));
+	} else {
+		fprintf(f, "??? ");
+	}
+	if (tb[TCA_U32_CLASSID]) {
+		SPRINT_BUF(b1);
+		fprintf(f, "%sflowid %s ",
+			!sel || !(sel->flags&TC_U32_TERMINAL) ? "*" : "",
+			sprint_tc_classid(*(__u32*)RTA_DATA(tb[TCA_U32_CLASSID]), b1));
+	} else if (sel && sel->flags&TC_U32_TERMINAL) {
+		fprintf(f, "terminal flowid ??? ");
+	}
+	if (tb[TCA_U32_LINK]) {
+		SPRINT_BUF(b1);
+		fprintf(f, "link %s ",
+			sprint_u32_handle(*(__u32*)RTA_DATA(tb[TCA_U32_LINK]), b1));
+	}
+
+	if (tb[TCA_U32_PCNT]) {
+		if (RTA_PAYLOAD(tb[TCA_U32_PCNT])  < sizeof(*pf)) {
+			fprintf(f, "Broken perf counters \n");
+			return -1;
+		}
+		pf = RTA_DATA(tb[TCA_U32_PCNT]);
+	}
+
+	if (sel && show_stats && NULL != pf)
+		fprintf(f, " (rule hit %llu success %llu)",
+			(unsigned long long) pf->rcnt,
+			(unsigned long long) pf->rhit);
+
+	if (tb[TCA_U32_MARK]) {
+		struct tc_u32_mark *mark = RTA_DATA(tb[TCA_U32_MARK]);
+		if (RTA_PAYLOAD(tb[TCA_U32_MARK]) < sizeof(*mark)) {
+			fprintf(f, "\n  Invalid mark (kernel&iproute2 mismatch)\n");
+		} else {
+			fprintf(f, "\n  mark 0x%04x 0x%04x (success %d)",
+				mark->val, mark->mask, mark->success);
+		}
+	}
+
+	if (sel) {
+		if (sel->nkeys) {
+			int i;
+			for (i=0; i<sel->nkeys; i++) {
+				show_keys(f, sel->keys + i);
+				if (show_stats && NULL != pf)
+					fprintf(f, " (success %llu ) ",
+						(unsigned long long) pf->kcnts[i]);
+			}
+		}
+
+		if (sel->flags&(TC_U32_VAROFFSET|TC_U32_OFFSET)) {
+			fprintf(f, "\n    offset ");
+			if (sel->flags&TC_U32_VAROFFSET)
+				fprintf(f, "%04x>>%d at %d ",
+					ntohs(sel->offmask),
+					sel->offshift,  sel->offoff);
+			if (sel->off)
+				fprintf(f, "plus %d ", sel->off);
+		}
+		if (sel->flags&TC_U32_EAT)
+			fprintf(f, " eat ");
+
+		if (sel->hmask) {
+			fprintf(f, "\n    hash mask %08x at %d ",
+				(unsigned int)htonl(sel->hmask), sel->hoff);
+		}
+	}
+
+	if (tb[TCA_U32_POLICE]) {
+		fprintf(f, "\n");
+		tc_print_police(f, tb[TCA_U32_POLICE]);
+	}
+	if (tb[TCA_U32_INDEV]) {
+		struct rtattr *idev = tb[TCA_U32_INDEV];
+		fprintf(f, "\n  input dev %s\n", (char *) RTA_DATA(idev));
+	}
+	if (tb[TCA_U32_ACT]) {
+		tc_print_action(f, tb[TCA_U32_ACT]);
+	}
+
+	return 0;
+}
+
+struct filter_util u32_filter_util = {
+	.id = "u32",
+	.parse_fopt = u32_parse_opt,
+	.print_fopt = u32_print_opt,
+};
diff --git a/tc/m_action.c b/tc/m_action.c
new file mode 100644
index 0000000..9f24022
--- /dev/null
+++ b/tc/m_action.c
@@ -0,0 +1,630 @@
+/*
+ * m_action.c		Action Management
+ *
+ *		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 (hadi@cyberus.ca)
+ *
+ * TODO:
+ * - parse to be passed a filedescriptor for logging purposes
+ *
+*/
+
+#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 <dlfcn.h>
+
+#include "utils.h"
+#include "tc_common.h"
+#include "tc_util.h"
+
+static struct action_util * action_list;
+#ifdef CONFIG_GACT
+int gact_ld = 0 ; //fuckin backward compatibility
+#endif
+int batch_c = 0;
+int tab_flush = 0;
+
+void act_usage(void)
+{
+	/*XXX: In the near future add a action->print_help to improve
+	 * usability
+	 * This would mean new tc will not be backward compatible
+	 * with any action .so from the old days. But if someone really
+	 * does that, they would know how to fix this ..
+	 *
+	*/
+	fprintf (stderr, "usage: tc actions <ACTSPECOP>*\n");
+	fprintf(stderr,
+		"Where: \tACTSPECOP := ACR | GD | FL\n"
+			"\tACR := add | change | replace <ACTSPEC>* \n"
+			"\tGD := get | delete | <ACTISPEC>*\n"
+			"\tFL := ls | list | flush | <ACTNAMESPEC>\n"
+			"\tACTNAMESPEC :=  action <ACTNAME>\n"
+			"\tACTISPEC := <ACTNAMESPEC> <INDEXSPEC>\n"
+			"\tACTSPEC := action <ACTDETAIL> [INDEXSPEC]\n"
+			"\tINDEXSPEC := index <32 bit indexvalue>\n"
+			"\tACTDETAIL := <ACTNAME> <ACTPARAMS>\n"
+			"\t\tExample ACTNAME is gact, mirred etc\n"
+			"\t\tEach action has its own parameters (ACTPARAMS)\n"
+			"\n");
+
+	exit(-1);
+}
+
+static int print_noaopt(struct action_util *au, FILE *f, struct rtattr *opt)
+{
+	if (opt && RTA_PAYLOAD(opt))
+		fprintf(f, "[Unknown action, optlen=%u] ",
+			(unsigned) RTA_PAYLOAD(opt));
+	return 0;
+}
+
+static int parse_noaopt(struct action_util *au, int *argc_p, char ***argv_p, int code, struct nlmsghdr *n)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc) {
+		fprintf(stderr, "Unknown action \"%s\", hence option \"%s\" is unparsable\n", au->id, *argv);
+	} else {
+		fprintf(stderr, "Unknown action \"%s\"\n", au->id);
+	}
+	return -1;
+}
+
+struct action_util *get_action_kind(char *str)
+{
+	static void *aBODY;
+	void *dlh;
+	char buf[256];
+	struct action_util *a;
+#ifdef CONFIG_GACT
+	int looked4gact = 0;
+restart_s:
+#endif
+	for (a = action_list; a; a = a->next) {
+		if (strcmp(a->id, str) == 0)
+			return a;
+	}
+
+	snprintf(buf, sizeof(buf), "m_%s.so", str);
+	dlh = dlopen(buf, RTLD_LAZY);
+	if (dlh == NULL) {
+		dlh = aBODY;
+		if (dlh == NULL) {
+			dlh = aBODY = dlopen(NULL, RTLD_LAZY);
+			if (dlh == NULL)
+				goto noexist;
+		}
+	}
+
+	snprintf(buf, sizeof(buf), "%s_action_util", str);
+	a = dlsym(dlh, buf);
+	if (a == NULL)
+		goto noexist;
+
+reg:
+	a->next = action_list;
+	action_list = a;
+	return a;
+
+noexist:
+#ifdef CONFIG_GACT
+	if (!looked4gact) {
+		looked4gact = 1;
+		strcpy(str,"gact");
+		goto restart_s;
+	}
+#endif
+	a = malloc(sizeof(*a));
+	if (a) {
+		memset(a, 0, sizeof(*a));
+		strncpy(a->id, "noact", 15);
+		a->parse_aopt = parse_noaopt;
+		a->print_aopt = print_noaopt;
+		goto reg;
+	}
+	return a;
+}
+
+int
+new_cmd(char **argv)
+{
+	if ((matches(*argv, "change") == 0) ||
+		(matches(*argv, "replace") == 0)||
+		(matches(*argv, "delete") == 0)||
+		(matches(*argv, "add") == 0))
+			return 1;
+
+	return 0;
+
+}
+
+int
+parse_action(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	struct rtattr *tail, *tail2;
+	char k[16];
+	int ok = 0;
+	int eap = 0; /* expect action parameters */
+
+	int ret = 0;
+	int prio = 0;
+
+	if (argc <= 0)
+		return -1;
+
+	tail = tail2 = NLMSG_TAIL(n);
+
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+
+	while (argc > 0) {
+
+		memset(k, 0, sizeof (k));
+
+		if (strcmp(*argv, "action") == 0 ) {
+			argc--;
+			argv++;
+			eap = 1;
+#ifdef CONFIG_GACT
+			if (!gact_ld) {
+				get_action_kind("gact");
+			}
+#endif
+			continue;
+		} else if (strcmp(*argv, "help") == 0) {
+			return -1;
+		} else if (new_cmd(argv)) {
+			goto done0;
+		} else {
+			struct action_util *a = NULL;
+			strncpy(k, *argv, sizeof (k) - 1);
+			eap = 0;
+			if (argc > 0 ) {
+				a = get_action_kind(k);
+			} else {
+done0:
+				if (ok)
+					break;
+				else
+					goto done;
+			}
+
+			if (NULL == a) {
+				goto bad_val;
+			}
+
+			tail = NLMSG_TAIL(n);
+			addattr_l(n, MAX_MSG, ++prio, NULL, 0);
+			addattr_l(n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
+
+			ret = a->parse_aopt(a,&argc, &argv, TCA_ACT_OPTIONS, n);
+
+			if (ret < 0) {
+				fprintf(stderr,"bad action parsing\n");
+				goto bad_val;
+			}
+			tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+			ok++;
+		}
+
+	}
+
+	if (eap > 0) {
+		fprintf(stderr,"bad action empty %d\n",eap);
+		goto bad_val;
+	}
+
+	tail2->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail2;
+
+done:
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+bad_val:
+	/* no need to undo things, returning from here should
+	 * cause enough pain */
+	fprintf(stderr, "parse_action: bad value (%d:%s)!\n",argc,*argv);
+	return -1;
+}
+
+int
+tc_print_one_action(FILE * f, struct rtattr *arg)
+{
+
+	struct rtattr *tb[TCA_ACT_MAX + 1];
+	int err = 0;
+	struct action_util *a = NULL;
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, TCA_ACT_MAX, arg);
+	if (tb[TCA_ACT_KIND] == NULL) {
+		fprintf(stderr, "NULL Action!\n");
+		return -1;
+	}
+
+
+	a = get_action_kind(RTA_DATA(tb[TCA_ACT_KIND]));
+	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]);
+
+
+	if (0 > err)
+		return err;
+
+	if (show_stats && tb[TCA_ACT_STATS]) {
+		fprintf(f, "\tAction statistics:\n");
+		print_tcstats2_attr(f, tb[TCA_ACT_STATS], "\t", NULL);
+		fprintf(f, "\n");
+	}
+
+	return 0;
+}
+
+int
+tc_print_action(FILE * f, const struct rtattr *arg)
+{
+
+	int i;
+	struct rtattr *tb[TCA_ACT_MAX_PRIO + 1];
+
+	if (arg == NULL)
+		return 0;
+
+	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;
+	}
+
+	for (i = 0; i < TCA_ACT_MAX_PRIO; i++) {
+		if (tb[i]) {
+			fprintf(f, "\n\taction order %d: ", i + batch_c);
+			if (0 > tc_print_one_action(f, tb[i])) {
+				fprintf(f, "Error printing action\n");
+			}
+		}
+
+	}
+
+	batch_c+=TCA_ACT_MAX_PRIO ;
+	return 0;
+}
+
+int print_action(const struct sockaddr_nl *who,
+			   struct nlmsghdr *n,
+			   void *arg)
+{
+	FILE *fp = (FILE*)arg;
+	struct tcamsg *t = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr * tb[TCAA_MAX+1];
+
+	len -= NLMSG_LENGTH(sizeof(*t));
+
+	if (len < 0) {
+		fprintf(stderr, "Wrong len %d\n", len);
+		return -1;
+	}
+
+	parse_rtattr(tb, TCAA_MAX, TA_RTA(t), len);
+
+	if (NULL == tb[TCA_ACT_TAB]) {
+		if (n->nlmsg_type != RTM_GETACTION)
+			fprintf(stderr, "print_action: NULL kind\n");
+		return -1;
+	}
+
+	if (n->nlmsg_type == RTM_DELACTION) {
+		if (n->nlmsg_flags & NLM_F_ROOT) {
+			fprintf(fp, "Flushed table ");
+			tab_flush = 1;
+		} else {
+			fprintf(fp, "deleted action ");
+		}
+	}
+
+	if (n->nlmsg_type == RTM_NEWACTION)
+		fprintf(fp, "Added action ");
+	tc_print_action(fp, tb[TCA_ACT_TAB]);
+
+	return 0;
+}
+
+int tc_action_gd(int cmd, unsigned flags, int *argc_p, char ***argv_p)
+{
+	char k[16];
+	struct action_util *a = NULL;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int prio = 0;
+	int ret = 0;
+	__u32 i;
+	struct sockaddr_nl nladdr;
+	struct rtattr *tail;
+	struct rtattr *tail2;
+	struct nlmsghdr *ans = NULL;
+
+	struct {
+		struct nlmsghdr         n;
+		struct tcamsg           t;
+		char                    buf[MAX_MSG];
+	} req;
+
+	req.t.tca_family = AF_UNSPEC;
+
+	memset(&req, 0, sizeof(req));
+
+	memset(&nladdr, 0, sizeof(nladdr));
+	nladdr.nl_family = AF_NETLINK;
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
+	req.n.nlmsg_type = cmd;
+	argc -=1;
+	argv +=1;
+
+
+	tail = NLMSG_TAIL(&req.n);
+	addattr_l(&req.n, MAX_MSG, TCA_ACT_TAB, NULL, 0);
+
+	while (argc > 0) {
+		if (strcmp(*argv, "action") == 0 ) {
+			argc--;
+			argv++;
+			continue;
+		} else if (strcmp(*argv, "help") == 0) {
+			return -1;
+		}
+
+		strncpy(k, *argv, sizeof (k) - 1);
+		a = get_action_kind(k);
+		if (NULL == a) {
+			fprintf(stderr, "Error: non existent action: %s\n",k);
+			ret = -1;
+			goto bad_val;
+		}
+		if (strcmp(a->id, k) != 0) {
+			fprintf(stderr, "Error: non existent action: %s\n",k);
+			ret = -1;
+			goto bad_val;
+		}
+
+		argc -=1;
+		argv +=1;
+		if (argc <= 0) {
+			fprintf(stderr, "Error: no index specified action: %s\n",k);
+			ret = -1;
+			goto bad_val;
+		}
+
+		if (matches(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&i, *argv, 10)) {
+				fprintf(stderr, "Illegal \"index\"\n");
+				ret = -1;
+				goto bad_val;
+			}
+			argc -=1;
+			argv +=1;
+		} else {
+			fprintf(stderr, "Error: no index specified action: %s\n",k);
+			ret = -1;
+			goto bad_val;
+		}
+
+		tail2 = NLMSG_TAIL(&req.n);
+		addattr_l(&req.n, MAX_MSG, ++prio, NULL, 0);
+		addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
+		addattr32(&req.n, MAX_MSG, TCA_ACT_INDEX, i);
+		tail2->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail2;
+
+	}
+
+	tail->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail;
+
+	req.n.nlmsg_seq = rth.dump = ++rth.seq;
+	if (cmd == RTM_GETACTION)
+		ans = &req.n;
+
+	if (rtnl_talk(&rth, &req.n, 0, 0, ans, NULL, NULL) < 0) {
+		fprintf(stderr, "We have an error talking to the kernel\n");
+		return 1;
+	}
+
+	if (ans && print_action(NULL, &req.n, (void*)stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+
+	*argc_p = argc;
+	*argv_p = argv;
+bad_val:
+	return ret;
+}
+
+int tc_action_modify(int cmd, unsigned flags, int *argc_p, char ***argv_p)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int ret = 0;
+
+	struct rtattr *tail;
+	struct {
+		struct nlmsghdr         n;
+		struct tcamsg           t;
+		char                    buf[MAX_MSG];
+	} req;
+
+	req.t.tca_family = AF_UNSPEC;
+
+	memset(&req, 0, sizeof(req));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
+	req.n.nlmsg_type = cmd;
+	tail = NLMSG_TAIL(&req.n);
+	argc -=1;
+	argv +=1;
+	if (parse_action(&argc, &argv, TCA_ACT_TAB, &req.n)) {
+		fprintf(stderr, "Illegal \"action\"\n");
+		return -1;
+	}
+	tail->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail;
+
+	if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) {
+		fprintf(stderr, "We have an error talking to the kernel\n");
+		ret = -1;
+	}
+
+	*argc_p = argc;
+	*argv_p = argv;
+
+	return ret;
+}
+
+int tc_act_list_or_flush(int argc, char **argv, int event)
+{
+	int ret = 0, prio = 0, msg_size = 0;
+	char k[16];
+	struct rtattr *tail,*tail2;
+	struct action_util *a = NULL;
+	struct {
+		struct nlmsghdr         n;
+		struct tcamsg           t;
+		char                    buf[MAX_MSG];
+	} req;
+
+	req.t.tca_family = AF_UNSPEC;
+
+	memset(&req, 0, sizeof(req));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg));
+
+	tail = NLMSG_TAIL(&req.n);
+	addattr_l(&req.n, MAX_MSG, TCA_ACT_TAB, NULL, 0);
+	tail2 = NLMSG_TAIL(&req.n);
+
+	strncpy(k, *argv, sizeof (k) - 1);
+#ifdef CONFIG_GACT
+	if (!gact_ld) {
+		get_action_kind("gact");
+	}
+#endif
+	a = get_action_kind(k);
+	if (NULL == a) {
+		fprintf(stderr,"bad action %s\n",k);
+		goto bad_val;
+	}
+	if (strcmp(a->id, k) != 0) {
+		fprintf(stderr,"bad action %s\n",k);
+		goto bad_val;
+	}
+	strncpy(k, *argv, sizeof (k) - 1);
+
+	addattr_l(&req.n, MAX_MSG, ++prio, NULL, 0);
+	addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
+	tail2->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail2;
+	tail->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail;
+
+	msg_size = NLMSG_ALIGN(req.n.nlmsg_len) - NLMSG_ALIGN(sizeof(struct nlmsghdr));
+
+	if (event == RTM_GETACTION) {
+		if (rtnl_dump_request(&rth, event, (void *)&req.t, msg_size) < 0) {
+			perror("Cannot send dump request");
+			return 1;
+		}
+		ret = rtnl_dump_filter(&rth, print_action, stdout, NULL, NULL);
+	}
+
+	if (event == RTM_DELACTION) {
+		req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len);
+		req.n.nlmsg_type = RTM_DELACTION;
+		req.n.nlmsg_flags |= NLM_F_ROOT;
+		req.n.nlmsg_flags |= NLM_F_REQUEST;
+		if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) {
+			fprintf(stderr, "We have an error flushing\n");
+			return 1;
+		}
+
+	}
+
+bad_val:
+
+	return ret;
+}
+
+int do_action(int argc, char **argv)
+{
+
+	int ret = 0;
+
+	while (argc > 0) {
+
+		if (matches(*argv, "add") == 0) {
+			ret =  tc_action_modify(RTM_NEWACTION, NLM_F_EXCL|NLM_F_CREATE, &argc, &argv);
+		} else if (matches(*argv, "change") == 0 ||
+			  matches(*argv, "replace") == 0) {
+			ret = tc_action_modify(RTM_NEWACTION, NLM_F_CREATE|NLM_F_REPLACE, &argc, &argv);
+		} else if (matches(*argv, "delete") == 0) {
+			argc -=1;
+			argv +=1;
+			ret = tc_action_gd(RTM_DELACTION, 0,  &argc, &argv);
+		} else if (matches(*argv, "get") == 0) {
+			argc -=1;
+			argv +=1;
+			ret = tc_action_gd(RTM_GETACTION, 0,  &argc, &argv);
+		} else if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+						|| matches(*argv, "lst") == 0) {
+			if (argc <= 2) {
+				act_usage();
+				return -1;
+			}
+			return tc_act_list_or_flush(argc-2, argv+2, RTM_GETACTION);
+		} else if (matches(*argv, "flush") == 0) {
+			if (argc <= 2) {
+				act_usage();
+				return -1;
+			}
+			return tc_act_list_or_flush(argc-2, argv+2, RTM_DELACTION);
+		} else if (matches(*argv, "help") == 0) {
+			act_usage();
+			return -1;
+		} else {
+
+			ret = -1;
+		}
+
+		if (ret < 0) {
+			fprintf(stderr, "Command \"%s\" is unknown, try \"tc actions help\".\n", *argv);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
diff --git a/tc/m_ematch.c b/tc/m_ematch.c
new file mode 100644
index 0000000..7f79a06
--- /dev/null
+++ b/tc/m_ematch.c
@@ -0,0 +1,568 @@
+/*
+ * m_ematch.c		Extended Matches
+ *
+ *		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:	Thomas Graf <tgraf@suug.ch>
+ */
+
+#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 <dlfcn.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "m_ematch.h"
+
+#define EMATCH_MAP "/etc/iproute2/ematch_map"
+
+static struct ematch_util *ematch_list;
+
+/* export to bison parser */
+int ematch_argc;
+char **ematch_argv;
+char *ematch_err = NULL;
+struct ematch *ematch_root;
+
+static int begin_argc;
+static char **begin_argv;
+
+static inline void map_warning(int num, char *kind)
+{
+	fprintf(stderr,
+	    "Error: Unable to find ematch \"%s\" in %s\n" \
+	    "Please assign a unique ID to the ematch kind the suggested " \
+	    "entry is:\n" \
+	    "\t%d\t%s\n",
+	    kind, EMATCH_MAP, num, kind);
+}
+
+static int lookup_map(__u16 num, char *dst, int len, const char *file)
+{
+	int err = -EINVAL;
+	char buf[512];
+	FILE *fd = fopen(file, "r");
+
+	if (fd == NULL)
+		return -errno;
+
+	while (fgets(buf, sizeof(buf), fd)) {
+		char namebuf[512], *p = buf;
+		int id;
+
+		while (*p == ' ' || *p == '\t')
+			p++;
+		if (*p == '#' || *p == '\n' || *p == 0)
+			continue;
+
+		if (sscanf(p, "%d %s", &id, namebuf) != 2) {
+			fprintf(stderr, "ematch map %s corrupted at %s\n",
+			    file, p);
+			goto out;
+		}
+
+		if (id == num) {
+			if (dst)
+				strncpy(dst, namebuf, len - 1);
+			err = 0;
+			goto out;
+		}
+	}
+
+	err = -ENOENT;
+out:
+	fclose(fd);
+	return err;
+}
+
+static int lookup_map_id(char *kind, int *dst, const char *file)
+{
+	int err = -EINVAL;
+	char buf[512];
+	FILE *fd = fopen(file, "r");
+
+	if (fd == NULL)
+		return -errno;
+
+	while (fgets(buf, sizeof(buf), fd)) {
+		char namebuf[512], *p = buf;
+		int id;
+
+		while (*p == ' ' || *p == '\t')
+			p++;
+		if (*p == '#' || *p == '\n' || *p == 0)
+			continue;
+
+		if (sscanf(p, "%d %s", &id, namebuf) != 2) {
+			fprintf(stderr, "ematch map %s corrupted at %s\n",
+			    file, p);
+			goto out;
+		}
+
+		if (!strcasecmp(namebuf, kind)) {
+			if (dst)
+				*dst = id;
+			err = 0;
+			goto out;
+		}
+	}
+
+	err = -ENOENT;
+	*dst = 0;
+out:
+	fclose(fd);
+	return err;
+}
+
+static struct ematch_util *get_ematch_kind(char *kind)
+{
+	static void *body;
+	void *dlh;
+	char buf[256];
+	struct ematch_util *e;
+
+	for (e = ematch_list; e; e = e->next) {
+		if (strcmp(e->kind, kind) == 0)
+			return e;
+	}
+
+	snprintf(buf, sizeof(buf), "em_%s.so", kind);
+	dlh = dlopen(buf, RTLD_LAZY);
+	if (dlh == NULL) {
+		dlh = body;
+		if (dlh == NULL) {
+			dlh = body = dlopen(NULL, RTLD_LAZY);
+			if (dlh == NULL)
+				return NULL;
+		}
+	}
+
+	snprintf(buf, sizeof(buf), "%s_ematch_util", kind);
+	e = dlsym(dlh, buf);
+	if (e == NULL)
+		return NULL;
+
+	e->next = ematch_list;
+	ematch_list = e;
+
+	return e;
+}
+
+static struct ematch_util *get_ematch_kind_num(__u16 kind)
+{
+	char name[32];
+
+	if (lookup_map(kind, name, sizeof(name), EMATCH_MAP) < 0)
+		return NULL;
+
+	return get_ematch_kind(name);
+}
+
+static int parse_tree(struct nlmsghdr *n, struct ematch *tree)
+{
+	int index = 1;
+	struct ematch *t;
+
+	for (t = tree; t; t = t->next) {
+		struct rtattr *tail = NLMSG_TAIL(n);
+		struct tcf_ematch_hdr hdr = {
+			.flags = t->relation
+		};
+
+		if (t->inverted)
+			hdr.flags |= TCF_EM_INVERT;
+
+		addattr_l(n, MAX_MSG, index++, NULL, 0);
+
+		if (t->child) {
+			__u32 r = t->child_ref;
+			addraw_l(n, MAX_MSG, &hdr, sizeof(hdr));
+			addraw_l(n, MAX_MSG, &r, sizeof(r));
+		} else {
+			int num = 0, err;
+			char buf[64];
+			struct ematch_util *e;
+
+			if (t->args == NULL)
+				return -1;
+
+			strncpy(buf, (char*) t->args->data, sizeof(buf)-1);
+			e = get_ematch_kind(buf);
+			if (e == NULL) {
+				fprintf(stderr, "Unknown ematch \"%s\"\n",
+				    buf);
+				return -1;
+			}
+
+			err = lookup_map_id(buf, &num, EMATCH_MAP);
+			if (err < 0) {
+				if (err == -ENOENT)
+					map_warning(e->kind_num, buf);
+				return err;
+			}
+
+			hdr.kind = num;
+			if (e->parse_eopt(n, &hdr, t->args->next) < 0)
+				return -1;
+		}
+
+		tail->rta_len = (void*) NLMSG_TAIL(n) - (void*) tail;
+	}
+
+	return 0;
+}
+
+static int flatten_tree(struct ematch *head, struct ematch *tree)
+{
+	int i, count = 0;
+	struct ematch *t;
+
+	for (;;) {
+		count++;
+
+		if (tree->child) {
+			for (t = head; t->next; t = t->next);
+			t->next = tree->child;
+			count += flatten_tree(head, tree->child);
+		}
+
+		if (tree->relation == 0)
+			break;
+
+		tree = tree->next;
+	}
+
+	for (i = 0, t = head; t; t = t->next, i++)
+		t->index = i;
+
+	for (t = head; t; t = t->next)
+		if (t->child)
+			t->child_ref = t->child->index;
+
+	return count;
+}
+
+int em_parse_error(int err, struct bstr *args, struct bstr *carg,
+		   struct ematch_util *e, char *fmt, ...)
+{
+	va_list a;
+
+	va_start(a, fmt);
+	vfprintf(stderr, fmt, a);
+	va_end(a);
+
+	if (ematch_err)
+		fprintf(stderr, ": %s\n... ", ematch_err);
+	else
+		fprintf(stderr, "\n... ");
+
+	while (ematch_argc < begin_argc) {
+		if (ematch_argc == (begin_argc - 1))
+			fprintf(stderr, ">>%s<< ", *begin_argv);
+		else
+			fprintf(stderr, "%s ", *begin_argv);
+		begin_argv++;
+		begin_argc--;
+	}
+
+	fprintf(stderr, "...\n");
+
+	if (args) {
+		fprintf(stderr, "... %s(", e->kind);
+		while (args) {
+			fprintf(stderr, "%s", args == carg ? ">>" : "");
+			bstr_print(stderr, args, 1);
+			fprintf(stderr, "%s%s", args == carg ? "<<" : "",
+			    args->next ? " " : "");
+			args = args->next;
+		}
+		fprintf(stderr, ")...\n");
+
+	}
+
+	if (e == NULL) {
+		fprintf(stderr,
+		    "Usage: EXPR\n" \
+		    "where: EXPR  := TERM [ { and | or } EXPR ]\n" \
+		    "       TERM  := [ not ] { MATCH | '(' EXPR ')' }\n" \
+		    "       MATCH := module '(' ARGS ')'\n" \
+		    "       ARGS := ARG1 ARG2 ...\n" \
+		    "\n" \
+		    "Example: a(x y) and not (b(x) or c(x y z))\n");
+	} else
+		e->print_usage(stderr);
+
+	return -err;
+}
+
+static inline void free_ematch_err(void)
+{
+	if (ematch_err) {
+		free(ematch_err);
+		ematch_err = NULL;
+	}
+}
+
+extern int ematch_parse(void);
+
+int parse_ematch(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+	begin_argc = ematch_argc = *argc_p;
+	begin_argv = ematch_argv = *argv_p;
+
+	if (ematch_parse()) {
+		int err = em_parse_error(EINVAL, NULL, NULL, NULL,
+		    "Parse error");
+		free_ematch_err();
+		return err;
+	}
+
+	free_ematch_err();
+
+	/* undo look ahead by parser */
+	ematch_argc++;
+	ematch_argv--;
+
+	if (ematch_root) {
+		struct rtattr *tail, *tail_list;
+
+		struct tcf_ematch_tree_hdr hdr = {
+			.nmatches = flatten_tree(ematch_root, ematch_root),
+			.progid = TCF_EM_PROG_TC
+		};
+
+		tail = NLMSG_TAIL(n);
+		addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+		addattr_l(n, MAX_MSG, TCA_EMATCH_TREE_HDR, &hdr, sizeof(hdr));
+
+		tail_list = NLMSG_TAIL(n);
+		addattr_l(n, MAX_MSG, TCA_EMATCH_TREE_LIST, NULL, 0);
+
+		if (parse_tree(n, ematch_root) < 0)
+			return -1;
+
+		tail_list->rta_len = (void*) NLMSG_TAIL(n) - (void*) tail_list;
+		tail->rta_len = (void*) NLMSG_TAIL(n) - (void*) tail;
+	}
+
+	*argc_p = ematch_argc;
+	*argv_p = ematch_argv;
+
+	return 0;
+}
+
+static int print_ematch_seq(FILE *fd, struct rtattr **tb, int start,
+			    int prefix)
+{
+	int n, i = start;
+	struct tcf_ematch_hdr *hdr;
+	int dlen;
+	void *data;
+
+	for (;;) {
+		if (tb[i] == NULL)
+			return -1;
+
+		dlen = RTA_PAYLOAD(tb[i]) - sizeof(*hdr);
+		data = (void *) RTA_DATA(tb[i]) + sizeof(*hdr);
+
+		if (dlen < 0)
+			return -1;
+
+		hdr = RTA_DATA(tb[i]);
+
+		if (hdr->flags & TCF_EM_INVERT)
+			fprintf(fd, "NOT ");
+
+		if (hdr->kind == 0) {
+			__u32 ref;
+
+			if (dlen < sizeof(__u32))
+				return -1;
+
+			ref = *(__u32 *) data;
+			fprintf(fd, "(\n");
+			for (n = 0; n <= prefix; n++)
+				fprintf(fd, "  ");
+			if (print_ematch_seq(fd, tb, ref + 1, prefix + 1) < 0)
+				return -1;
+			for (n = 0; n < prefix; n++)
+				fprintf(fd, "  ");
+			fprintf(fd, ") ");
+
+		} else {
+			struct ematch_util *e;
+
+			e = get_ematch_kind_num(hdr->kind);
+			if (e == NULL)
+				fprintf(fd, "[unknown ematch %d]\n",
+				    hdr->kind);
+			else {
+				fprintf(fd, "%s(", e->kind);
+				if (e->print_eopt(fd, hdr, data, dlen) < 0)
+					return -1;
+				fprintf(fd, ")\n");
+			}
+			if (hdr->flags & TCF_EM_REL_MASK)
+				for (n = 0; n < prefix; n++)
+					fprintf(fd, "  ");
+		}
+
+		switch (hdr->flags & TCF_EM_REL_MASK) {
+			case TCF_EM_REL_AND:
+				fprintf(fd, "AND ");
+				break;
+
+			case TCF_EM_REL_OR:
+				fprintf(fd, "OR ");
+				break;
+
+			default:
+				return 0;
+		}
+
+		i++;
+	}
+
+	return 0;
+}
+
+static int print_ematch_list(FILE *fd, struct tcf_ematch_tree_hdr *hdr,
+			     struct rtattr *rta)
+{
+	int err = -1;
+	struct rtattr **tb;
+
+	tb = malloc((hdr->nmatches + 1) * sizeof(struct rtattr *));
+	if (tb == NULL)
+		return -1;
+
+	if (parse_rtattr_nested(tb, hdr->nmatches, rta) < 0)
+		goto errout;
+
+	fprintf(fd, "\n  ");
+	if (print_ematch_seq(fd, tb, 1, 1) < 0)
+		goto errout;
+
+	err = 0;
+errout:
+	free(tb);
+	return err;
+}
+
+int print_ematch(FILE *fd, const struct rtattr *rta)
+{
+	struct rtattr *tb[TCA_EMATCH_TREE_MAX+1];
+	struct tcf_ematch_tree_hdr *hdr;
+
+	if (parse_rtattr_nested(tb, TCA_EMATCH_TREE_MAX, rta) < 0)
+		return -1;
+
+	if (tb[TCA_EMATCH_TREE_HDR] == NULL) {
+		fprintf(stderr, "Missing ematch tree header\n");
+		return -1;
+	}
+
+	if (tb[TCA_EMATCH_TREE_LIST] == NULL) {
+		fprintf(stderr, "Missing ematch tree list\n");
+		return -1;
+	}
+
+	if (RTA_PAYLOAD(tb[TCA_EMATCH_TREE_HDR]) < sizeof(*hdr)) {
+		fprintf(stderr, "Ematch tree header size mismatch\n");
+		return -1;
+	}
+
+	hdr = RTA_DATA(tb[TCA_EMATCH_TREE_HDR]);
+
+	return print_ematch_list(fd, hdr, tb[TCA_EMATCH_TREE_LIST]);
+}
+
+struct bstr * bstr_alloc(const char *text)
+{
+	struct bstr *b = calloc(1, sizeof(*b));
+
+	if (b == NULL)
+		return NULL;
+
+	b->data = strdup(text);
+	if (b->data == NULL) {
+		free(b);
+		return NULL;
+	}
+
+	b->len = strlen(text);
+
+	return b;
+}
+
+unsigned long bstrtoul(const struct bstr *b)
+{
+	char *inv = NULL;
+	unsigned long l;
+	char buf[b->len+1];
+
+	memcpy(buf, b->data, b->len);
+	buf[b->len] = '\0';
+
+	l = strtoul(buf, &inv, 0);
+	if (l == ULONG_MAX || inv == buf)
+		return ULONG_MAX;
+
+	return l;
+}
+
+void bstr_print(FILE *fd, const struct bstr *b, int ascii)
+{
+	int i;
+	char *s = b->data;
+
+	if (ascii)
+		for (i = 0; i < b->len; i++)
+		    fprintf(fd, "%c", isprint(s[i]) ? s[i] : '.');
+	else {
+		for (i = 0; i < b->len; i++)
+		    fprintf(fd, "%02x", s[i]);
+		fprintf(fd, "\"");
+		for (i = 0; i < b->len; i++)
+		    fprintf(fd, "%c", isprint(s[i]) ? s[i] : '.');
+		fprintf(fd, "\"");
+	}
+}
+
+void print_ematch_tree(const struct ematch *tree)
+{
+	const struct ematch *t;
+
+	for (t = tree; t; t = t->next) {
+		if (t->inverted)
+			printf("NOT ");
+
+		if (t->child) {
+			printf("(");
+			print_ematch_tree(t->child);
+			printf(")");
+		} else {
+			struct bstr *b;
+			for (b = t->args; b; b = b->next)
+				printf("%s%s", b->data, b->next ? " " : "");
+		}
+
+		if (t->relation == TCF_EM_REL_AND)
+			printf(" AND ");
+		else if (t->relation == TCF_EM_REL_OR)
+			printf(" OR ");
+	}
+}
diff --git a/tc/m_ematch.h b/tc/m_ematch.h
new file mode 100644
index 0000000..5036e9b
--- /dev/null
+++ b/tc/m_ematch.h
@@ -0,0 +1,111 @@
+#ifndef __TC_EMATCH_H_
+#define __TC_EMATCH_H_
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+#define EMATCHKINDSIZ 16
+
+struct bstr
+{
+	char	*data;
+	unsigned int	len;
+	int		quoted;
+	struct bstr	*next;
+};
+
+extern struct bstr * bstr_alloc(const char *text);
+
+static inline struct bstr * bstr_new(char *data, unsigned int len)
+{
+	struct bstr *b = calloc(1, sizeof(*b));
+
+	if (b == NULL)
+		return NULL;
+
+	b->data = data;
+	b->len = len;
+
+	return b;
+}
+
+static inline int bstrcmp(struct bstr *b, const char *text)
+{
+	int len = strlen(text);
+	int d = b->len - len;
+
+	if (d == 0)
+		return strncmp(b->data, text, len);
+
+	return d;
+}
+
+static inline struct bstr *bstr_next(struct bstr *b)
+{
+	return b->next;
+}
+
+extern unsigned long bstrtoul(const struct bstr *b);
+extern void bstr_print(FILE *fd, const struct bstr *b, int ascii);
+
+
+struct ematch
+{
+	struct bstr	*args;
+	int		index;
+	int		inverted;
+	int		relation;
+	int		child_ref;
+	struct ematch	*child;
+	struct ematch	*next;
+};
+
+static inline struct ematch * new_ematch(struct bstr *args, int inverted)
+{
+	struct ematch *e = calloc(1, sizeof(*e));
+
+	if (e == NULL)
+		return NULL;
+
+	e->args = args;
+	e->inverted = inverted;
+
+	return e;
+}
+
+extern void print_ematch_tree(const struct ematch *tree);
+
+
+struct ematch_util
+{
+	char			kind[EMATCHKINDSIZ];
+	int			kind_num;
+	int	(*parse_eopt)(struct nlmsghdr *,struct tcf_ematch_hdr *,
+			      struct bstr *);
+	int	(*print_eopt)(FILE *, struct tcf_ematch_hdr *, void *, int);
+	void	(*print_usage)(FILE *);
+	struct ematch_util	*next;
+};
+
+static inline int parse_layer(struct bstr *b)
+{
+	if (*((char *) b->data) == 'l')
+		return TCF_LAYER_LINK;
+	else if (*((char *) b->data) == 'n')
+		return TCF_LAYER_NETWORK;
+	else if (*((char *) b->data) == 't')
+		return TCF_LAYER_TRANSPORT;
+	else
+		return INT_MAX;
+}
+
+extern int em_parse_error(int err, struct bstr *args, struct bstr *carg,
+		   struct ematch_util *, char *fmt, ...);
+extern int print_ematch(FILE *, const struct rtattr *);
+extern int parse_ematch(int *, char ***, int, struct nlmsghdr *);
+
+#endif
diff --git a/tc/m_estimator.c b/tc/m_estimator.c
new file mode 100644
index 0000000..a9e5dbc
--- /dev/null
+++ b/tc/m_estimator.c
@@ -0,0 +1,64 @@
+/*
+ * m_estimator.c	Parse/print estimator module options.
+ *
+ *		This program is free software; you can u32istribute 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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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 est_help(void);
+
+static void est_help(void)
+{
+	fprintf(stderr, "Usage: ... estimator INTERVAL TIME-CONST\n");
+	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)
+{
+	int argc = *p_argc;
+	char **argv = *p_argv;
+	unsigned A, time_const;
+
+	NEXT_ARG();
+	if (est->ewma_log)
+		duparg("estimator", *argv);
+	if (matches(*argv, "help") == 0)
+		est_help();
+	if (get_time(&A, *argv))
+		invarg("estimator", "invalid estimator interval");
+	NEXT_ARG();
+	if (matches(*argv, "help") == 0)
+		est_help();
+	if (get_time(&time_const, *argv))
+		invarg("estimator", "invalid estimator time constant");
+	if (tc_setup_estimator(A, time_const, est) < 0) {
+		fprintf(stderr, "Error: estimator parameters are out of range.\n");
+		return -1;
+	}
+	if (show_raw)
+		fprintf(stderr, "[estimator i=%u e=%u]\n", est->interval, est->ewma_log);
+	*p_argc = argc;
+	*p_argv = argv;
+	return 0;
+}
diff --git a/tc/m_gact.c b/tc/m_gact.c
new file mode 100644
index 0000000..9f07851
--- /dev/null
+++ b/tc/m_gact.c
@@ -0,0 +1,254 @@
+/*
+ * m_gact.c		generic actions module
+ *
+ *		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 (hadi@cyberus.ca)
+ *
+ */
+
+#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_gact.h>
+
+/* define to turn on probablity stuff */
+
+#ifdef CONFIG_GACT_PROB
+static const char *prob_n2a(int p)
+{
+	if (p == PGACT_NONE)
+		return "none";
+	if (p == PGACT_NETRAND)
+		return "netrand";
+	if (p == PGACT_DETERM)
+		return "determ";
+	return "none";
+}
+#endif
+
+static void
+explain(void)
+{
+#ifdef CONFIG_GACT_PROB
+	fprintf(stderr, "Usage: ... gact <ACTION> [RAND] [INDEX]\n");
+	fprintf(stderr,
+		"Where: \tACTION := reclassify | drop | continue | pass \n"
+		        "\tRAND := random <RANDTYPE> <ACTION> <VAL>\n"
+		        "\tRANDTYPE := netrand | determ\n"
+			"\tVAL : = value not exceeding 10000\n"
+			"\tINDEX := index value used\n"
+			"\n");
+#else
+	fprintf(stderr, "Usage: ... gact <ACTION> [INDEX]\n");
+	fprintf(stderr,
+		"Where: \tACTION := reclassify | drop | continue | pass \n"
+		"\tINDEX := index value used\n"
+		"\n");
+#endif
+}
+
+
+static void
+usage(void)
+{
+	explain();
+	exit(-1);
+}
+
+int
+get_act(char ***argv_p)
+{
+	char **argv = *argv_p;
+
+	if (matches(*argv, "reclassify") == 0) {
+		return TC_ACT_RECLASSIFY;
+	} else if (matches(*argv, "drop") == 0 || matches(*argv, "shot") == 0) {
+		return TC_ACT_SHOT;
+	} else if (matches(*argv, "continue") == 0) {
+		return TC_ACT_UNSPEC;
+	} else if (matches(*argv, "pipe") == 0) {
+		return TC_ACT_PIPE;
+	} else if (matches(*argv, "pass") == 0 || matches(*argv, "ok") == 0)  {
+		return TC_ACT_OK;
+	} else {
+		fprintf(stderr,"bad action type %s\n",*argv);
+		return -10;
+	}
+}
+
+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;
+	int ok = 0;
+	int action = TC_POLICE_RECLASSIFY;
+	struct tc_gact p;
+#ifdef CONFIG_GACT_PROB
+	int rd = 0;
+	struct tc_gact_p pp;
+#endif
+	struct rtattr *tail;
+
+	memset(&p, 0, sizeof (p));
+	p.action = TC_POLICE_RECLASSIFY;
+
+	if (argc < 0)
+		return -1;
+
+
+	if (matches(*argv, "gact") == 0) {
+		ok++;
+	} else {
+		action = get_act(&argv);
+		if (action != -10) {
+			p.action = action;
+			ok++;
+		} else {
+			explain();
+			return action;
+		}
+	}
+
+	if (ok) {
+		argc--;
+		argv++;
+	}
+
+#ifdef CONFIG_GACT_PROB
+	if (ok && argc > 0) {
+		if (matches(*argv, "random") == 0) {
+			rd = 1;
+			NEXT_ARG();
+			if (matches(*argv, "netrand") == 0) {
+				NEXT_ARG();
+				pp.ptype = PGACT_NETRAND;
+			} else if  (matches(*argv, "determ") == 0) {
+				NEXT_ARG();
+				pp.ptype = PGACT_DETERM;
+			} else {
+				fprintf(stderr, "Illegal \"random type\"\n");
+				return -1;
+			}
+
+			action = get_act(&argv);
+			if (action != -10) { /* FIXME */
+				pp.paction = action;
+			} else {
+				explain();
+				return -1;
+			}
+			argc--;
+			argv++;
+			if (get_u16(&pp.pval, *argv, 10)) {
+				fprintf(stderr, "Illegal probability val 0x%x\n",pp.pval);
+				return -1;
+			}
+			if (pp.pval > 10000) {
+				fprintf(stderr, "Illegal probability val  0x%x\n",pp.pval);
+				return -1;
+			}
+			argc--;
+			argv++;
+		} else if (matches(*argv, "help") == 0) {
+				usage();
+		}
+	}
+#endif
+
+	if (argc > 0) {
+		if (matches(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&p.index, *argv, 10)) {
+				fprintf(stderr, "Illegal \"index\"\n");
+				return -1;
+			}
+			argc--;
+			argv++;
+			ok++;
+		} else if (matches(*argv, "help") == 0) {
+				usage();
+		}
+	}
+
+	if (!ok)
+		return -1;
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	addattr_l(n, MAX_MSG, TCA_GACT_PARMS, &p, sizeof (p));
+#ifdef CONFIG_GACT_PROB
+	if (rd) {
+		addattr_l(n, MAX_MSG, TCA_GACT_PROB, &pp, sizeof (pp));
+	}
+#endif
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+int
+print_gact(struct action_util *au,FILE * f, struct rtattr *arg)
+{
+	SPRINT_BUF(b1);
+#ifdef CONFIG_GACT_PROB
+	SPRINT_BUF(b2);
+	struct tc_gact_p *pp = NULL;
+	struct tc_gact_p pp_dummy;
+#endif
+	struct tc_gact *p = NULL;
+	struct rtattr *tb[TCA_GACT_MAX + 1];
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, TCA_GACT_MAX, arg);
+
+	if (tb[TCA_GACT_PARMS] == NULL) {
+		fprintf(f, "[NULL gact parameters]");
+		return -1;
+	}
+	p = RTA_DATA(tb[TCA_GACT_PARMS]);
+
+	fprintf(f, "gact action %s", action_n2a(p->action, b1, sizeof (b1)));
+#ifdef CONFIG_GACT_PROB
+	if (NULL != tb[TCA_GACT_PROB]) {
+		pp = RTA_DATA(tb[TCA_GACT_PROB]);
+	} else {
+		/* need to keep consistent output */
+		memset(&pp_dummy, 0, sizeof (pp_dummy));
+		pp = &pp_dummy;
+	}
+	fprintf(f, "\n\t random type %s %s val %d",prob_n2a(pp->ptype), action_n2a(pp->paction, b2, sizeof (b2)), pp->pval);
+#endif
+	fprintf(f, "\n\t index %d ref %d bind %d",p->index, p->refcnt, p->bindcnt);
+	if (show_stats) {
+		if (tb[TCA_GACT_TM]) {
+			struct tcf_t *tm = RTA_DATA(tb[TCA_GACT_TM]);
+			print_tm(f,tm);
+		}
+	}
+	fprintf(f, "\n ");
+	return 0;
+}
+
+struct action_util gact_action_util = {
+	.id = "gact",
+	.parse_aopt = parse_gact,
+	.print_aopt = print_gact,
+};
diff --git a/tc/m_ipt.c b/tc/m_ipt.c
new file mode 100644
index 0000000..99d9965
--- /dev/null
+++ b/tc/m_ipt.c
@@ -0,0 +1,620 @@
+/*
+ * m_ipt.c	iptables based targets
+ * 		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
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ *
+ * Authors:  J Hadi Salim (hadi@cyberus.ca)
+ */
+
+#include <syslog.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <linux/if.h>
+#include <iptables.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_ipt.h>
+#include <stdio.h>
+#include <dlfcn.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+static const char *pname = "tc-ipt";
+static const char *tname = "mangle";
+static const char *pversion = "0.1";
+
+static const char *ipthooks[] = {
+	"NF_IP_PRE_ROUTING",
+	"NF_IP_LOCAL_IN",
+	"NF_IP_FORWARD",
+	"NF_IP_LOCAL_OUT",
+	"NF_IP_POST_ROUTING",
+};
+
+static struct option original_opts[] = {
+	{"jump", 1, 0, 'j'},
+	{0, 0, 0, 0}
+};
+
+static struct iptables_target *t_list = NULL;
+static struct option *opts = original_opts;
+static unsigned int global_option_offset = 0;
+#define OPTION_OFFSET 256
+
+char *lib_dir;
+
+void
+register_target(struct iptables_target *me)
+{
+/*      fprintf(stderr, "\nDummy register_target %s \n", me->name);
+*/
+	me->next = t_list;
+	t_list = me;
+
+}
+
+void
+xtables_register_target(struct iptables_target *me)
+{
+	me->next = t_list;
+	t_list = me;
+}
+
+void
+exit_tryhelp(int status)
+{
+	fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
+		pname, pname);
+	exit(status);
+}
+
+void
+exit_error(enum exittype status, char *msg, ...)
+{
+	va_list args;
+
+	va_start(args, msg);
+	fprintf(stderr, "%s v%s: ", pname, pversion);
+	vfprintf(stderr, msg, args);
+	va_end(args);
+	fprintf(stderr, "\n");
+	if (status == PARAMETER_PROBLEM)
+		exit_tryhelp(status);
+	if (status == VERSION_PROBLEM)
+		fprintf(stderr,
+			"Perhaps iptables or your kernel needs to be upgraded.\n");
+	exit(status);
+}
+
+/* stolen from iptables 1.2.11
+They should really have them as a library so i can link to them
+Email them next time i remember
+*/
+
+char *
+addr_to_dotted(const struct in_addr *addrp)
+{
+	static char buf[20];
+	const unsigned char *bytep;
+
+	bytep = (const unsigned char *) &(addrp->s_addr);
+	sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
+	return buf;
+}
+
+int string_to_number_ll(const char *s, unsigned long long min,
+			unsigned long long max,
+		 unsigned long long *ret)
+{
+	unsigned long long number;
+	char *end;
+
+	/* Handle hex, octal, etc. */
+	errno = 0;
+	number = strtoull(s, &end, 0);
+	if (*end == '\0' && end != s) {
+		/* we parsed a number, let's see if we want this */
+		if (errno != ERANGE && min <= number && (!max || number <= max)) {
+			*ret = number;
+			return 0;
+		}
+	}
+	return -1;
+}
+
+int string_to_number_l(const char *s, unsigned long min, unsigned long max,
+		       unsigned long *ret)
+{
+	int result;
+	unsigned long long number;
+
+	result = string_to_number_ll(s, min, max, &number);
+	*ret = (unsigned long)number;
+
+	return result;
+}
+
+int string_to_number(const char *s, unsigned int min, unsigned int max,
+		unsigned int *ret)
+{
+	int result;
+	unsigned long number;
+
+	result = string_to_number_l(s, min, max, &number);
+	*ret = (unsigned int)number;
+
+	return result;
+}
+
+static void free_opts(struct option *local_opts)
+{
+	if (local_opts != original_opts) {
+		free(local_opts);
+		opts = original_opts;
+		global_option_offset = 0;
+	}
+}
+
+static struct option *
+merge_options(struct option *oldopts, const struct option *newopts,
+	      unsigned int *option_offset)
+{
+	struct option *merge;
+	unsigned int num_old, num_new, i;
+
+	for (num_old = 0; oldopts[num_old].name; num_old++) ;
+	for (num_new = 0; newopts[num_new].name; num_new++) ;
+
+	*option_offset = global_option_offset + OPTION_OFFSET;
+
+	merge = malloc(sizeof (struct option) * (num_new + num_old + 1));
+	memcpy(merge, oldopts, num_old * sizeof (struct option));
+	for (i = 0; i < num_new; i++) {
+		merge[num_old + i] = newopts[i];
+		merge[num_old + i].val += *option_offset;
+	}
+	memset(merge + num_old + num_new, 0, sizeof (struct option));
+
+	return merge;
+}
+
+static void *
+fw_calloc(size_t count, size_t size)
+{
+	void *p;
+
+	if ((p = (void *) calloc(count, size)) == NULL) {
+		perror("iptables: calloc failed");
+		exit(1);
+	}
+	return p;
+}
+
+static struct iptables_target *
+find_t(char *name)
+{
+	struct iptables_target *m;
+	for (m = t_list; m; m = m->next) {
+		if (strcmp(m->name, name) == 0)
+			return m;
+	}
+
+	return NULL;
+}
+
+static struct iptables_target *
+get_target_name(const char *name)
+{
+	void *handle;
+	char *error;
+	char *new_name, *lname;
+	struct iptables_target *m;
+	char path[strlen(lib_dir) + sizeof ("/libipt_.so") + strlen(name)];
+
+#ifdef NO_SHARED_LIBS
+	return NULL;
+#endif
+
+	new_name = malloc(strlen(name) + 1);
+	lname = malloc(strlen(name) + 1);
+	if (new_name)
+		memset(new_name, '\0', strlen(name) + 1);
+	else
+		exit_error(PARAMETER_PROBLEM, "get_target_name");
+
+	if (lname)
+		memset(lname, '\0', strlen(name) + 1);
+	else
+		exit_error(PARAMETER_PROBLEM, "get_target_name");
+
+	strcpy(new_name, name);
+	strcpy(lname, name);
+
+	if (isupper(lname[0])) {
+		int i;
+		for (i = 0; i < strlen(name); i++) {
+			lname[i] = tolower(lname[i]);
+		}
+	}
+
+	if (islower(new_name[0])) {
+		int i;
+		for (i = 0; i < strlen(new_name); i++) {
+			new_name[i] = toupper(new_name[i]);
+		}
+	}
+
+	/* try libxt_xx first */
+	sprintf(path, "%s/libxt_%s.so", lib_dir, new_name);
+	handle = dlopen(path, RTLD_LAZY);
+	if (!handle) {
+		/* try libipt_xx next */
+		sprintf(path, "%s/libipt_%s.so", lib_dir, new_name);
+		handle = dlopen(path, RTLD_LAZY);
+
+		if (!handle) {
+			sprintf(path, "%s/libxt_%s.so", lib_dir , lname);
+			handle = dlopen(path, RTLD_LAZY);
+		}
+
+		if (!handle) {
+			sprintf(path, "%s/libipt_%s.so", lib_dir , lname);
+			handle = dlopen(path, RTLD_LAZY);
+		}
+		/* ok, lets give up .. */
+		if (!handle) {
+			fputs(dlerror(), stderr);
+			printf("\n");
+			free(new_name);
+			return NULL;
+		}
+	}
+
+	m = dlsym(handle, new_name);
+	if ((error = dlerror()) != NULL) {
+		m = (struct iptables_target *) dlsym(handle, lname);
+		if ((error = dlerror()) != NULL) {
+			m = find_t(new_name);
+			if (NULL == m) {
+				m = find_t(lname);
+				if (NULL == m) {
+					fputs(error, stderr);
+					fprintf(stderr, "\n");
+					dlclose(handle);
+					free(new_name);
+					return NULL;
+				}
+			}
+		}
+	}
+
+	free(new_name);
+	return m;
+}
+
+
+struct in_addr *dotted_to_addr(const char *dotted)
+{
+	static struct in_addr addr;
+	unsigned char *addrp;
+	char *p, *q;
+	unsigned int onebyte;
+	int i;
+	char buf[20];
+
+	/* copy dotted string, because we need to modify it */
+	strncpy(buf, dotted, sizeof (buf) - 1);
+	addrp = (unsigned char *) &(addr.s_addr);
+
+	p = buf;
+	for (i = 0; i < 3; i++) {
+		if ((q = strchr(p, '.')) == NULL)
+			return (struct in_addr *) NULL;
+
+		*q = '\0';
+		if (string_to_number(p, 0, 255, &onebyte) == -1)
+			return (struct in_addr *) NULL;
+
+		addrp[i] = (unsigned char) onebyte;
+		p = q + 1;
+	}
+
+	/* we've checked 3 bytes, now we check the last one */
+	if (string_to_number(p, 0, 255, &onebyte) == -1)
+		return (struct in_addr *) NULL;
+
+	addrp[3] = (unsigned char) onebyte;
+
+	return &addr;
+}
+
+static void set_revision(char *name, u_int8_t revision)
+{
+	/* Old kernel sources don't have ".revision" field,
+	*  but we stole a byte from name. */
+	name[IPT_FUNCTION_MAXNAMELEN - 2] = '\0';
+	name[IPT_FUNCTION_MAXNAMELEN - 1] = revision;
+}
+
+/*
+ * we may need to check for version mismatch
+*/
+int
+build_st(struct iptables_target *target, struct ipt_entry_target *t)
+{
+	unsigned int nfcache = 0;
+
+	if (target) {
+		size_t size;
+
+		size =
+		    IPT_ALIGN(sizeof (struct ipt_entry_target)) + target->size;
+
+		if (NULL == t) {
+			target->t = fw_calloc(1, size);
+			target->t->u.target_size = size;
+
+			if (target->init != NULL)
+				target->init(target->t, &nfcache);
+			set_revision(target->t->u.user.name, target->revision);
+		} else {
+			target->t = t;
+		}
+		strcpy(target->t->u.user.name, target->name);
+		return 0;
+	}
+
+	return -1;
+}
+
+static int parse_ipt(struct action_util *a,int *argc_p,
+		     char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+	struct iptables_target *m = NULL;
+	struct ipt_entry fw;
+	struct rtattr *tail;
+	int c;
+	int rargc = *argc_p;
+	char **argv = *argv_p;
+	int argc = 0, iargc = 0;
+	char k[16];
+	int res = -1;
+	int size = 0;
+	int iok = 0, ok = 0;
+	__u32 hook = 0, index = 0;
+	res = 0;
+
+	lib_dir = getenv("IPTABLES_LIB_DIR");
+	if (!lib_dir)
+		lib_dir = IPT_LIB_DIR;
+
+	{
+		int i;
+		for (i = 0; i < rargc; i++) {
+			if (NULL == argv[i] || 0 == strcmp(argv[i], "action")) {
+				break;
+			}
+		}
+		iargc = argc = i;
+	}
+
+	if (argc <= 2) {
+		fprintf(stderr,"bad arguements to ipt %d vs %d \n", argc, rargc);
+		return -1;
+	}
+
+	while (1) {
+		c = getopt_long(argc, argv, "j:", opts, NULL);
+		if (c == -1)
+			break;
+		switch (c) {
+		case 'j':
+			m = get_target_name(optarg);
+			if (NULL != m) {
+
+				if (0 > build_st(m, NULL)) {
+					printf(" %s error \n", m->name);
+					return -1;
+				}
+				opts =
+				    merge_options(opts, m->extra_opts,
+						  &m->option_offset);
+			} else {
+				fprintf(stderr," failed to find target %s\n\n", optarg);
+				return -1;
+			}
+			ok++;
+			break;
+
+		default:
+			memset(&fw, 0, sizeof (fw));
+			if (m) {
+				m->parse(c - m->option_offset, argv, 0,
+					 &m->tflags, NULL, &m->t);
+			} else {
+				fprintf(stderr," failed to find target %s\n\n", optarg);
+				return -1;
+
+			}
+			ok++;
+			break;
+
+		}
+	}
+
+	if (iargc > optind) {
+		if (matches(argv[optind], "index") == 0) {
+			if (get_u32(&index, argv[optind + 1], 10)) {
+				fprintf(stderr, "Illegal \"index\"\n");
+				free_opts(opts);
+				return -1;
+			}
+			iok++;
+
+			optind += 2;
+		}
+	}
+
+	if (!ok && !iok) {
+		fprintf(stderr," ipt Parser BAD!! (%s)\n", *argv);
+		return -1;
+	}
+
+	/* check that we passed the correct parameters to the target */
+	if (m)
+		m->final_check(m->tflags);
+
+	{
+		struct tcmsg *t = NLMSG_DATA(n);
+		if (t->tcm_parent != TC_H_ROOT
+		    && t->tcm_parent == TC_H_MAJ(TC_H_INGRESS)) {
+			hook = NF_IP_PRE_ROUTING;
+		} else {
+			hook = NF_IP_POST_ROUTING;
+		}
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	fprintf(stdout, "tablename: %s hook: %s\n ", tname, ipthooks[hook]);
+	fprintf(stdout, "\ttarget: ");
+
+	if (m)
+		m->print(NULL, m->t, 0);
+	fprintf(stdout, " index %d\n", index);
+
+	if (strlen(tname) > 16) {
+		size = 16;
+		k[15] = 0;
+	} else {
+		size = 1 + strlen(tname);
+	}
+	strncpy(k, tname, size);
+
+	addattr_l(n, MAX_MSG, TCA_IPT_TABLE, k, size);
+	addattr_l(n, MAX_MSG, TCA_IPT_HOOK, &hook, 4);
+	addattr_l(n, MAX_MSG, TCA_IPT_INDEX, &index, 4);
+	if (m)
+		addattr_l(n, MAX_MSG, TCA_IPT_TARG, m->t, m->t->u.target_size);
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+
+	argc -= optind;
+	argv += optind;
+	*argc_p = rargc - iargc;
+	*argv_p = argv;
+
+	optind = 0;
+	free_opts(opts);
+	/* Clear flags if target will be used again */
+        m->tflags=0;
+        m->used=0;
+	/* Free allocated memory */
+        if (m->t)
+            free(m->t);
+
+
+	return 0;
+
+}
+
+static int
+print_ipt(struct action_util *au,FILE * f, struct rtattr *arg)
+{
+	struct rtattr *tb[TCA_IPT_MAX + 1];
+	struct ipt_entry_target *t = NULL;
+
+	if (arg == NULL)
+		return -1;
+
+	lib_dir = getenv("IPTABLES_LIB_DIR");
+	if (!lib_dir)
+		lib_dir = IPT_LIB_DIR;
+
+	parse_rtattr_nested(tb, TCA_IPT_MAX, arg);
+
+	if (tb[TCA_IPT_TABLE] == NULL) {
+		fprintf(f, "[NULL ipt table name ] assuming mangle ");
+	} else {
+		fprintf(f, "tablename: %s ",
+			(char *) RTA_DATA(tb[TCA_IPT_TABLE]));
+	}
+
+	if (tb[TCA_IPT_HOOK] == NULL) {
+		fprintf(f, "[NULL ipt hook name ]\n ");
+		return -1;
+	} else {
+		__u32 hook;
+		hook = *(__u32 *) RTA_DATA(tb[TCA_IPT_HOOK]);
+		fprintf(f, " hook: %s \n", ipthooks[hook]);
+	}
+
+	if (tb[TCA_IPT_TARG] == NULL) {
+		fprintf(f, "\t[NULL ipt target parameters ] \n");
+		return -1;
+	} else {
+		struct iptables_target *m = NULL;
+		t = RTA_DATA(tb[TCA_IPT_TARG]);
+		m = get_target_name(t->u.user.name);
+		if (NULL != m) {
+			if (0 > build_st(m, t)) {
+				fprintf(stderr, " %s error \n", m->name);
+				return -1;
+			}
+
+			opts =
+			    merge_options(opts, m->extra_opts,
+					  &m->option_offset);
+		} else {
+			fprintf(stderr, " failed to find target %s\n\n",
+				t->u.user.name);
+			return -1;
+		}
+		fprintf(f, "\ttarget ");
+		m->print(NULL, m->t, 0);
+		if (tb[TCA_IPT_INDEX] == NULL) {
+			fprintf(f, " [NULL ipt target index ]\n");
+		} else {
+			__u32 index;
+			index = *(__u32 *) RTA_DATA(tb[TCA_IPT_INDEX]);
+			fprintf(f, " \n\tindex %d", index);
+		}
+
+		if (tb[TCA_IPT_CNT]) {
+			struct tc_cnt *c  = RTA_DATA(tb[TCA_IPT_CNT]);;
+			fprintf(f, " ref %d bind %d", c->refcnt, c->bindcnt);
+		}
+		if (show_stats) {
+			if (tb[TCA_IPT_TM]) {
+				struct tcf_t *tm = RTA_DATA(tb[TCA_IPT_TM]);
+				print_tm(f,tm);
+			}
+		}
+		fprintf(f, " \n");
+
+	}
+	free_opts(opts);
+
+	return 0;
+}
+
+struct action_util ipt_action_util = {
+        .id = "ipt",
+        .parse_aopt = parse_ipt,
+        .print_aopt = print_ipt,
+};
+
diff --git a/tc/m_mirred.c b/tc/m_mirred.c
new file mode 100644
index 0000000..226df4d
--- /dev/null
+++ b/tc/m_mirred.c
@@ -0,0 +1,301 @@
+/*
+ * m_egress.c		ingress/egress packet mirror/redir actions module
+ *
+ *		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 (hadi@cyberus.ca)
+ *
+ * TODO: Add Ingress support
+ *
+ */
+
+#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 "tc_common.h"
+#include <linux/tc_act/tc_mirred.h>
+
+int mirred_d = 1;
+
+static void
+explain(void)
+{
+	fprintf(stderr, "Usage: mirred <DIRECTION> <ACTION> [index INDEX] <dev DEVICENAME> \n");
+	fprintf(stderr, "where: \n");
+	fprintf(stderr, "\tDIRECTION := <ingress | egress>\n");
+	fprintf(stderr, "\tACTION := <mirror | redirect>\n");
+	fprintf(stderr, "\tINDEX  is the specific policy instance id\n");
+	fprintf(stderr, "\tDEVICENAME is the devicename \n");
+
+}
+
+static void
+usage(void)
+{
+	explain();
+	exit(-1);
+}
+
+char *mirred_n2a(int action)
+{
+	switch (action) {
+	case TCA_EGRESS_REDIR:
+		return "Egress Redirect";
+	case TCA_INGRESS_REDIR:
+		return "Ingress Redirect";
+	case TCA_EGRESS_MIRROR:
+		return "Egress Mirror";
+	case TCA_INGRESS_MIRROR:
+		return "Ingress Mirror";
+	default:
+		return "unknown";
+	}
+}
+
+int
+parse_egress(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int ok = 0, iok = 0, mirror=0,redir=0;
+	struct tc_mirred p;
+	struct rtattr *tail;
+	char d[16];
+
+	memset(d,0,sizeof(d)-1);
+	memset(&p,0,sizeof(struct tc_mirred));
+
+	while (argc > 0) {
+
+		if (matches(*argv, "action") == 0) {
+			break;
+		} else if (matches(*argv, "egress") == 0) {
+			NEXT_ARG();
+			ok++;
+			continue;
+		} else {
+
+			if (matches(*argv, "index") == 0) {
+				NEXT_ARG();
+				if (get_u32(&p.index, *argv, 10)) {
+					fprintf(stderr, "Illegal \"index\"\n");
+					return -1;
+				}
+				iok++;
+				if (!ok) {
+					argc--;
+					argv++;
+					break;
+				}
+			} else if(!ok) {
+				fprintf(stderr, "was expecting egress (%s)\n", *argv);
+				break;
+
+			} else if (!mirror && matches(*argv, "mirror") == 0) {
+				mirror=1;
+				if (redir) {
+					fprintf(stderr, "Cant have both mirror and redir\n");
+					return -1;
+				}
+				p.eaction = TCA_EGRESS_MIRROR;
+				p.action = TC_ACT_PIPE;
+				ok++;
+			} else if (!redir && matches(*argv, "redirect") == 0) {
+				redir=1;
+				if (mirror) {
+					fprintf(stderr, "Cant have both mirror and redir\n");
+					return -1;
+				}
+				p.eaction = TCA_EGRESS_REDIR;
+				p.action = TC_ACT_STOLEN;
+				ok++;
+			} else if ((redir || mirror) && matches(*argv, "dev") == 0) {
+				NEXT_ARG();
+				if (strlen(d))
+					duparg("dev", *argv);
+
+				strncpy(d, *argv, sizeof(d)-1);
+				argc--;
+				argv++;
+
+				break;
+
+			}
+		}
+
+		NEXT_ARG();
+	}
+
+	if (!ok && !iok) {
+		return -1;
+	}
+
+
+
+	if (d[0])  {
+		int idx;
+		ll_init_map(&rth);
+
+		if ((idx = ll_name_to_index(d)) == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n", d);
+			return -1;
+		}
+
+		p.ifindex = idx;
+	}
+
+
+	if (argc && p.eaction == TCA_EGRESS_MIRROR) {
+
+		if (matches(*argv, "reclassify") == 0) {
+			p.action = TC_POLICE_RECLASSIFY;
+			NEXT_ARG();
+		} else if (matches(*argv, "pipe") == 0) {
+			p.action = TC_POLICE_PIPE;
+			NEXT_ARG();
+		} else if (matches(*argv, "drop") == 0 ||
+			   matches(*argv, "shot") == 0) {
+			p.action = TC_POLICE_SHOT;
+			NEXT_ARG();
+		} else if (matches(*argv, "continue") == 0) {
+			p.action = TC_POLICE_UNSPEC;
+			NEXT_ARG();
+		} else if (matches(*argv, "pass") == 0) {
+			p.action = TC_POLICE_OK;
+			NEXT_ARG();
+		}
+
+	}
+
+	if (argc) {
+		if (iok && matches(*argv, "index") == 0) {
+			fprintf(stderr, "mirred: Illegal double index\n");
+			return -1;
+		} else {
+			if (matches(*argv, "index") == 0) {
+				NEXT_ARG();
+				if (get_u32(&p.index, *argv, 10)) {
+					fprintf(stderr, "mirred: Illegal \"index\"\n");
+					return -1;
+				}
+				argc--;
+				argv++;
+			}
+		}
+	}
+
+	if (mirred_d)
+		fprintf(stdout, "Action %d device %s ifindex %d\n",p.action, d,p.ifindex);
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	addattr_l(n, MAX_MSG, TCA_MIRRED_PARMS, &p, sizeof (p));
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+
+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);
+		return -1;
+	}
+
+	if (matches(*argv, "mirred") == 0) {
+		NEXT_ARG();
+	} else {
+		fprintf(stderr,"mirred bad arguement %s\n", *argv);
+		return -1;
+	}
+
+
+	if (matches(*argv, "egress") == 0 || matches(*argv, "index") == 0) {
+		int ret = parse_egress(a, &argc, &argv, tca_id, n);
+		if (ret == 0) {
+			*argc_p = argc;
+			*argv_p = argv;
+			return 0;
+		}
+
+	} else if (matches(*argv, "ingress") == 0) {
+		fprintf(stderr,"mirred ingress not supported at the moment\n");
+	} else if (matches(*argv, "help") == 0) {
+		usage();
+	} else {
+		fprintf(stderr,"mirred option not supported %s\n", *argv);
+	}
+
+	return -1;
+
+}
+
+int
+print_mirred(struct action_util *au,FILE * f, struct rtattr *arg)
+{
+	struct tc_mirred *p;
+	struct rtattr *tb[TCA_MIRRED_MAX + 1];
+	const char *dev;
+	SPRINT_BUF(b1);
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, TCA_MIRRED_MAX, arg);
+
+	if (tb[TCA_MIRRED_PARMS] == NULL) {
+		fprintf(f, "[NULL mirred parameters]");
+		return -1;
+	}
+	p = RTA_DATA(tb[TCA_MIRRED_PARMS]);
+
+	/*
+	ll_init_map(&rth);
+	*/
+
+
+	if ((dev = ll_index_to_name(p->ifindex)) == 0) {
+		fprintf(stderr, "Cannot find device %d\n", p->ifindex);
+		return -1;
+	}
+
+	fprintf(f, "mirred (%s to device %s) %s", mirred_n2a(p->eaction), dev,action_n2a(p->action, b1, sizeof (b1)));
+
+	fprintf(f, "\n ");
+	fprintf(f, "\tindex %d ref %d bind %d",p->index,p->refcnt,p->bindcnt);
+
+	if (show_stats) {
+		if (tb[TCA_MIRRED_TM]) {
+			struct tcf_t *tm = RTA_DATA(tb[TCA_MIRRED_TM]);
+			print_tm(f,tm);
+		}
+	}
+	fprintf(f, "\n ");
+	return 0;
+}
+
+struct action_util mirred_action_util = {
+	.id = "mirred",
+	.parse_aopt = parse_mirred,
+	.print_aopt = print_mirred,
+};
diff --git a/tc/m_nat.c b/tc/m_nat.c
new file mode 100644
index 0000000..01ec032
--- /dev/null
+++ b/tc/m_nat.c
@@ -0,0 +1,212 @@
+/*
+ * m_nat.c		NAT module
+ *
+ *		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:	Herbert Xu <herbert@gondor.apana.org.au>
+ *
+ */
+
+#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_nat.h>
+
+static void
+explain(void)
+{
+	fprintf(stderr, "Usage: ... nat NAT\n"
+			"NAT := DIRECTION OLD NEW\n"
+			"DIRECTION := { ingress | egress }\n"
+			"OLD := PREFIX\n"
+			"NEW := ADDRESS\n");
+}
+
+static void
+usage(void)
+{
+	explain();
+	exit(-1);
+}
+
+static int
+parse_nat_args(int *argc_p, char ***argv_p,struct tc_nat *sel)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	inet_prefix addr;
+
+	if (argc <= 0)
+		return -1;
+
+	if (matches(*argv, "egress") == 0)
+		sel->flags |= TCA_NAT_FLAG_EGRESS;
+	else if (matches(*argv, "ingress") != 0)
+		goto bad_val;
+
+	NEXT_ARG();
+
+	if (get_prefix_1(&addr, *argv, AF_INET))
+		goto bad_val;
+
+	sel->old_addr = addr.data[0];
+	sel->mask = htonl(~0u << (32 - addr.bitlen));
+
+	NEXT_ARG();
+
+	if (get_prefix_1(&addr, *argv, AF_INET))
+		goto bad_val;
+
+	sel->new_addr = addr.data[0];
+
+	argc--;
+	argv++;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+
+bad_val:
+	return -1;
+}
+
+static int
+parse_nat(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+	struct tc_nat sel;
+
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int ok = 0;
+	struct rtattr *tail;
+
+	memset(&sel, 0, sizeof(sel));
+
+	while (argc > 0) {
+		if (matches(*argv, "nat") == 0) {
+			NEXT_ARG();
+			if (parse_nat_args(&argc, &argv, &sel)) {
+				fprintf(stderr, "Illegal nat construct (%s) \n",
+					*argv);
+				explain();
+				return -1;
+			}
+			ok++;
+			continue;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			break;
+		}
+
+	}
+
+	if (!ok) {
+		explain();
+		return -1;
+	}
+
+	if (argc) {
+		if (matches(*argv, "reclassify") == 0) {
+			sel.action = TC_ACT_RECLASSIFY;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "pipe") == 0) {
+			sel.action = TC_ACT_PIPE;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "drop") == 0 ||
+			matches(*argv, "shot") == 0) {
+			sel.action = TC_ACT_SHOT;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "continue") == 0) {
+			sel.action = TC_ACT_UNSPEC;
+			argc--;
+			argv++;
+		} else if (matches(*argv, "pass") == 0) {
+			sel.action = TC_ACT_OK;
+			argc--;
+			argv++;
+		}
+	}
+
+	if (argc) {
+		if (matches(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&sel.index, *argv, 10)) {
+				fprintf(stderr, "Pedit: Illegal \"index\"\n");
+				return -1;
+			}
+			argc--;
+			argv++;
+		}
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	addattr_l(n, MAX_MSG, TCA_NAT_PARMS, &sel, sizeof(sel));
+	tail->rta_len = (char *)NLMSG_TAIL(n) - (char *)tail;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+static int
+print_nat(struct action_util *au,FILE * f, struct rtattr *arg)
+{
+	struct tc_nat *sel;
+	struct rtattr *tb[TCA_NAT_MAX + 1];
+	char buf1[256];
+	char buf2[256];
+	SPRINT_BUF(buf3);
+	int len;
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, TCA_NAT_MAX, arg);
+
+	if (tb[TCA_NAT_PARMS] == NULL) {
+		fprintf(f, "[NULL nat parameters]");
+		return -1;
+	}
+	sel = RTA_DATA(tb[TCA_NAT_PARMS]);
+
+	len = ffs(sel->mask);
+	len = len ? 33 - len : 0;
+
+	fprintf(f, " nat %s %s/%d %s %s", sel->flags & TCA_NAT_FLAG_EGRESS ?
+					  "egress" : "ingress",
+		format_host(AF_INET, 4, &sel->old_addr, buf1, sizeof(buf1)),
+		len,
+		format_host(AF_INET, 4, &sel->new_addr, buf2, sizeof(buf2)),
+		action_n2a(sel->action, buf3, sizeof (buf3)));
+
+	if (show_stats) {
+		if (tb[TCA_NAT_TM]) {
+			struct tcf_t *tm = RTA_DATA(tb[TCA_NAT_TM]);
+			print_tm(f,tm);
+		}
+	}
+
+	return 0;
+}
+
+struct action_util nat_action_util = {
+	.id = "nat",
+	.parse_aopt = parse_nat,
+	.print_aopt = print_nat,
+};
diff --git a/tc/m_pedit.c b/tc/m_pedit.c
new file mode 100644
index 0000000..7499846
--- /dev/null
+++ b/tc/m_pedit.c
@@ -0,0 +1,608 @@
+/*
+ * m_pedit.c		generic packet editor actions module
+ *
+ *		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 (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.
+ *
+ */
+
+#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 <dlfcn.h>
+#include "utils.h"
+#include "tc_util.h"
+#include "m_pedit.h"
+
+static struct m_pedit_util *pedit_list;
+int pedit_debug = 1;
+
+static void
+explain(void)
+{
+	fprintf(stderr, "Usage: ... pedit munge <MUNGE>\n");
+	fprintf(stderr,
+		"Where: MUNGE := <RAW>|<LAYERED>\n"
+		"\t<RAW>:= <OFFSETC>[ATC]<CMD>\n "
+		"\t\tOFFSETC:= offset <offval> <u8|u16|u32>\n "
+		"\t\tATC:= at <atval> offmask <maskval> shift <shiftval>\n "
+		"\t\tNOTE: offval is byte offset, must be multiple of 4\n "
+		"\t\tNOTE: maskval is a 32 bit hex number\n "
+		"\t\tNOTE: shiftval is a is a shift value\n "
+		"\t\tCMD:= clear | invert | set <setval>| retain\n "
+		"\t<LAYERED>:= ip <ipdata> | ip6 <ip6data> \n "
+		" \t\t| udp <udpdata> | tcp <tcpdata> | icmp <icmpdata> \n"
+		"For Example usage look at the examples directory\n");
+
+}
+
+static void
+usage(void)
+{
+	explain();
+	exit(-1);
+}
+
+static int
+pedit_parse_nopopt (int *argc_p, char ***argv_p,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc) {
+		fprintf(stderr, "Unknown action  hence option \"%s\" is unparsable\n", *argv);
+			return -1;
+	}
+
+	return 0;
+
+}
+
+struct m_pedit_util
+*get_pedit_kind(char *str)
+{
+	static void *pBODY;
+	void *dlh;
+	char buf[256];
+	struct  m_pedit_util *p;
+
+	for (p = pedit_list; p; p = p->next) {
+		if (strcmp(p->id, str) == 0)
+			return p;
+	}
+
+	snprintf(buf, sizeof(buf), "p_%s.so", str);
+	dlh = dlopen(buf, RTLD_LAZY);
+	if (dlh == NULL) {
+		dlh = pBODY;
+		if (dlh == NULL) {
+			dlh = pBODY = dlopen(NULL, RTLD_LAZY);
+			if (dlh == NULL)
+				goto noexist;
+		}
+	}
+
+	snprintf(buf, sizeof(buf), "p_pedit_%s", str);
+	p = dlsym(dlh, buf);
+	if (p == NULL)
+		goto noexist;
+
+reg:
+	p->next = pedit_list;
+	pedit_list = p;
+	return p;
+
+noexist:
+	p = malloc(sizeof(*p));
+	if (p) {
+		memset(p, 0, sizeof(*p));
+		strncpy(p->id, str, sizeof(p->id)-1);
+		p->parse_peopt = pedit_parse_nopopt;
+		goto reg;
+	}
+	return p;
+}
+
+int
+pack_key(struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	int hwm = sel->nkeys;
+
+	if (hwm >= MAX_OFFS)
+		return -1;
+
+	if (tkey->off % 4) {
+		fprintf(stderr, "offsets MUST be in 32 bit boundaries\n");
+		return -1;
+	}
+
+	sel->keys[hwm].val = tkey->val;
+	sel->keys[hwm].mask = tkey->mask;
+	sel->keys[hwm].off = tkey->off;
+	sel->keys[hwm].at = tkey->at;
+	sel->keys[hwm].offmask = tkey->offmask;
+	sel->keys[hwm].shift = tkey->shift;
+	sel->nkeys++;
+	return 0;
+}
+
+
+int
+pack_key32(__u32 retain,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	if (tkey->off > (tkey->off & ~3)) {
+		fprintf(stderr,
+			"pack_key32: 32 bit offsets must begin in 32bit boundaries\n");
+		return -1;
+	}
+
+	tkey->val = htonl(tkey->val & retain);
+	tkey->mask = htonl(tkey->mask | ~retain);
+	/* jamal remove this - it is not necessary given the if check above */
+	tkey->off &= ~3;
+	return pack_key(sel,tkey);
+}
+
+int
+pack_key16(__u32 retain,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	int ind = 0, stride = 0;
+	__u32 m[4] = {0xFFFF0000,0xFF0000FF,0x0000FFFF};
+
+	if (0 > tkey->off) {
+		ind = tkey->off + 1;
+		if (0 > ind)
+			ind = -1*ind;
+	} else {
+		ind = tkey->off;
+	}
+
+	if (tkey->val > 0xFFFF || tkey->mask > 0xFFFF) {
+		fprintf(stderr, "pack_key16 bad value\n");
+		return -1;
+	}
+
+	ind = tkey->off & 3;
+
+	if (0 > ind || 2 < ind) {
+		fprintf(stderr, "pack_key16 bad index value %d\n",ind);
+		return -1;
+	}
+
+	stride = 8 * ind;
+	tkey->val = htons(tkey->val);
+	if (stride > 0) {
+		tkey->val <<= stride;
+		tkey->mask <<= stride;
+		retain <<= stride;
+	}
+	tkey->mask = retain|m[ind];
+
+	tkey->off &= ~3;
+
+	if (pedit_debug)
+		printf("pack_key16: Final val %08x mask %08x \n",tkey->val,tkey->mask);
+	return pack_key(sel,tkey);
+
+}
+
+int
+pack_key8(__u32 retain,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	int ind = 0, stride = 0;
+	__u32 m[4] = {0xFFFFFF00,0xFFFF00FF,0xFF00FFFF,0x00FFFFFF};
+
+	if (0 > tkey->off) {
+		ind = tkey->off + 1;
+		if (0 > ind)
+			ind = -1*ind;
+	} else {
+		ind = tkey->off;
+	}
+
+	if (tkey->val > 0xFF || tkey->mask > 0xFF) {
+		fprintf(stderr, "pack_key8 bad value (val %x mask %x\n", tkey->val, tkey->mask);
+		return -1;
+	}
+
+	ind = tkey->off & 3;
+	stride = 8 * ind;
+	tkey->val <<= stride;
+	tkey->mask <<= stride;
+	retain <<= stride;
+	tkey->mask = retain|m[ind];
+	tkey->off &= ~3;
+
+	if (pedit_debug)
+		printf("pack_key8: Final word off %d  val %08x mask %08x \n",tkey->off , tkey->val,tkey->mask);
+	return pack_key(sel,tkey);
+}
+
+int
+parse_val(int *argc_p, char ***argv_p, __u32 * val, int type)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc <= 0)
+		return -1;
+
+	if (TINT == type)
+		return get_integer((int *) val, *argv, 0);
+
+	if (TU32 == type)
+		return get_u32(val, *argv, 0);
+
+	if (TIPV4 == type) {
+		inet_prefix addr;
+		if (get_prefix_1(&addr, *argv, AF_INET)) {
+			return -1;
+		}
+		*val=addr.data[0];
+		return 0;
+	}
+	if (TIPV6 == type) {
+		/* not implemented yet */
+		return -1;
+	}
+
+	return -1;
+}
+
+int
+parse_cmd(int *argc_p, char ***argv_p, __u32 len, int type,__u32 retain,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	__u32 mask = 0, val = 0;
+	__u32 o = 0xFF;
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc <= 0)
+		return -1;
+
+	if (pedit_debug)
+		printf("parse_cmd argc %d %s offset %d length %d\n",argc,*argv,tkey->off,len);
+
+	if (len == 2)
+		o = 0xFFFF;
+	if (len == 4)
+		o = 0xFFFFFFFF;
+
+	if (matches(*argv, "invert") == 0) {
+		retain = val = mask = o;
+	} else if (matches(*argv, "set") == 0) {
+		NEXT_ARG();
+		if (parse_val(&argc, &argv, &val, type))
+			return -1;
+	} else if (matches(*argv, "preserve") == 0) {
+		retain = mask = o;
+	} else {
+		if (matches(*argv, "clear") != 0)
+			return -1;
+	}
+
+	argc--; argv++;
+
+	if (argc && matches(*argv, "retain") == 0) {
+		NEXT_ARG();
+		if (parse_val(&argc, &argv, &retain, TU32))
+			return -1;
+		argc--; argv++;
+	}
+
+	tkey->val = val;
+
+	if (len == 1) {
+		tkey->mask = 0xFF;
+		res = pack_key8(retain,sel,tkey);
+		goto done;
+	}
+	if (len == 2) {
+		tkey->mask = mask;
+		res = pack_key16(retain,sel,tkey);
+		goto done;
+	}
+	if (len == 4) {
+		tkey->mask = mask;
+		res = pack_key32(retain,sel,tkey);
+		goto done;
+	}
+
+	return -1;
+done:
+	if (pedit_debug)
+		printf("parse_cmd done argc %d %s offset %d length %d\n",argc,*argv,tkey->off,len);
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+
+}
+
+int
+parse_offset(int *argc_p, char ***argv_p,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	int off;
+	__u32 len, retain;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int res = -1;
+
+	if (argc <= 0)
+		return -1;
+
+	if (get_integer(&off, *argv, 0))
+		return -1;
+	tkey->off = off;
+
+	argc--;
+	argv++;
+
+	if (argc <= 0)
+		return -1;
+
+
+	if (matches(*argv, "u32") == 0) {
+		len = 4;
+		retain = 0xFFFFFFFF;
+		goto done;
+	}
+	if (matches(*argv, "u16") == 0) {
+		len = 2;
+		retain = 0x0;
+		goto done;
+	}
+	if (matches(*argv, "u8") == 0) {
+		len = 1;
+		retain = 0x0;
+		goto done;
+	}
+
+	return -1;
+
+done:
+
+	NEXT_ARG();
+
+	/* [at <someval> offmask <maskval> shift <shiftval>] */
+	if (matches(*argv, "at") == 0) {
+
+		__u32 atv=0,offmask=0x0,shift=0;
+
+		NEXT_ARG();
+		if (get_u32(&atv, *argv, 0))
+			return -1;
+		tkey->at = atv;
+
+		NEXT_ARG();
+
+		if (get_u32(&offmask, *argv, 16))
+			return -1;
+		tkey->offmask = offmask;
+
+		NEXT_ARG();
+
+		if (get_u32(&shift, *argv, 0))
+			return -1;
+		tkey->shift = shift;
+
+		NEXT_ARG();
+	}
+
+	res = parse_cmd(&argc, &argv, len, TU32,retain,sel,tkey);
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+int
+parse_munge(int *argc_p, char ***argv_p,struct tc_pedit_sel *sel)
+{
+	struct tc_pedit_key tkey;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int res = -1;
+
+	if (argc <= 0)
+		return -1;
+
+	memset(&tkey, 0, sizeof(tkey));
+
+	if (matches(*argv, "offset") == 0) {
+		NEXT_ARG();
+		res = parse_offset(&argc, &argv,sel,&tkey);
+		goto done;
+	} else {
+		char k[16];
+		struct m_pedit_util *p = NULL;
+
+		strncpy(k, *argv, sizeof (k) - 1);
+
+		if (argc > 0 ) {
+			p = get_pedit_kind(k);
+			if (NULL == p)
+				goto bad_val;
+			res = p->parse_peopt(&argc, &argv, sel,&tkey);
+			if (res < 0) {
+				fprintf(stderr,"bad pedit parsing\n");
+				goto bad_val;
+			}
+			goto done;
+		}
+	}
+
+bad_val:
+	return -1;
+
+done:
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+int
+parse_pedit(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+	struct {
+		struct tc_pedit_sel sel;
+		struct tc_pedit_key keys[MAX_OFFS];
+	} sel;
+
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int ok = 0, iok = 0;
+	struct rtattr *tail;
+
+	memset(&sel, 0, sizeof(sel));
+
+	while (argc > 0) {
+		if (pedit_debug > 1)
+			fprintf(stderr, "while pedit (%d:%s)\n",argc, *argv);
+		if (matches(*argv, "pedit") == 0) {
+			NEXT_ARG();
+			ok++;
+			continue;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else if (matches(*argv, "munge") == 0) {
+			if (!ok) {
+				fprintf(stderr, "Illegal pedit construct (%s) \n", *argv);
+				explain();
+				return -1;
+			}
+			NEXT_ARG();
+			if (parse_munge(&argc, &argv,&sel.sel)) {
+				fprintf(stderr, "Illegal pedit construct (%s) \n", *argv);
+				explain();
+				return -1;
+			}
+			ok++;
+		} else {
+			break;
+		}
+
+	}
+
+	if (!ok) {
+		explain();
+		return -1;
+	}
+
+	if (argc) {
+		if (matches(*argv, "reclassify") == 0) {
+			sel.sel.action = TC_ACT_RECLASSIFY;
+			NEXT_ARG();
+		} else if (matches(*argv, "pipe") == 0) {
+			sel.sel.action = TC_ACT_PIPE;
+			NEXT_ARG();
+		} else if (matches(*argv, "drop") == 0 ||
+			matches(*argv, "shot") == 0) {
+			sel.sel.action = TC_ACT_SHOT;
+			NEXT_ARG();
+		} else if (matches(*argv, "continue") == 0) {
+			sel.sel.action = TC_ACT_UNSPEC;
+			NEXT_ARG();
+		} else if (matches(*argv, "pass") == 0) {
+			sel.sel.action = TC_ACT_OK;
+			NEXT_ARG();
+		}
+	}
+
+	if (argc) {
+		if (matches(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&sel.sel.index, *argv, 10)) {
+				fprintf(stderr, "Pedit: Illegal \"index\"\n");
+				return -1;
+			}
+			argc--;
+			argv++;
+			iok++;
+		}
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	addattr_l(n, MAX_MSG, TCA_PEDIT_PARMS,&sel, sizeof(sel.sel)+sel.sel.nkeys*sizeof(struct tc_pedit_key));
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+int
+print_pedit(struct action_util *au,FILE * f, struct rtattr *arg)
+{
+	struct tc_pedit_sel *sel;
+	struct rtattr *tb[TCA_PEDIT_MAX + 1];
+	SPRINT_BUF(b1);
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, TCA_PEDIT_MAX, arg);
+
+	if (tb[TCA_PEDIT_PARMS] == NULL) {
+		fprintf(f, "[NULL pedit parameters]");
+		return -1;
+	}
+	sel = RTA_DATA(tb[TCA_PEDIT_PARMS]);
+
+	fprintf(f, " pedit action %s keys %d\n ", action_n2a(sel->action, b1, sizeof (b1)),sel->nkeys);
+	fprintf(f, "\t index %d ref %d bind %d", sel->index,sel->refcnt, sel->bindcnt);
+
+	if (show_stats) {
+		if (tb[TCA_PEDIT_TM]) {
+			struct tcf_t *tm = RTA_DATA(tb[TCA_PEDIT_TM]);
+			print_tm(f,tm);
+		}
+	}
+	if (sel->nkeys) {
+		int i;
+		struct tc_pedit_key *key = sel->keys;
+
+		for (i=0; i<sel->nkeys; i++, key++) {
+			fprintf(f, "\n\t key #%d",i);
+			fprintf(f, "  at %d: val %08x mask %08x",
+			(unsigned int)key->off,
+			(unsigned int)ntohl(key->val),
+			(unsigned int)ntohl(key->mask));
+		}
+	} else {
+		fprintf(f, "\npedit %x keys %d is not LEGIT", sel->index,sel->nkeys);
+	}
+
+
+	fprintf(f, "\n ");
+	return 0;
+}
+
+int
+pedit_print_xstats(struct action_util *au, FILE *f, struct rtattr *xstats)
+{
+	return 0;
+}
+
+struct action_util pedit_action_util = {
+	.id = "pedit",
+	.parse_aopt = parse_pedit,
+	.print_aopt = print_pedit,
+};
diff --git a/tc/m_pedit.h b/tc/m_pedit.h
new file mode 100644
index 0000000..1698c95
--- /dev/null
+++ b/tc/m_pedit.h
@@ -0,0 +1,62 @@
+/*
+ * m_pedit.h		generic packet editor actions module
+ *
+ *		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 (hadi@cyberus.ca)
+ *
+ */
+
+#ifndef _ACT_PEDIT_H_
+#define _ACT_PEDIT_H_ 1
+
+#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_pedit.h>
+
+#define MAX_OFFS 128
+
+#define TIPV4 1
+#define TIPV6 2
+#define TINT 3
+#define TU32 4
+
+#define RU32 0xFFFFFFFF
+#define RU16 0xFFFF
+#define RU8 0xFF
+
+#define PEDITKINDSIZ 16
+
+struct m_pedit_util
+{
+	struct m_pedit_util *next;
+	char    id[PEDITKINDSIZ];
+	int     (*parse_peopt)(int *argc_p, char ***argv_p,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey);
+};
+
+
+extern int parse_cmd(int *argc_p, char ***argv_p, __u32 len, int type,__u32 retain,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey);
+extern int pack_key(struct tc_pedit_sel *sel,struct tc_pedit_key *tkey);
+extern int pack_key32(__u32 retain,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey);
+extern int pack_key16(__u32 retain,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey);
+extern int pack_key8(__u32 retain,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey);
+extern int parse_val(int *argc_p, char ***argv_p, __u32 * val, int type);
+extern int parse_cmd(int *argc_p, char ***argv_p, __u32 len, int type,__u32 retain,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey);
+extern int parse_offset(int *argc_p, char ***argv_p,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey);
+int parse_pedit(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n);
+extern int print_pedit(struct action_util *au,FILE * f, struct rtattr *arg);
+extern int pedit_print_xstats(struct action_util *au, FILE *f, struct rtattr *xstats);
+
+#endif
diff --git a/tc/m_police.c b/tc/m_police.c
new file mode 100644
index 0000000..ace43b5
--- /dev/null
+++ b/tc/m_police.c
@@ -0,0 +1,371 @@
+/*
+ * m_police.c		Parse/print policing module options.
+ *
+ *		This program is free software; you can u32istribute 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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ * FIXES:       19990619 - J Hadi Salim (hadi@cyberus.ca)
+ *		simple addattr packaging fix.
+ *		2002: J Hadi Salim - Add tc action extensions syntax
+ *
+ */
+
+#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"
+
+struct action_util police_action_util = {
+	.id = "police",
+	.parse_aopt = act_parse_police,
+	.print_aopt = print_police,
+};
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ... police rate BPS burst BYTES[/BYTES] [ mtu BYTES[/BYTES] ]\n");
+	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");
+	exit(-1);
+}
+
+static void explain1(char *arg)
+{
+	fprintf(stderr, "Illegal \"%s\"\n", arg);
+}
+
+char *police_action_n2a(int action, char *buf, int len)
+{
+	switch (action) {
+	case -1:
+		return "continue";
+		break;
+	case TC_POLICE_OK:
+		return "pass";
+		break;
+	case TC_POLICE_SHOT:
+		return "drop";
+		break;
+	case TC_POLICE_RECLASSIFY:
+		return "reclassify";
+	case TC_POLICE_PIPE:
+		return "pipe";
+	default:
+		snprintf(buf, len, "%d", action);
+		return buf;
+	}
+}
+
+int police_action_a2n(char *arg, int *result)
+{
+	int res;
+
+	if (matches(arg, "continue") == 0)
+		res = -1;
+	else if (matches(arg, "drop") == 0)
+		res = TC_POLICE_SHOT;
+	else if (matches(arg, "shot") == 0)
+		res = TC_POLICE_SHOT;
+	else if (matches(arg, "pass") == 0)
+		res = TC_POLICE_OK;
+	else if (strcmp(arg, "ok") == 0)
+		res = TC_POLICE_OK;
+	else if (matches(arg, "reclassify") == 0)
+		res = TC_POLICE_RECLASSIFY;
+	else if (matches(arg, "pipe") == 0)
+		res = TC_POLICE_PIPE;
+	else {
+		char dummy;
+		if (sscanf(arg, "%d%c", &res, &dummy) != 1)
+			return -1;
+	}
+	*result = res;
+	return 0;
+}
+
+
+int get_police_result(int *action, int *result, char *arg)
+{
+	char *p = strchr(arg, '/');
+
+	if (p)
+		*p = 0;
+
+	if (police_action_a2n(arg, action)) {
+		if (p)
+			*p = '/';
+		return -1;
+	}
+
+	if (p) {
+		*p = '/';
+		if (police_action_a2n(p+1, result))
+			return -1;
+	}
+	return 0;
+}
+
+
+int act_parse_police(struct action_util *a,int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int res = -1;
+	int ok=0;
+	struct tc_police p;
+	__u32 rtab[256];
+	__u32 ptab[256];
+	__u32 avrate = 0;
+	int presult = 0;
+	unsigned buffer=0, mtu=0, mpu=0;
+	unsigned short overhead=0;
+	unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+	int Rcell_log=-1, Pcell_log = -1;
+	struct rtattr *tail;
+
+	memset(&p, 0, sizeof(p));
+	p.action = TC_POLICE_RECLASSIFY;
+
+	if (a) /* new way of doing things */
+		NEXT_ARG();
+
+	if (argc <= 0)
+		return -1;
+
+	while (argc > 0) {
+
+		if (matches(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&p.index, *argv, 10)) {
+				fprintf(stderr, "Illegal \"index\"\n");
+				return -1;
+			}
+		} else if (matches(*argv, "burst") == 0 ||
+			strcmp(*argv, "buffer") == 0 ||
+			strcmp(*argv, "maxburst") == 0) {
+			NEXT_ARG();
+			if (buffer) {
+				fprintf(stderr, "Double \"buffer/burst\" spec\n");
+				return -1;
+			}
+			if (get_size_and_cell(&buffer, &Rcell_log, *argv) < 0) {
+				explain1("buffer");
+				return -1;
+			}
+		} else if (strcmp(*argv, "mtu") == 0 ||
+			   strcmp(*argv, "minburst") == 0) {
+			NEXT_ARG();
+			if (mtu) {
+				fprintf(stderr, "Double \"mtu/minburst\" spec\n");
+				return -1;
+			}
+			if (get_size_and_cell(&mtu, &Pcell_log, *argv) < 0) {
+				explain1("mtu");
+				return -1;
+			}
+		} else if (strcmp(*argv, "mpu") == 0) {
+			NEXT_ARG();
+			if (mpu) {
+				fprintf(stderr, "Double \"mpu\" spec\n");
+				return -1;
+			}
+			if (get_size(&mpu, *argv)) {
+				explain1("mpu");
+				return -1;
+			}
+		} else if (strcmp(*argv, "rate") == 0) {
+			NEXT_ARG();
+			if (p.rate.rate) {
+				fprintf(stderr, "Double \"rate\" spec\n");
+				return -1;
+			}
+			if (get_rate(&p.rate.rate, *argv)) {
+				explain1("rate");
+				return -1;
+			}
+		} else if (strcmp(*argv, "avrate") == 0) {
+			NEXT_ARG();
+			if (avrate) {
+				fprintf(stderr, "Double \"avrate\" spec\n");
+				return -1;
+			}
+			if (get_rate(&avrate, *argv)) {
+				explain1("avrate");
+				return -1;
+			}
+		} else if (matches(*argv, "peakrate") == 0) {
+			NEXT_ARG();
+			if (p.peakrate.rate) {
+				fprintf(stderr, "Double \"peakrate\" spec\n");
+				return -1;
+			}
+			if (get_rate(&p.peakrate.rate, *argv)) {
+				explain1("peakrate");
+				return -1;
+			}
+		} else if (matches(*argv, "reclassify") == 0) {
+			p.action = TC_POLICE_RECLASSIFY;
+		} else if (matches(*argv, "drop") == 0 ||
+			   matches(*argv, "shot") == 0) {
+			p.action = TC_POLICE_SHOT;
+		} else if (matches(*argv, "continue") == 0) {
+			p.action = TC_POLICE_UNSPEC;
+		} else if (matches(*argv, "pass") == 0) {
+			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) {
+			NEXT_ARG();
+			if (get_police_result(&p.action, &presult, *argv)) {
+				fprintf(stderr, "Illegal \"action\"\n");
+				return -1;
+			}
+		} else if (matches(*argv, "overhead") == 0) {
+			NEXT_ARG();
+			if (get_u16(&overhead, *argv, 10)) {
+				explain1("overhead"); return -1;
+			}
+		} else if (matches(*argv, "linklayer") == 0) {
+			NEXT_ARG();
+			if (get_linklayer(&linklayer, *argv)) {
+				explain1("linklayer"); return -1;
+			}
+		} else if (strcmp(*argv, "help") == 0) {
+			usage();
+		} else {
+			break;
+		}
+		ok++;
+		argc--; argv++;
+	}
+
+	if (!ok)
+		return -1;
+
+	if (p.rate.rate && !buffer) {
+		fprintf(stderr, "\"burst\" requires \"rate\".\n");
+		return -1;
+	}
+	if (p.peakrate.rate) {
+		if (!p.rate.rate) {
+			fprintf(stderr, "\"peakrate\" requires \"rate\".\n");
+			return -1;
+		}
+		if (!mtu) {
+			fprintf(stderr, "\"mtu\" is required, if \"peakrate\" is requested.\n");
+			return -1;
+		}
+	}
+
+	if (p.rate.rate) {
+		p.rate.mpu = mpu;
+		p.rate.overhead = overhead;
+		if (tc_calc_rtable(&p.rate, rtab, Rcell_log, mtu, linklayer) < 0) {
+			fprintf(stderr, "TBF: failed to calculate rate table.\n");
+			return -1;
+		}
+		p.burst = tc_calc_xmittime(p.rate.rate, buffer);
+	}
+	p.mtu = mtu;
+	if (p.peakrate.rate) {
+		p.peakrate.mpu = mpu;
+		p.peakrate.overhead = overhead;
+		if (tc_calc_rtable(&p.peakrate, ptab, Pcell_log, mtu, linklayer) < 0) {
+			fprintf(stderr, "POLICE: failed to calculate peak rate table.\n");
+			return -1;
+		}
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	addattr_l(n, MAX_MSG, TCA_POLICE_TBF, &p, sizeof(p));
+	if (p.rate.rate)
+		addattr_l(n, MAX_MSG, TCA_POLICE_RATE, rtab, 1024);
+	if (p.peakrate.rate)
+                addattr_l(n, MAX_MSG, TCA_POLICE_PEAKRATE, ptab, 1024);
+	if (avrate)
+		addattr32(n, MAX_MSG, TCA_POLICE_AVRATE, avrate);
+	if (presult)
+		addattr32(n, MAX_MSG, TCA_POLICE_RESULT, presult);
+
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	res = 0;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+int parse_police(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+	return act_parse_police(NULL,argc_p,argv_p,tca_id,n);
+}
+
+int
+print_police(struct action_util *a, FILE *f, struct rtattr *arg)
+{
+	SPRINT_BUF(b1);
+	struct tc_police *p;
+	struct rtattr *tb[TCA_POLICE_MAX+1];
+	unsigned buffer;
+
+	if (arg == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_POLICE_MAX, arg);
+
+	if (tb[TCA_POLICE_TBF] == NULL) {
+		fprintf(f, "[NULL police tbf]");
+		return 0;
+	}
+#ifndef STOOPID_8BYTE
+	if (RTA_PAYLOAD(tb[TCA_POLICE_TBF])  < sizeof(*p)) {
+		fprintf(f, "[truncated police tbf]");
+		return -1;
+	}
+#endif
+	p = RTA_DATA(tb[TCA_POLICE_TBF]);
+
+	fprintf(f, " police 0x%x ", p->index);
+	fprintf(f, "rate %s ", sprint_rate(p->rate.rate, b1));
+	buffer = tc_calc_xmitsize(p->rate.rate, p->burst);
+	fprintf(f, "burst %s ", sprint_size(buffer, b1));
+	fprintf(f, "mtu %s ", sprint_size(p->mtu, b1));
+	if (show_raw)
+		fprintf(f, "[%08x] ", p->burst);
+	if (p->peakrate.rate)
+		fprintf(f, "peakrate %s ", sprint_rate(p->peakrate.rate, b1));
+	if (tb[TCA_POLICE_AVRATE])
+		fprintf(f, "avrate %s ", sprint_rate(*(__u32*)RTA_DATA(tb[TCA_POLICE_AVRATE]), b1));
+	fprintf(f, "action %s", police_action_n2a(p->action, b1, sizeof(b1)));
+	if (tb[TCA_POLICE_RESULT]) {
+		fprintf(f, "/%s ", police_action_n2a(*(int*)RTA_DATA(tb[TCA_POLICE_RESULT]), b1, sizeof(b1)));
+	} else
+		fprintf(f, " ");
+	fprintf(f, "overhead %ub ", p->rate.overhead);
+	fprintf(f, "\nref %d bind %d\n",p->refcnt, p->bindcnt);
+
+	return 0;
+}
+
+int
+tc_print_police(FILE *f, struct rtattr *arg) {
+	return print_police(&police_action_util,f,arg);
+}
diff --git a/tc/m_skbedit.c b/tc/m_skbedit.c
new file mode 100644
index 0000000..9044353
--- /dev/null
+++ b/tc/m_skbedit.c
@@ -0,0 +1,192 @@
+/*
+ * m_skbedit.c		SKB Editing module
+ *
+ * Copyright (c) 2008, Intel Corporation.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * Authors:	Alexander Duyck <alexander.h.duyck@intel.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_skbedit.h>
+
+static void
+explain(void)
+{
+	fprintf(stderr, "Usage: ... skbedit "
+			"queue_mapping QUEUE_MAPPING | priority PRIORITY \n"
+			"QUEUE_MAPPING = device transmit queue to use\n"
+			"PRIORITY = classID to assign to priority field\n");
+}
+
+static void
+usage(void)
+{
+	explain();
+	exit(-1);
+}
+
+static int
+parse_skbedit(struct action_util *a, int *argc_p, char ***argv_p, int tca_id,
+	      struct nlmsghdr *n)
+{
+	int argc = *argc_p;
+	char **argv = *argv_p;
+	int ok = 0;
+	struct rtattr *tail;
+	unsigned int tmp;
+	__u16 queue_mapping;
+	__u32 flags = 0, priority;
+	struct tc_skbedit sel = { 0 };
+
+	if (matches(*argv, "skbedit") != 0)
+		return -1;
+
+	NEXT_ARG();
+
+	while (argc > 0) {
+		if (matches(*argv, "queue_mapping") == 0) {
+			flags |= SKBEDIT_F_QUEUE_MAPPING;
+			NEXT_ARG();
+			if (get_unsigned(&tmp, *argv, 10) || tmp > 65535) {
+				fprintf(stderr, "Illegal queue_mapping\n");
+				return -1;
+			}
+			queue_mapping = tmp;
+			ok++;
+		} else if (matches(*argv, "priority") == 0) {
+			flags |= SKBEDIT_F_PRIORITY;
+			NEXT_ARG();
+			if (get_tc_classid(&priority, *argv)) {
+				fprintf(stderr, "Illegal priority\n");
+				return -1;
+			}
+			ok++;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			break;
+		}
+		argc--;
+		argv++;
+	}
+
+	if (argc) {
+		if (matches(*argv, "reclassify") == 0) {
+			sel.action = TC_ACT_RECLASSIFY;
+			NEXT_ARG();
+		} else if (matches(*argv, "pipe") == 0) {
+			sel.action = TC_ACT_PIPE;
+			NEXT_ARG();
+		} else if (matches(*argv, "drop") == 0 ||
+			matches(*argv, "shot") == 0) {
+			sel.action = TC_ACT_SHOT;
+			NEXT_ARG();
+		} else if (matches(*argv, "continue") == 0) {
+			sel.action = TC_ACT_UNSPEC;
+			NEXT_ARG();
+		} else if (matches(*argv, "pass") == 0) {
+			sel.action = TC_ACT_OK;
+			NEXT_ARG();
+		}
+	}
+
+	if (argc) {
+		if (matches(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&sel.index, *argv, 10)) {
+				fprintf(stderr, "Pedit: Illegal \"index\"\n");
+				return -1;
+			}
+			argc--;
+			argv++;
+			ok++;
+		}
+	}
+
+	if (!ok) {
+		explain();
+		return -1;
+	}
+
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	addattr_l(n, MAX_MSG, TCA_SKBEDIT_PARMS, &sel, sizeof(sel));
+	if (flags & SKBEDIT_F_QUEUE_MAPPING)
+		addattr_l(n, MAX_MSG, TCA_SKBEDIT_QUEUE_MAPPING,
+			  &queue_mapping, sizeof(queue_mapping));
+	if (flags & SKBEDIT_F_PRIORITY)
+		addattr_l(n, MAX_MSG, TCA_SKBEDIT_PRIORITY,
+			  &priority, sizeof(priority));
+	tail->rta_len = (char *)NLMSG_TAIL(n) - (char *)tail;
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+static int print_skbedit(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+	struct tc_skbedit *sel;
+	struct rtattr *tb[TCA_SKBEDIT_MAX + 1];
+	SPRINT_BUF(b1);
+	__u32 *priority;
+	__u16 *queue_mapping;
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, TCA_SKBEDIT_MAX, arg);
+
+	if (tb[TCA_SKBEDIT_PARMS] == NULL) {
+		fprintf(f, "[NULL skbedit parameters]");
+		return -1;
+	}
+
+	sel = RTA_DATA(tb[TCA_SKBEDIT_PARMS]);
+
+	fprintf(f, " skbedit");
+
+	if (tb[TCA_SKBEDIT_QUEUE_MAPPING] != NULL) {
+		queue_mapping = RTA_DATA(tb[TCA_SKBEDIT_QUEUE_MAPPING]);
+		fprintf(f, " queue_mapping %u", *queue_mapping);
+	}
+	if (tb[TCA_SKBEDIT_PRIORITY] != NULL) {
+		priority = RTA_DATA(tb[TCA_SKBEDIT_PRIORITY]);
+		fprintf(f, " priority %s", sprint_tc_classid(*priority, b1));
+	}
+
+	if (show_stats) {
+		if (tb[TCA_SKBEDIT_TM]) {
+			struct tcf_t *tm = RTA_DATA(tb[TCA_SKBEDIT_TM]);
+			print_tm(f, tm);
+		}
+	}
+
+	return 0;
+}
+
+struct action_util skbedit_action_util = {
+	.id = "skbedit",
+	.parse_aopt = parse_skbedit,
+	.print_aopt = print_skbedit,
+};
diff --git a/tc/m_xt.c b/tc/m_xt.c
new file mode 100644
index 0000000..3972d2d
--- /dev/null
+++ b/tc/m_xt.c
@@ -0,0 +1,346 @@
+/*
+ * m_xt.c	xtables based targets
+ * 		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
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ *
+ * Authors:  J Hadi Salim (hadi@cyberus.ca)
+ */
+
+#include <syslog.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <limits.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <xtables.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_ipt.h>
+#include <stdio.h>
+#include <dlfcn.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#ifndef XT_LIB_DIR
+#       define XT_LIB_DIR "/lib/xtables"
+#endif
+
+static const char *tname = "mangle";
+
+char *lib_dir;
+
+static const char *ipthooks[] = {
+	"NF_IP_PRE_ROUTING",
+	"NF_IP_LOCAL_IN",
+	"NF_IP_FORWARD",
+	"NF_IP_LOCAL_OUT",
+	"NF_IP_POST_ROUTING",
+};
+
+static struct option original_opts[] = {
+	{
+		.name = "jump",
+		.has_arg = 1,
+		.val = 'j'
+	},
+	{0, 0, 0, 0}
+};
+
+static struct xtables_globals tcipt_globals = {
+	.option_offset = 0,
+	.program_name = "tc-ipt",
+	.program_version = "0.2",
+	.orig_opts = original_opts,
+	.opts = original_opts,
+	.exit_err = NULL,
+};
+
+/*
+ * we may need to check for version mismatch
+*/
+int
+build_st(struct xtables_target *target, struct xt_entry_target *t)
+{
+
+	size_t size =
+		    XT_ALIGN(sizeof (struct xt_entry_target)) + target->size;
+
+	if (NULL == t) {
+		target->t = xtables_calloc(1, size);
+		target->t->u.target_size = size;
+		strcpy(target->t->u.user.name, target->name);
+		xtables_set_revision(target->t->u.user.name, target->revision);
+
+		if (target->init != NULL)
+			target->init(target->t);
+	} else {
+		target->t = t;
+	}
+	return 0;
+
+}
+
+inline void set_lib_dir(void)
+{
+
+	lib_dir = getenv("XTABLES_LIBDIR");
+	if (!lib_dir) {
+		lib_dir = getenv("IPTABLES_LIB_DIR");
+		if (lib_dir)
+			fprintf(stderr, "using deprecated IPTABLES_LIB_DIR \n");
+	}
+	if (lib_dir == NULL)
+		lib_dir = XT_LIB_DIR;
+
+}
+
+static int parse_ipt(struct action_util *a,int *argc_p,
+		     char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+	struct xtables_target *m = NULL;
+	struct ipt_entry fw;
+	struct rtattr *tail;
+	int c;
+	int rargc = *argc_p;
+	char **argv = *argv_p;
+	int argc = 0, iargc = 0;
+	char k[16];
+	int res = -1;
+	int size = 0;
+	int iok = 0, ok = 0;
+	__u32 hook = 0, index = 0;
+	res = 0;
+
+	xtables_init_all(&tcipt_globals, NFPROTO_IPV4);
+	set_lib_dir();
+
+	{
+		int i;
+		for (i = 0; i < rargc; i++) {
+			if (NULL == argv[i] || 0 == strcmp(argv[i], "action")) {
+				break;
+			}
+		}
+		iargc = argc = i;
+	}
+
+	if (argc <= 2) {
+		fprintf(stderr,"bad arguements to ipt %d vs %d \n", argc, rargc);
+		return -1;
+	}
+
+	while (1) {
+		c = getopt_long(argc, argv, "j:", tcipt_globals.opts, NULL);
+		if (c == -1)
+			break;
+		switch (c) {
+		case 'j':
+			m = xtables_find_target(optarg, XTF_TRY_LOAD);
+			if (NULL != m) {
+
+				if (0 > build_st(m, NULL)) {
+					printf(" %s error \n", m->name);
+					return -1;
+				}
+				tcipt_globals.opts =
+				    xtables_merge_options(tcipt_globals.opts,
+				                          m->extra_opts,
+				                          &m->option_offset);
+			} else {
+				fprintf(stderr," failed to find target %s\n\n", optarg);
+				return -1;
+			}
+			ok++;
+			break;
+
+		default:
+			memset(&fw, 0, sizeof (fw));
+			if (m) {
+				m->parse(c - m->option_offset, argv, 0,
+					 &m->tflags, NULL, &m->t);
+			} else {
+				fprintf(stderr," failed to find target %s\n\n", optarg);
+				return -1;
+
+			}
+			ok++;
+			break;
+
+		}
+	}
+
+	if (iargc > optind) {
+		if (matches(argv[optind], "index") == 0) {
+			if (get_u32(&index, argv[optind + 1], 10)) {
+				fprintf(stderr, "Illegal \"index\"\n");
+				xtables_free_opts(1);
+				return -1;
+			}
+			iok++;
+
+			optind += 2;
+		}
+	}
+
+	if (!ok && !iok) {
+		fprintf(stderr," ipt Parser BAD!! (%s)\n", *argv);
+		return -1;
+	}
+
+	/* check that we passed the correct parameters to the target */
+	if (m && m->final_check)
+		m->final_check(m->tflags);
+
+	{
+		struct tcmsg *t = NLMSG_DATA(n);
+		if (t->tcm_parent != TC_H_ROOT
+		    && t->tcm_parent == TC_H_MAJ(TC_H_INGRESS)) {
+			hook = NF_IP_PRE_ROUTING;
+		} else {
+			hook = NF_IP_POST_ROUTING;
+		}
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	fprintf(stdout, "tablename: %s hook: %s\n ", tname, ipthooks[hook]);
+	fprintf(stdout, "\ttarget: ");
+
+	if (m)
+		m->print(NULL, m->t, 0);
+	fprintf(stdout, " index %d\n", index);
+
+	if (strlen(tname) > 16) {
+		size = 16;
+		k[15] = 0;
+	} else {
+		size = 1 + strlen(tname);
+	}
+	strncpy(k, tname, size);
+
+	addattr_l(n, MAX_MSG, TCA_IPT_TABLE, k, size);
+	addattr_l(n, MAX_MSG, TCA_IPT_HOOK, &hook, 4);
+	addattr_l(n, MAX_MSG, TCA_IPT_INDEX, &index, 4);
+	if (m)
+		addattr_l(n, MAX_MSG, TCA_IPT_TARG, m->t, m->t->u.target_size);
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+
+	argc -= optind;
+	argv += optind;
+	*argc_p = rargc - iargc;
+	*argv_p = argv;
+
+	optind = 0;
+	xtables_free_opts(1);
+	/* Clear flags if target will be used again */
+        m->tflags=0;
+        m->used=0;
+	/* Free allocated memory */
+        if (m->t)
+            free(m->t);
+
+
+	return 0;
+
+}
+
+static int
+print_ipt(struct action_util *au,FILE * f, struct rtattr *arg)
+{
+	struct rtattr *tb[TCA_IPT_MAX + 1];
+	struct xt_entry_target *t = NULL;
+
+	if (arg == NULL)
+		return -1;
+
+	xtables_init_all(&tcipt_globals, NFPROTO_IPV4);
+	set_lib_dir();
+
+	parse_rtattr_nested(tb, TCA_IPT_MAX, arg);
+
+	if (tb[TCA_IPT_TABLE] == NULL) {
+		fprintf(f, "[NULL ipt table name ] assuming mangle ");
+	} else {
+		fprintf(f, "tablename: %s ",
+			(char *) RTA_DATA(tb[TCA_IPT_TABLE]));
+	}
+
+	if (tb[TCA_IPT_HOOK] == NULL) {
+		fprintf(f, "[NULL ipt hook name ]\n ");
+		return -1;
+	} else {
+		__u32 hook;
+		hook = *(__u32 *) RTA_DATA(tb[TCA_IPT_HOOK]);
+		fprintf(f, " hook: %s \n", ipthooks[hook]);
+	}
+
+	if (tb[TCA_IPT_TARG] == NULL) {
+		fprintf(f, "\t[NULL ipt target parameters ] \n");
+		return -1;
+	} else {
+		struct xtables_target *m = NULL;
+		t = RTA_DATA(tb[TCA_IPT_TARG]);
+		m = xtables_find_target(t->u.user.name, XTF_TRY_LOAD);
+		if (NULL != m) {
+			if (0 > build_st(m, t)) {
+				fprintf(stderr, " %s error \n", m->name);
+				return -1;
+			}
+
+			tcipt_globals.opts =
+			    xtables_merge_options(tcipt_globals.opts,
+			                          m->extra_opts,
+			                          &m->option_offset);
+		} else {
+			fprintf(stderr, " failed to find target %s\n\n",
+				t->u.user.name);
+			return -1;
+		}
+		fprintf(f, "\ttarget ");
+		m->print(NULL, m->t, 0);
+		if (tb[TCA_IPT_INDEX] == NULL) {
+			fprintf(f, " [NULL ipt target index ]\n");
+		} else {
+			__u32 index;
+			index = *(__u32 *) RTA_DATA(tb[TCA_IPT_INDEX]);
+			fprintf(f, " \n\tindex %d", index);
+		}
+
+		if (tb[TCA_IPT_CNT]) {
+			struct tc_cnt *c  = RTA_DATA(tb[TCA_IPT_CNT]);;
+			fprintf(f, " ref %d bind %d", c->refcnt, c->bindcnt);
+		}
+		if (show_stats) {
+			if (tb[TCA_IPT_TM]) {
+				struct tcf_t *tm = RTA_DATA(tb[TCA_IPT_TM]);
+				print_tm(f,tm);
+			}
+		}
+		fprintf(f, " \n");
+
+	}
+	xtables_free_opts(1);
+
+	return 0;
+}
+
+struct action_util ipt_action_util = {
+        .id = "ipt",
+        .parse_aopt = parse_ipt,
+        .print_aopt = print_ipt,
+};
+
diff --git a/tc/m_xt_old.c b/tc/m_xt_old.c
new file mode 100644
index 0000000..0c7ec60
--- /dev/null
+++ b/tc/m_xt_old.c
@@ -0,0 +1,433 @@
+/*
+ * m_xt.c	xtables based targets
+ * 		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
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ *
+ * Authors:  J Hadi Salim (hadi@cyberus.ca)
+ */
+
+/*XXX: in the future (xtables 1.4.3?) get rid of everything tagged
+ * as TC_CONFIG_XT_H */
+
+#include <syslog.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <xtables.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_ipt.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#ifdef TC_CONFIG_XT_H
+#include "xt-internal.h"
+#endif
+
+static const char *pname = "tc-ipt";
+static const char *tname = "mangle";
+static const char *pversion = "0.2";
+
+static const char *ipthooks[] = {
+	"NF_IP_PRE_ROUTING",
+	"NF_IP_LOCAL_IN",
+	"NF_IP_FORWARD",
+	"NF_IP_LOCAL_OUT",
+	"NF_IP_POST_ROUTING",
+};
+
+static struct option original_opts[] = {
+	{"jump", 1, 0, 'j'},
+	{0, 0, 0, 0}
+};
+
+static struct option *opts = original_opts;
+static unsigned int global_option_offset = 0;
+char *lib_dir;
+const char *program_version = XTABLES_VERSION;
+const char *program_name = "tc-ipt";
+struct afinfo afinfo = {
+	.family         = AF_INET,
+	.libprefix      = "libxt_",
+	.ipproto        = IPPROTO_IP,
+	.kmod           = "ip_tables",
+	.so_rev_target  = IPT_SO_GET_REVISION_TARGET,
+};
+
+
+#define OPTION_OFFSET 256
+
+/*XXX: TC_CONFIG_XT_H */
+static void free_opts(struct option *local_opts)
+{
+	if (local_opts != original_opts) {
+		free(local_opts);
+		opts = original_opts;
+		global_option_offset = 0;
+	}
+}
+
+/*XXX: TC_CONFIG_XT_H */
+static struct option *
+merge_options(struct option *oldopts, const struct option *newopts,
+	      unsigned int *option_offset)
+{
+	struct option *merge;
+	unsigned int num_old, num_new, i;
+
+	for (num_old = 0; oldopts[num_old].name; num_old++) ;
+	for (num_new = 0; newopts[num_new].name; num_new++) ;
+
+	*option_offset = global_option_offset + OPTION_OFFSET;
+
+	merge = malloc(sizeof (struct option) * (num_new + num_old + 1));
+	memcpy(merge, oldopts, num_old * sizeof (struct option));
+	for (i = 0; i < num_new; i++) {
+		merge[num_old + i] = newopts[i];
+		merge[num_old + i].val += *option_offset;
+	}
+	memset(merge + num_old + num_new, 0, sizeof (struct option));
+
+	return merge;
+}
+
+
+/*XXX: TC_CONFIG_XT_H */
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+/*XXX: TC_CONFIG_XT_H */
+int
+check_inverse(const char option[], int *invert, int *my_optind, int argc)
+{
+        if (option && strcmp(option, "!") == 0) {
+                if (*invert)
+                        exit_error(PARAMETER_PROBLEM,
+                                   "Multiple `!' flags not allowed");
+                *invert = TRUE;
+                if (my_optind != NULL) {
+                        ++*my_optind;
+                        if (argc && *my_optind > argc)
+                                exit_error(PARAMETER_PROBLEM,
+                                           "no argument following `!'");
+                }
+
+                return TRUE;
+        }
+        return FALSE;
+}
+
+/*XXX: TC_CONFIG_XT_H */
+void exit_error(enum exittype status, const char *msg, ...)
+{
+        va_list args;
+
+        va_start(args, msg);
+        fprintf(stderr, "%s v%s: ", pname, pversion);
+        vfprintf(stderr, msg, args);
+        va_end(args);
+        fprintf(stderr, "\n");
+        /* On error paths, make sure that we don't leak memory */
+        exit(status);
+}
+
+/*XXX: TC_CONFIG_XT_H */
+static void set_revision(char *name, u_int8_t revision)
+{
+	/* Old kernel sources don't have ".revision" field,
+	*  but we stole a byte from name. */
+	name[IPT_FUNCTION_MAXNAMELEN - 2] = '\0';
+	name[IPT_FUNCTION_MAXNAMELEN - 1] = revision;
+}
+
+/*
+ * we may need to check for version mismatch
+*/
+int
+build_st(struct xtables_target *target, struct xt_entry_target *t)
+{
+
+	size_t size =
+		    XT_ALIGN(sizeof (struct xt_entry_target)) + target->size;
+
+	if (NULL == t) {
+		target->t = fw_calloc(1, size);
+		target->t->u.target_size = size;
+		strcpy(target->t->u.user.name, target->name);
+		set_revision(target->t->u.user.name, target->revision);
+
+		if (target->init != NULL)
+			target->init(target->t);
+	} else {
+		target->t = t;
+	}
+	return 0;
+
+}
+
+inline void set_lib_dir(void)
+{
+
+	lib_dir = getenv("XTABLES_LIBDIR");
+	if (!lib_dir) {
+		lib_dir = getenv("IPTABLES_LIB_DIR");
+		if (lib_dir)
+			fprintf(stderr, "using deprecated IPTABLES_LIB_DIR \n");
+	}
+	if (lib_dir == NULL)
+		lib_dir = XT_LIB_DIR;
+
+}
+
+static int parse_ipt(struct action_util *a,int *argc_p,
+		     char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+	struct xtables_target *m = NULL;
+	struct ipt_entry fw;
+	struct rtattr *tail;
+	int c;
+	int rargc = *argc_p;
+	char **argv = *argv_p;
+	int argc = 0, iargc = 0;
+	char k[16];
+	int res = -1;
+	int size = 0;
+	int iok = 0, ok = 0;
+	__u32 hook = 0, index = 0;
+	res = 0;
+
+	set_lib_dir();
+
+	{
+		int i;
+		for (i = 0; i < rargc; i++) {
+			if (NULL == argv[i] || 0 == strcmp(argv[i], "action")) {
+				break;
+			}
+		}
+		iargc = argc = i;
+	}
+
+	if (argc <= 2) {
+		fprintf(stderr,"bad arguements to ipt %d vs %d \n", argc, rargc);
+		return -1;
+	}
+
+	while (1) {
+		c = getopt_long(argc, argv, "j:", opts, NULL);
+		if (c == -1)
+			break;
+		switch (c) {
+		case 'j':
+			m = find_target(optarg, TRY_LOAD);
+			if (NULL != m) {
+
+				if (0 > build_st(m, NULL)) {
+					printf(" %s error \n", m->name);
+					return -1;
+				}
+				opts =
+				    merge_options(opts, m->extra_opts,
+						  &m->option_offset);
+			} else {
+				fprintf(stderr," failed to find target %s\n\n", optarg);
+				return -1;
+			}
+			ok++;
+			break;
+
+		default:
+			memset(&fw, 0, sizeof (fw));
+			if (m) {
+				m->parse(c - m->option_offset, argv, 0,
+					 &m->tflags, NULL, &m->t);
+			} else {
+				fprintf(stderr," failed to find target %s\n\n", optarg);
+				return -1;
+
+			}
+			ok++;
+			break;
+
+		}
+	}
+
+	if (iargc > optind) {
+		if (matches(argv[optind], "index") == 0) {
+			if (get_u32(&index, argv[optind + 1], 10)) {
+				fprintf(stderr, "Illegal \"index\"\n");
+				free_opts(opts);
+				return -1;
+			}
+			iok++;
+
+			optind += 2;
+		}
+	}
+
+	if (!ok && !iok) {
+		fprintf(stderr," ipt Parser BAD!! (%s)\n", *argv);
+		return -1;
+	}
+
+	/* check that we passed the correct parameters to the target */
+	if (m)
+		m->final_check(m->tflags);
+
+	{
+		struct tcmsg *t = NLMSG_DATA(n);
+		if (t->tcm_parent != TC_H_ROOT
+		    && t->tcm_parent == TC_H_MAJ(TC_H_INGRESS)) {
+			hook = NF_IP_PRE_ROUTING;
+		} else {
+			hook = NF_IP_POST_ROUTING;
+		}
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, MAX_MSG, tca_id, NULL, 0);
+	fprintf(stdout, "tablename: %s hook: %s\n ", tname, ipthooks[hook]);
+	fprintf(stdout, "\ttarget: ");
+
+	if (m)
+		m->print(NULL, m->t, 0);
+	fprintf(stdout, " index %d\n", index);
+
+	if (strlen(tname) > 16) {
+		size = 16;
+		k[15] = 0;
+	} else {
+		size = 1 + strlen(tname);
+	}
+	strncpy(k, tname, size);
+
+	addattr_l(n, MAX_MSG, TCA_IPT_TABLE, k, size);
+	addattr_l(n, MAX_MSG, TCA_IPT_HOOK, &hook, 4);
+	addattr_l(n, MAX_MSG, TCA_IPT_INDEX, &index, 4);
+	if (m)
+		addattr_l(n, MAX_MSG, TCA_IPT_TARG, m->t, m->t->u.target_size);
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+
+	argc -= optind;
+	argv += optind;
+	*argc_p = rargc - iargc;
+	*argv_p = argv;
+
+	optind = 0;
+	free_opts(opts);
+	/* Clear flags if target will be used again */
+        m->tflags=0;
+        m->used=0;
+	/* Free allocated memory */
+        if (m->t)
+            free(m->t);
+
+
+	return 0;
+
+}
+
+static int
+print_ipt(struct action_util *au,FILE * f, struct rtattr *arg)
+{
+	struct rtattr *tb[TCA_IPT_MAX + 1];
+	struct xt_entry_target *t = NULL;
+
+	if (arg == NULL)
+		return -1;
+
+	set_lib_dir();
+
+	parse_rtattr_nested(tb, TCA_IPT_MAX, arg);
+
+	if (tb[TCA_IPT_TABLE] == NULL) {
+		fprintf(f, "[NULL ipt table name ] assuming mangle ");
+	} else {
+		fprintf(f, "tablename: %s ",
+			(char *) RTA_DATA(tb[TCA_IPT_TABLE]));
+	}
+
+	if (tb[TCA_IPT_HOOK] == NULL) {
+		fprintf(f, "[NULL ipt hook name ]\n ");
+		return -1;
+	} else {
+		__u32 hook;
+		hook = *(__u32 *) RTA_DATA(tb[TCA_IPT_HOOK]);
+		fprintf(f, " hook: %s \n", ipthooks[hook]);
+	}
+
+	if (tb[TCA_IPT_TARG] == NULL) {
+		fprintf(f, "\t[NULL ipt target parameters ] \n");
+		return -1;
+	} else {
+		struct xtables_target *m = NULL;
+		t = RTA_DATA(tb[TCA_IPT_TARG]);
+		m = find_target(t->u.user.name, TRY_LOAD);
+		if (NULL != m) {
+			if (0 > build_st(m, t)) {
+				fprintf(stderr, " %s error \n", m->name);
+				return -1;
+			}
+
+			opts =
+			    merge_options(opts, m->extra_opts,
+					  &m->option_offset);
+		} else {
+			fprintf(stderr, " failed to find target %s\n\n",
+				t->u.user.name);
+			return -1;
+		}
+		fprintf(f, "\ttarget ");
+		m->print(NULL, m->t, 0);
+		if (tb[TCA_IPT_INDEX] == NULL) {
+			fprintf(f, " [NULL ipt target index ]\n");
+		} else {
+			__u32 index;
+			index = *(__u32 *) RTA_DATA(tb[TCA_IPT_INDEX]);
+			fprintf(f, " \n\tindex %d", index);
+		}
+
+		if (tb[TCA_IPT_CNT]) {
+			struct tc_cnt *c  = RTA_DATA(tb[TCA_IPT_CNT]);;
+			fprintf(f, " ref %d bind %d", c->refcnt, c->bindcnt);
+		}
+		if (show_stats) {
+			if (tb[TCA_IPT_TM]) {
+				struct tcf_t *tm = RTA_DATA(tb[TCA_IPT_TM]);
+				print_tm(f,tm);
+			}
+		}
+		fprintf(f, " \n");
+
+	}
+	free_opts(opts);
+
+	return 0;
+}
+
+struct action_util ipt_action_util = {
+        .id = "ipt",
+        .parse_aopt = parse_ipt,
+        .print_aopt = print_ipt,
+};
+
diff --git a/tc/p_icmp.c b/tc/p_icmp.c
new file mode 100644
index 0000000..a4b80c2
--- /dev/null
+++ b/tc/p_icmp.c
@@ -0,0 +1,61 @@
+/*
+ * m_pedit_icmp.c	packet editor: ICMP header
+ *
+ *		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 (hadi@cyberus.ca)
+ *
+ */
+
+#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 "m_pedit.h"
+
+
+static int
+parse_icmp(int *argc_p, char ***argv_p,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	int res = -1;
+#if 0
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc < 2)
+		return -1;
+
+	if (strcmp(*argv, "type") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, 0);
+		goto done;
+	}
+	if (strcmp(*argv, "code") == 0) {
+		NEXT_ARG();
+		res = parse_u8(&argc, &argv, 1);
+		goto done;
+	}
+	return -1;
+
+      done:
+	*argc_p = argc;
+	*argv_p = argv;
+#endif
+	return res;
+}
+
+struct m_pedit_util p_pedit_icmp = {
+	NULL,
+	"icmp",
+	parse_icmp,
+};
diff --git a/tc/p_ip.c b/tc/p_ip.c
new file mode 100644
index 0000000..08fdbaa
--- /dev/null
+++ b/tc/p_ip.c
@@ -0,0 +1,159 @@
+/*
+ * m_pedit.c		packet editor: IPV4/6 header
+ *
+ *		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 (hadi@cyberus.ca)
+ *
+ */
+
+#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 "m_pedit.h"
+
+static int
+parse_ip(int *argc_p, char ***argv_p,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	int res = -1;
+	int argc = *argc_p;
+	char **argv = *argv_p;
+
+	if (argc < 2)
+		return -1;
+
+	if (strcmp(*argv, "src") == 0) {
+		NEXT_ARG();
+		tkey->off = 12;
+		res = parse_cmd(&argc, &argv, 4, TIPV4,RU32,sel,tkey);
+		goto done;
+	}
+	if (strcmp(*argv, "dst") == 0) {
+		NEXT_ARG();
+		tkey->off = 16;
+		res = parse_cmd(&argc, &argv, 4, TIPV4,RU32,sel,tkey);
+		goto done;
+	}
+	/* jamal - look at these and make them either old or new
+	** scheme given diffserv
+	** dont forget the CE bit
+	*/
+	if (strcmp(*argv, "tos") == 0 || matches(*argv, "dsfield") == 0) {
+		NEXT_ARG();
+		tkey->off = 1;
+		res = parse_cmd(&argc, &argv,  1, TU32,RU8,sel,tkey);
+		goto done;
+	}
+	if (strcmp(*argv, "ihl") == 0) {
+		NEXT_ARG();
+		tkey->off = 0;
+		res = parse_cmd(&argc, &argv, 1, TU32,RU8,sel,tkey);
+		goto done;
+	}
+	if (strcmp(*argv, "protocol") == 0) {
+		NEXT_ARG();
+		tkey->off = 9;
+		res = parse_cmd(&argc, &argv, 1, TU32,RU8,sel,tkey);
+		goto done;
+	}
+	/* jamal - fix this */
+	if (matches(*argv, "precedence") == 0) {
+		NEXT_ARG();
+		tkey->off = 1;
+		res = parse_cmd(&argc, &argv, 1, TU32,RU8,sel,tkey);
+		goto done;
+	}
+	/* jamal - validate this at some point */
+	if (strcmp(*argv, "nofrag") == 0) {
+		NEXT_ARG();
+		tkey->off = 6;
+		res = parse_cmd(&argc, &argv, 1, TU32,0x3F,sel,tkey);
+		goto done;
+	}
+	/* jamal - validate this at some point */
+	if (strcmp(*argv, "firstfrag") == 0) {
+		NEXT_ARG();
+		tkey->off = 6;
+		res = parse_cmd(&argc, &argv, 1, TU32,0x1F,sel,tkey);
+		goto done;
+	}
+	if (strcmp(*argv, "ce") == 0) {
+		NEXT_ARG();
+		tkey->off = 6;
+		res = parse_cmd(&argc, &argv, 1, TU32,0x80,sel,tkey);
+		goto done;
+	}
+	if (strcmp(*argv, "df") == 0) {
+		NEXT_ARG();
+		tkey->off = 6;
+		res = parse_cmd(&argc, &argv, 1, TU32,0x40,sel,tkey);
+		goto done;
+	}
+	if (strcmp(*argv, "mf") == 0) {
+		NEXT_ARG();
+		tkey->off = 6;
+		res = parse_cmd(&argc, &argv, 1, TU32,0x20,sel,tkey);
+		goto done;
+	}
+	if (strcmp(*argv, "dport") == 0) {
+		NEXT_ARG();
+		tkey->off = 22;
+		res = parse_cmd(&argc, &argv, 2, TU32,RU16,sel,tkey);
+		goto done;
+	}
+	if (strcmp(*argv, "sport") == 0) {
+		NEXT_ARG();
+		tkey->off = 20;
+		res = parse_cmd(&argc, &argv, 2, TU32,RU16,sel,tkey);
+		goto done;
+	}
+	if (strcmp(*argv, "icmp_type") == 0) {
+		NEXT_ARG();
+		tkey->off = 20;
+		res = parse_cmd(&argc, &argv, 1, TU32,RU8,sel,tkey);
+		goto done;
+	}
+	if (strcmp(*argv, "icmp_code") == 0) {
+		NEXT_ARG();
+		tkey->off = 20;
+		res = parse_cmd(&argc, &argv, 1, TU32,RU8,sel,tkey);
+		goto done;
+	}
+	return -1;
+
+      done:
+	*argc_p = argc;
+	*argv_p = argv;
+	return res;
+}
+
+static int
+parse_ip6(int *argc_p, char ***argv_p,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	int res = -1;
+	return res;
+}
+
+struct m_pedit_util p_pedit_ip = {
+	NULL,
+	"ip",
+	parse_ip,
+};
+
+
+struct m_pedit_util p_pedit_ip6 = {
+	NULL,
+	"ip6",
+	parse_ip6,
+};
diff --git a/tc/p_tcp.c b/tc/p_tcp.c
new file mode 100644
index 0000000..7f4b6f4
--- /dev/null
+++ b/tc/p_tcp.c
@@ -0,0 +1,38 @@
+/*
+ * m_pedit_tcp.c	packet editor: TCP header
+ *
+ *		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 (hadi@cyberus.ca)
+ *
+ */
+
+#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 "m_pedit.h"
+
+static int
+parse_tcp(int *argc_p, char ***argv_p,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	int res = -1;
+	return res;
+}
+struct m_pedit_util p_pedit_tcp = {
+	NULL,
+	"tcp",
+	parse_tcp,
+};
+
+
diff --git a/tc/p_udp.c b/tc/p_udp.c
new file mode 100644
index 0000000..1776289
--- /dev/null
+++ b/tc/p_udp.c
@@ -0,0 +1,38 @@
+/*
+ * m_pedit_udp.c	packet editor: UDP header
+ *
+ *		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 (hadi@cyberus.ca)
+ *
+ */
+
+#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 "m_pedit.h"
+
+static int
+parse_udp(int *argc_p, char ***argv_p,struct tc_pedit_sel *sel,struct tc_pedit_key *tkey)
+{
+	int res = -1;
+	return res;
+}
+
+struct m_pedit_util p_pedit_udp = {
+	NULL,
+	"udp",
+	parse_udp,
+};
+
diff --git a/tc/q_atm.c b/tc/q_atm.c
new file mode 100644
index 0000000..4c8dc0b
--- /dev/null
+++ b/tc/q_atm.c
@@ -0,0 +1,260 @@
+/*
+ * q_atm.c		ATM.
+ *
+ * Hacked 1998-2000 by Werner Almesberger, EPFL ICA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <atm.h>
+#include <linux/atmdev.h>
+#include <linux/atmarp.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+
+#define MAX_HDR_LEN 64
+
+#define usage() return(-1)
+
+
+static int atm_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	if (argc) {
+		fprintf(stderr,"Usage: atm\n");
+		return -1;
+	}
+	return 0;
+}
+
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... atm ( pvc ADDR | svc ADDR [ sap SAP ] ) "
+	    "[ qos QOS ] [ sndbuf BYTES ]\n");
+	fprintf(stderr, "  [ hdr HEX... ] [ excess ( CLASSID | clp ) ] "
+	  "[ clip ]\n");
+}
+
+
+static int atm_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
+   struct nlmsghdr *n)
+{
+	struct sockaddr_atmsvc addr;
+	struct atm_qos qos;
+	struct atm_sap sap;
+	unsigned char hdr[MAX_HDR_LEN];
+	__u32 excess = 0;
+	struct rtattr *tail;
+	int sndbuf = 0;
+	int hdr_len = -1;
+	int set_clip = 0;
+	int s;
+
+	memset(&addr,0,sizeof(addr));
+	(void) text2qos("aal5,ubr:sdu=9180,rx:none",&qos,0);
+	(void) text2sap("blli:l2=iso8802",&sap,0);
+	while (argc > 0) {
+		if (!strcmp(*argv,"pvc")) {
+			NEXT_ARG();
+			if (text2atm(*argv,(struct sockaddr *) &addr,
+			    sizeof(addr),T2A_PVC | T2A_NAME) < 0) {
+				explain();
+				return -1;
+			}
+		}
+		else if (!strcmp(*argv,"svc")) {
+			NEXT_ARG();
+			if (text2atm(*argv,(struct sockaddr *) &addr,
+			    sizeof(addr),T2A_SVC | T2A_NAME) < 0) {
+				explain();
+				return -1;
+			}
+		}
+		else if (!strcmp(*argv,"qos")) {
+			NEXT_ARG();
+			if (text2qos(*argv,&qos,0) < 0) {
+				explain();
+				return -1;
+			}
+		}
+		else if (!strcmp(*argv,"sndbuf")) {
+			char *end;
+
+			NEXT_ARG();
+			sndbuf = strtol(*argv,&end,0);
+			if (*end) {
+				explain();
+				return -1;
+			}
+		}
+		else if (!strcmp(*argv,"sap")) {
+			NEXT_ARG();
+			if (addr.sas_family != AF_ATMSVC ||
+			    text2sap(*argv,&sap,T2A_NAME) < 0) {
+				explain();
+				return -1;
+			}
+		}
+		else if (!strcmp(*argv,"hdr")) {
+			unsigned char *ptr;
+			char *walk;
+
+			NEXT_ARG();
+			ptr = hdr;
+			for (walk = *argv; *walk; walk++) {
+				int tmp;
+
+				if (ptr == hdr+MAX_HDR_LEN) {
+					fprintf(stderr,"header is too long\n");
+					return -1;
+				}
+				if (*walk == '.') continue;
+				if (!isxdigit(walk[0]) || !walk[1] ||
+				    !isxdigit(walk[1])) {
+					explain();
+					return -1;
+				}
+				sscanf(walk,"%2x",&tmp);
+				*ptr++ = tmp;
+				walk++;
+			}
+			hdr_len = ptr-hdr;
+		}
+		else if (!strcmp(*argv,"excess")) {
+			NEXT_ARG();
+			if (!strcmp(*argv,"clp")) excess = 0;
+			else if (get_tc_classid(&excess,*argv)) {
+					explain();
+					return -1;
+				}
+		}
+		else if (!strcmp(*argv,"clip")) {
+			set_clip = 1;
+		}
+		else {
+			explain();
+			return 1;
+		}
+		argc--;
+		argv++;
+	}
+	s = socket(addr.sas_family,SOCK_DGRAM,0);
+	if (s < 0) {
+		perror("socket");
+		return -1;
+	}
+	if (setsockopt(s,SOL_ATM,SO_ATMQOS,&qos,sizeof(qos)) < 0) {
+		perror("SO_ATMQOS");
+		return -1;
+	}
+	if (sndbuf)
+	    if (setsockopt(s,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf)) < 0) {
+		perror("SO_SNDBUF");
+	    return -1;
+	}
+	if (addr.sas_family == AF_ATMSVC && setsockopt(s,SOL_ATM,SO_ATMSAP,
+	    &sap,sizeof(sap)) < 0) {
+		perror("SO_ATMSAP");
+		return -1;
+	}
+	if (connect(s,(struct sockaddr *) &addr,addr.sas_family == AF_ATMPVC ?
+	    sizeof(struct sockaddr_atmpvc) : sizeof(addr)) < 0) {
+		perror("connect");
+		return -1;
+	}
+	if (set_clip)
+		if (ioctl(s,ATMARP_MKIP,0) < 0) {
+			perror("ioctl ATMARP_MKIP");
+			return -1;
+		}
+	tail = NLMSG_TAIL(n);
+	addattr_l(n,1024,TCA_OPTIONS,NULL,0);
+	addattr_l(n,1024,TCA_ATM_FD,&s,sizeof(s));
+	if (excess) addattr_l(n,1024,TCA_ATM_EXCESS,&excess,sizeof(excess));
+	if (hdr_len != -1) addattr_l(n,1024,TCA_ATM_HDR,hdr,hdr_len);
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+
+
+static int atm_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_ATM_MAX+1];
+	char buffer[MAX_ATM_ADDR_LEN+1];
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_ATM_MAX, opt);
+	if (tb[TCA_ATM_ADDR]) {
+		if (RTA_PAYLOAD(tb[TCA_ATM_ADDR]) <
+		    sizeof(struct sockaddr_atmpvc))
+			fprintf(stderr,"ATM: address too short\n");
+		else {
+			if (atm2text(buffer,MAX_ATM_ADDR_LEN,
+			    RTA_DATA(tb[TCA_ATM_ADDR]),A2T_PRETTY | A2T_NAME) <
+			    0) fprintf(stderr,"atm2text error\n");
+			fprintf(f,"pvc %s ",buffer);
+		}
+	}
+	if (tb[TCA_ATM_HDR]) {
+		int i;
+
+		fprintf(f,"hdr");
+		for (i = 0; i < RTA_PAYLOAD(tb[TCA_ATM_HDR]); i++)
+			fprintf(f,"%c%02x",i ? '.' : ' ',
+			    ((unsigned char *) RTA_DATA(tb[TCA_ATM_HDR]))[i]);
+		if (!i) fprintf(f," .");
+		fprintf(f," ");
+	}
+	if (tb[TCA_ATM_EXCESS]) {
+		__u32 excess;
+
+		if (RTA_PAYLOAD(tb[TCA_ATM_EXCESS]) < sizeof(excess))
+			fprintf(stderr,"ATM: excess class ID too short\n");
+		else {
+			excess = *(__u32 *) RTA_DATA(tb[TCA_ATM_EXCESS]);
+			if (!excess) fprintf(f,"excess clp ");
+			else {
+				char buf[64];
+
+				print_tc_classid(buf,sizeof(buf),excess);
+				fprintf(f,"excess %s ",buf);
+			}
+		}
+	}
+	if (tb[TCA_ATM_STATE]) {
+		static const char *map[] = { ATM_VS2TXT_MAP };
+		int state;
+
+		if (RTA_PAYLOAD(tb[TCA_ATM_STATE]) < sizeof(state))
+			fprintf(stderr,"ATM: state field too short\n");
+		else {
+			state = *(int *) RTA_DATA(tb[TCA_ATM_STATE]);
+			fprintf(f,"%s ",map[state]);
+		}
+	}
+	return 0;
+}
+
+
+struct qdisc_util atm_qdisc_util = {
+	.id 		= "atm",
+	.parse_qopt	= atm_parse_opt,
+	.print_qopt	= atm_print_opt,
+	.parse_copt	= atm_parse_class_opt,
+	.print_copt	= atm_print_opt,
+};
diff --git a/tc/q_cbq.c b/tc/q_cbq.c
new file mode 100644
index 0000000..c99dc3b
--- /dev/null
+++ b/tc/q_cbq.c
@@ -0,0 +1,581 @@
+/*
+ * q_cbq.c		CBQ.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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 "tc_cbq.h"
+
+static void explain_class(void)
+{
+	fprintf(stderr, "Usage: ... cbq bandwidth BPS rate BPS maxburst PKTS [ avpkt BYTES ]\n");
+	fprintf(stderr, "               [ minburst PKTS ] [ bounded ] [ isolated ]\n");
+	fprintf(stderr, "               [ allot BYTES ] [ mpu BYTES ] [ weight RATE ]\n");
+	fprintf(stderr, "               [ prio NUMBER ] [ cell BYTES ] [ ewma LOG ]\n");
+	fprintf(stderr, "               [ estimator INTERVAL TIME_CONSTANT ]\n");
+	fprintf(stderr, "               [ split CLASSID ] [ defmap MASK/CHANGE ]\n");
+	fprintf(stderr, "               [ overhead BYTES ] [ linklayer TYPE ]\n");
+}
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... cbq bandwidth BPS avpkt BYTES [ mpu BYTES ]\n");
+	fprintf(stderr, "               [ cell BYTES ] [ ewma LOG ]\n");
+}
+
+static void explain1(char *arg)
+{
+	fprintf(stderr, "Illegal \"%s\"\n", arg);
+}
+
+#define usage() return(-1)
+
+static int cbq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	struct tc_ratespec r;
+	struct tc_cbq_lssopt lss;
+	__u32 rtab[256];
+	unsigned mpu=0, avpkt=0, allot=0;
+	unsigned short overhead=0;
+	unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+	int cell_log=-1;
+	int ewma_log=-1;
+	struct rtattr *tail;
+
+	memset(&lss, 0, sizeof(lss));
+	memset(&r, 0, sizeof(r));
+
+	while (argc > 0) {
+		if (matches(*argv, "bandwidth") == 0 ||
+		    matches(*argv, "rate") == 0) {
+			NEXT_ARG();
+			if (get_rate(&r.rate, *argv)) {
+				explain1("bandwidth");
+				return -1;
+			}
+		} else if (matches(*argv, "ewma") == 0) {
+			NEXT_ARG();
+			if (get_integer(&ewma_log, *argv, 0)) {
+				explain1("ewma");
+				return -1;
+			}
+			if (ewma_log > 31) {
+				fprintf(stderr, "ewma_log must be < 32\n");
+				return -1;
+			}
+		} else if (matches(*argv, "cell") == 0) {
+			unsigned cell;
+			int i;
+			NEXT_ARG();
+			if (get_size(&cell, *argv)) {
+				explain1("cell");
+				return -1;
+			}
+			for (i=0; i<32; i++)
+				if ((1<<i) == cell)
+					break;
+			if (i>=32) {
+				fprintf(stderr, "cell must be 2^n\n");
+				return -1;
+			}
+			cell_log = i;
+		} else if (matches(*argv, "avpkt") == 0) {
+			NEXT_ARG();
+			if (get_size(&avpkt, *argv)) {
+				explain1("avpkt");
+				return -1;
+			}
+		} else if (matches(*argv, "mpu") == 0) {
+			NEXT_ARG();
+			if (get_size(&mpu, *argv)) {
+				explain1("mpu");
+				return -1;
+			}
+		} else if (matches(*argv, "allot") == 0) {
+			NEXT_ARG();
+			/* Accept and ignore "allot" for backward compatibility */
+			if (get_size(&allot, *argv)) {
+				explain1("allot");
+				return -1;
+			}
+		} else if (matches(*argv, "overhead") == 0) {
+			NEXT_ARG();
+			if (get_u16(&overhead, *argv, 10)) {
+				explain1("overhead"); return -1;
+			}
+		} else if (matches(*argv, "linklayer") == 0) {
+			NEXT_ARG();
+			if (get_linklayer(&linklayer, *argv)) {
+				explain1("linklayer"); return -1;
+			}
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	/* OK. All options are parsed. */
+
+	if (r.rate == 0) {
+		fprintf(stderr, "CBQ: bandwidth is required parameter.\n");
+		return -1;
+	}
+	if (avpkt == 0) {
+		fprintf(stderr, "CBQ: \"avpkt\" is required.\n");
+		return -1;
+	}
+	if (allot < (avpkt*3)/2)
+		allot = (avpkt*3)/2;
+
+	r.mpu = mpu;
+	r.overhead = overhead;
+	if (tc_calc_rtable(&r, rtab, cell_log, allot, linklayer) < 0) {
+		fprintf(stderr, "CBQ: failed to calculate rate table.\n");
+		return -1;
+	}
+
+	if (ewma_log < 0)
+		ewma_log = TC_CBQ_DEF_EWMA;
+	lss.ewma_log = ewma_log;
+	lss.maxidle = tc_calc_xmittime(r.rate, avpkt);
+	lss.change = TCF_CBQ_LSS_MAXIDLE|TCF_CBQ_LSS_EWMA|TCF_CBQ_LSS_AVPKT;
+	lss.avpkt = avpkt;
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	addattr_l(n, 1024, TCA_CBQ_RATE, &r, sizeof(r));
+	addattr_l(n, 1024, TCA_CBQ_LSSOPT, &lss, sizeof(lss));
+	addattr_l(n, 3024, TCA_CBQ_RTAB, rtab, 1024);
+	if (show_raw) {
+		int i;
+		for (i=0; i<256; i++)
+			printf("%u ", rtab[i]);
+		printf("\n");
+	}
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int cbq_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	int wrr_ok=0, fopt_ok=0;
+	struct tc_ratespec r;
+	struct tc_cbq_lssopt lss;
+	struct tc_cbq_wrropt wrr;
+	struct tc_cbq_fopt fopt;
+	struct tc_cbq_ovl ovl;
+	__u32 rtab[256];
+	unsigned mpu=0;
+	int cell_log=-1;
+	int ewma_log=-1;
+	unsigned bndw = 0;
+	unsigned minburst=0, maxburst=0;
+	unsigned short overhead=0;
+	unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+	struct rtattr *tail;
+
+	memset(&r, 0, sizeof(r));
+	memset(&lss, 0, sizeof(lss));
+	memset(&wrr, 0, sizeof(wrr));
+	memset(&fopt, 0, sizeof(fopt));
+	memset(&ovl, 0, sizeof(ovl));
+
+	while (argc > 0) {
+		if (matches(*argv, "rate") == 0) {
+			NEXT_ARG();
+			if (get_rate(&r.rate, *argv)) {
+				explain1("rate");
+				return -1;
+			}
+		} else if (matches(*argv, "bandwidth") == 0) {
+			NEXT_ARG();
+			if (get_rate(&bndw, *argv)) {
+				explain1("bandwidth");
+				return -1;
+			}
+		} else if (matches(*argv, "minidle") == 0) {
+			NEXT_ARG();
+			if (get_u32(&lss.minidle, *argv, 0)) {
+				explain1("minidle");
+				return -1;
+			}
+			lss.change |= TCF_CBQ_LSS_MINIDLE;
+		} else if (matches(*argv, "minburst") == 0) {
+			NEXT_ARG();
+			if (get_u32(&minburst, *argv, 0)) {
+				explain1("minburst");
+				return -1;
+			}
+			lss.change |= TCF_CBQ_LSS_OFFTIME;
+		} else if (matches(*argv, "maxburst") == 0) {
+			NEXT_ARG();
+			if (get_u32(&maxburst, *argv, 0)) {
+				explain1("maxburst");
+				return -1;
+			}
+			lss.change |= TCF_CBQ_LSS_MAXIDLE;
+		} else if (matches(*argv, "bounded") == 0) {
+			lss.flags |= TCF_CBQ_LSS_BOUNDED;
+			lss.change |= TCF_CBQ_LSS_FLAGS;
+		} else if (matches(*argv, "borrow") == 0) {
+			lss.flags &= ~TCF_CBQ_LSS_BOUNDED;
+			lss.change |= TCF_CBQ_LSS_FLAGS;
+		} else if (matches(*argv, "isolated") == 0) {
+			lss.flags |= TCF_CBQ_LSS_ISOLATED;
+			lss.change |= TCF_CBQ_LSS_FLAGS;
+		} else if (matches(*argv, "sharing") == 0) {
+			lss.flags &= ~TCF_CBQ_LSS_ISOLATED;
+			lss.change |= TCF_CBQ_LSS_FLAGS;
+		} else if (matches(*argv, "ewma") == 0) {
+			NEXT_ARG();
+			if (get_integer(&ewma_log, *argv, 0)) {
+				explain1("ewma");
+				return -1;
+			}
+			if (ewma_log > 31) {
+				fprintf(stderr, "ewma_log must be < 32\n");
+				return -1;
+			}
+			lss.change |= TCF_CBQ_LSS_EWMA;
+		} else if (matches(*argv, "cell") == 0) {
+			unsigned cell;
+			int i;
+			NEXT_ARG();
+			if (get_size(&cell, *argv)) {
+				explain1("cell");
+				return -1;
+			}
+			for (i=0; i<32; i++)
+				if ((1<<i) == cell)
+					break;
+			if (i>=32) {
+				fprintf(stderr, "cell must be 2^n\n");
+				return -1;
+			}
+			cell_log = i;
+		} else if (matches(*argv, "prio") == 0) {
+			unsigned prio;
+			NEXT_ARG();
+			if (get_u32(&prio, *argv, 0)) {
+				explain1("prio");
+				return -1;
+			}
+			if (prio > TC_CBQ_MAXPRIO) {
+				fprintf(stderr, "\"prio\" must be number in the range 1...%d\n", TC_CBQ_MAXPRIO);
+				return -1;
+			}
+			wrr.priority = prio;
+			wrr_ok++;
+		} else if (matches(*argv, "allot") == 0) {
+			NEXT_ARG();
+			if (get_size(&wrr.allot, *argv)) {
+				explain1("allot");
+				return -1;
+			}
+		} else if (matches(*argv, "avpkt") == 0) {
+			NEXT_ARG();
+			if (get_size(&lss.avpkt, *argv)) {
+				explain1("avpkt");
+				return -1;
+			}
+			lss.change |= TCF_CBQ_LSS_AVPKT;
+		} else if (matches(*argv, "mpu") == 0) {
+			NEXT_ARG();
+			if (get_size(&mpu, *argv)) {
+				explain1("mpu");
+				return -1;
+			}
+		} else if (matches(*argv, "weight") == 0) {
+			NEXT_ARG();
+			if (get_size(&wrr.weight, *argv)) {
+				explain1("weight");
+				return -1;
+			}
+			wrr_ok++;
+		} else if (matches(*argv, "split") == 0) {
+			NEXT_ARG();
+			if (get_tc_classid(&fopt.split, *argv)) {
+				fprintf(stderr, "Invalid split node ID.\n");
+				usage();
+			}
+			fopt_ok++;
+		} else if (matches(*argv, "defmap") == 0) {
+			int err;
+			NEXT_ARG();
+			err = sscanf(*argv, "%08x/%08x", &fopt.defmap, &fopt.defchange);
+			if (err < 1) {
+				fprintf(stderr, "Invalid defmap, should be MASK32[/MASK]\n");
+				return -1;
+			}
+			if (err == 1)
+				fopt.defchange = ~0;
+			fopt_ok++;
+		} else if (matches(*argv, "overhead") == 0) {
+			NEXT_ARG();
+			if (get_u16(&overhead, *argv, 10)) {
+				explain1("overhead"); return -1;
+			}
+		} else if (matches(*argv, "linklayer") == 0) {
+			NEXT_ARG();
+			if (get_linklayer(&linklayer, *argv)) {
+				explain1("linklayer"); return -1;
+			}
+		} else if (matches(*argv, "help") == 0) {
+			explain_class();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain_class();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	/* OK. All options are parsed. */
+
+	/* 1. Prepare link sharing scheduler parameters */
+	if (r.rate) {
+		unsigned pktsize = wrr.allot;
+		if (wrr.allot < (lss.avpkt*3)/2)
+			wrr.allot = (lss.avpkt*3)/2;
+		r.mpu = mpu;
+		r.overhead = overhead;
+		if (tc_calc_rtable(&r, rtab, cell_log, pktsize, linklayer) < 0) {
+			fprintf(stderr, "CBQ: failed to calculate rate table.\n");
+			return -1;
+		}
+	}
+	if (ewma_log < 0)
+		ewma_log = TC_CBQ_DEF_EWMA;
+	lss.ewma_log = ewma_log;
+	if (lss.change&(TCF_CBQ_LSS_OFFTIME|TCF_CBQ_LSS_MAXIDLE)) {
+		if (lss.avpkt == 0) {
+			fprintf(stderr, "CBQ: avpkt is required for max/minburst.\n");
+			return -1;
+		}
+		if (bndw==0 || r.rate == 0) {
+			fprintf(stderr, "CBQ: bandwidth&rate are required for max/minburst.\n");
+			return -1;
+		}
+	}
+	if (wrr.priority == 0 && (n->nlmsg_flags&NLM_F_EXCL)) {
+		wrr_ok = 1;
+		wrr.priority = TC_CBQ_MAXPRIO;
+		if (wrr.allot == 0)
+			wrr.allot = (lss.avpkt*3)/2;
+	}
+	if (wrr_ok) {
+		if (wrr.weight == 0)
+			wrr.weight = (wrr.priority == TC_CBQ_MAXPRIO) ? 1 : r.rate;
+		if (wrr.allot == 0) {
+			fprintf(stderr, "CBQ: \"allot\" is required to set WRR parameters.\n");
+			return -1;
+		}
+	}
+	if (lss.change&TCF_CBQ_LSS_MAXIDLE) {
+		lss.maxidle = tc_cbq_calc_maxidle(bndw, r.rate, lss.avpkt, ewma_log, maxburst);
+		lss.change |= TCF_CBQ_LSS_MAXIDLE;
+		lss.change |= TCF_CBQ_LSS_EWMA|TCF_CBQ_LSS_AVPKT;
+	}
+	if (lss.change&TCF_CBQ_LSS_OFFTIME) {
+		lss.offtime = tc_cbq_calc_offtime(bndw, r.rate, lss.avpkt, ewma_log, minburst);
+		lss.change |= TCF_CBQ_LSS_OFFTIME;
+		lss.change |= TCF_CBQ_LSS_EWMA|TCF_CBQ_LSS_AVPKT;
+	}
+	if (lss.change&TCF_CBQ_LSS_MINIDLE) {
+		lss.minidle <<= lss.ewma_log;
+		lss.change |= TCF_CBQ_LSS_EWMA;
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	if (lss.change) {
+		lss.change |= TCF_CBQ_LSS_FLAGS;
+		addattr_l(n, 1024, TCA_CBQ_LSSOPT, &lss, sizeof(lss));
+	}
+	if (wrr_ok)
+		addattr_l(n, 1024, TCA_CBQ_WRROPT, &wrr, sizeof(wrr));
+	if (fopt_ok)
+		addattr_l(n, 1024, TCA_CBQ_FOPT, &fopt, sizeof(fopt));
+	if (r.rate) {
+		addattr_l(n, 1024, TCA_CBQ_RATE, &r, sizeof(r));
+		addattr_l(n, 3024, TCA_CBQ_RTAB, rtab, 1024);
+		if (show_raw) {
+			int i;
+			for (i=0; i<256; i++)
+				printf("%u ", rtab[i]);
+			printf("\n");
+		}
+	}
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+
+static int cbq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_CBQ_MAX+1];
+	struct tc_ratespec *r = NULL;
+	struct tc_cbq_lssopt *lss = NULL;
+	struct tc_cbq_wrropt *wrr = NULL;
+	struct tc_cbq_fopt *fopt = NULL;
+	struct tc_cbq_ovl *ovl = NULL;
+	SPRINT_BUF(b1);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_CBQ_MAX, opt);
+
+	if (tb[TCA_CBQ_RATE]) {
+		if (RTA_PAYLOAD(tb[TCA_CBQ_RATE]) < sizeof(*r))
+			fprintf(stderr, "CBQ: too short rate opt\n");
+		else
+			r = RTA_DATA(tb[TCA_CBQ_RATE]);
+	}
+	if (tb[TCA_CBQ_LSSOPT]) {
+		if (RTA_PAYLOAD(tb[TCA_CBQ_LSSOPT]) < sizeof(*lss))
+			fprintf(stderr, "CBQ: too short lss opt\n");
+		else
+			lss = RTA_DATA(tb[TCA_CBQ_LSSOPT]);
+	}
+	if (tb[TCA_CBQ_WRROPT]) {
+		if (RTA_PAYLOAD(tb[TCA_CBQ_WRROPT]) < sizeof(*wrr))
+			fprintf(stderr, "CBQ: too short wrr opt\n");
+		else
+			wrr = RTA_DATA(tb[TCA_CBQ_WRROPT]);
+	}
+	if (tb[TCA_CBQ_FOPT]) {
+		if (RTA_PAYLOAD(tb[TCA_CBQ_FOPT]) < sizeof(*fopt))
+			fprintf(stderr, "CBQ: too short fopt\n");
+		else
+			fopt = RTA_DATA(tb[TCA_CBQ_FOPT]);
+	}
+	if (tb[TCA_CBQ_OVL_STRATEGY]) {
+		if (RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]) < sizeof(*ovl))
+			fprintf(stderr, "CBQ: too short overlimit strategy %u/%u\n",
+				(unsigned) RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]),
+				(unsigned) sizeof(*ovl));
+		else
+			ovl = RTA_DATA(tb[TCA_CBQ_OVL_STRATEGY]);
+	}
+
+	if (r) {
+		char buf[64];
+		print_rate(buf, sizeof(buf), r->rate);
+		fprintf(f, "rate %s ", buf);
+		if (show_details) {
+			fprintf(f, "cell %ub ", 1<<r->cell_log);
+			if (r->mpu)
+				fprintf(f, "mpu %ub ", r->mpu);
+			if (r->overhead)
+				fprintf(f, "overhead %ub ", r->overhead);
+		}
+	}
+	if (lss && lss->flags) {
+		int comma=0;
+		fprintf(f, "(");
+		if (lss->flags&TCF_CBQ_LSS_BOUNDED) {
+			fprintf(f, "bounded");
+			comma=1;
+		}
+		if (lss->flags&TCF_CBQ_LSS_ISOLATED) {
+			if (comma)
+				fprintf(f, ",");
+			fprintf(f, "isolated");
+		}
+		fprintf(f, ") ");
+	}
+	if (wrr) {
+		if (wrr->priority != TC_CBQ_MAXPRIO)
+			fprintf(f, "prio %u", wrr->priority);
+		else
+			fprintf(f, "prio no-transmit");
+		if (show_details) {
+			char buf[64];
+			fprintf(f, "/%u ", wrr->cpriority);
+			if (wrr->weight != 1) {
+				print_rate(buf, sizeof(buf), wrr->weight);
+				fprintf(f, "weight %s ", buf);
+			}
+			if (wrr->allot)
+				fprintf(f, "allot %ub ", wrr->allot);
+		}
+	}
+	if (lss && show_details) {
+		fprintf(f, "\nlevel %u ewma %u avpkt %ub ", lss->level, lss->ewma_log, lss->avpkt);
+		if (lss->maxidle) {
+			fprintf(f, "maxidle %s ", sprint_ticks(lss->maxidle>>lss->ewma_log, b1));
+			if (show_raw)
+				fprintf(f, "[%08x] ", lss->maxidle);
+		}
+		if (lss->minidle!=0x7fffffff) {
+			fprintf(f, "minidle %s ", sprint_ticks(lss->minidle>>lss->ewma_log, b1));
+			if (show_raw)
+				fprintf(f, "[%08x] ", lss->minidle);
+		}
+		if (lss->offtime) {
+			fprintf(f, "offtime %s ", sprint_ticks(lss->offtime, b1));
+			if (show_raw)
+				fprintf(f, "[%08x] ", lss->offtime);
+		}
+	}
+	if (fopt && show_details) {
+		char buf[64];
+		print_tc_classid(buf, sizeof(buf), fopt->split);
+		fprintf(f, "\nsplit %s ", buf);
+		if (fopt->defmap) {
+			fprintf(f, "defmap %08x", fopt->defmap);
+		}
+	}
+	return 0;
+}
+
+static int cbq_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+	struct tc_cbq_xstats *st;
+
+	if (xstats == NULL)
+		return 0;
+
+	if (RTA_PAYLOAD(xstats) < sizeof(*st))
+		return -1;
+
+	st = RTA_DATA(xstats);
+	fprintf(f, "  borrowed %u overactions %u avgidle %g undertime %g", st->borrows,
+		st->overactions, (double)st->avgidle, (double)st->undertime);
+	return 0;
+}
+
+struct qdisc_util cbq_qdisc_util = {
+	.id		= "cbq",
+	.parse_qopt	= cbq_parse_opt,
+	.print_qopt	= cbq_print_opt,
+	.print_xstats	= cbq_print_xstats,
+	.parse_copt	= cbq_parse_class_opt,
+	.print_copt	= cbq_print_opt,
+};
+
diff --git a/tc/q_drr.c b/tc/q_drr.c
new file mode 100644
index 0000000..7d2d874
--- /dev/null
+++ b/tc/q_drr.c
@@ -0,0 +1,124 @@
+/*
+ * q_drr.c		DRR.
+ *
+ *		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>
+ *
+ */
+
+#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: ... drr\n");
+}
+
+static void explain2(void)
+{
+	fprintf(stderr, "Usage: ... drr quantum SIZE\n");
+}
+
+#define usage() return(-1)
+
+static int drr_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	while (argc > 0) {
+		if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+	return 0;
+}
+
+static int drr_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
+			       struct nlmsghdr *n)
+{
+	struct rtattr *tail;
+	__u32 tmp;
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+
+	while (argc > 0) {
+		if (strcmp(*argv, "quantum") == 0) {
+			NEXT_ARG();
+			if (get_size(&tmp, *argv)) {
+				fprintf(stderr, "Illegal \"quantum\"\n");
+				return -1;
+			}
+			addattr_l(n, 1024, TCA_DRR_QUANTUM, &tmp, sizeof(tmp));
+		} else if (strcmp(*argv, "help") == 0) {
+			explain2();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain2();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *)tail;
+	return 0;
+}
+
+static int drr_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_DRR_MAX + 1];
+	SPRINT_BUF(b1);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_DRR_MAX, opt);
+
+	if (tb[TCA_DRR_QUANTUM])
+		fprintf(f, "quantum %s ",
+			sprint_size(*(__u32 *)RTA_DATA(tb[TCA_DRR_QUANTUM]), b1));
+	return 0;
+}
+
+static int drr_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+	struct tc_drr_stats *x;
+	SPRINT_BUF(b1);
+
+	if (xstats == NULL)
+		return 0;
+	if (RTA_PAYLOAD(xstats) < sizeof(*x))
+		return -1;
+	x = RTA_DATA(xstats);
+
+	fprintf(f, " deficit %s ", sprint_size(x->deficit, b1));
+	return 0;
+}
+
+struct qdisc_util drr_qdisc_util = {
+	.id		= "drr",
+	.parse_qopt	= drr_parse_opt,
+	.print_qopt	= drr_print_opt,
+	.print_xstats	= drr_print_xstats,
+	.parse_copt	= drr_parse_class_opt,
+	.print_copt	= drr_print_opt,
+};
diff --git a/tc/q_dsmark.c b/tc/q_dsmark.c
new file mode 100644
index 0000000..cdb5bf2
--- /dev/null
+++ b/tc/q_dsmark.c
@@ -0,0 +1,177 @@
+/*
+ * q_dsmark.c		Differentiated Services field marking.
+ *
+ * Hacked 1998,1999 by Werner Almesberger, EPFL ICA
+ *
+ */
+
+#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"
+
+
+#define usage() return(-1)
+
+
+static void explain(void)
+{
+	fprintf(stderr,"Usage: dsmark indices INDICES [ default_index "
+	    "DEFAULT_INDEX ] [ set_tc_index ]\n");
+}
+
+
+static int dsmark_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+    struct nlmsghdr *n)
+{
+	struct rtattr *tail;
+	__u16 ind;
+	char *end;
+	int dflt,set_tc_index;
+
+	ind = set_tc_index = 0;
+	dflt = -1;
+	while (argc > 0) {
+		if (!strcmp(*argv,"indices")) {
+			NEXT_ARG();
+			ind = strtoul(*argv,&end,0);
+			if (*end) {
+				explain();
+				return -1;
+			}
+		}
+		else if (!strcmp(*argv,"default_index") || !strcmp(*argv,
+		    "default")) {
+			NEXT_ARG();
+			dflt = strtoul(*argv,&end,0);
+			if (*end) {
+				explain();
+				return -1;
+			}
+		}
+		else if (!strcmp(*argv,"set_tc_index")) {
+			set_tc_index = 1;
+		}
+		else {
+			explain();
+			return -1;
+		}
+		argc--;
+		argv++;
+	}
+	if (!ind) {
+		explain();
+		return -1;
+	}
+	tail = NLMSG_TAIL(n);
+	addattr_l(n,1024,TCA_OPTIONS,NULL,0);
+	addattr_l(n,1024,TCA_DSMARK_INDICES,&ind,sizeof(ind));
+	if (dflt != -1) {
+	    __u16 tmp = dflt;
+
+	    addattr_l(n,1024,TCA_DSMARK_DEFAULT_INDEX,&tmp,sizeof(tmp));
+	}
+	if (set_tc_index) addattr_l(n,1024,TCA_DSMARK_SET_TC_INDEX,NULL,0);
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+
+static void explain_class(void)
+{
+	fprintf(stderr, "Usage: ... dsmark [ mask MASK ] [ value VALUE ]\n");
+}
+
+
+static int dsmark_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
+   struct nlmsghdr *n)
+{
+	struct rtattr *tail;
+	__u8 tmp;
+	char *end;
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n,1024,TCA_OPTIONS,NULL,0);
+	while (argc > 0) {
+		if (!strcmp(*argv,"mask")) {
+			NEXT_ARG();
+			tmp = strtoul(*argv,&end,0);
+			if (*end) {
+				explain_class();
+				return -1;
+			}
+			addattr_l(n,1024,TCA_DSMARK_MASK,&tmp,1);
+		}
+		else if (!strcmp(*argv,"value")) {
+			NEXT_ARG();
+			tmp = strtoul(*argv,&end,0);
+			if (*end) {
+				explain_class();
+				return -1;
+			}
+			addattr_l(n,1024,TCA_DSMARK_VALUE,&tmp,1);
+		}
+		else {
+			explain_class();
+			return -1;
+		}
+		argc--;
+		argv++;
+	}
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+
+
+static int dsmark_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_DSMARK_MAX+1];
+
+	if (!opt) return 0;
+	memset(tb, 0, sizeof(tb));
+	parse_rtattr(tb, TCA_DSMARK_MAX, RTA_DATA(opt), RTA_PAYLOAD(opt));
+	if (tb[TCA_DSMARK_MASK]) {
+		if (!RTA_PAYLOAD(tb[TCA_DSMARK_MASK]))
+			fprintf(stderr,"dsmark: empty mask\n");
+		else fprintf(f,"mask 0x%02x ",
+			    *(__u8 *) RTA_DATA(tb[TCA_DSMARK_MASK]));
+	}
+	if (tb[TCA_DSMARK_VALUE]) {
+		if (!RTA_PAYLOAD(tb[TCA_DSMARK_VALUE]))
+			fprintf(stderr,"dsmark: empty value\n");
+		else fprintf(f,"value 0x%02x ",
+			    *(__u8 *) RTA_DATA(tb[TCA_DSMARK_VALUE]));
+	}
+	if (tb[TCA_DSMARK_INDICES]) {
+		if (RTA_PAYLOAD(tb[TCA_DSMARK_INDICES]) < sizeof(__u16))
+			fprintf(stderr,"dsmark: indices too short\n");
+		else fprintf(f,"indices 0x%04x ",
+			    *(__u16 *) RTA_DATA(tb[TCA_DSMARK_INDICES]));
+	}
+	if (tb[TCA_DSMARK_DEFAULT_INDEX]) {
+		if (RTA_PAYLOAD(tb[TCA_DSMARK_DEFAULT_INDEX]) < sizeof(__u16))
+			fprintf(stderr,"dsmark: default_index too short\n");
+		else fprintf(f,"default_index 0x%04x ",
+			    *(__u16 *) RTA_DATA(tb[TCA_DSMARK_DEFAULT_INDEX]));
+	}
+	if (tb[TCA_DSMARK_SET_TC_INDEX]) fprintf(f,"set_tc_index ");
+	return 0;
+}
+
+
+struct qdisc_util dsmark_qdisc_util = {
+	.id		= "dsmark",
+	.parse_qopt	= dsmark_parse_opt,
+	.print_qopt	= dsmark_print_opt,
+	.parse_copt	= dsmark_parse_class_opt,
+	.print_copt	= dsmark_print_opt,
+};
diff --git a/tc/q_fifo.c b/tc/q_fifo.c
new file mode 100644
index 0000000..9f3b3eb
--- /dev/null
+++ b/tc/q_fifo.c
@@ -0,0 +1,98 @@
+/*
+ * q_fifo.c		FIFO.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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: ... [p|b]fifo [ limit NUMBER ]\n");
+}
+
+#define usage() return(-1)
+
+static int fifo_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	int ok=0;
+	struct tc_fifo_qopt opt;
+	memset(&opt, 0, sizeof(opt));
+
+	while (argc > 0) {
+		if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			if (get_size(&opt.limit, *argv)) {
+				fprintf(stderr, "Illegal \"limit\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	if (ok)
+		addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+	return 0;
+}
+
+static int fifo_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct tc_fifo_qopt *qopt;
+
+	if (opt == NULL)
+		return 0;
+
+	if (RTA_PAYLOAD(opt)  < sizeof(*qopt))
+		return -1;
+	qopt = RTA_DATA(opt);
+	if (strcmp(qu->id, "bfifo") == 0) {
+		SPRINT_BUF(b1);
+		fprintf(f, "limit %s", sprint_size(qopt->limit, b1));
+	} else
+		fprintf(f, "limit %up", qopt->limit);
+	return 0;
+}
+
+
+struct qdisc_util bfifo_qdisc_util = {
+	.id = "bfifo",
+	.parse_qopt = fifo_parse_opt,
+	.print_qopt = fifo_print_opt,
+};
+
+struct qdisc_util pfifo_qdisc_util = {
+	.id = "pfifo",
+	.parse_qopt = fifo_parse_opt,
+	.print_qopt = fifo_print_opt,
+};
+
+extern int prio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt);
+struct qdisc_util pfifo_fast_qdisc_util = {
+	.id = "pfifo_fast",
+	.print_qopt = prio_print_opt,
+};
diff --git a/tc/q_gred.c b/tc/q_gred.c
new file mode 100644
index 0000000..ecef42e
--- /dev/null
+++ b/tc/q_gred.c
@@ -0,0 +1,317 @@
+/*
+ * q_gred.c		GRED.
+ *
+ *		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:    J Hadi Salim(hadi@nortelnetworks.com)
+ *             code ruthlessly ripped from
+ *	       Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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 "tc_red.h"
+
+
+#if 0
+#define DPRINTF(format,args...) fprintf(stderr,format,##args)
+#else
+#define DPRINTF(format,args...)
+#endif
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... gred DP drop-probability limit BYTES "
+	    "min BYTES max BYTES\n");
+	fprintf(stderr, "    avpkt BYTES burst PACKETS probability PROBABILITY "
+	    "bandwidth KBPS\n");
+	fprintf(stderr, "    [prio value]\n");
+	fprintf(stderr," OR ...\n");
+	fprintf(stderr," gred setup DPs <num of DPs> default <default DP> "
+	    "[grio]\n");
+}
+
+#define usage() return(-1)
+
+static int init_gred(struct qdisc_util *qu, int argc, char **argv, 
+		     struct nlmsghdr *n)
+{
+
+	struct rtattr *tail;
+	struct tc_gred_sopt opt;
+	int dps = 0;
+	int def_dp = -1;
+
+	while (argc > 0) {
+		DPRINTF(stderr,"init_gred: invoked with %s\n",*argv);
+		if (strcmp(*argv, "DPs") == 0) {
+			NEXT_ARG();
+			DPRINTF(stderr,"init_gred: next_arg with %s\n",*argv);
+			dps = strtol(*argv, (char **)NULL, 10);
+			if (dps < 0 || dps >MAX_DPs) {
+				fprintf(stderr, "DPs =%d\n", dps);
+				fprintf(stderr, "Illegal \"DPs\"\n");
+				fprintf(stderr, "GRED: only %d DPs are "
+					"currently supported\n",MAX_DPs);
+				return -1;
+			}
+		} else if (strcmp(*argv, "default") == 0) {
+			NEXT_ARG();
+			def_dp = strtol(*argv, (char **)NULL, 10);
+			if (dps == 0) {
+				fprintf(stderr, "\"default DP\" must be "
+					"defined after DPs\n");
+				return -1;
+			}
+			if (def_dp < 0 || def_dp > dps) {
+				fprintf(stderr, 
+					"\"default DP\" must be less than %d\n",
+					opt.DPs);
+				return -1;
+			}
+		} else if (strcmp(*argv, "grio") == 0) {
+			opt.grio=1;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	if (!dps || def_dp == -1) {
+		fprintf(stderr, "Illegal gred setup parameters \n");
+		return -1;
+	}
+
+	memset(&opt, 0, sizeof(struct tc_gred_sopt));
+	opt.DPs = dps;
+	opt.def_DP = def_dp;
+
+	DPRINTF("TC_GRED: sending DPs=%d default=%d\n",opt.DPs,opt.def_DP);
+	n->nlmsg_flags|=NLM_F_CREATE;
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	addattr_l(n, 1024, TCA_GRED_DPS, &opt, sizeof(struct tc_gred_sopt));
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+/*
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+*/
+static int gred_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	int ok=0;
+	struct tc_gred_qopt opt;
+	unsigned burst = 0;
+	unsigned avpkt = 0;
+	double probability = 0.02;
+	unsigned rate = 0;
+	int wlog;
+	__u8 sbuf[256];
+	struct rtattr *tail;
+
+	memset(&opt, 0, sizeof(opt));
+
+	while (argc > 0) {
+		if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			if (get_size(&opt.limit, *argv)) {
+				fprintf(stderr, "Illegal \"limit\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "setup") == 0) {
+			if (ok) {
+				fprintf(stderr, "Illegal \"setup\"\n");
+				return -1;
+			}
+		return init_gred(qu,argc-1, argv+1,n);
+
+		} else if (strcmp(*argv, "min") == 0) {
+			NEXT_ARG();
+			if (get_size(&opt.qth_min, *argv)) {
+				fprintf(stderr, "Illegal \"min\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "max") == 0) {
+			NEXT_ARG();
+			if (get_size(&opt.qth_max, *argv)) {
+				fprintf(stderr, "Illegal \"max\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "DP") == 0) {
+			NEXT_ARG();
+			opt.DP=strtol(*argv, (char **)NULL, 10);
+			DPRINTF ("\n ******* DP =%u\n",opt.DP);
+			if (opt.DP >MAX_DPs) { /* need a better error check */
+				fprintf(stderr, "DP =%u \n",opt.DP);
+				fprintf(stderr, "Illegal \"DP\"\n");
+				fprintf(stderr, "GRED: only %d DPs are currently supported\n",MAX_DPs);
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "burst") == 0) {
+			NEXT_ARG();
+                        if (get_unsigned(&burst, *argv, 0)) {
+				fprintf(stderr, "Illegal \"burst\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "avpkt") == 0) {
+			NEXT_ARG();
+			if (get_size(&avpkt, *argv)) {
+				fprintf(stderr, "Illegal \"avpkt\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "probability") == 0) {
+			NEXT_ARG();
+			if (sscanf(*argv, "%lg", &probability) != 1) {
+				fprintf(stderr, "Illegal \"probability\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "prio") == 0) {
+			NEXT_ARG();
+			opt.prio=strtol(*argv, (char **)NULL, 10);
+			/* some error check here */
+			ok++;
+		} else if (strcmp(*argv, "bandwidth") == 0) {
+			NEXT_ARG();
+			if (get_rate(&rate, *argv)) {
+				fprintf(stderr, "Illegal \"bandwidth\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	if (!ok)
+		return 0;
+
+	if (rate == 0)
+		get_rate(&rate, "10Mbit");
+
+	if (!opt.qth_min || !opt.qth_max || !burst || !opt.limit || !avpkt ||
+	    (opt.DP<0)) {
+		fprintf(stderr, "Required parameter (min, max, burst, limit, "
+		    "avpket, DP) is missing\n");
+		return -1;
+	}
+
+	if ((wlog = tc_red_eval_ewma(opt.qth_min, burst, avpkt)) < 0) {
+		fprintf(stderr, "GRED: failed to calculate EWMA constant.\n");
+		return -1;
+	}
+	if (wlog >= 10)
+		fprintf(stderr, "GRED: WARNING. Burst %d seems to be to "
+		    "large.\n", burst);
+	opt.Wlog = wlog;
+	if ((wlog = tc_red_eval_P(opt.qth_min, opt.qth_max, probability)) < 0) {
+		fprintf(stderr, "GRED: failed to calculate probability.\n");
+		return -1;
+	}
+	opt.Plog = wlog;
+	if ((wlog = tc_red_eval_idle_damping(opt.Wlog, avpkt, rate, sbuf)) < 0)
+	    {
+		fprintf(stderr, "GRED: failed to calculate idle damping "
+		    "table.\n");
+		return -1;
+	}
+	opt.Scell_log = wlog;
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	addattr_l(n, 1024, TCA_GRED_PARMS, &opt, sizeof(opt));
+	addattr_l(n, 1024, TCA_GRED_STAB, sbuf, 256);
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int gred_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_GRED_STAB+1];
+	struct tc_gred_qopt *qopt;
+	int i;
+	SPRINT_BUF(b1);
+	SPRINT_BUF(b2);
+	SPRINT_BUF(b3);
+	SPRINT_BUF(b4);
+	SPRINT_BUF(b5);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_GRED_STAB, opt);
+
+	if (tb[TCA_GRED_PARMS] == NULL)
+		return -1;
+
+	qopt = RTA_DATA(tb[TCA_GRED_PARMS]);
+	if (RTA_PAYLOAD(tb[TCA_GRED_PARMS])  < sizeof(*qopt)*MAX_DPs) {
+		fprintf(f,"\n GRED received message smaller than expected\n");
+		return -1;
+		}
+
+/* Bad hack! should really return a proper message as shown above*/
+
+	for (i=0;i<MAX_DPs;i++, qopt++) {
+		if (qopt->DP >= MAX_DPs) continue;
+		fprintf(f, "\n DP:%d (prio %d) Average Queue %s Measured "
+		    "Queue %s  ",
+			qopt->DP,
+			qopt->prio,
+			sprint_size(qopt->qave, b4),
+			sprint_size(qopt->backlog, b5));
+		fprintf(f, "\n\t Packet drops: %d (forced %d early %d)  ",
+			qopt->forced+qopt->early,
+			qopt->forced,
+			qopt->early);
+		fprintf(f, "\n\t Packet totals: %u (bytes %u)  ",
+			qopt->packets,
+			qopt->bytesin);
+		if (show_details)
+			fprintf(f, "\n limit %s min %s max %s ",
+				sprint_size(qopt->limit, b1),
+				sprint_size(qopt->qth_min, b2),
+				sprint_size(qopt->qth_max, b3));
+				fprintf(f, "ewma %u Plog %u Scell_log %u",
+				    qopt->Wlog, qopt->Plog, qopt->Scell_log);
+	}
+	return 0;
+}
+
+struct qdisc_util gred_qdisc_util = {
+	.id		= "gred",
+	.parse_qopt	= gred_parse_opt,
+	.print_qopt	= gred_print_opt,
+};
diff --git a/tc/q_hfsc.c b/tc/q_hfsc.c
new file mode 100644
index 0000000..b190c71
--- /dev/null
+++ b/tc/q_hfsc.c
@@ -0,0 +1,406 @@
+/*
+ * q_hfsc.c	HFSC.
+ *
+ *		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>
+ *
+ */
+
+#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 int hfsc_get_sc(int *, char ***, struct tc_service_curve *);
+
+
+static void
+explain_qdisc(void)
+{
+	fprintf(stderr,
+		"Usage: ... hfsc [ default CLASSID ]\n"
+		"\n"
+		" default: default class for unclassified packets\n"
+	);
+}
+
+static void
+explain_class(void)
+{
+	fprintf(stderr,
+		"Usage: ... hfsc [ [ rt SC ] [ ls SC ] | [ sc SC ] ] [ ul SC ]\n"
+		"\n"
+		"SC := [ [ m1 BPS ] [ d SEC ] m2 BPS\n"
+		"\n"
+		" m1 : slope of first segment\n"
+		" d  : x-coordinate of intersection\n"
+		" m2 : slope of second segment\n"
+		"\n"
+		"Alternative format:\n"
+		"\n"
+		"SC := [ [ umax BYTE ] dmax SEC ] rate BPS\n"
+		"\n"
+		" umax : maximum unit of work\n"
+		" dmax : maximum delay\n"
+		" rate : rate\n"
+		"\n"
+	);
+}
+
+static void
+explain1(char *arg)
+{
+	fprintf(stderr, "HFSC: Illegal \"%s\"\n", arg);
+}
+
+static int
+hfsc_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	struct tc_hfsc_qopt qopt;
+
+	memset(&qopt, 0, sizeof(qopt));
+
+	while (argc > 0) {
+		if (matches(*argv, "default") == 0) {
+			NEXT_ARG();
+			if (qopt.defcls != 0) {
+				fprintf(stderr, "HFSC: Double \"default\"\n");
+				return -1;
+			}
+			if (get_u16(&qopt.defcls, *argv, 16) < 0) {
+				explain1("default");
+				return -1;
+			}
+		} else if (matches(*argv, "help") == 0) {
+			explain_qdisc();
+			return -1;
+		} else {
+			fprintf(stderr, "HFSC: What is \"%s\" ?\n", *argv);
+			explain_qdisc();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	addattr_l(n, 1024, TCA_OPTIONS, &qopt, sizeof(qopt));
+	return 0;
+}
+
+static int
+hfsc_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct tc_hfsc_qopt *qopt;
+
+	if (opt == NULL)
+		return 0;
+	if (RTA_PAYLOAD(opt) < sizeof(*qopt))
+		return -1;
+	qopt = RTA_DATA(opt);
+
+	if (qopt->defcls != 0)
+		fprintf(f, "default %x ", qopt->defcls);
+
+	return 0;
+}
+
+static int
+hfsc_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+	struct tc_hfsc_stats *st;
+
+	if (xstats == NULL)
+		return 0;
+	if (RTA_PAYLOAD(xstats) < sizeof(*st))
+		return -1;
+	st = RTA_DATA(xstats);
+
+	fprintf(f, " period %u ", st->period);
+	if (st->work != 0)
+		fprintf(f, "work %llu bytes ", (unsigned long long) st->work);
+	if (st->rtwork != 0)
+		fprintf(f, "rtwork %llu bytes ", (unsigned long long) st->rtwork);
+	fprintf(f, "level %u ", st->level);
+	fprintf(f, "\n");
+
+	return 0;
+}
+
+static int
+hfsc_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
+                     struct nlmsghdr *n)
+{
+	struct tc_service_curve rsc, fsc, usc;
+	int rsc_ok, fsc_ok, usc_ok;
+	struct rtattr *tail;
+
+	memset(&rsc, 0, sizeof(rsc));
+	memset(&fsc, 0, sizeof(fsc));
+	memset(&usc, 0, sizeof(usc));
+	rsc_ok = fsc_ok = usc_ok = 0;
+
+	while (argc > 0) {
+		if (matches(*argv, "rt") == 0) {
+			NEXT_ARG();
+			if (hfsc_get_sc(&argc, &argv, &rsc) < 0) {
+				explain1("rt");
+				return -1;
+			}
+			rsc_ok = 1;
+		} else if (matches(*argv, "ls") == 0) {
+			NEXT_ARG();
+			if (hfsc_get_sc(&argc, &argv, &fsc) < 0) {
+				explain1("ls");
+				return -1;
+			}
+			fsc_ok = 1;
+		} else if (matches(*argv, "sc") == 0) {
+			NEXT_ARG();
+			if (hfsc_get_sc(&argc, &argv, &rsc) < 0) {
+				explain1("sc");
+				return -1;
+			}
+			memcpy(&fsc, &rsc, sizeof(fsc));
+			rsc_ok = 1;
+			fsc_ok = 1;
+		} else if (matches(*argv, "ul") == 0) {
+			NEXT_ARG();
+			if (hfsc_get_sc(&argc, &argv, &usc) < 0) {
+				explain1("ul");
+				return -1;
+			}
+			usc_ok = 1;
+		} else if (matches(*argv, "help") == 0) {
+			explain_class();
+			return -1;
+		} else {
+			fprintf(stderr, "HFSC: What is \"%s\" ?\n", *argv);
+			explain_class();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (!(rsc_ok || fsc_ok || usc_ok)) {
+		fprintf(stderr, "HFSC: no parameters given\n");
+		explain_class();
+		return -1;
+	}
+	if (usc_ok && !fsc_ok) {
+		fprintf(stderr, "HFSC: Upper-limit Service Curve without "
+		                "Link-Share Service Curve\n");
+		explain_class();
+		return -1;
+	}
+
+	tail = NLMSG_TAIL(n);
+
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	if (rsc_ok)
+		addattr_l(n, 1024, TCA_HFSC_RSC, &rsc, sizeof(rsc));
+	if (fsc_ok)
+		addattr_l(n, 1024, TCA_HFSC_FSC, &fsc, sizeof(fsc));
+	if (usc_ok)
+		addattr_l(n, 1024, TCA_HFSC_USC, &usc, sizeof(usc));
+
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static void
+hfsc_print_sc(FILE *f, char *name, struct tc_service_curve *sc)
+{
+	SPRINT_BUF(b1);
+
+	fprintf(f, "%s ", name);
+	fprintf(f, "m1 %s ", sprint_rate(sc->m1, b1));
+	fprintf(f, "d %s ", sprint_time(tc_core_ktime2time(sc->d), b1));
+	fprintf(f, "m2 %s ", sprint_rate(sc->m2, b1));
+}
+
+static int
+hfsc_print_class_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_HFSC_MAX+1];
+	struct tc_service_curve *rsc = NULL, *fsc = NULL, *usc = NULL;
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_HFSC_MAX, opt);
+
+	if (tb[TCA_HFSC_RSC]) {
+		if (RTA_PAYLOAD(tb[TCA_HFSC_RSC]) < sizeof(*rsc))
+			fprintf(stderr, "HFSC: truncated realtime option\n");
+		else
+			rsc = RTA_DATA(tb[TCA_HFSC_RSC]);
+	}
+	if (tb[TCA_HFSC_FSC]) {
+		if (RTA_PAYLOAD(tb[TCA_HFSC_FSC]) < sizeof(*fsc))
+			fprintf(stderr, "HFSC: truncated linkshare option\n");
+		else
+			fsc = RTA_DATA(tb[TCA_HFSC_FSC]);
+	}
+	if (tb[TCA_HFSC_USC]) {
+		if (RTA_PAYLOAD(tb[TCA_HFSC_USC]) < sizeof(*usc))
+			fprintf(stderr, "HFSC: truncated upperlimit option\n");
+		else
+			usc = RTA_DATA(tb[TCA_HFSC_USC]);
+	}
+
+
+	if (rsc != NULL && fsc != NULL &&
+	    memcmp(rsc, fsc, sizeof(*rsc)) == 0)
+		hfsc_print_sc(f, "sc", rsc);
+	else {
+		if (rsc != NULL)
+			hfsc_print_sc(f, "rt", rsc);
+		if (fsc != NULL)
+			hfsc_print_sc(f, "ls", fsc);
+	}
+	if (usc != NULL)
+		hfsc_print_sc(f, "ul", usc);
+
+	return 0;
+}
+
+struct qdisc_util hfsc_qdisc_util = {
+	.id		= "hfsc",
+	.parse_qopt	= hfsc_parse_opt,
+	.print_qopt	= hfsc_print_opt,
+	.print_xstats	= hfsc_print_xstats,
+	.parse_copt	= hfsc_parse_class_opt,
+	.print_copt	= hfsc_print_class_opt,
+};
+
+static int
+hfsc_get_sc1(int *argcp, char ***argvp, struct tc_service_curve *sc)
+{
+	char **argv = *argvp;
+	int argc = *argcp;
+	unsigned int m1 = 0, d = 0, m2 = 0;
+
+	if (matches(*argv, "m1") == 0) {
+		NEXT_ARG();
+		if (get_rate(&m1, *argv) < 0) {
+			explain1("m1");
+			return -1;
+		}
+		NEXT_ARG();
+	}
+
+	if (matches(*argv, "d") == 0) {
+		NEXT_ARG();
+		if (get_time(&d, *argv) < 0) {
+			explain1("d");
+			return -1;
+		}
+		NEXT_ARG();
+	}
+
+	if (matches(*argv, "m2") == 0) {
+		NEXT_ARG();
+		if (get_rate(&m2, *argv) < 0) {
+			explain1("m2");
+			return -1;
+		}
+	} else
+		return -1;
+
+	sc->m1 = m1;
+	sc->d  = tc_core_time2ktime(d);
+	sc->m2 = m2;
+
+	*argvp = argv;
+	*argcp = argc;
+	return 0;
+}
+
+static int
+hfsc_get_sc2(int *argcp, char ***argvp, struct tc_service_curve *sc)
+{
+	char **argv = *argvp;
+	int argc = *argcp;
+	unsigned int umax = 0, dmax = 0, rate = 0;
+
+	if (matches(*argv, "umax") == 0) {
+		NEXT_ARG();
+		if (get_size(&umax, *argv) < 0) {
+			explain1("umax");
+			return -1;
+		}
+		NEXT_ARG();
+	}
+
+	if (matches(*argv, "dmax") == 0) {
+		NEXT_ARG();
+		if (get_time(&dmax, *argv) < 0) {
+			explain1("dmax");
+			return -1;
+		}
+		NEXT_ARG();
+	}
+
+	if (matches(*argv, "rate") == 0) {
+		NEXT_ARG();
+		if (get_rate(&rate, *argv) < 0) {
+			explain1("rate");
+			return -1;
+		}
+	} else
+		return -1;
+
+	if (umax != 0 && dmax == 0) {
+		fprintf(stderr, "HFSC: umax given but dmax is zero.\n");
+		return -1;
+	}
+
+	if (dmax != 0 && ceil(1.0 * umax * TIME_UNITS_PER_SEC / dmax) > rate) {
+		/*
+		 * concave curve, slope of first segment is umax/dmax,
+		 * intersection is at dmax
+		 */
+		sc->m1 = ceil(1.0 * umax * TIME_UNITS_PER_SEC / dmax); /* in bps */
+		sc->d  = tc_core_time2ktime(dmax);
+		sc->m2 = rate;
+	} else {
+		/*
+		 * convex curve, slope of first segment is 0, intersection
+		 * is at dmax - umax / rate
+		 */
+		sc->m1 = 0;
+		sc->d  = tc_core_time2ktime(ceil(dmax - umax * TIME_UNITS_PER_SEC / rate));
+		sc->m2 = rate;
+	}
+
+	*argvp = argv;
+	*argcp = argc;
+	return 0;
+}
+
+static int
+hfsc_get_sc(int *argcp, char ***argvp, struct tc_service_curve *sc)
+{
+	if (hfsc_get_sc1(argcp, argvp, sc) < 0 &&
+	    hfsc_get_sc2(argcp, argvp, sc) < 0)
+		return -1;
+
+	if (sc->m1 == 0 && sc->m2 == 0) {
+		fprintf(stderr, "HFSC: Service Curve has two zero slopes\n");
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/tc/q_htb.c b/tc/q_htb.c
new file mode 100644
index 0000000..c69e350
--- /dev/null
+++ b/tc/q_htb.c
@@ -0,0 +1,337 @@
+/*
+ * q_htb.c		HTB.
+ *
+ *		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:	Martin Devera, devik@cdi.cz
+ *
+ */
+
+#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"
+
+#define HTB_TC_VER 0x30003
+#if HTB_TC_VER >> 16 != TC_HTB_PROTOVER
+#error "Different kernel and TC HTB versions"
+#endif
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... qdisc add ... htb [default N] [r2q N]\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"
+		"... 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"
+		" rate     rate allocated to this class (class can still borrow)\n"
+		" burst    max bytes burst which can be accumulated during idle period {computed}\n"
+		" mpu      minimum packet size used in rate computations\n"
+		" overhead per-packet size overhead used in rate computations\n"
+		" linklay  adapting to a linklayer e.g. atm\n"
+		" ceil     definite upper class rate (no borrows) {rate}\n"
+		" cburst   burst but for ceil {computed}\n"
+		" mtu      max packet size we create rate map for {1600}\n"
+		" prio     priority of leaf; lower are served first {0}\n"
+		" quantum  how much bytes to serve from leaf at once {use r2q}\n"
+		"\nTC HTB version %d.%d\n",HTB_TC_VER>>16,HTB_TC_VER&0xffff
+		);
+}
+
+static void explain1(char *arg)
+{
+    fprintf(stderr, "Illegal \"%s\"\n", arg);
+    explain();
+}
+
+
+#define usage() return(-1)
+
+static int htb_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	struct tc_htb_glob opt;
+	struct rtattr *tail;
+	unsigned i; char *p;
+	memset(&opt,0,sizeof(opt));
+	opt.rate2quantum = 10;
+	opt.version = 3;
+
+	while (argc > 0) {
+		if (matches(*argv, "r2q") == 0) {
+		    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;
+		    }
+		} 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);
+		    }
+		} 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);
+	addattr_l(n, 2024, TCA_HTB_INIT, &opt, NLMSG_ALIGN(sizeof(opt)));
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	int ok=0;
+	struct tc_htb_opt opt;
+	__u32 rtab[256],ctab[256];
+	unsigned buffer=0,cbuffer=0;
+	int cell_log=-1,ccell_log = -1;
+	unsigned mtu;
+	unsigned short mpu = 0;
+	unsigned short overhead = 0;
+	unsigned int linklayer  = LINKLAYER_ETHERNET; /* Assume ethernet */
+	struct rtattr *tail;
+
+	memset(&opt, 0, sizeof(opt)); mtu = 1600; /* eth packet len */
+
+	while (argc > 0) {
+		if (matches(*argv, "prio") == 0) {
+			NEXT_ARG();
+			if (get_u32(&opt.prio, *argv, 10)) {
+				explain1("prio"); return -1;
+			}
+			ok++;
+		} else if (matches(*argv, "mtu") == 0) {
+			NEXT_ARG();
+			if (get_u32(&mtu, *argv, 10)) {
+				explain1("mtu"); return -1;
+			}
+		} else if (matches(*argv, "mpu") == 0) {
+			NEXT_ARG();
+			if (get_u16(&mpu, *argv, 10)) {
+				explain1("mpu"); return -1;
+			}
+		} else if (matches(*argv, "overhead") == 0) {
+			NEXT_ARG();
+			if (get_u16(&overhead, *argv, 10)) {
+				explain1("overhead"); return -1;
+			}
+		} else if (matches(*argv, "linklayer") == 0) {
+			NEXT_ARG();
+			if (get_linklayer(&linklayer, *argv)) {
+				explain1("linklayer"); return -1;
+			}
+		} else if (matches(*argv, "quantum") == 0) {
+			NEXT_ARG();
+			if (get_u32(&opt.quantum, *argv, 10)) {
+				explain1("quantum"); return -1;
+			}
+		} else if (matches(*argv, "burst") == 0 ||
+			strcmp(*argv, "buffer") == 0 ||
+			strcmp(*argv, "maxburst") == 0) {
+			NEXT_ARG();
+			if (get_size_and_cell(&buffer, &cell_log, *argv) < 0) {
+				explain1("buffer");
+				return -1;
+			}
+			ok++;
+		} else if (matches(*argv, "cburst") == 0 ||
+			strcmp(*argv, "cbuffer") == 0 ||
+			strcmp(*argv, "cmaxburst") == 0) {
+			NEXT_ARG();
+			if (get_size_and_cell(&cbuffer, &ccell_log, *argv) < 0) {
+				explain1("cbuffer");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "ceil") == 0) {
+			NEXT_ARG();
+			if (opt.ceil.rate) {
+				fprintf(stderr, "Double \"ceil\" spec\n");
+				return -1;
+			}
+			if (get_rate(&opt.ceil.rate, *argv)) {
+				explain1("ceil");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "rate") == 0) {
+			NEXT_ARG();
+			if (opt.rate.rate) {
+				fprintf(stderr, "Double \"rate\" spec\n");
+				return -1;
+			}
+			if (get_rate(&opt.rate.rate, *argv)) {
+				explain1("rate");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+/*	if (!ok)
+		return 0;*/
+
+	if (opt.rate.rate == 0) {
+		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;
+
+	/* 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;
+
+	opt.ceil.overhead = overhead;
+	opt.rate.overhead = overhead;
+
+	opt.ceil.mpu = mpu;
+	opt.rate.mpu = mpu;
+
+	if (tc_calc_rtable(&opt.rate, rtab, cell_log, mtu, linklayer) < 0) {
+		fprintf(stderr, "htb: failed to calculate rate table.\n");
+		return -1;
+	}
+	opt.buffer = tc_calc_xmittime(opt.rate.rate, 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);
+
+	tail = NLMSG_TAIL(n);
+	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);
+	addattr_l(n, 4024, TCA_HTB_CTAB, ctab, 1024);
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int htb_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_HTB_RTAB+1];
+	struct tc_htb_opt *hopt;
+	struct tc_htb_glob *gopt;
+	double buffer,cbuffer;
+	SPRINT_BUF(b1);
+	SPRINT_BUF(b2);
+	SPRINT_BUF(b3);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_HTB_RTAB, opt);
+
+	if (tb[TCA_HTB_PARMS]) {
+
+	    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);
+	}
+	if (tb[TCA_HTB_INIT]) {
+	    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);
+		if (show_details)
+			fprintf(f," ver %d.%d",gopt->version >> 16,gopt->version & 0xffff);
+	}
+	return 0;
+}
+
+static int htb_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+	struct tc_htb_xstats *st;
+	if (xstats == NULL)
+		return 0;
+
+	if (RTA_PAYLOAD(xstats) < sizeof(*st))
+		return -1;
+
+	st = RTA_DATA(xstats);
+	fprintf(f, " lended: %u borrowed: %u giants: %u\n",
+		st->lends,st->borrows,st->giants);
+	fprintf(f, " tokens: %d ctokens: %d\n", st->tokens,st->ctokens);
+	return 0;
+}
+
+struct qdisc_util htb_qdisc_util = {
+	.id 		= "htb",
+	.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,
+};
+
+/* 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_ingress.c b/tc/q_ingress.c
new file mode 100644
index 0000000..71fbd49
--- /dev/null
+++ b/tc/q_ingress.c
@@ -0,0 +1,69 @@
+/*
+ *
+ * q_ingress.c             INGRESS.
+ *
+ *              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:    J Hadi Salim
+ *
+ * This is here just in case it is needed
+ * useless right now; might be useful in the future
+ *
+ */
+
+#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: ... ingress \n");
+}
+
+#define usage() return(-1)
+
+static int ingress_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+
+	if (argc > 0) {
+		while (argc > 0) {
+
+			if (strcmp(*argv, "handle") == 0) {
+				NEXT_ARG();
+				argc--; argv++;
+			} else {
+				fprintf(stderr, "What is \"%s\"?\n", *argv);
+				explain();
+				return -1;
+			}
+		}
+	}
+
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	return 0;
+}
+
+static int ingress_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+
+		fprintf(f, "---------------- ");
+	return 0;
+}
+
+struct qdisc_util ingress_qdisc_util = {
+	.id		= "ingress",
+	.parse_qopt	= ingress_parse_opt,
+	.print_qopt	= ingress_print_opt,
+};
diff --git a/tc/q_multiq.c b/tc/q_multiq.c
new file mode 100644
index 0000000..306e170
--- /dev/null
+++ b/tc/q_multiq.c
@@ -0,0 +1,87 @@
+/*
+ * q_multiq.c		Multiqueue aware qdisc
+ *
+ * Copyright (c) 2008, Intel Corporation.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * 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)
+ *
+ */
+
+#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: ... multiq [help]\n");
+}
+
+#define usage() return(-1)
+
+static int multiq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+			    struct nlmsghdr *n)
+{
+	struct tc_multiq_qopt opt;
+
+	if (argc > 0) {
+		if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+	return 0;
+}
+
+int multiq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct tc_multiq_qopt *qopt;
+
+	if (opt == NULL)
+		return 0;
+	if (RTA_PAYLOAD(opt) < sizeof(*qopt))
+		return 0;
+
+	qopt = RTA_DATA(opt);
+
+	fprintf(f, "bands %u/%u ", qopt->bands, qopt->max_bands);
+
+	return 0;
+}
+
+struct qdisc_util multiq_qdisc_util = {
+	.id	 	= "multiq",
+	.parse_qopt	= multiq_parse_opt,
+	.print_qopt	= multiq_print_opt,
+};
diff --git a/tc/q_netem.c b/tc/q_netem.c
new file mode 100644
index 0000000..33b3d2a
--- /dev/null
+++ b/tc/q_netem.c
@@ -0,0 +1,398 @@
+/*
+ * q_netem.c		NETEM.
+ *
+ *		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@osdl.org>
+ *
+ */
+
+#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 "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+static void explain(void)
+{
+	fprintf(stderr,
+"Usage: ... netem [ limit PACKETS ] \n" \
+"                 [ delay TIME [ JITTER [CORRELATION]]]\n" \
+"                 [ distribution {uniform|normal|pareto|paretonormal} ]\n" \
+"                 [ drop PERCENT [CORRELATION]] \n" \
+"                 [ corrupt PERCENT [CORRELATION]] \n" \
+"                 [ duplicate PERCENT [CORRELATION]]\n" \
+"                 [ reorder PRECENT [CORRELATION] [ gap DISTANCE ]]\n");
+}
+
+static void explain1(const char *arg)
+{
+	fprintf(stderr, "Illegal \"%s\"\n", arg);
+}
+
+#define usage() return(-1)
+
+/* Upper bound on size of distribution 
+ *  really (TCA_BUF_MAX - other headers) / sizeof (__s16)
+ */
+#define MAX_DIST	(16*1024)
+
+/*
+ * Simplistic file parser for distrbution data.
+ * Format is:
+ *	# comment line(s)
+ *	data0 data1 ...
+ */
+static int get_distribution(const char *type, __s16 *data, int maxdata)
+{
+	FILE *f;
+	int n;
+	long x;
+	size_t len;
+	char *line = NULL;
+	char name[128];
+
+	snprintf(name, sizeof(name), "%s/%s.dist", get_tc_lib(), type);
+	if ((f = fopen(name, "r")) == NULL) {
+		fprintf(stderr, "No distribution data for %s (%s: %s)\n",
+			type, name, strerror(errno));
+		return -1;
+	}
+
+	n = 0;
+	while (getline(&line, &len, f) != -1) {
+		char *p, *endp;
+		if (*line == '\n' || *line == '#')
+			continue;
+
+		for (p = line; ; p = endp) {
+			x = strtol(p, &endp, 0);
+			if (endp == p)
+				break;
+
+			if (n >= maxdata) {
+				fprintf(stderr, "%s: too much data\n",
+					name);
+				n = -1;
+				goto error;
+			}
+			data[n++] = x;
+		}
+	}
+ error:
+	free(line);
+	fclose(f);
+	return n;
+}
+
+static int isnumber(const char *arg)
+{
+	char *p;
+
+	return strtod(arg, &p) != 0 || p != arg;
+}
+
+#define NEXT_IS_NUMBER() (NEXT_ARG_OK() && isnumber(argv[1]))
+
+/* Adjust for the fact that psched_ticks aren't always usecs
+   (based on kernel PSCHED_CLOCK configuration */
+static int get_ticks(__u32 *ticks, const char *str)
+{
+	unsigned t;
+
+	if(get_time(&t, str))
+		return -1;
+
+	if (tc_core_time2big(t)) {
+		fprintf(stderr, "Illegal %u time (too large)\n", t);
+		return -1;
+	}
+
+	*ticks = tc_core_time2tick(t);
+	return 0;
+}
+
+static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+			   struct nlmsghdr *n)
+{
+	size_t dist_size = 0;
+	struct rtattr *tail;
+	struct tc_netem_qopt opt;
+	struct tc_netem_corr cor;
+	struct tc_netem_reorder reorder;
+	struct tc_netem_corrupt corrupt;
+	__s16 *dist_data = NULL;
+	int present[__TCA_NETEM_MAX];
+
+	memset(&opt, 0, sizeof(opt));
+	opt.limit = 1000;
+	memset(&cor, 0, sizeof(cor));
+	memset(&reorder, 0, sizeof(reorder));
+	memset(&corrupt, 0, sizeof(corrupt));
+	memset(present, 0, sizeof(present));
+
+	while (argc > 0) {
+		if (matches(*argv, "limit") == 0) {
+			NEXT_ARG();
+			if (get_size(&opt.limit, *argv)) {
+				explain1("limit");
+				return -1;
+			}
+		} else if (matches(*argv, "latency") == 0 ||
+			   matches(*argv, "delay") == 0) {
+			NEXT_ARG();
+			if (get_ticks(&opt.latency, *argv)) {
+				explain1("latency");
+				return -1;
+			}
+
+			if (NEXT_IS_NUMBER()) {
+				NEXT_ARG();
+				if (get_ticks(&opt.jitter, *argv)) {
+					explain1("latency");
+					return -1;
+				}
+
+				if (NEXT_IS_NUMBER()) {
+					NEXT_ARG();
+					++present[TCA_NETEM_CORR];
+					if (get_percent(&cor.delay_corr,							*argv)) {
+						explain1("latency");
+						return -1;
+					}
+				}
+			}
+		} else if (matches(*argv, "loss") == 0 ||
+			   matches(*argv, "drop") == 0) {
+			NEXT_ARG();
+			if (get_percent(&opt.loss, *argv)) {
+				explain1("loss");
+				return -1;
+			}
+			if (NEXT_IS_NUMBER()) {
+				NEXT_ARG();
+				++present[TCA_NETEM_CORR];
+				if (get_percent(&cor.loss_corr, *argv)) {
+					explain1("loss");
+					return -1;
+				}
+			}
+		} else if (matches(*argv, "reorder") == 0) {
+			NEXT_ARG();
+			present[TCA_NETEM_REORDER] = 1;
+			if (get_percent(&reorder.probability, *argv)) {
+				explain1("reorder");
+				return -1;
+			}
+			if (NEXT_IS_NUMBER()) {
+				NEXT_ARG();
+				++present[TCA_NETEM_CORR];
+				if (get_percent(&reorder.correlation, *argv)) {
+					explain1("reorder");
+					return -1;
+				}
+			}
+		} else if (matches(*argv, "corrupt") == 0) {
+			NEXT_ARG();
+			present[TCA_NETEM_CORRUPT] = 1;
+			if (get_percent(&corrupt.probability, *argv)) {
+				explain1("corrupt");
+				return -1;
+			}
+			if (NEXT_IS_NUMBER()) {
+				NEXT_ARG();
+				++present[TCA_NETEM_CORR];
+				if (get_percent(&corrupt.correlation, *argv)) {
+					explain1("corrupt");
+					return -1;
+				}
+			}
+		} else if (matches(*argv, "gap") == 0) {
+			NEXT_ARG();
+			if (get_u32(&opt.gap, *argv, 0)) {
+				explain1("gap");
+				return -1;
+			}
+		} else if (matches(*argv, "duplicate") == 0) {
+			NEXT_ARG();
+			if (get_percent(&opt.duplicate, *argv)) {
+				explain1("duplicate");
+				return -1;
+			}
+			if (NEXT_IS_NUMBER()) {
+				NEXT_ARG();
+				if (get_percent(&cor.dup_corr, *argv)) {
+					explain1("duplicate");
+					return -1;
+				}
+			}
+		} else if (matches(*argv, "distribution") == 0) {
+			NEXT_ARG();
+			dist_data = calloc(sizeof(dist_data[0]), MAX_DIST);
+			dist_size = get_distribution(*argv, dist_data, MAX_DIST);
+			if (dist_size <= 0) {
+				free(dist_data);
+				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);
+
+	if (reorder.probability) {
+		if (opt.latency == 0) {
+			fprintf(stderr, "reordering not possible without specifying some delay\n");
+		}
+		if (opt.gap == 0)
+			opt.gap = 1;
+	} else if (opt.gap > 0) {
+		fprintf(stderr, "gap specified without reorder probability\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();
+		return -1;
+	}
+
+	if (addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)) < 0)
+		return -1;
+
+	if (present[TCA_NETEM_CORR] &&
+	    addattr_l(n, 1024, TCA_NETEM_CORR, &cor, sizeof(cor)) < 0)
+			return -1;
+
+	if (present[TCA_NETEM_REORDER] && 
+	    addattr_l(n, 1024, TCA_NETEM_REORDER, &reorder, sizeof(reorder)) < 0)
+		return -1;
+
+	if (present[TCA_NETEM_CORRUPT] &&
+	    addattr_l(n, 1024, TCA_NETEM_CORRUPT, &corrupt, sizeof(corrupt)) < 0)
+		return -1;
+
+	if (dist_data) {
+		if (addattr_l(n, MAX_DIST * sizeof(dist_data[0]),
+			      TCA_NETEM_DELAY_DIST,
+			      dist_data, dist_size * sizeof(dist_data[0])) < 0)
+			return -1;
+		free(dist_data);
+	}
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	const struct tc_netem_corr *cor = NULL;
+	const struct tc_netem_reorder *reorder = NULL;
+	const struct tc_netem_corrupt *corrupt = NULL;
+	struct tc_netem_qopt qopt;
+	int len = RTA_PAYLOAD(opt) - sizeof(qopt);
+	SPRINT_BUF(b1);
+
+	if (opt == NULL)
+		return 0;
+
+	if (len < 0) {
+		fprintf(stderr, "options size error\n");
+		return -1;
+	}
+	memcpy(&qopt, RTA_DATA(opt), sizeof(qopt));
+
+	if (len > 0) {
+		struct rtattr *tb[TCA_NETEM_MAX+1];
+		parse_rtattr(tb, TCA_NETEM_MAX, RTA_DATA(opt) + sizeof(qopt),
+			     len);
+
+		if (tb[TCA_NETEM_CORR]) {
+			if (RTA_PAYLOAD(tb[TCA_NETEM_CORR]) < sizeof(*cor))
+				return -1;
+			cor = RTA_DATA(tb[TCA_NETEM_CORR]);
+		}
+		if (tb[TCA_NETEM_REORDER]) {
+			if (RTA_PAYLOAD(tb[TCA_NETEM_REORDER]) < sizeof(*reorder))
+				return -1;
+			reorder = RTA_DATA(tb[TCA_NETEM_REORDER]);
+		}
+		if (tb[TCA_NETEM_CORRUPT]) {
+			if (RTA_PAYLOAD(tb[TCA_NETEM_CORRUPT]) < sizeof(*corrupt))
+				return -1;
+			corrupt = RTA_DATA(tb[TCA_NETEM_CORRUPT]);
+		}
+	}
+
+	fprintf(f, "limit %d", qopt.limit);
+
+	if (qopt.latency) {
+		fprintf(f, " delay %s", sprint_ticks(qopt.latency, b1));
+
+		if (qopt.jitter) {
+			fprintf(f, "  %s", sprint_ticks(qopt.jitter, b1));
+			if (cor && cor->delay_corr)
+				fprintf(f, " %s", sprint_percent(cor->delay_corr, b1));
+		}
+	}
+
+	if (qopt.loss) {
+		fprintf(f, " loss %s", sprint_percent(qopt.loss, b1));
+		if (cor && cor->loss_corr)
+			fprintf(f, " %s", sprint_percent(cor->loss_corr, b1));
+	}
+
+	if (qopt.duplicate) {
+		fprintf(f, " duplicate %s",
+			sprint_percent(qopt.duplicate, b1));
+		if (cor && cor->dup_corr)
+			fprintf(f, " %s", sprint_percent(cor->dup_corr, b1));
+	}
+
+	if (reorder && reorder->probability) {
+		fprintf(f, " reorder %s",
+			sprint_percent(reorder->probability, b1));
+		if (reorder->correlation)
+			fprintf(f, " %s",
+				sprint_percent(reorder->correlation, b1));
+	}
+
+	if (corrupt && corrupt->probability) {
+		fprintf(f, " corrupt %s",
+			sprint_percent(corrupt->probability, b1));
+		if (corrupt->correlation)
+			fprintf(f, " %s",
+				sprint_percent(corrupt->correlation, b1));
+	}
+
+	if (qopt.gap)
+		fprintf(f, " gap %lu", (unsigned long)qopt.gap);
+
+	return 0;
+}
+
+struct qdisc_util netem_qdisc_util = {
+	.id	   	= "netem",
+	.parse_qopt	= netem_parse_opt,
+	.print_qopt	= netem_print_opt,
+};
+
diff --git a/tc/q_prio.c b/tc/q_prio.c
new file mode 100644
index 0000000..31a37cd
--- /dev/null
+++ b/tc/q_prio.c
@@ -0,0 +1,129 @@
+/*
+ * q_prio.c		PRIO.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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: ... prio bands NUMBER priomap P1 P2...[multiqueue]\n");
+}
+
+#define usage() return(-1)
+
+static int prio_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	int ok=0;
+	int pmap_mode = 0;
+	int idx = 0;
+	struct tc_prio_qopt opt={3,{ 1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 }};
+	struct rtattr *nest;
+	unsigned char mq = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "bands") == 0) {
+			if (pmap_mode)
+				explain();
+			NEXT_ARG();
+			if (get_integer(&opt.bands, *argv, 10)) {
+				fprintf(stderr, "Illegal \"bands\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "priomap") == 0) {
+			if (pmap_mode) {
+				fprintf(stderr, "Error: duplicate priomap\n");
+				return -1;
+			}
+			pmap_mode = 1;
+		} else if (strcmp(*argv, "multiqueue") == 0) {
+			mq = 1;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			unsigned band;
+			if (!pmap_mode) {
+				fprintf(stderr, "What is \"%s\"?\n", *argv);
+				explain();
+				return -1;
+			}
+			if (get_unsigned(&band, *argv, 10)) {
+				fprintf(stderr, "Illegal \"priomap\" element\n");
+				return -1;
+			}
+			if (band > opt.bands) {
+				fprintf(stderr, "\"priomap\" element is out of bands\n");
+				return -1;
+			}
+			if (idx > TC_PRIO_MAX) {
+				fprintf(stderr, "\"priomap\" index > TC_PRIO_MAX=%u\n", TC_PRIO_MAX);
+				return -1;
+			}
+			opt.priomap[idx++] = band;
+		}
+		argc--; argv++;
+	}
+
+/*
+	if (pmap_mode) {
+		for (; idx < TC_PRIO_MAX; idx++)
+			opt.priomap[idx] = opt.priomap[TC_PRIO_BESTEFFORT];
+	}
+*/
+	nest = addattr_nest_compat(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+	if (mq)
+		addattr_l(n, 1024, TCA_PRIO_MQ, NULL, 0);
+	addattr_nest_compat_end(n, nest);
+	return 0;
+}
+
+int prio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	int i;
+	struct tc_prio_qopt *qopt;
+	struct rtattr *tb[TCA_PRIO_MAX+1];
+
+	if (opt == NULL)
+		return 0;
+
+	if (parse_rtattr_nested_compat(tb, TCA_PRIO_MAX, opt, qopt,
+					sizeof(*qopt)))
+                return -1;
+
+	fprintf(f, "bands %u priomap ", qopt->bands);
+	for (i=0; i<=TC_PRIO_MAX; i++)
+		fprintf(f, " %d", qopt->priomap[i]);
+
+	if (tb[TCA_PRIO_MQ])
+		fprintf(f, " multiqueue: %s ",
+		    *(unsigned char *)RTA_DATA(tb[TCA_PRIO_MQ]) ? "on" : "off");
+
+	return 0;
+}
+
+struct qdisc_util prio_qdisc_util = {
+	.id	 	= "prio",
+	.parse_qopt	= prio_parse_opt,
+	.print_qopt	= prio_print_opt,
+};
+
diff --git a/tc/q_red.c b/tc/q_red.c
new file mode 100644
index 0000000..6f93b26
--- /dev/null
+++ b/tc/q_red.c
@@ -0,0 +1,219 @@
+/*
+ * q_red.c		RED.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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 "tc_red.h"
+
+static void explain(void)
+{
+	fprintf(stderr, "Usage: ... red limit BYTES min BYTES max BYTES avpkt BYTES burst PACKETS\n");
+	fprintf(stderr, "               probability PROBABILITY bandwidth KBPS [ ecn ]\n");
+}
+
+#define usage() return(-1)
+
+static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	int ok=0;
+	struct tc_red_qopt opt;
+	unsigned burst = 0;
+	unsigned avpkt = 0;
+	double probability = 0.02;
+	unsigned rate = 0;
+	int ecn_ok = 0;
+	int wlog;
+	__u8 sbuf[256];
+	struct rtattr *tail;
+
+	memset(&opt, 0, sizeof(opt));
+
+	while (argc > 0) {
+		if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			if (get_size(&opt.limit, *argv)) {
+				fprintf(stderr, "Illegal \"limit\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "min") == 0) {
+			NEXT_ARG();
+			if (get_size(&opt.qth_min, *argv)) {
+				fprintf(stderr, "Illegal \"min\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "max") == 0) {
+			NEXT_ARG();
+			if (get_size(&opt.qth_max, *argv)) {
+				fprintf(stderr, "Illegal \"max\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "burst") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&burst, *argv, 0)) {
+				fprintf(stderr, "Illegal \"burst\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "avpkt") == 0) {
+			NEXT_ARG();
+			if (get_size(&avpkt, *argv)) {
+				fprintf(stderr, "Illegal \"avpkt\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "probability") == 0) {
+			NEXT_ARG();
+			if (sscanf(*argv, "%lg", &probability) != 1) {
+				fprintf(stderr, "Illegal \"probability\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "bandwidth") == 0) {
+			NEXT_ARG();
+			if (get_rate(&rate, *argv)) {
+				fprintf(stderr, "Illegal \"bandwidth\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "ecn") == 0) {
+			ecn_ok = 1;
+			ok++;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	if (!ok)
+		return 0;
+
+	if (rate == 0)
+		get_rate(&rate, "10Mbit");
+
+	if (!opt.qth_min || !opt.qth_max || !burst || !opt.limit || !avpkt) {
+		fprintf(stderr, "Required parameter (min, max, burst, limit, avpket) is missing\n");
+		return -1;
+	}
+
+	if ((wlog = tc_red_eval_ewma(opt.qth_min, burst, avpkt)) < 0) {
+		fprintf(stderr, "RED: failed to calculate EWMA constant.\n");
+		return -1;
+	}
+	if (wlog >= 10)
+		fprintf(stderr, "RED: WARNING. Burst %d seems to be to large.\n", burst);
+	opt.Wlog = wlog;
+	if ((wlog = tc_red_eval_P(opt.qth_min, opt.qth_max, probability)) < 0) {
+		fprintf(stderr, "RED: failed to calculate probability.\n");
+		return -1;
+	}
+	opt.Plog = wlog;
+	if ((wlog = tc_red_eval_idle_damping(opt.Wlog, avpkt, rate, sbuf)) < 0) {
+		fprintf(stderr, "RED: failed to calculate idle damping table.\n");
+		return -1;
+	}
+	opt.Scell_log = wlog;
+	if (ecn_ok) {
+#ifdef TC_RED_ECN
+		opt.flags |= TC_RED_ECN;
+#else
+		fprintf(stderr, "RED: ECN support is missing in this binary.\n");
+		return -1;
+#endif
+	}
+
+	tail = NLMSG_TAIL(n);
+	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+	addattr_l(n, 1024, TCA_RED_PARMS, &opt, sizeof(opt));
+	addattr_l(n, 1024, TCA_RED_STAB, sbuf, 256);
+	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+	return 0;
+}
+
+static int red_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct rtattr *tb[TCA_RED_STAB+1];
+	struct tc_red_qopt *qopt;
+	SPRINT_BUF(b1);
+	SPRINT_BUF(b2);
+	SPRINT_BUF(b3);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_RED_STAB, opt);
+
+	if (tb[TCA_RED_PARMS] == NULL)
+		return -1;
+	qopt = RTA_DATA(tb[TCA_RED_PARMS]);
+	if (RTA_PAYLOAD(tb[TCA_RED_PARMS])  < sizeof(*qopt))
+		return -1;
+	fprintf(f, "limit %s min %s max %s ",
+		sprint_size(qopt->limit, b1),
+		sprint_size(qopt->qth_min, b2),
+		sprint_size(qopt->qth_max, b3));
+#ifdef TC_RED_ECN
+	if (qopt->flags & TC_RED_ECN)
+		fprintf(f, "ecn ");
+#endif
+	if (show_details) {
+		fprintf(f, "ewma %u Plog %u Scell_log %u",
+			qopt->Wlog, qopt->Plog, qopt->Scell_log);
+	}
+	return 0;
+}
+
+static int red_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+#ifdef TC_RED_ECN
+	struct tc_red_xstats *st;
+
+	if (xstats == NULL)
+		return 0;
+
+	if (RTA_PAYLOAD(xstats) < sizeof(*st))
+		return -1;
+
+	st = RTA_DATA(xstats);
+	fprintf(f, "  marked %u early %u pdrop %u other %u",
+		st->marked, st->early, st->pdrop, st->other);
+	return 0;
+
+#endif
+	return 0;
+}
+
+
+struct qdisc_util red_qdisc_util = {
+	.id		= "red",
+	.parse_qopt	= red_parse_opt,
+	.print_qopt	= red_print_opt,
+	.print_xstats	= red_print_xstats,
+};
diff --git a/tc/q_rr.c b/tc/q_rr.c
new file mode 100644
index 0000000..9bb8c7d
--- /dev/null
+++ b/tc/q_rr.c
@@ -0,0 +1,122 @@
+/*
+ * q_rr.c		RR.
+ *
+ *		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:	PJ Waskiewicz, <peter.p.waskiewicz.jr@intel.com>
+ * Original Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> (from PRIO)
+ */
+
+#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: ... rr bands NUMBER priomap P1 P2... [multiqueue]\n");
+}
+
+#define usage() return(-1)
+
+static int rr_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	int ok = 0;
+	int pmap_mode = 0;
+	int idx = 0;
+	struct tc_prio_qopt opt={3,{ 1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 }};
+	struct rtattr *nest;
+	unsigned char mq = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "bands") == 0) {
+			if (pmap_mode)
+				explain();
+			NEXT_ARG();
+			if (get_integer(&opt.bands, *argv, 10)) {
+				fprintf(stderr, "Illegal \"bands\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "priomap") == 0) {
+			if (pmap_mode) {
+				fprintf(stderr, "Error: duplicate priomap\n");
+				return -1;
+			}
+			pmap_mode = 1;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else if (strcmp(*argv, "multiqueue") == 0) {
+			mq = 1;
+		} else {
+			unsigned band;
+			if (!pmap_mode) {
+				fprintf(stderr, "What is \"%s\"?\n", *argv);
+				explain();
+				return -1;
+			}
+			if (get_unsigned(&band, *argv, 10)) {
+				fprintf(stderr, "Illegal \"priomap\" element\n");
+				return -1;
+			}
+			if (band > opt.bands) {
+				fprintf(stderr, "\"priomap\" element is out of bands\n");
+				return -1;
+			}
+			if (idx > TC_PRIO_MAX) {
+				fprintf(stderr, "\"priomap\" index > TC_RR_MAX=%u\n", TC_PRIO_MAX);
+				return -1;
+			}
+			opt.priomap[idx++] = band;
+		}
+		argc--; argv++;
+	}
+
+	nest = addattr_nest_compat(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+	if (mq)
+		addattr_l(n, 1024, TCA_PRIO_MQ, NULL, 0);
+	addattr_nest_compat_end(n, nest);
+	return 0;
+}
+
+int rr_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	int i;
+	struct tc_prio_qopt *qopt;
+	struct rtattr *tb[TCA_PRIO_MAX + 1];
+
+	if (opt == NULL)
+		return 0;
+
+	if (parse_rtattr_nested_compat(tb, TCA_PRIO_MAX, opt, qopt,
+						sizeof(*qopt)))
+		return -1;
+
+	fprintf(f, "bands %u priomap ", qopt->bands);
+	for (i=0; i <= TC_PRIO_MAX; i++)
+		fprintf(f, " %d", qopt->priomap[i]);
+
+	if (tb[TCA_PRIO_MQ])
+		fprintf(f, " multiqueue: %s ",
+		    *(unsigned char *)RTA_DATA(tb[TCA_PRIO_MQ]) ? "on" : "off");
+
+	return 0;
+}
+
+struct qdisc_util rr_qdisc_util = {
+	.id	 	= "rr",
+	.parse_qopt	= rr_parse_opt,
+	.print_qopt	= rr_print_opt,
+};
diff --git a/tc/q_sfq.c b/tc/q_sfq.c
new file mode 100644
index 0000000..ce4dade
--- /dev/null
+++ b/tc/q_sfq.c
@@ -0,0 +1,124 @@
+/*
+ * q_sfq.c		SFQ.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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: ... sfq [ limit NUMBER ] [ perturb SECS ] [ quantum BYTES ]\n");
+}
+
+#define usage() return(-1)
+
+static int sfq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	int ok=0;
+	struct tc_sfq_qopt opt;
+
+	memset(&opt, 0, sizeof(opt));
+
+	while (argc > 0) {
+		if (strcmp(*argv, "quantum") == 0) {
+			NEXT_ARG();
+			if (get_size(&opt.quantum, *argv)) {
+				fprintf(stderr, "Illegal \"limit\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "perturb") == 0) {
+			NEXT_ARG();
+			if (get_integer(&opt.perturb_period, *argv, 0)) {
+				fprintf(stderr, "Illegal \"perturb\"\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			if (get_u32(&opt.limit, *argv, 0)) {
+				fprintf(stderr, "Illegal \"limit\"\n");
+				return -1;
+			}
+			if (opt.limit < 2) {
+				fprintf(stderr, "Illegal \"limit\", must be > 1\n");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	if (ok)
+		addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+	return 0;
+}
+
+static int sfq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+	struct tc_sfq_qopt *qopt;
+	SPRINT_BUF(b1);
+
+	if (opt == NULL)
+		return 0;
+
+	if (RTA_PAYLOAD(opt)  < sizeof(*qopt))
+		return -1;
+	qopt = RTA_DATA(opt);
+	fprintf(f, "limit %up ", qopt->limit);
+	fprintf(f, "quantum %s ", sprint_size(qopt->quantum, b1));
+	if (show_details) {
+		fprintf(f, "flows %u/%u ", qopt->flows, qopt->divisor);
+	}
+	if (qopt->perturb_period)
+		fprintf(f, "perturb %dsec ", qopt->perturb_period);
+	return 0;
+}
+
+static int sfq_print_xstats(struct qdisc_util *qu, FILE *f,
+			    struct rtattr *xstats)
+{
+	struct tc_sfq_xstats *st;
+
+	if (xstats == NULL)
+		return 0;
+	if (RTA_PAYLOAD(xstats) < sizeof(*st))
+		return -1;
+	st = RTA_DATA(xstats);
+
+	fprintf(f, " allot %d ", st->allot);
+	fprintf(f, "\n");
+	return 0;
+}
+
+struct qdisc_util sfq_qdisc_util = {
+	.id		= "sfq",
+	.parse_qopt	= sfq_parse_opt,
+	.print_qopt	= sfq_print_opt,
+	.print_xstats	= sfq_print_xstats,
+};
diff --git a/tc/q_tbf.c b/tc/q_tbf.c
new file mode 100644
index 0000000..dbf9586
--- /dev/null
+++ b/tc/q_tbf.c
@@ -0,0 +1,286 @@
+/*
+ * q_tbf.c		TBF.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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: ... tbf limit BYTES burst BYTES[/BYTES] rate KBPS [ mtu BYTES[/BYTES] ]\n");
+	fprintf(stderr, "               [ peakrate KBPS ] [ latency TIME ] ");
+	fprintf(stderr, "[ overhead BYTES ] [ linklayer TYPE ]\n");
+}
+
+static void explain1(char *arg)
+{
+	fprintf(stderr, "Illegal \"%s\"\n", arg);
+}
+
+
+#define usage() return(-1)
+
+static int tbf_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	int ok=0;
+	struct tc_tbf_qopt opt;
+	__u32 rtab[256];
+	__u32 ptab[256];
+	unsigned buffer=0, mtu=0, mpu=0, latency=0;
+	int Rcell_log=-1, Pcell_log = -1;
+	unsigned short overhead=0;
+	unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+	struct rtattr *tail;
+
+	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");
+				return -1;
+			}
+			if (get_size(&opt.limit, *argv)) {
+				explain1("limit");
+				return -1;
+			}
+			ok++;
+		} else if (matches(*argv, "latency") == 0) {
+			NEXT_ARG();
+			if (opt.limit || latency) {
+				fprintf(stderr, "Double \"limit/latency\" spec\n");
+				return -1;
+			}
+			if (get_time(&latency, *argv)) {
+				explain1("latency");
+				return -1;
+			}
+			ok++;
+		} else if (matches(*argv, "burst") == 0 ||
+			strcmp(*argv, "buffer") == 0 ||
+			strcmp(*argv, "maxburst") == 0) {
+			NEXT_ARG();
+			if (buffer) {
+				fprintf(stderr, "Double \"buffer/burst\" spec\n");
+				return -1;
+			}
+			if (get_size_and_cell(&buffer, &Rcell_log, *argv) < 0) {
+				explain1("buffer");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "mtu") == 0 ||
+			   strcmp(*argv, "minburst") == 0) {
+			NEXT_ARG();
+			if (mtu) {
+				fprintf(stderr, "Double \"mtu/minburst\" spec\n");
+				return -1;
+			}
+			if (get_size_and_cell(&mtu, &Pcell_log, *argv) < 0) {
+				explain1("mtu");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "mpu") == 0) {
+			NEXT_ARG();
+			if (mpu) {
+				fprintf(stderr, "Double \"mpu\" spec\n");
+				return -1;
+			}
+			if (get_size(&mpu, *argv)) {
+				explain1("mpu");
+				return -1;
+			}
+			ok++;
+		} else if (strcmp(*argv, "rate") == 0) {
+			NEXT_ARG();
+			if (opt.rate.rate) {
+				fprintf(stderr, "Double \"rate\" spec\n");
+				return -1;
+			}
+			if (get_rate(&opt.rate.rate, *argv)) {
+				explain1("rate");
+				return -1;
+			}
+			ok++;
+		} else if (matches(*argv, "peakrate") == 0) {
+			NEXT_ARG();
+			if (opt.peakrate.rate) {
+				fprintf(stderr, "Double \"peakrate\" spec\n");
+				return -1;
+			}
+			if (get_rate(&opt.peakrate.rate, *argv)) {
+				explain1("peakrate");
+				return -1;
+			}
+			ok++;
+		} else if (matches(*argv, "overhead") == 0) {
+			NEXT_ARG();
+			if (overhead) {
+				fprintf(stderr, "Double \"overhead\" spec\n");
+				return -1;
+			}
+			if (get_u16(&overhead, *argv, 10)) {
+				explain1("overhead"); return -1;
+			}
+		} else if (matches(*argv, "linklayer") == 0) {
+			NEXT_ARG();
+			if (get_linklayer(&linklayer, *argv)) {
+				explain1("linklayer"); return -1;
+			}
+		} else if (strcmp(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "What is \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	if (!ok)
+		return 0;
+
+	if (opt.rate.rate == 0 || !buffer) {
+		fprintf(stderr, "Both \"rate\" and \"burst\" are required.\n");
+		return -1;
+	}
+	if (opt.peakrate.rate) {
+		if (!mtu) {
+			fprintf(stderr, "\"mtu\" is required, if \"peakrate\" is requested.\n");
+			return -1;
+		}
+	}
+
+	if (opt.limit == 0 && latency == 0) {
+		fprintf(stderr, "Either \"limit\" or \"latency\" are required.\n");
+		return -1;
+	}
+
+	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;
+			if (lim2 < lim)
+				lim = lim2;
+		}
+		opt.limit = lim;
+	}
+
+	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");
+		return -1;
+	}
+	opt.buffer = tc_calc_xmittime(opt.rate.rate, buffer);
+
+	if (opt.peakrate.rate) {
+		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");
+			return -1;
+		}
+		opt.mtu = tc_calc_xmittime(opt.peakrate.rate, mtu);
+	}
+
+	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, 3024, TCA_TBF_RTAB, rtab, 1024);
+	if (opt.peakrate.rate)
+		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 tc_tbf_qopt *qopt;
+	double buffer, mtu;
+	double latency;
+	SPRINT_BUF(b1);
+	SPRINT_BUF(b2);
+
+	if (opt == NULL)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_TBF_PTAB, opt);
+
+	if (tb[TCA_TBF_PARMS] == NULL)
+		return -1;
+
+	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);
+	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));
+	} else {
+		fprintf(f, "burst %s ", sprint_size(buffer, b1));
+	}
+	if (show_raw)
+		fprintf(f, "[%08x] ", qopt->buffer);
+	if (qopt->peakrate.rate) {
+		fprintf(f, "peakrate %s ", sprint_rate(qopt->peakrate.rate, b1));
+		if (qopt->mtu || qopt->peakrate.mpu) {
+			mtu = tc_calc_xmitsize(qopt->peakrate.rate, 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));
+			} else {
+				fprintf(f, "minburst %s ", sprint_size(mtu, b1));
+			}
+			if (show_raw)
+				fprintf(f, "[%08x] ", qopt->mtu);
+		}
+	}
+
+	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);
+		if (lat2 > latency)
+			latency = lat2;
+	}
+	fprintf(f, "lat %s ", sprint_time(latency, b1));
+
+	if (qopt->rate.overhead) {
+		fprintf(f, "overhead %d", qopt->rate.overhead);
+	}
+
+	return 0;
+}
+
+struct qdisc_util tbf_qdisc_util = {
+	.id		= "tbf",
+	.parse_qopt	= tbf_parse_opt,
+	.print_qopt	= tbf_print_opt,
+};
+
diff --git a/tc/static-syms.c b/tc/static-syms.c
new file mode 100644
index 0000000..1ed3a8a
--- /dev/null
+++ b/tc/static-syms.c
@@ -0,0 +1,6 @@
+#include <string.h>
+void *_dlsym(const char *sym)
+{
+#include "static-syms.h"
+	return NULL;
+}
diff --git a/tc/tc.c b/tc/tc.c
new file mode 100644
index 0000000..8e362d2
--- /dev/null
+++ b/tc/tc.c
@@ -0,0 +1,320 @@
+/*
+ * tc.c		"tc" utility frontend.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Fixes:
+ *
+ * Petri Mattila <petri@prihateam.fi> 990308: wrong memset's resulted in faults
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <errno.h>
+
+#include "SNAPSHOT.h"
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+int show_stats = 0;
+int show_details = 0;
+int show_raw = 0;
+int show_pretty = 0;
+
+int resolve_hosts = 0;
+int use_iec = 0;
+int force = 0;
+struct rtnl_handle rth;
+
+static void *BODY = NULL;	/* cached handle dlopen(NULL) */
+static struct qdisc_util * qdisc_list;
+static struct filter_util * filter_list;
+
+static int print_noqopt(struct qdisc_util *qu, FILE *f,
+			struct rtattr *opt)
+{
+	if (opt && RTA_PAYLOAD(opt))
+		fprintf(f, "[Unknown qdisc, optlen=%u] ",
+			(unsigned) RTA_PAYLOAD(opt));
+	return 0;
+}
+
+static int parse_noqopt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+	if (argc) {
+		fprintf(stderr, "Unknown qdisc \"%s\", hence option \"%s\" is unparsable\n", qu->id, *argv);
+		return -1;
+	}
+	return 0;
+}
+
+static int print_nofopt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 fhandle)
+{
+	if (opt && RTA_PAYLOAD(opt))
+		fprintf(f, "fh %08x [Unknown filter, optlen=%u] ",
+			fhandle, (unsigned) RTA_PAYLOAD(opt));
+	else if (fhandle)
+		fprintf(f, "fh %08x ", fhandle);
+	return 0;
+}
+
+static int parse_nofopt(struct filter_util *qu, char *fhandle, int argc, char **argv, struct nlmsghdr *n)
+{
+	__u32 handle;
+
+	if (argc) {
+		fprintf(stderr, "Unknown filter \"%s\", hence option \"%s\" is unparsable\n", qu->id, *argv);
+		return -1;
+	}
+	if (fhandle) {
+		struct tcmsg *t = NLMSG_DATA(n);
+		if (get_u32(&handle, fhandle, 16)) {
+			fprintf(stderr, "Unparsable filter ID \"%s\"\n", fhandle);
+			return -1;
+		}
+		t->tcm_handle = handle;
+	}
+	return 0;
+}
+
+struct qdisc_util *get_qdisc_kind(const char *str)
+{
+	void *dlh;
+	char buf[256];
+	struct qdisc_util *q;
+
+	for (q = qdisc_list; q; q = q->next)
+		if (strcmp(q->id, str) == 0)
+			return q;
+
+	snprintf(buf, sizeof(buf), "%s/q_%s.so", get_tc_lib(), str);
+	dlh = dlopen(buf, RTLD_LAZY);
+	if (!dlh) {
+		/* look in current binary, only open once */
+		dlh = BODY;
+		if (dlh == NULL) {
+			dlh = BODY = dlopen(NULL, RTLD_LAZY);
+			if (dlh == NULL)
+				goto noexist;
+		}
+	}
+
+	snprintf(buf, sizeof(buf), "%s_qdisc_util", str);
+	q = dlsym(dlh, buf);
+	if (q == NULL)
+		goto noexist;
+
+reg:
+	q->next = qdisc_list;
+	qdisc_list = q;
+	return q;
+
+noexist:
+	q = malloc(sizeof(*q));
+	if (q) {
+
+		memset(q, 0, sizeof(*q));
+		q->id = strcpy(malloc(strlen(str)+1), str);
+		q->parse_qopt = parse_noqopt;
+		q->print_qopt = print_noqopt;
+		goto reg;
+	}
+	return q;
+}
+
+
+struct filter_util *get_filter_kind(const char *str)
+{
+	void *dlh;
+	char buf[256];
+	struct filter_util *q;
+
+	for (q = filter_list; q; q = q->next)
+		if (strcmp(q->id, str) == 0)
+			return q;
+
+	snprintf(buf, sizeof(buf), "%s/f_%s.so", get_tc_lib(), str);
+	dlh = dlopen(buf, RTLD_LAZY);
+	if (dlh == NULL) {
+		dlh = BODY;
+		if (dlh == NULL) {
+			dlh = BODY = dlopen(NULL, RTLD_LAZY);
+			if (dlh == NULL)
+				goto noexist;
+		}
+	}
+
+	snprintf(buf, sizeof(buf), "%s_filter_util", str);
+	q = dlsym(dlh, buf);
+	if (q == NULL)
+		goto noexist;
+
+reg:
+	q->next = filter_list;
+	filter_list = q;
+	return q;
+noexist:
+	q = malloc(sizeof(*q));
+	if (q) {
+		memset(q, 0, sizeof(*q));
+		strncpy(q->id, str, 15);
+		q->parse_fopt = parse_nofopt;
+		q->print_fopt = print_nofopt;
+		goto reg;
+	}
+	return q;
+}
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: tc [ OPTIONS ] OBJECT { COMMAND | help }\n"
+			"       tc [-force] -batch filename\n"
+	                "where  OBJECT := { qdisc | class | filter | action | monitor }\n"
+	                "       OPTIONS := { -s[tatistics] | -d[etails] | -r[aw] | -p[retty] | -b[atch] [filename] }\n");
+}
+
+static int do_cmd(int argc, char **argv)
+{
+	if (matches(*argv, "qdisc") == 0)
+		return do_qdisc(argc-1, argv+1);
+
+	if (matches(*argv, "class") == 0)
+		return do_class(argc-1, argv+1);
+
+	if (matches(*argv, "filter") == 0)
+		return do_filter(argc-1, argv+1);
+
+	if (matches(*argv, "actions") == 0)
+		return do_action(argc-1, argv+1);
+
+	if (matches(*argv, "monitor") == 0)
+		return do_tcmonitor(argc-1, argv+1);
+
+	if (matches(*argv, "help") == 0) {
+		usage();
+		return 0;
+	}
+
+	fprintf(stderr, "Object \"%s\" is unknown, try \"tc help\".\n",
+		*argv);
+	return -1;
+}
+
+static int batch(const char *name)
+{
+	char *line = NULL;
+	size_t len = 0;
+	int ret = 0;
+
+	if (name && strcmp(name, "-") != 0) {
+		if (freopen(name, "r", stdin) == NULL) {
+			fprintf(stderr, "Cannot open file \"%s\" for reading: %s\n",
+				name, strerror(errno));
+			return -1;
+		}
+	}
+
+	tc_core_init();
+
+	if (rtnl_open(&rth, 0) < 0) {
+		fprintf(stderr, "Cannot open rtnetlink\n");
+		return -1;
+	}
+
+	cmdlineno = 0;
+	while (getcmdline(&line, &len, stdin) != -1) {
+		char *largv[100];
+		int largc;
+
+		largc = makeargs(line, largv, 100);
+		if (largc == 0)
+			continue;	/* blank line */
+
+		if (do_cmd(largc, largv)) {
+			fprintf(stderr, "Command failed %s:%d\n", name, cmdlineno);
+			ret = 1;
+			if (!force)
+				break;
+		}
+	}
+	if (line)
+		free(line);
+
+	rtnl_close(&rth);
+	return ret;
+}
+
+
+int main(int argc, char **argv)
+{
+	int ret;
+	int do_batching = 0;
+	char *batchfile = NULL;
+
+	while (argc > 1) {
+		if (argv[1][0] != '-')
+			break;
+		if (matches(argv[1], "-stats") == 0 ||
+			 matches(argv[1], "-statistics") == 0) {
+			++show_stats;
+		} else if (matches(argv[1], "-details") == 0) {
+			++show_details;
+		} else if (matches(argv[1], "-raw") == 0) {
+			++show_raw;
+		} else if (matches(argv[1], "-pretty") == 0) {
+			++show_pretty;
+		} else if (matches(argv[1], "-Version") == 0) {
+			printf("tc utility, iproute2-ss%s\n", SNAPSHOT);
+			return 0;
+		} else if (matches(argv[1], "-iec") == 0) {
+			++use_iec;
+		} else if (matches(argv[1], "-help") == 0) {
+			usage();
+			return 0;
+		} else if (matches(argv[1], "-force") == 0) {
+			++force;
+		} else 	if (matches(argv[1], "-batch") == 0) {
+			do_batching = 1;
+			if (argc > 2)
+				batchfile = argv[2];
+			argc--;	argv++;
+		} else {
+			fprintf(stderr, "Option \"%s\" is unknown, try \"tc -help\".\n", argv[1]);
+			return -1;
+		}
+		argc--;	argv++;
+	}
+
+	if (do_batching)
+		return batch(batchfile);
+
+	if (argc <= 1) {
+		usage();
+		return 0;
+	}
+
+	tc_core_init();
+	if (rtnl_open(&rth, 0) < 0) {
+		fprintf(stderr, "Cannot open rtnetlink\n");
+		exit(1);
+	}
+
+	ret = do_cmd(argc-1, argv+1);
+	rtnl_close(&rth);
+
+	return ret;
+}
diff --git a/tc/tc_cbq.c b/tc/tc_cbq.c
new file mode 100644
index 0000000..0bb262e
--- /dev/null
+++ b/tc/tc_cbq.c
@@ -0,0 +1,57 @@
+/*
+ * tc_cbq.c		CBQ maintanance routines.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "tc_core.h"
+#include "tc_cbq.h"
+
+unsigned tc_cbq_calc_maxidle(unsigned bndw, unsigned rate, unsigned avpkt,
+			     int ewma_log, unsigned maxburst)
+{
+	double maxidle;
+	double g = 1.0 - 1.0/(1<<ewma_log);
+	double xmt = (double)avpkt/bndw;
+
+	maxidle = xmt*(1-g);
+	if (bndw != rate && maxburst) {
+		double vxmt = (double)avpkt/rate - xmt;
+		vxmt *= (pow(g, -(double)maxburst) - 1);
+		if (vxmt > maxidle)
+			maxidle = vxmt;
+	}
+	return tc_core_time2tick(maxidle*(1<<ewma_log)*TIME_UNITS_PER_SEC);
+}
+
+unsigned tc_cbq_calc_offtime(unsigned bndw, unsigned rate, unsigned avpkt,
+			     int ewma_log, unsigned minburst)
+{
+	double g = 1.0 - 1.0/(1<<ewma_log);
+	double offtime = (double)avpkt/rate - (double)avpkt/bndw;
+
+	if (minburst == 0)
+		return 0;
+	if (minburst == 1)
+		offtime *= pow(g, -(double)minburst) - 1;
+	else
+		offtime *= 1 + (pow(g, -(double)(minburst-1)) - 1)/(1-g);
+	return tc_core_time2tick(offtime*TIME_UNITS_PER_SEC);
+}
diff --git a/tc/tc_cbq.h b/tc/tc_cbq.h
new file mode 100644
index 0000000..8f95649
--- /dev/null
+++ b/tc/tc_cbq.h
@@ -0,0 +1,9 @@
+#ifndef _TC_CBQ_H_
+#define _TC_CBQ_H_ 1
+
+unsigned tc_cbq_calc_maxidle(unsigned bndw, unsigned rate, unsigned avpkt,
+			     int ewma_log, unsigned maxburst);
+unsigned tc_cbq_calc_offtime(unsigned bndw, unsigned rate, unsigned avpkt,
+			     int ewma_log, unsigned minburst);
+
+#endif
diff --git a/tc/tc_class.c b/tc/tc_class.c
new file mode 100644
index 0000000..9d4eea5
--- /dev/null
+++ b/tc/tc_class.c
@@ -0,0 +1,334 @@
+/*
+ * tc_class.c		"tc class".
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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"
+#include "tc_common.h"
+
+static void usage(void);
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: tc class [ add | del | change | replace | show ] dev STRING\n");
+	fprintf(stderr, "       [ classid CLASSID ] [ root | parent CLASSID ]\n");
+	fprintf(stderr, "       [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, "       tc class show [ dev STRING ] [ root | parent CLASSID ]\n");
+	fprintf(stderr, "Where:\n");
+	fprintf(stderr, "QDISC_KIND := { prio | cbq | etc. }\n");
+	fprintf(stderr, "OPTIONS := ... try tc class add <desired QDISC_KIND> help\n");
+	return;
+}
+
+int tc_class_modify(int cmd, unsigned flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr 	n;
+		struct tcmsg 		t;
+		char   			buf[4096];
+	} req;
+	struct qdisc_util *q = NULL;
+	struct tc_estimator est;
+	char  d[16];
+	char  k[16];
+
+	memset(&req, 0, sizeof(req));
+	memset(&est, 0, sizeof(est));
+	memset(d, 0, sizeof(d));
+	memset(k, 0, sizeof(k));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
+	req.n.nlmsg_type = cmd;
+	req.t.tcm_family = AF_UNSPEC;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (d[0])
+				duparg("dev", *argv);
+			strncpy(d, *argv, sizeof(d)-1);
+		} else if (strcmp(*argv, "classid") == 0) {
+			__u32 handle;
+			NEXT_ARG();
+			if (req.t.tcm_handle)
+				duparg("classid", *argv);
+			if (get_tc_classid(&handle, *argv))
+				invarg(*argv, "invalid class ID");
+			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) {
+			if (req.t.tcm_parent) {
+				fprintf(stderr, "Error: \"root\" is duplicate parent ID.\n");
+				return -1;
+			}
+			req.t.tcm_parent = TC_H_ROOT;
+		} else if (strcmp(*argv, "parent") == 0) {
+			__u32 handle;
+			NEXT_ARG();
+			if (req.t.tcm_parent)
+				duparg("parent", *argv);
+			if (get_tc_classid(&handle, *argv))
+				invarg(*argv, "invalid parent ID");
+			req.t.tcm_parent = handle;
+		} else if (matches(*argv, "estimator") == 0) {
+			if (parse_estimator(&argc, &argv, &est))
+				return -1;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			strncpy(k, *argv, sizeof(k)-1);
+
+			q = get_qdisc_kind(k);
+			argc--; argv++;
+			break;
+		}
+		argc--; argv++;
+	}
+
+	if (k[0])
+		addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
+	if (est.ewma_log)
+		addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
+
+	if (q) {
+		if (q->parse_copt == NULL) {
+			fprintf(stderr, "Error: Qdisc \"%s\" is classless.\n", k);
+			return 1;
+		}
+		if (q->parse_copt(q, argc, argv, &req.n))
+			return 1;
+	} else {
+		if (argc) {
+			if (matches(*argv, "help") == 0)
+				usage();
+			fprintf(stderr, "Garbage instead of arguments \"%s ...\". Try \"tc class help\".", *argv);
+			return -1;
+		}
+	}
+
+	if (d[0])  {
+		ll_init_map(&rth);
+
+		if ((req.t.tcm_ifindex = ll_name_to_index(d)) == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n", d);
+			return 1;
+		}
+	}
+
+	if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
+		return 2;
+
+	return 0;
+}
+
+int filter_ifindex;
+__u32 filter_qdisc;
+__u32 filter_classid;
+
+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 qdisc_util *q;
+	char abuf[256];
+
+	if (n->nlmsg_type != RTM_NEWTCLASS && n->nlmsg_type != RTM_DELTCLASS) {
+		fprintf(stderr, "Not a class\n");
+		return 0;
+	}
+	len -= NLMSG_LENGTH(sizeof(*t));
+	if (len < 0) {
+		fprintf(stderr, "Wrong len %d\n", len);
+		return -1;
+	}
+	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) {
+		fprintf(stderr, "print_class: NULL kind\n");
+		return -1;
+	}
+
+	if (n->nlmsg_type == RTM_DELTCLASS)
+		fprintf(fp, "deleted ");
+
+	abuf[0] = 0;
+	if (t->tcm_handle) {
+		if (filter_qdisc)
+			print_tc_classid(abuf, sizeof(abuf), TC_H_MIN(t->tcm_handle));
+		else
+			print_tc_classid(abuf, sizeof(abuf), t->tcm_handle);
+	}
+	fprintf(fp, "class %s %s ", (char*)RTA_DATA(tb[TCA_KIND]), abuf);
+
+	if (filter_ifindex == 0)
+		fprintf(fp, "dev %s ", ll_index_to_name(t->tcm_ifindex));
+
+	if (t->tcm_parent == TC_H_ROOT)
+		fprintf(fp, "root ");
+	else {
+		if (filter_qdisc)
+			print_tc_classid(abuf, sizeof(abuf), TC_H_MIN(t->tcm_parent));
+		else
+			print_tc_classid(abuf, sizeof(abuf), t->tcm_parent);
+		fprintf(fp, "parent %s ", abuf);
+	}
+	if (t->tcm_info)
+		fprintf(fp, "leaf %x: ", t->tcm_info>>16);
+	q = get_qdisc_kind(RTA_DATA(tb[TCA_KIND]));
+	if (tb[TCA_OPTIONS]) {
+		if (q && q->print_copt)
+			q->print_copt(q, fp, tb[TCA_OPTIONS]);
+		else
+			fprintf(fp, "[cannot parse class parameters]");
+	}
+	fprintf(fp, "\n");
+	if (show_stats) {
+		struct rtattr *xstats = NULL;
+
+		if (tb[TCA_STATS] || tb[TCA_STATS2]) {
+			print_tcstats_attr(fp, tb, " ", &xstats);
+			fprintf(fp, "\n");
+		}
+		if (q && (xstats || tb[TCA_XSTATS]) && q->print_xstats) {
+			q->print_xstats(q, fp, xstats ? : tb[TCA_XSTATS]);
+			fprintf(fp, "\n");
+		}
+	}
+	fflush(fp);
+	return 0;
+}
+
+
+int tc_class_list(int argc, char **argv)
+{
+	struct tcmsg t;
+	char d[16];
+
+	memset(&t, 0, sizeof(t));
+	t.tcm_family = AF_UNSPEC;
+	memset(d, 0, sizeof(d));
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (d[0])
+				duparg("dev", *argv);
+			strncpy(d, *argv, sizeof(d)-1);
+		} else if (strcmp(*argv, "qdisc") == 0) {
+			NEXT_ARG();
+			if (filter_qdisc)
+				duparg("qdisc", *argv);
+			if (get_qdisc_handle(&filter_qdisc, *argv))
+				invarg(*argv, "invalid qdisc ID");
+		} 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");
+		} else if (strcmp(*argv, "root") == 0) {
+			if (t.tcm_parent) {
+				fprintf(stderr, "Error: \"root\" is duplicate parent ID\n");
+				return -1;
+			}
+			t.tcm_parent = TC_H_ROOT;
+		} else if (strcmp(*argv, "parent") == 0) {
+			__u32 handle;
+			if (t.tcm_parent)
+				duparg("parent", *argv);
+			NEXT_ARG();
+			if (get_tc_classid(&handle, *argv))
+				invarg(*argv, "invalid parent ID");
+			t.tcm_parent = handle;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			fprintf(stderr, "What is \"%s\"? Try \"tc class help\".\n", *argv);
+			return -1;
+		}
+
+		argc--; argv++;
+	}
+
+ 	ll_init_map(&rth);
+
+	if (d[0]) {
+		if ((t.tcm_ifindex = ll_name_to_index(d)) == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n", d);
+			return 1;
+		}
+		filter_ifindex = t.tcm_ifindex;
+	}
+
+ 	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, NULL, NULL) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+int do_class(int argc, char **argv)
+{
+	if (argc < 1)
+		return tc_class_list(0, NULL);
+	if (matches(*argv, "add") == 0)
+		return tc_class_modify(RTM_NEWTCLASS, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1);
+	if (matches(*argv, "change") == 0)
+		return tc_class_modify(RTM_NEWTCLASS, 0, argc-1, argv+1);
+	if (matches(*argv, "replace") == 0)
+		return tc_class_modify(RTM_NEWTCLASS, NLM_F_CREATE, argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return tc_class_modify(RTM_DELTCLASS, 0,  argc-1, argv+1);
+#if 0
+	if (matches(*argv, "get") == 0)
+		return tc_class_get(RTM_GETTCLASS, 0,  argc-1, argv+1);
+#endif
+	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+	    || matches(*argv, "lst") == 0)
+		return tc_class_list(argc-1, argv+1);
+	if (matches(*argv, "help") == 0) {
+		usage();
+		return 0;
+	}
+	fprintf(stderr, "Command \"%s\" is unknown, try \"tc class help\".\n", *argv);
+	return -1;
+}
diff --git a/tc/tc_common.h b/tc/tc_common.h
new file mode 100644
index 0000000..4f88856
--- /dev/null
+++ b/tc/tc_common.h
@@ -0,0 +1,21 @@
+
+#define TCA_BUF_MAX	(64*1024)
+
+extern struct rtnl_handle rth;
+extern int do_qdisc(int argc, char **argv);
+extern int do_class(int argc, char **argv);
+extern int do_filter(int argc, char **argv);
+extern int do_action(int argc, char **argv);
+extern int do_tcmonitor(int argc, char **argv);
+extern int print_action(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg);
+extern int print_filter(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg);
+extern int print_qdisc(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg);
+extern int print_class(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg);
+extern void print_size_table(FILE *fp, const char *prefix, struct rtattr *rta);
+
+struct tc_estimator;
+extern int parse_estimator(int *p_argc, char ***p_argv, struct tc_estimator *est);
+
+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);
diff --git a/tc/tc_core.c b/tc/tc_core.c
new file mode 100644
index 0000000..9a0ff39
--- /dev/null
+++ b/tc/tc_core.c
@@ -0,0 +1,211 @@
+/*
+ * tc_core.c		TC core library.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "tc_core.h"
+#include <linux/atm.h>
+
+static double tick_in_usec = 1;
+static double clock_factor = 1;
+
+int tc_core_time2big(unsigned time)
+{
+	__u64 t = time;
+
+	t *= tick_in_usec;
+	return (t >> 32) != 0;
+}
+
+
+unsigned tc_core_time2tick(unsigned time)
+{
+	return time*tick_in_usec;
+}
+
+unsigned tc_core_tick2time(unsigned tick)
+{
+	return tick/tick_in_usec;
+}
+
+unsigned tc_core_time2ktime(unsigned time)
+{
+	return time * clock_factor;
+}
+
+unsigned tc_core_ktime2time(unsigned ktime)
+{
+	return ktime / clock_factor;
+}
+
+unsigned tc_calc_xmittime(unsigned rate, unsigned size)
+{
+	return tc_core_time2tick(TIME_UNITS_PER_SEC*((double)size/rate));
+}
+
+unsigned tc_calc_xmitsize(unsigned rate, unsigned ticks)
+{
+	return ((double)rate*tc_core_tick2time(ticks))/TIME_UNITS_PER_SEC;
+}
+
+/*
+ * The align to ATM cells is used for determining the (ATM) SAR
+ * alignment overhead at the ATM layer. (SAR = Segmentation And
+ * Reassembly).  This is for example needed when scheduling packet on
+ * an ADSL connection.  Note that the extra ATM-AAL overhead is _not_
+ * included in this calculation. This overhead is added in the kernel
+ * before doing the rate table lookup, as this gives better precision
+ * (as the table will always be aligned for 48 bytes).
+ *  --Hawk, d.7/11-2004. <hawk@diku.dk>
+ */
+unsigned tc_align_to_atm(unsigned size)
+{
+	int linksize, cells;
+	cells = size / ATM_CELL_PAYLOAD;
+	if ((size % ATM_CELL_PAYLOAD) > 0)
+		cells++;
+
+	linksize = cells * ATM_CELL_SIZE; /* Use full cell size to add ATM tax */
+	return linksize;
+}
+
+unsigned tc_adjust_size(unsigned sz, unsigned mpu, enum link_layer linklayer)
+{
+	if (sz < mpu)
+		sz = mpu;
+
+	switch (linklayer) {
+	case LINKLAYER_ATM:
+		return tc_align_to_atm(sz);
+	case LINKLAYER_ETHERNET:
+	default:
+		// No size adjustments on Ethernet
+		return sz;
+	}
+}
+
+/*
+   rtab[pkt_len>>cell_log] = pkt_xmit_time
+ */
+
+int tc_calc_rtable(struct tc_ratespec *r, __u32 *rtab,
+		   int cell_log, unsigned mtu,
+		   enum link_layer linklayer)
+{
+	int i;
+	unsigned sz;
+	unsigned bps = r->rate;
+	unsigned mpu = r->mpu;
+
+	if (mtu == 0)
+		mtu = 2047;
+
+	if (cell_log < 0) {
+		cell_log = 0;
+		while ((mtu >> cell_log) > 255)
+			cell_log++;
+	}
+
+	for (i=0; i<256; i++) {
+		sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer);
+		rtab[i] = tc_calc_xmittime(bps, sz);
+	}
+
+	r->cell_align=-1; // Due to the sz calc
+	r->cell_log=cell_log;
+	return cell_log;
+}
+
+/*
+   stab[pkt_len>>cell_log] = pkt_xmit_size>>size_log
+ */
+
+int tc_calc_size_table(struct tc_sizespec *s, __u16 **stab)
+{
+	int i;
+	enum link_layer linklayer = s->linklayer;
+	unsigned int sz;
+
+	if (linklayer <= LINKLAYER_ETHERNET && s->mpu == 0) {
+		/* don't need data table in this case (only overhead set) */
+		s->mtu = 0;
+		s->tsize = 0;
+		s->cell_log = 0;
+		s->cell_align = 0;
+		*stab = NULL;
+		return 0;
+	}
+
+	if (s->mtu == 0)
+		s->mtu = 2047;
+	if (s->tsize == 0)
+		s->tsize = 512;
+
+	s->cell_log = 0;
+	while ((s->mtu >> s->cell_log) > s->tsize - 1)
+		s->cell_log++;
+
+	*stab = malloc(s->tsize * sizeof(__u16));
+	if (!*stab)
+		return -1;
+
+again:
+	for (i = s->tsize - 1; i >= 0; i--) {
+		sz = tc_adjust_size((i + 1) << s->cell_log, s->mpu, linklayer);
+		if ((sz >> s->size_log) > UINT16_MAX) {
+			s->size_log++;
+			goto again;
+		}
+		(*stab)[i] = sz >> s->size_log;
+	}
+
+	s->cell_align = -1; // Due to the sz calc
+	return 0;
+}
+
+int tc_core_init()
+{
+	FILE *fp;
+	__u32 clock_res;
+	__u32 t2us;
+	__u32 us2t;
+
+	fp = fopen("/proc/net/psched", "r");
+	if (fp == NULL)
+		return -1;
+
+	if (fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res) != 3) {
+		fclose(fp);
+		return -1;
+	}
+	fclose(fp);
+
+	/* compatibility hack: for old iproute binaries (ignoring
+	 * the kernel clock resolution) the kernel advertises a
+	 * tick multiplier of 1000 in case of nano-second resolution,
+	 * which really is 1. */
+	if (clock_res == 1000000000)
+		t2us = us2t;
+
+	clock_factor  = (double)clock_res / TIME_UNITS_PER_SEC;
+	tick_in_usec = (double)t2us / us2t * clock_factor;
+	return 0;
+}
diff --git a/tc/tc_core.h b/tc/tc_core.h
new file mode 100644
index 0000000..5a693ba
--- /dev/null
+++ b/tc/tc_core.h
@@ -0,0 +1,34 @@
+#ifndef _TC_CORE_H_
+#define _TC_CORE_H_ 1
+
+#include <asm/types.h>
+#include <linux/pkt_sched.h>
+
+#define TIME_UNITS_PER_SEC	1000000
+
+enum link_layer {
+	LINKLAYER_UNSPEC,
+	LINKLAYER_ETHERNET,
+	LINKLAYER_ATM,
+};
+
+
+int  tc_core_time2big(unsigned time);
+unsigned tc_core_time2tick(unsigned time);
+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);
+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);
+
+int tc_setup_estimator(unsigned A, unsigned time_const, struct tc_estimator *est);
+
+int tc_core_init(void);
+
+extern struct rtnl_handle g_rth;
+extern int is_batch_mode;
+
+#endif
diff --git a/tc/tc_estimator.c b/tc/tc_estimator.c
new file mode 100644
index 0000000..e559add
--- /dev/null
+++ b/tc/tc_estimator.c
@@ -0,0 +1,44 @@
+/*
+ * tc_core.c		TC core library.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "tc_core.h"
+
+int tc_setup_estimator(unsigned A, unsigned time_const, struct tc_estimator *est)
+{
+	for (est->interval=0; est->interval<=5; est->interval++) {
+		if (A <= (1<<est->interval)*(TIME_UNITS_PER_SEC/4))
+			break;
+	}
+	if (est->interval > 5)
+		return -1;
+	est->interval -= 2;
+	for (est->ewma_log=1; est->ewma_log<32; est->ewma_log++) {
+		double w = 1.0 - 1.0/(1<<est->ewma_log);
+		if (A/(-log(w)) > time_const)
+			break;
+	}
+	est->ewma_log--;
+	if (est->ewma_log==0 || est->ewma_log >= 31)
+		return -1;
+	return 0;
+}
diff --git a/tc/tc_filter.c b/tc/tc_filter.c
new file mode 100644
index 0000000..919c57c
--- /dev/null
+++ b/tc/tc_filter.c
@@ -0,0 +1,378 @@
+/*
+ * tc_filter.c		"tc 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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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 <linux/if_ether.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+static void usage(void);
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: tc filter [ add | del | change | replace | show ] dev STRING\n");
+	fprintf(stderr, "       [ pref PRIO ] protocol PROTO\n");
+	fprintf(stderr, "       [ estimator INTERVAL TIME_CONSTANT ]\n");
+	fprintf(stderr, "       [ root | classid CLASSID ] [ handle FILTERID ]\n");
+	fprintf(stderr, "       [ [ FILTER_TYPE ] [ help | OPTIONS ] ]\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, "       tc filter show [ dev STRING ] [ root | parent CLASSID ]\n");
+	fprintf(stderr, "Where:\n");
+	fprintf(stderr, "FILTER_TYPE := { rsvp | u32 | fw | route | etc. }\n");
+	fprintf(stderr, "FILTERID := ... format depends on classifier, see there\n");
+	fprintf(stderr, "OPTIONS := ... try tc filter add <desired FILTER_KIND> help\n");
+	return;
+}
+
+
+int tc_filter_modify(int cmd, unsigned flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr 	n;
+		struct tcmsg 		t;
+		char   			buf[MAX_MSG];
+	} req;
+	struct filter_util *q = NULL;
+	__u32 prio = 0;
+	__u32 protocol = 0;
+	int protocol_set = 0;
+	char *fhandle = NULL;
+	char  d[16];
+	char  k[16];
+	struct tc_estimator est;
+
+	memset(&req, 0, sizeof(req));
+	memset(&est, 0, sizeof(est));
+	memset(d, 0, sizeof(d));
+	memset(k, 0, sizeof(k));
+	memset(&req, 0, sizeof(req));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
+	req.n.nlmsg_type = cmd;
+	req.t.tcm_family = AF_UNSPEC;
+
+	if (cmd == RTM_NEWTFILTER && flags & NLM_F_CREATE)
+		protocol = ETH_P_ALL;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (d[0])
+				duparg("dev", *argv);
+			strncpy(d, *argv, sizeof(d)-1);
+		} else if (strcmp(*argv, "root") == 0) {
+			if (req.t.tcm_parent) {
+				fprintf(stderr, "Error: \"root\" is duplicate parent ID\n");
+				return -1;
+			}
+			req.t.tcm_parent = TC_H_ROOT;
+		} else if (strcmp(*argv, "parent") == 0) {
+			__u32 handle;
+			NEXT_ARG();
+			if (req.t.tcm_parent)
+				duparg("parent", *argv);
+			if (get_tc_classid(&handle, *argv))
+				invarg(*argv, "Invalid parent ID");
+			req.t.tcm_parent = handle;
+		} else if (strcmp(*argv, "handle") == 0) {
+			NEXT_ARG();
+			if (fhandle)
+				duparg("handle", *argv);
+			fhandle = *argv;
+		} else if (matches(*argv, "preference") == 0 ||
+			   matches(*argv, "priority") == 0) {
+			NEXT_ARG();
+			if (prio)
+				duparg("priority", *argv);
+			if (get_u32(&prio, *argv, 0))
+				invarg(*argv, "invalid priority value");
+		} 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");
+			protocol = id;
+			protocol_set = 1;
+		} else if (matches(*argv, "estimator") == 0) {
+			if (parse_estimator(&argc, &argv, &est) < 0)
+				return -1;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+			return 0;
+		} else {
+			strncpy(k, *argv, sizeof(k)-1);
+
+			q = get_filter_kind(k);
+			argc--; argv++;
+			break;
+		}
+
+		argc--; argv++;
+	}
+
+	req.t.tcm_info = TC_H_MAKE(prio<<16, protocol);
+
+	if (k[0])
+		addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
+
+	if (q) {
+		if (q->parse_fopt(q, fhandle, argc, argv, &req.n))
+			return 1;
+	} else {
+		if (fhandle) {
+			fprintf(stderr, "Must specify filter type when using "
+				"\"handle\"\n");
+			return -1;
+		}
+		if (argc) {
+			if (matches(*argv, "help") == 0)
+				usage();
+			fprintf(stderr, "Garbage instead of arguments \"%s ...\". Try \"tc filter help\".\n", *argv);
+			return -1;
+		}
+	}
+	if (est.ewma_log)
+		addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
+
+
+	if (d[0])  {
+ 		ll_init_map(&rth);
+
+		if ((req.t.tcm_ifindex = ll_name_to_index(d)) == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n", d);
+			return 1;
+		}
+	}
+
+ 	if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) {
+		fprintf(stderr, "We have an error talking to the kernel\n");
+		return 2;
+	}
+
+	return 0;
+}
+
+static __u32 filter_parent;
+static int filter_ifindex;
+static __u32 filter_prio;
+static __u32 filter_protocol;
+__u16 f_proto = 0;
+
+int print_filter(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 filter_util *q;
+	char abuf[256];
+
+	if (n->nlmsg_type != RTM_NEWTFILTER && n->nlmsg_type != RTM_DELTFILTER) {
+		fprintf(stderr, "Not a filter\n");
+		return 0;
+	}
+	len -= NLMSG_LENGTH(sizeof(*t));
+	if (len < 0) {
+		fprintf(stderr, "Wrong len %d\n", len);
+		return -1;
+	}
+
+	memset(tb, 0, sizeof(tb));
+	parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len);
+
+	if (tb[TCA_KIND] == NULL) {
+		fprintf(stderr, "print_filter: NULL kind\n");
+		return -1;
+	}
+
+	if (n->nlmsg_type == RTM_DELTFILTER)
+		fprintf(fp, "deleted ");
+
+	fprintf(fp, "filter ");
+	if (!filter_ifindex || filter_ifindex != t->tcm_ifindex)
+		fprintf(fp, "dev %s ", ll_index_to_name(t->tcm_ifindex));
+
+	if (!filter_parent || filter_parent != t->tcm_parent) {
+		if (t->tcm_parent == TC_H_ROOT)
+			fprintf(fp, "root ");
+		else {
+			print_tc_classid(abuf, sizeof(abuf), t->tcm_parent);
+			fprintf(fp, "parent %s ", abuf);
+		}
+	}
+	if (t->tcm_info) {
+		f_proto = TC_H_MIN(t->tcm_info);
+		__u32 prio = TC_H_MAJ(t->tcm_info)>>16;
+		if (!filter_protocol || filter_protocol != f_proto) {
+			if (f_proto) {
+				SPRINT_BUF(b1);
+				fprintf(fp, "protocol %s ",
+					ll_proto_n2a(f_proto, b1, sizeof(b1)));
+			}
+		}
+		if (!filter_prio || filter_prio != prio) {
+			if (prio)
+				fprintf(fp, "pref %u ", prio);
+		}
+	}
+	fprintf(fp, "%s ", (char*)RTA_DATA(tb[TCA_KIND]));
+	q = get_filter_kind(RTA_DATA(tb[TCA_KIND]));
+	if (tb[TCA_OPTIONS]) {
+		if (q)
+			q->print_fopt(q, fp, tb[TCA_OPTIONS], t->tcm_handle);
+		else
+			fprintf(fp, "[cannot parse parameters]");
+	}
+	fprintf(fp, "\n");
+
+	if (show_stats && (tb[TCA_STATS] || tb[TCA_STATS2])) {
+		print_tcstats_attr(fp, tb, " ", NULL);
+		fprintf(fp, "\n");
+	}
+
+	fflush(fp);
+	return 0;
+}
+
+
+int tc_filter_list(int argc, char **argv)
+{
+	struct tcmsg t;
+	char d[16];
+	__u32 prio = 0;
+	__u32 protocol = 0;
+	char *fhandle = NULL;
+
+	memset(&t, 0, sizeof(t));
+	t.tcm_family = AF_UNSPEC;
+	memset(d, 0, sizeof(d));
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (d[0])
+				duparg("dev", *argv);
+			strncpy(d, *argv, sizeof(d)-1);
+		} else if (strcmp(*argv, "root") == 0) {
+			if (t.tcm_parent) {
+				fprintf(stderr, "Error: \"root\" is duplicate parent ID\n");
+				return -1;
+			}
+			filter_parent = t.tcm_parent = TC_H_ROOT;
+		} else if (strcmp(*argv, "parent") == 0) {
+			__u32 handle;
+			NEXT_ARG();
+			if (t.tcm_parent)
+				duparg("parent", *argv);
+			if (get_tc_classid(&handle, *argv))
+				invarg(*argv, "invalid parent ID");
+			filter_parent = t.tcm_parent = handle;
+		} else if (strcmp(*argv, "handle") == 0) {
+			NEXT_ARG();
+			if (fhandle)
+				duparg("handle", *argv);
+			fhandle = *argv;
+		} else if (matches(*argv, "preference") == 0 ||
+			   matches(*argv, "priority") == 0) {
+			NEXT_ARG();
+			if (prio)
+				duparg("priority", *argv);
+			if (get_u32(&prio, *argv, 0))
+				invarg(*argv, "invalid preference");
+			filter_prio = prio;
+		} else if (matches(*argv, "protocol") == 0) {
+			__u16 res;
+			NEXT_ARG();
+			if (protocol)
+				duparg("protocol", *argv);
+			if (ll_proto_a2n(&res, *argv))
+				invarg(*argv, "invalid protocol");
+			protocol = res;
+			filter_protocol = protocol;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			fprintf(stderr, " What is \"%s\"? Try \"tc filter help\"\n", *argv);
+			return -1;
+		}
+
+		argc--; argv++;
+	}
+
+	t.tcm_info = TC_H_MAKE(prio<<16, protocol);
+
+ 	ll_init_map(&rth);
+
+	if (d[0]) {
+		if ((t.tcm_ifindex = ll_name_to_index(d)) == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n", d);
+			return 1;
+		}
+		filter_ifindex = t.tcm_ifindex;
+	}
+
+ 	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, NULL, NULL) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+int do_filter(int argc, char **argv)
+{
+	if (argc < 1)
+		return tc_filter_list(0, NULL);
+	if (matches(*argv, "add") == 0)
+		return tc_filter_modify(RTM_NEWTFILTER, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1);
+	if (matches(*argv, "change") == 0)
+		return tc_filter_modify(RTM_NEWTFILTER, 0, argc-1, argv+1);
+	if (matches(*argv, "replace") == 0)
+		return tc_filter_modify(RTM_NEWTFILTER, NLM_F_CREATE, argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return tc_filter_modify(RTM_DELTFILTER, 0,  argc-1, argv+1);
+#if 0
+	if (matches(*argv, "get") == 0)
+		return tc_filter_get(RTM_GETTFILTER, 0,  argc-1, argv+1);
+#endif
+	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+	    || matches(*argv, "lst") == 0)
+		return tc_filter_list(argc-1, argv+1);
+	if (matches(*argv, "help") == 0) {
+		usage();
+		return 0;
+        }
+	fprintf(stderr, "Command \"%s\" is unknown, try \"tc filter help\".\n", *argv);
+	return -1;
+}
+
diff --git a/tc/tc_monitor.c b/tc/tc_monitor.c
new file mode 100644
index 0000000..bf58744
--- /dev/null
+++ b/tc/tc_monitor.c
@@ -0,0 +1,110 @@
+/*
+ * tc_monitor.c		"tc 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:	Jamal Hadi Salim
+ *
+ */
+
+#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 <time.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: tc monitor\n");
+	exit(-1);
+}
+
+
+int accept_tcmsg(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE*)arg;
+
+	if (n->nlmsg_type == RTM_NEWTFILTER || n->nlmsg_type == RTM_DELTFILTER) {
+		print_filter(who, n, arg);
+		return 0;
+	}
+	if (n->nlmsg_type == RTM_NEWTCLASS || n->nlmsg_type == RTM_DELTCLASS) {
+		print_class(who, n, arg);
+		return 0;
+	}
+	if (n->nlmsg_type == RTM_NEWQDISC || n->nlmsg_type == RTM_DELQDISC) {
+		print_qdisc(who, n, arg);
+		return 0;
+	}
+	if (n->nlmsg_type == RTM_GETACTION || n->nlmsg_type == RTM_NEWACTION ||
+	    n->nlmsg_type == RTM_DELACTION) {
+		print_action(who, n, arg);
+		return 0;
+	}
+	if (n->nlmsg_type != NLMSG_ERROR && n->nlmsg_type != NLMSG_NOOP &&
+	    n->nlmsg_type != NLMSG_DONE) {
+		fprintf(fp, "Unknown message: length %08d type %08x flags %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+	}
+	return 0;
+}
+
+int do_tcmonitor(int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	char *file = NULL;
+	unsigned groups = nl_mgrp(RTNLGRP_TC);
+
+	while (argc > 0) {
+		if (matches(*argv, "file") == 0) {
+			NEXT_ARG();
+			file = *argv;
+		} else {
+			if (matches(*argv, "help") == 0) {
+				usage();
+			} else {
+				fprintf(stderr, "Argument \"%s\" is unknown, try \"tc monitor help\".\n", *argv);
+				exit(-1);
+			}
+		}
+		argc--;	argv++;
+	}
+
+	if (file) {
+		FILE *fp;
+		fp = fopen(file, "r");
+		if (fp == NULL) {
+			perror("Cannot fopen");
+			exit(-1);
+		}
+		return rtnl_from_file(fp, accept_tcmsg, (void*)stdout);
+	}
+
+	if (rtnl_open(&rth, groups) < 0)
+		exit(1);
+
+	ll_init_map(&rth);
+
+	if (rtnl_listen(&rth, accept_tcmsg, (void*)stdout) < 0) {
+		rtnl_close(&rth);
+		exit(2);
+	}
+
+	rtnl_close(&rth);
+	exit(0);
+}
diff --git a/tc/tc_qdisc.c b/tc/tc_qdisc.c
new file mode 100644
index 0000000..c7f2988
--- /dev/null
+++ b/tc/tc_qdisc.c
@@ -0,0 +1,361 @@
+/*
+ * tc_qdisc.c		"tc qdisc".
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *		J Hadi Salim: Extension to ingress
+ */
+
+#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 <malloc.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+static int usage(void);
+
+static int usage(void)
+{
+	fprintf(stderr, "Usage: tc qdisc [ add | del | replace | change | show ] dev STRING\n");
+	fprintf(stderr, "       [ handle QHANDLE ] [ root | ingress | parent CLASSID ]\n");
+	fprintf(stderr, "       [ estimator INTERVAL TIME_CONSTANT ]\n");
+	fprintf(stderr, "       [ stab [ help | STAB_OPTIONS] ]\n");
+	fprintf(stderr, "       [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n");
+	fprintf(stderr, "\n");
+	fprintf(stderr, "       tc qdisc show [ dev STRING ] [ingress]\n");
+	fprintf(stderr, "Where:\n");
+	fprintf(stderr, "QDISC_KIND := { [p|b]fifo | tbf | prio | cbq | red | etc. }\n");
+	fprintf(stderr, "OPTIONS := ... try tc qdisc add <desired QDISC_KIND> help\n");
+	fprintf(stderr, "STAB_OPTIONS := ... try tc qdisc add stab help\n");
+	return -1;
+}
+
+int tc_qdisc_modify(int cmd, unsigned flags, int argc, char **argv)
+{
+	struct qdisc_util *q = NULL;
+	struct tc_estimator est;
+	struct {
+		struct tc_sizespec	szopts;
+		__u16			*data;
+	} stab;
+	char  d[16];
+	char  k[16];
+	struct {
+		struct nlmsghdr 	n;
+		struct tcmsg 		t;
+		char   			buf[TCA_BUF_MAX];
+	} req;
+
+	memset(&req, 0, sizeof(req));
+	memset(&stab, 0, sizeof(stab));
+	memset(&est, 0, sizeof(est));
+	memset(&d, 0, sizeof(d));
+	memset(&k, 0, sizeof(k));
+
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
+	req.n.nlmsg_type = cmd;
+	req.t.tcm_family = AF_UNSPEC;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (d[0])
+				duparg("dev", *argv);
+			strncpy(d, *argv, sizeof(d)-1);
+		} else if (strcmp(*argv, "handle") == 0) {
+			__u32 handle;
+			if (req.t.tcm_handle)
+				duparg("handle", *argv);
+			NEXT_ARG();
+			if (get_qdisc_handle(&handle, *argv))
+				invarg(*argv, "invalid qdisc ID");
+			req.t.tcm_handle = handle;
+		} else if (strcmp(*argv, "root") == 0) {
+			if (req.t.tcm_parent) {
+				fprintf(stderr, "Error: \"root\" is duplicate parent ID\n");
+				return -1;
+			}
+			req.t.tcm_parent = TC_H_ROOT;
+#ifdef TC_H_INGRESS
+		} else if (strcmp(*argv, "ingress") == 0) {
+			if (req.t.tcm_parent) {
+				fprintf(stderr, "Error: \"ingress\" is a duplicate parent ID\n");
+				return -1;
+			}
+			req.t.tcm_parent = TC_H_INGRESS;
+			strncpy(k, "ingress", sizeof(k)-1);
+			q = get_qdisc_kind(k);
+			req.t.tcm_handle = 0xffff0000;
+
+			argc--; argv++;
+			break;
+#endif
+		} else if (strcmp(*argv, "parent") == 0) {
+			__u32 handle;
+			NEXT_ARG();
+			if (req.t.tcm_parent)
+				duparg("parent", *argv);
+			if (get_tc_classid(&handle, *argv))
+				invarg(*argv, "invalid parent ID");
+			req.t.tcm_parent = handle;
+		} else if (matches(*argv, "estimator") == 0) {
+			if (parse_estimator(&argc, &argv, &est))
+				return -1;
+		} else if (matches(*argv, "stab") == 0) {
+			if (parse_size_table(&argc, &argv, &stab.szopts) < 0)
+				return -1;
+			continue;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			strncpy(k, *argv, sizeof(k)-1);
+
+			q = get_qdisc_kind(k);
+			argc--; argv++;
+			break;
+		}
+		argc--; argv++;
+	}
+
+	if (k[0])
+		addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
+	if (est.ewma_log)
+		addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
+
+	if (q) {
+		if (!q->parse_qopt) {
+			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)
+				usage();
+
+			fprintf(stderr, "Garbage instead of arguments \"%s ...\". Try \"tc qdisc help\".\n", *argv);
+			return -1;
+		}
+	}
+
+	if (check_size_table_opts(&stab.szopts)) {
+		struct rtattr *tail;
+
+		if (tc_calc_size_table(&stab.szopts, &stab.data) < 0) {
+			fprintf(stderr, "failed to calculate size table.\n");
+			return -1;
+		}
+
+		tail = NLMSG_TAIL(&req.n);
+		addattr_l(&req.n, sizeof(req), TCA_STAB, NULL, 0);
+		addattr_l(&req.n, sizeof(req), TCA_STAB_BASE, &stab.szopts,
+			  sizeof(stab.szopts));
+		if (stab.data)
+			addattr_l(&req.n, sizeof(req), TCA_STAB_DATA, stab.data,
+				  stab.szopts.tsize * sizeof(__u16));
+		tail->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail;
+		if (stab.data)
+			free(stab.data);
+	}
+
+	if (d[0])  {
+		int idx;
+
+ 		ll_init_map(&rth);
+
+		if ((idx = ll_name_to_index(d)) == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n", d);
+			return 1;
+		}
+		req.t.tcm_ifindex = idx;
+	}
+
+ 	if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
+		return 2;
+
+	return 0;
+}
+
+static int filter_ifindex;
+
+int print_qdisc(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 qdisc_util *q;
+	char abuf[256];
+
+	if (n->nlmsg_type != RTM_NEWQDISC && n->nlmsg_type != RTM_DELQDISC) {
+		fprintf(stderr, "Not a qdisc\n");
+		return 0;
+	}
+	len -= NLMSG_LENGTH(sizeof(*t));
+	if (len < 0) {
+		fprintf(stderr, "Wrong len %d\n", len);
+		return -1;
+	}
+
+	if (filter_ifindex && filter_ifindex != t->tcm_ifindex)
+		return 0;
+
+	memset(tb, 0, sizeof(tb));
+	parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len);
+
+	if (tb[TCA_KIND] == NULL) {
+		fprintf(stderr, "print_qdisc: NULL kind\n");
+		return -1;
+	}
+
+	if (n->nlmsg_type == RTM_DELQDISC)
+		fprintf(fp, "deleted ");
+
+	fprintf(fp, "qdisc %s %x: ", (char*)RTA_DATA(tb[TCA_KIND]), t->tcm_handle>>16);
+	if (filter_ifindex == 0)
+		fprintf(fp, "dev %s ", ll_index_to_name(t->tcm_ifindex));
+	if (t->tcm_parent == TC_H_ROOT)
+		fprintf(fp, "root ");
+	else if (t->tcm_parent) {
+		print_tc_classid(abuf, sizeof(abuf), t->tcm_parent);
+		fprintf(fp, "parent %s ", abuf);
+	}
+	if (t->tcm_info != 1) {
+		fprintf(fp, "refcnt %d ", t->tcm_info);
+	}
+	/* pfifo_fast is generic enough to warrant the hardcoding --JHS */
+
+	if (0 == strcmp("pfifo_fast", RTA_DATA(tb[TCA_KIND])))
+		q = get_qdisc_kind("prio");
+	else
+		q = get_qdisc_kind(RTA_DATA(tb[TCA_KIND]));
+
+	if (tb[TCA_OPTIONS]) {
+		if (q)
+			q->print_qopt(q, fp, tb[TCA_OPTIONS]);
+		else
+			fprintf(fp, "[cannot parse qdisc parameters]");
+	}
+	fprintf(fp, "\n");
+	if (show_details && tb[TCA_STAB]) {
+		print_size_table(fp, " ", tb[TCA_STAB]);
+		fprintf(fp, "\n");
+	}
+	if (show_stats) {
+		struct rtattr *xstats = NULL;
+
+		if (tb[TCA_STATS] || tb[TCA_STATS2] || tb[TCA_XSTATS]) {
+			print_tcstats_attr(fp, tb, " ", &xstats);
+			fprintf(fp, "\n");
+		}
+
+		if (q && xstats && q->print_xstats) {
+			q->print_xstats(q, fp, xstats);
+			fprintf(fp, "\n");
+		}
+	}
+	fflush(fp);
+	return 0;
+}
+
+
+int tc_qdisc_list(int argc, char **argv)
+{
+	struct tcmsg t;
+	char d[16];
+
+	memset(&t, 0, sizeof(t));
+	t.tcm_family = AF_UNSPEC;
+	memset(&d, 0, sizeof(d));
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			strncpy(d, *argv, sizeof(d)-1);
+#ifdef TC_H_INGRESS
+                } else if (strcmp(*argv, "ingress") == 0) {
+                             if (t.tcm_parent) {
+                                     fprintf(stderr, "Duplicate parent ID\n");
+                                     usage();
+                             }
+                             t.tcm_parent = TC_H_INGRESS;
+#endif
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			fprintf(stderr, "What is \"%s\"? Try \"tc qdisc help\".\n", *argv);
+			return -1;
+		}
+
+		argc--; argv++;
+	}
+
+ 	ll_init_map(&rth);
+
+	if (d[0]) {
+		if ((t.tcm_ifindex = ll_name_to_index(d)) == 0) {
+			fprintf(stderr, "Cannot find device \"%s\"\n", d);
+			return 1;
+		}
+		filter_ifindex = t.tcm_ifindex;
+	}
+
+ 	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, NULL, NULL) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+int do_qdisc(int argc, char **argv)
+{
+	if (argc < 1)
+		return tc_qdisc_list(0, NULL);
+	if (matches(*argv, "add") == 0)
+		return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1);
+	if (matches(*argv, "change") == 0)
+		return tc_qdisc_modify(RTM_NEWQDISC, 0, argc-1, argv+1);
+	if (matches(*argv, "replace") == 0)
+		return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
+	if (matches(*argv, "link") == 0)
+		return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_REPLACE, argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return tc_qdisc_modify(RTM_DELQDISC, 0,  argc-1, argv+1);
+#if 0
+	if (matches(*argv, "get") == 0)
+		return tc_qdisc_get(RTM_GETQDISC, 0,  argc-1, argv+1);
+#endif
+	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+	    || matches(*argv, "lst") == 0)
+		return tc_qdisc_list(argc-1, argv+1);
+	if (matches(*argv, "help") == 0) {
+		usage();
+		return 0;
+        }
+	fprintf(stderr, "Command \"%s\" is unknown, try \"tc qdisc help\".\n", *argv);
+	return -1;
+}
diff --git a/tc/tc_red.c b/tc/tc_red.c
new file mode 100644
index 0000000..8f9bde0
--- /dev/null
+++ b/tc/tc_red.c
@@ -0,0 +1,97 @@
+/*
+ * tc_red.c		RED maintanance routines.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "tc_core.h"
+#include "tc_red.h"
+
+/*
+   Plog = log(prob/(qmax - qmin))
+ */
+int tc_red_eval_P(unsigned qmin, unsigned qmax, double prob)
+{
+	int i = qmax - qmin;
+
+	if (i <= 0)
+		return -1;
+
+	prob /= i;
+
+	for (i=0; i<32; i++) {
+		if (prob > 1.0)
+			break;
+		prob *= 2;
+	}
+	if (i>=32)
+		return -1;
+	return i;
+}
+
+/*
+   burst + 1 - qmin/avpkt < (1-(1-W)^burst)/W
+ */
+
+int tc_red_eval_ewma(unsigned qmin, unsigned burst, unsigned avpkt)
+{
+	int wlog = 1;
+	double W = 0.5;
+	double a = (double)burst + 1 - (double)qmin/avpkt;
+
+	if (a < 1.0)
+		return -1;
+	for (wlog=1; wlog<32; wlog++, W /= 2) {
+		if (a <= (1 - pow(1-W, burst))/W)
+			return wlog;
+	}
+	return -1;
+}
+
+/*
+   Stab[t>>Scell_log] = -log(1-W) * t/xmit_time
+ */
+
+int tc_red_eval_idle_damping(int Wlog, unsigned avpkt, unsigned bps, __u8 *sbuf)
+{
+	double xmit_time = tc_calc_xmittime(bps, avpkt);
+	double lW = -log(1.0 - 1.0/(1<<Wlog))/xmit_time;
+	double maxtime = 31/lW;
+	int clog;
+	int i;
+	double tmp;
+
+	tmp = maxtime;
+	for (clog=0; clog<32; clog++) {
+		if (maxtime/(1<<clog) < 512)
+			break;
+	}
+	if (clog >= 32)
+		return -1;
+
+	sbuf[0] = 0;
+	for (i=1; i<255; i++) {
+		sbuf[i] = (i<<clog)*lW;
+		if (sbuf[i] > 31)
+			sbuf[i] = 31;
+	}
+	sbuf[255] = 31;
+	return clog;
+}
diff --git a/tc/tc_red.h b/tc/tc_red.h
new file mode 100644
index 0000000..6f6b09e
--- /dev/null
+++ b/tc/tc_red.h
@@ -0,0 +1,8 @@
+#ifndef _TC_RED_H_
+#define _TC_RED_H_ 1
+
+extern int tc_red_eval_P(unsigned qmin, unsigned qmax, double prob);
+extern int tc_red_eval_ewma(unsigned qmin, unsigned burst, unsigned avpkt);
+extern int tc_red_eval_idle_damping(int wlog, unsigned avpkt, unsigned bandwidth, __u8 *sbuf);
+
+#endif
diff --git a/tc/tc_stab.c b/tc/tc_stab.c
new file mode 100644
index 0000000..47b4e5e
--- /dev/null
+++ b/tc/tc_stab.c
@@ -0,0 +1,160 @@
+/*
+ * tc_stab.c		"tc qdisc ... stab *".
+ *
+ *		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:	Jussi Kivilinna, <jussi.kivilinna@mbnet.fi>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <malloc.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_core.h"
+#include "tc_common.h"
+
+static void stab_help(void)
+{
+	fprintf(stderr,
+		"Usage: ... stab [ mtu BYTES ] [ tsize SLOTS ] [ mpu BYTES ] \n"
+		"                [ overhead BYTES ] [ linklayer TYPE ] ...\n"
+		"   mtu       : max packet size we create rate map for {2047}\n"
+		"   tsize     : how many slots should size table have {512}\n"
+		"   mpu       : minimum packet size used in rate computations\n"
+		"   overhead  : per-packet size overhead used in rate computations\n"
+		"   linklayer : adapting to a linklayer e.g. atm\n"
+		"Example: ... stab overhead 20 linklayer atm\n");
+
+	return;
+}
+
+int check_size_table_opts(struct tc_sizespec *s)
+{
+	return s->linklayer >= LINKLAYER_ETHERNET || s->mpu != 0 ||
+							s->overhead != 0;
+}
+
+int parse_size_table(int *argcp, char ***argvp, struct tc_sizespec *sp)
+{
+	char **argv = *argvp;
+	int argc = *argcp;
+	struct tc_sizespec s;
+
+	memset(&s, 0, sizeof(s));
+
+	NEXT_ARG();
+	if (matches(*argv, "help") == 0) {
+		stab_help();
+		return -1;
+	}
+	while (argc > 0) {
+		if (matches(*argv, "mtu") == 0) {
+			NEXT_ARG();
+			if (s.mtu)
+				duparg("mtu", *argv);
+			if (get_u32(&s.mtu, *argv, 10)) {
+				invarg("mtu", "invalid mtu");
+				return -1;
+			}
+		} else if (matches(*argv, "mpu") == 0) {
+			NEXT_ARG();
+			if (s.mpu)
+				duparg("mpu", *argv);
+			if (get_u32(&s.mpu, *argv, 10)) {
+				invarg("mpu", "invalid mpu");
+				return -1;
+			}
+		} else if (matches(*argv, "overhead") == 0) {
+			NEXT_ARG();
+			if (s.overhead)
+				duparg("overhead", *argv);
+			if (get_integer(&s.overhead, *argv, 10)) {
+				invarg("overhead", "invalid overhead");
+				return -1;
+			}
+		} else if (matches(*argv, "tsize") == 0) {
+			NEXT_ARG();
+			if (s.tsize)
+				duparg("tsize", *argv);
+			if (get_u32(&s.tsize, *argv, 10)) {
+				invarg("tsize", "invalid table size");
+				return -1;
+			}
+		} else if (matches(*argv, "linklayer") == 0) {
+			NEXT_ARG();
+			if (s.linklayer != LINKLAYER_UNSPEC)
+				duparg("linklayer", *argv);
+			if (get_linklayer(&s.linklayer, *argv)) {
+				invarg("linklayer", "invalid linklayer");
+				return -1;
+			}
+		} else
+			break;
+		argc--; argv++;
+	}
+
+	if (!check_size_table_opts(&s))
+		return -1;
+
+	*sp = s;
+	*argvp = argv;
+	*argcp = argc;
+	return 0;
+}
+
+void print_size_table(FILE *fp, const char *prefix, struct rtattr *rta)
+{
+	struct rtattr *tb[TCA_STAB_MAX + 1];
+	SPRINT_BUF(b1);
+
+	parse_rtattr_nested(tb, TCA_STAB_MAX, rta);
+
+	if (tb[TCA_STAB_BASE]) {
+		struct tc_sizespec s = {0};
+		memcpy(&s, RTA_DATA(tb[TCA_STAB_BASE]),
+				MIN(RTA_PAYLOAD(tb[TCA_STAB_BASE]), sizeof(s)));
+
+		fprintf(fp, "%s", prefix);
+		if (s.linklayer)
+			fprintf(fp, "linklayer %s ",
+					sprint_linklayer(s.linklayer, b1));
+		if (s.overhead)
+			fprintf(fp, "overhead %d ", s.overhead);
+		if (s.mpu)
+			fprintf(fp, "mpu %u ", s.mpu);
+		if (s.mtu)
+			fprintf(fp, "mtu %u ", s.mtu);
+		if (s.tsize)
+			fprintf(fp, "tsize %u ", s.tsize);
+	}
+
+#if 0
+	if (tb[TCA_STAB_DATA]) {
+		unsigned i, j, dlen;
+		__u16 *data = RTA_DATA(tb[TCA_STAB_DATA]);
+		dlen = RTA_PAYLOAD(tb[TCA_STAB_DATA]) / sizeof(__u16);
+
+		fprintf(fp, "\n%sstab data:", prefix);
+		for (i = 0; i < dlen/12; i++) {
+			fprintf(fp, "\n%s %3u:", prefix, i * 12);
+			for (j = 0; i * 12 + j < dlen; j++)
+				fprintf(fp, " %05x", data[i * 12 + j]);
+		}
+	}
+#endif
+}
+
diff --git a/tc/tc_util.c b/tc/tc_util.c
new file mode 100644
index 0000000..fe2c7eb
--- /dev/null
+++ b/tc/tc_util.c
@@ -0,0 +1,580 @@
+/*
+ * tc_util.c		Misc TC utility functions.
+ *
+ *		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:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#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"
+
+#ifndef LIBDIR
+#define LIBDIR "/usr/lib/"
+#endif
+
+const char *get_tc_lib(void)
+{
+	const char *lib_dir;
+
+	lib_dir = getenv("TC_LIB_DIR");
+	if (!lib_dir)
+		lib_dir = LIBDIR "/tc/";
+
+	return lib_dir;
+}
+
+int get_qdisc_handle(__u32 *h, const char *str)
+{
+	__u32 maj;
+	char *p;
+
+	maj = TC_H_UNSPEC;
+	if (strcmp(str, "none") == 0)
+		goto ok;
+	maj = strtoul(str, &p, 16);
+	if (p == str)
+		return -1;
+	maj <<= 16;
+	if (*p != ':' && *p!=0)
+		return -1;
+ok:
+	*h = maj;
+	return 0;
+}
+
+int get_tc_classid(__u32 *h, const char *str)
+{
+	__u32 maj, min;
+	char *p;
+
+	maj = TC_H_ROOT;
+	if (strcmp(str, "root") == 0)
+		goto ok;
+	maj = TC_H_UNSPEC;
+	if (strcmp(str, "none") == 0)
+		goto ok;
+	maj = strtoul(str, &p, 16);
+	if (p == str) {
+		maj = 0;
+		if (*p != ':')
+			return -1;
+	}
+	if (*p == ':') {
+		if (maj >= (1<<16))
+			return -1;
+		maj <<= 16;
+		str = p+1;
+		min = strtoul(str, &p, 16);
+		if (*p != 0)
+			return -1;
+		if (min >= (1<<16))
+			return -1;
+		maj |= min;
+	} else if (*p != 0)
+		return -1;
+
+ok:
+	*h = maj;
+	return 0;
+}
+
+int print_tc_classid(char *buf, int len, __u32 h)
+{
+	if (h == TC_H_ROOT)
+		sprintf(buf, "root");
+	else if (h == TC_H_UNSPEC)
+		snprintf(buf, len, "none");
+	else if (TC_H_MAJ(h) == 0)
+		snprintf(buf, len, ":%x", TC_H_MIN(h));
+	else if (TC_H_MIN(h) == 0)
+		snprintf(buf, len, "%x:", TC_H_MAJ(h)>>16);
+	else
+		snprintf(buf, len, "%x:%x", TC_H_MAJ(h)>>16, TC_H_MIN(h));
+	return 0;
+}
+
+char * sprint_tc_classid(__u32 h, char *buf)
+{
+	if (print_tc_classid(buf, SPRINT_BSIZE-1, h))
+		strcpy(buf, "???");
+	return buf;
+}
+
+/* See http://physics.nist.gov/cuu/Units/binary.html */
+static const struct rate_suffix {
+	const char *name;
+	double scale;
+} suffixes[] = {
+	{ "bit",	1. },
+	{ "Kibit",	1024. },
+	{ "kbit",	1000. },
+	{ "mibit",	1024.*1024. },
+	{ "mbit",	1000000. },
+	{ "gibit",	1024.*1024.*1024. },
+	{ "gbit",	1000000000. },
+	{ "tibit",	1024.*1024.*1024.*1024. },
+	{ "tbit",	1000000000000. },
+	{ "Bps",	8. },
+	{ "KiBps",	8.*1024. },
+	{ "KBps",	8000. },
+	{ "MiBps",	8.*1024*1024. },
+	{ "MBps",	8000000. },
+	{ "GiBps",	8.*1024.*1024.*1024. },
+	{ "GBps",	8000000000. },
+	{ "TiBps",	8.*1024.*1024.*1024.*1024. },
+	{ "TBps",	8000000000000. },
+	{ NULL }
+};
+
+
+int get_rate(unsigned *rate, const char *str)
+{
+	char *p;
+	double bps = strtod(str, &p);
+	const struct rate_suffix *s;
+
+	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;
+		}
+	}
+
+	return -1;
+}
+
+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))
+		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)
+{
+	double tmp = (double)rate*8;
+	extern int use_iec;
+
+	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);
+	}
+}
+
+char * sprint_rate(__u32 rate, char *buf)
+{
+	print_rate(buf, SPRINT_BSIZE-1, rate);
+	return buf;
+}
+
+int get_time(unsigned *time, const char *str)
+{
+	double t;
+	char *p;
+
+	t = strtod(str, &p);
+	if (p == str)
+		return -1;
+
+	if (*p) {
+		if (strcasecmp(p, "s") == 0 || strcasecmp(p, "sec")==0 ||
+		    strcasecmp(p, "secs")==0)
+			t *= TIME_UNITS_PER_SEC;
+		else if (strcasecmp(p, "ms") == 0 || strcasecmp(p, "msec")==0 ||
+			 strcasecmp(p, "msecs") == 0)
+			t *= TIME_UNITS_PER_SEC/1000;
+		else if (strcasecmp(p, "us") == 0 || strcasecmp(p, "usec")==0 ||
+			 strcasecmp(p, "usecs") == 0)
+			t *= TIME_UNITS_PER_SEC/1000000;
+		else
+			return -1;
+	}
+
+	*time = t;
+	return 0;
+}
+
+
+void print_time(char *buf, int len, __u32 time)
+{
+	double tmp = time;
+
+	if (tmp >= TIME_UNITS_PER_SEC)
+		snprintf(buf, len, "%.1fs", tmp/TIME_UNITS_PER_SEC);
+	else if (tmp >= TIME_UNITS_PER_SEC/1000)
+		snprintf(buf, len, "%.1fms", tmp/(TIME_UNITS_PER_SEC/1000));
+	else
+		snprintf(buf, len, "%uus", time);
+}
+
+char * sprint_time(__u32 time, char *buf)
+{
+	print_time(buf, SPRINT_BSIZE-1, time);
+	return buf;
+}
+
+char * sprint_ticks(__u32 ticks, char *buf)
+{
+	return sprint_time(tc_core_tick2time(ticks), buf);
+}
+
+int get_size(unsigned *size, const char *str)
+{
+	double sz;
+	char *p;
+
+	sz = strtod(str, &p);
+	if (p == str)
+		return -1;
+
+	if (*p) {
+		if (strcasecmp(p, "kb") == 0 || strcasecmp(p, "k")==0)
+			sz *= 1024;
+		else if (strcasecmp(p, "gb") == 0 || strcasecmp(p, "g")==0)
+			sz *= 1024*1024*1024;
+		else if (strcasecmp(p, "gbit") == 0)
+			sz *= 1024*1024*1024/8;
+		else if (strcasecmp(p, "mb") == 0 || strcasecmp(p, "m")==0)
+			sz *= 1024*1024;
+		else if (strcasecmp(p, "mbit") == 0)
+			sz *= 1024*1024/8;
+		else if (strcasecmp(p, "kbit") == 0)
+			sz *= 1024/8;
+		else if (strcasecmp(p, "b") != 0)
+			return -1;
+	}
+
+	*size = sz;
+	return 0;
+}
+
+int get_size_and_cell(unsigned *size, int *cell_log, char *str)
+{
+	char * slash = strchr(str, '/');
+
+	if (slash)
+		*slash = 0;
+
+	if (get_size(size, str))
+		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_size(char *buf, int len, __u32 sz)
+{
+	double tmp = sz;
+
+	if (sz >= 1024*1024 && fabs(1024*1024*rint(tmp/(1024*1024)) - sz) < 1024)
+		snprintf(buf, len, "%gMb", rint(tmp/(1024*1024)));
+	else if (sz >= 1024 && fabs(1024*rint(tmp/1024) - sz) < 16)
+		snprintf(buf, len, "%gKb", rint(tmp/1024));
+	else
+		snprintf(buf, len, "%ub", sz);
+}
+
+char * sprint_size(__u32 size, char *buf)
+{
+	print_size(buf, SPRINT_BSIZE-1, size);
+	return buf;
+}
+
+static const double max_percent_value = 0xffffffff;
+
+int get_percent(__u32 *percent, const char *str)
+{
+	char *p;
+	double per = strtod(str, &p) / 100.;
+
+	if (per > 1. || per < 0)
+		return -1;
+	if (*p && strcmp(p, "%"))
+		return -1;
+
+	*percent = (unsigned) rint(per * max_percent_value);
+	return 0;
+}
+
+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)
+{
+	print_percent(buf, SPRINT_BSIZE-1, per);
+	return buf;
+}
+
+void print_qdisc_handle(char *buf, int len, __u32 h)
+{
+	snprintf(buf, len, "%x:", TC_H_MAJ(h)>>16);
+}
+
+char * sprint_qdisc_handle(__u32 h, char *buf)
+{
+	print_qdisc_handle(buf, SPRINT_BSIZE-1, h);
+	return buf;
+}
+
+char * action_n2a(int action, char *buf, int len)
+{
+	switch (action) {
+	case -1:
+		return "continue";
+		break;
+	case TC_ACT_OK:
+		return "pass";
+		break;
+	case TC_ACT_SHOT:
+		return "drop";
+		break;
+	case TC_ACT_RECLASSIFY:
+		return "reclassify";
+	case TC_ACT_PIPE:
+		return "pipe";
+	case TC_ACT_STOLEN:
+		return "stolen";
+	default:
+		snprintf(buf, len, "%d", action);
+		return buf;
+	}
+}
+
+int action_a2n(char *arg, int *result)
+{
+	int res;
+
+	if (matches(arg, "continue") == 0)
+		res = -1;
+	else if (matches(arg, "drop") == 0)
+		res = TC_ACT_SHOT;
+	else if (matches(arg, "shot") == 0)
+		res = TC_ACT_SHOT;
+	else if (matches(arg, "pass") == 0)
+		res = TC_ACT_OK;
+	else if (strcmp(arg, "ok") == 0)
+		res = TC_ACT_OK;
+	else if (matches(arg, "reclassify") == 0)
+		res = TC_ACT_RECLASSIFY;
+	else {
+		char dummy;
+		if (sscanf(arg, "%d%c", &res, &dummy) != 1)
+			return -1;
+	}
+	*result = res;
+	return 0;
+}
+
+int get_linklayer(unsigned *val, const char *arg)
+{
+	int res;
+
+	if (matches(arg, "ethernet") == 0)
+		res = LINKLAYER_ETHERNET;
+	else if (matches(arg, "atm") == 0)
+		res = LINKLAYER_ATM;
+	else if (matches(arg, "adsl") == 0)
+		res = LINKLAYER_ATM;
+	else
+		return -1; /* Indicate error */
+
+	*val = res;
+	return 0;
+}
+
+void print_linklayer(char *buf, int len, unsigned linklayer)
+{
+	switch (linklayer) {
+	case LINKLAYER_UNSPEC:
+		snprintf(buf, len, "%s", "unspec");
+		return;
+	case LINKLAYER_ETHERNET:
+		snprintf(buf, len, "%s", "ethernet");
+		return;
+	case LINKLAYER_ATM:
+		snprintf(buf, len, "%s", "atm");
+		return;
+	default:
+		snprintf(buf, len, "%s", "unknown");
+		return;
+	}
+}
+
+char *sprint_linklayer(unsigned linklayer, char *buf)
+{
+	print_linklayer(buf, SPRINT_BSIZE-1, linklayer);
+	return buf;
+}
+
+void print_tm(FILE * f, const struct tcf_t *tm)
+{
+	int hz = get_user_hz();
+	if (tm->install != 0)
+		fprintf(f, " installed %u sec", (unsigned)(tm->install/hz));
+	if (tm->lastuse != 0)
+		fprintf(f, " used %u sec", (unsigned)(tm->lastuse/hz));
+	if (tm->expires != 0)
+		fprintf(f, " expires %u sec", (unsigned)(tm->expires/hz));
+}
+
+void print_tcstats2_attr(FILE *fp, struct rtattr *rta, char *prefix, struct rtattr **xstats)
+{
+	SPRINT_BUF(b1);
+	struct rtattr *tbs[TCA_STATS_MAX + 1];
+
+	parse_rtattr_nested(tbs, TCA_STATS_MAX, rta);
+
+	if (tbs[TCA_STATS_BASIC]) {
+		struct gnet_stats_basic bs = {0};
+		memcpy(&bs, RTA_DATA(tbs[TCA_STATS_BASIC]), MIN(RTA_PAYLOAD(tbs[TCA_STATS_BASIC]), sizeof(bs)));
+		fprintf(fp, "%sSent %llu bytes %u pkt",
+			prefix, (unsigned long long) bs.bytes, bs.packets);
+	}
+
+	if (tbs[TCA_STATS_QUEUE]) {
+		struct gnet_stats_queue q = {0};
+		memcpy(&q, RTA_DATA(tbs[TCA_STATS_QUEUE]), MIN(RTA_PAYLOAD(tbs[TCA_STATS_QUEUE]), sizeof(q)));
+		fprintf(fp, " (dropped %u, overlimits %u requeues %u) ",
+			q.drops, q.overlimits, q.requeues);
+	}
+
+	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)));
+		fprintf(fp, "\n%srate %s %upps ",
+			prefix, sprint_rate(re.bps, b1), re.pps);
+	}
+
+	if (tbs[TCA_STATS_QUEUE]) {
+		struct gnet_stats_queue q = {0};
+		memcpy(&q, RTA_DATA(tbs[TCA_STATS_QUEUE]), MIN(RTA_PAYLOAD(tbs[TCA_STATS_QUEUE]), sizeof(q)));
+		if (!tbs[TCA_STATS_RATE_EST])
+			fprintf(fp, "\n%s", prefix);
+		fprintf(fp, "backlog %s %up requeues %u ",
+			sprint_size(q.backlog, b1), q.qlen, q.requeues);
+	}
+
+	if (xstats)
+		*xstats = tbs[TCA_STATS_APP] ? : NULL;
+}
+
+void print_tcstats_attr(FILE *fp, struct rtattr *tb[], char *prefix, struct rtattr **xstats)
+{
+	SPRINT_BUF(b1);
+
+	if (tb[TCA_STATS2]) {
+		print_tcstats2_attr(fp, tb[TCA_STATS2], prefix, xstats);
+		if (xstats && NULL == *xstats)
+			goto compat_xstats;
+		return;
+	}
+	/* backward compatibility */
+	if (tb[TCA_STATS]) {
+		struct tc_stats st;
+
+		/* handle case where kernel returns more/less than we know about */
+		memset(&st, 0, sizeof(st));
+		memcpy(&st, RTA_DATA(tb[TCA_STATS]), MIN(RTA_PAYLOAD(tb[TCA_STATS]), sizeof(st)));
+
+		fprintf(fp, "%sSent %llu bytes %u pkts (dropped %u, overlimits %u) ",
+			prefix, (unsigned long long)st.bytes, st.packets, st.drops,
+			st.overlimits);
+
+		if (st.bps || st.pps || st.qlen || st.backlog) {
+			fprintf(fp, "\n%s", prefix);
+			if (st.bps || st.pps) {
+				fprintf(fp, "rate ");
+				if (st.bps)
+					fprintf(fp, "%s ", sprint_rate(st.bps, b1));
+				if (st.pps)
+					fprintf(fp, "%upps ", st.pps);
+			}
+			if (st.qlen || st.backlog) {
+				fprintf(fp, "backlog ");
+				if (st.backlog)
+					fprintf(fp, "%s ", sprint_size(st.backlog, b1));
+				if (st.qlen)
+					fprintf(fp, "%up ", st.qlen);
+			}
+		}
+	}
+
+compat_xstats:
+	if (tb[TCA_XSTATS] && xstats)
+		*xstats = tb[TCA_XSTATS];
+}
+
diff --git a/tc/tc_util.h b/tc/tc_util.h
new file mode 100644
index 0000000..d84b09a
--- /dev/null
+++ b/tc/tc_util.h
@@ -0,0 +1,104 @@
+#ifndef _TC_UTIL_H_
+#define _TC_UTIL_H_ 1
+
+#define MAX_MSG 16384
+#include <linux/pkt_sched.h>
+#include <linux/pkt_cls.h>
+#include <linux/gen_stats.h>
+#include "tc_core.h"
+
+/* This is the deprecated multiqueue interface */
+#ifndef TCA_PRIO_MAX
+enum
+{
+	TCA_PRIO_UNSPEC,
+	TCA_PRIO_MQ,
+	__TCA_PRIO_MAX
+};
+
+#define TCA_PRIO_MAX    (__TCA_PRIO_MAX - 1)
+#endif
+
+struct qdisc_util
+{
+	struct  qdisc_util *next;
+	const char *id;
+	int	(*parse_qopt)(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n);
+	int	(*print_qopt)(struct qdisc_util *qu, FILE *f, struct rtattr *opt);
+	int 	(*print_xstats)(struct qdisc_util *qu, FILE *f, struct rtattr *xstats);
+
+	int	(*parse_copt)(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n);
+	int	(*print_copt)(struct qdisc_util *qu, FILE *f, struct rtattr *opt);
+};
+
+extern __u16 f_proto;
+struct filter_util
+{
+	struct filter_util *next;
+	char	id[16];
+	int	(*parse_fopt)(struct filter_util *qu, char *fhandle, int argc,
+			      char **argv, struct nlmsghdr *n);
+	int	(*print_fopt)(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 fhandle);
+};
+
+struct action_util
+{
+	struct  action_util *next;
+	char    id[16];
+	int     (*parse_aopt)(struct action_util *a, int *argc, char ***argv,
+			      int code, struct nlmsghdr *n);
+	int     (*print_aopt)(struct action_util *au, FILE *f, struct rtattr *opt);
+	int     (*print_xstats)(struct action_util *au, FILE *f, struct rtattr *xstats);
+};
+
+extern const char *get_tc_lib(void);
+
+extern struct qdisc_util *get_qdisc_kind(const char *str);
+extern struct filter_util *get_filter_kind(const char *str);
+
+extern int get_qdisc_handle(__u32 *h, const char *str);
+extern int get_rate(unsigned *rate, const char *str);
+extern int get_percent(unsigned *percent, 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_size(char *buf, int len, __u32 size);
+extern void print_percent(char *buf, int len, __u32 percent);
+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_size(__u32 size, char *buf);
+extern char * sprint_qdisc_handle(__u32 h, char *buf);
+extern char * sprint_tc_classid(__u32 h, char *buf);
+extern char * sprint_time(__u32 time, char *buf);
+extern char * sprint_ticks(__u32 ticks, char *buf);
+extern char * sprint_percent(__u32 percent, char *buf);
+extern char * sprint_linklayer(unsigned linklayer, char *buf);
+
+extern void print_tcstats_attr(FILE *fp, struct rtattr *tb[], char *prefix, struct rtattr **xstats);
+extern void print_tcstats2_attr(FILE *fp, struct rtattr *rta, char *prefix, struct rtattr **xstats);
+
+extern int get_tc_classid(__u32 *h, const char *str);
+extern int print_tc_classid(char *buf, int len, __u32 h);
+extern char * sprint_tc_classid(__u32 h, char *buf);
+
+extern int tc_print_police(FILE *f, struct rtattr *tb);
+extern int parse_police(int *, char ***, int, struct nlmsghdr *);
+
+extern char *action_n2a(int action, char *buf, int len);
+extern int  action_a2n(char *arg, int *result);
+extern int  act_parse_police(struct action_util *a,int *, char ***, int, struct nlmsghdr *);
+extern int  print_police(struct action_util *a, FILE *f,
+			 struct rtattr *tb);
+extern int  police_print_xstats(struct action_util *a,FILE *f,
+				struct rtattr *tb);
+extern int  tc_print_action(FILE *f, const struct rtattr *tb);
+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);
+
+#endif