diff options
Diffstat (limited to '')
-rw-r--r-- | src/meta.c | 1043 |
1 files changed, 1043 insertions, 0 deletions
diff --git a/src/meta.c b/src/meta.c new file mode 100644 index 0000000..b578d5e --- /dev/null +++ b/src/meta.c @@ -0,0 +1,1043 @@ +/* + * Meta expression/statement related definition and types. + * + * Copyright (c) 2008 Patrick McHardy <kaber@trash.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Development of this code funded by Astaro AG (http://www.astaro.com/) + */ + +#include <nft.h> + +#include <errno.h> +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <pwd.h> +#include <grp.h> +#include <arpa/inet.h> +#include <linux/netfilter.h> +#include <linux/pkt_sched.h> +#include <linux/if_packet.h> +#include <time.h> + +#include <nftables.h> +#include <expression.h> +#include <statement.h> +#include <datatype.h> +#include <meta.h> +#include <gmputil.h> +#include <utils.h> +#include <erec.h> +#include <iface.h> +#include <json.h> + +static void tchandle_type_print(const struct expr *expr, + struct output_ctx *octx) +{ + uint32_t handle = mpz_get_uint32(expr->value); + + switch(handle) { + case TC_H_ROOT: + nft_print(octx, "root"); + break; + case TC_H_UNSPEC: + nft_print(octx, "none"); + break; + default: + nft_print(octx, "%0x:%0x", + TC_H_MAJ(handle) >> 16, + TC_H_MIN(handle)); + break; + } +} + +static struct error_record *tchandle_type_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + uint32_t handle; + char *str = NULL; + + if (strcmp(sym->identifier, "root") == 0) + handle = TC_H_ROOT; + else if (strcmp(sym->identifier, "none") == 0) + handle = TC_H_UNSPEC; + else if (strchr(sym->identifier, ':')) { + uint32_t tmp; + char *colon; + + str = xstrdup(sym->identifier); + + colon = strchr(str, ':'); + if (!colon) + goto err; + + *colon = '\0'; + + errno = 0; + tmp = strtoull(str, NULL, 16); + if (errno != 0) + goto err; + + handle = (tmp << 16); + if (str[strlen(str) - 1] == ':') + goto out; + + errno = 0; + tmp = strtoull(colon + 1, NULL, 16); + if (errno != 0) + goto err; + + handle |= tmp; + } else { + handle = strtoull(sym->identifier, NULL, 0); + } +out: + xfree(str); + *res = constant_expr_alloc(&sym->location, sym->dtype, + BYTEORDER_HOST_ENDIAN, + sizeof(handle) * BITS_PER_BYTE, &handle); + return NULL; +err: + xfree(str); + return error(&sym->location, "Could not parse %s", sym->dtype->desc); +} + +const struct datatype tchandle_type = { + .type = TYPE_CLASSID, + .name = "classid", + .desc = "TC classid", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = 4 * BITS_PER_BYTE, + .basetype = &integer_type, + .print = tchandle_type_print, + .parse = tchandle_type_parse, +}; + +static void ifindex_type_print(const struct expr *expr, struct output_ctx *octx) +{ + char name[IFNAMSIZ]; + int ifindex; + + ifindex = mpz_get_uint32(expr->value); + if (nft_if_indextoname(ifindex, name)) + nft_print(octx, "\"%s\"", name); + else + nft_print(octx, "%d", ifindex); +} + +static struct error_record *ifindex_type_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + int ifindex; + + ifindex = nft_if_nametoindex(sym->identifier); + if (ifindex == 0) { + char *end; + long res; + + errno = 0; + res = strtol(sym->identifier, &end, 10); + + if (res < 0 || res > INT_MAX || *end || errno) + return error(&sym->location, "Interface does not exist"); + + ifindex = (int)res; + } + + *res = constant_expr_alloc(&sym->location, sym->dtype, + BYTEORDER_HOST_ENDIAN, + sizeof(ifindex) * BITS_PER_BYTE, &ifindex); + return NULL; +} + +const struct datatype ifindex_type = { + .type = TYPE_IFINDEX, + .name = "iface_index", + .desc = "network interface index", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = 4 * BITS_PER_BYTE, + .basetype = &integer_type, + .print = ifindex_type_print, + .parse = ifindex_type_parse, +}; + +static const struct symbol_table arphrd_tbl = { + .base = BASE_HEXADECIMAL, + .symbols = { + SYMBOL("ether", ARPHRD_ETHER), + SYMBOL("ppp", ARPHRD_PPP), + /* dummy types */ + SYMBOL("ipip", ARPHRD_TUNNEL), + SYMBOL("ipip6", ARPHRD_TUNNEL6), + SYMBOL("loopback", ARPHRD_LOOPBACK), + SYMBOL("sit", ARPHRD_SIT), + SYMBOL("ipgre", ARPHRD_IPGRE), + SYMBOL_LIST_END, + }, +}; + +const struct datatype arphrd_type = { + .type = TYPE_ARPHRD, + .name = "iface_type", + .desc = "network interface type", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = 2 * BITS_PER_BYTE, + .basetype = &integer_type, + .sym_tbl = &arphrd_tbl, +}; + +static void uid_type_print(const struct expr *expr, struct output_ctx *octx) +{ + struct passwd *pw; + + if (nft_output_guid(octx)) { + uint32_t uid = mpz_get_uint32(expr->value); + + pw = getpwuid(uid); + if (pw != NULL) + nft_print(octx, "\"%s\"", pw->pw_name); + else + nft_print(octx, "%d", uid); + return; + } + expr_basetype(expr)->print(expr, octx); +} + +static struct error_record *uid_type_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + struct passwd *pw; + uid_t uid; + char *endptr = NULL; + + pw = getpwnam(sym->identifier); + if (pw != NULL) + uid = pw->pw_uid; + else { + uint64_t _uid = strtoull(sym->identifier, &endptr, 10); + + if (_uid > UINT32_MAX) + return error(&sym->location, "Value too large"); + else if (*endptr) + return error(&sym->location, "User does not exist"); + uid = _uid; + } + + *res = constant_expr_alloc(&sym->location, sym->dtype, + BYTEORDER_HOST_ENDIAN, + sizeof(pw->pw_uid) * BITS_PER_BYTE, &uid); + return NULL; +} + +const struct datatype uid_type = { + .type = TYPE_UID, + .name = "uid", + .desc = "user ID", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = sizeof(uid_t) * BITS_PER_BYTE, + .basetype = &integer_type, + .print = uid_type_print, + .json = uid_type_json, + .parse = uid_type_parse, +}; + +static void gid_type_print(const struct expr *expr, struct output_ctx *octx) +{ + struct group *gr; + + if (nft_output_guid(octx)) { + uint32_t gid = mpz_get_uint32(expr->value); + + gr = getgrgid(gid); + if (gr != NULL) + nft_print(octx, "\"%s\"", gr->gr_name); + else + nft_print(octx, "%u", gid); + return; + } + expr_basetype(expr)->print(expr, octx); +} + +static struct error_record *gid_type_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + struct group *gr; + gid_t gid; + char *endptr = NULL; + + gr = getgrnam(sym->identifier); + if (gr != NULL) + gid = gr->gr_gid; + else { + uint64_t _gid = strtoull(sym->identifier, &endptr, 0); + + if (_gid > UINT32_MAX) + return error(&sym->location, "Value too large"); + else if (*endptr) + return error(&sym->location, "Group does not exist"); + gid = _gid; + } + + *res = constant_expr_alloc(&sym->location, sym->dtype, + BYTEORDER_HOST_ENDIAN, + sizeof(gr->gr_gid) * BITS_PER_BYTE, &gid); + return NULL; +} + +const struct datatype gid_type = { + .type = TYPE_GID, + .name = "gid", + .desc = "group ID", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = sizeof(gid_t) * BITS_PER_BYTE, + .basetype = &integer_type, + .print = gid_type_print, + .json = gid_type_json, + .parse = gid_type_parse, +}; + +static const struct symbol_table pkttype_type_tbl = { + .base = BASE_DECIMAL, + .symbols = { + SYMBOL("host", PACKET_HOST), + SYMBOL("unicast", PACKET_HOST), /* backwards compat */ + SYMBOL("broadcast", PACKET_BROADCAST), + SYMBOL("multicast", PACKET_MULTICAST), + SYMBOL("other", PACKET_OTHERHOST), + SYMBOL_LIST_END, + }, +}; + +static void pkttype_type_print(const struct expr *expr, struct output_ctx *octx) +{ + return symbolic_constant_print(&pkttype_type_tbl, expr, false, octx); +} + +const struct datatype pkttype_type = { + .type = TYPE_PKTTYPE, + .name = "pkt_type", + .desc = "packet type", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = BITS_PER_BYTE, + .basetype = &integer_type, + .print = pkttype_type_print, + .sym_tbl = &pkttype_type_tbl, +}; + +void devgroup_table_init(struct nft_ctx *ctx) +{ + ctx->output.tbl.devgroup = rt_symbol_table_init("/etc/iproute2/group"); +} + +void devgroup_table_exit(struct nft_ctx *ctx) +{ + rt_symbol_table_free(ctx->output.tbl.devgroup); +} + +static void devgroup_type_print(const struct expr *expr, + struct output_ctx *octx) +{ + return symbolic_constant_print(octx->tbl.devgroup, expr, true, octx); +} + +static struct error_record *devgroup_type_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + return symbolic_constant_parse(ctx, sym, ctx->tbl->devgroup, res); +} + +const struct datatype devgroup_type = { + .type = TYPE_DEVGROUP, + .name = "devgroup", + .desc = "devgroup name", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = 4 * BITS_PER_BYTE, + .basetype = &integer_type, + .print = devgroup_type_print, + .json = devgroup_type_json, + .parse = devgroup_type_parse, + .flags = DTYPE_F_PREFIX, +}; + +const struct datatype ifname_type = { + .type = TYPE_IFNAME, + .name = "ifname", + .desc = "network interface name", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = IFNAMSIZ * BITS_PER_BYTE, + .basetype = &string_type, +}; + +static void date_type_print(const struct expr *expr, struct output_ctx *octx) +{ + uint64_t tstamp64 = mpz_get_uint64(expr->value); + char timestr[21]; + time_t tstamp; + struct tm tm; + + /* Convert from nanoseconds to seconds */ + tstamp64 /= 1000000000L; + + /* Obtain current tm, to add tm_gmtoff to the timestamp */ + tstamp = tstamp64; + if (localtime_r(&tstamp, &tm)) + tstamp64 += tm.tm_gmtoff; + + tstamp = tstamp64; + if (gmtime_r(&tstamp, &tm) && + strftime(timestr, sizeof(timestr) - 1, "%Y-%m-%d %T", &tm)) + nft_print(octx, "\"%s\"", timestr); + else + nft_print(octx, "Error converting timestamp to printed time"); +} + +static bool parse_iso_date(uint64_t *tstamp, const char *sym) +{ + struct tm cur_tm; + struct tm tm; + time_t ts; + + memset(&tm, 0, sizeof(struct tm)); + + if (strptime(sym, "%Y-%m-%d %T", &tm)) + goto success; + if (strptime(sym, "%Y-%m-%d %R", &tm)) + goto success; + if (strptime(sym, "%Y-%m-%d", &tm)) + goto success; + + return false; + +success: + /* + * Overwriting TZ is problematic if we're parsing hour types in this same process, + * hence I'd rather use timegm() which doesn't take into account the TZ env variable, + * even though it's Linux-specific. + */ + ts = timegm(&tm); + + if (ts == (time_t) -1) + return false; + + /* Obtain current tm as well (at the specified time), so that we can substract tm_gmtoff */ + if (!localtime_r(&ts, &cur_tm)) + return false; + + /* Substract tm_gmtoff to get the current time */ + *tstamp = ts - cur_tm.tm_gmtoff; + + return true; +} + +static struct error_record *date_type_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + const char *endptr = sym->identifier; + uint64_t tstamp; + + if (parse_iso_date(&tstamp, sym->identifier)) + goto success; + + tstamp = strtoul(sym->identifier, (char **) &endptr, 10); + if (*endptr == '\0' && endptr != sym->identifier) + goto success; + + return error(&sym->location, "Cannot parse date"); + +success: + /* Convert to nanoseconds */ + tstamp *= 1000000000L; + *res = constant_expr_alloc(&sym->location, sym->dtype, + BYTEORDER_HOST_ENDIAN, + sizeof(uint64_t) * BITS_PER_BYTE, + &tstamp); + return NULL; +} + +static const struct symbol_table day_type_tbl = { + .base = BASE_DECIMAL, + .symbols = { + SYMBOL("Sunday", 0), + SYMBOL("Monday", 1), + SYMBOL("Tuesday", 2), + SYMBOL("Wednesday", 3), + SYMBOL("Thursday", 4), + SYMBOL("Friday", 5), + SYMBOL("Saturday", 6), + SYMBOL_LIST_END, + }, +}; + +static void day_type_print(const struct expr *expr, struct output_ctx *octx) +{ + return symbolic_constant_print(&day_type_tbl, expr, true, octx); +} + +#define SECONDS_PER_DAY (60 * 60 * 24) + +static void hour_type_print(const struct expr *expr, struct output_ctx *octx) +{ + uint32_t seconds = mpz_get_uint32(expr->value), minutes, hours; + struct tm cur_tm; + time_t ts; + + /* Obtain current tm, so that we can add tm_gmtoff */ + ts = time(NULL); + if (ts != ((time_t) -1) && localtime_r(&ts, &cur_tm)) + seconds = (seconds + cur_tm.tm_gmtoff) % SECONDS_PER_DAY; + + minutes = seconds / 60; + seconds %= 60; + hours = minutes / 60; + minutes %= 60; + + nft_print(octx, "\"%02d:%02d", hours, minutes); + if (seconds) + nft_print(octx, ":%02d", seconds); + nft_print(octx, "\""); +} + +static struct error_record *hour_type_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + struct error_record *er; + struct tm cur_tm_data; + struct tm *cur_tm; + uint32_t result; + uint64_t tmp; + char *endptr; + struct tm tm; + time_t ts; + + memset(&tm, 0, sizeof(struct tm)); + + /* First, try to parse it as a number */ + result = strtoul(sym->identifier, (char **) &endptr, 10); + if (*endptr == '\0' && endptr != sym->identifier) + goto success; + + result = 0; + + /* Obtain current tm, so that we can substract tm_gmtoff */ + ts = time(NULL); + if (ts != ((time_t) -1) && localtime_r(&ts, &cur_tm_data)) + cur_tm = &cur_tm_data; + else + cur_tm = NULL; + + endptr = strptime(sym->identifier, "%T", &tm); + if (endptr && *endptr == '\0') + goto convert; + + endptr = strptime(sym->identifier, "%R", &tm); + if (endptr && *endptr == '\0') + goto convert; + + if (endptr && *endptr) + return error(&sym->location, "Can't parse trailing input: \"%s\"\n", endptr); + + if ((er = time_parse(&sym->location, sym->identifier, &tmp)) == NULL) { + result = tmp / 1000; + goto convert; + } + + return er; + +convert: + /* Convert the hour to the number of seconds since midnight */ + if (result == 0) + result = tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec; + + /* Substract tm_gmtoff to get the current time */ + if (cur_tm) { + if ((long int) result >= cur_tm->tm_gmtoff) + result = (result - cur_tm->tm_gmtoff) % 86400; + else + result = 86400 - cur_tm->tm_gmtoff + result; + } + +success: + *res = constant_expr_alloc(&sym->location, sym->dtype, + BYTEORDER_HOST_ENDIAN, + sizeof(uint32_t) * BITS_PER_BYTE, + &result); + return NULL; +} + +const struct datatype date_type = { + .type = TYPE_TIME_DATE, + .name = "time", + .desc = "Relative time of packet reception", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = sizeof(uint64_t) * BITS_PER_BYTE, + .basetype = &integer_type, + .print = date_type_print, + .parse = date_type_parse, +}; + +const struct datatype day_type = { + .type = TYPE_TIME_DAY, + .name = "day", + .desc = "Day of week of packet reception", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = 1 * BITS_PER_BYTE, + .basetype = &integer_type, + .print = day_type_print, + .sym_tbl = &day_type_tbl, +}; + +const struct datatype hour_type = { + .type = TYPE_TIME_HOUR, + .name = "hour", + .desc = "Hour of day of packet reception", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = sizeof(uint32_t) * BITS_PER_BYTE, + .basetype = &integer_type, + .print = hour_type_print, + .parse = hour_type_parse, +}; + +const struct meta_template meta_templates[] = { + [NFT_META_LEN] = META_TEMPLATE("length", &integer_type, + 4 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_PROTOCOL] = META_TEMPLATE("protocol", ðertype_type, + 2 * 8, BYTEORDER_BIG_ENDIAN), + [NFT_META_NFPROTO] = META_TEMPLATE("nfproto", &nfproto_type, + 1 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_L4PROTO] = META_TEMPLATE("l4proto", &inet_protocol_type, + 1 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_PRIORITY] = META_TEMPLATE("priority", &tchandle_type, + 4 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_MARK] = META_TEMPLATE("mark", &mark_type, + 4 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_IIF] = META_TEMPLATE("iif", &ifindex_type, + 4 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_IIFNAME] = META_TEMPLATE("iifname", &ifname_type, + IFNAMSIZ * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_IIFTYPE] = META_TEMPLATE("iiftype", &arphrd_type, + 2 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_OIF] = META_TEMPLATE("oif", &ifindex_type, + 4 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_OIFNAME] = META_TEMPLATE("oifname", &ifname_type, + IFNAMSIZ * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_OIFTYPE] = META_TEMPLATE("oiftype", &arphrd_type, + 2 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_SKUID] = META_TEMPLATE("skuid", &uid_type, + 4 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_SKGID] = META_TEMPLATE("skgid", &gid_type, + 4 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_NFTRACE] = META_TEMPLATE("nftrace", &integer_type, + 1 , BYTEORDER_HOST_ENDIAN), + [NFT_META_RTCLASSID] = META_TEMPLATE("rtclassid", &realm_type, + 4 * 8, BYTEORDER_HOST_ENDIAN), + [NFT_META_BRI_IIFNAME] = META_TEMPLATE("ibrname", &ifname_type, + IFNAMSIZ * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_BRI_OIFNAME] = META_TEMPLATE("obrname", &ifname_type, + IFNAMSIZ * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_PKTTYPE] = META_TEMPLATE("pkttype", &pkttype_type, + BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_CPU] = META_TEMPLATE("cpu", &integer_type, + 4 * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_IIFGROUP] = META_TEMPLATE("iifgroup", &devgroup_type, + 4 * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_OIFGROUP] = META_TEMPLATE("oifgroup", &devgroup_type, + 4 * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_CGROUP] = META_TEMPLATE("cgroup", &integer_type, + 4 * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_PRANDOM] = META_TEMPLATE("random", &integer_type, + 4 * BITS_PER_BYTE, + BYTEORDER_BIG_ENDIAN), /* avoid conversion; doesn't have endianess */ + [NFT_META_SECPATH] = META_TEMPLATE("ipsec", &boolean_type, + BITS_PER_BYTE, BYTEORDER_HOST_ENDIAN), + [NFT_META_IIFKIND] = META_TEMPLATE("iifkind", &ifname_type, + IFNAMSIZ * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_OIFKIND] = META_TEMPLATE("oifkind", &ifname_type, + IFNAMSIZ * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_BRI_IIFPVID] = META_TEMPLATE("ibrpvid", &integer_type, + 2 * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_BRI_IIFVPROTO] = META_TEMPLATE("ibrvproto", ðertype_type, + 2 * BITS_PER_BYTE, + BYTEORDER_BIG_ENDIAN), + [NFT_META_TIME_NS] = META_TEMPLATE("time", &date_type, + 8 * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_TIME_DAY] = META_TEMPLATE("day", &day_type, + 1 * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_TIME_HOUR] = META_TEMPLATE("hour", &hour_type, + 4 * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_SECMARK] = META_TEMPLATE("secmark", &integer_type, + 32, BYTEORDER_HOST_ENDIAN), + [NFT_META_SDIF] = META_TEMPLATE("sdif", &ifindex_type, + sizeof(int) * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_SDIFNAME] = META_TEMPLATE("sdifname", &ifname_type, + IFNAMSIZ * BITS_PER_BYTE, + BYTEORDER_HOST_ENDIAN), + [NFT_META_BRI_BROUTE] = META_TEMPLATE("broute", &integer_type, + 1 , BYTEORDER_HOST_ENDIAN), +}; + +static bool meta_key_is_unqualified(enum nft_meta_keys key) +{ + switch (key) { + case NFT_META_IIF: + case NFT_META_OIF: + case NFT_META_IIFNAME: + case NFT_META_OIFNAME: + case NFT_META_IIFGROUP: + case NFT_META_OIFGROUP: + return true; + default: + return false; + } +} + +static void meta_expr_print(const struct expr *expr, struct output_ctx *octx) +{ + const char *token = "unknown"; + uint32_t key = expr->meta.key; + + if (key < array_size(meta_templates)) + token = meta_templates[key].token; + + if (meta_key_is_unqualified(key)) + nft_print(octx, "%s", token); + else + nft_print(octx, "meta %s", token); +} + +static bool meta_expr_cmp(const struct expr *e1, const struct expr *e2) +{ + return e1->meta.key == e2->meta.key; +} + +static void meta_expr_clone(struct expr *new, const struct expr *expr) +{ + new->meta.key = expr->meta.key; + new->meta.base = expr->meta.base; + new->meta.inner_desc = expr->meta.inner_desc; +} + +/** + * meta_expr_pctx_update - update protocol context based on meta match + * + * @ctx: protocol context + * @expr: relational meta expression + * + * Update LL protocol context based on IIFTYPE meta match in non-LL hooks. + */ +static void meta_expr_pctx_update(struct proto_ctx *ctx, + const struct location *loc, + const struct expr *left, + const struct expr *right) +{ + const struct hook_proto_desc *h = &hook_proto_desc[ctx->family]; + const struct proto_desc *desc; + uint8_t protonum; + + switch (left->meta.key) { + case NFT_META_IIFTYPE: + if (h->base < PROTO_BASE_NETWORK_HDR && + ctx->family != NFPROTO_INET && + ctx->family != NFPROTO_NETDEV) + return; + + desc = proto_dev_desc(mpz_get_uint16(right->value)); + if (desc == NULL) + desc = &proto_unknown; + + proto_ctx_update(ctx, PROTO_BASE_LL_HDR, loc, desc); + break; + case NFT_META_NFPROTO: + protonum = mpz_get_uint8(right->value); + if (protonum == NFPROTO_IPV4 && h->desc == &proto_ip) + break; + else if (protonum == NFPROTO_IPV6 && h->desc == &proto_ip6) + break; + + desc = proto_find_upper(h->desc, protonum); + if (desc == NULL) { + desc = &proto_unknown; + + if (protonum == ctx->family && + h->base == PROTO_BASE_NETWORK_HDR) + desc = h->desc; + } + + proto_ctx_update(ctx, PROTO_BASE_NETWORK_HDR, loc, desc); + break; + case NFT_META_L4PROTO: + desc = proto_find_upper(&proto_inet_service, + mpz_get_uint8(right->value)); + if (desc == NULL) + desc = &proto_unknown; + + proto_ctx_update(ctx, PROTO_BASE_TRANSPORT_HDR, loc, desc); + break; + case NFT_META_PROTOCOL: + if (h->base != PROTO_BASE_LL_HDR) + return; + + if (ctx->family != NFPROTO_NETDEV && + ctx->family != NFPROTO_BRIDGE) + return; + + desc = proto_find_upper(h->desc, ntohs(mpz_get_uint16(right->value))); + if (desc == NULL) + desc = &proto_unknown; + + proto_ctx_update(ctx, PROTO_BASE_NETWORK_HDR, loc, desc); + break; + default: + break; + } +} + +#define NFTNL_UDATA_META_KEY 0 +#define NFTNL_UDATA_META_INNER_DESC 1 +#define NFTNL_UDATA_META_MAX 2 + +static int meta_expr_build_udata(struct nftnl_udata_buf *udbuf, + const struct expr *expr) +{ + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_META_KEY, expr->meta.key); + + if (expr->meta.inner_desc) { + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_META_INNER_DESC, + expr->meta.inner_desc->id); + } + + return 0; +} + +static int meta_parse_udata(const struct nftnl_udata *attr, void *data) +{ + const struct nftnl_udata **ud = data; + uint8_t type = nftnl_udata_type(attr); + uint8_t len = nftnl_udata_len(attr); + + switch (type) { + case NFTNL_UDATA_META_KEY: + case NFTNL_UDATA_META_INNER_DESC: + if (len != sizeof(uint32_t)) + return -1; + break; + default: + return 0; + } + + ud[type] = attr; + return 0; +} + +static struct expr *meta_expr_parse_udata(const struct nftnl_udata *attr) +{ + const struct nftnl_udata *ud[NFTNL_UDATA_META_MAX + 1] = {}; + const struct proto_desc *desc; + struct expr *expr; + uint32_t key; + int err; + + err = nftnl_udata_parse(nftnl_udata_get(attr), nftnl_udata_len(attr), + meta_parse_udata, ud); + if (err < 0) + return NULL; + + if (!ud[NFTNL_UDATA_META_KEY]) + return NULL; + + key = nftnl_udata_get_u32(ud[NFTNL_UDATA_META_KEY]); + + expr = meta_expr_alloc(&internal_location, key); + + if (ud[NFTNL_UDATA_META_INNER_DESC]) { + desc = find_proto_desc(ud[NFTNL_UDATA_META_INNER_DESC]); + expr->meta.inner_desc = desc; + } + + return expr; +} + +const struct expr_ops meta_expr_ops = { + .type = EXPR_META, + .name = "meta", + .print = meta_expr_print, + .json = meta_expr_json, + .cmp = meta_expr_cmp, + .clone = meta_expr_clone, + .pctx_update = meta_expr_pctx_update, + .build_udata = meta_expr_build_udata, + .parse_udata = meta_expr_parse_udata, +}; + +struct expr *meta_expr_alloc(const struct location *loc, enum nft_meta_keys key) +{ + const struct meta_template *tmpl = &meta_templates[key]; + struct expr *expr; + + expr = expr_alloc(loc, EXPR_META, tmpl->dtype, + tmpl->byteorder, tmpl->len); + expr->meta.key = key; + + switch (key) { + case NFT_META_IIFTYPE: + expr->flags |= EXPR_F_PROTOCOL; + break; + case NFT_META_NFPROTO: + expr->flags |= EXPR_F_PROTOCOL; + expr->meta.base = PROTO_BASE_LL_HDR; + break; + case NFT_META_L4PROTO: + expr->flags |= EXPR_F_PROTOCOL; + expr->meta.base = PROTO_BASE_NETWORK_HDR; + break; + case NFT_META_PROTOCOL: + expr->flags |= EXPR_F_PROTOCOL; + expr->meta.base = PROTO_BASE_LL_HDR; + break; + default: + break; + } + + return expr; +} + +static void meta_stmt_print(const struct stmt *stmt, struct output_ctx *octx) +{ + const char *token = "unknown"; + uint32_t key = stmt->meta.key; + + if (key < array_size(meta_templates)) + token = meta_templates[key].token; + + if (meta_key_is_unqualified(stmt->meta.key)) + nft_print(octx, "%s set ", token); + else + nft_print(octx, "meta %s set ", token); + + expr_print(stmt->meta.expr, octx); +} + +static void meta_stmt_destroy(struct stmt *stmt) +{ + expr_free(stmt->meta.expr); +} + +static const struct stmt_ops meta_stmt_ops = { + .type = STMT_META, + .name = "meta", + .print = meta_stmt_print, + .json = meta_stmt_json, + .destroy = meta_stmt_destroy, +}; + +struct stmt *meta_stmt_alloc(const struct location *loc, enum nft_meta_keys key, + struct expr *expr) +{ + struct stmt *stmt; + + stmt = stmt_alloc(loc, &meta_stmt_ops); + stmt->meta.key = key; + stmt->meta.expr = expr; + + if (key < array_size(meta_templates)) + stmt->meta.tmpl = &meta_templates[key]; + + return stmt; +} + +/* + * @expr: payload expression + * @res: dependency expression + * + * Generate a NFT_META_IIFTYPE expression to check for ethernet frames. + * Only works on input path. + */ +struct stmt *meta_stmt_meta_iiftype(const struct location *loc, uint16_t type) +{ + struct expr *dep, *left, *right; + + left = meta_expr_alloc(loc, NFT_META_IIFTYPE); + right = constant_expr_alloc(loc, &arphrd_type, + BYTEORDER_HOST_ENDIAN, + 2 * BITS_PER_BYTE, &type); + + dep = relational_expr_alloc(loc, OP_EQ, left, right); + return expr_stmt_alloc(&dep->location, dep); +} + +struct error_record *meta_key_parse(const struct location *loc, + const char *str, + unsigned int *value) +{ + const char *sep = ""; + size_t offset = 0; + unsigned int i; + char buf[1024]; + size_t len; + + for (i = 0; i < array_size(meta_templates); i++) { + if (!meta_templates[i].token || strcmp(meta_templates[i].token, str)) + continue; + + *value = i; + return NULL; + } + + /* Backwards compat hack */ + if (strcmp(str, "ibriport") == 0) { + *value = NFT_META_BRI_IIFNAME; + return NULL; + } else if (strcmp(str, "obriport") == 0) { + *value = NFT_META_BRI_OIFNAME; + return NULL; + } else if (strcmp(str, "secpath") == 0) { + *value = NFT_META_SECPATH; + return NULL; + } + + len = (int)sizeof(buf); + + for (i = 0; i < array_size(meta_templates); i++) { + int ret; + + if (!meta_templates[i].token) + continue; + + if (offset) + sep = ", "; + + ret = snprintf(buf+offset, len, "%s%s", sep, meta_templates[i].token); + SNPRINTF_BUFFER_SIZE(ret, &len, &offset); + assert(len > 0); + } + + return error(loc, "syntax error, unexpected %s, known keys are %s", str, buf); +} |