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