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