netfilter: nft_payload: add packet mangling support
authorPatrick McHardy <kaber@trash.net>
Tue, 24 Nov 2015 10:00:22 +0000 (10:00 +0000)
committerPablo Neira Ayuso <pablo@netfilter.org>
Wed, 25 Nov 2015 12:54:51 +0000 (13:54 +0100)
Add support for mangling packet payload. Checksum for the specified base
header is updated automatically if requested, however no updates for any
kind of pseudo headers are supported, meaning no stateless NAT is supported.

For checksum updates different checksumming methods can be specified. The
currently supported methods are NONE for no checksum updates, and INET for
internet type checksums.

Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/netfilter/nf_tables_core.h
include/uapi/linux/netfilter/nf_tables.h
net/netfilter/nft_payload.c

index c6f400cfaac8d76673d559bf041c5da4b32abf13..4ff5424909aa224f31b3ae2282693f79c4db1b52 100644 (file)
@@ -47,6 +47,15 @@ struct nft_payload {
        enum nft_registers      dreg:8;
 };
 
+struct nft_payload_set {
+       enum nft_payload_bases  base:8;
+       u8                      offset;
+       u8                      len;
+       enum nft_registers      sreg:8;
+       u8                      csum_type;
+       u8                      csum_offset;
+};
+
 extern const struct nft_expr_ops nft_payload_fast_ops;
 
 int nft_payload_module_init(void);
index d8c8a7c9d88a7068c00d2b1cebe251c3c3c0dde4..5f3ececf84b3244b7ac26fdaeeb26c2138a4f483 100644 (file)
@@ -597,6 +597,17 @@ enum nft_payload_bases {
        NFT_PAYLOAD_TRANSPORT_HEADER,
 };
 
