| /* |
| * lib/route/cls/ematch/meta.c Metadata Match |
| * |
| * 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) 2010-2013 Thomas Graf <tgraf@suug.ch> |
| */ |
| |
| /** |
| * @ingroup ematch |
| * @defgroup em_meta Metadata Match |
| * |
| * @{ |
| */ |
| |
| #include <netlink-private/netlink.h> |
| #include <netlink-private/tc.h> |
| #include <netlink/netlink.h> |
| #include <netlink/route/cls/ematch.h> |
| #include <netlink/route/cls/ematch/meta.h> |
| |
| struct rtnl_meta_value |
| { |
| uint8_t mv_type; |
| uint8_t mv_shift; |
| uint16_t mv_id; |
| size_t mv_len; |
| }; |
| |
| struct meta_data |
| { |
| struct rtnl_meta_value * left; |
| struct rtnl_meta_value * right; |
| uint8_t opnd; |
| }; |
| |
| static struct rtnl_meta_value *meta_alloc(uint8_t type, uint16_t id, |
| uint8_t shift, void *data, |
| size_t len) |
| { |
| struct rtnl_meta_value *value; |
| |
| if (!(value = calloc(1, sizeof(*value) + len))) |
| return NULL; |
| |
| value->mv_type = type; |
| value->mv_id = id; |
| value->mv_shift = shift; |
| value->mv_len = len; |
| |
| memcpy(value + 1, data, len); |
| |
| return value; |
| } |
| |
| struct rtnl_meta_value *rtnl_meta_value_alloc_int(uint64_t value) |
| { |
| return meta_alloc(TCF_META_TYPE_INT, TCF_META_ID_VALUE, 0, &value, 8); |
| } |
| |
| struct rtnl_meta_value *rtnl_meta_value_alloc_var(void *data, size_t len) |
| { |
| return meta_alloc(TCF_META_TYPE_VAR, TCF_META_ID_VALUE, 0, data, len); |
| } |
| |
| struct rtnl_meta_value *rtnl_meta_value_alloc_id(uint8_t type, uint16_t id, |
| uint8_t shift, uint64_t mask) |
| { |
| size_t masklen = 0; |
| |
| if (id > TCF_META_ID_MAX) |
| return NULL; |
| |
| if (mask) { |
| if (type == TCF_META_TYPE_VAR) |
| return NULL; |
| |
| masklen = 8; |
| } |
| |
| return meta_alloc(type, id, shift, &mask, masklen); |
| } |
| |
| void rtnl_meta_value_put(struct rtnl_meta_value *mv) |
| { |
| free(mv); |
| } |
| |
| void rtnl_ematch_meta_set_lvalue(struct rtnl_ematch *e, struct rtnl_meta_value *v) |
| { |
| struct meta_data *m = rtnl_ematch_data(e); |
| m->left = v; |
| } |
| |
| void rtnl_ematch_meta_set_rvalue(struct rtnl_ematch *e, struct rtnl_meta_value *v) |
| { |
| struct meta_data *m = rtnl_ematch_data(e); |
| m->right = v; |
| } |
| |
| void rtnl_ematch_meta_set_operand(struct rtnl_ematch *e, uint8_t opnd) |
| { |
| struct meta_data *m = rtnl_ematch_data(e); |
| m->opnd = opnd; |
| } |
| |
| static struct nla_policy meta_policy[TCA_EM_META_MAX+1] = { |
| [TCA_EM_META_HDR] = { .minlen = sizeof(struct tcf_meta_hdr) }, |
| [TCA_EM_META_LVALUE] = { .minlen = 1, }, |
| [TCA_EM_META_RVALUE] = { .minlen = 1, }, |
| }; |
| |
| static int meta_parse(struct rtnl_ematch *e, void *data, size_t len) |
| { |
| struct meta_data *m = rtnl_ematch_data(e); |
| struct nlattr *tb[TCA_EM_META_MAX+1]; |
| struct rtnl_meta_value *v; |
| struct tcf_meta_hdr *hdr; |
| void *vdata = NULL; |
| size_t vlen = 0; |
| int err; |
| |
| if ((err = nla_parse(tb, TCA_EM_META_MAX, data, len, meta_policy)) < 0) |
| return err; |
| |
| if (!tb[TCA_EM_META_HDR]) |
| return -NLE_MISSING_ATTR; |
| |
| hdr = nla_data(tb[TCA_EM_META_HDR]); |
| |
| if (tb[TCA_EM_META_LVALUE]) { |
| vdata = nla_data(tb[TCA_EM_META_LVALUE]); |
| vlen = nla_len(tb[TCA_EM_META_LVALUE]); |
| } |
| |
| v = meta_alloc(TCF_META_TYPE(hdr->left.kind), |
| TCF_META_ID(hdr->left.kind), |
| hdr->left.shift, vdata, vlen); |
| if (!v) |
| return -NLE_NOMEM; |
| |
| m->left = v; |
| |
| vlen = 0; |
| if (tb[TCA_EM_META_RVALUE]) { |
| vdata = nla_data(tb[TCA_EM_META_RVALUE]); |
| vlen = nla_len(tb[TCA_EM_META_RVALUE]); |
| } |
| |
| v = meta_alloc(TCF_META_TYPE(hdr->right.kind), |
| TCF_META_ID(hdr->right.kind), |
| hdr->right.shift, vdata, vlen); |
| if (!v) { |
| rtnl_meta_value_put(m->left); |
| return -NLE_NOMEM; |
| } |
| |
| m->right = v; |
| m->opnd = hdr->left.op; |
| |
| return 0; |
| } |
| |
| static const struct trans_tbl meta_int[] = { |
| __ADD(TCF_META_ID_RANDOM, random) |
| __ADD(TCF_META_ID_LOADAVG_0, loadavg_0) |
| __ADD(TCF_META_ID_LOADAVG_1, loadavg_1) |
| __ADD(TCF_META_ID_LOADAVG_2, loadavg_2) |
| __ADD(TCF_META_ID_DEV, dev) |
| __ADD(TCF_META_ID_PRIORITY, prio) |
| __ADD(TCF_META_ID_PROTOCOL, proto) |
| __ADD(TCF_META_ID_PKTTYPE, pkttype) |
| __ADD(TCF_META_ID_PKTLEN, pktlen) |
| __ADD(TCF_META_ID_DATALEN, datalen) |
| __ADD(TCF_META_ID_MACLEN, maclen) |
| __ADD(TCF_META_ID_NFMARK, mark) |
| __ADD(TCF_META_ID_TCINDEX, tcindex) |
| __ADD(TCF_META_ID_RTCLASSID, rtclassid) |
| __ADD(TCF_META_ID_RTIIF, rtiif) |
| __ADD(TCF_META_ID_SK_FAMILY, sk_family) |
| __ADD(TCF_META_ID_SK_STATE, sk_state) |
| __ADD(TCF_META_ID_SK_REUSE, sk_reuse) |
| __ADD(TCF_META_ID_SK_REFCNT, sk_refcnt) |
| __ADD(TCF_META_ID_SK_RCVBUF, sk_rcvbuf) |
| __ADD(TCF_META_ID_SK_SNDBUF, sk_sndbuf) |
| __ADD(TCF_META_ID_SK_SHUTDOWN, sk_sutdown) |
| __ADD(TCF_META_ID_SK_PROTO, sk_proto) |
| __ADD(TCF_META_ID_SK_TYPE, sk_type) |
| __ADD(TCF_META_ID_SK_RMEM_ALLOC, sk_rmem_alloc) |
| __ADD(TCF_META_ID_SK_WMEM_ALLOC, sk_wmem_alloc) |
| __ADD(TCF_META_ID_SK_WMEM_QUEUED, sk_wmem_queued) |
| __ADD(TCF_META_ID_SK_RCV_QLEN, sk_rcv_qlen) |
| __ADD(TCF_META_ID_SK_SND_QLEN, sk_snd_qlen) |
| __ADD(TCF_META_ID_SK_ERR_QLEN, sk_err_qlen) |
| __ADD(TCF_META_ID_SK_FORWARD_ALLOCS, sk_forward_allocs) |
| __ADD(TCF_META_ID_SK_ALLOCS, sk_allocs) |
| __ADD(TCF_META_ID_SK_ROUTE_CAPS, sk_route_caps) |
| __ADD(TCF_META_ID_SK_HASH, sk_hash) |
| __ADD(TCF_META_ID_SK_LINGERTIME, sk_lingertime) |
| __ADD(TCF_META_ID_SK_ACK_BACKLOG, sk_ack_backlog) |
| __ADD(TCF_META_ID_SK_MAX_ACK_BACKLOG, sk_max_ack_backlog) |
| __ADD(TCF_META_ID_SK_PRIO, sk_prio) |
| __ADD(TCF_META_ID_SK_RCVLOWAT, sk_rcvlowat) |
| __ADD(TCF_META_ID_SK_RCVTIMEO, sk_rcvtimeo) |
| __ADD(TCF_META_ID_SK_SNDTIMEO, sk_sndtimeo) |
| __ADD(TCF_META_ID_SK_SENDMSG_OFF, sk_sendmsg_off) |
| __ADD(TCF_META_ID_SK_WRITE_PENDING, sk_write_pending) |
| __ADD(TCF_META_ID_VLAN_TAG, vlan) |
| __ADD(TCF_META_ID_RXHASH, rxhash) |
| }; |
| |
| static char *int_id2str(int id, char *buf, size_t size) |
| { |
| return __type2str(id, buf, size, meta_int, ARRAY_SIZE(meta_int)); |
| } |
| |
| static const struct trans_tbl meta_var[] = { |
| __ADD(TCF_META_ID_DEV,devname) |
| __ADD(TCF_META_ID_SK_BOUND_IF,sk_bound_if) |
| }; |
| |
| static char *var_id2str(int id, char *buf, size_t size) |
| { |
| return __type2str(id, buf, size, meta_var, ARRAY_SIZE(meta_var)); |
| } |
| |
| static void dump_value(struct rtnl_meta_value *v, struct nl_dump_params *p) |
| { |
| char buf[32]; |
| |
| switch (v->mv_type) { |
| case TCF_META_TYPE_INT: |
| if (v->mv_id == TCF_META_ID_VALUE) { |
| nl_dump(p, "%u", |
| *(uint32_t *) (v + 1)); |
| } else { |
| nl_dump(p, "%s", |
| int_id2str(v->mv_id, buf, sizeof(buf))); |
| |
| if (v->mv_shift) |
| nl_dump(p, " >> %u", v->mv_shift); |
| |
| if (v->mv_len == 4) |
| nl_dump(p, " & %#x", *(uint32_t *) (v + 1)); |
| else if (v->mv_len == 8) |
| nl_dump(p, " & %#x", *(uint64_t *) (v + 1)); |
| } |
| break; |
| |
| case TCF_META_TYPE_VAR: |
| if (v->mv_id == TCF_META_ID_VALUE) { |
| nl_dump(p, "%s", (char *) (v + 1)); |
| } else { |
| nl_dump(p, "%s", |
| var_id2str(v->mv_id, buf, sizeof(buf))); |
| |
| if (v->mv_shift) |
| nl_dump(p, " >> %u", v->mv_shift); |
| } |
| break; |
| } |
| } |
| |
| static void meta_dump(struct rtnl_ematch *e, struct nl_dump_params *p) |
| { |
| struct meta_data *m = rtnl_ematch_data(e); |
| char buf[32]; |
| |
| nl_dump(p, "meta("); |
| dump_value(m->left, p); |
| |
| nl_dump(p, " %s ", rtnl_ematch_opnd2txt(m->opnd, buf, sizeof(buf))); |
| |
| dump_value(m->right, p); |
| nl_dump(p, ")"); |
| } |
| |
| static int meta_fill(struct rtnl_ematch *e, struct nl_msg *msg) |
| { |
| struct meta_data *m = rtnl_ematch_data(e); |
| struct tcf_meta_hdr hdr; |
| |
| if (!(m->left && m->right)) |
| return -NLE_MISSING_ATTR; |
| |
| memset(&hdr, 0, sizeof(hdr)); |
| hdr.left.kind = (m->left->mv_type << 12) & TCF_META_TYPE_MASK; |
| hdr.left.kind |= m->left->mv_id & TCF_META_ID_MASK; |
| hdr.left.shift = m->left->mv_shift; |
| hdr.left.op = m->opnd; |
| hdr.right.kind = (m->right->mv_type << 12) & TCF_META_TYPE_MASK; |
| hdr.right.kind |= m->right->mv_id & TCF_META_ID_MASK; |
| |
| NLA_PUT(msg, TCA_EM_META_HDR, sizeof(hdr), &hdr); |
| |
| if (m->left->mv_len) |
| NLA_PUT(msg, TCA_EM_META_LVALUE, m->left->mv_len, (m->left + 1)); |
| |
| if (m->right->mv_len) |
| NLA_PUT(msg, TCA_EM_META_RVALUE, m->right->mv_len, (m->right + 1)); |
| |
| return 0; |
| |
| nla_put_failure: |
| return -NLE_NOMEM; |
| } |
| |
| static void meta_free(struct rtnl_ematch *e) |
| { |
| struct meta_data *m = rtnl_ematch_data(e); |
| free(m->left); |
| free(m->right); |
| } |
| |
| static struct rtnl_ematch_ops meta_ops = { |
| .eo_kind = TCF_EM_META, |
| .eo_name = "meta", |
| .eo_minlen = sizeof(struct tcf_meta_hdr), |
| .eo_datalen = sizeof(struct meta_data), |
| .eo_parse = meta_parse, |
| .eo_dump = meta_dump, |
| .eo_fill = meta_fill, |
| .eo_free = meta_free, |
| }; |
| |
| static void __init meta_init(void) |
| { |
| rtnl_ematch_register(&meta_ops); |
| } |
| |
| /** @} */ |