/* * lib/route/cls/ematch.c Extended Matches * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * Copyright (c) 2008-2013 Thomas Graf <tgraf@suug.ch> */ /** * @ingroup cls * @defgroup ematch Extended Match * * @{ */ #include <netlink-private/netlink.h> #include <netlink-private/tc.h> #include <netlink/netlink.h> #include <netlink/route/classifier.h> #include <netlink/route/cls/ematch.h> #include <netlink/route/cls/ematch/cmp.h> #include "ematch_syntax.h" #include "ematch_grammar.h" /** * @name Module API * @{ */ static NL_LIST_HEAD(ematch_ops_list); /** * Register ematch module * @arg ops Module operations. * * This function must be called by each ematch module at initialization * time. It registers the calling module as available module. * * @return 0 on success or a negative error code. */ int rtnl_ematch_register(struct rtnl_ematch_ops *ops) { if (rtnl_ematch_lookup_ops(ops->eo_kind)) return -NLE_EXIST; NL_DBG(1, "ematch module \"%s\" registered\n", ops->eo_name); nl_list_add_tail(&ops->eo_list, &ematch_ops_list); return 0; } /** * Lookup ematch module by identification number. * @arg kind Module kind. * * Searches the list of registered ematch modules for match and returns it. * * @return Module operations or NULL if not found. */ struct rtnl_ematch_ops *rtnl_ematch_lookup_ops(int kind) { struct rtnl_ematch_ops *ops; nl_list_for_each_entry(ops, &ematch_ops_list, eo_list) if (ops->eo_kind == kind) return ops; return NULL; } /** * Lookup ematch module by name * @arg name Name of ematch module. * * Searches the list of registered ematch modules for a match and returns it. * * @return Module operations or NULL if not fuond. */ struct rtnl_ematch_ops *rtnl_ematch_lookup_ops_by_name(const char *name) { struct rtnl_ematch_ops *ops; nl_list_for_each_entry(ops, &ematch_ops_list, eo_list) if (!strcasecmp(ops->eo_name, name)) return ops; return NULL; } /** @} */ /** * @name Match */ /** * Allocate ematch object. * * Allocates and initializes an ematch object. * * @return New ematch object or NULL. */ struct rtnl_ematch *rtnl_ematch_alloc(void) { struct rtnl_ematch *e; if (!(e = calloc(1, sizeof(*e)))) return NULL; NL_DBG(2, "allocated ematch %p\n", e); NL_INIT_LIST_HEAD(&e->e_list); NL_INIT_LIST_HEAD(&e->e_childs); return e; } /** * Add ematch to the end of the parent's list of children. * @arg parent parent ematch object * @arg child ematch object to be added to parent * * The parent must be a container ematch. */ int rtnl_ematch_add_child(struct rtnl_ematch *parent, struct rtnl_ematch *child) { if (parent->e_kind != TCF_EM_CONTAINER) return -NLE_OPNOTSUPP; NL_DBG(2, "added ematch %p \"%s\" to container %p\n", child, child->e_ops->eo_name, parent); nl_list_add_tail(&child->e_list, &parent->e_childs); return 0; } /** * Remove ematch from the list of ematches it is linked to. * @arg ematch ematch object */ void rtnl_ematch_unlink(struct rtnl_ematch *ematch) { NL_DBG(2, "unlinked ematch %p from any lists\n", ematch); if (!nl_list_empty(&ematch->e_childs)) NL_DBG(1, "warning: ematch %p with childs was unlinked\n", ematch); nl_list_del(&ematch->e_list); nl_init_list_head(&ematch->e_list); } void rtnl_ematch_free(struct rtnl_ematch *ematch) { NL_DBG(2, "freed ematch %p\n", ematch); rtnl_ematch_unlink(ematch); free(ematch->e_data); free(ematch); } int rtnl_ematch_set_ops(struct rtnl_ematch *ematch, struct rtnl_ematch_ops *ops) { if (ematch->e_ops) return -NLE_EXIST; ematch->e_ops = ops; ematch->e_kind = ops->eo_kind; if (ops->eo_datalen) { ematch->e_data = calloc(1, ops->eo_datalen); if (!ematch->e_data) return -NLE_NOMEM; ematch->e_datalen = ops->eo_datalen; } return 0; } int rtnl_ematch_set_kind(struct rtnl_ematch *ematch, uint16_t kind) { struct rtnl_ematch_ops *ops; if (ematch->e_kind) return -NLE_EXIST; ematch->e_kind = kind; if ((ops = rtnl_ematch_lookup_ops(kind))) rtnl_ematch_set_ops(ematch, ops); return 0; } int rtnl_ematch_set_name(struct rtnl_ematch *ematch, const char *name) { struct rtnl_ematch_ops *ops; if (ematch->e_kind) return -NLE_EXIST; if (!(ops = rtnl_ematch_lookup_ops_by_name(name))) return -NLE_OPNOTSUPP; rtnl_ematch_set_ops(ematch, ops); return 0; } void rtnl_ematch_set_flags(struct rtnl_ematch *ematch, uint16_t flags) { ematch->e_flags |= flags; } void rtnl_ematch_unset_flags(struct rtnl_ematch *ematch, uint16_t flags) { ematch->e_flags &= ~flags; } uint16_t rtnl_ematch_get_flags(struct rtnl_ematch *ematch) { return ematch->e_flags; } void *rtnl_ematch_data(struct rtnl_ematch *ematch) { return ematch->e_data; } /** @} */ /** * @name Tree */ /** * Allocate ematch tree object * @arg progid program id */ struct rtnl_ematch_tree *rtnl_ematch_tree_alloc(uint16_t progid) { struct rtnl_ematch_tree *tree; if (!(tree = calloc(1, sizeof(*tree)))) return NULL; NL_INIT_LIST_HEAD(&tree->et_list); tree->et_progid = progid; NL_DBG(2, "allocated new ematch tree %p, progid=%u\n", tree, progid); return tree; } static void free_ematch_list(struct nl_list_head *head) { struct rtnl_ematch *pos, *next; nl_list_for_each_entry_safe(pos, next, head, e_list) { if (!nl_list_empty(&pos->e_childs)) free_ematch_list(&pos->e_childs); rtnl_ematch_free(pos); } } /** * Free ematch tree object * @arg tree ematch tree object * * This function frees the ematch tree and all ematches attached to it. */ void rtnl_ematch_tree_free(struct rtnl_ematch_tree *tree) { if (!tree) return; free_ematch_list(&tree->et_list); NL_DBG(2, "Freed ematch tree %p\n", tree); free(tree); } /** * Add ematch object to the end of the ematch tree * @arg tree ematch tree object * @arg ematch ematch object to add */ void rtnl_ematch_tree_add(struct rtnl_ematch_tree *tree, struct rtnl_ematch *ematch) { nl_list_add_tail(&ematch->e_list, &tree->et_list); } static inline uint32_t container_ref(struct rtnl_ematch *ematch) { return *((uint32_t *) rtnl_ematch_data(ematch)); } static int link_tree(struct rtnl_ematch *index[], int nmatches, int pos, struct nl_list_head *root) { struct rtnl_ematch *ematch; int i; for (i = pos; i < nmatches; i++) { ematch = index[i]; nl_list_add_tail(&ematch->e_list, root); if (ematch->e_kind == TCF_EM_CONTAINER) link_tree(index, nmatches, container_ref(ematch), &ematch->e_childs); if (!(ematch->e_flags & TCF_EM_REL_MASK)) return 0; } /* Last entry in chain can't possibly have no relation */ return -NLE_INVAL; } static struct nla_policy tree_policy[TCA_EMATCH_TREE_MAX+1] = { [TCA_EMATCH_TREE_HDR] = { .minlen=sizeof(struct tcf_ematch_tree_hdr) }, [TCA_EMATCH_TREE_LIST] = { .type = NLA_NESTED }, }; /** * Parse ematch netlink attributes * * @return 0 on success or a negative error code. */ int rtnl_ematch_parse_attr(struct nlattr *attr, struct rtnl_ematch_tree **result) { struct nlattr *a, *tb[TCA_EMATCH_TREE_MAX+1]; struct tcf_ematch_tree_hdr *thdr; struct rtnl_ematch_tree *tree; struct rtnl_ematch **index; int nmatches = 0, err, remaining; NL_DBG(2, "Parsing attribute %p as ematch tree\n", attr); err = nla_parse_nested(tb, TCA_EMATCH_TREE_MAX, attr, tree_policy); if (err < 0) return err; if (!tb[TCA_EMATCH_TREE_HDR]) return -NLE_MISSING_ATTR; thdr = nla_data(tb[TCA_EMATCH_TREE_HDR]); /* Ignore empty trees */ if (thdr->nmatches == 0) { NL_DBG(2, "Ignoring empty ematch configuration\n"); return 0; } if (!tb[TCA_EMATCH_TREE_LIST]) return -NLE_MISSING_ATTR; NL_DBG(2, "ematch tree found with nmatches=%u, progid=%u\n", thdr->nmatches, thdr->progid); /* * Do some basic sanity checking since we will allocate * index[thdr->nmatches]. Calculate how many ematch headers fit into * the provided data and make sure nmatches does not exceed it. */ if (thdr->nmatches > (nla_len(tb[TCA_EMATCH_TREE_LIST]) / nla_total_size(sizeof(struct tcf_ematch_hdr)))) return -NLE_INVAL; if (!(index = calloc(thdr->nmatches, sizeof(struct rtnl_ematch *)))) return -NLE_NOMEM; if (!(tree = rtnl_ematch_tree_alloc(thdr->progid))) { err = -NLE_NOMEM; goto errout; } nla_for_each_nested(a, tb[TCA_EMATCH_TREE_LIST], remaining) { struct rtnl_ematch_ops *ops; struct tcf_ematch_hdr *hdr; struct rtnl_ematch *ematch; void *data; size_t len; NL_DBG(3, "parsing ematch attribute %d, len=%u\n", nmatches+1, nla_len(a)); if (nla_len(a) < sizeof(*hdr)) { err = -NLE_INVAL; goto errout; } /* Quit as soon as we've parsed more matches than expected */ if (nmatches >= thdr->nmatches) { err = -NLE_RANGE; goto errout; } hdr = nla_data(a); data = nla_data(a) + NLA_ALIGN(sizeof(*hdr)); len = nla_len(a) - NLA_ALIGN(sizeof(*hdr)); NL_DBG(3, "ematch attribute matchid=%u, kind=%u, flags=%u\n", hdr->matchid, hdr->kind, hdr->flags); /* * Container matches contain a reference to another sequence * of matches. Ensure that the reference is within boundries. */ if (hdr->kind == TCF_EM_CONTAINER && *((uint32_t *) data) >= thdr->nmatches) { err = -NLE_INVAL; goto errout; } if (!(ematch = rtnl_ematch_alloc())) { err = -NLE_NOMEM; goto errout; } ematch->e_id = hdr->matchid; ematch->e_kind = hdr->kind; ematch->e_flags = hdr->flags; if ((ops = rtnl_ematch_lookup_ops(hdr->kind))) { if (ops->eo_minlen && len < ops->eo_minlen) { rtnl_ematch_free(ematch); err = -NLE_INVAL; goto errout; } rtnl_ematch_set_ops(ematch, ops); if (ops->eo_parse && (err = ops->eo_parse(ematch, data, len)) < 0) { rtnl_ematch_free(ematch); goto errout; } } NL_DBG(3, "index[%d] = %p\n", nmatches, ematch); index[nmatches++] = ematch; } if (nmatches != thdr->nmatches) { err = -NLE_INVAL; goto errout; } err = link_tree(index, nmatches, 0, &tree->et_list); if (err < 0) goto errout; free(index); *result = tree; return 0; errout: rtnl_ematch_tree_free(tree); free(index); return err; } static void dump_ematch_sequence(struct nl_list_head *head, struct nl_dump_params *p) { struct rtnl_ematch *match; nl_list_for_each_entry(match, head, e_list) { if (match->e_flags & TCF_EM_INVERT) nl_dump(p, "!"); if (match->e_kind == TCF_EM_CONTAINER) { nl_dump(p, "("); dump_ematch_sequence(&match->e_childs, p); nl_dump(p, ")"); } else if (!match->e_ops) { nl_dump(p, "[unknown ematch %d]", match->e_kind); } else { if (match->e_ops->eo_dump) match->e_ops->eo_dump(match, p); else nl_dump(p, "[data]"); } switch (match->e_flags & TCF_EM_REL_MASK) { case TCF_EM_REL_AND: nl_dump(p, " AND "); break; case TCF_EM_REL_OR: nl_dump(p, " OR "); break; default: /* end of first level ematch sequence */ return; } } } void rtnl_ematch_tree_dump(struct rtnl_ematch_tree *tree, struct nl_dump_params *p) { if (!tree) BUG(); dump_ematch_sequence(&tree->et_list, p); nl_dump(p, "\n"); } static int update_container_index(struct nl_list_head *list, int *index) { struct rtnl_ematch *e; nl_list_for_each_entry(e, list, e_list) e->e_index = (*index)++; nl_list_for_each_entry(e, list, e_list) { if (e->e_kind == TCF_EM_CONTAINER) { int err; if (nl_list_empty(&e->e_childs)) return -NLE_OBJ_NOTFOUND; *((uint32_t *) e->e_data) = *index; err = update_container_index(&e->e_childs, index); if (err < 0) return err; } } return 0; } static int fill_ematch_sequence(struct nl_msg *msg, struct nl_list_head *list) { struct rtnl_ematch *e; nl_list_for_each_entry(e, list, e_list) { struct tcf_ematch_hdr match = { .matchid = e->e_id, .kind = e->e_kind, .flags = e->e_flags, }; struct nlattr *attr; int err = 0; if (!(attr = nla_nest_start(msg, e->e_index + 1))) return -NLE_NOMEM; if (nlmsg_append(msg, &match, sizeof(match), 0) < 0) return -NLE_NOMEM; if (e->e_ops->eo_fill) err = e->e_ops->eo_fill(e, msg); else if (e->e_flags & TCF_EM_SIMPLE) err = nlmsg_append(msg, e->e_data, 4, 0); else if (e->e_datalen > 0) err = nlmsg_append(msg, e->e_data, e->e_datalen, 0); NL_DBG(3, "msg %p: added ematch [%d] id=%d kind=%d flags=%d\n", msg, e->e_index, match.matchid, match.kind, match.flags); if (err < 0) return -NLE_NOMEM; nla_nest_end(msg, attr); } nl_list_for_each_entry(e, list, e_list) { if (e->e_kind == TCF_EM_CONTAINER && fill_ematch_sequence(msg, &e->e_childs) < 0) return -NLE_NOMEM; } return 0; } int rtnl_ematch_fill_attr(struct nl_msg *msg, int attrid, struct rtnl_ematch_tree *tree) { struct tcf_ematch_tree_hdr thdr = { .progid = tree->et_progid, }; struct nlattr *list, *topattr; int err, index = 0; /* Assign index number to each ematch to allow for references * to be made while constructing the sequence of matches. */ err = update_container_index(&tree->et_list, &index); if (err < 0) return err; if (!(topattr = nla_nest_start(msg, attrid))) goto nla_put_failure; thdr.nmatches = index; NLA_PUT(msg, TCA_EMATCH_TREE_HDR, sizeof(thdr), &thdr); if (!(list = nla_nest_start(msg, TCA_EMATCH_TREE_LIST))) goto nla_put_failure; if (fill_ematch_sequence(msg, &tree->et_list) < 0) goto nla_put_failure; nla_nest_end(msg, list); nla_nest_end(msg, topattr); return 0; nla_put_failure: return -NLE_NOMEM; } /** @} */ extern int ematch_parse(void *, char **, struct nl_list_head *); int rtnl_ematch_parse_expr(const char *expr, char **errp, struct rtnl_ematch_tree **result) { struct rtnl_ematch_tree *tree; YY_BUFFER_STATE buf = NULL; yyscan_t scanner = NULL; int err; NL_DBG(2, "Parsing ematch expression \"%s\"\n", expr); if (!(tree = rtnl_ematch_tree_alloc(RTNL_EMATCH_PROGID))) return -NLE_FAILURE; if ((err = ematch_lex_init(&scanner)) < 0) { err = -NLE_FAILURE; goto errout; } buf = ematch__scan_string(expr, scanner); if ((err = ematch_parse(scanner, errp, &tree->et_list)) != 0) { ematch__delete_buffer(buf, scanner); err = -NLE_PARSE_ERR; goto errout; } ematch_lex_destroy(scanner); *result = tree; return 0; errout: if (scanner) ematch_lex_destroy(scanner); rtnl_ematch_tree_free(tree); return err; } static const char *layer_txt[] = { [TCF_LAYER_LINK] = "eth", [TCF_LAYER_NETWORK] = "ip", [TCF_LAYER_TRANSPORT] = "tcp", }; char *rtnl_ematch_offset2txt(uint8_t layer, uint16_t offset, char *buf, size_t len) { snprintf(buf, len, "%s+%u", (layer <= TCF_LAYER_MAX) ? layer_txt[layer] : "?", offset); return buf; } static const char *operand_txt[] = { [TCF_EM_OPND_EQ] = "=", [TCF_EM_OPND_LT] = "<", [TCF_EM_OPND_GT] = ">", }; char *rtnl_ematch_opnd2txt(uint8_t opnd, char *buf, size_t len) { snprintf(buf, len, "%s", opnd < ARRAY_SIZE(operand_txt) ? operand_txt[opnd] : "?"); return buf; } /** @} */