Add new extended match files.
diff --git a/tc/em_cmp.c b/tc/em_cmp.c
new file mode 100644
index 0000000..c636c53
--- /dev/null
+++ b/tc/em_cmp.c
@@ -0,0 +1,188 @@
+/*
+ * em_cmp.c		Simle coparison 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 <dlfcn.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 | header | next-header | 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..86186c1
--- /dev/null
+++ b/tc/em_meta.c
@@ -0,0 +1,560 @@
+/*
+ * 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 <dlfcn.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" \
+	    "         meta(dev eq indev)\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(INDEV,		"indev",	"iv",
+				"Device the packet came in"),
+	__A(REALDEV,		"realdev",	"iv",
+				"Underlying real device"),
+
+	__A(SECTION,		"Packet attributes", "", ""),
+	__A(PRIORITY,		"priority",	"i",
+				"Priority of packet"),
+	__A(PROTOCOL,		"protocol",	"i",
+				"Link layer protocol"),
+	__A(SECURITY,		"security",	"i",
+				"Security level"),
+	__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(TCVERDICT,		"tc_verdict",	"i",	"TC Verdict"),
+	__A(TCCLASSID,		"tc_classid",	"i",	"TC ClassID"),
+
+	__A(SECTION,		"Routing", "", ""),
+	__A(RTCLASSID,		"rt_classid",	"i",
+				"Routing ClassID (cls_route)"),
+	__A(RTIIF,		"rt_iif",	"i",
+				"Incoming interface index"),
+
+	__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 != LONG_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 == LONG_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 == LONG_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));
+
+	if (lvalue)
+		dump_value(n, TCA_EM_META_LVALUE, lvalue, &meta_hdr.left);
+
+	if (rvalue)
+		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..e0ed5ba
--- /dev/null
+++ b/tc/em_nbyte.c
@@ -0,0 +1,144 @@
+/*
+ * 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 <dlfcn.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 | header | next-header | 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..b8857f1
--- /dev/null
+++ b/tc/em_u32.c
@@ -0,0 +1,178 @@
+/*
+ * 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 <dlfcn.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..80ab0da
--- /dev/null
+++ b/tc/emp_ematch.l
@@ -0,0 +1,143 @@
+%{
+ #include "emp_ematch.yacc.h"
+ #include "m_ematch.h"
+
+ extern int ematch_argc;
+ extern char **ematch_argv;
+
+ #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 );
+%}
+
+%x str
+
+%option 8bit stack warn bison-bridge noyywrap prefix="ematch_"
+%%
+
+ static unsigned char *strbuf;
+ static unsigned int strbuf_size;
+ static unsigned int strbuf_index;
+
+ static inline void strbuf_enlarge(void)
+ {
+ 	strbuf_size += 512;
+ 	strbuf = realloc(strbuf, strbuf_size);
+ }
+
+ static inline void strbuf_append_char(unsigned char c)
+ {
+ 	while (strbuf_index >= strbuf_size)
+ 		strbuf_enlarge();
+ 	strbuf[strbuf_index++] = c;
+ }
+
+ static inline void strbuf_append_charp(unsigned char *s)
+ {
+ 	while (strbuf_index >= strbuf_size)
+ 		strbuf_enlarge();
+ 	memcpy(strbuf + strbuf_index, s, strlen(s));
+ 	strbuf_index += strlen(s);
+ }
+
+[ \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..b4e4a3e
--- /dev/null
+++ b/tc/emp_ematch.y
@@ -0,0 +1,102 @@
+%{
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <malloc.h>
+ #include <string.h>
+ #include "m_ematch.h"
+%}
+
+%locations
+%pure_parser
+%token-table
+%error-verbose
+%name-prefix="ematch_"
+
+%union {
+	unsigned int i;
+	struct bstr *b;
+	struct ematch *e;
+}
+
+%{
+ extern int yylex(YYSTYPE *, YYLTYPE *);
+ 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);
+ }
+