xref: /openbsd-src/sys/net/if_sec.c (revision f90ef06a3045119dcc88b72d8b98ca60e3c00d5a)
1 /*	$OpenBSD: if_sec.c,v 1.5 2023/08/11 02:34:56 dlg Exp $ */
2 
3 /*
4  * Copyright (c) 2022 The University of Queensland
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /*
20  * This code was written by David Gwynne <dlg@uq.edu.au> as part
21  * of the Information Technology Infrastructure Group (ITIG) in the
22  * Faculty of Engineering, Architecture and Information Technology
23  * (EAIT).
24  */
25 
26 #ifndef IPSEC
27 #error sec enabled without IPSEC defined
28 #endif
29 
30 #include "bpfilter.h"
31 #include "pf.h"
32 
33 #include <sys/param.h>
34 #include <sys/mbuf.h>
35 #include <sys/socket.h>
36 #include <sys/sockio.h>
37 #include <sys/systm.h>
38 #include <sys/errno.h>
39 #include <sys/smr.h>
40 #include <sys/refcnt.h>
41 #include <sys/task.h>
42 #include <sys/mutex.h>
43 
44 #include <net/if.h>
45 #include <net/if_var.h>
46 #include <net/if_types.h>
47 #include <net/toeplitz.h>
48 
49 #include <netinet/in.h>
50 #include <netinet/ip.h>
51 #include <netinet/ip_ipsp.h>
52 
53 #ifdef INET6
54 #include <netinet/ip6.h>
55 #endif
56 
57 #if NBPFILTER > 0
58 #include <net/bpf.h>
59 #endif
60 
61 #if NPF > 0
62 #include <net/pfvar.h>
63 #endif
64 
65 #define SEC_MTU		1280
66 #define SEC_MTU_MIN	1280
67 #define SEC_MTU_MAX	32768	/* could get closer to 64k... */
68 
69 struct sec_softc {
70 	struct ifnet			sc_if;
71 	unsigned int			sc_dead;
72 	unsigned int			sc_up;
73 
74 	struct task			sc_send;
75 
76 	unsigned int			sc_unit;
77 	SMR_SLIST_ENTRY(sec_softc)	sc_entry;
78 	struct refcnt			sc_refs;
79 };
80 
81 SMR_SLIST_HEAD(sec_bucket, sec_softc);
82 
83 static int	sec_output(struct ifnet *, struct mbuf *, struct sockaddr *,
84 		    struct rtentry *);
85 static int	sec_enqueue(struct ifnet *, struct mbuf *);
86 static void	sec_send(void *);
87 static void	sec_start(struct ifqueue *);
88 
89 static int	sec_ioctl(struct ifnet *, u_long, caddr_t);
90 static int	sec_up(struct sec_softc *);
91 static int	sec_down(struct sec_softc *);
92 
93 static int	sec_clone_create(struct if_clone *, int);
94 static int	sec_clone_destroy(struct ifnet *);
95 
96 static struct tdb *
97 		sec_tdb_get(unsigned int);
98 static void	sec_tdb_gc(void *);
99 
100 static struct if_clone sec_cloner =
101     IF_CLONE_INITIALIZER("sec", sec_clone_create, sec_clone_destroy);
102 
103 static struct sec_bucket	 sec_map[256] __aligned(CACHELINESIZE);
104 static struct tdb		*sec_tdbh[256] __aligned(CACHELINESIZE);
105 
106 static struct tdb		*sec_tdb_gc_list;
107 static struct task		 sec_tdb_gc_task =
108 				     TASK_INITIALIZER(sec_tdb_gc, NULL);
109 static struct mutex		 sec_tdb_gc_mtx =
110 				     MUTEX_INITIALIZER(IPL_MPFLOOR);
111 
112 void
113 secattach(int n)
114 {
115 	if_clone_attach(&sec_cloner);
116 }
117 
118 static int
119 sec_clone_create(struct if_clone *ifc, int unit)
120 {
121 	struct sec_softc *sc;
122 	struct ifnet *ifp;
123 
124 	sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO);
125 
126 	sc->sc_unit = unit;
127 
128 	task_set(&sc->sc_send, sec_send, sc);
129 
130 	snprintf(sc->sc_if.if_xname, sizeof sc->sc_if.if_xname, "%s%d",
131 	    ifc->ifc_name, unit);
132 
133 	ifp = &sc->sc_if;
134 	ifp->if_softc = sc;
135 	ifp->if_type = IFT_TUNNEL;
136 	ifp->if_mtu = SEC_MTU;
137 	ifp->if_flags = IFF_POINTOPOINT|IFF_MULTICAST;
138 	ifp->if_xflags = IFXF_CLONED | IFXF_MPSAFE;
139 	ifp->if_bpf_mtap = p2p_bpf_mtap;
140 	ifp->if_input = p2p_input;
141 	ifp->if_output = sec_output;
142 	ifp->if_enqueue = sec_enqueue;
143 	ifp->if_qstart = sec_start;
144 	ifp->if_ioctl = sec_ioctl;
145 	ifp->if_rtrequest = p2p_rtrequest;
146 
147 	if_counters_alloc(ifp);
148 	if_attach(ifp);
149 	if_alloc_sadl(ifp);
150 
151 #if NBPFILTER > 0
152 	bpfattach(&ifp->if_bpf, ifp, DLT_LOOP, sizeof(uint32_t));
153 #endif
154 
155 	return (0);
156 }
157 
158 static int
159 sec_clone_destroy(struct ifnet *ifp)
160 {
161 	struct sec_softc *sc = ifp->if_softc;
162 
163 	NET_LOCK();
164 	sc->sc_dead = 1;
165 	if (ISSET(ifp->if_flags, IFF_RUNNING))
166 		sec_down(sc);
167 	NET_UNLOCK();
168 
169 	if_detach(ifp);
170 
171 	free(sc, M_DEVBUF, sizeof(*sc));
172 
173 	return (0);
174 }
175 
176 static int
177 sec_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
178 {
179 	struct sec_softc *sc = ifp->if_softc;
180 	struct ifreq *ifr = (struct ifreq *)data;
181 	int error = 0;
182 
183 	switch (cmd) {
184 	case SIOCSIFADDR:
185 		break;
186 
187 	case SIOCSIFFLAGS:
188 		if (ISSET(ifp->if_flags, IFF_UP)) {
189 			if (!ISSET(ifp->if_flags, IFF_RUNNING))
190 				error = sec_up(sc);
191 			else
192 				error = 0;
193 		} else {
194 			if (ISSET(ifp->if_flags, IFF_RUNNING))
195 				error = sec_down(sc);
196 		}
197 		break;
198 
199 	case SIOCADDMULTI:
200 	case SIOCDELMULTI:
201 		break;
202 
203 	case SIOCSIFMTU:
204 		if (ifr->ifr_mtu < SEC_MTU_MIN ||
205 		    ifr->ifr_mtu > SEC_MTU_MAX) {
206 			error = EINVAL;
207 			break;
208 		}
209 
210 		ifp->if_mtu = ifr->ifr_mtu;
211 		break;
212 
213 	default:
214 		error = ENOTTY;
215 		break;
216 	}
217 
218 	return (error);
219 }
220 
221 static int
222 sec_up(struct sec_softc *sc)
223 {
224 	struct ifnet *ifp = &sc->sc_if;
225 	unsigned int idx = stoeplitz_h32(sc->sc_unit) % nitems(sec_map);
226 
227 	NET_ASSERT_LOCKED();
228 	KASSERT(!ISSET(ifp->if_flags, IFF_RUNNING));
229 
230 	if (sc->sc_dead)
231 		return (ENXIO);
232 
233 	/*
234 	 * coordinate with sec_down(). if sc_up is still up and
235 	 * we're here then something else is running sec_down.
236 	 */
237 	if (sc->sc_up)
238 		return (EBUSY);
239 
240 	sc->sc_up = 1;
241 
242 	refcnt_init(&sc->sc_refs);
243 	SET(ifp->if_flags, IFF_RUNNING);
244 	SMR_SLIST_INSERT_HEAD_LOCKED(&sec_map[idx], sc, sc_entry);
245 
246 	return (0);
247 }
248 
249 static int
250 sec_down(struct sec_softc *sc)
251 {
252 	struct ifnet *ifp = &sc->sc_if;
253 	unsigned int idx = stoeplitz_h32(sc->sc_unit) % nitems(sec_map);
254 
255 	NET_ASSERT_LOCKED();
256 	KASSERT(ISSET(ifp->if_flags, IFF_RUNNING));
257 
258 	/*
259 	 * taking sec down involves waiting for it to stop running
260 	 * in various contexts. this thread cannot hold netlock
261 	 * while waiting for a barrier for a task that could be trying
262 	 * to take netlock itself. so give up netlock, but don't clear
263 	 * sc_up to prevent sec_up from running.
264 	 */
265 
266 	CLR(ifp->if_flags, IFF_RUNNING);
267 	NET_UNLOCK();
268 
269 	smr_barrier();
270 	taskq_del_barrier(systq, &sc->sc_send);
271 
272 	refcnt_finalize(&sc->sc_refs, "secdown");
273 
274 	NET_LOCK();
275 	SMR_SLIST_REMOVE_LOCKED(&sec_map[idx], sc, sec_softc, sc_entry);
276 	sc->sc_up = 0;
277 
278 	return (0);
279 }
280 
281 static int
282 sec_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
283     struct rtentry *rt)
284 {
285 	struct m_tag *mtag;
286 	int error = 0;
287 
288 	if (!ISSET(ifp->if_flags, IFF_RUNNING)) {
289 		error = ENETDOWN;
290 		goto drop;
291 	}
292 
293 	switch (dst->sa_family) {
294 	case AF_INET:
295 #ifdef INET6
296 	case AF_INET6:
297 #endif
298 #ifdef MPLS
299 	case AF_MPLS:
300 #endif
301 		break;
302 	default:
303 		error = EAFNOSUPPORT;
304 		goto drop;
305 	}
306 
307 	mtag = NULL;
308 	while ((mtag = m_tag_find(m, PACKET_TAG_GRE, mtag)) != NULL) {
309 		if (ifp->if_index == *(int *)(mtag + 1)) {
310 			error = EIO;
311 			goto drop;
312 		}
313 	}
314 
315 	m->m_pkthdr.ph_family = dst->sa_family;
316 
317 	error = if_enqueue(ifp, m);
318 	if (error != 0)
319 		counters_inc(ifp->if_counters, ifc_oerrors);
320 
321 	return (error);
322 
323 drop:
324 	m_freem(m);
325 	return (error);
326 }
327 
328 static int
329 sec_enqueue(struct ifnet *ifp, struct mbuf *m)
330 {
331 	struct sec_softc *sc = ifp->if_softc;
332 	struct ifqueue *ifq = &ifp->if_snd;
333 	int error;
334 
335 	error = ifq_enqueue(ifq, m);
336 	if (error)
337 		return (error);
338 
339 	task_add(systq, &sc->sc_send);
340 
341 	return (0);
342 }
343 
344 static void
345 sec_send(void *arg)
346 {
347 	struct sec_softc *sc = arg;
348 	struct ifnet *ifp = &sc->sc_if;
349 	struct ifqueue *ifq = &ifp->if_snd;
350 	struct tdb *tdb;
351 	struct mbuf *m;
352 	int error;
353 
354 	if (!ISSET(ifp->if_flags, IFF_RUNNING))
355 		return;
356 
357 	tdb = sec_tdb_get(sc->sc_unit);
358 	if (tdb == NULL)
359 		goto purge;
360 
361 	NET_LOCK();
362 	while ((m = ifq_dequeue(ifq)) != NULL) {
363 		CLR(m->m_flags, M_BCAST|M_MCAST);
364 
365 #if NPF > 0
366 		pf_pkt_addr_changed(m);
367 #endif
368 
369 		error = ipsp_process_packet(m, tdb,
370 		    m->m_pkthdr.ph_family, /* already tunnelled? */ 0);
371 		if (error != 0)
372 			counters_inc(ifp->if_counters, ifc_oerrors);
373 	}
374 	NET_UNLOCK();
375 
376 	tdb_unref(tdb);
377 	return;
378 
379 purge:
380 	counters_add(ifp->if_counters, ifc_oerrors, ifq_purge(ifq));
381 }
382 
383 static void
384 sec_start(struct ifqueue *ifq)
385 {
386 	struct ifnet *ifp = ifq->ifq_if;
387 	struct sec_softc *sc = ifp->if_softc;
388 
389 	/* move this back to systq for KERNEL_LOCK */
390 	task_add(systq, &sc->sc_send);
391 }
392 
393 /*
394  * ipsec_input handling
395  */
396 
397 struct sec_softc *
398 sec_get(unsigned int unit)
399 {
400 	unsigned int idx = stoeplitz_h32(unit) % nitems(sec_map);
401 	struct sec_bucket *sb = &sec_map[idx];
402 	struct sec_softc *sc;
403 
404 	smr_read_enter();
405 	SMR_SLIST_FOREACH(sc, sb, sc_entry) {
406 		if (sc->sc_unit == unit) {
407 			refcnt_take(&sc->sc_refs);
408 			break;
409 		}
410 	}
411 	smr_read_leave();
412 
413 	return (sc);
414 }
415 
416 void
417 sec_input(struct sec_softc *sc, int af, int proto, struct mbuf *m)
418 {
419 	struct ip *iph;
420 	int hlen;
421 
422 	switch (af) {
423 	case AF_INET:
424 		iph = mtod(m, struct ip *);
425 		hlen = iph->ip_hl << 2;
426 		break;
427 #ifdef INET6
428 	case AF_INET6:
429 		hlen = sizeof(struct ip6_hdr);
430 		break;
431 #endif
432 	default:
433 		unhandled_af(af);
434 	}
435 
436 	m_adj(m, hlen);
437 
438 	switch (proto) {
439 	case IPPROTO_IPV4:
440 		af = AF_INET;
441 		break;
442 	case IPPROTO_IPV6:
443 		af = AF_INET6;
444 		break;
445 	case IPPROTO_MPLS:
446 		af = AF_MPLS;
447 		break;
448 	default:
449 		af = AF_UNSPEC;
450 		break;
451 	}
452 
453 	m->m_pkthdr.ph_family = af;
454 
455 	if_vinput(&sc->sc_if, m);
456 }
457 
458 void
459 sec_put(struct sec_softc *sc)
460 {
461 	refcnt_rele_wake(&sc->sc_refs);
462 }
463 
464 /*
465  * tdb handling
466  */
467 
468 static int
469 sec_tdb_valid(struct tdb *tdb)
470 {
471 	KASSERT(ISSET(tdb->tdb_flags, TDBF_IFACE));
472 
473 	if (!ISSET(tdb->tdb_flags, TDBF_TUNNELING))
474 		return (0);
475 	if (ISSET(tdb->tdb_flags, TDBF_INVALID))
476 		return (0);
477 
478 	if (tdb->tdb_iface_dir != IPSP_DIRECTION_OUT)
479 		return (0);
480 
481 	return (1);
482 }
483 
484 /*
485  * these are called from netinet/ip_ipsp.c with tdb_sadb_mtx held,
486  * which we rely on to serialise modifications to the sec_tdbh.
487  */
488 
489 void
490 sec_tdb_insert(struct tdb *tdb)
491 {
492 	unsigned int idx;
493 	struct tdb **tdbp;
494 	struct tdb *ltdb;
495 
496 	if (!sec_tdb_valid(tdb))
497 		return;
498 
499 	idx = stoeplitz_h32(tdb->tdb_iface) % nitems(sec_tdbh);
500 	tdbp = &sec_tdbh[idx];
501 
502 	tdb_ref(tdb); /* take a ref for the SMR pointer */
503 
504 	/* wire the tdb into the head of the list */
505 	ltdb = SMR_PTR_GET_LOCKED(tdbp);
506 	SMR_PTR_SET_LOCKED(&tdb->tdb_dnext, ltdb);
507 	SMR_PTR_SET_LOCKED(tdbp, tdb);
508 }
509 
510 void
511 sec_tdb_remove(struct tdb *tdb)
512 {
513 	struct tdb **tdbp;
514 	struct tdb *ltdb;
515 	unsigned int idx;
516 
517 	if (!sec_tdb_valid(tdb))
518 		return;
519 
520 	idx = stoeplitz_h32(tdb->tdb_iface) % nitems(sec_tdbh);
521 	tdbp = &sec_tdbh[idx];
522 
523 	while ((ltdb = SMR_PTR_GET_LOCKED(tdbp)) != NULL) {
524 		if (ltdb == tdb) {
525 			/* take the tdb out of the list */
526 			ltdb = SMR_PTR_GET_LOCKED(&tdb->tdb_dnext);
527 			SMR_PTR_SET_LOCKED(tdbp, ltdb);
528 
529 			/* move the ref to the gc */
530 
531 			mtx_enter(&sec_tdb_gc_mtx);
532 			tdb->tdb_dnext = sec_tdb_gc_list;
533 			sec_tdb_gc_list = tdb;
534 			mtx_leave(&sec_tdb_gc_mtx);
535 			task_add(systq, &sec_tdb_gc_task);
536 
537 			return;
538 		}
539 
540 		tdbp = &ltdb->tdb_dnext;
541 	}
542 
543 	panic("%s: unable to find tdb %p", __func__, tdb);
544 }
545 
546 static void
547 sec_tdb_gc(void *null)
548 {
549 	struct tdb *tdb, *ntdb;
550 
551 	mtx_enter(&sec_tdb_gc_mtx);
552 	tdb = sec_tdb_gc_list;
553 	sec_tdb_gc_list = NULL;
554 	mtx_leave(&sec_tdb_gc_mtx);
555 
556 	if (tdb == NULL)
557 		return;
558 
559 	smr_barrier();
560 
561 	NET_LOCK();
562 	do {
563 		ntdb = tdb->tdb_dnext;
564 		tdb_unref(tdb);
565 		tdb = ntdb;
566 	} while (tdb != NULL);
567 	NET_UNLOCK();
568 }
569 
570 struct tdb *
571 sec_tdb_get(unsigned int unit)
572 {
573 	unsigned int idx;
574 	struct tdb **tdbp;
575 	struct tdb *tdb;
576 
577 	idx = stoeplitz_h32(unit) % nitems(sec_map);
578 	tdbp = &sec_tdbh[idx];
579 
580 	smr_read_enter();
581 	while ((tdb = SMR_PTR_GET(tdbp)) != NULL) {
582 		KASSERT(ISSET(tdb->tdb_flags, TDBF_IFACE));
583 		if (!ISSET(tdb->tdb_flags, TDBF_DELETED) &&
584 		    tdb->tdb_iface == unit) {
585 			tdb_ref(tdb);
586 			break;
587 		}
588 
589 		tdbp = &tdb->tdb_dnext;
590 	}
591 	smr_read_leave();
592 
593 	return (tdb);
594 }
595