diff mbox series

[iptables-nft,1/7] nft-shared: support native tcp port delinearize

Message ID 20220125165301.5960-2-fw@strlen.de
State Accepted, archived
Delegated to: Pablo Neira
Headers show
Series iptables: prefer native expressions for udp and tcp matches | expand

Commit Message

Florian Westphal Jan. 25, 2022, 4:52 p.m. UTC
This extends iptables-nft dissector to decode native tcp
port matching.  nft ruleset:

table ip filter {
        chain INPUT {
                type filter hook input priority filter; policy accept;
                tcp sport 12345
                tcp sport 12345 tcp dport 6789
                tcp sport < 1024
                tcp dport >= 1024
        }
}

$ iptables-nft-save
-A INPUT -p tcp -m tcp --sport 12345
-A INPUT -p tcp -m tcp --sport 12345 --dport 6789
-A INPUT -p tcp -m tcp --sport 0:1023
-A INPUT -p tcp -m tcp --dport 1024:65535

This would allow to extend iptables-nft to prefer
native payload expressions for --sport,dport in the future.

Also, parse_cmp must not clear the "payload" flag, this is because
cmp-based range expressions will contain following sequence:

payload => reg1
cmp reg1 > minv
cmp reg1 < maxv

... so second cmp would work.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 iptables/nft-shared.c | 178 +++++++++++++++++++++++++++++++++++++++++-
 iptables/nft-shared.h |   3 +
 2 files changed, 179 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/iptables/nft-shared.c b/iptables/nft-shared.c
index d0d0d558dde4..674c72b35ae7 100644
--- a/iptables/nft-shared.c
+++ b/iptables/nft-shared.c
@@ -468,6 +468,170 @@  static void nft_parse_bitwise(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
 	ctx->flags |= NFT_XT_CTX_BITWISE;
 }
 
