162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * MPLS GSO Support 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Authors: Simon Horman (horms@verge.net.au) 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Based on: GSO portions of net/ipv4/gre.c 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/err.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/netdev_features.h> 1562306a36Sopenharmony_ci#include <linux/netdevice.h> 1662306a36Sopenharmony_ci#include <linux/skbuff.h> 1762306a36Sopenharmony_ci#include <net/gso.h> 1862306a36Sopenharmony_ci#include <net/mpls.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic struct sk_buff *mpls_gso_segment(struct sk_buff *skb, 2162306a36Sopenharmony_ci netdev_features_t features) 2262306a36Sopenharmony_ci{ 2362306a36Sopenharmony_ci struct sk_buff *segs = ERR_PTR(-EINVAL); 2462306a36Sopenharmony_ci u16 mac_offset = skb->mac_header; 2562306a36Sopenharmony_ci netdev_features_t mpls_features; 2662306a36Sopenharmony_ci u16 mac_len = skb->mac_len; 2762306a36Sopenharmony_ci __be16 mpls_protocol; 2862306a36Sopenharmony_ci unsigned int mpls_hlen; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci skb_reset_network_header(skb); 3162306a36Sopenharmony_ci mpls_hlen = skb_inner_network_header(skb) - skb_network_header(skb); 3262306a36Sopenharmony_ci if (unlikely(!mpls_hlen || mpls_hlen % MPLS_HLEN)) 3362306a36Sopenharmony_ci goto out; 3462306a36Sopenharmony_ci if (unlikely(!pskb_may_pull(skb, mpls_hlen))) 3562306a36Sopenharmony_ci goto out; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci /* Setup inner SKB. */ 3862306a36Sopenharmony_ci mpls_protocol = skb->protocol; 3962306a36Sopenharmony_ci skb->protocol = skb->inner_protocol; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci __skb_pull(skb, mpls_hlen); 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci skb->mac_len = 0; 4462306a36Sopenharmony_ci skb_reset_mac_header(skb); 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci /* Segment inner packet. */ 4762306a36Sopenharmony_ci mpls_features = skb->dev->mpls_features & features; 4862306a36Sopenharmony_ci segs = skb_mac_gso_segment(skb, mpls_features); 4962306a36Sopenharmony_ci if (IS_ERR_OR_NULL(segs)) { 5062306a36Sopenharmony_ci skb_gso_error_unwind(skb, mpls_protocol, mpls_hlen, mac_offset, 5162306a36Sopenharmony_ci mac_len); 5262306a36Sopenharmony_ci goto out; 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci skb = segs; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci mpls_hlen += mac_len; 5762306a36Sopenharmony_ci do { 5862306a36Sopenharmony_ci skb->mac_len = mac_len; 5962306a36Sopenharmony_ci skb->protocol = mpls_protocol; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci skb_reset_inner_network_header(skb); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci __skb_push(skb, mpls_hlen); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci skb_reset_mac_header(skb); 6662306a36Sopenharmony_ci skb_set_network_header(skb, mac_len); 6762306a36Sopenharmony_ci } while ((skb = skb->next)); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ciout: 7062306a36Sopenharmony_ci return segs; 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic struct packet_offload mpls_mc_offload __read_mostly = { 7462306a36Sopenharmony_ci .type = cpu_to_be16(ETH_P_MPLS_MC), 7562306a36Sopenharmony_ci .priority = 15, 7662306a36Sopenharmony_ci .callbacks = { 7762306a36Sopenharmony_ci .gso_segment = mpls_gso_segment, 7862306a36Sopenharmony_ci }, 7962306a36Sopenharmony_ci}; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic struct packet_offload mpls_uc_offload __read_mostly = { 8262306a36Sopenharmony_ci .type = cpu_to_be16(ETH_P_MPLS_UC), 8362306a36Sopenharmony_ci .priority = 15, 8462306a36Sopenharmony_ci .callbacks = { 8562306a36Sopenharmony_ci .gso_segment = mpls_gso_segment, 8662306a36Sopenharmony_ci }, 8762306a36Sopenharmony_ci}; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic int __init mpls_gso_init(void) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci pr_info("MPLS GSO support\n"); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci dev_add_offload(&mpls_uc_offload); 9462306a36Sopenharmony_ci dev_add_offload(&mpls_mc_offload); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci return 0; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic void __exit mpls_gso_exit(void) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci dev_remove_offload(&mpls_uc_offload); 10262306a36Sopenharmony_ci dev_remove_offload(&mpls_mc_offload); 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cimodule_init(mpls_gso_init); 10662306a36Sopenharmony_cimodule_exit(mpls_gso_exit); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ciMODULE_DESCRIPTION("MPLS GSO support"); 10962306a36Sopenharmony_ciMODULE_AUTHOR("Simon Horman (horms@verge.net.au)"); 11062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 111