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
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 */
2227static 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+
167253static 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