+static struct xtables_match *
+nft_create_match(struct nft_xt_ctx *ctx,
+		 struct iptables_command_state *cs,
+		 const char *name)
+{
+	struct xtables_match *match;
+	struct xt_entry_match *m;
+	unsigned int size;
+
+	match = xtables_find_match(name, XTF_TRY_LOAD,
+				   &cs->matches);
+	if (!match)
+		return NULL;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_match)) + match->size;
+	m = xtables_calloc(1, size);
+	m->u.match_size = size;
+	m->u.user.revision = match->revision;
+
+	strcpy(m->u.user.name, match->name);
+	match->m = m;
+
+	xs_init_match(match);
+
+	return match;
+}
+
+static struct xt_tcp *nft_tcp_match(struct nft_xt_ctx *ctx,
+			            struct iptables_command_state *cs)
+{
+	struct xt_tcp *tcp = ctx->tcpudp.tcp;
+	struct xtables_match *match;
+
+	if (!tcp) {
+		match = nft_create_match(ctx, cs, "tcp");
+		if (!match)
+			return NULL;
+
+		tcp = (void*)match->m->data;
+		ctx->tcpudp.tcp = tcp;
+	}
+
+	return tcp;
+}
+
+static void nft_complete_tcp(struct nft_xt_ctx *ctx,
+			     struct iptables_command_state *cs,
+			     int sport, int dport,
+			     uint8_t op)
+{
+	struct xt_tcp *tcp = nft_tcp_match(ctx, cs);
+
+	if (!tcp)
+		return;
+
+	if (sport >= 0) {
+		switch (op) {
+		case NFT_CMP_NEQ:
+			tcp->invflags |= XT_TCP_INV_SRCPT;
+			/* fallthrough */
+		case NFT_CMP_EQ:
+			tcp->spts[0] = sport;
+			tcp->spts[1] = sport;
+			break;
+		case NFT_CMP_LT:
+			tcp->spts[1] = sport > 1 ? sport - 1 : 1;
+			break;
+		case NFT_CMP_LTE:
+			tcp->spts[1] = sport;
+			break;
+		case NFT_CMP_GT:
+			tcp->spts[0] = sport < 0xffff ? sport + 1 : 0xffff;
+			break;
+		case NFT_CMP_GTE:
+			tcp->spts[0] = sport;
+			break;
+		}
+	}
+
+	if (dport >= 0) {
+		switch (op) {
+		case NFT_CMP_NEQ:
+			tcp->invflags |= XT_TCP_INV_DSTPT;
+			/* fallthrough */
+		case NFT_CMP_EQ:
+			tcp->dpts[0] = dport;
+			tcp->dpts[1] = dport;
+			break;
+		case NFT_CMP_LT:
+			tcp->dpts[1] = dport > 1 ? dport - 1 : 1;
+			break;
+		case NFT_CMP_LTE:
+			tcp->dpts[1] = dport;
+			break;
+		case NFT_CMP_GT:
+			tcp->dpts[0] = dport < 0xffff ? dport + 1 : 0xffff;
+			break;
+		case NFT_CMP_GTE:
+			tcp->dpts[0] = dport;
+			break;
+		}
+	}
+}
+
+static void nft_complete_th_port(struct nft_xt_ctx *ctx,
+				 struct iptables_command_state *cs,
+				 uint8_t proto,
+				 int sport, int dport, uint8_t op)
+{
+	switch (proto) {
+	case IPPROTO_TCP:
+		nft_complete_tcp(ctx, cs, sport, dport, op);
+		break;
+	}
+}
+
+static void nft_complete_transport(struct nft_xt_ctx *ctx,
+				   struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	uint32_t sdport;
+	uint16_t port;
+	uint8_t proto, op;
+	unsigned int len;
+
+	switch (ctx->h->family) {
+	case NFPROTO_IPV4:
+		proto = ctx->cs->fw.ip.proto;
+		break;
+	case NFPROTO_IPV6:
+		proto = ctx->cs->fw6.ipv6.proto;
+		break;
+	default:
+		proto = 0;
+		break;
+	}
+
+	nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len);
+	op = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP);
+
+	switch(ctx->payload.offset) {
+	case 0: /* th->sport */
+		switch (len) {
+		case 2: /* load sport only */
+			port = ntohs(nftnl_expr_get_u16(e, NFTNL_EXPR_CMP_DATA));
+			nft_complete_th_port(ctx, cs, proto, port, -1, op);
+			return;
+		case 4: /* load both src and dst port */
+			sdport = ntohl(nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_DATA));
+			nft_complete_th_port(ctx, cs, proto, sdport >> 16, sdport & 0xffff, op);
+			return;
+		}
+		break;
+	case 2: /* th->dport */
+		switch (len) {
+		case 2:
+			port = ntohs(nftnl_expr_get_u16(e, NFTNL_EXPR_CMP_DATA));
+			nft_complete_th_port(ctx, cs, proto, -1, port, op);
+			return;
+		}
+		break;
+	}
+}
+
 static void nft_parse_cmp(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
 {
 	void *data = ctx->cs;
@@ -483,8 +647,18 @@  static void nft_parse_cmp(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
 	}
 	/* bitwise context is interpreted from payload */
 	if (ctx->flags & NFT_XT_CTX_PAYLOAD) {
-		ctx->h->ops->parse_payload(ctx, e, data);
-		ctx->flags &= ~NFT_XT_CTX_PAYLOAD;
+		switch (ctx->payload.base) {
+		case NFT_PAYLOAD_LL_HEADER:
+			if (ctx->h->family == NFPROTO_BRIDGE)
+				ctx->h->ops->parse_payload(ctx, e, data);
+			break;
+		case NFT_PAYLOAD_NETWORK_HEADER:
+			ctx->h->ops->parse_payload(ctx, e, data);
+			break;
+		case NFT_PAYLOAD_TRANSPORT_HEADER:
+			nft_complete_transport(ctx, e, data);
+			break;
+		}
 	}
 }
 
diff --git a/iptables/nft-shared.h b/iptables/nft-shared.h
index c3241f4b8c72..0a8be7099aa2 100644
--- a/iptables/nft-shared.h
+++ b/iptables/nft-shared.h
@@ -53,6 +53,9 @@  struct nft_xt_ctx {
 	struct nft_handle *h;
 	uint32_t flags;
 	const char *table;
+	union {
+		struct xt_tcp *tcp;
+	} tcpudp;
 
 	uint32_t reg;
 	struct {