Home | History | Annotate | Line # | Download | only in net
      1 /*	$NetBSD: pfil.c,v 1.42 2022/08/16 04:35:57 knakahara Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2013 Mindaugas Rasiukevicius <rmind at NetBSD org>
      5  * Copyright (c) 1996 Matthew R. Green
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  * 1. Redistributions of source code must retain the above copyright
     12  *    notice, this list of conditions and the following disclaimer.
     13  * 2. Redistributions in binary form must reproduce the above copyright
     14  *    notice, this list of conditions and the following disclaimer in the
     15  *    documentation and/or other materials provided with the distribution.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
     22  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
     24  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     27  * SUCH DAMAGE.
     28  */
     29 
     30 #include <sys/cdefs.h>
     31 __KERNEL_RCSID(0, "$NetBSD: pfil.c,v 1.42 2022/08/16 04:35:57 knakahara Exp $");
     32 
     33 #if defined(_KERNEL_OPT)
     34 #include "opt_net_mpsafe.h"
     35 #endif
     36 
     37 #include <sys/param.h>
     38 #include <sys/systm.h>
     39 #include <sys/queue.h>
     40 #include <sys/kmem.h>
     41 #include <sys/psref.h>
     42 #include <sys/cpu.h>
     43 
     44 #include <net/if.h>
     45 #include <net/pfil.h>
     46 
     47 #define	MAX_HOOKS	8
     48 
     49 /* Func is either pfil_func_t or pfil_ifunc_t. */
     50 typedef void		(*pfil_polyfunc_t)(void);
     51 
     52 typedef struct {
     53 	pfil_polyfunc_t pfil_func;
     54 	void *		pfil_arg;
     55 } pfil_hook_t;
     56 
     57 typedef struct {
     58 	pfil_hook_t	hooks[MAX_HOOKS];
     59 	u_int		nhooks;
     60 	struct psref_target psref;
     61 } pfil_list_t;
     62 
     63 typedef struct {
     64 	pfil_list_t	*active;	/* lists[0] or lists[1] */
     65 	pfil_list_t	lists[2];
     66 } pfil_listset_t;
     67 
     68 CTASSERT(PFIL_IN == 1);
     69 CTASSERT(PFIL_OUT == 2);
     70 
     71 struct pfil_head {
     72 	pfil_listset_t	ph_in;
     73 	pfil_listset_t	ph_out;
     74 	pfil_listset_t	ph_ifaddr;
     75 	pfil_listset_t	ph_ifevent;
     76 	int		ph_type;
     77 	void *		ph_key;
     78 	LIST_ENTRY(pfil_head) ph_list;
     79 };
     80 
     81 static const int pfil_flag_cases[] = {
     82 	PFIL_IN, PFIL_OUT
     83 };
     84 
     85 static LIST_HEAD(, pfil_head) pfil_head_list __read_mostly =
     86     LIST_HEAD_INITIALIZER(&pfil_head_list);
     87 
     88 static kmutex_t pfil_mtx __cacheline_aligned;
     89 static struct psref_class *pfil_psref_class __read_mostly;
     90 #ifdef NET_MPSAFE
     91 static pserialize_t pfil_psz;
     92 #endif
     93 
     94 void
     95 pfil_init(void)
     96 {
     97 	mutex_init(&pfil_mtx, MUTEX_DEFAULT, IPL_NONE);
     98 #ifdef NET_MPSAFE
     99 	pfil_psz = pserialize_create();
    100 #endif
    101 	pfil_psref_class = psref_class_create("pfil", IPL_SOFTNET);
    102 }
    103 
    104 static inline void
    105 pfil_listset_init(pfil_listset_t *pflistset)
    106 {
    107 	pflistset->active = &pflistset->lists[0];
    108 	psref_target_init(&pflistset->active->psref, pfil_psref_class);
    109 }
    110 
    111 /*
    112  * pfil_head_create: create and register a packet filter head.
    113  */
    114 pfil_head_t *
    115 pfil_head_create(int type, void *key)
    116 {
    117 	pfil_head_t *ph;
    118 
    119 	if (pfil_head_get(type, key)) {
    120 		return NULL;
    121 	}
    122 	ph = kmem_zalloc(sizeof(pfil_head_t), KM_SLEEP);
    123 	ph->ph_type = type;
    124 	ph->ph_key = key;
    125 
    126 	pfil_listset_init(&ph->ph_in);
    127 	pfil_listset_init(&ph->ph_out);
    128 	pfil_listset_init(&ph->ph_ifaddr);
    129 	pfil_listset_init(&ph->ph_ifevent);
    130 
    131 	LIST_INSERT_HEAD(&pfil_head_list, ph, ph_list);
    132 	return ph;
    133 }
    134 
    135 /*
    136  * pfil_head_destroy: remove and destroy a packet filter head.
    137  */
    138 void
    139 pfil_head_destroy(pfil_head_t *pfh)
    140 {
    141 	LIST_REMOVE(pfh, ph_list);
    142 
    143 	psref_target_destroy(&pfh->ph_in.active->psref, pfil_psref_class);
    144 	psref_target_destroy(&pfh->ph_out.active->psref, pfil_psref_class);
    145 	psref_target_destroy(&pfh->ph_ifaddr.active->psref, pfil_psref_class);
    146 	psref_target_destroy(&pfh->ph_ifevent.active->psref, pfil_psref_class);
    147 
    148 	kmem_free(pfh, sizeof(pfil_head_t));
    149 }
    150 
    151 /*
    152  * pfil_head_get: returns the packer filter head for a given key.
    153  */
    154 pfil_head_t *
    155 pfil_head_get(int type, void *key)
    156 {
    157 	pfil_head_t *ph;
    158 
    159 	LIST_FOREACH(ph, &pfil_head_list, ph_list) {
    160 		if (ph->ph_type == type && ph->ph_key == key)
    161 			break;
    162 	}
    163 	return ph;
    164 }
    165 
    166 static pfil_listset_t *
    167 pfil_hook_get(int dir, pfil_head_t *ph)
    168 {
    169 	switch (dir) {
    170 	case PFIL_IN:
    171 		return &ph->ph_in;
    172 	case PFIL_OUT:
    173 		return &ph->ph_out;
    174 	case PFIL_IFADDR:
    175 		return &ph->ph_ifaddr;
    176 	case PFIL_IFNET:
    177 		return &ph->ph_ifevent;
    178 	}
    179 	return NULL;
    180 }
    181 
    182 static int
    183 pfil_list_add(pfil_listset_t *phlistset, pfil_polyfunc_t func, void *arg,
    184               int flags)
    185 {
    186 	u_int nhooks;
    187 	pfil_list_t *newlist, *oldlist;
    188 	pfil_hook_t *pfh;
    189 
    190 	mutex_enter(&pfil_mtx);
    191 
    192 	/* Check if we have a free slot. */
    193 	nhooks = phlistset->active->nhooks;
    194 	if (nhooks == MAX_HOOKS) {
    195 		mutex_exit(&pfil_mtx);
    196 		return ENOSPC;
    197 	}
    198 	KASSERT(nhooks < MAX_HOOKS);
    199 
    200 	if (phlistset->active == &phlistset->lists[0]) {
    201 		oldlist = &phlistset->lists[0];
    202 		newlist = &phlistset->lists[1];
    203 	} else{
    204 		oldlist = &phlistset->lists[1];
    205 		newlist = &phlistset->lists[0];
    206 	}
    207 
    208 	/* Make sure the hook is not already added. */
    209 	for (u_int i = 0; i < nhooks; i++) {
    210 		pfh = &oldlist->hooks[i];
    211 		if (pfh->pfil_func == func && pfh->pfil_arg == arg) {
    212 			mutex_exit(&pfil_mtx);
    213 			return EEXIST;
    214 		}
    215 	}
    216 
    217 	/* create new pfil_list_t copied from old */
    218 	memcpy(newlist, oldlist, sizeof(pfil_list_t));
    219 	psref_target_init(&newlist->psref, pfil_psref_class);
    220 
    221 	/*
    222 	 * Finally, add the hook.  Note: for PFIL_IN we insert the hooks in
    223 	 * reverse order of the PFIL_OUT so that the same path is followed
    224 	 * in or out of the kernel.
    225 	 */
    226 	if (flags & PFIL_IN) {
    227 		/* XXX: May want to revisit this later; */
    228 		size_t len = sizeof(pfil_hook_t) * nhooks;
    229 		pfh = &newlist->hooks[0];
    230 		memmove(&newlist->hooks[1], pfh, len);
    231 	} else {
    232 		pfh = &newlist->hooks[nhooks];
    233 	}
    234 	newlist->nhooks++;
    235 
    236 	pfh->pfil_func = func;
    237 	pfh->pfil_arg  = arg;
    238 
    239 	/* switch from oldlist to newlist */
    240 	atomic_store_release(&phlistset->active, newlist);
    241 #ifdef NET_MPSAFE
    242 	pserialize_perform(pfil_psz);
    243 #endif
    244 	mutex_exit(&pfil_mtx);
    245 
    246 	/* Wait for all readers */
    247 #ifdef NET_MPSAFE
    248 	psref_target_destroy(&oldlist->psref, pfil_psref_class);
    249 #endif
    250 
    251 	return 0;
    252 }
    253 
    254 /*
    255  * pfil_add_hook: add a function (hook) to the packet filter head.
    256  * The possible flags are:
    257  *
    258  *	PFIL_IN		call on incoming packets
    259  *	PFIL_OUT	call on outgoing packets
    260  *	PFIL_ALL	call on all of the above
    261  */
    262 int
    263 pfil_add_hook(pfil_func_t func, void *arg, int flags, pfil_head_t *ph)
    264 {
    265 	int error = 0;
    266 
    267 	KASSERT(func != NULL);
    268 	KASSERT((flags & ~PFIL_ALL) == 0);
    269 
    270 	ASSERT_SLEEPABLE();
    271 
    272 	for (u_int i = 0; i < __arraycount(pfil_flag_cases); i++) {
    273 		const int fcase = pfil_flag_cases[i];
    274 		pfil_listset_t *phlistset;
    275 
    276 		if ((flags & fcase) == 0) {
    277 			continue;
    278 		}
    279 		phlistset = pfil_hook_get(fcase, ph);
    280 		error = pfil_list_add(phlistset, (pfil_polyfunc_t)func, arg,
    281 		    flags);
    282 		if (error && (error != EEXIST))
    283 			break;
    284 	}
    285 	if (error && (error != EEXIST)) {
    286 		pfil_remove_hook(func, arg, flags, ph);
    287 	}
    288 	return error;
    289 }
    290 
    291 /*
    292  * pfil_add_ihook: add an interface-event function (hook) to the packet
    293  * filter head.  The possible flags are:
    294  *
    295  *	PFIL_IFADDR	call on interface reconfig (cmd is ioctl #)
    296  *	PFIL_IFNET	call on interface attach/detach (cmd is PFIL_IFNET_*)
    297  */
    298 int
    299 pfil_add_ihook(pfil_ifunc_t func, void *arg, int flags, pfil_head_t *ph)
    300 {
    301 	pfil_listset_t *phlistset;
    302 
    303 	KASSERT(func != NULL);
    304 	KASSERT(flags == PFIL_IFADDR || flags == PFIL_IFNET);
    305 
    306 	ASSERT_SLEEPABLE();
    307 
    308 	phlistset = pfil_hook_get(flags, ph);
    309 	return pfil_list_add(phlistset, (pfil_polyfunc_t)func, arg, flags);
    310 }
    311 
    312 /*
    313  * pfil_list_remove: remove the hook from a specified list.
    314  */
    315 static int
    316 pfil_list_remove(pfil_listset_t *phlistset, pfil_polyfunc_t func, void *arg)
    317 {
    318 	u_int nhooks;
    319 	pfil_list_t *oldlist, *newlist;
    320 
    321 	mutex_enter(&pfil_mtx);
    322 
    323 	/* create new pfil_list_t copied from old */
    324 	if (phlistset->active == &phlistset->lists[0]) {
    325 		oldlist = &phlistset->lists[0];
    326 		newlist = &phlistset->lists[1];
    327 	} else{
    328 		oldlist = &phlistset->lists[1];
    329 		newlist = &phlistset->lists[0];
    330 	}
    331 	memcpy(newlist, oldlist, sizeof(*newlist));
    332 	psref_target_init(&newlist->psref, pfil_psref_class);
    333 
    334 	nhooks = newlist->nhooks;
    335 	for (u_int i = 0; i < nhooks; i++) {
    336 		pfil_hook_t *last, *pfh = &newlist->hooks[i];
    337 
    338 		if (pfh->pfil_func != func || pfh->pfil_arg != arg) {
    339 			continue;
    340 		}
    341 		if ((last = &newlist->hooks[nhooks - 1]) != pfh) {
    342 			memcpy(pfh, last, sizeof(pfil_hook_t));
    343 		}
    344 		newlist->nhooks--;
    345 
    346 		/* switch from oldlist to newlist */
    347 		atomic_store_release(&phlistset->active, newlist);
    348 #ifdef NET_MPSAFE
    349 		pserialize_perform(pfil_psz);
    350 #endif
    351 		mutex_exit(&pfil_mtx);
    352 
    353 		/* Wait for all readers */
    354 #ifdef NET_MPSAFE
    355 		psref_target_destroy(&oldlist->psref, pfil_psref_class);
    356 #endif
    357 
    358 		return 0;
    359 	}
    360 	mutex_exit(&pfil_mtx);
    361 	return ENOENT;
    362 }
    363 
    364 /*
    365  * pfil_remove_hook: remove the hook from the packet filter head.
    366  */
    367 int
    368 pfil_remove_hook(pfil_func_t func, void *arg, int flags, pfil_head_t *ph)
    369 {
    370 	KASSERT((flags & ~PFIL_ALL) == 0);
    371 
    372 	ASSERT_SLEEPABLE();
    373 
    374 	for (u_int i = 0; i < __arraycount(pfil_flag_cases); i++) {
    375 		const int fcase = pfil_flag_cases[i];
    376 		pfil_listset_t *pflistset;
    377 
    378 		if ((flags & fcase) == 0) {
    379 			continue;
    380 		}
    381 		pflistset = pfil_hook_get(fcase, ph);
    382 		(void)pfil_list_remove(pflistset, (pfil_polyfunc_t)func, arg);
    383 	}
    384 	return 0;
    385 }
    386 
    387 int
    388 pfil_remove_ihook(pfil_ifunc_t func, void *arg, int flags, pfil_head_t *ph)
    389 {
    390 	pfil_listset_t *pflistset;
    391 
    392 	KASSERT(flags == PFIL_IFADDR || flags == PFIL_IFNET);
    393 
    394 	ASSERT_SLEEPABLE();
    395 
    396 	pflistset = pfil_hook_get(flags, ph);
    397 	(void)pfil_list_remove(pflistset, (pfil_polyfunc_t)func, arg);
    398 	return 0;
    399 }
    400 
    401 /*
    402  * pfil_run_hooks: run the specified packet filter hooks.
    403  */
    404 int
    405 pfil_run_hooks(pfil_head_t *ph, struct mbuf **mp, ifnet_t *ifp, int dir)
    406 {
    407 	struct mbuf *m = mp ? *mp : NULL;
    408 	pfil_listset_t *phlistset;
    409 	pfil_list_t *phlist;
    410 	struct psref psref;
    411 	int s, bound;
    412 	int ret = 0;
    413 
    414 	KASSERT(dir == PFIL_IN || dir == PFIL_OUT);
    415 	KASSERT(!cpu_intr_p());
    416 
    417 	if (ph == NULL) {
    418 		return ret;
    419 	}
    420 
    421 	if (__predict_false((phlistset = pfil_hook_get(dir, ph)) == NULL)) {
    422 		return ret;
    423 	}
    424 
    425 	bound = curlwp_bind();
    426 	s = pserialize_read_enter();
    427 	phlist = atomic_load_consume(&phlistset->active);
    428 	if (phlist->nhooks == 0) {
    429 		pserialize_read_exit(s);
    430 		curlwp_bindx(bound);
    431 		return ret;
    432 	}
    433 	psref_acquire(&psref, &phlist->psref, pfil_psref_class);
    434 	pserialize_read_exit(s);
    435 	for (u_int i = 0; i < phlist->nhooks; i++) {
    436 		pfil_hook_t *pfh = &phlist->hooks[i];
    437 		pfil_func_t func = (pfil_func_t)pfh->pfil_func;
    438 
    439 		ret = (*func)(pfh->pfil_arg, &m, ifp, dir);
    440 		if (m == NULL || ret)
    441 			break;
    442 	}
    443 	psref_release(&psref, &phlist->psref, pfil_psref_class);
    444 	curlwp_bindx(bound);
    445 
    446 	if (mp) {
    447 		*mp = m;
    448 	}
    449 	return ret;
    450 }
    451 
    452 static void
    453 pfil_run_arg(pfil_listset_t *phlistset, u_long cmd, void *arg)
    454 {
    455 	pfil_list_t *phlist;
    456 	struct psref psref;
    457 	int s, bound;
    458 
    459 	KASSERT(!cpu_intr_p());
    460 
    461 	bound = curlwp_bind();
    462 	s = pserialize_read_enter();
    463 	phlist = atomic_load_consume(&phlistset->active);
    464 	psref_acquire(&psref, &phlist->psref, pfil_psref_class);
    465 	pserialize_read_exit(s);
    466 	for (u_int i = 0; i < phlist->nhooks; i++) {
    467 		pfil_hook_t *pfh = &phlist->hooks[i];
    468 		pfil_ifunc_t func = (pfil_ifunc_t)pfh->pfil_func;
    469 		(*func)(pfh->pfil_arg, cmd, arg);
    470 	}
    471 	psref_release(&psref, &phlist->psref, pfil_psref_class);
    472 	curlwp_bindx(bound);
    473 }
    474 
    475 void
    476 pfil_run_addrhooks(pfil_head_t *ph, u_long cmd, struct ifaddr *ifa)
    477 {
    478 	pfil_run_arg(&ph->ph_ifaddr, cmd, ifa);
    479 }
    480 
    481 void
    482 pfil_run_ifhooks(pfil_head_t *ph, u_long cmd, struct ifnet *ifp)
    483 {
    484 	pfil_run_arg(&ph->ph_ifevent, cmd, ifp);
    485 }
    486