diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c index e5b5bc34fb87..bc4cd393ce25 100644 --- a/net/bridge/netfilter/nf_conntrack_bridge.c +++ b/net/bridge/netfilter/nf_conntrack_bridge.c @@ -30,6 +26,68 @@ struct conntrack_bridge_net { static int conntrack_bridge_net_id __read_mostly; +/* A variant of ip_do_fragment which preserves geometry. */ +static int br_ip_do_fragment(struct net *net, struct sock *sk, + struct sk_buff *skb, int mtu, + int (*output)(struct net *, struct sock *sk, + struct sk_buff *)) +{ + unsigned int hlen, frag_max_size, ll_rs; + struct ip_frag_state state; + bool clone = false; + struct iphdr *iph; + int err; + + WARN_ON_ONCE(!skb_has_frag_list(skb)); + + /* for offloaded checksums cleanup checksum before fragmentation */ + if (skb->ip_summed == CHECKSUM_PARTIAL && + (err = skb_checksum_help(skb))) + goto blackhole; + + if (skb_cloned(skb)) + clone = true; + + iph = ip_hdr(skb); + hlen = iph->ihl * 4; + frag_max_size -= hlen; + ll_rs = LL_RESERVED_SPACE(skb->dev); + + if (skb_has_frag_list(skb)) { + unsigned int first_len = skb_pagelen(skb); + + if (first_len - hlen > mtu || + skb_headroom(skb) < ll_rs) + goto blackhole; + } + + ip_do_fragment_init(skb, iph, hlen, &state); + + for (;;) { + ip_do_fragment_prepare(skb, &state, hlen); + + err = output(net, sk, skb); + if (!err) + IP_INC_STATS(net, IPSTATS_MIB_FRAGCREATES); + if (err || !state.frag) + break; + + if (clone) { + skb = skb_clone(state.frag, GFP_ATOMIC); + if (skb) + break; + } else { + skb = state.frag; + } + state.frag = skb->next; + skb_mark_not_on_list(skb); + } + return err; +blackhole: + kfree_skb(skb); + IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS); +} + struct bridge_frag_data { char mac[ETH_HLEN]; u8 hook;