1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* xfrm6_protocol.c - Generic xfrm protocol multiplexer for ipv6. |
3 | * |
4 | * Copyright (C) 2013 secunet Security Networks AG |
5 | * |
6 | * Author: |
7 | * Steffen Klassert <steffen.klassert@secunet.com> |
8 | * |
9 | * Based on: |
10 | * net/ipv4/xfrm4_protocol.c |
11 | */ |
12 | |
13 | #include <linux/init.h> |
14 | #include <linux/mutex.h> |
15 | #include <linux/skbuff.h> |
16 | #include <linux/icmpv6.h> |
17 | #include <net/ip6_route.h> |
18 | #include <net/ipv6.h> |
19 | #include <net/protocol.h> |
20 | #include <net/xfrm.h> |
21 | |
22 | static struct xfrm6_protocol __rcu *esp6_handlers __read_mostly; |
23 | static struct xfrm6_protocol __rcu *ah6_handlers __read_mostly; |
24 | static struct xfrm6_protocol __rcu *ipcomp6_handlers __read_mostly; |
25 | static DEFINE_MUTEX(xfrm6_protocol_mutex); |
26 | |
27 | static inline struct xfrm6_protocol __rcu **proto_handlers(u8 protocol) |
28 | { |
29 | switch (protocol) { |
30 | case IPPROTO_ESP: |
31 | return &esp6_handlers; |
32 | case IPPROTO_AH: |
33 | return &ah6_handlers; |
34 | case IPPROTO_COMP: |
35 | return &ipcomp6_handlers; |
36 | } |
37 | |
38 | return NULL; |
39 | } |
40 | |
41 | #define for_each_protocol_rcu(head, handler) \ |
42 | for (handler = rcu_dereference(head); \ |
43 | handler != NULL; \ |
44 | handler = rcu_dereference(handler->next)) \ |
45 | |
46 | static int xfrm6_rcv_cb(struct sk_buff *skb, u8 protocol, int err) |
47 | { |
48 | int ret; |
49 | struct xfrm6_protocol *handler; |
50 | struct xfrm6_protocol __rcu **head = proto_handlers(protocol); |
51 | |
52 | if (!head) |
53 | return 0; |
54 | |
55 | for_each_protocol_rcu(*proto_handlers(protocol), handler) |
56 | if ((ret = handler->cb_handler(skb, err)) <= 0) |
57 | return ret; |
58 | |
59 | return 0; |
60 | } |
61 | |
62 | int xfrm6_rcv_encap(struct sk_buff *skb, int nexthdr, __be32 spi, |
63 | int encap_type) |
64 | { |
65 | int ret; |
66 | struct xfrm6_protocol *handler; |
67 | struct xfrm6_protocol __rcu **head = proto_handlers(protocol: nexthdr); |
68 | |
69 | XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL; |
70 | XFRM_SPI_SKB_CB(skb)->family = AF_INET6; |
71 | XFRM_SPI_SKB_CB(skb)->daddroff = offsetof(struct ipv6hdr, daddr); |
72 | |
73 | if (!head) |
74 | goto out; |
75 | |
76 | if (!skb_dst(skb)) { |
77 | const struct ipv6hdr *ip6h = ipv6_hdr(skb); |
78 | int flags = RT6_LOOKUP_F_HAS_SADDR; |
79 | struct dst_entry *dst; |
80 | struct flowi6 fl6 = { |
81 | .flowi6_iif = skb->dev->ifindex, |
82 | .daddr = ip6h->daddr, |
83 | .saddr = ip6h->saddr, |
84 | .flowlabel = ip6_flowinfo(hdr: ip6h), |
85 | .flowi6_mark = skb->mark, |
86 | .flowi6_proto = ip6h->nexthdr, |
87 | }; |
88 | |
89 | dst = ip6_route_input_lookup(net: dev_net(dev: skb->dev), dev: skb->dev, fl6: &fl6, |
90 | skb, flags); |
91 | if (dst->error) |
92 | goto drop; |
93 | skb_dst_set(skb, dst); |
94 | } |
95 | |
96 | for_each_protocol_rcu(*head, handler) |
97 | if ((ret = handler->input_handler(skb, nexthdr, spi, encap_type)) != -EINVAL) |
98 | return ret; |
99 | |
100 | out: |
101 | icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, info: 0); |
102 | |
103 | drop: |
104 | kfree_skb(skb); |
105 | return 0; |
106 | } |
107 | EXPORT_SYMBOL(xfrm6_rcv_encap); |
108 | |
109 | static int xfrm6_esp_rcv(struct sk_buff *skb) |
110 | { |
111 | int ret; |
112 | struct xfrm6_protocol *handler; |
113 | |
114 | XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL; |
115 | |
116 | for_each_protocol_rcu(esp6_handlers, handler) |
117 | if ((ret = handler->handler(skb)) != -EINVAL) |
118 | return ret; |
119 | |
120 | icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, info: 0); |
121 | |
122 | kfree_skb(skb); |
123 | return 0; |
124 | } |
125 | |
126 | static int xfrm6_esp_err(struct sk_buff *skb, struct inet6_skb_parm *opt, |
127 | u8 type, u8 code, int offset, __be32 info) |
128 | { |
129 | struct xfrm6_protocol *handler; |
130 | |
131 | for_each_protocol_rcu(esp6_handlers, handler) |
132 | if (!handler->err_handler(skb, opt, type, code, offset, info)) |
133 | return 0; |
134 | |
135 | return -ENOENT; |
136 | } |
137 | |
138 | static int xfrm6_ah_rcv(struct sk_buff *skb) |
139 | { |
140 | int ret; |
141 | struct xfrm6_protocol *handler; |
142 | |
143 | XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL; |
144 | |
145 | for_each_protocol_rcu(ah6_handlers, handler) |
146 | if ((ret = handler->handler(skb)) != -EINVAL) |
147 | return ret; |
148 | |
149 | icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, info: 0); |
150 | |
151 | kfree_skb(skb); |
152 | return 0; |
153 | } |
154 | |
155 | static int xfrm6_ah_err(struct sk_buff *skb, struct inet6_skb_parm *opt, |
156 | u8 type, u8 code, int offset, __be32 info) |
157 | { |
158 | struct xfrm6_protocol *handler; |
159 | |
160 | for_each_protocol_rcu(ah6_handlers, handler) |
161 | if (!handler->err_handler(skb, opt, type, code, offset, info)) |
162 | return 0; |
163 | |
164 | return -ENOENT; |
165 | } |
166 | |
167 | static int xfrm6_ipcomp_rcv(struct sk_buff *skb) |
168 | { |
169 | int ret; |
170 | struct xfrm6_protocol *handler; |
171 | |
172 | XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL; |
173 | |
174 | for_each_protocol_rcu(ipcomp6_handlers, handler) |
175 | if ((ret = handler->handler(skb)) != -EINVAL) |
176 | return ret; |
177 | |
178 | icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, info: 0); |
179 | |
180 | kfree_skb(skb); |
181 | return 0; |
182 | } |
183 | |
184 | static int xfrm6_ipcomp_err(struct sk_buff *skb, struct inet6_skb_parm *opt, |
185 | u8 type, u8 code, int offset, __be32 info) |
186 | { |
187 | struct xfrm6_protocol *handler; |
188 | |
189 | for_each_protocol_rcu(ipcomp6_handlers, handler) |
190 | if (!handler->err_handler(skb, opt, type, code, offset, info)) |
191 | return 0; |
192 | |
193 | return -ENOENT; |
194 | } |
195 | |
196 | static const struct inet6_protocol esp6_protocol = { |
197 | .handler = xfrm6_esp_rcv, |
198 | .err_handler = xfrm6_esp_err, |
199 | .flags = INET6_PROTO_NOPOLICY, |
200 | }; |
201 | |
202 | static const struct inet6_protocol ah6_protocol = { |
203 | .handler = xfrm6_ah_rcv, |
204 | .err_handler = xfrm6_ah_err, |
205 | .flags = INET6_PROTO_NOPOLICY, |
206 | }; |
207 | |
208 | static const struct inet6_protocol ipcomp6_protocol = { |
209 | .handler = xfrm6_ipcomp_rcv, |
210 | .err_handler = xfrm6_ipcomp_err, |
211 | .flags = INET6_PROTO_NOPOLICY, |
212 | }; |
213 | |
214 | static const struct xfrm_input_afinfo xfrm6_input_afinfo = { |
215 | .family = AF_INET6, |
216 | .callback = xfrm6_rcv_cb, |
217 | }; |
218 | |
219 | static inline const struct inet6_protocol *netproto(unsigned char protocol) |
220 | { |
221 | switch (protocol) { |
222 | case IPPROTO_ESP: |
223 | return &esp6_protocol; |
224 | case IPPROTO_AH: |
225 | return &ah6_protocol; |
226 | case IPPROTO_COMP: |
227 | return &ipcomp6_protocol; |
228 | } |
229 | |
230 | return NULL; |
231 | } |
232 | |
233 | int xfrm6_protocol_register(struct xfrm6_protocol *handler, |
234 | unsigned char protocol) |
235 | { |
236 | struct xfrm6_protocol __rcu **pprev; |
237 | struct xfrm6_protocol *t; |
238 | bool add_netproto = false; |
239 | int ret = -EEXIST; |
240 | int priority = handler->priority; |
241 | |
242 | if (!proto_handlers(protocol) || !netproto(protocol)) |
243 | return -EINVAL; |
244 | |
245 | mutex_lock(&xfrm6_protocol_mutex); |
246 | |
247 | if (!rcu_dereference_protected(*proto_handlers(protocol), |
248 | lockdep_is_held(&xfrm6_protocol_mutex))) |
249 | add_netproto = true; |
250 | |
251 | for (pprev = proto_handlers(protocol); |
252 | (t = rcu_dereference_protected(*pprev, |
253 | lockdep_is_held(&xfrm6_protocol_mutex))) != NULL; |
254 | pprev = &t->next) { |
255 | if (t->priority < priority) |
256 | break; |
257 | if (t->priority == priority) |
258 | goto err; |
259 | } |
260 | |
261 | handler->next = *pprev; |
262 | rcu_assign_pointer(*pprev, handler); |
263 | |
264 | ret = 0; |
265 | |
266 | err: |
267 | mutex_unlock(lock: &xfrm6_protocol_mutex); |
268 | |
269 | if (add_netproto) { |
270 | if (inet6_add_protocol(prot: netproto(protocol), num: protocol)) { |
271 | pr_err("%s: can't add protocol\n" , __func__); |
272 | ret = -EAGAIN; |
273 | } |
274 | } |
275 | |
276 | return ret; |
277 | } |
278 | EXPORT_SYMBOL(xfrm6_protocol_register); |
279 | |
280 | int xfrm6_protocol_deregister(struct xfrm6_protocol *handler, |
281 | unsigned char protocol) |
282 | { |
283 | struct xfrm6_protocol __rcu **pprev; |
284 | struct xfrm6_protocol *t; |
285 | int ret = -ENOENT; |
286 | |
287 | if (!proto_handlers(protocol) || !netproto(protocol)) |
288 | return -EINVAL; |
289 | |
290 | mutex_lock(&xfrm6_protocol_mutex); |
291 | |
292 | for (pprev = proto_handlers(protocol); |
293 | (t = rcu_dereference_protected(*pprev, |
294 | lockdep_is_held(&xfrm6_protocol_mutex))) != NULL; |
295 | pprev = &t->next) { |
296 | if (t == handler) { |
297 | *pprev = handler->next; |
298 | ret = 0; |
299 | break; |
300 | } |
301 | } |
302 | |
303 | if (!rcu_dereference_protected(*proto_handlers(protocol), |
304 | lockdep_is_held(&xfrm6_protocol_mutex))) { |
305 | if (inet6_del_protocol(prot: netproto(protocol), num: protocol) < 0) { |
306 | pr_err("%s: can't remove protocol\n" , __func__); |
307 | ret = -EAGAIN; |
308 | } |
309 | } |
310 | |
311 | mutex_unlock(lock: &xfrm6_protocol_mutex); |
312 | |
313 | synchronize_net(); |
314 | |
315 | return ret; |
316 | } |
317 | EXPORT_SYMBOL(xfrm6_protocol_deregister); |
318 | |
319 | int __init xfrm6_protocol_init(void) |
320 | { |
321 | return xfrm_input_register_afinfo(afinfo: &xfrm6_input_afinfo); |
322 | } |
323 | |
324 | void xfrm6_protocol_fini(void) |
325 | { |
326 | xfrm_input_unregister_afinfo(afinfo: &xfrm6_input_afinfo); |
327 | } |
328 | |