Skip to content

Commit 1814096

Browse files
committed
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 <[email protected]>
1 parent e0ffdbc commit 1814096

File tree

3 files changed

+109
-5
lines changed

3 files changed

+109
-5
lines changed

include/net/netfilter/nf_tables_core.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct nft_payload_set {
4545
enum nft_registers sreg:8;
4646
u8 csum_type;
4747
u8 csum_offset;
48+
u8 csum_flags;
4849
};
4950

5051
extern const struct nft_expr_ops nft_payload_fast_ops;

include/uapi/linux/netfilter/nf_tables.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,10 @@ enum nft_payload_csum_types {
659659
NFT_PAYLOAD_CSUM_INET,
660660
};
661661

662+
enum nft_payload_csum_flags {
663+
NFT_PAYLOAD_L4CSUM_PSEUDOHDR = (1 << 0),
664+
};
665+
662666
/**
663667
* enum nft_payload_attributes - nf_tables payload expression netlink attributes
664668
*
@@ -669,6 +673,7 @@ enum nft_payload_csum_types {
669673
* @NFTA_PAYLOAD_SREG: source register to load data from (NLA_U32: nft_registers)
670674
* @NFTA_PAYLOAD_CSUM_TYPE: checksum type (NLA_U32)
671675
* @NFTA_PAYLOAD_CSUM_OFFSET: checksum offset relative to base (NLA_U32)
676+
* @NFTA_PAYLOAD_CSUM_FLAGS: checksum flags (NLA_U32)
672677
*/
673678
enum nft_payload_attributes {
674679
NFTA_PAYLOAD_UNSPEC,
@@ -679,6 +684,7 @@ enum nft_payload_attributes {
679684
NFTA_PAYLOAD_SREG,
680685
NFTA_PAYLOAD_CSUM_TYPE,
681686
NFTA_PAYLOAD_CSUM_OFFSET,
687+
NFTA_PAYLOAD_CSUM_FLAGS,
682688
__NFTA_PAYLOAD_MAX
683689
};
684690
#define NFTA_PAYLOAD_MAX (__NFTA_PAYLOAD_MAX - 1)

net/netfilter/nft_payload.c

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright (c) 2008-2009 Patrick McHardy <[email protected]>
3+
* Copyright (c) 2016 Pablo Neira Ayuso <[email protected]>
34
*
45
* This program is free software; you can redistribute it and/or modify
56
* it under the terms of the GNU General Public License version 2 as
@@ -17,6 +18,10 @@
1718
#include <linux/netfilter/nf_tables.h>
1819
#include <net/netfilter/nf_tables_core.h>
1920
#include <net/netfilter/nf_tables.h>
21+
/* For layer 4 checksum field offset. */
22+
#include <linux/tcp.h>
23+
#include <linux/udp.h>
24+
#include <linux/icmpv6.h>
2025

2126
/* add vlan header into the user buffer for if tag was removed by offloads */
2227
static bool
@@ -164,6 +169,87 @@ const struct nft_expr_ops nft_payload_fast_ops = {
164169
.dump = nft_payload_dump,
165170
};
166171

172+
static inline void nft_csum_replace(__sum16 *sum, __wsum fsum, __wsum tsum)
173+
{
174+
*sum = csum_fold(csum_add(csum_sub(~csum_unfold(*sum), fsum), tsum));
175+
if (*sum == 0)
176+
*sum = CSUM_MANGLED_0;
177+
}
178+
179+
static bool nft_payload_udp_checksum(struct sk_buff *skb, unsigned int thoff)
180+
{
181+
struct udphdr *uh, _uh;
182+
183+
uh = skb_header_pointer(skb, thoff, sizeof(_uh), &_uh);
184+
if (!uh)
185+
return false;
186+
187+
return uh->check;
188+
}
189+
190+
static int nft_payload_l4csum_offset(const struct nft_pktinfo *pkt,
191+
struct sk_buff *skb,
192+
unsigned int *l4csum_offset)
193+
{
194+
switch (pkt->tprot) {
195+
case IPPROTO_TCP:
196+
*l4csum_offset = offsetof(struct tcphdr, check);
197+
break;
198+
case IPPROTO_UDP:
199+
if (!nft_payload_udp_checksum(skb, pkt->xt.thoff))
200+
return -1;
201+
/* Fall through. */
202+
case IPPROTO_UDPLITE:
203+
*l4csum_offset = offsetof(struct udphdr, check);
204+
break;
205+
case IPPROTO_ICMPV6:
206+
*l4csum_offset = offsetof(struct icmp6hdr, icmp6_cksum);
207+
break;
208+
default:
209+
return -1;
210+
}
211+
212+
*l4csum_offset += pkt->xt.thoff;
213+
return 0;
214+
}
215+
216+
static int nft_payload_l4csum_update(const struct nft_pktinfo *pkt,
217+
struct sk_buff *skb,
218+
__wsum fsum, __wsum tsum)
219+
{
220+
int l4csum_offset;
221+
__sum16 sum;
222+
223+
/* If we cannot determine layer 4 checksum offset or this packet doesn't
224+
* require layer 4 checksum recalculation, skip this packet.
225+
*/
226+
if (nft_payload_l4csum_offset(pkt, skb, &l4csum_offset) < 0)
227+
return 0;
228+
229+
if (skb_copy_bits(skb, l4csum_offset, &sum, sizeof(sum)) < 0)
230+
return -1;
231+
232+
/* Checksum mangling for an arbitrary amount of bytes, based on
233+
* inet_proto_csum_replace*() functions.
234+
*/
235+
if (skb->ip_summed != CHECKSUM_PARTIAL) {
236+
nft_csum_replace(&sum, fsum, tsum);
237+
if (skb->ip_summed == CHECKSUM_COMPLETE) {
238+
skb->csum = ~csum_add(csum_sub(~(skb->csum), fsum),
239+
tsum);
240+
}
241+
} else {
242+
sum = ~csum_fold(csum_add(csum_sub(csum_unfold(sum), fsum),
243+
tsum));
244+
}
245+
246+
if (!skb_make_writable(skb, l4csum_offset + sizeof(sum)) ||
247+
skb_store_bits(skb, l4csum_offset, &sum, sizeof(sum)) < 0)
248+
return -1;
249+
250+
return 0;
251+
}
252+
167253
static void nft_payload_set_eval(const struct nft_expr *expr,
168254
struct nft_regs *regs,
169255
const struct nft_pktinfo *pkt)
@@ -204,14 +290,15 @@ static void nft_payload_set_eval(const struct nft_expr *expr,
204290

205291
fsum = skb_checksum(skb, offset, priv->len, 0);
206292
tsum = csum_partial(src, priv->len, 0);
207-
sum = csum_fold(csum_add(csum_sub(~csum_unfold(sum), fsum),
208-
tsum));
209-
if (sum == 0)
210-
sum = CSUM_MANGLED_0;
293+
nft_csum_replace(&sum, fsum, tsum);
211294

212295
if (!skb_make_writable(skb, csum_offset + sizeof(sum)) ||
213296
skb_store_bits(skb, csum_offset, &sum, sizeof(sum)) < 0)
214297
goto err;
298+
299+
if (priv->csum_flags &&
300+
nft_payload_l4csum_update(pkt, skb, fsum, tsum) < 0)
301+
goto err;
215302
}
216303

217304
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,
240327
if (tb[NFTA_PAYLOAD_CSUM_OFFSET])
241328
priv->csum_offset =
242329
ntohl(nla_get_be32(tb[NFTA_PAYLOAD_CSUM_OFFSET]));
330+
if (tb[NFTA_PAYLOAD_CSUM_FLAGS]) {
331+
u32 flags;
332+
333+
flags = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_CSUM_FLAGS]));
334+
if (flags & ~NFT_PAYLOAD_L4CSUM_PSEUDOHDR)
335+
return -EINVAL;
336+
337+
priv->csum_flags = flags;
338+
}
243339

244340
switch (priv->csum_type) {
245341
case NFT_PAYLOAD_CSUM_NONE:
@@ -262,7 +358,8 @@ static int nft_payload_set_dump(struct sk_buff *skb, const struct nft_expr *expr
262358
nla_put_be32(skb, NFTA_PAYLOAD_LEN, htonl(priv->len)) ||
263359
nla_put_be32(skb, NFTA_PAYLOAD_CSUM_TYPE, htonl(priv->csum_type)) ||
264360
nla_put_be32(skb, NFTA_PAYLOAD_CSUM_OFFSET,
265-
htonl(priv->csum_offset)))
361+
htonl(priv->csum_offset)) ||
362+
nla_put_be32(skb, NFTA_PAYLOAD_CSUM_FLAGS, htonl(priv->csum_flags)))
266363
goto nla_put_failure;
267364
return 0;
268365

0 commit comments

Comments
 (0)