mpls_gso.c 2.5 KB
Newer Older
Simon Horman's avatar
Simon Horman committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*
 *	MPLS GSO Support
 *
 *	Authors: Simon Horman (horms@verge.net.au)
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 *
 *	Based on: GSO portions of net/ipv4/gre.c
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/err.h>
#include <linux/module.h>
#include <linux/netdev_features.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>

static struct sk_buff *mpls_gso_segment(struct sk_buff *skb,
				       netdev_features_t features)
{
	struct sk_buff *segs = ERR_PTR(-EINVAL);
David Ahern's avatar
David Ahern committed
26
	u16 mac_offset = skb->mac_header;
Simon Horman's avatar
Simon Horman committed
27
	netdev_features_t mpls_features;
David Ahern's avatar
David Ahern committed
28
	u16 mac_len = skb->mac_len;
Simon Horman's avatar
Simon Horman committed
29
	__be16 mpls_protocol;
David Ahern's avatar
David Ahern committed
30 31 32 33 34 35
	unsigned int mpls_hlen;

	skb_reset_network_header(skb);
	mpls_hlen = skb_inner_network_header(skb) - skb_network_header(skb);
	if (unlikely(!pskb_may_pull(skb, mpls_hlen)))
		goto out;
Simon Horman's avatar
Simon Horman committed
36 37 38 39 40

	/* Setup inner SKB. */
	mpls_protocol = skb->protocol;
	skb->protocol = skb->inner_protocol;

David Ahern's avatar
David Ahern committed
41 42 43 44
	__skb_pull(skb, mpls_hlen);

	skb->mac_len = 0;
	skb_reset_mac_header(skb);
Simon Horman's avatar
Simon Horman committed
45 46

	/* Segment inner packet. */
47
	mpls_features = skb->dev->mpls_features & features;
Simon Horman's avatar
Simon Horman committed
48
	segs = skb_mac_gso_segment(skb, mpls_features);
David Ahern's avatar
David Ahern committed
49 50 51 52 53 54 55 56 57 58 59
	if (IS_ERR_OR_NULL(segs)) {
		skb_gso_error_unwind(skb, mpls_protocol, mpls_hlen, mac_offset,
				     mac_len);
		goto out;
	}
	skb = segs;

	mpls_hlen += mac_len;
	do {
		skb->mac_len = mac_len;
		skb->protocol = mpls_protocol;
Simon Horman's avatar
Simon Horman committed
60

David Ahern's avatar
David Ahern committed
61
		skb_reset_inner_network_header(skb);
Simon Horman's avatar
Simon Horman committed
62

David Ahern's avatar
David Ahern committed
63
		__skb_push(skb, mpls_hlen);
Simon Horman's avatar
Simon Horman committed
64

David Ahern's avatar
David Ahern committed
65 66 67
		skb_reset_mac_header(skb);
		skb_set_network_header(skb, mac_len);
	} while ((skb = skb->next));
68

David Ahern's avatar
David Ahern committed
69
out:
Simon Horman's avatar
Simon Horman committed
70 71 72
	return segs;
}

73
static struct packet_offload mpls_mc_offload __read_mostly = {
Simon Horman's avatar
Simon Horman committed
74
	.type = cpu_to_be16(ETH_P_MPLS_MC),
75
	.priority = 15,
Simon Horman's avatar
Simon Horman committed
76 77 78 79 80
	.callbacks = {
		.gso_segment    =	mpls_gso_segment,
	},
};

81
static struct packet_offload mpls_uc_offload __read_mostly = {
Simon Horman's avatar
Simon Horman committed
82
	.type = cpu_to_be16(ETH_P_MPLS_UC),
83
	.priority = 15,
Simon Horman's avatar
Simon Horman committed
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
	.callbacks = {
		.gso_segment    =	mpls_gso_segment,
	},
};

static int __init mpls_gso_init(void)
{
	pr_info("MPLS GSO support\n");

	dev_add_offload(&mpls_uc_offload);
	dev_add_offload(&mpls_mc_offload);

	return 0;
}

static void __exit mpls_gso_exit(void)
{
	dev_remove_offload(&mpls_uc_offload);
	dev_remove_offload(&mpls_mc_offload);
}

module_init(mpls_gso_init);
module_exit(mpls_gso_exit);

MODULE_DESCRIPTION("MPLS GSO support");
MODULE_AUTHOR("Simon Horman (horms@verge.net.au)");
MODULE_LICENSE("GPL");