sync to iproute2-2.6.16-060323
[iproute2.git] / tc / em_meta.c
diff --git a/tc/em_meta.c b/tc/em_meta.c
new file mode 100644 (file)
index 0000000..bd1e151
--- /dev/null
@@ -0,0 +1,550 @@
+/*
+ * 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(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(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
+};