diff options
author | Pablo Neira Ayuso <pablo@netfilter.org> | 2019-05-29 07:25:31 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2019-05-30 17:18:17 -0400 |
commit | c8b17be0b7a45d707fc202c11d257c25bc3952b8 (patch) | |
tree | ebf07ec10ea419e1be811da58d0c09b73ce1d962 | |
parent | d48ecb40b5b5a156ae3658dc336bfc29ea502eb6 (diff) |
net: ipv4: add skbuff fraglist splitter
This patch adds the skbuff fraglist splitter. This API provides an
iterator to transform the fraglist into single skbuff objects, it
consists of:
* ip_fraglist_init(), that initializes the internal state of the
fraglist splitter.
* ip_fraglist_prepare(), that restores the IPv4 header on the
fragments.
* ip_fraglist_next(), that retrieves the fragment from the fraglist and
it updates the internal state of the splitter to point to the next
fragment skbuff in the fraglist.
The ip_fraglist_iter object stores the internal state of the iterator.
This code has been extracted from ip_do_fragment(). Symbols are also
exported to allow to reuse this iterator from the bridge codepath to
build its own refragmentation routine by reusing the existing codebase.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/net/ip.h | 23 | ||||
-rw-r--r-- | net/ipv4/ip_output.c | 88 |
2 files changed, 78 insertions, 33 deletions
diff --git a/include/net/ip.h b/include/net/ip.h index 2d3cce7c3e8a..be899677504b 100644 --- a/include/net/ip.h +++ b/include/net/ip.h | |||
@@ -165,6 +165,29 @@ int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb); | |||
165 | int ip_mc_output(struct net *net, struct sock *sk, struct sk_buff *skb); | 165 | int ip_mc_output(struct net *net, struct sock *sk, struct sk_buff *skb); |
166 | int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, | 166 | int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, |
167 | int (*output)(struct net *, struct sock *, struct sk_buff *)); | 167 | int (*output)(struct net *, struct sock *, struct sk_buff *)); |
168 | |||
169 | struct ip_fraglist_iter { | ||
170 | struct sk_buff *frag_list; | ||
171 | struct sk_buff *frag; | ||
172 | struct iphdr *iph; | ||
173 | int offset; | ||
174 | unsigned int hlen; | ||
175 | }; | ||
176 | |||
177 | void ip_fraglist_init(struct sk_buff *skb, struct iphdr *iph, | ||
178 | unsigned int hlen, struct ip_fraglist_iter *iter); | ||
179 | void ip_fraglist_prepare(struct sk_buff *skb, struct ip_fraglist_iter *iter); | ||
180 | |||
181 | static inline struct sk_buff *ip_fraglist_next(struct ip_fraglist_iter *iter) | ||
182 | { | ||
183 | struct sk_buff *skb = iter->frag; | ||
184 | |||
185 | iter->frag = skb->next; | ||
186 | skb_mark_not_on_list(skb); | ||
187 | |||
188 | return skb; | ||
189 | } | ||
190 | |||
168 | void ip_send_check(struct iphdr *ip); | 191 | void ip_send_check(struct iphdr *ip); |
169 | int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb); | 192 | int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb); |
170 | int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb); | 193 | int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb); |
diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index bfd0ca554977..d03eb4ae0dd4 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c | |||
@@ -561,6 +561,54 @@ static int ip_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, | |||
561 | return ip_do_fragment(net, sk, skb, output); | 561 | return ip_do_fragment(net, sk, skb, output); |
562 | } | 562 | } |
563 | 563 | ||
564 | void ip_fraglist_init(struct sk_buff *skb, struct iphdr *iph, | ||
565 | unsigned int hlen, struct ip_fraglist_iter *iter) | ||
566 | { | ||
567 | unsigned int first_len = skb_pagelen(skb); | ||
568 | |||
569 | iter->frag_list = skb_shinfo(skb)->frag_list; | ||
570 | iter->frag = iter->frag_list; | ||
571 | skb_frag_list_init(skb); | ||
572 | |||
573 | iter->offset = 0; | ||
574 | iter->iph = iph; | ||
575 | iter->hlen = hlen; | ||
576 | |||
577 | skb->data_len = first_len - skb_headlen(skb); | ||
578 | skb->len = first_len; | ||
579 | iph->tot_len = htons(first_len); | ||
580 | iph->frag_off = htons(IP_MF); | ||
581 | ip_send_check(iph); | ||
582 | } | ||
583 | EXPORT_SYMBOL(ip_fraglist_init); | ||
584 | |||
585 | void ip_fraglist_prepare(struct sk_buff *skb, struct ip_fraglist_iter *iter) | ||
586 | { | ||
587 | unsigned int hlen = iter->hlen; | ||
588 | struct iphdr *iph = iter->iph; | ||
589 | struct sk_buff *frag; | ||
590 | |||
591 | frag = iter->frag; | ||
592 | frag->ip_summed = CHECKSUM_NONE; | ||
593 | skb_reset_transport_header(frag); | ||
594 | __skb_push(frag, hlen); | ||
595 | skb_reset_network_header(frag); | ||
596 | memcpy(skb_network_header(frag), iph, hlen); | ||
597 | iter->iph = ip_hdr(frag); | ||
598 | iph = iter->iph; | ||
599 | iph->tot_len = htons(frag->len); | ||
600 | ip_copy_metadata(frag, skb); | ||
601 | if (iter->offset == 0) | ||
602 | ip_options_fragment(frag); | ||
603 | iter->offset += skb->len - hlen; | ||
604 | iph->frag_off = htons(iter->offset >> 3); | ||
605 | if (frag->next) | ||
606 | iph->frag_off |= htons(IP_MF); | ||
607 | /* Ready, complete checksum */ | ||
608 | ip_send_check(iph); | ||
609 | } | ||
610 | EXPORT_SYMBOL(ip_fraglist_prepare); | ||
611 | |||
564 | /* | 612 | /* |
565 | * This IP datagram is too large to be sent in one piece. Break it up into | 613 | * This IP datagram is too large to be sent in one piece. Break it up into |
566 | * smaller pieces (each of size equal to IP header plus | 614 | * smaller pieces (each of size equal to IP header plus |
@@ -578,6 +626,7 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, | |||
578 | int offset; | 626 | int offset; |
579 | __be16 not_last_frag; | 627 | __be16 not_last_frag; |
580 | struct rtable *rt = skb_rtable(skb); | 628 | struct rtable *rt = skb_rtable(skb); |
629 | struct ip_fraglist_iter iter; | ||
581 | int err = 0; | 630 | int err = 0; |
582 | 631 | ||
583 | /* for offloaded checksums cleanup checksum before fragmentation */ | 632 | /* for offloaded checksums cleanup checksum before fragmentation */ |
@@ -642,49 +691,22 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, | |||
642 | } | 691 | } |
643 | 692 | ||
644 | /* Everything is OK. Generate! */ | 693 | /* Everything is OK. Generate! */ |
645 | 694 | ip_fraglist_init(skb, iph, hlen, &iter); | |
646 | err = 0; | ||
647 | offset = 0; | ||
648 | frag = skb_shinfo(skb)->frag_list; | ||
649 | skb_frag_list_init(skb); | ||
650 | skb->data_len = first_len - skb_headlen(skb); | ||
651 | skb->len = first_len; | ||
652 | iph->tot_len = htons(first_len); | ||
653 | iph->frag_off = htons(IP_MF); | ||
654 | ip_send_check(iph); | ||
655 | 695 | ||
656 | for (;;) { | 696 | for (;;) { |
657 | /* Prepare header of the next frame, | 697 | /* Prepare header of the next frame, |
658 | * before previous one went down. */ | 698 | * before previous one went down. */ |
659 | if (frag) { | 699 | if (iter.frag) |
660 | frag->ip_summed = CHECKSUM_NONE; | 700 | ip_fraglist_prepare(skb, &iter); |
661 | skb_reset_transport_header(frag); | ||
662 | __skb_push(frag, hlen); | ||
663 | skb_reset_network_header(frag); | ||
664 | memcpy(skb_network_header(frag), iph, hlen); | ||
665 | iph = ip_hdr(frag); | ||
666 | iph->tot_len = htons(frag->len); | ||
667 | ip_copy_metadata(frag, skb); | ||
668 | if (offset == 0) | ||
669 | ip_options_fragment(frag); | ||
670 | offset += skb->len - hlen; | ||
671 | iph->frag_off = htons(offset>>3); | ||
672 | if (frag->next) | ||
673 | iph->frag_off |= htons(IP_MF); | ||
674 | /* Ready, complete checksum */ | ||
675 | ip_send_check(iph); | ||
676 | } | ||
677 | 701 | ||
678 | err = output(net, sk, skb); | 702 | err = output(net, sk, skb); |
679 | 703 | ||
680 | if (!err) | 704 | if (!err) |
681 | IP_INC_STATS(net, IPSTATS_MIB_FRAGCREATES); | 705 | IP_INC_STATS(net, IPSTATS_MIB_FRAGCREATES); |
682 | if (err || !frag) | 706 | if (err || !iter.frag) |
683 | break; | 707 | break; |
684 | 708 | ||
685 | skb = frag; | 709 | skb = ip_fraglist_next(&iter); |
686 | frag = skb->next; | ||
687 | skb_mark_not_on_list(skb); | ||
688 | } | 710 | } |
689 | 711 | ||
690 | if (err == 0) { | 712 | if (err == 0) { |
@@ -692,7 +714,7 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, | |||
692 | return 0; | 714 | return 0; |
693 | } | 715 | } |
694 | 716 | ||
695 | kfree_skb_list(frag); | 717 | kfree_skb_list(iter.frag_list); |
696 | 718 | ||
697 | IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS); | 719 | IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS); |
698 | return err; | 720 | return err; |