1 /* $OpenBSD: if_sec.c,v 1.11 2024/03/19 03:49:11 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 int sc_txprio;
76
77 unsigned int sc_unit;
78 SMR_SLIST_ENTRY(sec_softc) sc_entry;
79 struct refcnt sc_refs;
80 };
81
82 SMR_SLIST_HEAD(sec_bucket, sec_softc);
83
84 static int sec_output(struct ifnet *, struct mbuf *, struct sockaddr *,
85 struct rtentry *);
86 static int sec_enqueue(struct ifnet *, struct mbuf *);
87 static void sec_send(void *);
88 static void sec_start(struct ifqueue *);
89
90 static int sec_ioctl(struct ifnet *, u_long, caddr_t);
91 static int sec_up(struct sec_softc *);
92 static int sec_down(struct sec_softc *);
93
94 static int sec_clone_create(struct if_clone *, int);
95 static int sec_clone_destroy(struct ifnet *);
96
97 static struct tdb *
98 sec_tdb_get(unsigned int);
99 static void sec_tdb_gc(void *);
100
101 static struct if_clone sec_cloner =
102 IF_CLONE_INITIALIZER("sec", sec_clone_create, sec_clone_destroy);
103
104 static unsigned int sec_mix;
105 static struct sec_bucket sec_map[256] __aligned(CACHELINESIZE);
106 static struct tdb *sec_tdbh[256] __aligned(CACHELINESIZE);
107
108 static struct tdb *sec_tdb_gc_list;
109 static struct task sec_tdb_gc_task =
110 TASK_INITIALIZER(sec_tdb_gc, NULL);
111 static struct mutex sec_tdb_gc_mtx =
112 MUTEX_INITIALIZER(IPL_MPFLOOR);
113
114 void
secattach(int n)115 secattach(int n)
116 {
117 sec_mix = arc4random();
118 if_clone_attach(&sec_cloner);
119 }
120
121 static int
sec_clone_create(struct if_clone * ifc,int unit)122 sec_clone_create(struct if_clone *ifc, int unit)
123 {
124 struct sec_softc *sc;
125 struct ifnet *ifp;
126
127 sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO);
128
129 sc->sc_unit = unit;
130
131 task_set(&sc->sc_send, sec_send, sc);
132
133 snprintf(sc->sc_if.if_xname, sizeof sc->sc_if.if_xname, "%s%d",
134 ifc->ifc_name, unit);
135
136 ifp = &sc->sc_if;
137 ifp->if_softc = sc;
138 ifp->if_type = IFT_TUNNEL;
139 ifp->if_mtu = SEC_MTU;
140 ifp->if_flags = IFF_POINTOPOINT|IFF_MULTICAST;
141 ifp->if_xflags = IFXF_CLONED | IFXF_MPSAFE;
142 ifp->if_bpf_mtap = p2p_bpf_mtap;
143 ifp->if_input = p2p_input;
144 ifp->if_output = sec_output;
145 ifp->if_enqueue = sec_enqueue;
146 ifp->if_qstart = sec_start;
147 ifp->if_ioctl = sec_ioctl;
148 ifp->if_rtrequest = p2p_rtrequest;
149
150 if_counters_alloc(ifp);
151 if_attach(ifp);
152 if_alloc_sadl(ifp);
153
154 #if NBPFILTER > 0
155 bpfattach(&ifp->if_bpf, ifp, DLT_LOOP, sizeof(uint32_t));
156 #endif
157
158 return (0);
159 }
160
161 static int
sec_clone_destroy(struct ifnet * ifp)162 sec_clone_destroy(struct ifnet *ifp)
163 {
164 struct sec_softc *sc = ifp->if_softc;
165
166 NET_LOCK();
167 sc->sc_dead = 1;
168 if (ISSET(ifp->if_flags, IFF_RUNNING))
169 sec_down(sc);
170 NET_UNLOCK();
171
172 if_detach(ifp);
173
174 free(sc, M_DEVBUF, sizeof(*sc));
175
176 return (0);
177 }
178
179 static int
sec_ioctl(struct ifnet * ifp,u_long cmd,caddr_t data)180 sec_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
181 {
182 struct sec_softc *sc = ifp->if_softc;
183 struct ifreq *ifr = (struct ifreq *)data;
184 int error = 0;
185
186 switch (cmd) {
187 case SIOCSIFADDR:
188 break;
189
190 case SIOCSIFFLAGS:
191 if (ISSET(ifp->if_flags, IFF_UP)) {
192 if (!ISSET(ifp->if_flags, IFF_RUNNING))
193 error = sec_up(sc);
194 else
195 error = 0;
196 } else {
197 if (ISSET(ifp->if_flags, IFF_RUNNING))
198 error = sec_down(sc);
199 }
200 break;
201
202 case SIOCADDMULTI:
203 case SIOCDELMULTI:
204 break;
205
206 case SIOCSIFMTU:
207 if (ifr->ifr_mtu < SEC_MTU_MIN ||
208 ifr->ifr_mtu > SEC_MTU_MAX) {
209 error = EINVAL;
210 break;
211 }
212
213 ifp->if_mtu = ifr->ifr_mtu;
214 break;
215
216 default:
217 error = ENOTTY;
218 break;
219 }
220
221 return (error);
222 }
223
224 static int
sec_up(struct sec_softc * sc)225 sec_up(struct sec_softc *sc)
226 {
227 struct ifnet *ifp = &sc->sc_if;
228 unsigned int idx = stoeplitz_h32(sc->sc_unit) % nitems(sec_map);
229
230 NET_ASSERT_LOCKED();
231 KASSERT(!ISSET(ifp->if_flags, IFF_RUNNING));
232
233 if (sc->sc_dead)
234 return (ENXIO);
235
236 /*
237 * coordinate with sec_down(). if sc_up is still up and
238 * we're here then something else is running sec_down.
239 */
240 if (sc->sc_up)
241 return (EBUSY);
242
243 sc->sc_up = 1;
244
245 refcnt_init(&sc->sc_refs);
246 SET(ifp->if_flags, IFF_RUNNING);
247 SMR_SLIST_INSERT_HEAD_LOCKED(&sec_map[idx], sc, sc_entry);
248
249 return (0);
250 }
251
252 static int
sec_down(struct sec_softc * sc)253 sec_down(struct sec_softc *sc)
254 {
255 struct ifnet *ifp = &sc->sc_if;
256 unsigned int idx = stoeplitz_h32(sc->sc_unit) % nitems(sec_map);
257
258 NET_ASSERT_LOCKED();
259 KASSERT(ISSET(ifp->if_flags, IFF_RUNNING));
260
261 /*
262 * taking sec down involves waiting for it to stop running
263 * in various contexts. this thread cannot hold netlock
264 * while waiting for a barrier for a task that could be trying
265 * to take netlock itself. so give up netlock, but don't clear
266 * sc_up to prevent sec_up from running.
267 */
268
269 CLR(ifp->if_flags, IFF_RUNNING);
270 NET_UNLOCK();
271
272 smr_barrier();
273 taskq_del_barrier(systq, &sc->sc_send);
274
275 refcnt_finalize(&sc->sc_refs, "secdown");
276
277 NET_LOCK();
278 SMR_SLIST_REMOVE_LOCKED(&sec_map[idx], sc, sec_softc, sc_entry);
279 sc->sc_up = 0;
280
281 return (0);
282 }
283
284 static int
sec_output(struct ifnet * ifp,struct mbuf * m,struct sockaddr * dst,struct rtentry * rt)285 sec_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
286 struct rtentry *rt)
287 {
288 struct m_tag *mtag;
289 int error = 0;
290
291 if (!ISSET(ifp->if_flags, IFF_RUNNING)) {
292 error = ENETDOWN;
293 goto drop;
294 }
295
296 switch (dst->sa_family) {
297 case AF_INET:
298 #ifdef INET6
299 case AF_INET6:
300 #endif
301 #ifdef MPLS
302 case AF_MPLS:
303 #endif
304 break;
305 default:
306 error = EAFNOSUPPORT;
307 goto drop;
308 }
309
310 mtag = NULL;
311 while ((mtag = m_tag_find(m, PACKET_TAG_GRE, mtag)) != NULL) {
312 if (ifp->if_index == *(int *)(mtag + 1)) {
313 error = EIO;
314 goto drop;
315 }
316 }
317
318 mtag = m_tag_get(PACKET_TAG_GRE, sizeof(ifp->if_index), M_NOWAIT);
319 if (mtag == NULL) {
320 error = ENOBUFS;
321 goto drop;
322 }
323 *(int *)(mtag + 1) = ifp->if_index;
324 m_tag_prepend(m, mtag);
325
326 m->m_pkthdr.ph_family = dst->sa_family;
327
328 error = if_enqueue(ifp, m);
329 if (error != 0)
330 counters_inc(ifp->if_counters, ifc_oqdrops);
331
332 return (error);
333
334 drop:
335 m_freem(m);
336 return (error);
337 }
338
339 static int
sec_enqueue(struct ifnet * ifp,struct mbuf * m)340 sec_enqueue(struct ifnet *ifp, struct mbuf *m)
341 {
342 struct sec_softc *sc = ifp->if_softc;
343 struct ifqueue *ifq = &ifp->if_snd;
344 int error;
345
346 error = ifq_enqueue(ifq, m);
347 if (error)
348 return (error);
349
350 task_add(systq, &sc->sc_send);
351
352 return (0);
353 }
354
355 static void
sec_send(void * arg)356 sec_send(void *arg)
357 {
358 struct sec_softc *sc = arg;
359 struct ifnet *ifp = &sc->sc_if;
360 struct ifqueue *ifq = &ifp->if_snd;
361 struct tdb *tdb;
362 struct mbuf *m;
363 int error;
364 unsigned int flowid;
365
366 if (!ISSET(ifp->if_flags, IFF_RUNNING))
367 return;
368
369 tdb = sec_tdb_get(sc->sc_unit);
370 if (tdb == NULL)
371 goto purge;
372
373 flowid = sc->sc_unit ^ sec_mix;
374
375 NET_LOCK();
376 while ((m = ifq_dequeue(ifq)) != NULL) {
377 CLR(m->m_flags, M_BCAST|M_MCAST);
378
379 #if NPF > 0
380 pf_pkt_addr_changed(m);
381 #endif
382
383 #if NBPFILTER > 0
384 if (ifp->if_bpf)
385 bpf_mtap_af(ifp->if_bpf, m->m_pkthdr.ph_family, m,
386 BPF_DIRECTION_OUT);
387 #endif
388
389 m->m_pkthdr.pf.prio = sc->sc_txprio;
390 SET(m->m_pkthdr.csum_flags, M_FLOWID);
391 m->m_pkthdr.ph_flowid = flowid;
392
393 error = ipsp_process_packet(m, tdb,
394 m->m_pkthdr.ph_family, /* already tunnelled? */ 0);
395 if (error != 0)
396 counters_inc(ifp->if_counters, ifc_oerrors);
397 }
398 NET_UNLOCK();
399
400 tdb_unref(tdb);
401 return;
402
403 purge:
404 counters_add(ifp->if_counters, ifc_oerrors, ifq_purge(ifq));
405 }
406
407 static void
sec_start(struct ifqueue * ifq)408 sec_start(struct ifqueue *ifq)
409 {
410 struct ifnet *ifp = ifq->ifq_if;
411 struct sec_softc *sc = ifp->if_softc;
412
413 /* move this back to systq for KERNEL_LOCK */
414 task_add(systq, &sc->sc_send);
415 }
416
417 /*
418 * ipsec_input handling
419 */
420
421 struct sec_softc *
sec_get(unsigned int unit)422 sec_get(unsigned int unit)
423 {
424 unsigned int idx = stoeplitz_h32(unit) % nitems(sec_map);
425 struct sec_bucket *sb = &sec_map[idx];
426 struct sec_softc *sc;
427
428 smr_read_enter();
429 SMR_SLIST_FOREACH(sc, sb, sc_entry) {
430 if (sc->sc_unit == unit) {
431 refcnt_take(&sc->sc_refs);
432 break;
433 }
434 }
435 smr_read_leave();
436
437 return (sc);
438 }
439
440 void
sec_input(struct sec_softc * sc,int af,int proto,struct mbuf * m)441 sec_input(struct sec_softc *sc, int af, int proto, struct mbuf *m)
442 {
443 struct ip *iph;
444 int hlen;
445
446 switch (af) {
447 case AF_INET:
448 iph = mtod(m, struct ip *);
449 hlen = iph->ip_hl << 2;
450 break;
451 #ifdef INET6
452 case AF_INET6:
453 hlen = sizeof(struct ip6_hdr);
454 break;
455 #endif
456 default:
457 unhandled_af(af);
458 }
459
460 m_adj(m, hlen);
461
462 switch (proto) {
463 case IPPROTO_IPV4:
464 af = AF_INET;
465 break;
466 case IPPROTO_IPV6:
467 af = AF_INET6;
468 break;
469 case IPPROTO_MPLS:
470 af = AF_MPLS;
471 break;
472 default:
473 af = AF_UNSPEC;
474 break;
475 }
476
477 m->m_pkthdr.ph_family = af;
478
479 if_vinput(&sc->sc_if, m);
480 }
481
482 void
sec_put(struct sec_softc * sc)483 sec_put(struct sec_softc *sc)
484 {
485 refcnt_rele_wake(&sc->sc_refs);
486 }
487
488 /*
489 * tdb handling
490 */
491
492 static int
sec_tdb_valid(struct tdb * tdb)493 sec_tdb_valid(struct tdb *tdb)
494 {
495 KASSERT(ISSET(tdb->tdb_flags, TDBF_IFACE));
496
497 if (!ISSET(tdb->tdb_flags, TDBF_TUNNELING))
498 return (0);
499 if (ISSET(tdb->tdb_flags, TDBF_INVALID))
500 return (0);
501
502 if (tdb->tdb_iface_dir != IPSP_DIRECTION_OUT)
503 return (0);
504
505 return (1);
506 }
507
508 /*
509 * these are called from netinet/ip_ipsp.c with tdb_sadb_mtx held,
510 * which we rely on to serialise modifications to the sec_tdbh.
511 */
512
513 void
sec_tdb_insert(struct tdb * tdb)514 sec_tdb_insert(struct tdb *tdb)
515 {
516 unsigned int idx;
517 struct tdb **tdbp;
518 struct tdb *ltdb;
519
520 if (!sec_tdb_valid(tdb))
521 return;
522
523 idx = stoeplitz_h32(tdb->tdb_iface) % nitems(sec_tdbh);
524 tdbp = &sec_tdbh[idx];
525
526 tdb_ref(tdb); /* take a ref for the SMR pointer */
527
528 /* wire the tdb into the head of the list */
529 ltdb = SMR_PTR_GET_LOCKED(tdbp);
530 SMR_PTR_SET_LOCKED(&tdb->tdb_dnext, ltdb);
531 SMR_PTR_SET_LOCKED(tdbp, tdb);
532 }
533
534 void
sec_tdb_remove(struct tdb * tdb)535 sec_tdb_remove(struct tdb *tdb)
536 {
537 struct tdb **tdbp;
538 struct tdb *ltdb;
539 unsigned int idx;
540
541 if (!sec_tdb_valid(tdb))
542 return;
543
544 idx = stoeplitz_h32(tdb->tdb_iface) % nitems(sec_tdbh);
545 tdbp = &sec_tdbh[idx];
546
547 while ((ltdb = SMR_PTR_GET_LOCKED(tdbp)) != NULL) {
548 if (ltdb == tdb) {
549 /* take the tdb out of the list */
550 ltdb = SMR_PTR_GET_LOCKED(&tdb->tdb_dnext);
551 SMR_PTR_SET_LOCKED(tdbp, ltdb);
552
553 /* move the ref to the gc */
554
555 mtx_enter(&sec_tdb_gc_mtx);
556 tdb->tdb_dnext = sec_tdb_gc_list;
557 sec_tdb_gc_list = tdb;
558 mtx_leave(&sec_tdb_gc_mtx);
559 task_add(systq, &sec_tdb_gc_task);
560
561 return;
562 }
563
564 tdbp = <db->tdb_dnext;
565 }
566
567 panic("%s: unable to find tdb %p", __func__, tdb);
568 }
569
570 static void
sec_tdb_gc(void * null)571 sec_tdb_gc(void *null)
572 {
573 struct tdb *tdb, *ntdb;
574
575 mtx_enter(&sec_tdb_gc_mtx);
576 tdb = sec_tdb_gc_list;
577 sec_tdb_gc_list = NULL;
578 mtx_leave(&sec_tdb_gc_mtx);
579
580 if (tdb == NULL)
581 return;
582
583 smr_barrier();
584
585 NET_LOCK();
586 do {
587 ntdb = tdb->tdb_dnext;
588 tdb_unref(tdb);
589 tdb = ntdb;
590 } while (tdb != NULL);
591 NET_UNLOCK();
592 }
593
594 struct tdb *
sec_tdb_get(unsigned int unit)595 sec_tdb_get(unsigned int unit)
596 {
597 unsigned int idx;
598 struct tdb **tdbp;
599 struct tdb *tdb;
600
601 idx = stoeplitz_h32(unit) % nitems(sec_map);
602 tdbp = &sec_tdbh[idx];
603
604 smr_read_enter();
605 while ((tdb = SMR_PTR_GET(tdbp)) != NULL) {
606 KASSERT(ISSET(tdb->tdb_flags, TDBF_IFACE));
607 if (!ISSET(tdb->tdb_flags, TDBF_DELETED) &&
608 tdb->tdb_iface == unit) {
609 tdb_ref(tdb);
610 break;
611 }
612
613 tdbp = &tdb->tdb_dnext;
614 }
615 smr_read_leave();
616
617 return (tdb);
618 }
619