/* SPDX-License-Identifier: LGPL-2.1-only */ /* * Copyright (c) 2010-2013 Thomas Graf */ /** * @ingroup ematch * @defgroup em_meta Metadata Match * * @{ */ #include #include #include #include #include #include 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; if (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, " & %#lx", (long unsigned) *(uint32_t *) (v + 1)); else if (v->mv_len == 8) nl_dump(p, " & %#llx", (long long unsigned) (*(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); } /** @} */