18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *	MPLS GSO Support
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *	Authors: Simon Horman (horms@verge.net.au)
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci *	Based on: GSO portions of net/ipv4/gre.c
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/err.h>
138c2ecf20Sopenharmony_ci#include <linux/module.h>
148c2ecf20Sopenharmony_ci#include <linux/netdev_features.h>
158c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
168c2ecf20Sopenharmony_ci#include <linux/skbuff.h>
178c2ecf20Sopenharmony_ci#include <net/mpls.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistatic struct sk_buff *mpls_gso_segment(struct sk_buff *skb,
208c2ecf20Sopenharmony_ci				       netdev_features_t features)
218c2ecf20Sopenharmony_ci{
228c2ecf20Sopenharmony_ci	struct sk_buff *segs = ERR_PTR(-EINVAL);
238c2ecf20Sopenharmony_ci	u16 mac_offset = skb->mac_header;
248c2ecf20Sopenharmony_ci	netdev_features_t mpls_features;
258c2ecf20Sopenharmony_ci	u16 mac_len = skb->mac_len;
268c2ecf20Sopenharmony_ci	__be16 mpls_protocol;
278c2ecf20Sopenharmony_ci	unsigned int mpls_hlen;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	skb_reset_network_header(skb);
308c2ecf20Sopenharmony_ci	mpls_hlen = skb_inner_network_header(skb) - skb_network_header(skb);
318c2ecf20Sopenharmony_ci	if (unlikely(!mpls_hlen || mpls_hlen % MPLS_HLEN))
328c2ecf20Sopenharmony_ci		goto out;
338c2ecf20Sopenharmony_ci	if (unlikely(!pskb_may_pull(skb, mpls_hlen)))
348c2ecf20Sopenharmony_ci		goto out;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	/* Setup inner SKB. */
378c2ecf20Sopenharmony_ci	mpls_protocol = skb->protocol;
388c2ecf20Sopenharmony_ci	skb->protocol = skb->inner_protocol;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	__skb_pull(skb, mpls_hlen);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	skb->mac_len = 0;
438c2ecf20Sopenharmony_ci	skb_reset_mac_header(skb);
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	/* Segment inner packet. */
468c2ecf20Sopenharmony_ci	mpls_features = skb->dev->mpls_features & features;
478c2ecf20Sopenharmony_ci	segs = skb_mac_gso_segment(skb, mpls_features);
488c2ecf20Sopenharmony_ci	if (IS_ERR_OR_NULL(segs)) {
498c2ecf20Sopenharmony_ci		skb_gso_error_unwind(skb, mpls_protocol, mpls_hlen, mac_offset,
508c2ecf20Sopenharmony_ci				     mac_len);
518c2ecf20Sopenharmony_ci		goto out;
528c2ecf20Sopenharmony_ci	}
538c2ecf20Sopenharmony_ci	skb = segs;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	mpls_hlen += mac_len;
568c2ecf20Sopenharmony_ci	do {
578c2ecf20Sopenharmony_ci		skb->mac_len = mac_len;
588c2ecf20Sopenharmony_ci		skb->protocol = mpls_protocol;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci		skb_reset_inner_network_header(skb);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci		__skb_push(skb, mpls_hlen);
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci		skb_reset_mac_header(skb);
658c2ecf20Sopenharmony_ci		skb_set_network_header(skb, mac_len);
668c2ecf20Sopenharmony_ci	} while ((skb = skb->next));
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ciout:
698c2ecf20Sopenharmony_ci	return segs;
708c2ecf20Sopenharmony_ci}
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_cistatic struct packet_offload mpls_mc_offload __read_mostly = {
738c2ecf20Sopenharmony_ci	.type = cpu_to_be16(ETH_P_MPLS_MC),
748c2ecf20Sopenharmony_ci	.priority = 15,
758c2ecf20Sopenharmony_ci	.callbacks = {
768c2ecf20Sopenharmony_ci		.gso_segment    =	mpls_gso_segment,
778c2ecf20Sopenharmony_ci	},
788c2ecf20Sopenharmony_ci};
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic struct packet_offload mpls_uc_offload __read_mostly = {
818c2ecf20Sopenharmony_ci	.type = cpu_to_be16(ETH_P_MPLS_UC),
828c2ecf20Sopenharmony_ci	.priority = 15,
838c2ecf20Sopenharmony_ci	.callbacks = {
848c2ecf20Sopenharmony_ci		.gso_segment    =	mpls_gso_segment,
858c2ecf20Sopenharmony_ci	},
868c2ecf20Sopenharmony_ci};
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_cistatic int __init mpls_gso_init(void)
898c2ecf20Sopenharmony_ci{
908c2ecf20Sopenharmony_ci	pr_info("MPLS GSO support\n");
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	dev_add_offload(&mpls_uc_offload);
938c2ecf20Sopenharmony_ci	dev_add_offload(&mpls_mc_offload);
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	return 0;
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistatic void __exit mpls_gso_exit(void)
998c2ecf20Sopenharmony_ci{
1008c2ecf20Sopenharmony_ci	dev_remove_offload(&mpls_uc_offload);
1018c2ecf20Sopenharmony_ci	dev_remove_offload(&mpls_mc_offload);
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_cimodule_init(mpls_gso_init);
1058c2ecf20Sopenharmony_cimodule_exit(mpls_gso_exit);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MPLS GSO support");
1088c2ecf20Sopenharmony_ciMODULE_AUTHOR("Simon Horman (horms@verge.net.au)");
1098c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
110