1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * GRE over IPv4 demultiplexer driver |
4 | * |
5 | * Authors: Dmitry Kozlov (xeb@mail.ru) |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/if.h> |
12 | #include <linux/icmp.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/kmod.h> |
15 | #include <linux/skbuff.h> |
16 | #include <linux/in.h> |
17 | #include <linux/ip.h> |
18 | #include <linux/netdevice.h> |
19 | #include <linux/if_tunnel.h> |
20 | #include <linux/spinlock.h> |
21 | #include <net/protocol.h> |
22 | #include <net/gre.h> |
23 | #include <net/erspan.h> |
24 | |
25 | #include <net/icmp.h> |
26 | #include <net/route.h> |
27 | #include <net/xfrm.h> |
28 | |
29 | static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly; |
30 | |
31 | int gre_add_protocol(const struct gre_protocol *proto, u8 version) |
32 | { |
33 | if (version >= GREPROTO_MAX) |
34 | return -EINVAL; |
35 | |
36 | return (cmpxchg((const struct gre_protocol **)&gre_proto[version], NULL, proto) == NULL) ? |
37 | 0 : -EBUSY; |
38 | } |
39 | EXPORT_SYMBOL_GPL(gre_add_protocol); |
40 | |
41 | int gre_del_protocol(const struct gre_protocol *proto, u8 version) |
42 | { |
43 | int ret; |
44 | |
45 | if (version >= GREPROTO_MAX) |
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 | |
54 | synchronize_rcu(); |
55 | return 0; |
56 | } |
57 | EXPORT_SYMBOL_GPL(gre_del_protocol); |
58 | |
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 | */ |
62 | int (struct sk_buff *skb, struct tnl_ptk_info *tpi, |
63 | bool *csum_err, __be16 proto, int nhs) |
64 | { |
65 | const struct gre_base_hdr *greh; |
66 | __be32 *options; |
67 | int hdr_len; |
68 | |
69 | if (unlikely(!pskb_may_pull(skb, nhs + sizeof(struct gre_base_hdr)))) |
70 | return -EINVAL; |
71 | |
72 | greh = (struct gre_base_hdr *)(skb->data + nhs); |
73 | if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING))) |
74 | return -EINVAL; |
75 | |
76 | tpi->flags = gre_flags_to_tnl_flags(flags: greh->flags); |
77 | hdr_len = gre_calc_hlen(o_flags: tpi->flags); |
78 | |
79 | if (!pskb_may_pull(skb, len: nhs + hdr_len)) |
80 | return -EINVAL; |
81 | |
82 | greh = (struct gre_base_hdr *)(skb->data + nhs); |
83 | tpi->proto = greh->protocol; |
84 | |
85 | options = (__be32 *)(greh + 1); |
86 | if (greh->flags & GRE_CSUM) { |
87 | if (!skb_checksum_simple_validate(skb)) { |
88 | skb_checksum_try_convert(skb, IPPROTO_GRE, |
89 | null_compute_pseudo); |
90 | } else if (csum_err) { |
91 | *csum_err = true; |
92 | return -EINVAL; |
93 | } |
94 | |
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. |
111 | * - Change protocol to IPv4/IPv6 |
112 | * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header |
113 | */ |
114 | if (greh->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) { |
115 | u8 _val, *val; |
116 | |
117 | val = skb_header_pointer(skb, offset: nhs + hdr_len, |
118 | len: sizeof(_val), buffer: &_val); |
119 | if (!val) |
120 | return -EINVAL; |
121 | tpi->proto = proto; |
122 | if ((*val & 0xF0) != 0x40) |
123 | hdr_len += 4; |
124 | } |
125 | tpi->hdr_len = hdr_len; |
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 | */ |
131 | if ((greh->protocol == htons(ETH_P_ERSPAN) && hdr_len != 4) || |
132 | greh->protocol == htons(ETH_P_ERSPAN2)) { |
133 | struct erspan_base_hdr *ershdr; |
134 | |
135 | if (!pskb_may_pull(skb, len: nhs + hdr_len + sizeof(*ershdr))) |
136 | return -EINVAL; |
137 | |
138 | ershdr = (struct erspan_base_hdr *)(skb->data + nhs + hdr_len); |
139 | tpi->key = cpu_to_be32(get_session_id(ershdr)); |
140 | } |
141 | |
142 | return hdr_len; |
143 | } |
144 | EXPORT_SYMBOL(gre_parse_header); |
145 | |
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, len: 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 | |
174 | static int gre_err(struct sk_buff *skb, u32 info) |
175 | { |
176 | const struct gre_protocol *proto; |
177 | const struct iphdr *iph = (const struct iphdr *)skb->data; |
178 | u8 ver = skb->data[(iph->ihl<<2) + 1]&0x7f; |
179 | int err = 0; |
180 | |
181 | if (ver >= GREPROTO_MAX) |
182 | return -EINVAL; |
183 | |
184 | rcu_read_lock(); |
185 | proto = rcu_dereference(gre_proto[ver]); |
186 | if (proto && proto->err_handler) |
187 | proto->err_handler(skb, info); |
188 | else |
189 | err = -EPROTONOSUPPORT; |
190 | rcu_read_unlock(); |
191 | |
192 | return err; |
193 | } |
194 | |
195 | static const struct net_protocol net_gre_protocol = { |
196 | .handler = gre_rcv, |
197 | .err_handler = gre_err, |
198 | }; |
199 | |
200 | static int __init gre_init(void) |
201 | { |
202 | pr_info("GRE over IPv4 demultiplexor driver\n" ); |
203 | |
204 | if (inet_add_protocol(prot: &net_gre_protocol, IPPROTO_GRE) < 0) { |
205 | pr_err("can't add protocol\n" ); |
206 | return -EAGAIN; |
207 | } |
208 | return 0; |
209 | } |
210 | |
211 | static void __exit gre_exit(void) |
212 | { |
213 | inet_del_protocol(prot: &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" ); |
222 | |