/* * 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-2009 Thomas Graf <tgraf@suug.ch> */ /** * @ingroup cls * @defgroup ematch Extended Match * * @{ */ #include <netlink-local.h> #include <netlink-tc.h> #include <netlink/netlink.h> #include <netlink/route/classifier.h> #include <netlink/route/classifier-modules.h> #include <netlink/route/cls/ematch.h> /** * @name Module Registration * @{ */ static NL_LIST_HEAD(ematch_ops_list); /** * Register ematch module * @arg ops Module operations. * * @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_list_add_tail(&ops->eo_list, &ematch_ops_list); return 0; } /** * Unregister ematch module * @arg ops Module operations. * * @return 0 on success or a negative error code. */ int rtnl_ematch_unregister(struct rtnl_ematch_ops *ops) { struct rtnl_ematch_ops *o; nl_list_for_each_entry(o, &ematch_ops_list, eo_list) { if (ops->eo_kind == o->eo_kind) { nl_list_del(&o->eo_list); return 0; } } return -NLE_OBJ_NOTFOUND; } /** * Lookup ematch module by kind * @arg kind Module kind. * * @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. * * @return Module operations or NULL if not fuond. */ struct rtnl_ematch_ops *rtnl_ematch_lookup_ops_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 */ struct rtnl_ematch *rtnl_ematch_alloc(struct rtnl_ematch_ops *ops) { struct rtnl_ematch *e; size_t len = sizeof(*e) + (ops ? ops->eo_datalen : 0); if (!(e = calloc(1, len))) return NULL; NL_INIT_LIST_HEAD(&e->e_list); NL_INIT_LIST_HEAD(&e->e_childs); if (ops) { e->e_ops = ops; e->e_kind = ops->eo_kind; } return e; } /** * Add ematch to the end of the parent's list of children. * @arg parent Parent ematch. * @arg child Ematch to be added as new child of parent. */ void rtnl_ematch_add_child(struct rtnl_ematch *parent, struct rtnl_ematch *child) { nl_list_add_tail(&child->e_list, &parent->e_childs); } /** * Remove ematch from the list it is linked to. * @arg ematch Ematch to be unlinked. */ void rtnl_ematch_unlink(struct rtnl_ematch *ematch) { nl_list_del(&ematch->e_list); } void rtnl_ematch_free(struct rtnl_ematch *ematch) { if (!ematch) return; free(ematch); } 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 */ 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; 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); } } void rtnl_ematch_tree_free(struct rtnl_ematch_tree *tree) { if (!tree) return; free_ematch_list(&tree->et_list); free(tree); } void rtnl_ematch_tree_add_tail(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(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; 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) return 0; if (!tb[TCA_EMATCH_TREE_LIST]) return -NLE_MISSING_ATTR; 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; if (nla_len(a) < sizeof(*hdr)) { err = -NLE_INVAL; goto errout; } 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)); ops = rtnl_ematch_lookup_ops(hdr->kind); if (ops && ops->eo_datalen && len < ops->eo_datalen) { err = -NLE_INVAL; goto errout; } if (!(ematch = rtnl_ematch_alloc(ops))) { err = -NLE_NOMEM; goto errout; } ematch->e_id = hdr->matchid; ematch->e_kind = hdr->kind; ematch->e_flags = hdr->flags; if (ops && (err = ops->eo_parse(ematch, data, len)) < 0) goto errout; if (hdr->kind == TCF_EM_CONTAINER && container_ref(ematch) >= thdr->nmatches) { err = -NLE_INVAL; goto errout; } 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, "NOT "); 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 { nl_dump(p, "%s(", match->e_ops->eo_name); if (match->e_ops->eo_dump) match->e_ops->eo_dump(match, p); nl_dump(p, ")"); } 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) { dump_ematch_sequence(&tree->et_list, p); nl_dump(p, "\n"); } /** @} */ /** @} */