1
1
/*
2
2
* Copyright (c) 2008-2009 Patrick McHardy <[email protected] >
3
+ * Copyright (c) 2016 Pablo Neira Ayuso <[email protected] >
3
4
*
4
5
* This program is free software; you can redistribute it and/or modify
5
6
* it under the terms of the GNU General Public License version 2 as
17
18
#include <linux/netfilter/nf_tables.h>
18
19
#include <net/netfilter/nf_tables_core.h>
19
20
#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>
20
25
21
26
/* add vlan header into the user buffer for if tag was removed by offloads */
22
27
static bool
@@ -164,6 +169,87 @@ const struct nft_expr_ops nft_payload_fast_ops = {
164
169
.dump = nft_payload_dump ,
165
170
};
166
171
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
+
167
253
static void nft_payload_set_eval (const struct nft_expr * expr ,
168
254
struct nft_regs * regs ,
169
255
const struct nft_pktinfo * pkt )
@@ -204,14 +290,15 @@ static void nft_payload_set_eval(const struct nft_expr *expr,
204
290
205
291
fsum = skb_checksum (skb , offset , priv -> len , 0 );
206
292
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 );
211
294
212
295
if (!skb_make_writable (skb , csum_offset + sizeof (sum )) ||
213
296
skb_store_bits (skb , csum_offset , & sum , sizeof (sum )) < 0 )
214
297
goto err ;
298
+
299
+ if (priv -> csum_flags &&
300
+ nft_payload_l4csum_update (pkt , skb , fsum , tsum ) < 0 )
301
+ goto err ;
215
302
}
216
303
217
304
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,
240
327
if (tb [NFTA_PAYLOAD_CSUM_OFFSET ])
241
328
priv -> csum_offset =
242
329
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
+ }
243
339
244
340
switch (priv -> csum_type ) {
245
341
case NFT_PAYLOAD_CSUM_NONE :
@@ -262,7 +358,8 @@ static int nft_payload_set_dump(struct sk_buff *skb, const struct nft_expr *expr
262
358
nla_put_be32 (skb , NFTA_PAYLOAD_LEN , htonl (priv -> len )) ||
263
359
nla_put_be32 (skb , NFTA_PAYLOAD_CSUM_TYPE , htonl (priv -> csum_type )) ||
264
360
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 )))
266
363
goto nla_put_failure ;
267
364
return 0 ;
268
365
0 commit comments