Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
00959ade DK |
2 | /* |
3 | * GRE over IPv4 demultiplexer driver | |
4 | * | |
5 | * Authors: Dmitry Kozlov (xeb@mail.ru) | |
00959ade DK |
6 | */ |
7 | ||
afd46503 JP |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | ||
00959ade | 10 | #include <linux/module.h> |
bda7bb46 PS |
11 | #include <linux/if.h> |
12 | #include <linux/icmp.h> | |
00959ade DK |
13 | #include <linux/kernel.h> |
14 | #include <linux/kmod.h> | |
15 | #include <linux/skbuff.h> | |
16 | #include <linux/in.h> | |
559fafb9 | 17 | #include <linux/ip.h> |
00959ade | 18 | #include <linux/netdevice.h> |
68c33163 | 19 | #include <linux/if_tunnel.h> |
00959ade DK |
20 | #include <linux/spinlock.h> |
21 | #include <net/protocol.h> | |
22 | #include <net/gre.h> | |
cb73ee40 | 23 | #include <net/erspan.h> |
00959ade | 24 | |
bda7bb46 PS |
25 | #include <net/icmp.h> |
26 | #include <net/route.h> | |
27 | #include <net/xfrm.h> | |
00959ade | 28 | |
6f0bcf15 | 29 | static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly; |
00959ade DK |
30 | |
31 | int gre_add_protocol(const struct gre_protocol *proto, u8 version) | |
32 | { | |
33 | if (version >= GREPROTO_MAX) | |
20fd4d1f | 34 | return -EINVAL; |
00959ade | 35 | |
20fd4d1f PS |
36 | return (cmpxchg((const struct gre_protocol **)&gre_proto[version], NULL, proto) == NULL) ? |
37 | 0 : -EBUSY; | |
00959ade DK |
38 | } |
39 | EXPORT_SYMBOL_GPL(gre_add_protocol); | |
40 | ||
41 | int gre_del_protocol(const struct gre_protocol *proto, u8 version) | |
42 | { | |
20fd4d1f PS |
43 | int ret; |
44 | ||
00959ade | 45 | if (version >= GREPROTO_MAX) |
20fd4d1f PS |
46 | return -EINVAL; |
47 | ||
48 | ret = (cmpxchg((const struct gre_protocol **)&gre_proto[version], proto, NULL) == proto) ? | |
49 | 0 : -EBUSY; | |
50 | ||
51 | if (ret) | |
52 | return ret; | |
53 | ||
00959ade DK |
54 | synchronize_rcu(); |
55 | return 0; | |
00959ade DK |
56 | } |
57 | EXPORT_SYMBOL_GPL(gre_del_protocol); | |
58 | ||
17c25caf ED |
59 | /* Fills in tpi and returns header length to be pulled. |
60 | * Note that caller must use pskb_may_pull() before pulling GRE header. | |
61 | */ | |
95f5c64c | 62 | int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi, |
e582615a | 63 | bool *csum_err, __be16 proto, int nhs) |
95f5c64c TH |
64 | { |
65 | const struct gre_base_hdr *greh; | |
66 | __be32 *options; | |
67 | int hdr_len; | |
68 | ||
e582615a | 69 | if (unlikely(!pskb_may_pull(skb, nhs + sizeof(struct gre_base_hdr)))) |
95f5c64c TH |
70 | return -EINVAL; |
71 | ||
e582615a | 72 | greh = (struct gre_base_hdr *)(skb->data + nhs); |
95f5c64c TH |
73 | if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING))) |
74 | return -EINVAL; | |
75 | ||
76 | tpi->flags = gre_flags_to_tnl_flags(greh->flags); | |
77 | hdr_len = gre_calc_hlen(tpi->flags); | |
78 | ||
e582615a | 79 | if (!pskb_may_pull(skb, nhs + hdr_len)) |
95f5c64c TH |
80 | return -EINVAL; |
81 | ||
e582615a | 82 | greh = (struct gre_base_hdr *)(skb->data + nhs); |
95f5c64c TH |
83 | tpi->proto = greh->protocol; |
84 | ||
85 | options = (__be32 *)(greh + 1); | |
86 | if (greh->flags & GRE_CSUM) { | |
b0350d51 | 87 | if (!skb_checksum_simple_validate(skb)) { |
e4aa33ad | 88 | skb_checksum_try_convert(skb, IPPROTO_GRE, |
b0350d51 HY |
89 | null_compute_pseudo); |
90 | } else if (csum_err) { | |
95f5c64c TH |
91 | *csum_err = true; |
92 | return -EINVAL; | |
93 | } | |
94 | ||
95f5c64c TH |
95 | options++; |
96 | } | |
97 | ||
98 | if (greh->flags & GRE_KEY) { | |
99 | tpi->key = *options; | |
100 | options++; | |
101 | } else { | |
102 | tpi->key = 0; | |
103 | } | |
104 | if (unlikely(greh->flags & GRE_SEQ)) { | |
105 | tpi->seq = *options; | |
106 | options++; | |
107 | } else { | |
108 | tpi->seq = 0; | |
109 | } | |
110 | /* WCCP version 1 and 2 protocol decoding. | |
da73b4e9 | 111 | * - Change protocol to IPv4/IPv6 |
95f5c64c TH |
112 | * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header |
113 | */ | |
114 | if (greh->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) { | |
17c25caf ED |
115 | u8 _val, *val; |
116 | ||
117 | val = skb_header_pointer(skb, nhs + hdr_len, | |
118 | sizeof(_val), &_val); | |
119 | if (!val) | |
120 | return -EINVAL; | |
da73b4e9 | 121 | tpi->proto = proto; |
17c25caf | 122 | if ((*val & 0xF0) != 0x40) |
95f5c64c | 123 | hdr_len += 4; |
95f5c64c | 124 | } |
9b8c6d7b | 125 | tpi->hdr_len = hdr_len; |
cb73ee40 LB |
126 | |
127 | /* ERSPAN ver 1 and 2 protocol sets GRE key field | |
128 | * to 0 and sets the configured key in the | |
129 | * inner erspan header field | |
130 | */ | |
085c7c4e | 131 | if ((greh->protocol == htons(ETH_P_ERSPAN) && hdr_len != 4) || |
cb73ee40 LB |
132 | greh->protocol == htons(ETH_P_ERSPAN2)) { |
133 | struct erspan_base_hdr *ershdr; | |
134 | ||
135 | if (!pskb_may_pull(skb, nhs + hdr_len + sizeof(*ershdr))) | |
136 | return -EINVAL; | |
137 | ||
0e494092 | 138 | ershdr = (struct erspan_base_hdr *)(skb->data + nhs + hdr_len); |
cb73ee40 LB |
139 | tpi->key = cpu_to_be32(get_session_id(ershdr)); |
140 | } | |
141 | ||
f132ae7c | 142 | return hdr_len; |
95f5c64c TH |
143 | } |
144 | EXPORT_SYMBOL(gre_parse_header); | |
145 | ||
00959ade DK |
146 | static int gre_rcv(struct sk_buff *skb) |
147 | { | |
148 | const struct gre_protocol *proto; | |
149 | u8 ver; | |
150 | int ret; | |
151 | ||
152 | if (!pskb_may_pull(skb, 12)) | |
153 | goto drop; | |
154 | ||
155 | ver = skb->data[1]&0x7f; | |
156 | if (ver >= GREPROTO_MAX) | |
157 | goto drop; | |
158 | ||
159 | rcu_read_lock(); | |
160 | proto = rcu_dereference(gre_proto[ver]); | |
161 | if (!proto || !proto->handler) | |
162 | goto drop_unlock; | |
163 | ret = proto->handler(skb); | |
164 | rcu_read_unlock(); | |
165 | return ret; | |
166 | ||
167 | drop_unlock: | |
168 | rcu_read_unlock(); | |
169 | drop: | |
170 | kfree_skb(skb); | |
171 | return NET_RX_DROP; | |
172 | } | |
173 | ||
32bbd879 | 174 | static int gre_err(struct sk_buff *skb, u32 info) |
00959ade DK |
175 | { |
176 | const struct gre_protocol *proto; | |
559fafb9 | 177 | const struct iphdr *iph = (const struct iphdr *)skb->data; |
178 | u8 ver = skb->data[(iph->ihl<<2) + 1]&0x7f; | |
32bbd879 | 179 | int err = 0; |
00959ade | 180 | |
00959ade | 181 | if (ver >= GREPROTO_MAX) |
32bbd879 | 182 | return -EINVAL; |
00959ade DK |
183 | |
184 | rcu_read_lock(); | |
185 | proto = rcu_dereference(gre_proto[ver]); | |
559fafb9 | 186 | if (proto && proto->err_handler) |
187 | proto->err_handler(skb, info); | |
32bbd879 SB |
188 | else |
189 | err = -EPROTONOSUPPORT; | |
00959ade | 190 | rcu_read_unlock(); |
32bbd879 SB |
191 | |
192 | return err; | |
00959ade DK |
193 | } |
194 | ||
195 | static const struct net_protocol net_gre_protocol = { | |
196 | .handler = gre_rcv, | |
197 | .err_handler = gre_err, | |
00959ade DK |
198 | }; |
199 | ||
200 | static int __init gre_init(void) | |
201 | { | |
afd46503 | 202 | pr_info("GRE over IPv4 demultiplexor driver\n"); |
00959ade DK |
203 | |
204 | if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) { | |
afd46503 | 205 | pr_err("can't add protocol\n"); |
9f57c67c | 206 | return -EAGAIN; |
bda7bb46 | 207 | } |
00959ade DK |
208 | return 0; |
209 | } | |
210 | ||
211 | static void __exit gre_exit(void) | |
212 | { | |
213 | inet_del_protocol(&net_gre_protocol, IPPROTO_GRE); | |
214 | } | |
215 | ||
216 | module_init(gre_init); | |
217 | module_exit(gre_exit); | |
218 | ||
219 | MODULE_DESCRIPTION("GRE over IPv4 demultiplexer driver"); | |
220 | MODULE_AUTHOR("D. Kozlov (xeb@mail.ru)"); | |
221 | MODULE_LICENSE("GPL"); |