/* * 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 (hdr->nmatches > 0) { 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 "); } }