162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
262306a36Sopenharmony_ci#include <linux/if_vlan.h>
362306a36Sopenharmony_ci#include <net/netlink.h>
462306a36Sopenharmony_ci#include <net/sch_generic.h>
562306a36Sopenharmony_ci#include <net/pkt_sched.h>
662306a36Sopenharmony_ci#include <net/dst.h>
762306a36Sopenharmony_ci#include <net/ip.h>
862306a36Sopenharmony_ci#include <net/ip6_fib.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_cistruct sch_frag_data {
1162306a36Sopenharmony_ci	unsigned long dst;
1262306a36Sopenharmony_ci	struct qdisc_skb_cb cb;
1362306a36Sopenharmony_ci	__be16 inner_protocol;
1462306a36Sopenharmony_ci	u16 vlan_tci;
1562306a36Sopenharmony_ci	__be16 vlan_proto;
1662306a36Sopenharmony_ci	unsigned int l2_len;
1762306a36Sopenharmony_ci	u8 l2_data[VLAN_ETH_HLEN];
1862306a36Sopenharmony_ci	int (*xmit)(struct sk_buff *skb);
1962306a36Sopenharmony_ci};
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct sch_frag_data, sch_frag_data_storage);
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic int sch_frag_xmit(struct net *net, struct sock *sk, struct sk_buff *skb)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	struct sch_frag_data *data = this_cpu_ptr(&sch_frag_data_storage);
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	if (skb_cow_head(skb, data->l2_len) < 0) {
2862306a36Sopenharmony_ci		kfree_skb(skb);
2962306a36Sopenharmony_ci		return -ENOMEM;
3062306a36Sopenharmony_ci	}
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	__skb_dst_copy(skb, data->dst);
3362306a36Sopenharmony_ci	*qdisc_skb_cb(skb) = data->cb;
3462306a36Sopenharmony_ci	skb->inner_protocol = data->inner_protocol;
3562306a36Sopenharmony_ci	if (data->vlan_tci & VLAN_CFI_MASK)
3662306a36Sopenharmony_ci		__vlan_hwaccel_put_tag(skb, data->vlan_proto,
3762306a36Sopenharmony_ci				       data->vlan_tci & ~VLAN_CFI_MASK);
3862306a36Sopenharmony_ci	else
3962306a36Sopenharmony_ci		__vlan_hwaccel_clear_tag(skb);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	/* Reconstruct the MAC header.  */
4262306a36Sopenharmony_ci	skb_push(skb, data->l2_len);
4362306a36Sopenharmony_ci	memcpy(skb->data, &data->l2_data, data->l2_len);
4462306a36Sopenharmony_ci	skb_postpush_rcsum(skb, skb->data, data->l2_len);
4562306a36Sopenharmony_ci	skb_reset_mac_header(skb);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	return data->xmit(skb);
4862306a36Sopenharmony_ci}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic void sch_frag_prepare_frag(struct sk_buff *skb,
5162306a36Sopenharmony_ci				  int (*xmit)(struct sk_buff *skb))
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	unsigned int hlen = skb_network_offset(skb);
5462306a36Sopenharmony_ci	struct sch_frag_data *data;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	data = this_cpu_ptr(&sch_frag_data_storage);
5762306a36Sopenharmony_ci	data->dst = skb->_skb_refdst;
5862306a36Sopenharmony_ci	data->cb = *qdisc_skb_cb(skb);
5962306a36Sopenharmony_ci	data->xmit = xmit;
6062306a36Sopenharmony_ci	data->inner_protocol = skb->inner_protocol;
6162306a36Sopenharmony_ci	if (skb_vlan_tag_present(skb))
6262306a36Sopenharmony_ci		data->vlan_tci = skb_vlan_tag_get(skb) | VLAN_CFI_MASK;
6362306a36Sopenharmony_ci	else
6462306a36Sopenharmony_ci		data->vlan_tci = 0;
6562306a36Sopenharmony_ci	data->vlan_proto = skb->vlan_proto;
6662306a36Sopenharmony_ci	data->l2_len = hlen;
6762306a36Sopenharmony_ci	memcpy(&data->l2_data, skb->data, hlen);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
7062306a36Sopenharmony_ci	skb_pull(skb, hlen);
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic unsigned int
7462306a36Sopenharmony_cisch_frag_dst_get_mtu(const struct dst_entry *dst)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	return dst->dev->mtu;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic struct dst_ops sch_frag_dst_ops = {
8062306a36Sopenharmony_ci	.family = AF_UNSPEC,
8162306a36Sopenharmony_ci	.mtu = sch_frag_dst_get_mtu,
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int sch_fragment(struct net *net, struct sk_buff *skb,
8562306a36Sopenharmony_ci			u16 mru, int (*xmit)(struct sk_buff *skb))
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	int ret = -1;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	if (skb_network_offset(skb) > VLAN_ETH_HLEN) {
9062306a36Sopenharmony_ci		net_warn_ratelimited("L2 header too long to fragment\n");
9162306a36Sopenharmony_ci		goto err;
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (skb_protocol(skb, true) == htons(ETH_P_IP)) {
9562306a36Sopenharmony_ci		struct rtable sch_frag_rt = { 0 };
9662306a36Sopenharmony_ci		unsigned long orig_dst;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci		sch_frag_prepare_frag(skb, xmit);
9962306a36Sopenharmony_ci		dst_init(&sch_frag_rt.dst, &sch_frag_dst_ops, NULL, 1,
10062306a36Sopenharmony_ci			 DST_OBSOLETE_NONE, DST_NOCOUNT);
10162306a36Sopenharmony_ci		sch_frag_rt.dst.dev = skb->dev;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci		orig_dst = skb->_skb_refdst;
10462306a36Sopenharmony_ci		skb_dst_set_noref(skb, &sch_frag_rt.dst);
10562306a36Sopenharmony_ci		IPCB(skb)->frag_max_size = mru;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci		ret = ip_do_fragment(net, skb->sk, skb, sch_frag_xmit);
10862306a36Sopenharmony_ci		refdst_drop(orig_dst);
10962306a36Sopenharmony_ci	} else if (skb_protocol(skb, true) == htons(ETH_P_IPV6)) {
11062306a36Sopenharmony_ci		unsigned long orig_dst;
11162306a36Sopenharmony_ci		struct rt6_info sch_frag_rt;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci		sch_frag_prepare_frag(skb, xmit);
11462306a36Sopenharmony_ci		memset(&sch_frag_rt, 0, sizeof(sch_frag_rt));
11562306a36Sopenharmony_ci		dst_init(&sch_frag_rt.dst, &sch_frag_dst_ops, NULL, 1,
11662306a36Sopenharmony_ci			 DST_OBSOLETE_NONE, DST_NOCOUNT);
11762306a36Sopenharmony_ci		sch_frag_rt.dst.dev = skb->dev;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci		orig_dst = skb->_skb_refdst;
12062306a36Sopenharmony_ci		skb_dst_set_noref(skb, &sch_frag_rt.dst);
12162306a36Sopenharmony_ci		IP6CB(skb)->frag_max_size = mru;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci		ret = ipv6_stub->ipv6_fragment(net, skb->sk, skb,
12462306a36Sopenharmony_ci					       sch_frag_xmit);
12562306a36Sopenharmony_ci		refdst_drop(orig_dst);
12662306a36Sopenharmony_ci	} else {
12762306a36Sopenharmony_ci		net_warn_ratelimited("Fail frag %s: eth=%x, MRU=%d, MTU=%d\n",
12862306a36Sopenharmony_ci				     netdev_name(skb->dev),
12962306a36Sopenharmony_ci				     ntohs(skb_protocol(skb, true)), mru,
13062306a36Sopenharmony_ci				     skb->dev->mtu);
13162306a36Sopenharmony_ci		goto err;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	return ret;
13562306a36Sopenharmony_cierr:
13662306a36Sopenharmony_ci	kfree_skb(skb);
13762306a36Sopenharmony_ci	return ret;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ciint sch_frag_xmit_hook(struct sk_buff *skb, int (*xmit)(struct sk_buff *skb))
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	u16 mru = tc_skb_cb(skb)->mru;
14362306a36Sopenharmony_ci	int err;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	if (mru && skb->len > mru + skb->dev->hard_header_len)
14662306a36Sopenharmony_ci		err = sch_fragment(dev_net(skb->dev), skb, mru, xmit);
14762306a36Sopenharmony_ci	else
14862306a36Sopenharmony_ci		err = xmit(skb);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	return err;
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sch_frag_xmit_hook);
153