hc
2024-03-26 e0728245c89800c2038c23308f2d88969d5b41c8
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *    MPLS GSO Support
 *
 *    Authors: Simon Horman (horms@verge.net.au)
 *
 *    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>
#include <net/mpls.h>
 
static struct sk_buff *mpls_gso_segment(struct sk_buff *skb,
                      netdev_features_t features)
{
   struct sk_buff *segs = ERR_PTR(-EINVAL);
   u16 mac_offset = skb->mac_header;
   netdev_features_t mpls_features;
   u16 mac_len = skb->mac_len;
   __be16 mpls_protocol;
   unsigned int mpls_hlen;
 
   skb_reset_network_header(skb);
   mpls_hlen = skb_inner_network_header(skb) - skb_network_header(skb);
   if (unlikely(!mpls_hlen || mpls_hlen % MPLS_HLEN))
       goto out;
   if (unlikely(!pskb_may_pull(skb, mpls_hlen)))
       goto out;
 
   /* Setup inner SKB. */
   mpls_protocol = skb->protocol;
   skb->protocol = skb->inner_protocol;
 
   __skb_pull(skb, mpls_hlen);
 
   skb->mac_len = 0;
   skb_reset_mac_header(skb);
 
   /* Segment inner packet. */
   mpls_features = skb->dev->mpls_features & features;
   segs = skb_mac_gso_segment(skb, mpls_features);
   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;
 
       skb_reset_inner_network_header(skb);
 
       __skb_push(skb, mpls_hlen);
 
       skb_reset_mac_header(skb);
       skb_set_network_header(skb, mac_len);
   } while ((skb = skb->next));
 
out:
   return segs;
}
 
static struct packet_offload mpls_mc_offload __read_mostly = {
   .type = cpu_to_be16(ETH_P_MPLS_MC),
   .priority = 15,
   .callbacks = {
       .gso_segment    =    mpls_gso_segment,
   },
};
 
static struct packet_offload mpls_uc_offload __read_mostly = {
   .type = cpu_to_be16(ETH_P_MPLS_UC),
   .priority = 15,
   .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");