/* * Copyright (c) 2008 Patrick McHardy * Copyright (c) 2013 Pablo Neira Ayuso * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int netlink_parse_expr(const struct nftnl_expr *nle, struct netlink_parse_ctx *ctx); static void __fmtstring(3, 4) netlink_error(struct netlink_parse_ctx *ctx, const struct location *loc, const char *fmt, ...) { struct error_record *erec; va_list ap; va_start(ap, fmt); erec = erec_vcreate(EREC_ERROR, loc, fmt, ap); va_end(ap); erec_queue(erec, ctx->msgs); } static unsigned int netlink_parse_register(const struct nftnl_expr *nle, unsigned int attr) { unsigned int reg; reg = nftnl_expr_get_u32(nle, attr); /* Translate 128bit registers to corresponding 32bit registers */ if (reg >= NFT_REG_1 && reg <= NFT_REG_4) reg = 1 + (reg - NFT_REG_1) * (NFT_REG_SIZE / NFT_REG32_SIZE); else if (reg >= NFT_REG32_00) reg = 1 + reg - NFT_REG32_00; return reg; } static void netlink_set_register(struct netlink_parse_ctx *ctx, enum nft_registers reg, struct expr *expr) { if (reg == NFT_REG_VERDICT || reg > MAX_REGS) { netlink_error(ctx, &expr->location, "Invalid destination register %u", reg); expr_free(expr); return; } if (ctx->registers[reg] != NULL) expr_free(ctx->registers[reg]); ctx->registers[reg] = expr; } static struct expr *netlink_get_register(struct netlink_parse_ctx *ctx, const struct location *loc, enum nft_registers reg) { struct expr *expr; if (reg == NFT_REG_VERDICT || reg > MAX_REGS) { netlink_error(ctx, loc, "Invalid source register %u", reg); return NULL; } expr = ctx->registers[reg]; if (expr != NULL) expr = expr_clone(expr); return expr; } static void netlink_release_registers(struct netlink_parse_ctx *ctx) { int i; for (i = 0; i <= MAX_REGS; i++) expr_free(ctx->registers[i]); } static struct expr *netlink_parse_concat_expr(struct netlink_parse_ctx *ctx, const struct location *loc, unsigned int reg, unsigned int len) { struct expr *concat, *expr; unsigned int consumed; concat = concat_expr_alloc(loc); while (len > 0) { expr = netlink_get_register(ctx, loc, reg); if (expr == NULL) { netlink_error(ctx, loc, "Relational expression size mismatch"); goto err; } compound_expr_add(concat, expr); consumed = netlink_padded_len(expr->len); assert(consumed > 0); len -= consumed; reg += netlink_register_space(expr->len); } return concat; err: expr_free(concat); return NULL; } static struct expr *netlink_parse_concat_key(struct netlink_parse_ctx *ctx, const struct location *loc, unsigned int reg, const struct expr *key) { uint32_t type = key->dtype->type; unsigned int n, len = key->len; struct expr *concat, *expr; unsigned int consumed; concat = concat_expr_alloc(loc); n = div_round_up(fls(type), TYPE_BITS); while (len > 0) { const struct datatype *i; expr = netlink_get_register(ctx, loc, reg); if (expr == NULL) { netlink_error(ctx, loc, "Concat expression size mismatch"); goto err; } if (n > 0 && concat_subtype_id(type, --n)) { i = concat_subtype_lookup(type, n); expr_set_type(expr, i, i->byteorder); } compound_expr_add(concat, expr); consumed = netlink_padded_len(expr->len); assert(consumed > 0); len -= consumed; reg += netlink_register_space(expr->len); } return concat; err: expr_free(concat); return NULL; } static struct expr *netlink_parse_concat_data(struct netlink_parse_ctx *ctx, const struct location *loc, unsigned int reg, unsigned int len, struct expr *data) { struct expr *concat, *expr, *i; concat = concat_expr_alloc(loc); while (len > 0) { expr = netlink_get_register(ctx, loc, reg); if (expr == NULL) { netlink_error(ctx, loc, "Relational expression size mismatch"); goto err; } i = constant_expr_splice(data, expr->len); data->len -= netlink_padding_len(expr->len); compound_expr_add(concat, i); len -= netlink_padded_len(expr->len); reg += netlink_register_space(expr->len); } return concat; err: expr_free(concat); return NULL; } static void netlink_parse_chain_verdict(struct netlink_parse_ctx *ctx, const struct location *loc, struct expr *expr, enum nft_verdicts verdict) { char chain_name[NFT_CHAIN_MAXNAMELEN] = {}; struct chain *chain; expr_chain_export(expr->chain, chain_name); chain = chain_binding_lookup(ctx->table, chain_name); /* Special case: 'nft list chain x y' needs to pull in implicit chains */ if (!chain && !strncmp(chain_name, "__chain", strlen("__chain"))) { nft_chain_cache_update(ctx->nlctx, ctx->table, chain_name); chain = chain_binding_lookup(ctx->table, chain_name); } if (chain) { ctx->stmt = chain_stmt_alloc(loc, chain, verdict); expr_free(expr); } else { ctx->stmt = verdict_stmt_alloc(loc, expr); } } static void netlink_parse_immediate(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct nft_data_delinearize nld; enum nft_registers dreg; struct expr *expr; if (nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_VERDICT)) { nld.verdict = nftnl_expr_get_u32(nle, NFTNL_EXPR_IMM_VERDICT); if (nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_CHAIN)) { nld.chain = nftnl_expr_get(nle, NFTNL_EXPR_IMM_CHAIN, &nld.len); } } else if (nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_DATA)) { nld.value = nftnl_expr_get(nle, NFTNL_EXPR_IMM_DATA, &nld.len); } dreg = netlink_parse_register(nle, NFTNL_EXPR_IMM_DREG); expr = netlink_alloc_data(loc, &nld, dreg); if (dreg == NFT_REG_VERDICT) { switch (expr->verdict) { case NFT_JUMP: netlink_parse_chain_verdict(ctx, loc, expr, NFT_JUMP); break; case NFT_GOTO: netlink_parse_chain_verdict(ctx, loc, expr, NFT_GOTO); break; default: ctx->stmt = verdict_stmt_alloc(loc, expr); break; } } else { netlink_set_register(ctx, dreg, expr); } } static void netlink_parse_xfrm(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers dreg; enum nft_xfrm_keys key; struct expr *expr; uint32_t spnum; uint8_t dir; key = nftnl_expr_get_u32(nle, NFTNL_EXPR_XFRM_KEY); dir = nftnl_expr_get_u8(nle, NFTNL_EXPR_XFRM_DIR); spnum = nftnl_expr_get_u32(nle, NFTNL_EXPR_XFRM_SPNUM); expr = xfrm_expr_alloc(loc, dir, spnum, key); dreg = netlink_parse_register(nle, NFTNL_EXPR_XFRM_DREG); netlink_set_register(ctx, dreg, expr); } static enum ops netlink_parse_range_op(const struct nftnl_expr *nle) { switch (nftnl_expr_get_u32(nle, NFTNL_EXPR_RANGE_OP)) { case NFT_RANGE_EQ: return OP_EQ; case NFT_RANGE_NEQ: return OP_NEQ; default: return OP_INVALID; } } static void netlink_parse_range(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct expr *expr, *left, *right, *from, *to; struct nft_data_delinearize nld; enum nft_registers sreg; enum ops op; sreg = netlink_parse_register(nle, NFTNL_EXPR_RANGE_SREG); left = netlink_get_register(ctx, loc, sreg); if (left == NULL) return netlink_error(ctx, loc, "Relational expression has no left hand side"); op = netlink_parse_range_op(nle); nld.value = nftnl_expr_get(nle, NFTNL_EXPR_RANGE_FROM_DATA, &nld.len); from = netlink_alloc_value(loc, &nld); nld.value = nftnl_expr_get(nle, NFTNL_EXPR_RANGE_TO_DATA, &nld.len); to = netlink_alloc_value(loc, &nld); right = range_expr_alloc(loc, from, to); expr = relational_expr_alloc(loc, op, left, right); ctx->stmt = expr_stmt_alloc(loc, expr); } static enum ops netlink_parse_cmp_op(const struct nftnl_expr *nle) { switch (nftnl_expr_get_u32(nle, NFTNL_EXPR_CMP_OP)) { case NFT_CMP_EQ: return OP_EQ; case NFT_CMP_NEQ: return OP_NEQ; case NFT_CMP_LT: return OP_LT; case NFT_CMP_LTE: return OP_LTE; case NFT_CMP_GT: return OP_GT; case NFT_CMP_GTE: return OP_GTE; default: return OP_INVALID; } } static void netlink_parse_cmp(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct nft_data_delinearize nld; enum nft_registers sreg; struct expr *expr, *left, *right, *tmp; enum ops op; sreg = netlink_parse_register(nle, NFTNL_EXPR_CMP_SREG); left = netlink_get_register(ctx, loc, sreg); if (left == NULL) return netlink_error(ctx, loc, "Relational expression has no left " "hand side"); op = netlink_parse_cmp_op(nle); nld.value = nftnl_expr_get(nle, NFTNL_EXPR_CMP_DATA, &nld.len); right = netlink_alloc_value(loc, &nld); if (left->len > right->len && expr_basetype(left) != &string_type) { mpz_lshift_ui(right->value, left->len - right->len); right = prefix_expr_alloc(loc, right, right->len); right->prefix->len = left->len; } else if (left->len > 0 && left->len < right->len) { expr_free(left); left = netlink_parse_concat_expr(ctx, loc, sreg, right->len); if (left == NULL) goto err_free; tmp = netlink_parse_concat_data(ctx, loc, sreg, right->len, right); if (tmp == NULL) goto err_free; expr_free(right); right = tmp; } expr = relational_expr_alloc(loc, op, left, right); ctx->stmt = expr_stmt_alloc(loc, expr); return; err_free: expr_free(left); expr_free(right); } static void netlink_parse_lookup(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers sreg, dreg; const char *name; struct expr *expr, *left, *right; struct set *set; uint32_t flag; name = nftnl_expr_get_str(nle, NFTNL_EXPR_LOOKUP_SET); set = set_cache_find(ctx->table, name); if (set == NULL) return netlink_error(ctx, loc, "Unknown set '%s' in lookup expression", name); sreg = netlink_parse_register(nle, NFTNL_EXPR_LOOKUP_SREG); left = netlink_get_register(ctx, loc, sreg); if (left == NULL) return netlink_error(ctx, loc, "Lookup expression has no left hand side"); if (left->len < set->key->len) { expr_free(left); left = netlink_parse_concat_expr(ctx, loc, sreg, set->key->len); if (left == NULL) return; } right = set_ref_expr_alloc(loc, set); if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOOKUP_DREG)) { dreg = netlink_parse_register(nle, NFTNL_EXPR_LOOKUP_DREG); expr = map_expr_alloc(loc, left, right); if (dreg != NFT_REG_VERDICT) return netlink_set_register(ctx, dreg, expr); } else { expr = relational_expr_alloc(loc, OP_EQ, left, right); } if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOOKUP_FLAGS)) { flag = nftnl_expr_get_u32(nle, NFTNL_EXPR_LOOKUP_FLAGS); if (flag & NFT_LOOKUP_F_INV) expr->op = OP_NEQ; } ctx->stmt = expr_stmt_alloc(loc, expr); } static struct expr *netlink_parse_bitwise_bool(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle, enum nft_registers sreg, struct expr *left) { struct nft_data_delinearize nld; struct expr *expr, *mask, *xor, *or; mpz_t m, x, o; expr = left; nld.value = nftnl_expr_get(nle, NFTNL_EXPR_BITWISE_MASK, &nld.len); mask = netlink_alloc_value(loc, &nld); mpz_init_set(m, mask->value); nld.value = nftnl_expr_get(nle, NFTNL_EXPR_BITWISE_XOR, &nld.len); xor = netlink_alloc_value(loc, &nld); mpz_init_set(x, xor->value); mpz_init_set_ui(o, 0); if (mpz_scan0(m, 0) != mask->len || mpz_cmp_ui(x, 0)) { /* o = (m & x) ^ x */ mpz_and(o, m, x); mpz_xor(o, o, x); /* x &= m */ mpz_and(x, x, m); /* m |= o */ mpz_ior(m, m, o); } if (left->len > 0 && mpz_scan0(m, 0) == left->len) { /* mask encompasses the entire value */ expr_free(mask); } else { mpz_set(mask->value, m); expr = binop_expr_alloc(loc, OP_AND, expr, mask); expr->len = left->len; } if (mpz_cmp_ui(x, 0)) { mpz_set(xor->value, x); expr = binop_expr_alloc(loc, OP_XOR, expr, xor); expr->len = left->len; } else expr_free(xor); if (mpz_cmp_ui(o, 0)) { nld.value = nftnl_expr_get(nle, NFTNL_EXPR_BITWISE_XOR, &nld.len); or = netlink_alloc_value(loc, &nld); mpz_set(or->value, o); expr = binop_expr_alloc(loc, OP_OR, expr, or); expr->len = left->len; } mpz_clear(m); mpz_clear(x); mpz_clear(o); return expr; } static struct expr *netlink_parse_bitwise_shift(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle, enum ops op, enum nft_registers sreg, struct expr *left) { struct nft_data_delinearize nld; struct expr *expr, *right; nld.value = nftnl_expr_get(nle, NFTNL_EXPR_BITWISE_DATA, &nld.len); right = netlink_alloc_value(loc, &nld); right->byteorder = BYTEORDER_HOST_ENDIAN; expr = binop_expr_alloc(loc, op, left, right); expr->len = left->len; return expr; } static void netlink_parse_bitwise(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers sreg, dreg; struct expr *expr, *left; enum nft_bitwise_ops op; sreg = netlink_parse_register(nle, NFTNL_EXPR_BITWISE_SREG); left = netlink_get_register(ctx, loc, sreg); if (left == NULL) return netlink_error(ctx, loc, "Bitwise expression has no left " "hand side"); op = nftnl_expr_get_u32(nle, NFTNL_EXPR_BITWISE_OP); switch (op) { case NFT_BITWISE_BOOL: expr = netlink_parse_bitwise_bool(ctx, loc, nle, sreg, left); break; case NFT_BITWISE_LSHIFT: expr = netlink_parse_bitwise_shift(ctx, loc, nle, OP_LSHIFT, sreg, left); break; case NFT_BITWISE_RSHIFT: expr = netlink_parse_bitwise_shift(ctx, loc, nle, OP_RSHIFT, sreg, left); break; default: BUG("invalid bitwise operation %u\n", op); } dreg = netlink_parse_register(nle, NFTNL_EXPR_BITWISE_DREG); netlink_set_register(ctx, dreg, expr); } static void netlink_parse_byteorder(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers sreg, dreg; struct expr *expr, *arg; enum ops op; sreg = netlink_parse_register(nle, NFTNL_EXPR_BYTEORDER_SREG); arg = netlink_get_register(ctx, loc, sreg); if (arg == NULL) return netlink_error(ctx, loc, "Byteorder expression has no left " "hand side"); switch (nftnl_expr_get_u32(nle, NFTNL_EXPR_BYTEORDER_OP)) { case NFT_BYTEORDER_NTOH: op = OP_NTOH; break; case NFT_BYTEORDER_HTON: op = OP_HTON; break; default: BUG("invalid byteorder operation %u\n", nftnl_expr_get_u32(nle, NFTNL_EXPR_BYTEORDER_OP)); } expr = unary_expr_alloc(loc, op, arg); expr->len = arg->len; dreg = netlink_parse_register(nle, NFTNL_EXPR_BYTEORDER_DREG); netlink_set_register(ctx, dreg, expr); } static void netlink_parse_payload_expr(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers dreg; uint32_t base, offset, len; struct expr *expr; base = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_BASE) + 1; offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_OFFSET) * BITS_PER_BYTE; len = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_LEN) * BITS_PER_BYTE; expr = payload_expr_alloc(loc, NULL, 0); payload_init_raw(expr, base, offset, len); dreg = netlink_parse_register(nle, NFTNL_EXPR_PAYLOAD_DREG); netlink_set_register(ctx, dreg, expr); } static void netlink_parse_payload_stmt(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers sreg; uint32_t base, offset, len; struct expr *expr, *val; struct stmt *stmt; base = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_BASE) + 1; offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_OFFSET) * BITS_PER_BYTE; len = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_LEN) * BITS_PER_BYTE; sreg = netlink_parse_register(nle, NFTNL_EXPR_PAYLOAD_SREG); val = netlink_get_register(ctx, loc, sreg); if (val == NULL) return netlink_error(ctx, loc, "payload statement has no expression"); expr = payload_expr_alloc(loc, NULL, 0); payload_init_raw(expr, base, offset, len); stmt = payload_stmt_alloc(loc, expr, val); rule_stmt_append(ctx->rule, stmt); } static void netlink_parse_payload(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { if (nftnl_expr_is_set(nle, NFTNL_EXPR_PAYLOAD_DREG)) netlink_parse_payload_expr(ctx, loc, nle); else netlink_parse_payload_stmt(ctx, loc, nle); } static void netlink_parse_exthdr(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { uint32_t offset, len, flags; enum nft_registers dreg; enum nft_exthdr_op op; uint8_t type; struct expr *expr; type = nftnl_expr_get_u8(nle, NFTNL_EXPR_EXTHDR_TYPE); offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET) * BITS_PER_BYTE; len = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_LEN) * BITS_PER_BYTE; op = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_OP); flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_FLAGS); expr = exthdr_expr_alloc(loc, NULL, 0); exthdr_init_raw(expr, type, offset, len, op, flags); if (nftnl_expr_is_set(nle, NFTNL_EXPR_EXTHDR_DREG)) { dreg = netlink_parse_register(nle, NFTNL_EXPR_EXTHDR_DREG); netlink_set_register(ctx, dreg, expr); } else if (nftnl_expr_is_set(nle, NFTNL_EXPR_EXTHDR_SREG)) { enum nft_registers sreg; struct stmt *stmt; struct expr *val; sreg = netlink_parse_register(nle, NFTNL_EXPR_EXTHDR_SREG); val = netlink_get_register(ctx, loc, sreg); if (val == NULL) { expr_free(expr); return netlink_error(ctx, loc, "exthdr statement has no expression"); } expr_set_type(val, expr->dtype, expr->byteorder); stmt = exthdr_stmt_alloc(loc, expr, val); rule_stmt_append(ctx->rule, stmt); } else { struct stmt *stmt = optstrip_stmt_alloc(loc, expr); rule_stmt_append(ctx->rule, stmt); } } static void netlink_parse_hash(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers sreg, dreg; struct expr *expr, *hexpr; uint32_t mod, seed, len, offset; enum nft_hash_types type; bool seed_set; type = nftnl_expr_get_u32(nle, NFTNL_EXPR_HASH_TYPE); offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_HASH_OFFSET); seed_set = nftnl_expr_is_set(nle, NFTNL_EXPR_HASH_SEED); seed = nftnl_expr_get_u32(nle, NFTNL_EXPR_HASH_SEED); mod = nftnl_expr_get_u32(nle, NFTNL_EXPR_HASH_MODULUS); expr = hash_expr_alloc(loc, mod, seed_set, seed, offset, type); if (type != NFT_HASH_SYM) { sreg = netlink_parse_register(nle, NFTNL_EXPR_HASH_SREG); hexpr = netlink_get_register(ctx, loc, sreg); if (hexpr == NULL) { netlink_error(ctx, loc, "hash statement has no expression"); goto out_err; } len = nftnl_expr_get_u32(nle, NFTNL_EXPR_HASH_LEN) * BITS_PER_BYTE; if (hexpr->len < len) { expr_free(hexpr); hexpr = netlink_parse_concat_expr(ctx, loc, sreg, len); if (hexpr == NULL) goto out_err; } expr->hash.expr = hexpr; } dreg = netlink_parse_register(nle, NFTNL_EXPR_HASH_DREG); netlink_set_register(ctx, dreg, expr); return; out_err: expr_free(expr); } static void netlink_parse_fib(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers dreg; struct expr *expr; uint32_t flags, result; flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_FIB_FLAGS); result = nftnl_expr_get_u32(nle, NFTNL_EXPR_FIB_RESULT); expr = fib_expr_alloc(loc, flags, result); dreg = netlink_parse_register(nle, NFTNL_EXPR_FIB_DREG); netlink_set_register(ctx, dreg, expr); } static void netlink_parse_meta_expr(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers dreg; uint32_t key; struct expr *expr; key = nftnl_expr_get_u32(nle, NFTNL_EXPR_META_KEY); expr = meta_expr_alloc(loc, key); dreg = netlink_parse_register(nle, NFTNL_EXPR_META_DREG); netlink_set_register(ctx, dreg, expr); } static void netlink_parse_socket(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers dreg; uint32_t key, level; struct expr * expr; key = nftnl_expr_get_u32(nle, NFTNL_EXPR_SOCKET_KEY); level = nftnl_expr_get_u32(nle, NFTNL_EXPR_SOCKET_LEVEL); expr = socket_expr_alloc(loc, key, level); dreg = netlink_parse_register(nle, NFTNL_EXPR_SOCKET_DREG); netlink_set_register(ctx, dreg, expr); } static void netlink_parse_osf(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers dreg; struct expr *expr; uint32_t flags; uint8_t ttl; ttl = nftnl_expr_get_u8(nle, NFTNL_EXPR_OSF_TTL); flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_OSF_FLAGS); expr = osf_expr_alloc(loc, ttl, flags); dreg = netlink_parse_register(nle, NFTNL_EXPR_OSF_DREG); netlink_set_register(ctx, dreg, expr); } static void netlink_parse_meta_stmt(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers sreg; uint32_t key; struct stmt *stmt; struct expr *expr; sreg = netlink_parse_register(nle, NFTNL_EXPR_META_SREG); expr = netlink_get_register(ctx, loc, sreg); if (expr == NULL) return netlink_error(ctx, loc, "meta statement has no expression"); key = nftnl_expr_get_u32(nle, NFTNL_EXPR_META_KEY); stmt = meta_stmt_alloc(loc, key, expr); expr_set_type(expr, stmt->meta.tmpl->dtype, stmt->meta.tmpl->byteorder); ctx->stmt = stmt; } static void netlink_parse_meta(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { if (nftnl_expr_is_set(nle, NFTNL_EXPR_META_DREG)) netlink_parse_meta_expr(ctx, loc, nle); else netlink_parse_meta_stmt(ctx, loc, nle); } static void netlink_parse_rt(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers dreg; uint32_t key; struct expr *expr; key = nftnl_expr_get_u32(nle, NFTNL_EXPR_RT_KEY); expr = rt_expr_alloc(loc, key, false); dreg = netlink_parse_register(nle, NFTNL_EXPR_RT_DREG); netlink_set_register(ctx, dreg, expr); } static void netlink_parse_numgen(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { uint32_t type, until, offset; enum nft_registers dreg; struct expr *expr; type = nftnl_expr_get_u32(nle, NFTNL_EXPR_NG_TYPE); until = nftnl_expr_get_u32(nle, NFTNL_EXPR_NG_MODULUS); offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_NG_OFFSET); expr = numgen_expr_alloc(loc, type, until, offset); dreg = netlink_parse_register(nle, NFTNL_EXPR_NG_DREG); netlink_set_register(ctx, dreg, expr); } static void netlink_parse_notrack(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { ctx->stmt = notrack_stmt_alloc(loc); } static void netlink_parse_flow_offload(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { const char *table_name; table_name = xstrdup(nftnl_expr_get_str(nle, NFTNL_EXPR_FLOW_TABLE_NAME)); ctx->stmt = flow_offload_stmt_alloc(loc, table_name); } static void netlink_parse_ct_stmt(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers sreg; uint32_t key; struct stmt *stmt; struct expr *expr; int8_t dir = -1; sreg = netlink_parse_register(nle, NFTNL_EXPR_CT_SREG); expr = netlink_get_register(ctx, loc, sreg); if (expr == NULL) return netlink_error(ctx, loc, "ct statement has no expression"); if (nftnl_expr_is_set(nle, NFTNL_EXPR_CT_DIR)) dir = nftnl_expr_get_u8(nle, NFTNL_EXPR_CT_DIR); key = nftnl_expr_get_u32(nle, NFTNL_EXPR_CT_KEY); stmt = ct_stmt_alloc(loc, key, dir, expr); expr_set_type(expr, stmt->ct.tmpl->dtype, stmt->ct.tmpl->byteorder); ctx->stmt = stmt; } static void netlink_parse_ct_expr(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct expr *expr = NULL; enum nft_registers dreg; int8_t dir = -1; uint32_t key; if (nftnl_expr_is_set(nle, NFTNL_EXPR_CT_DIR)) dir = nftnl_expr_get_u8(nle, NFTNL_EXPR_CT_DIR); key = nftnl_expr_get_u32(nle, NFTNL_EXPR_CT_KEY); expr = ct_expr_alloc(loc, key, dir); dreg = netlink_parse_register(nle, NFTNL_EXPR_CT_DREG); netlink_set_register(ctx, dreg, expr); } static void netlink_parse_ct(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { if (nftnl_expr_is_set(nle, NFTNL_EXPR_CT_DREG)) netlink_parse_ct_expr(ctx, loc, nle); else netlink_parse_ct_stmt(ctx, loc, nle); } static void netlink_parse_connlimit(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct stmt *stmt; stmt = connlimit_stmt_alloc(loc); stmt->connlimit.count = nftnl_expr_get_u32(nle, NFTNL_EXPR_CONNLIMIT_COUNT); stmt->connlimit.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_CONNLIMIT_FLAGS); ctx->stmt = stmt; } static void netlink_parse_counter(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct stmt *stmt; stmt = counter_stmt_alloc(loc); stmt->counter.packets = nftnl_expr_get_u64(nle, NFTNL_EXPR_CTR_PACKETS); stmt->counter.bytes = nftnl_expr_get_u64(nle, NFTNL_EXPR_CTR_BYTES); ctx->stmt = stmt; } static void netlink_parse_log(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct stmt *stmt; const char *prefix; stmt = log_stmt_alloc(loc); prefix = nftnl_expr_get_str(nle, NFTNL_EXPR_LOG_PREFIX); if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_PREFIX)) { stmt->log.prefix = constant_expr_alloc(&internal_location, &string_type, BYTEORDER_HOST_ENDIAN, (strlen(prefix) + 1) * BITS_PER_BYTE, prefix); stmt->log.flags |= STMT_LOG_PREFIX; } if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_GROUP)) { stmt->log.group = nftnl_expr_get_u16(nle, NFTNL_EXPR_LOG_GROUP); stmt->log.flags |= STMT_LOG_GROUP; } if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_SNAPLEN)) { stmt->log.snaplen = nftnl_expr_get_u32(nle, NFTNL_EXPR_LOG_SNAPLEN); stmt->log.flags |= STMT_LOG_SNAPLEN; } if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_QTHRESHOLD)) { stmt->log.qthreshold = nftnl_expr_get_u16(nle, NFTNL_EXPR_LOG_QTHRESHOLD); stmt->log.flags |= STMT_LOG_QTHRESHOLD; } if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_LEVEL)) { stmt->log.level = nftnl_expr_get_u32(nle, NFTNL_EXPR_LOG_LEVEL); stmt->log.flags |= STMT_LOG_LEVEL; } if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_FLAGS)) { stmt->log.logflags = nftnl_expr_get_u32(nle, NFTNL_EXPR_LOG_FLAGS); } ctx->stmt = stmt; } static void netlink_parse_limit(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct stmt *stmt; stmt = limit_stmt_alloc(loc); stmt->limit.rate = nftnl_expr_get_u64(nle, NFTNL_EXPR_LIMIT_RATE); stmt->limit.unit = nftnl_expr_get_u64(nle, NFTNL_EXPR_LIMIT_UNIT); stmt->limit.type = nftnl_expr_get_u32(nle, NFTNL_EXPR_LIMIT_TYPE); stmt->limit.burst = nftnl_expr_get_u32(nle, NFTNL_EXPR_LIMIT_BURST); stmt->limit.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_LIMIT_FLAGS); ctx->stmt = stmt; } static void netlink_parse_quota(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct stmt *stmt; stmt = quota_stmt_alloc(loc); stmt->quota.bytes = nftnl_expr_get_u64(nle, NFTNL_EXPR_QUOTA_BYTES); stmt->quota.used = nftnl_expr_get_u64(nle, NFTNL_EXPR_QUOTA_CONSUMED); stmt->quota.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_QUOTA_FLAGS); ctx->stmt = stmt; } static void netlink_parse_reject(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *expr) { struct stmt *stmt; uint8_t icmp_code; stmt = reject_stmt_alloc(loc); stmt->reject.type = nftnl_expr_get_u32(expr, NFTNL_EXPR_REJECT_TYPE); icmp_code = nftnl_expr_get_u8(expr, NFTNL_EXPR_REJECT_CODE); stmt->reject.icmp_code = icmp_code; stmt->reject.expr = constant_expr_alloc(loc, &integer_type, BYTEORDER_HOST_ENDIAN, 8, &icmp_code); ctx->stmt = stmt; } static bool is_nat_addr_map(const struct expr *addr, uint8_t family, struct stmt *stmt) { const struct expr *mappings, *data; const struct set *set; if (!addr || expr_ops(addr)->type != EXPR_MAP) return false; mappings = addr->right; if (expr_ops(mappings)->type != EXPR_SET_REF) return false; set = mappings->set; data = set->data; if (!(data->flags & EXPR_F_INTERVAL)) return false; stmt->nat.family = family; /* if we're dealing with an address:address map, * the length will be bit_sizeof(addr) + 32 (one register). */ switch (family) { case NFPROTO_IPV4: if (data->len == 32 + 32) { stmt->nat.type_flags |= STMT_NAT_F_INTERVAL; return true; } else if (data->len == 32 + 32 + 32 + 32) { stmt->nat.type_flags |= STMT_NAT_F_INTERVAL | STMT_NAT_F_CONCAT; return true; } break; case NFPROTO_IPV6: if (data->len == 128 + 128) { stmt->nat.type_flags |= STMT_NAT_F_INTERVAL; return true; } else if (data->len == 128 + 32 + 128 + 32) { stmt->nat.type_flags |= STMT_NAT_F_INTERVAL | STMT_NAT_F_CONCAT; return true; } } return false; } static bool is_nat_proto_map(const struct expr *addr, uint8_t family) { const struct expr *mappings, *data; const struct set *set; if (!addr || expr_ops(addr)->type != EXPR_MAP) return false; mappings = addr->right; if (expr_ops(mappings)->type != EXPR_SET_REF) return false; set = mappings->set; data = set->data; /* if we're dealing with an address:inet_service map, * the length will be bit_sizeof(addr) + 32 (one register). */ switch (family) { case NFPROTO_IPV4: return data->len == 32 + 32; case NFPROTO_IPV6: return data->len == 128 + 32; } return false; } static void netlink_parse_nat(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct stmt *stmt; struct expr *addr, *proto; enum nft_registers reg1, reg2; int family; stmt = nat_stmt_alloc(loc, nftnl_expr_get_u32(nle, NFTNL_EXPR_NAT_TYPE)); family = nftnl_expr_get_u32(nle, NFTNL_EXPR_NAT_FAMILY); if (ctx->table->handle.family == NFPROTO_INET) stmt->nat.family = family; if (nftnl_expr_is_set(nle, NFTNL_EXPR_NAT_FLAGS)) stmt->nat.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_NAT_FLAGS); if (stmt->nat.flags & NF_NAT_RANGE_NETMAP) stmt->nat.type_flags |= STMT_NAT_F_PREFIX; addr = NULL; reg1 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MIN); if (reg1) { addr = netlink_get_register(ctx, loc, reg1); if (addr == NULL) { netlink_error(ctx, loc, "NAT statement has no address expression"); goto out_err; } if (family == NFPROTO_IPV4) expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN); else expr_set_type(addr, &ip6addr_type, BYTEORDER_BIG_ENDIAN); stmt->nat.addr = addr; } if (is_nat_addr_map(addr, family, stmt)) { stmt->nat.family = family; ctx->stmt = stmt; return; } reg2 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MAX); if (reg2 && reg2 != reg1) { addr = netlink_get_register(ctx, loc, reg2); if (addr == NULL) { netlink_error(ctx, loc, "NAT statement has no address expression"); goto out_err; } if (family == NFPROTO_IPV4) expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN); else expr_set_type(addr, &ip6addr_type, BYTEORDER_BIG_ENDIAN); if (stmt->nat.addr != NULL) { addr = range_expr_alloc(loc, stmt->nat.addr, addr); addr = range_expr_to_prefix(addr); } stmt->nat.addr = addr; } if (is_nat_proto_map(addr, family)) { stmt->nat.family = family; stmt->nat.type_flags |= STMT_NAT_F_CONCAT; ctx->stmt = stmt; return; } reg1 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_PROTO_MIN); if (reg1) { proto = netlink_get_register(ctx, loc, reg1); if (proto == NULL) { netlink_error(ctx, loc, "NAT statement has no proto expression"); goto out_err; } expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN); stmt->nat.proto = proto; } reg2 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_PROTO_MAX); if (reg2 && reg2 != reg1) { proto = netlink_get_register(ctx, loc, reg2); if (proto == NULL) { netlink_error(ctx, loc, "NAT statement has no proto expression"); goto out_err; } expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN); if (stmt->nat.proto != NULL) proto = range_expr_alloc(loc, stmt->nat.proto, proto); stmt->nat.proto = proto; } ctx->stmt = stmt; return; out_err: stmt_free(stmt); } static void netlink_parse_synproxy(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct stmt *stmt; stmt = synproxy_stmt_alloc(loc); stmt->synproxy.mss = nftnl_expr_get_u16(nle, NFTNL_EXPR_SYNPROXY_MSS); stmt->synproxy.wscale = nftnl_expr_get_u8(nle, NFTNL_EXPR_SYNPROXY_WSCALE); stmt->synproxy.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_SYNPROXY_FLAGS); ctx->stmt = stmt; } static void netlink_parse_tproxy(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct stmt *stmt; struct expr *addr, *port; enum nft_registers reg; stmt = tproxy_stmt_alloc(loc); stmt->tproxy.family = nftnl_expr_get_u32(nle, NFTNL_EXPR_TPROXY_FAMILY); stmt->tproxy.table_family = ctx->table->handle.family; reg = netlink_parse_register(nle, NFTNL_EXPR_TPROXY_REG_ADDR); if (reg) { addr = netlink_get_register(ctx, loc, reg); if (addr == NULL) goto err; switch (stmt->tproxy.family) { case NFPROTO_IPV4: expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN); break; case NFPROTO_IPV6: expr_set_type(addr, &ip6addr_type, BYTEORDER_BIG_ENDIAN); break; default: netlink_error(ctx, loc, "tproxy address must be IPv4 or IPv6"); goto err; } stmt->tproxy.addr = addr; } reg = netlink_parse_register(nle, NFTNL_EXPR_TPROXY_REG_PORT); if (reg) { port = netlink_get_register(ctx, loc, reg); if (port == NULL) goto err; expr_set_type(port, &inet_service_type, BYTEORDER_BIG_ENDIAN); stmt->tproxy.port = port; } ctx->stmt = stmt; return; err: stmt_free(stmt); } static void netlink_parse_masq(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers reg1, reg2; struct expr *proto; struct stmt *stmt; uint32_t flags = 0; if (nftnl_expr_is_set(nle, NFTNL_EXPR_MASQ_FLAGS)) flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_MASQ_FLAGS); stmt = nat_stmt_alloc(loc, NFT_NAT_MASQ); stmt->nat.flags = flags; reg1 = netlink_parse_register(nle, NFTNL_EXPR_MASQ_REG_PROTO_MIN); if (reg1) { proto = netlink_get_register(ctx, loc, reg1); if (proto == NULL) { netlink_error(ctx, loc, "MASQUERADE statement has no proto expression"); goto out_err; } expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN); stmt->nat.proto = proto; } reg2 = netlink_parse_register(nle, NFTNL_EXPR_MASQ_REG_PROTO_MAX); if (reg2 && reg2 != reg1) { proto = netlink_get_register(ctx, loc, reg2); if (proto == NULL) { netlink_error(ctx, loc, "MASQUERADE statement has no proto expression"); goto out_err; } expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN); if (stmt->nat.proto != NULL) proto = range_expr_alloc(loc, stmt->nat.proto, proto); stmt->nat.proto = proto; } ctx->stmt = stmt; return; out_err: stmt_free(stmt); } static void netlink_parse_redir(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct stmt *stmt; struct expr *proto; enum nft_registers reg1, reg2; uint32_t flags; stmt = nat_stmt_alloc(loc, NFT_NAT_REDIR); if (nftnl_expr_is_set(nle, NFTNL_EXPR_REDIR_FLAGS)) { flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_REDIR_FLAGS); stmt->nat.flags = flags; } reg1 = netlink_parse_register(nle, NFTNL_EXPR_REDIR_REG_PROTO_MIN); if (reg1) { proto = netlink_get_register(ctx, loc, reg1); if (proto == NULL) { netlink_error(ctx, loc, "redirect statement has no proto expression"); goto out_err; } expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN); stmt->nat.proto = proto; } reg2 = netlink_parse_register(nle, NFTNL_EXPR_REDIR_REG_PROTO_MAX); if (reg2 && reg2 != reg1) { proto = netlink_get_register(ctx, loc, reg2); if (proto == NULL) { netlink_error(ctx, loc, "redirect statement has no proto expression"); goto out_err; } expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN); if (stmt->nat.proto != NULL) proto = range_expr_alloc(loc, stmt->nat.proto, proto); stmt->nat.proto = proto; } ctx->stmt = stmt; return; out_err: stmt_free(stmt); } static void netlink_parse_dup(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers reg1, reg2; struct expr *addr, *dev; struct stmt *stmt; stmt = dup_stmt_alloc(loc); reg1 = netlink_parse_register(nle, NFTNL_EXPR_DUP_SREG_ADDR); if (reg1) { addr = netlink_get_register(ctx, loc, reg1); if (addr == NULL) { netlink_error(ctx, loc, "DUP statement has no destination expression"); goto out_err; } switch (ctx->table->handle.family) { case NFPROTO_IPV4: expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN); break; case NFPROTO_IPV6: expr_set_type(addr, &ip6addr_type, BYTEORDER_BIG_ENDIAN); break; } stmt->dup.to = addr; } reg2 = netlink_parse_register(nle, NFTNL_EXPR_DUP_SREG_DEV); if (reg2) { dev = netlink_get_register(ctx, loc, reg2); if (dev == NULL) { netlink_error(ctx, loc, "DUP statement has no output expression"); goto out_err; } expr_set_type(dev, &ifindex_type, BYTEORDER_HOST_ENDIAN); if (stmt->dup.to == NULL) stmt->dup.to = dev; else stmt->dup.dev = dev; } ctx->stmt = stmt; return; out_err: stmt_free(stmt); } static void netlink_parse_fwd(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { enum nft_registers reg1, reg2; struct expr *dev, *addr; struct stmt *stmt; stmt = fwd_stmt_alloc(loc); reg1 = netlink_parse_register(nle, NFTNL_EXPR_FWD_SREG_DEV); if (reg1) { dev = netlink_get_register(ctx, loc, reg1); if (dev == NULL) { netlink_error(ctx, loc, "fwd statement has no output expression"); goto out_err; } expr_set_type(dev, &ifindex_type, BYTEORDER_HOST_ENDIAN); stmt->fwd.dev = dev; } if (nftnl_expr_is_set(nle, NFTNL_EXPR_FWD_NFPROTO)) { stmt->fwd.family = nftnl_expr_get_u32(nle, NFTNL_EXPR_FWD_NFPROTO); } if (nftnl_expr_is_set(nle, NFTNL_EXPR_FWD_SREG_ADDR)) { reg2 = netlink_parse_register(nle, NFTNL_EXPR_FWD_SREG_ADDR); if (reg2) { addr = netlink_get_register(ctx, loc, reg2); if (addr == NULL) { netlink_error(ctx, loc, "fwd statement has no output expression"); goto out_err; } switch (stmt->fwd.family) { case AF_INET: expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN); break; case AF_INET6: expr_set_type(addr, &ip6addr_type, BYTEORDER_BIG_ENDIAN); break; default: netlink_error(ctx, loc, "fwd statement has no family"); goto out_err; } stmt->fwd.addr = addr; } } ctx->stmt = stmt; return; out_err: stmt_free(stmt); } static void netlink_parse_queue(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct expr *expr; uint16_t flags; if (nftnl_expr_is_set(nle, NFTNL_EXPR_QUEUE_SREG_QNUM)) { enum nft_registers reg = netlink_parse_register(nle, NFTNL_EXPR_QUEUE_SREG_QNUM); expr = netlink_get_register(ctx, loc, reg); if (!expr) { netlink_error(ctx, loc, "queue statement has no sreg expression"); return; } } else { uint16_t total = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_TOTAL); uint16_t num = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_NUM); expr = constant_expr_alloc(loc, &integer_type, BYTEORDER_HOST_ENDIAN, 16, &num); if (total > 1) { struct expr *high; total += num - 1; high = constant_expr_alloc(loc, &integer_type, BYTEORDER_HOST_ENDIAN, 16, &total); expr = range_expr_alloc(loc, expr, high); } } flags = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_FLAGS); ctx->stmt = queue_stmt_alloc(loc, expr, flags); } struct dynset_parse_ctx { struct netlink_parse_ctx *nlctx; const struct location *loc; struct list_head stmt_list; }; static int dynset_parse_expressions(struct nftnl_expr *e, void *data) { struct dynset_parse_ctx *dynset_parse_ctx = data; struct netlink_parse_ctx *ctx = dynset_parse_ctx->nlctx; const struct location *loc = dynset_parse_ctx->loc; struct stmt *stmt; if (netlink_parse_expr(e, ctx) < 0 || !ctx->stmt) { netlink_error(ctx, loc, "Could not parse dynset stmt"); return -1; } stmt = ctx->stmt; list_add_tail(&stmt->list, &dynset_parse_ctx->stmt_list); return 0; } static void netlink_parse_dynset(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { struct dynset_parse_ctx dynset_parse_ctx = { .nlctx = ctx, .loc = loc, }; struct expr *expr, *expr_data = NULL; enum nft_registers sreg, sreg_data; struct stmt *stmt, *dstmt, *next; const struct nftnl_expr *dnle; struct set *set; const char *name; init_list_head(&dynset_parse_ctx.stmt_list); name = nftnl_expr_get_str(nle, NFTNL_EXPR_DYNSET_SET_NAME); set = set_cache_find(ctx->table, name); if (set == NULL) return netlink_error(ctx, loc, "Unknown set '%s' in dynset statement", name); sreg = netlink_parse_register(nle, NFTNL_EXPR_DYNSET_SREG_KEY); expr = netlink_get_register(ctx, loc, sreg); if (expr == NULL) return netlink_error(ctx, loc, "Dynset statement has no key expression"); if (expr->len < set->key->len) { expr_free(expr); expr = netlink_parse_concat_key(ctx, loc, sreg, set->key); if (expr == NULL) return; } expr = set_elem_expr_alloc(&expr->location, expr); expr->timeout = nftnl_expr_get_u64(nle, NFTNL_EXPR_DYNSET_TIMEOUT); if (nftnl_expr_is_set(nle, NFTNL_EXPR_DYNSET_EXPR)) { dstmt = NULL; dnle = nftnl_expr_get(nle, NFTNL_EXPR_DYNSET_EXPR, NULL); if (dnle != NULL) { if (netlink_parse_expr(dnle, ctx) < 0) goto out_err; if (ctx->stmt == NULL) { netlink_error(ctx, loc, "Could not parse dynset stmt"); goto out_err; } dstmt = ctx->stmt; list_add_tail(&dstmt->list, &dynset_parse_ctx.stmt_list); } } else if (nftnl_expr_is_set(nle, NFTNL_EXPR_DYNSET_EXPRESSIONS)) { if (nftnl_expr_expr_foreach(nle, dynset_parse_expressions, &dynset_parse_ctx) < 0) goto out_err; } if (nftnl_expr_is_set(nle, NFTNL_EXPR_DYNSET_SREG_DATA)) { sreg_data = netlink_parse_register(nle, NFTNL_EXPR_DYNSET_SREG_DATA); expr_data = netlink_get_register(ctx, loc, sreg_data); } if (expr_data != NULL) { expr_set_type(expr_data, set->data->dtype, set->data->byteorder); stmt = map_stmt_alloc(loc); stmt->map.set = set_ref_expr_alloc(loc, set); stmt->map.key = expr; stmt->map.data = expr_data; stmt->map.op = nftnl_expr_get_u32(nle, NFTNL_EXPR_DYNSET_OP); list_splice_tail(&dynset_parse_ctx.stmt_list, &stmt->map.stmt_list); } else { if (!list_empty(&dynset_parse_ctx.stmt_list) && set_is_anonymous(set->flags)) { stmt = meter_stmt_alloc(loc); stmt->meter.set = set_ref_expr_alloc(loc, set); stmt->meter.key = expr; stmt->meter.stmt = list_first_entry(&dynset_parse_ctx.stmt_list, struct stmt, list); stmt->meter.size = set->desc.size; } else { stmt = set_stmt_alloc(loc); stmt->set.set = set_ref_expr_alloc(loc, set); stmt->set.op = nftnl_expr_get_u32(nle, NFTNL_EXPR_DYNSET_OP); stmt->set.key = expr; list_splice_tail(&dynset_parse_ctx.stmt_list, &stmt->set.stmt_list); } } ctx->stmt = stmt; return; out_err: list_for_each_entry_safe(dstmt, next, &dynset_parse_ctx.stmt_list, list) stmt_free(dstmt); expr_free(expr); } static void netlink_parse_objref(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { uint32_t type = nftnl_expr_get_u32(nle, NFTNL_EXPR_OBJREF_IMM_TYPE); struct expr *expr; struct stmt *stmt; if (nftnl_expr_is_set(nle, NFTNL_EXPR_OBJREF_IMM_NAME)) { struct nft_data_delinearize nld; type = nftnl_expr_get_u32(nle, NFTNL_EXPR_OBJREF_IMM_TYPE); nld.value = nftnl_expr_get(nle, NFTNL_EXPR_OBJREF_IMM_NAME, &nld.len); expr = netlink_alloc_value(&netlink_location, &nld); datatype_set(expr, &string_type); expr->byteorder = BYTEORDER_HOST_ENDIAN; } else if (nftnl_expr_is_set(nle, NFTNL_EXPR_OBJREF_SET_SREG)) { struct expr *left, *right; enum nft_registers sreg; const char *name; struct set *set; name = nftnl_expr_get_str(nle, NFTNL_EXPR_OBJREF_SET_NAME); set = set_cache_find(ctx->table, name); if (set == NULL) return netlink_error(ctx, loc, "Unknown set '%s' in objref expression", name); sreg = netlink_parse_register(nle, NFTNL_EXPR_OBJREF_SET_SREG); left = netlink_get_register(ctx, loc, sreg); if (left == NULL) return netlink_error(ctx, loc, "objref expression has no left hand side"); if (left->len < set->key->len) { expr_free(left); left = netlink_parse_concat_expr(ctx, loc, sreg, set->key->len); if (left == NULL) return; } right = set_ref_expr_alloc(loc, set); expr = map_expr_alloc(loc, left, right); expr_set_type(expr, &string_type, BYTEORDER_HOST_ENDIAN); type = set->objtype; } else { netlink_error(ctx, loc, "unknown objref expression type %u", type); return; } stmt = objref_stmt_alloc(loc); stmt->objref.type = type; stmt->objref.expr = expr; ctx->stmt = stmt; } struct expr_handler { const char *name; void (*parse)(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle); }; static const struct expr_handler netlink_parsers[] = { { .name = "immediate", .parse = netlink_parse_immediate }, { .name = "cmp", .parse = netlink_parse_cmp }, { .name = "lookup", .parse = netlink_parse_lookup }, { .name = "bitwise", .parse = netlink_parse_bitwise }, { .name = "byteorder", .parse = netlink_parse_byteorder }, { .name = "payload", .parse = netlink_parse_payload }, { .name = "exthdr", .parse = netlink_parse_exthdr }, { .name = "meta", .parse = netlink_parse_meta }, { .name = "socket", .parse = netlink_parse_socket }, { .name = "osf", .parse = netlink_parse_osf }, { .name = "rt", .parse = netlink_parse_rt }, { .name = "ct", .parse = netlink_parse_ct }, { .name = "connlimit", .parse = netlink_parse_connlimit }, { .name = "counter", .parse = netlink_parse_counter }, { .name = "log", .parse = netlink_parse_log }, { .name = "limit", .parse = netlink_parse_limit }, { .name = "range", .parse = netlink_parse_range }, { .name = "reject", .parse = netlink_parse_reject }, { .name = "nat", .parse = netlink_parse_nat }, { .name = "tproxy", .parse = netlink_parse_tproxy }, { .name = "notrack", .parse = netlink_parse_notrack }, { .name = "masq", .parse = netlink_parse_masq }, { .name = "redir", .parse = netlink_parse_redir }, { .name = "dup", .parse = netlink_parse_dup }, { .name = "queue", .parse = netlink_parse_queue }, { .name = "dynset", .parse = netlink_parse_dynset }, { .name = "fwd", .parse = netlink_parse_fwd }, { .name = "target", .parse = netlink_parse_target }, { .name = "match", .parse = netlink_parse_match }, { .name = "objref", .parse = netlink_parse_objref }, { .name = "quota", .parse = netlink_parse_quota }, { .name = "numgen", .parse = netlink_parse_numgen }, { .name = "hash", .parse = netlink_parse_hash }, { .name = "fib", .parse = netlink_parse_fib }, { .name = "tcpopt", .parse = netlink_parse_exthdr }, { .name = "flow_offload", .parse = netlink_parse_flow_offload }, { .name = "xfrm", .parse = netlink_parse_xfrm }, { .name = "synproxy", .parse = netlink_parse_synproxy }, }; static int netlink_parse_expr(const struct nftnl_expr *nle, struct netlink_parse_ctx *ctx) { const char *type = nftnl_expr_get_str(nle, NFTNL_EXPR_NAME); struct location loc; unsigned int i; memset(&loc, 0, sizeof(loc)); loc.indesc = &indesc_netlink; loc.nle = nle; for (i = 0; i < array_size(netlink_parsers); i++) { if (strcmp(type, netlink_parsers[i].name)) continue; netlink_parsers[i].parse(ctx, &loc, nle); return 0; } netlink_error(ctx, &loc, "unknown expression type '%s'", type); return 0; } static int netlink_parse_rule_expr(struct nftnl_expr *nle, void *arg) { struct netlink_parse_ctx *ctx = arg; int err; err = netlink_parse_expr(nle, ctx); if (err < 0) return err; if (ctx->stmt != NULL) { rule_stmt_append(ctx->rule, ctx->stmt); ctx->stmt = NULL; } return 0; } struct stmt *netlink_parse_set_expr(const struct set *set, const struct nft_cache *cache, const struct nftnl_expr *nle) { struct netlink_parse_ctx ctx, *pctx = &ctx; struct handle h = {}; handle_merge(&h, &set->handle); pctx->rule = rule_alloc(&netlink_location, &h); pctx->table = table_cache_find(&cache->table_cache, set->handle.table.name, set->handle.family); assert(pctx->table != NULL); if (netlink_parse_expr(nle, pctx) < 0) return NULL; init_list_head(&pctx->rule->stmts); rule_free(pctx->rule); return pctx->stmt; } static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp); static void payload_match_expand(struct rule_pp_ctx *ctx, struct expr *expr, struct expr *payload) { struct expr *left = payload, *right = expr->right, *tmp; struct list_head list = LIST_HEAD_INIT(list); struct stmt *nstmt; struct expr *nexpr = NULL; enum proto_bases base = left->payload.base; payload_expr_expand(&list, left, &ctx->pctx); list_for_each_entry(left, &list, list) { tmp = constant_expr_splice(right, left->len); expr_set_type(tmp, left->dtype, left->byteorder); if (left->payload.tmpl && (left->len < left->payload.tmpl->len)) { mpz_lshift_ui(tmp->value, left->payload.tmpl->len - left->len); tmp->len = left->payload.tmpl->len; tmp = prefix_expr_alloc(&tmp->location, tmp, left->len); } nexpr = relational_expr_alloc(&expr->location, expr->op, left, tmp); if (expr->op == OP_EQ) relational_expr_pctx_update(&ctx->pctx, nexpr); nstmt = expr_stmt_alloc(&ctx->stmt->location, nexpr); list_add_tail(&nstmt->list, &ctx->stmt->list); assert(left->etype == EXPR_PAYLOAD); assert(left->payload.base); assert(base == left->payload.base); if (payload_is_stacked(ctx->pctx.protocol[base].desc, nexpr)) base--; /* Remember the first payload protocol expression to * kill it later on if made redundant by a higher layer * payload expression. */ payload_dependency_kill(&ctx->pdctx, nexpr->left, ctx->pctx.family); if (expr->op == OP_EQ && left->flags & EXPR_F_PROTOCOL) payload_dependency_store(&ctx->pdctx, nstmt, base); } list_del(&ctx->stmt->list); stmt_free(ctx->stmt); ctx->stmt = NULL; } static void payload_icmp_check(struct rule_pp_ctx *rctx, struct expr *expr, const struct expr *value) { const struct proto_hdr_template *tmpl; const struct proto_desc *desc; uint8_t icmp_type; unsigned int i; assert(expr->etype == EXPR_PAYLOAD); assert(value->etype == EXPR_VALUE); if (expr->payload.base != PROTO_BASE_TRANSPORT_HDR) return; /* icmp(v6) type is 8 bit, if value is smaller or larger, this is not * a protocol dependency. */ if (expr->len != 8 || value->len != 8 || rctx->pctx.th_dep.icmp.type) return; desc = rctx->pctx.protocol[expr->payload.base].desc; if (desc == NULL) return; /* not icmp? ignore. */ if (desc != &proto_icmp && desc != &proto_icmp6) return; assert(desc->base == expr->payload.base); icmp_type = mpz_get_uint8(value->value); for (i = 1; i < array_size(desc->templates); i++) { tmpl = &desc->templates[i]; if (tmpl->len == 0) return; if (tmpl->offset != expr->payload.offset || tmpl->len != expr->len) continue; /* Matches but doesn't load a protocol key -> ignore. */ if (desc->protocol_key != i) return; expr->payload.desc = desc; expr->payload.tmpl = tmpl; rctx->pctx.th_dep.icmp.type = icmp_type; return; } } static void payload_match_postprocess(struct rule_pp_ctx *ctx, struct expr *expr, struct expr *payload) { enum proto_bases base = payload->payload.base; assert(payload->payload.offset >= ctx->pctx.protocol[base].offset); payload->payload.offset -= ctx->pctx.protocol[base].offset; switch (expr->op) { case OP_EQ: case OP_NEQ: if (expr->right->etype == EXPR_VALUE) { payload_match_expand(ctx, expr, payload); break; } else if (expr->right->etype == EXPR_SET_REF) { struct set *set = expr->right->set; if (set_is_anonymous(set->flags) && set->init && !list_empty(&set->init->expressions)) { struct expr *elem; elem = list_first_entry(&set->init->expressions, struct expr, list); if (elem->etype == EXPR_SET_ELEM && elem->key->etype == EXPR_VALUE) payload_icmp_check(ctx, payload, elem->key); } } /* Fall through */ default: payload_expr_complete(payload, &ctx->pctx); expr_set_type(expr->right, payload->dtype, payload->byteorder); payload_dependency_kill(&ctx->pdctx, payload, ctx->pctx.family); break; } } static uint8_t ether_type_to_nfproto(uint16_t l3proto) { switch(l3proto) { case ETH_P_IP: return NFPROTO_IPV4; case ETH_P_IPV6: return NFPROTO_IPV6; default: break; } return NFPROTO_UNSPEC; } static bool __meta_dependency_may_kill(const struct expr *dep, uint8_t *nfproto) { uint16_t l3proto; switch (dep->left->etype) { case EXPR_META: switch (dep->left->meta.key) { case NFT_META_NFPROTO: *nfproto = mpz_get_uint8(dep->right->value); break; case NFT_META_PROTOCOL: l3proto = mpz_get_uint16(dep->right->value); *nfproto = ether_type_to_nfproto(l3proto); break; default: return true; } break; case EXPR_PAYLOAD: if (dep->left->payload.base != PROTO_BASE_LL_HDR) return true; if (dep->left->dtype != ðertype_type) return true; l3proto = mpz_get_uint16(dep->right->value); *nfproto = ether_type_to_nfproto(l3proto); break; default: return true; } return false; } /* We have seen a protocol key expression that restricts matching at the network * base, leave it in place since this is meaningful in bridge, inet and netdev * families. Exceptions are ICMP and ICMPv6 where this code assumes that can * only happen with IPv4 and IPv6. */ static bool meta_may_dependency_kill(struct payload_dep_ctx *ctx, unsigned int family, const struct expr *expr) { uint8_t l4proto, nfproto = NFPROTO_UNSPEC; struct expr *dep = payload_dependency_get(ctx, PROTO_BASE_NETWORK_HDR); if (!dep) return true; if (__meta_dependency_may_kill(dep, &nfproto)) return true; switch (family) { case NFPROTO_INET: case NFPROTO_NETDEV: case NFPROTO_BRIDGE: break; case NFPROTO_IPV4: case NFPROTO_IPV6: return family == nfproto; default: return true; } if (expr->left->meta.key != NFT_META_L4PROTO) return true; l4proto = mpz_get_uint8(expr->right->value); switch (l4proto) { case IPPROTO_ICMP: case IPPROTO_ICMPV6: break; default: return false; } if ((nfproto == NFPROTO_IPV4 && l4proto == IPPROTO_ICMPV6) || (nfproto == NFPROTO_IPV6 && l4proto == IPPROTO_ICMP)) return false; return true; } static void ct_meta_common_postprocess(struct rule_pp_ctx *ctx, const struct expr *expr, enum proto_bases base) { const struct expr *left = expr->left; struct expr *right = expr->right; if (right->etype == EXPR_SET || right->etype == EXPR_SET_REF) expr_set_type(right, left->dtype, left->byteorder); switch (expr->op) { case OP_EQ: if (expr->right->etype == EXPR_RANGE || expr->right->etype == EXPR_SET || expr->right->etype == EXPR_SET_REF) break; relational_expr_pctx_update(&ctx->pctx, expr); if (base < PROTO_BASE_TRANSPORT_HDR) { if (payload_dependency_exists(&ctx->pdctx, base) && meta_may_dependency_kill(&ctx->pdctx, ctx->pctx.family, expr)) payload_dependency_release(&ctx->pdctx, base); if (left->flags & EXPR_F_PROTOCOL) payload_dependency_store(&ctx->pdctx, ctx->stmt, base); } break; default: break; } } static void meta_match_postprocess(struct rule_pp_ctx *ctx, const struct expr *expr) { const struct expr *left = expr->left; ct_meta_common_postprocess(ctx, expr, left->meta.base); } static void ct_match_postprocess(struct rule_pp_ctx *ctx, const struct expr *expr) { const struct expr *left = expr->left; ct_meta_common_postprocess(ctx, expr, left->ct.base); } /* Convert a bitmask to a prefix length */ static unsigned int expr_mask_to_prefix(const struct expr *expr) { unsigned long n; n = mpz_scan1(expr->value, 0); if (n == ULONG_MAX) return 0; return mpz_scan0(expr->value, n + 1) - n; } /* Return true if a bitmask can be expressed as a prefix length */ static bool expr_mask_is_prefix(const struct expr *expr) { unsigned long n1, n2; n1 = mpz_scan1(expr->value, 0); if (n1 == ULONG_MAX) return true; n2 = mpz_scan0(expr->value, n1 + 1); if (n2 < expr->len || n2 == ULONG_MAX) return false; return true; } /* Convert a series of inclusive OR expressions into a list */ static struct expr *binop_tree_to_list(struct expr *list, struct expr *expr) { if (expr->etype == EXPR_BINOP && expr->op == OP_OR) { if (list == NULL) list = list_expr_alloc(&expr->location); list = binop_tree_to_list(list, expr->left); list = binop_tree_to_list(list, expr->right); } else { if (list == NULL) return expr_get(expr); compound_expr_add(list, expr_get(expr)); } return list; } static void binop_adjust_one(const struct expr *binop, struct expr *value, unsigned int shift) { struct expr *left = binop->left; assert(value->len >= binop->right->len); mpz_rshift_ui(value->value, shift); switch (left->etype) { case EXPR_PAYLOAD: case EXPR_EXTHDR: value->len = left->len; break; default: BUG("unknown expression type %s\n", expr_name(left)); break; } } static void binop_adjust(const struct expr *binop, struct expr *right, unsigned int shift) { struct expr *i; switch (right->etype) { case EXPR_VALUE: binop_adjust_one(binop, right, shift); break; case EXPR_SET_REF: list_for_each_entry(i, &right->set->init->expressions, list) { switch (i->key->etype) { case EXPR_VALUE: binop_adjust_one(binop, i->key, shift); break; case EXPR_RANGE: binop_adjust_one(binop, i->key->left, shift); binop_adjust_one(binop, i->key->right, shift); break; case EXPR_SET_ELEM: binop_adjust(binop, i->key->key, shift); break; default: BUG("unknown expression type %s\n", expr_name(i->key)); } } break; case EXPR_RANGE: binop_adjust_one(binop, right->left, shift); binop_adjust_one(binop, right->right, shift); break; default: BUG("unknown expression type %s\n", expr_name(right)); break; } } static void binop_postprocess(struct rule_pp_ctx *ctx, struct expr *expr, struct expr **expr_binop) { struct expr *binop = *expr_binop; struct expr *left = binop->left; struct expr *mask = binop->right; unsigned int shift; assert(binop->etype == EXPR_BINOP); if ((left->etype == EXPR_PAYLOAD && payload_expr_trim(left, mask, &ctx->pctx, &shift)) || (left->etype == EXPR_EXTHDR && exthdr_find_template(left, mask, &shift))) { struct expr *right = NULL; /* mask is implicit, binop needs to be removed. * * Fix all values of the expression according to the mask * and then process the payload instruction using the real * sizes and offsets we're interested in. * * Finally, convert the expression to 1) by replacing * the binop with the binop payload/exthdr expression. */ switch (expr->etype) { case EXPR_BINOP: case EXPR_RELATIONAL: right = expr->right; binop_adjust(binop, right, shift); break; case EXPR_MAP: right = expr->mappings; binop_adjust(binop, right, shift); break; default: break; } assert(binop->left == left); *expr_binop = expr_get(left); expr_free(binop); if (left->etype == EXPR_PAYLOAD) payload_match_postprocess(ctx, expr, left); else if (left->etype == EXPR_EXTHDR && right) expr_set_type(right, left->dtype, left->byteorder); } } static void map_binop_postprocess(struct rule_pp_ctx *ctx, struct expr *expr) { struct expr *binop = expr->map; if (binop->op != OP_AND) return; if (binop->left->etype == EXPR_PAYLOAD && binop->right->etype == EXPR_VALUE) binop_postprocess(ctx, expr, &expr->map); } static bool is_shift_by_zero(const struct expr *binop) { struct expr *rhs; if (binop->op != OP_RSHIFT && binop->op != OP_LSHIFT) return false; rhs = binop->right; if (rhs->etype != EXPR_VALUE || rhs->len > 64) return false; return mpz_get_uint64(rhs->value) == 0; } static void relational_binop_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) { struct expr *expr = *exprp, *binop = expr->left, *right = expr->right; if (binop->op == OP_AND && (expr->op == OP_NEQ || expr->op == OP_EQ) && right->dtype->basetype && right->dtype->basetype->type == TYPE_BITMASK) { switch (right->etype) { case EXPR_VALUE: if (!mpz_cmp_ui(right->value, 0)) { /* Flag comparison: data & flags != 0 * * Split the flags into a list of flag values and convert the * op to OP_EQ. */ expr_free(right); expr->left = expr_get(binop->left); expr->right = binop_tree_to_list(NULL, binop->right); switch (expr->op) { case OP_NEQ: expr->op = OP_IMPLICIT; break; case OP_EQ: expr->op = OP_NEG; break; default: BUG("unknown operation type %d\n", expr->op); } expr_free(binop); } else if (binop->right->etype == EXPR_VALUE && right->etype == EXPR_VALUE && !mpz_cmp(right->value, binop->right->value)) { /* Skip flag / flag representation for: * data & flag == flag * data & flag != flag */ ; } else { *exprp = flagcmp_expr_alloc(&expr->location, expr->op, expr_get(binop->left), binop_tree_to_list(NULL, binop->right), expr_get(right)); expr_free(expr); } break; case EXPR_BINOP: *exprp = flagcmp_expr_alloc(&expr->location, expr->op, expr_get(binop->left), binop_tree_to_list(NULL, binop->right), binop_tree_to_list(NULL, right)); expr_free(expr); break; default: break; } } else if (binop->left->dtype->flags & DTYPE_F_PREFIX && binop->op == OP_AND && expr->right->etype == EXPR_VALUE && expr_mask_is_prefix(binop->right)) { expr->left = expr_get(binop->left); expr->right = prefix_expr_alloc(&expr->location, expr_get(right), expr_mask_to_prefix(binop->right)); expr_free(right); expr_free(binop); } else if (binop->op == OP_AND && binop->right->etype == EXPR_VALUE) { /* * This *might* be a payload match testing header fields that * have non byte divisible offsets and/or bit lengths. * * Thus we need to deal with two different cases. * * 1 the simple version: * relation * payload value|setlookup * * expr: relation, left: payload, right: value, e.g. tcp dport == 22. * * 2. The '&' version (this is what we're looking at now). * relation * binop value1|setlookup * payload value2 * * expr: relation, left: binop, right: value, e.g. * ip saddr 10.0.0.0/8 * * payload_expr_trim will figure out if the mask is needed to match * templates. */ binop_postprocess(ctx, expr, &expr->left); } else if (binop->op == OP_RSHIFT && binop->left->op == OP_AND && binop->right->etype == EXPR_VALUE && binop->left->right->etype == EXPR_VALUE) { /* Handle 'ip version @s4' and similar, i.e. set lookups where the lhs needs * fixups to mask out unwanted bits AND a shift. */ binop_postprocess(ctx, binop, &binop->left); if (is_shift_by_zero(binop)) { struct expr *lhs = binop->left; expr_get(lhs); expr_free(binop); expr->left = lhs; } } } static struct expr *string_wildcard_expr_alloc(struct location *loc, const struct expr *mask, const struct expr *expr) { unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); char data[len + 2]; int pos; mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len); pos = div_round_up(expr_mask_to_prefix(mask), BITS_PER_BYTE); data[pos] = '*'; data[pos + 1] = '\0'; return constant_expr_alloc(loc, expr->dtype, BYTEORDER_HOST_ENDIAN, expr->len + BITS_PER_BYTE, data); } /* This calculates the string length and checks if it is nul-terminated, this * function is quite a hack :) */ static bool __expr_postprocess_string(struct expr **exprp) { struct expr *expr = *exprp; unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); char data[len + 1]; mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len); if (data[len - 1] != '\0') return false; len = strlen(data); if (len && data[len - 1] == '*') { data[len - 1] = '\\'; data[len] = '*'; data[len + 1] = '\0'; expr = constant_expr_alloc(&expr->location, expr->dtype, BYTEORDER_HOST_ENDIAN, (len + 2) * BITS_PER_BYTE, data); expr_free(*exprp); *exprp = expr; } return true; } static struct expr *expr_postprocess_string(struct expr *expr) { struct expr *mask, *out; assert(expr_basetype(expr)->type == TYPE_STRING); if (__expr_postprocess_string(&expr)) return expr; mask = constant_expr_alloc(&expr->location, &integer_type, BYTEORDER_HOST_ENDIAN, expr->len + BITS_PER_BYTE, NULL); mpz_clear(mask->value); mpz_init_bitmask(mask->value, expr->len); out = string_wildcard_expr_alloc(&expr->location, mask, expr); expr_free(expr); expr_free(mask); return out; } static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) { struct expr *expr = *exprp, *i; switch (expr->etype) { case EXPR_MAP: switch (expr->map->etype) { case EXPR_BINOP: map_binop_postprocess(ctx, expr); break; default: break; } expr_postprocess(ctx, &expr->map); expr_postprocess(ctx, &expr->mappings); break; case EXPR_MAPPING: expr_postprocess(ctx, &expr->left); expr_postprocess(ctx, &expr->right); break; case EXPR_SET: list_for_each_entry(i, &expr->expressions, list) expr_postprocess(ctx, &i); break; case EXPR_CONCAT: { unsigned int type = expr->dtype->type, ntype = 0; int off = expr->dtype->subtypes; const struct datatype *dtype; list_for_each_entry(i, &expr->expressions, list) { if (type) { dtype = concat_subtype_lookup(type, --off); expr_set_type(i, dtype, dtype->byteorder); } expr_postprocess(ctx, &i); ntype = concat_subtype_add(ntype, i->dtype->type); } datatype_set(expr, concat_type_alloc(ntype)); break; } case EXPR_UNARY: expr_postprocess(ctx, &expr->arg); expr_set_type(expr, expr->arg->dtype, !expr->arg->byteorder); break; case EXPR_BINOP: expr_postprocess(ctx, &expr->left); switch (expr->op) { case OP_LSHIFT: case OP_RSHIFT: expr_set_type(expr->right, &integer_type, BYTEORDER_HOST_ENDIAN); break; default: expr_set_type(expr->right, expr->left->dtype, expr->left->byteorder); } expr_postprocess(ctx, &expr->right); expr_set_type(expr, expr->left->dtype, expr->left->byteorder); break; case EXPR_RELATIONAL: switch (expr->left->etype) { case EXPR_PAYLOAD: payload_match_postprocess(ctx, expr, expr->left); return; default: expr_postprocess(ctx, &expr->left); break; } expr_set_type(expr->right, expr->left->dtype, expr->left->byteorder); expr_postprocess(ctx, &expr->right); switch (expr->left->etype) { case EXPR_CT: ct_match_postprocess(ctx, expr); break; case EXPR_META: meta_match_postprocess(ctx, expr); break; case EXPR_BINOP: relational_binop_postprocess(ctx, exprp); break; default: break; } break; case EXPR_PAYLOAD: payload_expr_complete(expr, &ctx->pctx); payload_dependency_kill(&ctx->pdctx, expr, ctx->pctx.family); break; case EXPR_VALUE: // FIXME if (expr->byteorder == BYTEORDER_HOST_ENDIAN) mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE); if (expr_basetype(expr)->type == TYPE_STRING) *exprp = expr_postprocess_string(expr); expr = *exprp; if (expr->dtype->basetype != NULL && expr->dtype->basetype->type == TYPE_BITMASK) *exprp = bitmask_expr_to_binops(expr); break; case EXPR_RANGE: expr_postprocess(ctx, &expr->left); expr_postprocess(ctx, &expr->right); break; case EXPR_PREFIX: expr_postprocess(ctx, &expr->prefix); break; case EXPR_SET_ELEM: expr_postprocess(ctx, &expr->key); break; case EXPR_EXTHDR: exthdr_dependency_kill(&ctx->pdctx, expr, ctx->pctx.family); break; case EXPR_SET_REF: case EXPR_META: case EXPR_RT: case EXPR_VERDICT: case EXPR_NUMGEN: case EXPR_FIB: case EXPR_SOCKET: case EXPR_OSF: case EXPR_XFRM: break; case EXPR_HASH: if (expr->hash.expr) expr_postprocess(ctx, &expr->hash.expr); break; case EXPR_CT: ct_expr_update_type(&ctx->pctx, expr); break; default: BUG("unknown expression type %s\n", expr_name(expr)); } } static void stmt_reject_postprocess(struct rule_pp_ctx *rctx) { const struct proto_desc *desc, *base; struct stmt *stmt = rctx->stmt; int protocol; switch (rctx->pctx.family) { case NFPROTO_IPV4: stmt->reject.family = rctx->pctx.family; datatype_set(stmt->reject.expr, &icmp_code_type); if (stmt->reject.type == NFT_REJECT_TCP_RST && payload_dependency_exists(&rctx->pdctx, PROTO_BASE_TRANSPORT_HDR)) payload_dependency_release(&rctx->pdctx, PROTO_BASE_TRANSPORT_HDR); break; case NFPROTO_IPV6: stmt->reject.family = rctx->pctx.family; datatype_set(stmt->reject.expr, &icmpv6_code_type); if (stmt->reject.type == NFT_REJECT_TCP_RST && payload_dependency_exists(&rctx->pdctx, PROTO_BASE_TRANSPORT_HDR)) payload_dependency_release(&rctx->pdctx, PROTO_BASE_TRANSPORT_HDR); break; case NFPROTO_INET: case NFPROTO_BRIDGE: case NFPROTO_NETDEV: if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH) { datatype_set(stmt->reject.expr, &icmpx_code_type); break; } /* always print full icmp(6) name, simple 'reject' might be ambiguious * because ipv4 vs. ipv6 info might be lost */ stmt->reject.verbose_print = 1; base = rctx->pctx.protocol[PROTO_BASE_LL_HDR].desc; desc = rctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; protocol = proto_find_num(base, desc); switch (protocol) { case NFPROTO_IPV4: /* INET */ case __constant_htons(ETH_P_IP): /* BRIDGE, NETDEV */ stmt->reject.family = NFPROTO_IPV4; datatype_set(stmt->reject.expr, &icmp_code_type); break; case NFPROTO_IPV6: /* INET */ case __constant_htons(ETH_P_IPV6): /* BRIDGE, NETDEV */ stmt->reject.family = NFPROTO_IPV6; datatype_set(stmt->reject.expr, &icmpv6_code_type); break; default: break; } if (payload_dependency_exists(&rctx->pdctx, PROTO_BASE_NETWORK_HDR)) payload_dependency_release(&rctx->pdctx, PROTO_BASE_NETWORK_HDR); break; default: break; } } static bool expr_may_merge_range(struct expr *expr, struct expr *prev, enum ops *op) { struct expr *left, *prev_left; if (prev->etype == EXPR_RELATIONAL && expr->etype == EXPR_RELATIONAL) { /* ct and meta needs an unary to swap byteorder, in this case * we have to explore the inner branch in this tree. */ if (expr->left->etype == EXPR_UNARY) left = expr->left->arg; else left = expr->left; if (prev->left->etype == EXPR_UNARY) prev_left = prev->left->arg; else prev_left = prev->left; if (left->etype == prev_left->etype) { if (expr->op == OP_LTE && prev->op == OP_GTE) { *op = OP_EQ; return true; } else if (expr->op == OP_GT && prev->op == OP_LT) { *op = OP_NEQ; return true; } } } return false; } static void expr_postprocess_range(struct rule_pp_ctx *ctx, enum ops op) { struct stmt *nstmt, *stmt = ctx->stmt; struct expr *nexpr, *rel; nexpr = range_expr_alloc(&ctx->pdctx.prev->location, expr_clone(ctx->pdctx.prev->expr->right), expr_clone(stmt->expr->right)); expr_set_type(nexpr, stmt->expr->right->dtype, stmt->expr->right->byteorder); rel = relational_expr_alloc(&ctx->pdctx.prev->location, op, expr_clone(stmt->expr->left), nexpr); nstmt = expr_stmt_alloc(&stmt->location, rel); list_add_tail(&nstmt->list, &stmt->list); list_del(&ctx->pdctx.prev->list); stmt_free(ctx->pdctx.prev); list_del(&stmt->list); stmt_free(stmt); ctx->stmt = nstmt; } static void stmt_expr_postprocess(struct rule_pp_ctx *ctx) { enum ops op; expr_postprocess(ctx, &ctx->stmt->expr); if (ctx->pdctx.prev && ctx->stmt && ctx->stmt->ops->type == ctx->pdctx.prev->ops->type && expr_may_merge_range(ctx->stmt->expr, ctx->pdctx.prev->expr, &op)) expr_postprocess_range(ctx, op); } static void stmt_payload_binop_pp(struct rule_pp_ctx *ctx, struct expr *binop) { struct expr *payload = binop->left; struct expr *mask = binop->right; unsigned int shift; assert(payload->etype == EXPR_PAYLOAD); if (payload_expr_trim(payload, mask, &ctx->pctx, &shift)) { binop_adjust(binop, mask, shift); payload_expr_complete(payload, &ctx->pctx); expr_set_type(mask, payload->dtype, payload->byteorder); } } /** * stmt_payload_binop_postprocess - decode payload set binop * * @ctx: rule postprocessing context * * This helper has to be called if expr_postprocess() failed to * decode the payload operation. * * Usually a failure to decode means that userspace had to munge * the original payload expression because it has an odd size or * a non-byte divisible offset/length. * * If that was the case, the 'value' expression is not a value but * a binop expression with a munged payload expression on the left * and a mask to clear the real payload offset/length. * * So chech if we have one of the following binops: * I) * binop (|) * binop(&) value/set * payload value(mask) * * This is the normal case, the | RHS is the value the user wants * to set, the & RHS is the mask value that discards bits we need * to clear but retains everything unrelated to the set operation. * * IIa) * binop (&) * payload mask * * User specified a zero set value -- netlink bitwise decoding * discarded the redundant "| 0" part. This is identical to I), * we can just set value to 0 after we inferred the real payload size. * * IIb) * binop (|) * payload value/set * * This happens when user wants to set all bits, netlink bitwise * decoding changed '(payload & mask) ^ bits_to_set' into * 'payload | bits_to_set', discarding the redundant "& 0xfff...". */ static void stmt_payload_binop_postprocess(struct rule_pp_ctx *ctx) { struct expr *expr, *binop, *payload, *value, *mask; struct stmt *stmt = ctx->stmt; mpz_t bitmask; expr = stmt->payload.val; if (expr->etype != EXPR_BINOP) return; switch (expr->left->etype) { case EXPR_BINOP: {/* I? */ mpz_t tmp; if (expr->op != OP_OR) return; value = expr->right; if (value->etype != EXPR_VALUE) return; binop = expr->left; if (binop->op != OP_AND) return; payload = binop->left; if (payload->etype != EXPR_PAYLOAD) return; if (!payload_expr_cmp(stmt->payload.expr, payload)) return; mask = binop->right; if (mask->etype != EXPR_VALUE) return; mpz_init(tmp); mpz_set(tmp, mask->value); mpz_init_bitmask(bitmask, payload->len); mpz_xor(bitmask, bitmask, mask->value); mpz_xor(bitmask, bitmask, value->value); mpz_set(mask->value, bitmask); mpz_clear(bitmask); binop_postprocess(ctx, expr, &expr->left); if (!payload_is_known(payload)) { mpz_set(mask->value, tmp); mpz_clear(tmp); return; } mpz_clear(tmp); expr_free(stmt->payload.expr); stmt->payload.expr = expr_get(payload); stmt->payload.val = expr_get(expr->right); expr_free(expr); break; } case EXPR_PAYLOAD: /* II? */ value = expr->right; if (value->etype != EXPR_VALUE) return; switch (expr->op) { case OP_AND: /* IIa */ payload = expr->left; mpz_init_bitmask(bitmask, payload->len); mpz_xor(bitmask, bitmask, value->value); mpz_set(value->value, bitmask); mpz_clear(bitmask); break; case OP_OR: /* IIb */ break; default: /* No idea */ return; } stmt_payload_binop_pp(ctx, expr); if (!payload_is_known(expr->left)) return; expr_free(stmt->payload.expr); switch (expr->op) { case OP_AND: /* Mask was used to match payload, i.e. * user asked to set zero value. */ mpz_set_ui(value->value, 0); break; default: break; } stmt->payload.expr = expr_get(expr->left); stmt->payload.val = expr_get(expr->right); expr_free(expr); break; default: /* No idea */ break; } } static void stmt_payload_postprocess(struct rule_pp_ctx *ctx) { struct stmt *stmt = ctx->stmt; expr_postprocess(ctx, &stmt->payload.expr); expr_set_type(stmt->payload.val, stmt->payload.expr->dtype, stmt->payload.expr->byteorder); if (!payload_is_known(stmt->payload.expr)) stmt_payload_binop_postprocess(ctx); expr_postprocess(ctx, &stmt->payload.val); } static void stmt_queue_postprocess(struct rule_pp_ctx *ctx) { struct stmt *stmt = ctx->stmt; struct expr *e = stmt->queue.queue; if (e == NULL || e->etype == EXPR_VALUE || e->etype == EXPR_RANGE) return; expr_postprocess(ctx, &stmt->queue.queue); } /* * We can only remove payload dependencies if they occur without * a statement with side effects in between. * * For instance: * 'ip protocol tcp tcp dport 22 counter' is same as * 'tcp dport 22 counter'. * * 'ip protocol tcp counter tcp dport 22' cannot be written as * 'counter tcp dport 22' (that would be counter ip protocol tcp, but * that counts every packet, not just ip/tcp). */ static void rule_maybe_reset_payload_deps(struct payload_dep_ctx *pdctx, enum stmt_types t) { if (t == STMT_EXPRESSION) return; payload_dependency_reset(pdctx); } static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *rule) { struct stmt *stmt, *next; struct rule_pp_ctx rctx; struct expr *expr; memset(&rctx, 0, sizeof(rctx)); proto_ctx_init(&rctx.pctx, rule->handle.family, ctx->debug_mask); list_for_each_entry_safe(stmt, next, &rule->stmts, list) { enum stmt_types type = stmt->ops->type; rctx.stmt = stmt; switch (type) { case STMT_EXPRESSION: stmt_expr_postprocess(&rctx); break; case STMT_PAYLOAD: stmt_payload_postprocess(&rctx); break; case STMT_METER: expr_postprocess(&rctx, &stmt->meter.key); break; case STMT_META: if (stmt->meta.expr != NULL) expr_postprocess(&rctx, &stmt->meta.expr); break; case STMT_CT: if (stmt->ct.expr != NULL) { expr_postprocess(&rctx, &stmt->ct.expr); if (stmt->ct.expr->etype == EXPR_BINOP && stmt->ct.key == NFT_CT_EVENTMASK) { expr = binop_tree_to_list(NULL, stmt->ct.expr); expr_free(stmt->ct.expr); stmt->ct.expr = expr; } } break; case STMT_NAT: if (stmt->nat.addr != NULL) expr_postprocess(&rctx, &stmt->nat.addr); if (stmt->nat.proto != NULL) { payload_dependency_reset(&rctx.pdctx); expr_postprocess(&rctx, &stmt->nat.proto); } break; case STMT_TPROXY: if (stmt->tproxy.addr) expr_postprocess(&rctx, &stmt->tproxy.addr); if (stmt->tproxy.port) { payload_dependency_reset(&rctx.pdctx); expr_postprocess(&rctx, &stmt->tproxy.port); } break; case STMT_REJECT: stmt_reject_postprocess(&rctx); break; case STMT_SET: expr_postprocess(&rctx, &stmt->set.key); break; case STMT_MAP: expr_postprocess(&rctx, &stmt->map.key); expr_postprocess(&rctx, &stmt->map.data); break; case STMT_DUP: if (stmt->dup.to != NULL) expr_postprocess(&rctx, &stmt->dup.to); if (stmt->dup.dev != NULL) expr_postprocess(&rctx, &stmt->dup.dev); break; case STMT_FWD: expr_postprocess(&rctx, &stmt->fwd.dev); if (stmt->fwd.addr != NULL) expr_postprocess(&rctx, &stmt->fwd.addr); break; case STMT_XT: stmt_xt_postprocess(&rctx, stmt, rule); break; case STMT_OBJREF: expr_postprocess(&rctx, &stmt->objref.expr); break; case STMT_QUEUE: stmt_queue_postprocess(&rctx); break; default: break; } rctx.pdctx.prev = rctx.stmt; rule_maybe_reset_payload_deps(&rctx.pdctx, type); } } static int parse_rule_udata_cb(const struct nftnl_udata *attr, void *data) { unsigned char *value = nftnl_udata_get(attr); uint8_t type = nftnl_udata_type(attr); uint8_t len = nftnl_udata_len(attr); const struct nftnl_udata **tb = data; switch (type) { case NFTNL_UDATA_RULE_COMMENT: if (value[len - 1] != '\0') return -1; break; default: return 0; } tb[type] = attr; return 0; } static char *nftnl_rule_get_comment(const struct nftnl_rule *nlr) { const struct nftnl_udata *tb[NFTNL_UDATA_RULE_MAX + 1] = {}; const void *data; uint32_t len; if (!nftnl_rule_is_set(nlr, NFTNL_RULE_USERDATA)) return NULL; data = nftnl_rule_get_data(nlr, NFTNL_RULE_USERDATA, &len); if (nftnl_udata_parse(data, len, parse_rule_udata_cb, tb) < 0) return NULL; if (!tb[NFTNL_UDATA_RULE_COMMENT]) return NULL; return xstrdup(nftnl_udata_get(tb[NFTNL_UDATA_RULE_COMMENT])); } struct rule *netlink_delinearize_rule(struct netlink_ctx *ctx, struct nftnl_rule *nlr) { struct netlink_parse_ctx _ctx, *pctx = &_ctx; struct handle h; memset(&_ctx, 0, sizeof(_ctx)); _ctx.msgs = ctx->msgs; _ctx.debug_mask = ctx->nft->debug_mask; _ctx.nlctx = ctx; memset(&h, 0, sizeof(h)); h.family = nftnl_rule_get_u32(nlr, NFTNL_RULE_FAMILY); h.table.name = xstrdup(nftnl_rule_get_str(nlr, NFTNL_RULE_TABLE)); h.chain.name = xstrdup(nftnl_rule_get_str(nlr, NFTNL_RULE_CHAIN)); h.handle.id = nftnl_rule_get_u64(nlr, NFTNL_RULE_HANDLE); if (nftnl_rule_is_set(nlr, NFTNL_RULE_POSITION)) h.position.id = nftnl_rule_get_u64(nlr, NFTNL_RULE_POSITION); pctx->rule = rule_alloc(&netlink_location, &h); pctx->table = table_cache_find(&ctx->nft->cache.table_cache, h.table.name, h.family); assert(pctx->table != NULL); pctx->rule->comment = nftnl_rule_get_comment(nlr); nftnl_expr_foreach(nlr, netlink_parse_rule_expr, pctx); rule_parse_postprocess(pctx, pctx->rule); netlink_release_registers(pctx); return pctx->rule; }