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;
