1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2013 Patrick McHardy <kaber@trash.net> |
4 | */ |
5 | |
6 | #include <linux/module.h> |
7 | #include <linux/skbuff.h> |
8 | #include <asm/unaligned.h> |
9 | #include <net/tcp.h> |
10 | #include <net/netns/generic.h> |
11 | #include <linux/proc_fs.h> |
12 | |
13 | #include <linux/netfilter_ipv6.h> |
14 | #include <linux/netfilter/nf_synproxy.h> |
15 | |
16 | #include <net/netfilter/nf_conntrack.h> |
17 | #include <net/netfilter/nf_conntrack_ecache.h> |
18 | #include <net/netfilter/nf_conntrack_extend.h> |
19 | #include <net/netfilter/nf_conntrack_seqadj.h> |
20 | #include <net/netfilter/nf_conntrack_synproxy.h> |
21 | #include <net/netfilter/nf_conntrack_zones.h> |
22 | #include <net/netfilter/nf_synproxy.h> |
23 | |
24 | unsigned int synproxy_net_id; |
25 | EXPORT_SYMBOL_GPL(synproxy_net_id); |
26 | |
27 | bool |
28 | synproxy_parse_options(const struct sk_buff *skb, unsigned int doff, |
29 | const struct tcphdr *th, struct synproxy_options *opts) |
30 | { |
31 | int length = (th->doff * 4) - sizeof(*th); |
32 | u8 buf[40], *ptr; |
33 | |
34 | if (unlikely(length < 0)) |
35 | return false; |
36 | |
37 | ptr = skb_header_pointer(skb, offset: doff + sizeof(*th), len: length, buffer: buf); |
38 | if (ptr == NULL) |
39 | return false; |
40 | |
41 | opts->options = 0; |
42 | while (length > 0) { |
43 | int opcode = *ptr++; |
44 | int opsize; |
45 | |
46 | switch (opcode) { |
47 | case TCPOPT_EOL: |
48 | return true; |
49 | case TCPOPT_NOP: |
50 | length--; |
51 | continue; |
52 | default: |
53 | if (length < 2) |
54 | return true; |
55 | opsize = *ptr++; |
56 | if (opsize < 2) |
57 | return true; |
58 | if (opsize > length) |
59 | return true; |
60 | |
61 | switch (opcode) { |
62 | case TCPOPT_MSS: |
63 | if (opsize == TCPOLEN_MSS) { |
64 | opts->mss_option = get_unaligned_be16(p: ptr); |
65 | opts->options |= NF_SYNPROXY_OPT_MSS; |
66 | } |
67 | break; |
68 | case TCPOPT_WINDOW: |
69 | if (opsize == TCPOLEN_WINDOW) { |
70 | opts->wscale = *ptr; |
71 | if (opts->wscale > TCP_MAX_WSCALE) |
72 | opts->wscale = TCP_MAX_WSCALE; |
73 | opts->options |= NF_SYNPROXY_OPT_WSCALE; |
74 | } |
75 | break; |
76 | case TCPOPT_TIMESTAMP: |
77 | if (opsize == TCPOLEN_TIMESTAMP) { |
78 | opts->tsval = get_unaligned_be32(p: ptr); |
79 | opts->tsecr = get_unaligned_be32(p: ptr + 4); |
80 | opts->options |= NF_SYNPROXY_OPT_TIMESTAMP; |
81 | } |
82 | break; |
83 | case TCPOPT_SACK_PERM: |
84 | if (opsize == TCPOLEN_SACK_PERM) |
85 | opts->options |= NF_SYNPROXY_OPT_SACK_PERM; |
86 | break; |
87 | } |
88 | |
89 | ptr += opsize - 2; |
90 | length -= opsize; |
91 | } |
92 | } |
93 | return true; |
94 | } |
95 | EXPORT_SYMBOL_GPL(synproxy_parse_options); |
96 | |
97 | static unsigned int |
98 | synproxy_options_size(const struct synproxy_options *opts) |
99 | { |
100 | unsigned int size = 0; |
101 | |
102 | if (opts->options & NF_SYNPROXY_OPT_MSS) |
103 | size += TCPOLEN_MSS_ALIGNED; |
104 | if (opts->options & NF_SYNPROXY_OPT_TIMESTAMP) |
105 | size += TCPOLEN_TSTAMP_ALIGNED; |
106 | else if (opts->options & NF_SYNPROXY_OPT_SACK_PERM) |
107 | size += TCPOLEN_SACKPERM_ALIGNED; |
108 | if (opts->options & NF_SYNPROXY_OPT_WSCALE) |
109 | size += TCPOLEN_WSCALE_ALIGNED; |
110 | |
111 | return size; |
112 | } |
113 | |
114 | static void |
115 | synproxy_build_options(struct tcphdr *th, const struct synproxy_options *opts) |
116 | { |
117 | __be32 *ptr = (__be32 *)(th + 1); |
118 | u8 options = opts->options; |
119 | |
120 | if (options & NF_SYNPROXY_OPT_MSS) |
121 | *ptr++ = htonl((TCPOPT_MSS << 24) | |
122 | (TCPOLEN_MSS << 16) | |
123 | opts->mss_option); |
124 | |
125 | if (options & NF_SYNPROXY_OPT_TIMESTAMP) { |
126 | if (options & NF_SYNPROXY_OPT_SACK_PERM) |
127 | *ptr++ = htonl((TCPOPT_SACK_PERM << 24) | |
128 | (TCPOLEN_SACK_PERM << 16) | |
129 | (TCPOPT_TIMESTAMP << 8) | |
130 | TCPOLEN_TIMESTAMP); |
131 | else |
132 | *ptr++ = htonl((TCPOPT_NOP << 24) | |
133 | (TCPOPT_NOP << 16) | |
134 | (TCPOPT_TIMESTAMP << 8) | |
135 | TCPOLEN_TIMESTAMP); |
136 | |
137 | *ptr++ = htonl(opts->tsval); |
138 | *ptr++ = htonl(opts->tsecr); |
139 | } else if (options & NF_SYNPROXY_OPT_SACK_PERM) |
140 | *ptr++ = htonl((TCPOPT_NOP << 24) | |
141 | (TCPOPT_NOP << 16) | |
142 | (TCPOPT_SACK_PERM << 8) | |
143 | TCPOLEN_SACK_PERM); |
144 | |
145 | if (options & NF_SYNPROXY_OPT_WSCALE) |
146 | *ptr++ = htonl((TCPOPT_NOP << 24) | |
147 | (TCPOPT_WINDOW << 16) | |
148 | (TCPOLEN_WINDOW << 8) | |
149 | opts->wscale); |
150 | } |
151 | |
152 | void synproxy_init_timestamp_cookie(const struct nf_synproxy_info *info, |
153 | struct synproxy_options *opts) |
154 | { |
155 | opts->tsecr = opts->tsval; |
156 | opts->tsval = tcp_clock_ms() & ~0x3f; |
157 | |
158 | if (opts->options & NF_SYNPROXY_OPT_WSCALE) { |
159 | opts->tsval |= opts->wscale; |
160 | opts->wscale = info->wscale; |
161 | } else |
162 | opts->tsval |= 0xf; |
163 | |
164 | if (opts->options & NF_SYNPROXY_OPT_SACK_PERM) |
165 | opts->tsval |= 1 << 4; |
166 | |
167 | if (opts->options & NF_SYNPROXY_OPT_ECN) |
168 | opts->tsval |= 1 << 5; |
169 | } |
170 | EXPORT_SYMBOL_GPL(synproxy_init_timestamp_cookie); |
171 | |
172 | static void |
173 | synproxy_check_timestamp_cookie(struct synproxy_options *opts) |
174 | { |
175 | opts->wscale = opts->tsecr & 0xf; |
176 | if (opts->wscale != 0xf) |
177 | opts->options |= NF_SYNPROXY_OPT_WSCALE; |
178 | |
179 | opts->options |= opts->tsecr & (1 << 4) ? NF_SYNPROXY_OPT_SACK_PERM : 0; |
180 | |
181 | opts->options |= opts->tsecr & (1 << 5) ? NF_SYNPROXY_OPT_ECN : 0; |
182 | } |
183 | |
184 | static unsigned int |
185 | synproxy_tstamp_adjust(struct sk_buff *skb, unsigned int protoff, |
186 | struct tcphdr *th, struct nf_conn *ct, |
187 | enum ip_conntrack_info ctinfo, |
188 | const struct nf_conn_synproxy *synproxy) |
189 | { |
190 | unsigned int optoff, optend; |
191 | __be32 *ptr, old; |
192 | |
193 | if (synproxy->tsoff == 0) |
194 | return 1; |
195 | |
196 | optoff = protoff + sizeof(struct tcphdr); |
197 | optend = protoff + th->doff * 4; |
198 | |
199 | if (skb_ensure_writable(skb, write_len: optend)) |
200 | return 0; |
201 | |
202 | while (optoff < optend) { |
203 | unsigned char *op = skb->data + optoff; |
204 | |
205 | switch (op[0]) { |
206 | case TCPOPT_EOL: |
207 | return 1; |
208 | case TCPOPT_NOP: |
209 | optoff++; |
210 | continue; |
211 | default: |
212 | if (optoff + 1 == optend || |
213 | optoff + op[1] > optend || |
214 | op[1] < 2) |
215 | return 0; |
216 | if (op[0] == TCPOPT_TIMESTAMP && |
217 | op[1] == TCPOLEN_TIMESTAMP) { |
218 | if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) { |
219 | ptr = (__be32 *)&op[2]; |
220 | old = *ptr; |
221 | *ptr = htonl(ntohl(*ptr) - |
222 | synproxy->tsoff); |
223 | } else { |
224 | ptr = (__be32 *)&op[6]; |
225 | old = *ptr; |
226 | *ptr = htonl(ntohl(*ptr) + |
227 | synproxy->tsoff); |
228 | } |
229 | inet_proto_csum_replace4(sum: &th->check, skb, |
230 | from: old, to: *ptr, pseudohdr: false); |
231 | return 1; |
232 | } |
233 | optoff += op[1]; |
234 | } |
235 | } |
236 | return 1; |
237 | } |
238 | |
239 | #ifdef CONFIG_PROC_FS |
240 | static void *synproxy_cpu_seq_start(struct seq_file *seq, loff_t *pos) |
241 | { |
242 | struct synproxy_net *snet = synproxy_pernet(net: seq_file_net(seq)); |
243 | int cpu; |
244 | |
245 | if (*pos == 0) |
246 | return SEQ_START_TOKEN; |
247 | |
248 | for (cpu = *pos - 1; cpu < nr_cpu_ids; cpu++) { |
249 | if (!cpu_possible(cpu)) |
250 | continue; |
251 | *pos = cpu + 1; |
252 | return per_cpu_ptr(snet->stats, cpu); |
253 | } |
254 | |
255 | return NULL; |
256 | } |
257 | |
258 | static void *synproxy_cpu_seq_next(struct seq_file *seq, void *v, loff_t *pos) |
259 | { |
260 | struct synproxy_net *snet = synproxy_pernet(net: seq_file_net(seq)); |
261 | int cpu; |
262 | |
263 | for (cpu = *pos; cpu < nr_cpu_ids; cpu++) { |
264 | if (!cpu_possible(cpu)) |
265 | continue; |
266 | *pos = cpu + 1; |
267 | return per_cpu_ptr(snet->stats, cpu); |
268 | } |
269 | (*pos)++; |
270 | return NULL; |
271 | } |
272 | |
273 | static void synproxy_cpu_seq_stop(struct seq_file *seq, void *v) |
274 | { |
275 | return; |
276 | } |
277 | |
278 | static int synproxy_cpu_seq_show(struct seq_file *seq, void *v) |
279 | { |
280 | struct synproxy_stats *stats = v; |
281 | |
282 | if (v == SEQ_START_TOKEN) { |
283 | seq_puts(m: seq, s: "entries\t\tsyn_received\t" |
284 | "cookie_invalid\tcookie_valid\t" |
285 | "cookie_retrans\tconn_reopened\n" ); |
286 | return 0; |
287 | } |
288 | |
289 | seq_printf(m: seq, fmt: "%08x\t%08x\t%08x\t%08x\t%08x\t%08x\n" , 0, |
290 | stats->syn_received, |
291 | stats->cookie_invalid, |
292 | stats->cookie_valid, |
293 | stats->cookie_retrans, |
294 | stats->conn_reopened); |
295 | |
296 | return 0; |
297 | } |
298 | |
299 | static const struct seq_operations synproxy_cpu_seq_ops = { |
300 | .start = synproxy_cpu_seq_start, |
301 | .next = synproxy_cpu_seq_next, |
302 | .stop = synproxy_cpu_seq_stop, |
303 | .show = synproxy_cpu_seq_show, |
304 | }; |
305 | |
306 | static int __net_init synproxy_proc_init(struct net *net) |
307 | { |
308 | if (!proc_create_net("synproxy" , 0444, net->proc_net_stat, |
309 | &synproxy_cpu_seq_ops, sizeof(struct seq_net_private))) |
310 | return -ENOMEM; |
311 | return 0; |
312 | } |
313 | |
314 | static void __net_exit synproxy_proc_exit(struct net *net) |
315 | { |
316 | remove_proc_entry("synproxy" , net->proc_net_stat); |
317 | } |
318 | #else |
319 | static int __net_init synproxy_proc_init(struct net *net) |
320 | { |
321 | return 0; |
322 | } |
323 | |
324 | static void __net_exit synproxy_proc_exit(struct net *net) |
325 | { |
326 | return; |
327 | } |
328 | #endif /* CONFIG_PROC_FS */ |
329 | |
330 | static int __net_init synproxy_net_init(struct net *net) |
331 | { |
332 | struct synproxy_net *snet = synproxy_pernet(net); |
333 | struct nf_conn *ct; |
334 | int err = -ENOMEM; |
335 | |
336 | ct = nf_ct_tmpl_alloc(net, zone: &nf_ct_zone_dflt, GFP_KERNEL); |
337 | if (!ct) |
338 | goto err1; |
339 | |
340 | if (!nfct_seqadj_ext_add(ct)) |
341 | goto err2; |
342 | if (!nfct_synproxy_ext_add(ct)) |
343 | goto err2; |
344 | |
345 | __set_bit(IPS_CONFIRMED_BIT, &ct->status); |
346 | snet->tmpl = ct; |
347 | |
348 | snet->stats = alloc_percpu(struct synproxy_stats); |
349 | if (snet->stats == NULL) |
350 | goto err2; |
351 | |
352 | err = synproxy_proc_init(net); |
353 | if (err < 0) |
354 | goto err3; |
355 | |
356 | return 0; |
357 | |
358 | err3: |
359 | free_percpu(pdata: snet->stats); |
360 | err2: |
361 | nf_ct_tmpl_free(tmpl: ct); |
362 | err1: |
363 | return err; |
364 | } |
365 | |
366 | static void __net_exit synproxy_net_exit(struct net *net) |
367 | { |
368 | struct synproxy_net *snet = synproxy_pernet(net); |
369 | |
370 | nf_ct_put(ct: snet->tmpl); |
371 | synproxy_proc_exit(net); |
372 | free_percpu(pdata: snet->stats); |
373 | } |
374 | |
375 | static struct pernet_operations synproxy_net_ops = { |
376 | .init = synproxy_net_init, |
377 | .exit = synproxy_net_exit, |
378 | .id = &synproxy_net_id, |
379 | .size = sizeof(struct synproxy_net), |
380 | }; |
381 | |
382 | static int __init synproxy_core_init(void) |
383 | { |
384 | return register_pernet_subsys(&synproxy_net_ops); |
385 | } |
386 | |
387 | static void __exit synproxy_core_exit(void) |
388 | { |
389 | unregister_pernet_subsys(&synproxy_net_ops); |
390 | } |
391 | |
392 | module_init(synproxy_core_init); |
393 | module_exit(synproxy_core_exit); |
394 | |
395 | static struct iphdr * |
396 | synproxy_build_ip(struct net *net, struct sk_buff *skb, __be32 saddr, |
397 | __be32 daddr) |
398 | { |
399 | struct iphdr *iph; |
400 | |
401 | skb_reset_network_header(skb); |
402 | iph = skb_put(skb, len: sizeof(*iph)); |
403 | iph->version = 4; |
404 | iph->ihl = sizeof(*iph) / 4; |
405 | iph->tos = 0; |
406 | iph->id = 0; |
407 | iph->frag_off = htons(IP_DF); |
408 | iph->ttl = READ_ONCE(net->ipv4.sysctl_ip_default_ttl); |
409 | iph->protocol = IPPROTO_TCP; |
410 | iph->check = 0; |
411 | iph->saddr = saddr; |
412 | iph->daddr = daddr; |
413 | |
414 | return iph; |
415 | } |
416 | |
417 | static void |
418 | synproxy_send_tcp(struct net *net, |
419 | const struct sk_buff *skb, struct sk_buff *nskb, |
420 | struct nf_conntrack *nfct, enum ip_conntrack_info ctinfo, |
421 | struct iphdr *niph, struct tcphdr *nth, |
422 | unsigned int tcp_hdr_size) |
423 | { |
424 | nth->check = ~tcp_v4_check(len: tcp_hdr_size, saddr: niph->saddr, daddr: niph->daddr, base: 0); |
425 | nskb->ip_summed = CHECKSUM_PARTIAL; |
426 | nskb->csum_start = (unsigned char *)nth - nskb->head; |
427 | nskb->csum_offset = offsetof(struct tcphdr, check); |
428 | |
429 | skb_dst_set_noref(skb: nskb, dst: skb_dst(skb)); |
430 | nskb->protocol = htons(ETH_P_IP); |
431 | if (ip_route_me_harder(net, sk: nskb->sk, skb: nskb, addr_type: RTN_UNSPEC)) |
432 | goto free_nskb; |
433 | |
434 | if (nfct) { |
435 | nf_ct_set(skb: nskb, ct: (struct nf_conn *)nfct, info: ctinfo); |
436 | nf_conntrack_get(nfct); |
437 | } |
438 | |
439 | ip_local_out(net, sk: nskb->sk, skb: nskb); |
440 | return; |
441 | |
442 | free_nskb: |
443 | kfree_skb(skb: nskb); |
444 | } |
445 | |
446 | void |
447 | synproxy_send_client_synack(struct net *net, |
448 | const struct sk_buff *skb, const struct tcphdr *th, |
449 | const struct synproxy_options *opts) |
450 | { |
451 | struct sk_buff *nskb; |
452 | struct iphdr *iph, *niph; |
453 | struct tcphdr *nth; |
454 | unsigned int tcp_hdr_size; |
455 | u16 mss = opts->mss_encode; |
456 | |
457 | iph = ip_hdr(skb); |
458 | |
459 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); |
460 | nskb = alloc_skb(size: sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, |
461 | GFP_ATOMIC); |
462 | if (!nskb) |
463 | return; |
464 | skb_reserve(skb: nskb, MAX_TCP_HEADER); |
465 | |
466 | niph = synproxy_build_ip(net, skb: nskb, saddr: iph->daddr, daddr: iph->saddr); |
467 | |
468 | skb_reset_transport_header(skb: nskb); |
469 | nth = skb_put(skb: nskb, len: tcp_hdr_size); |
470 | nth->source = th->dest; |
471 | nth->dest = th->source; |
472 | nth->seq = htonl(__cookie_v4_init_sequence(iph, th, &mss)); |
473 | nth->ack_seq = htonl(ntohl(th->seq) + 1); |
474 | tcp_flag_word(nth) = TCP_FLAG_SYN | TCP_FLAG_ACK; |
475 | if (opts->options & NF_SYNPROXY_OPT_ECN) |
476 | tcp_flag_word(nth) |= TCP_FLAG_ECE; |
477 | nth->doff = tcp_hdr_size / 4; |
478 | nth->window = 0; |
479 | nth->check = 0; |
480 | nth->urg_ptr = 0; |
481 | |
482 | synproxy_build_options(th: nth, opts); |
483 | |
484 | synproxy_send_tcp(net, skb, nskb, nfct: skb_nfct(skb), |
485 | ctinfo: IP_CT_ESTABLISHED_REPLY, niph, nth, tcp_hdr_size); |
486 | } |
487 | EXPORT_SYMBOL_GPL(synproxy_send_client_synack); |
488 | |
489 | static void |
490 | synproxy_send_server_syn(struct net *net, |
491 | const struct sk_buff *skb, const struct tcphdr *th, |
492 | const struct synproxy_options *opts, u32 recv_seq) |
493 | { |
494 | struct synproxy_net *snet = synproxy_pernet(net); |
495 | struct sk_buff *nskb; |
496 | struct iphdr *iph, *niph; |
497 | struct tcphdr *nth; |
498 | unsigned int tcp_hdr_size; |
499 | |
500 | iph = ip_hdr(skb); |
501 | |
502 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); |
503 | nskb = alloc_skb(size: sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, |
504 | GFP_ATOMIC); |
505 | if (!nskb) |
506 | return; |
507 | skb_reserve(skb: nskb, MAX_TCP_HEADER); |
508 | |
509 | niph = synproxy_build_ip(net, skb: nskb, saddr: iph->saddr, daddr: iph->daddr); |
510 | |
511 | skb_reset_transport_header(skb: nskb); |
512 | nth = skb_put(skb: nskb, len: tcp_hdr_size); |
513 | nth->source = th->source; |
514 | nth->dest = th->dest; |
515 | nth->seq = htonl(recv_seq - 1); |
516 | /* ack_seq is used to relay our ISN to the synproxy hook to initialize |
517 | * sequence number translation once a connection tracking entry exists. |
518 | */ |
519 | nth->ack_seq = htonl(ntohl(th->ack_seq) - 1); |
520 | tcp_flag_word(nth) = TCP_FLAG_SYN; |
521 | if (opts->options & NF_SYNPROXY_OPT_ECN) |
522 | tcp_flag_word(nth) |= TCP_FLAG_ECE | TCP_FLAG_CWR; |
523 | nth->doff = tcp_hdr_size / 4; |
524 | nth->window = th->window; |
525 | nth->check = 0; |
526 | nth->urg_ptr = 0; |
527 | |
528 | synproxy_build_options(th: nth, opts); |
529 | |
530 | synproxy_send_tcp(net, skb, nskb, nfct: &snet->tmpl->ct_general, ctinfo: IP_CT_NEW, |
531 | niph, nth, tcp_hdr_size); |
532 | } |
533 | |
534 | static void |
535 | synproxy_send_server_ack(struct net *net, |
536 | const struct ip_ct_tcp *state, |
537 | const struct sk_buff *skb, const struct tcphdr *th, |
538 | const struct synproxy_options *opts) |
539 | { |
540 | struct sk_buff *nskb; |
541 | struct iphdr *iph, *niph; |
542 | struct tcphdr *nth; |
543 | unsigned int tcp_hdr_size; |
544 | |
545 | iph = ip_hdr(skb); |
546 | |
547 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); |
548 | nskb = alloc_skb(size: sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, |
549 | GFP_ATOMIC); |
550 | if (!nskb) |
551 | return; |
552 | skb_reserve(skb: nskb, MAX_TCP_HEADER); |
553 | |
554 | niph = synproxy_build_ip(net, skb: nskb, saddr: iph->daddr, daddr: iph->saddr); |
555 | |
556 | skb_reset_transport_header(skb: nskb); |
557 | nth = skb_put(skb: nskb, len: tcp_hdr_size); |
558 | nth->source = th->dest; |
559 | nth->dest = th->source; |
560 | nth->seq = htonl(ntohl(th->ack_seq)); |
561 | nth->ack_seq = htonl(ntohl(th->seq) + 1); |
562 | tcp_flag_word(nth) = TCP_FLAG_ACK; |
563 | nth->doff = tcp_hdr_size / 4; |
564 | nth->window = htons(state->seen[IP_CT_DIR_ORIGINAL].td_maxwin); |
565 | nth->check = 0; |
566 | nth->urg_ptr = 0; |
567 | |
568 | synproxy_build_options(th: nth, opts); |
569 | |
570 | synproxy_send_tcp(net, skb, nskb, NULL, ctinfo: 0, niph, nth, tcp_hdr_size); |
571 | } |
572 | |
573 | static void |
574 | synproxy_send_client_ack(struct net *net, |
575 | const struct sk_buff *skb, const struct tcphdr *th, |
576 | const struct synproxy_options *opts) |
577 | { |
578 | struct sk_buff *nskb; |
579 | struct iphdr *iph, *niph; |
580 | struct tcphdr *nth; |
581 | unsigned int tcp_hdr_size; |
582 | |
583 | iph = ip_hdr(skb); |
584 | |
585 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); |
586 | nskb = alloc_skb(size: sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, |
587 | GFP_ATOMIC); |
588 | if (!nskb) |
589 | return; |
590 | skb_reserve(skb: nskb, MAX_TCP_HEADER); |
591 | |
592 | niph = synproxy_build_ip(net, skb: nskb, saddr: iph->saddr, daddr: iph->daddr); |
593 | |
594 | skb_reset_transport_header(skb: nskb); |
595 | nth = skb_put(skb: nskb, len: tcp_hdr_size); |
596 | nth->source = th->source; |
597 | nth->dest = th->dest; |
598 | nth->seq = htonl(ntohl(th->seq) + 1); |
599 | nth->ack_seq = th->ack_seq; |
600 | tcp_flag_word(nth) = TCP_FLAG_ACK; |
601 | nth->doff = tcp_hdr_size / 4; |
602 | nth->window = htons(ntohs(th->window) >> opts->wscale); |
603 | nth->check = 0; |
604 | nth->urg_ptr = 0; |
605 | |
606 | synproxy_build_options(th: nth, opts); |
607 | |
608 | synproxy_send_tcp(net, skb, nskb, nfct: skb_nfct(skb), |
609 | ctinfo: IP_CT_ESTABLISHED_REPLY, niph, nth, tcp_hdr_size); |
610 | } |
611 | |
612 | bool |
613 | synproxy_recv_client_ack(struct net *net, |
614 | const struct sk_buff *skb, const struct tcphdr *th, |
615 | struct synproxy_options *opts, u32 recv_seq) |
616 | { |
617 | struct synproxy_net *snet = synproxy_pernet(net); |
618 | int mss; |
619 | |
620 | mss = __cookie_v4_check(iph: ip_hdr(skb), th, ntohl(th->ack_seq) - 1); |
621 | if (mss == 0) { |
622 | this_cpu_inc(snet->stats->cookie_invalid); |
623 | return false; |
624 | } |
625 | |
626 | this_cpu_inc(snet->stats->cookie_valid); |
627 | opts->mss_option = mss; |
628 | opts->options |= NF_SYNPROXY_OPT_MSS; |
629 | |
630 | if (opts->options & NF_SYNPROXY_OPT_TIMESTAMP) |
631 | synproxy_check_timestamp_cookie(opts); |
632 | |
633 | synproxy_send_server_syn(net, skb, th, opts, recv_seq); |
634 | return true; |
635 | } |
636 | EXPORT_SYMBOL_GPL(synproxy_recv_client_ack); |
637 | |
638 | unsigned int |
639 | ipv4_synproxy_hook(void *priv, struct sk_buff *skb, |
640 | const struct nf_hook_state *nhs) |
641 | { |
642 | struct net *net = nhs->net; |
643 | struct synproxy_net *snet = synproxy_pernet(net); |
644 | enum ip_conntrack_info ctinfo; |
645 | struct nf_conn *ct; |
646 | struct nf_conn_synproxy *synproxy; |
647 | struct synproxy_options opts = {}; |
648 | const struct ip_ct_tcp *state; |
649 | struct tcphdr *th, _th; |
650 | unsigned int thoff; |
651 | |
652 | ct = nf_ct_get(skb, ctinfo: &ctinfo); |
653 | if (!ct) |
654 | return NF_ACCEPT; |
655 | |
656 | synproxy = nfct_synproxy(ct); |
657 | if (!synproxy) |
658 | return NF_ACCEPT; |
659 | |
660 | if (nf_is_loopback_packet(skb) || |
661 | ip_hdr(skb)->protocol != IPPROTO_TCP) |
662 | return NF_ACCEPT; |
663 | |
664 | thoff = ip_hdrlen(skb); |
665 | th = skb_header_pointer(skb, offset: thoff, len: sizeof(_th), buffer: &_th); |
666 | if (!th) |
667 | return NF_DROP; |
668 | |
669 | state = &ct->proto.tcp; |
670 | switch (state->state) { |
671 | case TCP_CONNTRACK_CLOSE: |
672 | if (th->rst && CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) { |
673 | nf_ct_seqadj_init(ct, ctinfo, off: synproxy->isn - |
674 | ntohl(th->seq) + 1); |
675 | break; |
676 | } |
677 | |
678 | if (!th->syn || th->ack || |
679 | CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) |
680 | break; |
681 | |
682 | /* Reopened connection - reset the sequence number and timestamp |
683 | * adjustments, they will get initialized once the connection is |
684 | * reestablished. |
685 | */ |
686 | nf_ct_seqadj_init(ct, ctinfo, off: 0); |
687 | synproxy->tsoff = 0; |
688 | this_cpu_inc(snet->stats->conn_reopened); |
689 | fallthrough; |
690 | case TCP_CONNTRACK_SYN_SENT: |
691 | if (!synproxy_parse_options(skb, thoff, th, &opts)) |
692 | return NF_DROP; |
693 | |
694 | if (!th->syn && th->ack && |
695 | CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) { |
696 | /* Keep-Alives are sent with SEG.SEQ = SND.NXT-1, |
697 | * therefore we need to add 1 to make the SYN sequence |
698 | * number match the one of first SYN. |
699 | */ |
700 | if (synproxy_recv_client_ack(net, skb, th, &opts, |
701 | ntohl(th->seq) + 1)) { |
702 | this_cpu_inc(snet->stats->cookie_retrans); |
703 | consume_skb(skb); |
704 | return NF_STOLEN; |
705 | } else { |
706 | return NF_DROP; |
707 | } |
708 | } |
709 | |
710 | synproxy->isn = ntohl(th->ack_seq); |
711 | if (opts.options & NF_SYNPROXY_OPT_TIMESTAMP) |
712 | synproxy->its = opts.tsecr; |
713 | |
714 | nf_conntrack_event_cache(event: IPCT_SYNPROXY, ct); |
715 | break; |
716 | case TCP_CONNTRACK_SYN_RECV: |
717 | if (!th->syn || !th->ack) |
718 | break; |
719 | |
720 | if (!synproxy_parse_options(skb, thoff, th, &opts)) |
721 | return NF_DROP; |
722 | |
723 | if (opts.options & NF_SYNPROXY_OPT_TIMESTAMP) { |
724 | synproxy->tsoff = opts.tsval - synproxy->its; |
725 | nf_conntrack_event_cache(event: IPCT_SYNPROXY, ct); |
726 | } |
727 | |
728 | opts.options &= ~(NF_SYNPROXY_OPT_MSS | |
729 | NF_SYNPROXY_OPT_WSCALE | |
730 | NF_SYNPROXY_OPT_SACK_PERM); |
731 | |
732 | swap(opts.tsval, opts.tsecr); |
733 | synproxy_send_server_ack(net, state, skb, th, opts: &opts); |
734 | |
735 | nf_ct_seqadj_init(ct, ctinfo, off: synproxy->isn - ntohl(th->seq)); |
736 | nf_conntrack_event_cache(event: IPCT_SEQADJ, ct); |
737 | |
738 | swap(opts.tsval, opts.tsecr); |
739 | synproxy_send_client_ack(net, skb, th, opts: &opts); |
740 | |
741 | consume_skb(skb); |
742 | return NF_STOLEN; |
743 | default: |
744 | break; |
745 | } |
746 | |
747 | synproxy_tstamp_adjust(skb, protoff: thoff, th, ct, ctinfo, synproxy); |
748 | return NF_ACCEPT; |
749 | } |
750 | EXPORT_SYMBOL_GPL(ipv4_synproxy_hook); |
751 | |
752 | static const struct nf_hook_ops ipv4_synproxy_ops[] = { |
753 | { |
754 | .hook = ipv4_synproxy_hook, |
755 | .pf = NFPROTO_IPV4, |
756 | .hooknum = NF_INET_LOCAL_IN, |
757 | .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1, |
758 | }, |
759 | { |
760 | .hook = ipv4_synproxy_hook, |
761 | .pf = NFPROTO_IPV4, |
762 | .hooknum = NF_INET_POST_ROUTING, |
763 | .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1, |
764 | }, |
765 | }; |
766 | |
767 | int nf_synproxy_ipv4_init(struct synproxy_net *snet, struct net *net) |
768 | { |
769 | int err; |
770 | |
771 | if (snet->hook_ref4 == 0) { |
772 | err = nf_register_net_hooks(net, reg: ipv4_synproxy_ops, |
773 | ARRAY_SIZE(ipv4_synproxy_ops)); |
774 | if (err) |
775 | return err; |
776 | } |
777 | |
778 | snet->hook_ref4++; |
779 | return 0; |
780 | } |
781 | EXPORT_SYMBOL_GPL(nf_synproxy_ipv4_init); |
782 | |
783 | void nf_synproxy_ipv4_fini(struct synproxy_net *snet, struct net *net) |
784 | { |
785 | snet->hook_ref4--; |
786 | if (snet->hook_ref4 == 0) |
787 | nf_unregister_net_hooks(net, reg: ipv4_synproxy_ops, |
788 | ARRAY_SIZE(ipv4_synproxy_ops)); |
789 | } |
790 | EXPORT_SYMBOL_GPL(nf_synproxy_ipv4_fini); |
791 | |
792 | #if IS_ENABLED(CONFIG_IPV6) |
793 | static struct ipv6hdr * |
794 | synproxy_build_ip_ipv6(struct net *net, struct sk_buff *skb, |
795 | const struct in6_addr *saddr, |
796 | const struct in6_addr *daddr) |
797 | { |
798 | struct ipv6hdr *iph; |
799 | |
800 | skb_reset_network_header(skb); |
801 | iph = skb_put(skb, len: sizeof(*iph)); |
802 | ip6_flow_hdr(hdr: iph, tclass: 0, flowlabel: 0); |
803 | iph->hop_limit = net->ipv6.devconf_all->hop_limit; |
804 | iph->nexthdr = IPPROTO_TCP; |
805 | iph->saddr = *saddr; |
806 | iph->daddr = *daddr; |
807 | |
808 | return iph; |
809 | } |
810 | |
811 | static void |
812 | synproxy_send_tcp_ipv6(struct net *net, |
813 | const struct sk_buff *skb, struct sk_buff *nskb, |
814 | struct nf_conntrack *nfct, enum ip_conntrack_info ctinfo, |
815 | struct ipv6hdr *niph, struct tcphdr *nth, |
816 | unsigned int tcp_hdr_size) |
817 | { |
818 | struct dst_entry *dst; |
819 | struct flowi6 fl6; |
820 | int err; |
821 | |
822 | nth->check = ~tcp_v6_check(len: tcp_hdr_size, saddr: &niph->saddr, daddr: &niph->daddr, base: 0); |
823 | nskb->ip_summed = CHECKSUM_PARTIAL; |
824 | nskb->csum_start = (unsigned char *)nth - nskb->head; |
825 | nskb->csum_offset = offsetof(struct tcphdr, check); |
826 | |
827 | memset(&fl6, 0, sizeof(fl6)); |
828 | fl6.flowi6_proto = IPPROTO_TCP; |
829 | fl6.saddr = niph->saddr; |
830 | fl6.daddr = niph->daddr; |
831 | fl6.fl6_sport = nth->source; |
832 | fl6.fl6_dport = nth->dest; |
833 | security_skb_classify_flow(skb: (struct sk_buff *)skb, |
834 | flic: flowi6_to_flowi_common(fl6: &fl6)); |
835 | err = nf_ip6_route(net, dst: &dst, fl: flowi6_to_flowi(fl6: &fl6), strict: false); |
836 | if (err) { |
837 | goto free_nskb; |
838 | } |
839 | |
840 | dst = xfrm_lookup(net, dst_orig: dst, fl: flowi6_to_flowi(fl6: &fl6), NULL, flags: 0); |
841 | if (IS_ERR(ptr: dst)) |
842 | goto free_nskb; |
843 | |
844 | skb_dst_set(skb: nskb, dst); |
845 | |
846 | if (nfct) { |
847 | nf_ct_set(skb: nskb, ct: (struct nf_conn *)nfct, info: ctinfo); |
848 | nf_conntrack_get(nfct); |
849 | } |
850 | |
851 | ip6_local_out(net, sk: nskb->sk, skb: nskb); |
852 | return; |
853 | |
854 | free_nskb: |
855 | kfree_skb(skb: nskb); |
856 | } |
857 | |
858 | void |
859 | synproxy_send_client_synack_ipv6(struct net *net, |
860 | const struct sk_buff *skb, |
861 | const struct tcphdr *th, |
862 | const struct synproxy_options *opts) |
863 | { |
864 | struct sk_buff *nskb; |
865 | struct ipv6hdr *iph, *niph; |
866 | struct tcphdr *nth; |
867 | unsigned int tcp_hdr_size; |
868 | u16 mss = opts->mss_encode; |
869 | |
870 | iph = ipv6_hdr(skb); |
871 | |
872 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); |
873 | nskb = alloc_skb(size: sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, |
874 | GFP_ATOMIC); |
875 | if (!nskb) |
876 | return; |
877 | skb_reserve(skb: nskb, MAX_TCP_HEADER); |
878 | |
879 | niph = synproxy_build_ip_ipv6(net, skb: nskb, saddr: &iph->daddr, daddr: &iph->saddr); |
880 | |
881 | skb_reset_transport_header(skb: nskb); |
882 | nth = skb_put(skb: nskb, len: tcp_hdr_size); |
883 | nth->source = th->dest; |
884 | nth->dest = th->source; |
885 | nth->seq = htonl(nf_ipv6_cookie_init_sequence(iph, th, &mss)); |
886 | nth->ack_seq = htonl(ntohl(th->seq) + 1); |
887 | tcp_flag_word(nth) = TCP_FLAG_SYN | TCP_FLAG_ACK; |
888 | if (opts->options & NF_SYNPROXY_OPT_ECN) |
889 | tcp_flag_word(nth) |= TCP_FLAG_ECE; |
890 | nth->doff = tcp_hdr_size / 4; |
891 | nth->window = 0; |
892 | nth->check = 0; |
893 | nth->urg_ptr = 0; |
894 | |
895 | synproxy_build_options(th: nth, opts); |
896 | |
897 | synproxy_send_tcp_ipv6(net, skb, nskb, nfct: skb_nfct(skb), |
898 | ctinfo: IP_CT_ESTABLISHED_REPLY, niph, nth, |
899 | tcp_hdr_size); |
900 | } |
901 | EXPORT_SYMBOL_GPL(synproxy_send_client_synack_ipv6); |
902 | |
903 | static void |
904 | synproxy_send_server_syn_ipv6(struct net *net, const struct sk_buff *skb, |
905 | const struct tcphdr *th, |
906 | const struct synproxy_options *opts, u32 recv_seq) |
907 | { |
908 | struct synproxy_net *snet = synproxy_pernet(net); |
909 | struct sk_buff *nskb; |
910 | struct ipv6hdr *iph, *niph; |
911 | struct tcphdr *nth; |
912 | unsigned int tcp_hdr_size; |
913 | |
914 | iph = ipv6_hdr(skb); |
915 | |
916 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); |
917 | nskb = alloc_skb(size: sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, |
918 | GFP_ATOMIC); |
919 | if (!nskb) |
920 | return; |
921 | skb_reserve(skb: nskb, MAX_TCP_HEADER); |
922 | |
923 | niph = synproxy_build_ip_ipv6(net, skb: nskb, saddr: &iph->saddr, daddr: &iph->daddr); |
924 | |
925 | skb_reset_transport_header(skb: nskb); |
926 | nth = skb_put(skb: nskb, len: tcp_hdr_size); |
927 | nth->source = th->source; |
928 | nth->dest = th->dest; |
929 | nth->seq = htonl(recv_seq - 1); |
930 | /* ack_seq is used to relay our ISN to the synproxy hook to initialize |
931 | * sequence number translation once a connection tracking entry exists. |
932 | */ |
933 | nth->ack_seq = htonl(ntohl(th->ack_seq) - 1); |
934 | tcp_flag_word(nth) = TCP_FLAG_SYN; |
935 | if (opts->options & NF_SYNPROXY_OPT_ECN) |
936 | tcp_flag_word(nth) |= TCP_FLAG_ECE | TCP_FLAG_CWR; |
937 | nth->doff = tcp_hdr_size / 4; |
938 | nth->window = th->window; |
939 | nth->check = 0; |
940 | nth->urg_ptr = 0; |
941 | |
942 | synproxy_build_options(th: nth, opts); |
943 | |
944 | synproxy_send_tcp_ipv6(net, skb, nskb, nfct: &snet->tmpl->ct_general, |
945 | ctinfo: IP_CT_NEW, niph, nth, tcp_hdr_size); |
946 | } |
947 | |
948 | static void |
949 | synproxy_send_server_ack_ipv6(struct net *net, const struct ip_ct_tcp *state, |
950 | const struct sk_buff *skb, |
951 | const struct tcphdr *th, |
952 | const struct synproxy_options *opts) |
953 | { |
954 | struct sk_buff *nskb; |
955 | struct ipv6hdr *iph, *niph; |
956 | struct tcphdr *nth; |
957 | unsigned int tcp_hdr_size; |
958 | |
959 | iph = ipv6_hdr(skb); |
960 | |
961 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); |
962 | nskb = alloc_skb(size: sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, |
963 | GFP_ATOMIC); |
964 | if (!nskb) |
965 | return; |
966 | skb_reserve(skb: nskb, MAX_TCP_HEADER); |
967 | |
968 | niph = synproxy_build_ip_ipv6(net, skb: nskb, saddr: &iph->daddr, daddr: &iph->saddr); |
969 | |
970 | skb_reset_transport_header(skb: nskb); |
971 | nth = skb_put(skb: nskb, len: tcp_hdr_size); |
972 | nth->source = th->dest; |
973 | nth->dest = th->source; |
974 | nth->seq = htonl(ntohl(th->ack_seq)); |
975 | nth->ack_seq = htonl(ntohl(th->seq) + 1); |
976 | tcp_flag_word(nth) = TCP_FLAG_ACK; |
977 | nth->doff = tcp_hdr_size / 4; |
978 | nth->window = htons(state->seen[IP_CT_DIR_ORIGINAL].td_maxwin); |
979 | nth->check = 0; |
980 | nth->urg_ptr = 0; |
981 | |
982 | synproxy_build_options(th: nth, opts); |
983 | |
984 | synproxy_send_tcp_ipv6(net, skb, nskb, NULL, ctinfo: 0, niph, nth, |
985 | tcp_hdr_size); |
986 | } |
987 | |
988 | static void |
989 | synproxy_send_client_ack_ipv6(struct net *net, const struct sk_buff *skb, |
990 | const struct tcphdr *th, |
991 | const struct synproxy_options *opts) |
992 | { |
993 | struct sk_buff *nskb; |
994 | struct ipv6hdr *iph, *niph; |
995 | struct tcphdr *nth; |
996 | unsigned int tcp_hdr_size; |
997 | |
998 | iph = ipv6_hdr(skb); |
999 | |
1000 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); |
1001 | nskb = alloc_skb(size: sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, |
1002 | GFP_ATOMIC); |
1003 | if (!nskb) |
1004 | return; |
1005 | skb_reserve(skb: nskb, MAX_TCP_HEADER); |
1006 | |
1007 | niph = synproxy_build_ip_ipv6(net, skb: nskb, saddr: &iph->saddr, daddr: &iph->daddr); |
1008 | |
1009 | skb_reset_transport_header(skb: nskb); |
1010 | nth = skb_put(skb: nskb, len: tcp_hdr_size); |
1011 | nth->source = th->source; |
1012 | nth->dest = th->dest; |
1013 | nth->seq = htonl(ntohl(th->seq) + 1); |
1014 | nth->ack_seq = th->ack_seq; |
1015 | tcp_flag_word(nth) = TCP_FLAG_ACK; |
1016 | nth->doff = tcp_hdr_size / 4; |
1017 | nth->window = htons(ntohs(th->window) >> opts->wscale); |
1018 | nth->check = 0; |
1019 | nth->urg_ptr = 0; |
1020 | |
1021 | synproxy_build_options(th: nth, opts); |
1022 | |
1023 | synproxy_send_tcp_ipv6(net, skb, nskb, nfct: skb_nfct(skb), |
1024 | ctinfo: IP_CT_ESTABLISHED_REPLY, niph, nth, |
1025 | tcp_hdr_size); |
1026 | } |
1027 | |
1028 | bool |
1029 | synproxy_recv_client_ack_ipv6(struct net *net, |
1030 | const struct sk_buff *skb, |
1031 | const struct tcphdr *th, |
1032 | struct synproxy_options *opts, u32 recv_seq) |
1033 | { |
1034 | struct synproxy_net *snet = synproxy_pernet(net); |
1035 | int mss; |
1036 | |
1037 | mss = nf_cookie_v6_check(iph: ipv6_hdr(skb), th, ntohl(th->ack_seq) - 1); |
1038 | if (mss == 0) { |
1039 | this_cpu_inc(snet->stats->cookie_invalid); |
1040 | return false; |
1041 | } |
1042 | |
1043 | this_cpu_inc(snet->stats->cookie_valid); |
1044 | opts->mss_option = mss; |
1045 | opts->options |= NF_SYNPROXY_OPT_MSS; |
1046 | |
1047 | if (opts->options & NF_SYNPROXY_OPT_TIMESTAMP) |
1048 | synproxy_check_timestamp_cookie(opts); |
1049 | |
1050 | synproxy_send_server_syn_ipv6(net, skb, th, opts, recv_seq); |
1051 | return true; |
1052 | } |
1053 | EXPORT_SYMBOL_GPL(synproxy_recv_client_ack_ipv6); |
1054 | |
1055 | unsigned int |
1056 | ipv6_synproxy_hook(void *priv, struct sk_buff *skb, |
1057 | const struct nf_hook_state *nhs) |
1058 | { |
1059 | struct net *net = nhs->net; |
1060 | struct synproxy_net *snet = synproxy_pernet(net); |
1061 | enum ip_conntrack_info ctinfo; |
1062 | struct nf_conn *ct; |
1063 | struct nf_conn_synproxy *synproxy; |
1064 | struct synproxy_options opts = {}; |
1065 | const struct ip_ct_tcp *state; |
1066 | struct tcphdr *th, _th; |
1067 | __be16 frag_off; |
1068 | u8 nexthdr; |
1069 | int thoff; |
1070 | |
1071 | ct = nf_ct_get(skb, ctinfo: &ctinfo); |
1072 | if (!ct) |
1073 | return NF_ACCEPT; |
1074 | |
1075 | synproxy = nfct_synproxy(ct); |
1076 | if (!synproxy) |
1077 | return NF_ACCEPT; |
1078 | |
1079 | if (nf_is_loopback_packet(skb)) |
1080 | return NF_ACCEPT; |
1081 | |
1082 | nexthdr = ipv6_hdr(skb)->nexthdr; |
1083 | thoff = ipv6_skip_exthdr(skb, start: sizeof(struct ipv6hdr), nexthdrp: &nexthdr, |
1084 | frag_offp: &frag_off); |
1085 | if (thoff < 0 || nexthdr != IPPROTO_TCP) |
1086 | return NF_ACCEPT; |
1087 | |
1088 | th = skb_header_pointer(skb, offset: thoff, len: sizeof(_th), buffer: &_th); |
1089 | if (!th) |
1090 | return NF_DROP; |
1091 | |
1092 | state = &ct->proto.tcp; |
1093 | switch (state->state) { |
1094 | case TCP_CONNTRACK_CLOSE: |
1095 | if (th->rst && CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) { |
1096 | nf_ct_seqadj_init(ct, ctinfo, off: synproxy->isn - |
1097 | ntohl(th->seq) + 1); |
1098 | break; |
1099 | } |
1100 | |
1101 | if (!th->syn || th->ack || |
1102 | CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) |
1103 | break; |
1104 | |
1105 | /* Reopened connection - reset the sequence number and timestamp |
1106 | * adjustments, they will get initialized once the connection is |
1107 | * reestablished. |
1108 | */ |
1109 | nf_ct_seqadj_init(ct, ctinfo, off: 0); |
1110 | synproxy->tsoff = 0; |
1111 | this_cpu_inc(snet->stats->conn_reopened); |
1112 | fallthrough; |
1113 | case TCP_CONNTRACK_SYN_SENT: |
1114 | if (!synproxy_parse_options(skb, thoff, th, &opts)) |
1115 | return NF_DROP; |
1116 | |
1117 | if (!th->syn && th->ack && |
1118 | CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) { |
1119 | /* Keep-Alives are sent with SEG.SEQ = SND.NXT-1, |
1120 | * therefore we need to add 1 to make the SYN sequence |
1121 | * number match the one of first SYN. |
1122 | */ |
1123 | if (synproxy_recv_client_ack_ipv6(net, skb, th, &opts, |
1124 | ntohl(th->seq) + 1)) { |
1125 | this_cpu_inc(snet->stats->cookie_retrans); |
1126 | consume_skb(skb); |
1127 | return NF_STOLEN; |
1128 | } else { |
1129 | return NF_DROP; |
1130 | } |
1131 | } |
1132 | |
1133 | synproxy->isn = ntohl(th->ack_seq); |
1134 | if (opts.options & NF_SYNPROXY_OPT_TIMESTAMP) |
1135 | synproxy->its = opts.tsecr; |
1136 | |
1137 | nf_conntrack_event_cache(event: IPCT_SYNPROXY, ct); |
1138 | break; |
1139 | case TCP_CONNTRACK_SYN_RECV: |
1140 | if (!th->syn || !th->ack) |
1141 | break; |
1142 | |
1143 | if (!synproxy_parse_options(skb, thoff, th, &opts)) |
1144 | return NF_DROP; |
1145 | |
1146 | if (opts.options & NF_SYNPROXY_OPT_TIMESTAMP) { |
1147 | synproxy->tsoff = opts.tsval - synproxy->its; |
1148 | nf_conntrack_event_cache(event: IPCT_SYNPROXY, ct); |
1149 | } |
1150 | |
1151 | opts.options &= ~(NF_SYNPROXY_OPT_MSS | |
1152 | NF_SYNPROXY_OPT_WSCALE | |
1153 | NF_SYNPROXY_OPT_SACK_PERM); |
1154 | |
1155 | swap(opts.tsval, opts.tsecr); |
1156 | synproxy_send_server_ack_ipv6(net, state, skb, th, opts: &opts); |
1157 | |
1158 | nf_ct_seqadj_init(ct, ctinfo, off: synproxy->isn - ntohl(th->seq)); |
1159 | nf_conntrack_event_cache(event: IPCT_SEQADJ, ct); |
1160 | |
1161 | swap(opts.tsval, opts.tsecr); |
1162 | synproxy_send_client_ack_ipv6(net, skb, th, opts: &opts); |
1163 | |
1164 | consume_skb(skb); |
1165 | return NF_STOLEN; |
1166 | default: |
1167 | break; |
1168 | } |
1169 | |
1170 | synproxy_tstamp_adjust(skb, protoff: thoff, th, ct, ctinfo, synproxy); |
1171 | return NF_ACCEPT; |
1172 | } |
1173 | EXPORT_SYMBOL_GPL(ipv6_synproxy_hook); |
1174 | |
1175 | static const struct nf_hook_ops ipv6_synproxy_ops[] = { |
1176 | { |
1177 | .hook = ipv6_synproxy_hook, |
1178 | .pf = NFPROTO_IPV6, |
1179 | .hooknum = NF_INET_LOCAL_IN, |
1180 | .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1, |
1181 | }, |
1182 | { |
1183 | .hook = ipv6_synproxy_hook, |
1184 | .pf = NFPROTO_IPV6, |
1185 | .hooknum = NF_INET_POST_ROUTING, |
1186 | .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1, |
1187 | }, |
1188 | }; |
1189 | |
1190 | int |
1191 | nf_synproxy_ipv6_init(struct synproxy_net *snet, struct net *net) |
1192 | { |
1193 | int err; |
1194 | |
1195 | if (snet->hook_ref6 == 0) { |
1196 | err = nf_register_net_hooks(net, reg: ipv6_synproxy_ops, |
1197 | ARRAY_SIZE(ipv6_synproxy_ops)); |
1198 | if (err) |
1199 | return err; |
1200 | } |
1201 | |
1202 | snet->hook_ref6++; |
1203 | return 0; |
1204 | } |
1205 | EXPORT_SYMBOL_GPL(nf_synproxy_ipv6_init); |
1206 | |
1207 | void |
1208 | nf_synproxy_ipv6_fini(struct synproxy_net *snet, struct net *net) |
1209 | { |
1210 | snet->hook_ref6--; |
1211 | if (snet->hook_ref6 == 0) |
1212 | nf_unregister_net_hooks(net, reg: ipv6_synproxy_ops, |
1213 | ARRAY_SIZE(ipv6_synproxy_ops)); |
1214 | } |
1215 | EXPORT_SYMBOL_GPL(nf_synproxy_ipv6_fini); |
1216 | #endif /* CONFIG_IPV6 */ |
1217 | |
1218 | MODULE_LICENSE("GPL" ); |
1219 | MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>" ); |
1220 | MODULE_DESCRIPTION("nftables SYNPROXY expression support" ); |
1221 | |