netfilter: nft_payload: layer 4 checksum adjustment for pseudoheader fields
This patch adds a new flag that signals the kernel to update layer 4 checksum if the packet field belongs to the layer 4 pseudoheader. This implicitly provides stateless NAT 1:1 that is useful under very specific usecases. Since rules mangling layer 3 fields that are part of the pseudoheader may potentially convey any layer 4 packet, we have to deal with the layer 4 checksum adjustment using protocol specific code. This patch adds support for TCP, UDP and ICMPv6, since they include the pseudoheader in the layer 4 checksum calculation. ICMP doesn't, so we can skip it. Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
parent
e0ffdbc78d
commit
1814096980
|
@ -45,6 +45,7 @@ struct nft_payload_set {
|
||||||
enum nft_registers sreg:8;
|
enum nft_registers sreg:8;
|
||||||
u8 csum_type;
|
u8 csum_type;
|
||||||
u8 csum_offset;
|
u8 csum_offset;
|
||||||
|
u8 csum_flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct nft_expr_ops nft_payload_fast_ops;
|
extern const struct nft_expr_ops nft_payload_fast_ops;
|
||||||
|
|
|
@ -659,6 +659,10 @@ enum nft_payload_csum_types {
|
||||||
NFT_PAYLOAD_CSUM_INET,
|
NFT_PAYLOAD_CSUM_INET,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum nft_payload_csum_flags {
|
||||||
|
NFT_PAYLOAD_L4CSUM_PSEUDOHDR = (1 << 0),
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* enum nft_payload_attributes - nf_tables payload expression netlink attributes
|
* enum nft_payload_attributes - nf_tables payload expression netlink attributes
|
||||||
*
|
*
|
||||||
|
@ -669,6 +673,7 @@ enum nft_payload_csum_types {
|
||||||
* @NFTA_PAYLOAD_SREG: source register to load data from (NLA_U32: nft_registers)
|
* @NFTA_PAYLOAD_SREG: source register to load data from (NLA_U32: nft_registers)
|
||||||
* @NFTA_PAYLOAD_CSUM_TYPE: checksum type (NLA_U32)
|
* @NFTA_PAYLOAD_CSUM_TYPE: checksum type (NLA_U32)
|
||||||
* @NFTA_PAYLOAD_CSUM_OFFSET: checksum offset relative to base (NLA_U32)
|
* @NFTA_PAYLOAD_CSUM_OFFSET: checksum offset relative to base (NLA_U32)
|
||||||
|
* @NFTA_PAYLOAD_CSUM_FLAGS: checksum flags (NLA_U32)
|
||||||
*/
|
*/
|
||||||
enum nft_payload_attributes {
|
enum nft_payload_attributes {
|
||||||
NFTA_PAYLOAD_UNSPEC,
|
NFTA_PAYLOAD_UNSPEC,
|
||||||
|
@ -679,6 +684,7 @@ enum nft_payload_attributes {
|
||||||
NFTA_PAYLOAD_SREG,
|
NFTA_PAYLOAD_SREG,
|
||||||
NFTA_PAYLOAD_CSUM_TYPE,
|
NFTA_PAYLOAD_CSUM_TYPE,
|
||||||
NFTA_PAYLOAD_CSUM_OFFSET,
|
NFTA_PAYLOAD_CSUM_OFFSET,
|
||||||
|
NFTA_PAYLOAD_CSUM_FLAGS,
|
||||||
__NFTA_PAYLOAD_MAX
|
__NFTA_PAYLOAD_MAX
|
||||||
};
|
};
|
||||||
#define NFTA_PAYLOAD_MAX (__NFTA_PAYLOAD_MAX - 1)
|
#define NFTA_PAYLOAD_MAX (__NFTA_PAYLOAD_MAX - 1)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2008-2009 Patrick McHardy <kaber@trash.net>
|
* Copyright (c) 2008-2009 Patrick McHardy <kaber@trash.net>
|
||||||
|
* Copyright (c) 2016 Pablo Neira Ayuso <pablo@netfilter.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
|
@ -17,6 +18,10 @@
|
||||||
#include <linux/netfilter/nf_tables.h>
|
#include <linux/netfilter/nf_tables.h>
|
||||||
#include <net/netfilter/nf_tables_core.h>
|
#include <net/netfilter/nf_tables_core.h>
|
||||||
#include <net/netfilter/nf_tables.h>
|
#include <net/netfilter/nf_tables.h>
|
||||||
|
/* For layer 4 checksum field offset. */
|
||||||
|
#include <linux/tcp.h>
|
||||||
|
#include <linux/udp.h>
|
||||||
|
#include <linux/icmpv6.h>
|
||||||
|
|
||||||
/* add vlan header into the user buffer for if tag was removed by offloads */
|
/* add vlan header into the user buffer for if tag was removed by offloads */
|
||||||
static bool
|
static bool
|
||||||
|
@ -164,6 +169,87 @@ const struct nft_expr_ops nft_payload_fast_ops = {
|
||||||
.dump = nft_payload_dump,
|
.dump = nft_payload_dump,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline void nft_csum_replace(__sum16 *sum, __wsum fsum, __wsum tsum)
|
||||||
|
{
|
||||||
|
*sum = csum_fold(csum_add(csum_sub(~csum_unfold(*sum), fsum), tsum));
|
||||||
|
if (*sum == 0)
|
||||||
|
*sum = CSUM_MANGLED_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool nft_payload_udp_checksum(struct sk_buff *skb, unsigned int thoff)
|
||||||
|
{
|
||||||
|
struct udphdr *uh, _uh;
|
||||||
|
|
||||||
|
uh = skb_header_pointer(skb, thoff, sizeof(_uh), &_uh);
|
||||||
|
if (!uh)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return uh->check;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nft_payload_l4csum_offset(const struct nft_pktinfo *pkt,
|
||||||
|
struct sk_buff *skb,
|
||||||
|
unsigned int *l4csum_offset)
|
||||||
|
{
|
||||||
|
switch (pkt->tprot) {
|
||||||
|
case IPPROTO_TCP:
|
||||||
|
*l4csum_offset = offsetof(struct tcphdr, check);
|
||||||
|
break;
|
||||||
|
case IPPROTO_UDP:
|
||||||
|
if (!nft_payload_udp_checksum(skb, pkt->xt.thoff))
|
||||||
|
return -1;
|
||||||
|
/* Fall through. */
|
||||||
|
case IPPROTO_UDPLITE:
|
||||||
|
*l4csum_offset = offsetof(struct udphdr, check);
|
||||||
|
break;
|
||||||
|
case IPPROTO_ICMPV6:
|
||||||
|
*l4csum_offset = offsetof(struct icmp6hdr, icmp6_cksum);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*l4csum_offset += pkt->xt.thoff;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nft_payload_l4csum_update(const struct nft_pktinfo *pkt,
|
||||||
|
struct sk_buff *skb,
|
||||||
|
__wsum fsum, __wsum tsum)
|
||||||
|
{
|
||||||
|
int l4csum_offset;
|
||||||
|
__sum16 sum;
|
||||||
|
|
||||||
|
/* If we cannot determine layer 4 checksum offset or this packet doesn't
|
||||||
|
* require layer 4 checksum recalculation, skip this packet.
|
||||||
|
*/
|
||||||
|
if (nft_payload_l4csum_offset(pkt, skb, &l4csum_offset) < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (skb_copy_bits(skb, l4csum_offset, &sum, sizeof(sum)) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Checksum mangling for an arbitrary amount of bytes, based on
|
||||||
|
* inet_proto_csum_replace*() functions.
|
||||||
|
*/
|
||||||
|
if (skb->ip_summed != CHECKSUM_PARTIAL) {
|
||||||
|
nft_csum_replace(&sum, fsum, tsum);
|
||||||
|
if (skb->ip_summed == CHECKSUM_COMPLETE) {
|
||||||
|
skb->csum = ~csum_add(csum_sub(~(skb->csum), fsum),
|
||||||
|
tsum);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sum = ~csum_fold(csum_add(csum_sub(csum_unfold(sum), fsum),
|
||||||
|
tsum));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skb_make_writable(skb, l4csum_offset + sizeof(sum)) ||
|
||||||
|
skb_store_bits(skb, l4csum_offset, &sum, sizeof(sum)) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void nft_payload_set_eval(const struct nft_expr *expr,
|
static void nft_payload_set_eval(const struct nft_expr *expr,
|
||||||
struct nft_regs *regs,
|
struct nft_regs *regs,
|
||||||
const struct nft_pktinfo *pkt)
|
const struct nft_pktinfo *pkt)
|
||||||
|
@ -204,14 +290,15 @@ static void nft_payload_set_eval(const struct nft_expr *expr,
|
||||||
|
|
||||||
fsum = skb_checksum(skb, offset, priv->len, 0);
|
fsum = skb_checksum(skb, offset, priv->len, 0);
|
||||||
tsum = csum_partial(src, priv->len, 0);
|
tsum = csum_partial(src, priv->len, 0);
|
||||||
sum = csum_fold(csum_add(csum_sub(~csum_unfold(sum), fsum),
|
nft_csum_replace(&sum, fsum, tsum);
|
||||||
tsum));
|
|
||||||
if (sum == 0)
|
|
||||||
sum = CSUM_MANGLED_0;
|
|
||||||
|
|
||||||
if (!skb_make_writable(skb, csum_offset + sizeof(sum)) ||
|
if (!skb_make_writable(skb, csum_offset + sizeof(sum)) ||
|
||||||
skb_store_bits(skb, csum_offset, &sum, sizeof(sum)) < 0)
|
skb_store_bits(skb, csum_offset, &sum, sizeof(sum)) < 0)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
|
if (priv->csum_flags &&
|
||||||
|
nft_payload_l4csum_update(pkt, skb, fsum, tsum) < 0)
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skb_make_writable(skb, max(offset + priv->len, 0)) ||
|
if (!skb_make_writable(skb, max(offset + priv->len, 0)) ||
|
||||||
|
@ -240,6 +327,15 @@ static int nft_payload_set_init(const struct nft_ctx *ctx,
|
||||||
if (tb[NFTA_PAYLOAD_CSUM_OFFSET])
|
if (tb[NFTA_PAYLOAD_CSUM_OFFSET])
|
||||||
priv->csum_offset =
|
priv->csum_offset =
|
||||||
ntohl(nla_get_be32(tb[NFTA_PAYLOAD_CSUM_OFFSET]));
|
ntohl(nla_get_be32(tb[NFTA_PAYLOAD_CSUM_OFFSET]));
|
||||||
|
if (tb[NFTA_PAYLOAD_CSUM_FLAGS]) {
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
flags = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_CSUM_FLAGS]));
|
||||||
|
if (flags & ~NFT_PAYLOAD_L4CSUM_PSEUDOHDR)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
priv->csum_flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
switch (priv->csum_type) {
|
switch (priv->csum_type) {
|
||||||
case NFT_PAYLOAD_CSUM_NONE:
|
case NFT_PAYLOAD_CSUM_NONE:
|
||||||
|
@ -262,7 +358,8 @@ static int nft_payload_set_dump(struct sk_buff *skb, const struct nft_expr *expr
|
||||||
nla_put_be32(skb, NFTA_PAYLOAD_LEN, htonl(priv->len)) ||
|
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_TYPE, htonl(priv->csum_type)) ||
|
||||||
nla_put_be32(skb, NFTA_PAYLOAD_CSUM_OFFSET,
|
nla_put_be32(skb, NFTA_PAYLOAD_CSUM_OFFSET,
|
||||||
htonl(priv->csum_offset)))
|
htonl(priv->csum_offset)) ||
|
||||||
|
nla_put_be32(skb, NFTA_PAYLOAD_CSUM_FLAGS, htonl(priv->csum_flags)))
|
||||||
goto nla_put_failure;
|
goto nla_put_failure;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue