summaryrefslogtreecommitdiffstats
path: root/src/ct.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ct.c')
-rw-r--r--src/ct.c593
1 files changed, 593 insertions, 0 deletions
diff --git a/src/ct.c b/src/ct.c
new file mode 100644
index 0000000..1dda799
--- /dev/null
+++ b/src/ct.c
@@ -0,0 +1,593 @@
+/*
+ * Conntrack expression related definitions 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 <stddef.h>
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <netinet/ip.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter/nf_conntrack_common.h>
+#include <linux/netfilter/nf_conntrack_tuple_common.h>
+
+#include <errno.h>
+#include <erec.h>
+#include <expression.h>
+#include <datatype.h>
+#include <ct.h>
+#include <gmputil.h>
+#include <utils.h>
+#include <statement.h>
+
+#define CONNLABEL_CONF DEFAULT_INCLUDE_PATH "/connlabel.conf"
+
+static const struct symbol_table ct_state_tbl = {
+ .base = BASE_HEXADECIMAL,
+ .symbols = {
+ SYMBOL("invalid", NF_CT_STATE_INVALID_BIT),
+ SYMBOL("new", NF_CT_STATE_BIT(IP_CT_NEW)),
+ SYMBOL("established", NF_CT_STATE_BIT(IP_CT_ESTABLISHED)),
+ SYMBOL("related", NF_CT_STATE_BIT(IP_CT_RELATED)),
+ SYMBOL("untracked", NF_CT_STATE_UNTRACKED_BIT),
+ SYMBOL_LIST_END
+ }
+};
+
+const struct datatype ct_state_type = {
+ .type = TYPE_CT_STATE,
+ .name = "ct_state",
+ .desc = "conntrack state",
+ .byteorder = BYTEORDER_HOST_ENDIAN,
+ .size = 4 * BITS_PER_BYTE,
+ .basetype = &bitmask_type,
+ .sym_tbl = &ct_state_tbl,
+};
+
+static const struct symbol_table ct_dir_tbl = {
+ .base = BASE_DECIMAL,
+ .symbols = {
+ SYMBOL("original", IP_CT_DIR_ORIGINAL),
+ SYMBOL("reply", IP_CT_DIR_REPLY),
+ SYMBOL_LIST_END
+ }
+};
+
+const char *ct_dir2str(int dir)
+{
+ const struct symbolic_constant *s;
+
+ for (s = ct_dir_tbl.symbols; s->identifier != NULL; s++) {
+ if (dir == (int)s->value)
+ return s->identifier;
+ }
+
+ return NULL;
+}
+
+const struct datatype ct_dir_type = {
+ .type = TYPE_CT_DIR,
+ .name = "ct_dir",
+ .desc = "conntrack direction",
+ .byteorder = BYTEORDER_INVALID,
+ .size = BITS_PER_BYTE,
+ .basetype = &integer_type,
+ .sym_tbl = &ct_dir_tbl,
+};
+
+static const struct symbol_table ct_status_tbl = {
+ /*
+ * There are more, but most of them don't make sense for filtering.
+ */
+ .base = BASE_HEXADECIMAL,
+ .symbols = {
+ SYMBOL("expected", IPS_EXPECTED),
+ SYMBOL("seen-reply", IPS_SEEN_REPLY),
+ SYMBOL("assured", IPS_ASSURED),
+ SYMBOL("confirmed", IPS_CONFIRMED),
+ SYMBOL("snat", IPS_SRC_NAT),
+ SYMBOL("dnat", IPS_DST_NAT),
+ SYMBOL("dying", IPS_DYING),
+ SYMBOL_LIST_END
+ },
+};
+
+const struct datatype ct_status_type = {
+ .type = TYPE_CT_STATUS,
+ .name = "ct_status",
+ .desc = "conntrack status",
+ .byteorder = BYTEORDER_HOST_ENDIAN,
+ .size = 4 * BITS_PER_BYTE,
+ .basetype = &bitmask_type,
+ .sym_tbl = &ct_status_tbl,
+};
+
+static const struct symbol_table ct_events_tbl = {
+ .base = BASE_HEXADECIMAL,
+ .symbols = {
+ SYMBOL("new", 1 << IPCT_NEW),
+ SYMBOL("related", 1 << IPCT_RELATED),
+ SYMBOL("destroy", 1 << IPCT_DESTROY),
+ SYMBOL("reply", 1 << IPCT_REPLY),
+ SYMBOL("assured", 1 << IPCT_ASSURED),
+ SYMBOL("protoinfo", 1 << IPCT_PROTOINFO),
+ SYMBOL("helper", 1 << IPCT_HELPER),
+ SYMBOL("mark", 1 << IPCT_MARK),
+ SYMBOL("seqadj", 1 << IPCT_SEQADJ),
+ SYMBOL("secmark", 1 << IPCT_SECMARK),
+ SYMBOL("label", 1 << IPCT_LABEL),
+ SYMBOL_LIST_END
+ },
+};
+
+const struct datatype ct_event_type = {
+ .type = TYPE_CT_EVENTBIT,
+ .name = "ct_event",
+ .desc = "conntrack event bits",
+ .byteorder = BYTEORDER_HOST_ENDIAN,
+ .size = 4 * BITS_PER_BYTE,
+ .basetype = &bitmask_type,
+ .sym_tbl = &ct_events_tbl,
+};
+
+#define CT_LABEL_BIT_SIZE 128
+
+const char *ct_label2str(const struct symbol_table *ct_label_tbl,
+ unsigned long value)
+{
+ const struct symbolic_constant *s;
+
+ for (s = ct_label_tbl->symbols; s->identifier; s++) {
+ if (value == s->value)
+ return s->identifier;
+ }
+
+ return NULL;
+}
+
+static void ct_label_type_print(const struct expr *expr,
+ struct output_ctx *octx)
+{
+ unsigned long bit = mpz_scan1(expr->value, 0);
+ const char *labelstr = ct_label2str(octx->tbl.ct_label, bit);
+
+ if (labelstr) {
+ nft_print(octx, "\"%s\"", labelstr);
+ return;
+ }
+ /* can happen when connlabel.conf is altered after rules were added */
+ nft_print(octx, "%lu", bit);
+}
+
+static struct error_record *ct_label_type_parse(struct parse_ctx *ctx,
+ const struct expr *sym,
+ struct expr **res)
+{
+ const struct symbolic_constant *s;
+ const struct datatype *dtype;
+ uint8_t data[CT_LABEL_BIT_SIZE / BITS_PER_BYTE];
+ uint64_t bit;
+ mpz_t value;
+
+ for (s = ctx->tbl->ct_label->symbols; s->identifier != NULL; s++) {
+ if (!strcmp(sym->identifier, s->identifier))
+ break;
+ }
+
+ dtype = sym->dtype;
+ if (s->identifier == NULL) {
+ char *ptr;
+
+ errno = 0;
+ bit = strtoull(sym->identifier, &ptr, 0);
+ if (*ptr)
+ return error(&sym->location, "%s: could not parse %s \"%s\"",
+ CONNLABEL_CONF, dtype->desc, sym->identifier);
+ if (errno)
+ return error(&sym->location, "%s: could not parse %s \"%s\": %s",
+ CONNLABEL_CONF, dtype->desc, sym->identifier, strerror(errno));
+
+ } else {
+ bit = s->value;
+ }
+
+ if (bit >= CT_LABEL_BIT_SIZE)
+ return error(&sym->location, "%s: bit %" PRIu64 " out of range (%u max)",
+ sym->identifier, bit, CT_LABEL_BIT_SIZE);
+
+ mpz_init2(value, dtype->size);
+ mpz_setbit(value, bit);
+ mpz_export_data(data, value, BYTEORDER_HOST_ENDIAN, sizeof(data));
+
+ *res = constant_expr_alloc(&sym->location, dtype,
+ dtype->byteorder, CT_LABEL_BIT_SIZE, data);
+ mpz_clear(value);
+ return NULL;
+}
+
+const struct datatype ct_label_type = {
+ .type = TYPE_CT_LABEL,
+ .name = "ct_label",
+ .desc = "conntrack label",
+ .byteorder = BYTEORDER_HOST_ENDIAN,
+ .size = CT_LABEL_BIT_SIZE,
+ .basetype = &bitmask_type,
+ .print = ct_label_type_print,
+ .json = ct_label_type_json,
+ .parse = ct_label_type_parse,
+};
+
+void ct_label_table_init(struct nft_ctx *ctx)
+{
+ ctx->output.tbl.ct_label = rt_symbol_table_init(CONNLABEL_CONF);
+}
+
+void ct_label_table_exit(struct nft_ctx *ctx)
+{
+ rt_symbol_table_free(ctx->output.tbl.ct_label);
+}
+
+#ifndef NF_CT_HELPER_NAME_LEN
+#define NF_CT_HELPER_NAME_LEN 16
+#endif
+
+const struct ct_template ct_templates[__NFT_CT_MAX] = {
+ [NFT_CT_STATE] = CT_TEMPLATE("state", &ct_state_type,
+ BYTEORDER_HOST_ENDIAN,
+ 4 * BITS_PER_BYTE),
+ [NFT_CT_DIRECTION] = CT_TEMPLATE("direction", &ct_dir_type,
+ BYTEORDER_HOST_ENDIAN,
+ BITS_PER_BYTE),
+ [NFT_CT_STATUS] = CT_TEMPLATE("status", &ct_status_type,
+ BYTEORDER_HOST_ENDIAN,
+ 4 * BITS_PER_BYTE),
+ [NFT_CT_MARK] = CT_TEMPLATE("mark", &mark_type,
+ BYTEORDER_HOST_ENDIAN,
+ 4 * BITS_PER_BYTE),
+ [NFT_CT_EXPIRATION] = CT_TEMPLATE("expiration", &time_type,
+ BYTEORDER_HOST_ENDIAN,
+ 4 * BITS_PER_BYTE),
+ [NFT_CT_HELPER] = CT_TEMPLATE("helper", &string_type,
+ BYTEORDER_HOST_ENDIAN,
+ NF_CT_HELPER_NAME_LEN * BITS_PER_BYTE),
+ [NFT_CT_L3PROTOCOL] = CT_TEMPLATE("l3proto", &nfproto_type,
+ BYTEORDER_HOST_ENDIAN,
+ BITS_PER_BYTE),
+ [NFT_CT_SRC] = CT_TEMPLATE("saddr", &invalid_type,
+ BYTEORDER_BIG_ENDIAN, 0),
+ [NFT_CT_DST] = CT_TEMPLATE("daddr", &invalid_type,
+ BYTEORDER_BIG_ENDIAN, 0),
+ [NFT_CT_PROTOCOL] = CT_TEMPLATE("protocol", &inet_protocol_type,
+ BYTEORDER_BIG_ENDIAN,
+ BITS_PER_BYTE),
+ [NFT_CT_PROTO_SRC] = CT_TEMPLATE("proto-src", &inet_service_type,
+ BYTEORDER_BIG_ENDIAN,
+ 2 * BITS_PER_BYTE),
+ [NFT_CT_PROTO_DST] = CT_TEMPLATE("proto-dst", &inet_service_type,
+ BYTEORDER_BIG_ENDIAN,
+ 2 * BITS_PER_BYTE),
+ [NFT_CT_LABELS] = CT_TEMPLATE("label", &ct_label_type,
+ BYTEORDER_HOST_ENDIAN,
+ CT_LABEL_BIT_SIZE),
+ [NFT_CT_BYTES] = CT_TEMPLATE("bytes", &integer_type,
+ BYTEORDER_HOST_ENDIAN, 64),
+ [NFT_CT_PKTS] = CT_TEMPLATE("packets", &integer_type,
+ BYTEORDER_HOST_ENDIAN, 64),
+ [NFT_CT_AVGPKT] = CT_TEMPLATE("avgpkt", &integer_type,
+ BYTEORDER_HOST_ENDIAN, 64),
+ [NFT_CT_ZONE] = CT_TEMPLATE("zone", &integer_type,
+ BYTEORDER_HOST_ENDIAN, 16),
+ [NFT_CT_EVENTMASK] = CT_TEMPLATE("event", &ct_event_type,
+ BYTEORDER_HOST_ENDIAN, 32),
+ [NFT_CT_SRC_IP] = CT_TEMPLATE("ip saddr", &ipaddr_type,
+ BYTEORDER_BIG_ENDIAN, 32),
+ [NFT_CT_DST_IP] = CT_TEMPLATE("ip daddr", &ipaddr_type,
+ BYTEORDER_BIG_ENDIAN, 32),
+ [NFT_CT_SRC_IP6] = CT_TEMPLATE("ip6 saddr", &ip6addr_type,
+ BYTEORDER_BIG_ENDIAN, 128),
+ [NFT_CT_DST_IP6] = CT_TEMPLATE("ip6 daddr", &ip6addr_type,
+ BYTEORDER_BIG_ENDIAN, 128),
+ [NFT_CT_SECMARK] = CT_TEMPLATE("secmark", &integer_type,
+ BYTEORDER_HOST_ENDIAN, 32),
+ [NFT_CT_ID] = CT_TEMPLATE("id", &integer_type,
+ BYTEORDER_BIG_ENDIAN, 32),
+};
+
+static void ct_print(enum nft_ct_keys key, int8_t dir, uint8_t nfproto,
+ struct output_ctx *octx)
+{
+ const char *dirstr = ct_dir2str(dir);
+ const struct proto_desc *desc;
+
+ nft_print(octx, "ct ");
+ if (dir < 0)
+ goto done;
+
+ if (dirstr)
+ nft_print(octx, "%s ", dirstr);
+
+ switch (key) {
+ case NFT_CT_SRC:
+ case NFT_CT_DST:
+ desc = proto_find_upper(&proto_inet, nfproto);
+ if (desc)
+ nft_print(octx, "%s ", desc->name);
+ break;
+ default:
+ break;
+ }
+
+ done:
+ nft_print(octx, "%s", ct_templates[key].token);
+}
+
+static void ct_expr_print(const struct expr *expr, struct output_ctx *octx)
+{
+ ct_print(expr->ct.key, expr->ct.direction, expr->ct.nfproto, octx);
+}
+
+static bool ct_expr_cmp(const struct expr *e1, const struct expr *e2)
+{
+ if (e1->ct.key != e2->ct.key)
+ return false;
+
+ return e1->ct.direction == e2->ct.direction;
+}
+
+static void ct_expr_clone(struct expr *new, const struct expr *expr)
+{
+ new->ct = expr->ct;
+}
+
+static void ct_expr_pctx_update(struct proto_ctx *ctx,
+ const struct location *loc,
+ const struct expr *left,
+ const struct expr *right)
+{
+ const struct proto_desc *base = NULL, *desc;
+ uint32_t nhproto;
+
+ nhproto = mpz_get_uint32(right->value);
+
+ base = ctx->protocol[left->ct.base].desc;
+ if (!base)
+ return;
+ desc = proto_find_upper(base, nhproto);
+ if (!desc)
+ return;
+
+ proto_ctx_update(ctx, left->ct.base + 1, loc, desc);
+}
+
+#define NFTNL_UDATA_CT_KEY 0
+#define NFTNL_UDATA_CT_DIR 1
+#define NFTNL_UDATA_CT_MAX 2
+
+static int ct_expr_build_udata(struct nftnl_udata_buf *udbuf,
+ const struct expr *expr)
+{
+ nftnl_udata_put_u32(udbuf, NFTNL_UDATA_CT_KEY, expr->ct.key);
+ nftnl_udata_put_u32(udbuf, NFTNL_UDATA_CT_DIR, expr->ct.direction);
+
+ return 0;
+}
+
+static int ct_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_CT_KEY:
+ case NFTNL_UDATA_CT_DIR:
+ if (len != sizeof(uint32_t))
+ return -1;
+ break;
+ default:
+ return 0;
+ }
+
+ ud[type] = attr;
+ return 0;
+}
+
+static struct expr *ct_expr_parse_udata(const struct nftnl_udata *attr)
+{
+ const struct nftnl_udata *ud[NFTNL_UDATA_CT_MAX + 1] = {};
+ uint32_t key, dir;
+ int err;
+
+ err = nftnl_udata_parse(nftnl_udata_get(attr), nftnl_udata_len(attr),
+ ct_parse_udata, ud);
+ if (err < 0)
+ return NULL;
+
+ if (!ud[NFTNL_UDATA_CT_KEY] ||
+ !ud[NFTNL_UDATA_CT_DIR])
+ return NULL;
+
+ key = nftnl_udata_get_u32(ud[NFTNL_UDATA_CT_KEY]);
+ dir = nftnl_udata_get_u32(ud[NFTNL_UDATA_CT_DIR]);
+
+ return ct_expr_alloc(&internal_location, key, dir);
+}
+
+const struct expr_ops ct_expr_ops = {
+ .type = EXPR_CT,
+ .name = "ct",
+ .print = ct_expr_print,
+ .json = ct_expr_json,
+ .cmp = ct_expr_cmp,
+ .clone = ct_expr_clone,
+ .pctx_update = ct_expr_pctx_update,
+ .parse_udata = ct_expr_parse_udata,
+ .build_udata = ct_expr_build_udata,
+};
+
+struct expr *ct_expr_alloc(const struct location *loc, enum nft_ct_keys key,
+ int8_t direction)
+{
+ const struct ct_template *tmpl = &ct_templates[key];
+ struct expr *expr;
+
+ expr = expr_alloc(loc, EXPR_CT, tmpl->dtype,
+ tmpl->byteorder, tmpl->len);
+ expr->ct.key = key;
+ expr->ct.direction = direction;
+
+ switch (key) {
+ case NFT_CT_SRC:
+ case NFT_CT_DST:
+ expr->ct.base = PROTO_BASE_NETWORK_HDR;
+ break;
+ case NFT_CT_PROTO_SRC:
+ case NFT_CT_PROTO_DST:
+ expr->ct.base = PROTO_BASE_TRANSPORT_HDR;
+ break;
+ case NFT_CT_PROTOCOL:
+ expr->flags = EXPR_F_PROTOCOL;
+ expr->ct.base = PROTO_BASE_NETWORK_HDR;
+ break;
+ case NFT_CT_L3PROTOCOL:
+ expr->flags = EXPR_F_PROTOCOL;
+ expr->ct.base = PROTO_BASE_LL_HDR;
+ break;
+ default:
+ break;
+ }
+
+ return expr;
+}
+
+void ct_expr_update_type(struct proto_ctx *ctx, struct expr *expr)
+{
+ const struct proto_desc *desc;
+
+ desc = ctx->protocol[expr->ct.base].desc;
+
+ switch (expr->ct.key) {
+ case NFT_CT_SRC:
+ case NFT_CT_DST:
+ if (desc == &proto_ip) {
+ datatype_set(expr, &ipaddr_type);
+ expr->ct.nfproto = NFPROTO_IPV4;
+ } else if (desc == &proto_ip6) {
+ datatype_set(expr, &ip6addr_type);
+ expr->ct.nfproto = NFPROTO_IPV6;
+ }
+
+ expr->len = expr->dtype->size;
+ break;
+ case NFT_CT_PROTO_SRC:
+ case NFT_CT_PROTO_DST:
+ if (desc == NULL)
+ break;
+ datatype_set(expr, &inet_service_type);
+ break;
+ case NFT_CT_SRC_IP:
+ case NFT_CT_DST_IP:
+ expr->dtype = &ipaddr_type;
+ expr->len = expr->dtype->size;
+ break;
+ case NFT_CT_SRC_IP6:
+ case NFT_CT_DST_IP6:
+ expr->dtype = &ip6addr_type;
+ expr->len = expr->dtype->size;
+ break;
+ default:
+ break;
+ }
+}
+
+static void ct_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
+{
+ ct_print(stmt->ct.key, stmt->ct.direction, 0, octx);
+ nft_print(octx, " set ");
+ expr_print(stmt->ct.expr, octx);
+}
+
+static void ct_stmt_destroy(struct stmt *stmt)
+{
+ expr_free(stmt->ct.expr);
+}
+
+static const struct stmt_ops ct_stmt_ops = {
+ .type = STMT_CT,
+ .name = "ct",
+ .print = ct_stmt_print,
+ .json = ct_stmt_json,
+ .destroy = ct_stmt_destroy,
+};
+
+struct stmt *ct_stmt_alloc(const struct location *loc, enum nft_ct_keys key,
+ int8_t direction, struct expr *expr)
+{
+ struct stmt *stmt;
+
+ stmt = stmt_alloc(loc, &ct_stmt_ops);
+ stmt->ct.key = key;
+ stmt->ct.tmpl = &ct_templates[key];
+ stmt->ct.expr = expr;
+ stmt->ct.direction = direction;
+
+ return stmt;
+}
+
+static void notrack_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
+{
+ nft_print(octx, "notrack");
+}
+
+static const struct stmt_ops notrack_stmt_ops = {
+ .type = STMT_NOTRACK,
+ .name = "notrack",
+ .print = notrack_stmt_print,
+ .json = notrack_stmt_json,
+};
+
+struct stmt *notrack_stmt_alloc(const struct location *loc)
+{
+ return stmt_alloc(loc, &notrack_stmt_ops);
+}
+
+static void flow_offload_stmt_print(const struct stmt *stmt,
+ struct output_ctx *octx)
+{
+ nft_print(octx, "flow add @%s", stmt->flow.table_name);
+}
+
+static void flow_offload_stmt_destroy(struct stmt *stmt)
+{
+ xfree(stmt->flow.table_name);
+}
+
+static const struct stmt_ops flow_offload_stmt_ops = {
+ .type = STMT_FLOW_OFFLOAD,
+ .name = "flow_offload",
+ .print = flow_offload_stmt_print,
+ .destroy = flow_offload_stmt_destroy,
+ .json = flow_offload_stmt_json,
+};
+
+struct stmt *flow_offload_stmt_alloc(const struct location *loc,
+ const char *table_name)
+{
+ struct stmt *stmt;
+
+ stmt = stmt_alloc(loc, &flow_offload_stmt_ops);
+ stmt->flow.table_name = table_name;
+
+ return stmt;
+}