+/**
+ * enum nft_payload_csum_types - nf_tables payload expression checksum types
+ *
+ * @NFT_PAYLOAD_CSUM_NONE: no checksumming
+ * @NFT_PAYLOAD_CSUM_INET: internet checksum (RFC 791)
+ */
+enum nft_payload_csum_types {
+       NFT_PAYLOAD_CSUM_NONE,
+       NFT_PAYLOAD_CSUM_INET,
+};
+
 /**
  * enum nft_payload_attributes - nf_tables payload expression netlink attributes
  *
@@ -604,6 +615,9 @@ enum nft_payload_bases {
  * @NFTA_PAYLOAD_BASE: payload base (NLA_U32: nft_payload_bases)
  * @NFTA_PAYLOAD_OFFSET: payload offset relative to base (NLA_U32)
  * @NFTA_PAYLOAD_LEN: payload length (NLA_U32)
+ * @NFTA_PAYLOAD_SREG: source register to load data from (NLA_U32: nft_registers)
+ * @NFTA_PAYLOAD_CSUM_TYPE: checksum type (NLA_U32)
+ * @NFTA_PAYLOAD_CSUM_OFFSET: checksum offset relative to base (NLA_U32)
  */
 enum nft_payload_attributes {
        NFTA_PAYLOAD_UNSPEC,
@@ -611,6 +625,9 @@ enum nft_payload_attributes {
        NFTA_PAYLOAD_BASE,
        NFTA_PAYLOAD_OFFSET,
        NFTA_PAYLOAD_LEN,
+       NFTA_PAYLOAD_SREG,
+       NFTA_PAYLOAD_CSUM_TYPE,
+       NFTA_PAYLOAD_CSUM_OFFSET,
        __NFTA_PAYLOAD_MAX
 };
 #define NFTA_PAYLOAD_MAX       (__NFTA_PAYLOAD_MAX - 1)
index 09b4b07eb67644fdc90ef357378c46d243b7a642..12cd4bf16d17d36f1c5c250771792d853443c853 100644 (file)
@@ -107,10 +107,13 @@ err:
 }
 
 static const struct nla_policy nft_payload_policy[NFTA_PAYLOAD_MAX + 1] = {
-       [NFTA_PAYLOAD_DREG]     = { .type = NLA_U32 },
-       [NFTA_PAYLOAD_BASE]     = { .type = NLA_U32 },
-       [NFTA_PAYLOAD_OFFSET]   = { .type = NLA_U32 },
-       [NFTA_PAYLOAD_LEN]      = { .type = NLA_U32 },
+       [NFTA_PAYLOAD_SREG]             = { .type = NLA_U32 },
+       [NFTA_PAYLOAD_DREG]             = { .type = NLA_U32 },
+       [NFTA_PAYLOAD_BASE]             = { .type = NLA_U32 },
+       [NFTA_PAYLOAD_OFFSET]           = { .type = NLA_U32 },
+       [NFTA_PAYLOAD_LEN]              = { .type = NLA_U32 },
+       [NFTA_PAYLOAD_CSUM_TYPE]        = { .type = NLA_U32 },
+       [NFTA_PAYLOAD_CSUM_OFFSET]      = { .type = NLA_U32 },
 };
 
 static int nft_payload_init(const struct nft_ctx *ctx,
@@ -160,6 +163,118 @@ const struct nft_expr_ops nft_payload_fast_ops = {
        .dump           = nft_payload_dump,
 };
 
+static void nft_payload_set_eval(const struct nft_expr *expr,
+                                struct nft_regs *regs,
+                                const struct nft_pktinfo *pkt)
+{
+       const struct nft_payload_set *priv = nft_expr_priv(expr);
+       struct sk_buff *skb = pkt->skb;
+       const u32 *src = &regs->data[priv->sreg];
+       int offset, csum_offset;
+       __wsum fsum, tsum;
+       __sum16 sum;
+
+       switch (priv->base) {
+       case NFT_PAYLOAD_LL_HEADER:
+               if (!skb_mac_header_was_set(skb))
+                       goto err;
+               offset = skb_mac_header(skb) - skb->data;
+               break;
+       case NFT_PAYLOAD_NETWORK_HEADER:
+               offset = skb_network_offset(skb);
+               break;
+       case NFT_PAYLOAD_TRANSPORT_HEADER:
+               offset = pkt->xt.thoff;
+               break;
+       default:
+               BUG();
+       }
+
+       csum_offset = offset + priv->csum_offset;
+       offset += priv->offset;
+
+       if (priv->csum_type == NFT_PAYLOAD_CSUM_INET &&
+           (priv->base != NFT_PAYLOAD_TRANSPORT_HEADER ||
+            skb->ip_summed != CHECKSUM_PARTIAL)) {
+               if (skb_copy_bits(skb, csum_offset, &sum, sizeof(sum)) < 0)
+                       goto err;
+
+               fsum = skb_checksum(skb, offset, priv->len, 0);
+               tsum = csum_partial(src, priv->len, 0);
+               sum = csum_fold(csum_add(csum_sub(~csum_unfold(sum), fsum),
+                                        tsum));
+               if (sum == 0)
+                       sum = CSUM_MANGLED_0;
+
+               if (!skb_make_writable(skb, csum_offset + sizeof(sum)) ||
+                   skb_store_bits(skb, csum_offset, &sum, sizeof(sum)) < 0)
+                       goto err;
+       }
+
+       if (!skb_make_writable(skb, max(offset + priv->len, 0)) ||
+           skb_store_bits(skb, offset, src, priv->len) < 0)
+               goto err;
+
+       return;
+err:
+       regs->verdict.code = NFT_BREAK;
+}
+
+static int nft_payload_set_init(const struct nft_ctx *ctx,
+                               const struct nft_expr *expr,
+                               const struct nlattr * const tb[])
+{
+       struct nft_payload_set *priv = nft_expr_priv(expr);
+
+       priv->base        = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_BASE]));
+       priv->offset      = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_OFFSET]));
+       priv->len         = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_LEN]));
+       priv->sreg        = nft_parse_register(tb[NFTA_PAYLOAD_SREG]);
+
+       if (tb[NFTA_PAYLOAD_CSUM_TYPE])
+               priv->csum_type =
+                       ntohl(nla_get_be32(tb[NFTA_PAYLOAD_CSUM_TYPE]));
+       if (tb[NFTA_PAYLOAD_CSUM_OFFSET])
+               priv->csum_offset =
+                       ntohl(nla_get_be32(tb[NFTA_PAYLOAD_CSUM_OFFSET]));
+
+       switch (priv->csum_type) {
+       case NFT_PAYLOAD_CSUM_NONE:
+       case NFT_PAYLOAD_CSUM_INET:
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return nft_validate_register_load(priv->sreg, priv->len);
+}
+
+static int nft_payload_set_dump(struct sk_buff *skb, const struct nft_expr *expr)
+{
+       const struct nft_payload_set *priv = nft_expr_priv(expr);
+
+       if (nft_dump_register(skb, NFTA_PAYLOAD_SREG, priv->sreg) ||
+           nla_put_be32(skb, NFTA_PAYLOAD_BASE, htonl(priv->base)) ||
+           nla_put_be32(skb, NFTA_PAYLOAD_OFFSET, htonl(priv->offset)) ||
+           nla_put_be32(skb, NFTA_PAYLOAD_LEN, htonl(priv->len)) ||
+           nla_put_be32(skb, NFTA_PAYLOAD_CSUM_TYPE, htonl(priv->csum_type)) ||
+           nla_put_be32(skb, NFTA_PAYLOAD_CSUM_OFFSET,
+                        htonl(priv->csum_offset)))
+               goto nla_put_failure;
+       return 0;
+
+nla_put_failure:
+       return -1;
+}
+
+static const struct nft_expr_ops nft_payload_set_ops = {
+       .type           = &nft_payload_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_payload_set)),
+       .eval           = nft_payload_set_eval,
+       .init           = nft_payload_set_init,
+       .dump           = nft_payload_set_dump,
+};
+
 static const struct nft_expr_ops *
 nft_payload_select_ops(const struct nft_ctx *ctx,
                       const struct nlattr * const tb[])
@@ -167,8 +282,7 @@ nft_payload_select_ops(const struct nft_ctx *ctx,
        enum nft_payload_bases base;
        unsigned int offset, len;
 
-       if (tb[NFTA_PAYLOAD_DREG] == NULL ||
-           tb[NFTA_PAYLOAD_BASE] == NULL ||
+       if (tb[NFTA_PAYLOAD_BASE] == NULL ||
            tb[NFTA_PAYLOAD_OFFSET] == NULL ||
            tb[NFTA_PAYLOAD_LEN] == NULL)
                return ERR_PTR(-EINVAL);
@@ -183,6 +297,15 @@ nft_payload_select_ops(const struct nft_ctx *ctx,
                return ERR_PTR(-EOPNOTSUPP);
        }
 
+       if (tb[NFTA_PAYLOAD_SREG] != NULL) {
+               if (tb[NFTA_PAYLOAD_DREG] != NULL)
+                       return ERR_PTR(-EINVAL);
+               return &nft_payload_set_ops;
+       }
+
+       if (tb[NFTA_PAYLOAD_DREG] == NULL)
+               return ERR_PTR(-EINVAL);
+
        offset = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_OFFSET]));
        len    = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_LEN]));