| /* |
| * lib/route/cls/u32.c u32 classifier |
| * |
| * 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) 2003-2009 Thomas Graf <tgraf@suug.ch> |
| * Copyright (c) 2005-2006 Petr Gotthard <petr.gotthard@siemens.com> |
| * Copyright (c) 2005-2006 Siemens AG Oesterreich |
| */ |
| |
| /** |
| * @ingroup cls_api |
| * @defgroup u32 Universal 32-bit Classifier |
| * |
| * @{ |
| */ |
| |
| #include <netlink-local.h> |
| #include <netlink-tc.h> |
| #include <netlink/netlink.h> |
| #include <netlink/attr.h> |
| #include <netlink/utils.h> |
| #include <netlink/route/tc.h> |
| #include <netlink/route/classifier.h> |
| #include <netlink/route/classifier-modules.h> |
| #include <netlink/route/cls/u32.h> |
| |
| /** @cond SKIP */ |
| #define U32_ATTR_DIVISOR 0x001 |
| #define U32_ATTR_HASH 0x002 |
| #define U32_ATTR_CLASSID 0x004 |
| #define U32_ATTR_LINK 0x008 |
| #define U32_ATTR_PCNT 0x010 |
| #define U32_ATTR_SELECTOR 0x020 |
| #define U32_ATTR_ACTION 0x040 |
| #define U32_ATTR_POLICE 0x080 |
| #define U32_ATTR_INDEV 0x100 |
| /** @endcond */ |
| |
| static inline struct tc_u32_sel *u32_selector(struct rtnl_u32 *u) |
| { |
| return (struct tc_u32_sel *) u->cu_selector->d_data; |
| } |
| |
| static inline struct tc_u32_sel *u32_selector_alloc(struct rtnl_u32 *u) |
| { |
| if (!u->cu_selector) |
| u->cu_selector = nl_data_alloc(NULL, sizeof(struct tc_u32_sel)); |
| |
| return u32_selector(u); |
| } |
| |
| static struct nla_policy u32_policy[TCA_U32_MAX+1] = { |
| [TCA_U32_DIVISOR] = { .type = NLA_U32 }, |
| [TCA_U32_HASH] = { .type = NLA_U32 }, |
| [TCA_U32_CLASSID] = { .type = NLA_U32 }, |
| [TCA_U32_LINK] = { .type = NLA_U32 }, |
| [TCA_U32_INDEV] = { .type = NLA_STRING, |
| .maxlen = IFNAMSIZ }, |
| [TCA_U32_SEL] = { .minlen = sizeof(struct tc_u32_sel) }, |
| [TCA_U32_PCNT] = { .minlen = sizeof(struct tc_u32_pcnt) }, |
| }; |
| |
| static int u32_msg_parser(struct rtnl_cls *cls) |
| { |
| struct rtnl_u32 *u = rtnl_cls_data(cls); |
| struct nlattr *tb[TCA_U32_MAX + 1]; |
| int err; |
| |
| err = tca_parse(tb, TCA_U32_MAX, (struct rtnl_tca *) cls, u32_policy); |
| if (err < 0) |
| return err; |
| |
| if (tb[TCA_U32_DIVISOR]) { |
| u->cu_divisor = nla_get_u32(tb[TCA_U32_DIVISOR]); |
| u->cu_mask |= U32_ATTR_DIVISOR; |
| } |
| |
| if (tb[TCA_U32_SEL]) { |
| u->cu_selector = nl_data_alloc_attr(tb[TCA_U32_SEL]); |
| if (!u->cu_selector) |
| goto errout_nomem; |
| u->cu_mask |= U32_ATTR_SELECTOR; |
| } |
| |
| if (tb[TCA_U32_HASH]) { |
| u->cu_hash = nla_get_u32(tb[TCA_U32_HASH]); |
| u->cu_mask |= U32_ATTR_HASH; |
| } |
| |
| if (tb[TCA_U32_CLASSID]) { |
| u->cu_classid = nla_get_u32(tb[TCA_U32_CLASSID]); |
| u->cu_mask |= U32_ATTR_CLASSID; |
| } |
| |
| if (tb[TCA_U32_LINK]) { |
| u->cu_link = nla_get_u32(tb[TCA_U32_LINK]); |
| u->cu_mask |= U32_ATTR_LINK; |
| } |
| |
| if (tb[TCA_U32_ACT]) { |
| u->cu_act = nl_data_alloc_attr(tb[TCA_U32_ACT]); |
| if (!u->cu_act) |
| goto errout_nomem; |
| u->cu_mask |= U32_ATTR_ACTION; |
| } |
| |
| if (tb[TCA_U32_POLICE]) { |
| u->cu_police = nl_data_alloc_attr(tb[TCA_U32_POLICE]); |
| if (!u->cu_police) |
| goto errout_nomem; |
| u->cu_mask |= U32_ATTR_POLICE; |
| } |
| |
| if (tb[TCA_U32_PCNT]) { |
| struct tc_u32_sel *sel; |
| int pcnt_size; |
| |
| if (!tb[TCA_U32_SEL]) { |
| err = -NLE_MISSING_ATTR; |
| goto errout; |
| } |
| |
| sel = u->cu_selector->d_data; |
| pcnt_size = sizeof(struct tc_u32_pcnt) + |
| (sel->nkeys * sizeof(uint64_t)); |
| if (nla_len(tb[TCA_U32_PCNT]) < pcnt_size) { |
| err = -NLE_INVAL; |
| goto errout; |
| } |
| |
| u->cu_pcnt = nl_data_alloc_attr(tb[TCA_U32_PCNT]); |
| if (!u->cu_pcnt) |
| goto errout_nomem; |
| u->cu_mask |= U32_ATTR_PCNT; |
| } |
| |
| if (tb[TCA_U32_INDEV]) { |
| nla_strlcpy(u->cu_indev, tb[TCA_U32_INDEV], IFNAMSIZ); |
| u->cu_mask |= U32_ATTR_INDEV; |
| } |
| |
| return 0; |
| |
| errout_nomem: |
| err = -NLE_NOMEM; |
| errout: |
| return err; |
| } |
| |
| static void u32_free_data(struct rtnl_cls *cls) |
| { |
| struct rtnl_u32 *u = rtnl_cls_data(cls); |
| |
| nl_data_free(u->cu_selector); |
| nl_data_free(u->cu_act); |
| nl_data_free(u->cu_police); |
| nl_data_free(u->cu_pcnt); |
| } |
| |
| static int u32_clone(struct rtnl_cls *_dst, struct rtnl_cls *_src) |
| { |
| struct rtnl_u32 *dst = rtnl_cls_data(_dst); |
| struct rtnl_u32 *src = rtnl_cls_data(_src); |
| |
| if (src->cu_selector && |
| !(dst->cu_selector = nl_data_clone(src->cu_selector))) |
| return -NLE_NOMEM; |
| |
| if (src->cu_act && !(dst->cu_act = nl_data_clone(src->cu_act))) |
| return -NLE_NOMEM; |
| |
| if (src->cu_police && !(dst->cu_police = nl_data_clone(src->cu_police))) |
| return -NLE_NOMEM; |
| |
| if (src->cu_pcnt && !(dst->cu_pcnt = nl_data_clone(src->cu_pcnt))) |
| return -NLE_NOMEM; |
| |
| return 0; |
| } |
| |
| static void u32_dump_line(struct rtnl_cls *cls, struct nl_dump_params *p) |
| { |
| struct rtnl_u32 *u = rtnl_cls_data(cls); |
| char buf[32]; |
| |
| if (u->cu_mask & U32_ATTR_DIVISOR) |
| nl_dump(p, " divisor %u", u->cu_divisor); |
| else if (u->cu_mask & U32_ATTR_CLASSID) |
| nl_dump(p, " target %s", |
| rtnl_tc_handle2str(u->cu_classid, buf, sizeof(buf))); |
| } |
| |
| static void print_selector(struct nl_dump_params *p, struct tc_u32_sel *sel, |
| struct rtnl_cls *cls, struct rtnl_u32 *u) |
| { |
| int i; |
| struct tc_u32_key *key; |
| |
| if (sel->hmask || sel->hoff) { |
| /* I guess this will never be used since the kernel only |
| * exports the selector if no divisor is set but hash offset |
| * and hash mask make only sense in hash filters with divisor |
| * set */ |
| nl_dump(p, " hash at %u & 0x%x", sel->hoff, sel->hmask); |
| } |
| |
| if (sel->flags & (TC_U32_OFFSET | TC_U32_VAROFFSET)) { |
| nl_dump(p, " offset at %u", sel->off); |
| |
| if (sel->flags & TC_U32_VAROFFSET) |
| nl_dump(p, " variable (at %u & 0x%x) >> %u", |
| sel->offoff, ntohs(sel->offmask), sel->offshift); |
| } |
| |
| if (sel->flags) { |
| int flags = sel->flags; |
| nl_dump(p, " <"); |
| |
| #define PRINT_FLAG(f) if (flags & TC_U32_##f) { \ |
| flags &= ~TC_U32_##f; nl_dump(p, #f "%s", flags ? "," : ""); } |
| |
| PRINT_FLAG(TERMINAL); |
| PRINT_FLAG(OFFSET); |
| PRINT_FLAG(VAROFFSET); |
| PRINT_FLAG(EAT); |
| #undef PRINT_FLAG |
| |
| nl_dump(p, ">"); |
| } |
| |
| |
| for (i = 0; i < sel->nkeys; i++) { |
| key = (struct tc_u32_key *) ((char *) sel + sizeof(*sel)) + i; |
| |
| nl_dump(p, "\n"); |
| nl_dump_line(p, " match key at %s%u ", |
| key->offmask ? "nexthdr+" : "", key->off); |
| |
| if (key->offmask) |
| nl_dump(p, "[0x%u] ", key->offmask); |
| |
| nl_dump(p, "& 0x%08x == 0x%08x", ntohl(key->mask), ntohl(key->val)); |
| |
| if (p->dp_type == NL_DUMP_STATS && |
| (u->cu_mask & U32_ATTR_PCNT)) { |
| struct tc_u32_pcnt *pcnt = u->cu_pcnt->d_data; |
| nl_dump(p, " successful %" PRIu64, pcnt->kcnts[i]); |
| } |
| } |
| } |
| |
| static void u32_dump_details(struct rtnl_cls *cls, struct nl_dump_params *p) |
| { |
| struct rtnl_u32 *u = rtnl_cls_data(cls); |
| struct tc_u32_sel *s; |
| |
| if (!(u->cu_mask & U32_ATTR_SELECTOR)) { |
| nl_dump(p, "no-selector\n"); |
| return; |
| } |
| |
| s = u->cu_selector->d_data; |
| |
| nl_dump(p, "nkeys %u ", s->nkeys); |
| |
| if (u->cu_mask & U32_ATTR_HASH) |
| nl_dump(p, "ht key 0x%x hash 0x%u", |
| TC_U32_USERHTID(u->cu_hash), TC_U32_HASH(u->cu_hash)); |
| |
| if (u->cu_mask & U32_ATTR_LINK) |
| nl_dump(p, "link %u ", u->cu_link); |
| |
| if (u->cu_mask & U32_ATTR_INDEV) |
| nl_dump(p, "indev %s ", u->cu_indev); |
| |
| print_selector(p, s, cls, u); |
| nl_dump(p, "\n"); |
| |
| #if 0 |
| #define U32_ATTR_ACTION 0x040 |
| #define U32_ATTR_POLICE 0x080 |
| |
| struct nl_data act; |
| struct nl_data police; |
| #endif |
| } |
| |
| static void u32_dump_stats(struct rtnl_cls *cls, struct nl_dump_params *p) |
| { |
| struct rtnl_u32 *u = rtnl_cls_data(cls); |
| |
| if (u->cu_mask & U32_ATTR_PCNT) { |
| struct tc_u32_pcnt *pc = u->cu_pcnt->d_data; |
| nl_dump(p, "\n"); |
| nl_dump_line(p, " hit %8llu count %8llu\n", |
| pc->rhit, pc->rcnt); |
| } |
| } |
| |
| static int u32_get_opts(struct rtnl_cls *cls, struct nl_msg *msg) |
| { |
| struct rtnl_u32 *u = rtnl_cls_data(cls); |
| |
| if (u->cu_mask & U32_ATTR_DIVISOR) |
| NLA_PUT_U32(msg, TCA_U32_DIVISOR, u->cu_divisor); |
| |
| if (u->cu_mask & U32_ATTR_HASH) |
| NLA_PUT_U32(msg, TCA_U32_HASH, u->cu_hash); |
| |
| if (u->cu_mask & U32_ATTR_CLASSID) |
| NLA_PUT_U32(msg, TCA_U32_CLASSID, u->cu_classid); |
| |
| if (u->cu_mask & U32_ATTR_LINK) |
| NLA_PUT_U32(msg, TCA_U32_LINK, u->cu_link); |
| |
| if (u->cu_mask & U32_ATTR_SELECTOR) |
| NLA_PUT_DATA(msg, TCA_U32_SEL, u->cu_selector); |
| |
| if (u->cu_mask & U32_ATTR_ACTION) |
| NLA_PUT_DATA(msg, TCA_U32_ACT, u->cu_act); |
| |
| if (u->cu_mask & U32_ATTR_POLICE) |
| NLA_PUT_DATA(msg, TCA_U32_POLICE, u->cu_police); |
| |
| if (u->cu_mask & U32_ATTR_INDEV) |
| NLA_PUT_STRING(msg, TCA_U32_INDEV, u->cu_indev); |
| |
| return 0; |
| |
| nla_put_failure: |
| return -NLE_NOMEM; |
| } |
| |
| /** |
| * @name Attribute Modifications |
| * @{ |
| */ |
| |
| void rtnl_u32_set_handle(struct rtnl_cls *cls, int htid, int hash, |
| int nodeid) |
| { |
| uint32_t handle = (htid << 20) | (hash << 12) | nodeid; |
| |
| tca_set_handle((struct rtnl_tca *) cls, handle ); |
| } |
| |
| int rtnl_u32_set_classid(struct rtnl_cls *cls, uint32_t classid) |
| { |
| struct rtnl_u32 *u = rtnl_cls_data(cls); |
| |
| u->cu_classid = classid; |
| u->cu_mask |= U32_ATTR_CLASSID; |
| |
| return 0; |
| } |
| |
| /** @} */ |
| |
| /** |
| * @name Selector Modifications |
| * @{ |
| */ |
| |
| int rtnl_u32_set_flags(struct rtnl_cls *cls, int flags) |
| { |
| struct tc_u32_sel *sel; |
| struct rtnl_u32 *u = rtnl_cls_data(cls); |
| |
| sel = u32_selector_alloc(u); |
| if (!sel) |
| return -NLE_NOMEM; |
| |
| sel->flags |= flags; |
| u->cu_mask |= U32_ATTR_SELECTOR; |
| |
| return 0; |
| } |
| |
| /** |
| * Append new 32-bit key to the selector |
| * |
| * @arg cls classifier to be modifier |
| * @arg val value to be matched (network byte-order) |
| * @arg mask mask to be applied before matching (network byte-order) |
| * @arg off offset, in bytes, to start matching |
| * @arg offmask offset mask |
| * |
| * General selectors define the pattern, mask and offset the pattern will be |
| * matched to the packet contents. Using the general selectors you can match |
| * virtually any single bit in the IP (or upper layer) header. |
| * |
| */ |
| int rtnl_u32_add_key(struct rtnl_cls *cls, uint32_t val, uint32_t mask, |
| int off, int offmask) |
| { |
| struct tc_u32_sel *sel; |
| struct rtnl_u32 *u = rtnl_cls_data(cls); |
| int err; |
| |
| sel = u32_selector_alloc(u); |
| if (!sel) |
| return -NLE_NOMEM; |
| |
| err = nl_data_append(u->cu_selector, NULL, sizeof(struct tc_u32_key)); |
| if (err < 0) |
| return err; |
| |
| /* the selector might have been moved by realloc */ |
| sel = u32_selector(u); |
| |
| sel->keys[sel->nkeys].mask = mask; |
| sel->keys[sel->nkeys].val = val & mask; |
| sel->keys[sel->nkeys].off = off; |
| sel->keys[sel->nkeys].offmask = offmask; |
| sel->nkeys++; |
| u->cu_mask |= U32_ATTR_SELECTOR; |
| |
| return 0; |
| } |
| |
| int rtnl_u32_add_key_uint8(struct rtnl_cls *cls, uint8_t val, uint8_t mask, |
| int off, int offmask) |
| { |
| int shift = 24 - 8 * (off & 3); |
| |
| return rtnl_u32_add_key(cls, htonl((uint32_t)val << shift), |
| htonl((uint32_t)mask << shift), |
| off & ~3, offmask); |
| } |
| |
| /** |
| * Append new selector key to match a 16-bit number |
| * |
| * @arg cls classifier to be modified |
| * @arg val value to be matched (host byte-order) |
| * @arg mask mask to be applied before matching (host byte-order) |
| * @arg off offset, in bytes, to start matching |
| * @arg offmask offset mask |
| */ |
| int rtnl_u32_add_key_uint16(struct rtnl_cls *cls, uint16_t val, uint16_t mask, |
| int off, int offmask) |
| { |
| int shift = ((off & 3) == 0 ? 16 : 0); |
| if (off % 2) |
| return -NLE_INVAL; |
| |
| return rtnl_u32_add_key(cls, htonl((uint32_t)val << shift), |
| htonl((uint32_t)mask << shift), |
| off & ~3, offmask); |
| } |
| |
| /** |
| * Append new selector key to match a 32-bit number |
| * |
| * @arg cls classifier to be modified |
| * @arg val value to be matched (host byte-order) |
| * @arg mask mask to be applied before matching (host byte-order) |
| * @arg off offset, in bytes, to start matching |
| * @arg offmask offset mask |
| */ |
| int rtnl_u32_add_key_uint32(struct rtnl_cls *cls, uint32_t val, uint32_t mask, |
| int off, int offmask) |
| { |
| return rtnl_u32_add_key(cls, htonl(val), htonl(mask), |
| off & ~3, offmask); |
| } |
| |
| int rtnl_u32_add_key_in_addr(struct rtnl_cls *cls, struct in_addr *addr, |
| uint8_t bitmask, int off, int offmask) |
| { |
| uint32_t mask = 0xFFFFFFFF << (32 - bitmask); |
| return rtnl_u32_add_key(cls, addr->s_addr, htonl(mask), off, offmask); |
| } |
| |
| int rtnl_u32_add_key_in6_addr(struct rtnl_cls *cls, struct in6_addr *addr, |
| uint8_t bitmask, int off, int offmask) |
| { |
| int i, err; |
| |
| for (i = 1; i <= 4; i++) { |
| if (32 * i - bitmask <= 0) { |
| if ((err = rtnl_u32_add_key(cls, addr->s6_addr32[i-1], |
| 0xFFFFFFFF, off+4*(i-1), offmask)) < 0) |
| return err; |
| } |
| else if (32 * i - bitmask < 32) { |
| uint32_t mask = 0xFFFFFFFF << (32 * i - bitmask); |
| if ((err = rtnl_u32_add_key(cls, addr->s6_addr32[i-1], |
| htonl(mask), off+4*(i-1), offmask)) < 0) |
| return err; |
| } |
| /* otherwise, if (32*i - bitmask >= 32) no key is generated */ |
| } |
| |
| return 0; |
| } |
| |
| /** @} */ |
| |
| static struct rtnl_cls_ops u32_ops = { |
| .co_kind = "u32", |
| .co_size = sizeof(struct rtnl_u32), |
| .co_msg_parser = u32_msg_parser, |
| .co_free_data = u32_free_data, |
| .co_clone = u32_clone, |
| .co_get_opts = u32_get_opts, |
| .co_dump = { |
| [NL_DUMP_LINE] = u32_dump_line, |
| [NL_DUMP_DETAILS] = u32_dump_details, |
| [NL_DUMP_STATS] = u32_dump_stats, |
| }, |
| }; |
| |
| static void __init u32_init(void) |
| { |
| rtnl_cls_register(&u32_ops); |
| } |
| |
| static void __exit u32_exit(void) |
| { |
| rtnl_cls_unregister(&u32_ops); |
| } |
| |
| /** @} */ |