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