]> git.ipfire.org Git - thirdparty/iptables.git/commitdiff
nft-shared: support native tcp port delinearize
authorFlorian Westphal <fw@strlen.de>
Tue, 25 Jan 2022 16:52:55 +0000 (17:52 +0100)
committerFlorian Westphal <fw@strlen.de>
Sat, 29 Jan 2022 12:33:36 +0000 (13:33 +0100)
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
iptables/nft-shared.h

index d0d0d558dde43492dda193bf46b94789ac99f622..061893cf876d1ec5fc0bcb9d4fda71ab94adee81 100644 (file)
@@ -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_parse_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_parse_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_parse_tcp(ctx, cs, sport, dport, op);
+               break;
+       }
+}
+
+static void nft_parse_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_parse_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_parse_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_parse_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_parse_transport(ctx, e, data);
+                       break;
+               }
        }
 }
 
index c3241f4b8c7264302e83cf9960c5696b9e81952b..0a8be7099aa20d47ab4d793925a7a8ca311b4603 100644 (file)
@@ -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 {