| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * IPv6 fragment reassembly |
|---|
| 3 | 4 | * Linux INET6 implementation |
|---|
| .. | .. |
|---|
| 6 | 7 | * Pedro Roque <roque@di.fc.ul.pt> |
|---|
| 7 | 8 | * |
|---|
| 8 | 9 | * Based on: net/ipv4/ip_fragment.c |
|---|
| 9 | | - * |
|---|
| 10 | | - * This program is free software; you can redistribute it and/or |
|---|
| 11 | | - * modify it under the terms of the GNU General Public License |
|---|
| 12 | | - * as published by the Free Software Foundation; either version |
|---|
| 13 | | - * 2 of the License, or (at your option) any later version. |
|---|
| 14 | 10 | */ |
|---|
| 15 | 11 | |
|---|
| 16 | 12 | /* |
|---|
| .. | .. |
|---|
| 46 | 42 | #include <linux/skbuff.h> |
|---|
| 47 | 43 | #include <linux/slab.h> |
|---|
| 48 | 44 | #include <linux/export.h> |
|---|
| 45 | +#include <linux/tcp.h> |
|---|
| 46 | +#include <linux/udp.h> |
|---|
| 49 | 47 | |
|---|
| 50 | 48 | #include <net/sock.h> |
|---|
| 51 | 49 | #include <net/snmp.h> |
|---|
| .. | .. |
|---|
| 76 | 74 | { |
|---|
| 77 | 75 | struct inet_frag_queue *frag = from_timer(frag, t, timer); |
|---|
| 78 | 76 | struct frag_queue *fq; |
|---|
| 79 | | - struct net *net; |
|---|
| 80 | 77 | |
|---|
| 81 | 78 | fq = container_of(frag, struct frag_queue, q); |
|---|
| 82 | | - net = container_of(fq->q.net, struct net, ipv6.frags); |
|---|
| 83 | 79 | |
|---|
| 84 | | - ip6frag_expire_frag_queue(net, fq); |
|---|
| 80 | + ip6frag_expire_frag_queue(fq->q.fqdir->net, fq); |
|---|
| 85 | 81 | } |
|---|
| 86 | 82 | |
|---|
| 87 | 83 | static struct frag_queue * |
|---|
| .. | .. |
|---|
| 100 | 96 | IPV6_ADDR_LINKLOCAL))) |
|---|
| 101 | 97 | key.iif = 0; |
|---|
| 102 | 98 | |
|---|
| 103 | | - q = inet_frag_find(&net->ipv6.frags, &key); |
|---|
| 99 | + q = inet_frag_find(net->ipv6.fqdir, &key); |
|---|
| 104 | 100 | if (!q) |
|---|
| 105 | 101 | return NULL; |
|---|
| 106 | 102 | |
|---|
| .. | .. |
|---|
| 200 | 196 | fq->q.stamp = skb->tstamp; |
|---|
| 201 | 197 | fq->q.meat += skb->len; |
|---|
| 202 | 198 | fq->ecn |= ecn; |
|---|
| 203 | | - add_frag_mem_limit(fq->q.net, skb->truesize); |
|---|
| 199 | + add_frag_mem_limit(fq->q.fqdir, skb->truesize); |
|---|
| 204 | 200 | |
|---|
| 205 | 201 | fragsize = -skb_network_offset(skb) + skb->len; |
|---|
| 206 | 202 | if (fragsize > fq->q.max_size) |
|---|
| .. | .. |
|---|
| 254 | 250 | static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff *skb, |
|---|
| 255 | 251 | struct sk_buff *prev_tail, struct net_device *dev) |
|---|
| 256 | 252 | { |
|---|
| 257 | | - struct net *net = container_of(fq->q.net, struct net, ipv6.frags); |
|---|
| 253 | + struct net *net = fq->q.fqdir->net; |
|---|
| 258 | 254 | unsigned int nhoff; |
|---|
| 259 | 255 | void *reasm_data; |
|---|
| 260 | 256 | int payload_len; |
|---|
| .. | .. |
|---|
| 288 | 284 | |
|---|
| 289 | 285 | skb_reset_transport_header(skb); |
|---|
| 290 | 286 | |
|---|
| 291 | | - inet_frag_reasm_finish(&fq->q, skb, reasm_data); |
|---|
| 287 | + inet_frag_reasm_finish(&fq->q, skb, reasm_data, true); |
|---|
| 292 | 288 | |
|---|
| 293 | 289 | skb->dev = dev; |
|---|
| 294 | 290 | ipv6_hdr(skb)->payload_len = htons(payload_len); |
|---|
| .. | .. |
|---|
| 302 | 298 | skb_network_header_len(skb)); |
|---|
| 303 | 299 | |
|---|
| 304 | 300 | rcu_read_lock(); |
|---|
| 305 | | - __IP6_INC_STATS(net, __in6_dev_get(dev), IPSTATS_MIB_REASMOKS); |
|---|
| 301 | + __IP6_INC_STATS(net, __in6_dev_stats_get(dev, skb), IPSTATS_MIB_REASMOKS); |
|---|
| 306 | 302 | rcu_read_unlock(); |
|---|
| 307 | | - fq->q.fragments = NULL; |
|---|
| 308 | 303 | fq->q.rb_fragments = RB_ROOT; |
|---|
| 309 | 304 | fq->q.fragments_tail = NULL; |
|---|
| 310 | 305 | fq->q.last_run_head = NULL; |
|---|
| .. | .. |
|---|
| 317 | 312 | net_dbg_ratelimited("ip6_frag_reasm: no memory for reassembly\n"); |
|---|
| 318 | 313 | out_fail: |
|---|
| 319 | 314 | rcu_read_lock(); |
|---|
| 320 | | - __IP6_INC_STATS(net, __in6_dev_get(dev), IPSTATS_MIB_REASMFAILS); |
|---|
| 315 | + __IP6_INC_STATS(net, __in6_dev_stats_get(dev, skb), IPSTATS_MIB_REASMFAILS); |
|---|
| 321 | 316 | rcu_read_unlock(); |
|---|
| 322 | 317 | inet_frag_kill(&fq->q); |
|---|
| 323 | 318 | return -1; |
|---|
| .. | .. |
|---|
| 329 | 324 | struct frag_queue *fq; |
|---|
| 330 | 325 | const struct ipv6hdr *hdr = ipv6_hdr(skb); |
|---|
| 331 | 326 | struct net *net = dev_net(skb_dst(skb)->dev); |
|---|
| 327 | + u8 nexthdr; |
|---|
| 332 | 328 | int iif; |
|---|
| 333 | 329 | |
|---|
| 334 | 330 | if (IP6CB(skb)->flags & IP6SKB_FRAGMENTED) |
|---|
| .. | .. |
|---|
| 358 | 354 | IP6CB(skb)->frag_max_size = ntohs(hdr->payload_len) + |
|---|
| 359 | 355 | sizeof(struct ipv6hdr); |
|---|
| 360 | 356 | return 1; |
|---|
| 357 | + } |
|---|
| 358 | + |
|---|
| 359 | + /* RFC 8200, Section 4.5 Fragment Header: |
|---|
| 360 | + * If the first fragment does not include all headers through an |
|---|
| 361 | + * Upper-Layer header, then that fragment should be discarded and |
|---|
| 362 | + * an ICMP Parameter Problem, Code 3, message should be sent to |
|---|
| 363 | + * the source of the fragment, with the Pointer field set to zero. |
|---|
| 364 | + */ |
|---|
| 365 | + nexthdr = hdr->nexthdr; |
|---|
| 366 | + if (ipv6frag_thdr_truncated(skb, skb_transport_offset(skb), &nexthdr)) { |
|---|
| 367 | + __IP6_INC_STATS(net, __in6_dev_get_safely(skb->dev), |
|---|
| 368 | + IPSTATS_MIB_INHDRERRORS); |
|---|
| 369 | + icmpv6_param_prob(skb, ICMPV6_HDR_INCOMP, 0); |
|---|
| 370 | + return -1; |
|---|
| 361 | 371 | } |
|---|
| 362 | 372 | |
|---|
| 363 | 373 | iif = skb->dev ? skb->dev->ifindex : 0; |
|---|
| .. | .. |
|---|
| 404 | 414 | static struct ctl_table ip6_frags_ns_ctl_table[] = { |
|---|
| 405 | 415 | { |
|---|
| 406 | 416 | .procname = "ip6frag_high_thresh", |
|---|
| 407 | | - .data = &init_net.ipv6.frags.high_thresh, |
|---|
| 408 | 417 | .maxlen = sizeof(unsigned long), |
|---|
| 409 | 418 | .mode = 0644, |
|---|
| 410 | 419 | .proc_handler = proc_doulongvec_minmax, |
|---|
| 411 | | - .extra1 = &init_net.ipv6.frags.low_thresh |
|---|
| 412 | 420 | }, |
|---|
| 413 | 421 | { |
|---|
| 414 | 422 | .procname = "ip6frag_low_thresh", |
|---|
| 415 | | - .data = &init_net.ipv6.frags.low_thresh, |
|---|
| 416 | 423 | .maxlen = sizeof(unsigned long), |
|---|
| 417 | 424 | .mode = 0644, |
|---|
| 418 | 425 | .proc_handler = proc_doulongvec_minmax, |
|---|
| 419 | | - .extra2 = &init_net.ipv6.frags.high_thresh |
|---|
| 420 | 426 | }, |
|---|
| 421 | 427 | { |
|---|
| 422 | 428 | .procname = "ip6frag_time", |
|---|
| 423 | | - .data = &init_net.ipv6.frags.timeout, |
|---|
| 424 | 429 | .maxlen = sizeof(int), |
|---|
| 425 | 430 | .mode = 0644, |
|---|
| 426 | 431 | .proc_handler = proc_dointvec_jiffies, |
|---|
| .. | .. |
|---|
| 452 | 457 | if (!table) |
|---|
| 453 | 458 | goto err_alloc; |
|---|
| 454 | 459 | |
|---|
| 455 | | - table[0].data = &net->ipv6.frags.high_thresh; |
|---|
| 456 | | - table[0].extra1 = &net->ipv6.frags.low_thresh; |
|---|
| 457 | | - table[0].extra2 = &init_net.ipv6.frags.high_thresh; |
|---|
| 458 | | - table[1].data = &net->ipv6.frags.low_thresh; |
|---|
| 459 | | - table[1].extra2 = &net->ipv6.frags.high_thresh; |
|---|
| 460 | | - table[2].data = &net->ipv6.frags.timeout; |
|---|
| 461 | 460 | } |
|---|
| 461 | + table[0].data = &net->ipv6.fqdir->high_thresh; |
|---|
| 462 | + table[0].extra1 = &net->ipv6.fqdir->low_thresh; |
|---|
| 463 | + table[1].data = &net->ipv6.fqdir->low_thresh; |
|---|
| 464 | + table[1].extra2 = &net->ipv6.fqdir->high_thresh; |
|---|
| 465 | + table[2].data = &net->ipv6.fqdir->timeout; |
|---|
| 462 | 466 | |
|---|
| 463 | 467 | hdr = register_net_sysctl(net, "net/ipv6", table); |
|---|
| 464 | 468 | if (!hdr) |
|---|
| .. | .. |
|---|
| 521 | 525 | { |
|---|
| 522 | 526 | int res; |
|---|
| 523 | 527 | |
|---|
| 524 | | - net->ipv6.frags.high_thresh = IPV6_FRAG_HIGH_THRESH; |
|---|
| 525 | | - net->ipv6.frags.low_thresh = IPV6_FRAG_LOW_THRESH; |
|---|
| 526 | | - net->ipv6.frags.timeout = IPV6_FRAG_TIMEOUT; |
|---|
| 527 | | - net->ipv6.frags.f = &ip6_frags; |
|---|
| 528 | | - |
|---|
| 529 | | - res = inet_frags_init_net(&net->ipv6.frags); |
|---|
| 528 | + res = fqdir_init(&net->ipv6.fqdir, &ip6_frags, net); |
|---|
| 530 | 529 | if (res < 0) |
|---|
| 531 | 530 | return res; |
|---|
| 532 | 531 | |
|---|
| 532 | + net->ipv6.fqdir->high_thresh = IPV6_FRAG_HIGH_THRESH; |
|---|
| 533 | + net->ipv6.fqdir->low_thresh = IPV6_FRAG_LOW_THRESH; |
|---|
| 534 | + net->ipv6.fqdir->timeout = IPV6_FRAG_TIMEOUT; |
|---|
| 535 | + |
|---|
| 533 | 536 | res = ip6_frags_ns_sysctl_register(net); |
|---|
| 534 | 537 | if (res < 0) |
|---|
| 535 | | - inet_frags_exit_net(&net->ipv6.frags); |
|---|
| 538 | + fqdir_exit(net->ipv6.fqdir); |
|---|
| 536 | 539 | return res; |
|---|
| 540 | +} |
|---|
| 541 | + |
|---|
| 542 | +static void __net_exit ipv6_frags_pre_exit_net(struct net *net) |
|---|
| 543 | +{ |
|---|
| 544 | + fqdir_pre_exit(net->ipv6.fqdir); |
|---|
| 537 | 545 | } |
|---|
| 538 | 546 | |
|---|
| 539 | 547 | static void __net_exit ipv6_frags_exit_net(struct net *net) |
|---|
| 540 | 548 | { |
|---|
| 541 | 549 | ip6_frags_ns_sysctl_unregister(net); |
|---|
| 542 | | - inet_frags_exit_net(&net->ipv6.frags); |
|---|
| 550 | + fqdir_exit(net->ipv6.fqdir); |
|---|
| 543 | 551 | } |
|---|
| 544 | 552 | |
|---|
| 545 | 553 | static struct pernet_operations ip6_frags_ops = { |
|---|
| 546 | | - .init = ipv6_frags_init_net, |
|---|
| 547 | | - .exit = ipv6_frags_exit_net, |
|---|
| 554 | + .init = ipv6_frags_init_net, |
|---|
| 555 | + .pre_exit = ipv6_frags_pre_exit_net, |
|---|
| 556 | + .exit = ipv6_frags_exit_net, |
|---|
| 548 | 557 | }; |
|---|
| 549 | 558 | |
|---|
| 550 | 559 | static const struct rhashtable_params ip6_rhash_params = { |
|---|