diff --git a/include/net/ip.h b/include/net/ip.h index e44b1a44f67a..9fd33df313ef 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -160,6 +160,19 @@ int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb); int ip_mc_output(struct net *net, struct sock *sk, struct sk_buff *skb); int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, int (*output)(struct net *, struct sock *, struct sk_buff *)); + +struct ip_frag_state { + int offset; + struct iphdr *iph; + unsigned int hlen; + struct sk_buff *frag; +}; + +void ip_do_fragment_init(struct sk_buff *skb, struct iphdr *iph, + unsigned int hlen, struct ip_frag_state *state); +void ip_do_fragment_prepare(struct sk_buff *skb, struct ip_frag_state *state, + unsigned int hlen); + void ip_send_check(struct iphdr *ip); int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb); 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 c09219e7f230..96f86d3b3159 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -561,6 +561,55 @@ static int ip_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, return ip_do_fragment(net, sk, skb, output); } +void ip_do_fragment_init(struct sk_buff *skb, struct iphdr *iph, + unsigned int hlen, struct ip_frag_state *state) +{ + unsigned int first_len = skb_pagelen(skb); + + state->frag = skb_shinfo(skb)->frag_list; + state->offset = 0; + state->iph = iph; + state->hlen = hlen; + + skb_frag_list_init(skb); + skb->data_len = first_len - skb_headlen(skb); + skb->len = first_len; + iph->tot_len = htons(first_len); + iph->frag_off = htons(IP_MF); + ip_send_check(iph); +} +EXPORT_SYMBOL(ip_do_fragment_init); + +void ip_do_fragment_prepare(struct sk_buff *skb, + struct ip_frag_state *state, + unsigned int hlen) +{ + struct iphdr *iph = state->iph; + struct sk_buff *frag; + + if (!state->frag) + return; + + frag = state->frag; + frag->ip_summed = CHECKSUM_NONE; + skb_reset_transport_header(frag); + __skb_push(frag, hlen); + skb_reset_network_header(frag); + memcpy(skb_network_header(frag), iph, hlen); + state->iph = iph = ip_hdr(frag); + iph->tot_len = htons(frag->len); + ip_copy_metadata(frag, skb); + if (state->offset == 0) + ip_options_fragment(frag); + state->offset += skb->len - hlen; + iph->frag_off = htons(state->offset >> 3); + if (frag->next) + iph->frag_off |= htons(IP_MF); + /* Ready, complete checksum */ + ip_send_check(iph); +} +EXPORT_SYMBOL(ip_do_fragment_prepare); + /* * This IP datagram is too large to be sent in one piece. Break it up into * smaller pieces (each of size equal to IP header plus @@ -578,6 +627,7 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, int offset; __be16 not_last_frag; struct rtable *rt = skb_rtable(skb); + struct ip_frag_state state; int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ @@ -642,38 +692,12 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, } /* Everything is OK. Generate! */ - - err = 0; - offset = 0; - frag = skb_shinfo(skb)->frag_list; - skb_frag_list_init(skb); - skb->data_len = first_len - skb_headlen(skb); - skb->len = first_len; - iph->tot_len = htons(first_len); - iph->frag_off = htons(IP_MF); - ip_send_check(iph); + ip_do_fragment_init(skb, iph, hlen, &state); for (;;) { /* Prepare header of the next frame, * before previous one went down. */ - if (frag) { - frag->ip_summed = CHECKSUM_NONE; - skb_reset_transport_header(frag); - __skb_push(frag, hlen); - skb_reset_network_header(frag); - memcpy(skb_network_header(frag), iph, hlen); - iph = ip_hdr(frag); - iph->tot_len = htons(frag->len); - ip_copy_metadata(frag, skb); - if (offset == 0) - ip_options_fragment(frag); - offset += skb->len - hlen; - iph->frag_off = htons(offset>>3); - if (frag->next) - iph->frag_off |= htons(IP_MF); - /* Ready, complete checksum */ - ip_send_check(iph); - } + ip_do_fragment_prepare(skb, &state, hlen); err = output(net, sk, skb); @@ -682,8 +706,8 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, if (err || !frag) break; - skb = frag; - frag = skb->next; + skb = state.frag; + state.frag = skb->next; skb_mark_not_on_list(skb); }