Home | History | Annotate | Line # | Download | only in net
pfil.c revision 1.39
      1 /*	$NetBSD: pfil.c,v 1.39 2020/06/22 16:39:56 maxv 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.39 2020/06/22 16:39:56 maxv 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 
     43 #include <net/if.h>
     44 #include <net/pfil.h>
     45 
     46 #define	MAX_HOOKS	8
     47 
     48 /* Func is either pfil_func_t or pfil_ifunc_t. */
     49 typedef void		(*pfil_polyfunc_t)(void);
     50 
     51 typedef struct {
     52 	pfil_polyfunc_t pfil_func;
     53 	void *		pfil_arg;
     54 } pfil_hook_t;
     55 
     56 typedef struct {
     57 	pfil_hook_t	hooks[MAX_HOOKS];
     58 	u_int		nhooks;
     59 	struct psref_target psref;
     60 } pfil_list_t;
     61 
     62 typedef struct {
     63 	pfil_list_t	*active;	/* lists[0] or lists[1] */
     64 	pfil_list_t	lists[2];
     65 } pfil_listset_t;
     66 
     67 CTASSERT(PFIL_IN == 1);
     68 CTASSERT(PFIL_OUT == 2);
     69 
     70 struct pfil_head {
     71 	pfil_listset_t	ph_in;
     72 	pfil_listset_t	ph_out;
     73 	pfil_listset_t	ph_ifaddr;
     74 	pfil_listset_t	ph_ifevent;
     75 	int		ph_type;
     76 	void *		ph_key;
     77 	LIST_ENTRY(pfil_head) ph_list;
     78 };
     79 
     80 static const int pfil_flag_cases[] = {
     81 	PFIL_IN, PFIL_OUT
     82 };
     83 
     84 static LIST_HEAD(, pfil_head) pfil_head_list __read_mostly =
     85     LIST_HEAD_INITIALIZER(&pfil_head_list);
     86 
     87 static kmutex_t pfil_mtx __cacheline_aligned;
     88 static struct psref_class *pfil_psref_class __read_mostly;
     89 #ifdef NET_MPSAFE
     90 static pserialize_t pfil_psz;
     91 #endif
     92 
     93 void
     94 pfil_init(void)
     95 {
     96 	mutex_init(&pfil_mtx, MUTEX_DEFAULT, IPL_NONE);
     97 #ifdef NET_MPSAFE
     98 	pfil_psz = pserialize_create();
     99 #endif
    100 	pfil_psref_class = psref_class_create("pfil", IPL_SOFTNET);
    101 }
    102 
    103 static inline void
    104 pfil_listset_init(pfil_listset_t *pflistset)
    105 {
    106 	pflistset->active = &pflistset->lists[0];
    107 	psref_target_init(&pflistset->active->psref, pfil_psref_class);
    108 }
    109 
    110 /*
    111  * pfil_head_create: create and register a packet filter head.
    112  */
    113 pfil_head_t *
    114 pfil_head_create(int type, void *key)
    115 {
    116 	pfil_head_t *ph;
    117 
    118 	if (pfil_head_get(type, key)) {
    119 		return NULL;
    120 	}
    121 	ph = kmem_zalloc(sizeof(pfil_head_t), KM_SLEEP);
    122 	ph->ph_type = type;
    123 	ph->ph_key = key;
    124 
    125 	pfil_listset_init(&ph->ph_in);
    126 	pfil_listset_init(&ph->ph_out);
    127 	pfil_listset_init(&ph->ph_ifaddr);
    128 	pfil_listset_init(&ph->ph_ifevent);
    129 
    130 	LIST_INSERT_HEAD(&pfil_head_list, ph, ph_list);
    131 	return ph;
    132 }
    133 
    134 /*
    135  * pfil_head_destroy: remove and destroy a packet filter head.
    136  */
    137 void
    138 pfil_head_destroy(pfil_head_t *pfh)
    139 {
    140 	LIST_REMOVE(pfh, ph_list);
    141 
    142 	psref_target_destroy(&pfh->ph_in.active->psref, pfil_psref_class);
    143 	psref_target_destroy(&pfh->ph_out.active->psref, pfil_psref_class);
    144 	psref_target_destroy(&pfh->ph_ifaddr.active->psref, pfil_psref_class);
    145 	psref_target_destroy(&pfh->ph_ifevent.active->psref, pfil_psref_class);
    146 
    147 	kmem_free(pfh, sizeof(pfil_head_t));
    148 }
    149 
    150 /*
    151  * pfil_head_get: returns the packer filter head for a given key.
    152  */
    153 pfil_head_t *
    154 pfil_head_get(int type, void *key)
    155 {
    156 	pfil_head_t *ph;
    157 
    158 	LIST_FOREACH(ph, &pfil_head_list, ph_list) {
    159 		if (ph->ph_type == type && ph->ph_key == key)
    160 			break;
    161 	}
    162 	return ph;
    163 }
    164 
    165 static pfil_listset_t *
    166 pfil_hook_get(int dir, pfil_head_t *ph)
    167 {
    168 	switch (dir) {
    169 	case PFIL_IN:
    170 		return &ph->ph_in;
    171 	case PFIL_OUT:
    172 		return &ph->ph_out;
    173 	case PFIL_IFADDR:
    174 		return &ph->ph_ifaddr;
    175 	case PFIL_IFNET:
    176 		return &ph->ph_ifevent;
    177 	}
    178 	return NULL;
    179 }
    180 
    181 static int
    182 pfil_list_add(pfil_listset_t *phlistset, pfil_polyfunc_t func, void *arg,
    183               int flags)
    184 {
    185 	u_int nhooks;
    186 	pfil_list_t *newlist, *oldlist;
    187 	pfil_hook_t *pfh;
    188 
    189 	mutex_enter(&pfil_mtx);
    190 
    191 	/* Check if we have a free slot. */
    192 	nhooks = phlistset->active->nhooks;
    193 	if (nhooks == MAX_HOOKS) {
    194 		mutex_exit(&pfil_mtx);
    195 		return ENOSPC;
    196 	}
    197 	KASSERT(nhooks < MAX_HOOKS);
    198 
    199 	if (phlistset->active == &phlistset->lists[0]) {
    200 		oldlist = &phlistset->lists[0];
    201 		newlist = &phlistset->lists[1];
    202 	} else{
    203 		oldlist = &phlistset->lists[1];
    204 		newlist = &phlistset->lists[0];
    205 	}
    206 
    207 	/* Make sure the hook is not already added. */
    208 	for (u_int i = 0; i < nhooks; i++) {
    209 		pfh = &oldlist->hooks[i];
    210 		if (pfh->pfil_func == func && pfh->pfil_arg == arg) {
    211 			mutex_exit(&pfil_mtx);
    212 			return EEXIST;
    213 		}
    214 	}
    215 
    216 	/* create new pfil_list_t copied from old */
    217 	memcpy(newlist, oldlist, sizeof(pfil_list_t));
    218 	psref_target_init(&newlist->psref, pfil_psref_class);
    219 
    220 	/*
    221 	 * Finally, add the hook.  Note: for PFIL_IN we insert the hooks in
    222 	 * reverse order of the PFIL_OUT so that the same path is followed
    223 	 * in or out of the kernel.
    224 	 */
    225 	if (flags & PFIL_IN) {
    226 		/* XXX: May want to revisit this later; */
    227 		size_t len = sizeof(pfil_hook_t) * nhooks;
    228 		pfh = &newlist->hooks[0];
    229 		memmove(&newlist->hooks[1], pfh, len);
    230 	} else {
    231 		pfh = &newlist->hooks[nhooks];
    232 	}
    233 	newlist->nhooks++;
    234 
    235 	pfh->pfil_func = func;
    236 	pfh->pfil_arg  = arg;
    237 
    238 	/* switch from oldlist to newlist */
    239 	atomic_store_release(&phlistset->active, newlist);
    240 #ifdef NET_MPSAFE
    241 	pserialize_perform(pfil_psz);
    242 #endif
    243 	mutex_exit(&pfil_mtx);
    244 
    245 	/* Wait for all readers */
    246 #ifdef NET_MPSAFE
    247 	psref_target_destroy(&oldlist->psref, pfil_psref_class);
    248 #endif
    249 
    250 	return 0;
    251 }
    252 
    253 /*
    254  * pfil_add_hook: add a function (hook) to the packet filter head.
    255  * The possible flags are:
    256  *
    257  *	PFIL_IN		call on incoming packets
    258  *	PFIL_OUT	call on outgoing packets
    259  *	PFIL_ALL	call on all of the above
    260  */
    261 int
    262 pfil_add_hook(pfil_func_t func, void *arg, int flags, pfil_head_t *ph)
    263 {
    264 	int error = 0;
    265 
    266 	KASSERT(func != NULL);
    267 	KASSERT((flags & ~PFIL_ALL) == 0);
    268 
    269 	for (u_int i = 0; i < __arraycount(pfil_flag_cases); i++) {
    270 		const int fcase = pfil_flag_cases[i];
    271 		pfil_listset_t *phlistset;
    272 
    273 		if ((flags & fcase) == 0) {
    274 			continue;
    275 		}
    276 		phlistset = pfil_hook_get(fcase, ph);
    277 		error = pfil_list_add(phlistset, (pfil_polyfunc_t)func, arg,
    278 		    flags);
    279 		if (error && (error != EEXIST))
    280 			break;
    281 	}
    282 	if (error && (error != EEXIST)) {
    283 		pfil_remove_hook(func, arg, flags, ph);
    284 	}
    285 	return error;
    286 }
    287 
    288 /*
    289  * pfil_add_ihook: add an interface-event function (hook) to the packet
    290  * filter head.  The possible flags are:
    291  *
    292  *	PFIL_IFADDR	call on interface reconfig (cmd is ioctl #)
    293  *	PFIL_IFNET	call on interface attach/detach (cmd is PFIL_IFNET_*)
    294  */
    295 int
    296 pfil_add_ihook(pfil_ifunc_t func, void *arg, int flags, pfil_head_t *ph)
    297 {
    298 	pfil_listset_t *phlistset;
    299 
    300 	KASSERT(func != NULL);
    301 	KASSERT(flags == PFIL_IFADDR || flags == PFIL_IFNET);
    302 
    303 	phlistset = pfil_hook_get(flags, ph);
    304 	return pfil_list_add(phlistset, (pfil_polyfunc_t)func, arg, flags);
    305 }
    306 
    307 /*
    308  * pfil_list_remove: remove the hook from a specified list.
    309  */
    310 static int
    311 pfil_list_remove(pfil_listset_t *phlistset, pfil_polyfunc_t func, void *arg)
    312 {
    313 	u_int nhooks;
    314 	pfil_list_t *oldlist, *newlist;
    315 
    316 	mutex_enter(&pfil_mtx);
    317 
    318 	/* create new pfil_list_t copied from old */
    319 	if (phlistset->active == &phlistset->lists[0]) {
    320 		oldlist = &phlistset->lists[0];
    321 		newlist = &phlistset->lists[1];
    322 	} else{
    323 		oldlist = &phlistset->lists[1];
    324 		newlist = &phlistset->lists[0];
    325 	}
    326 	memcpy(newlist, oldlist, sizeof(*newlist));
    327 	psref_target_init(&newlist->psref, pfil_psref_class);
    328 
    329 	nhooks = newlist->nhooks;
    330 	for (u_int i = 0; i < nhooks; i++) {
    331 		pfil_hook_t *last, *pfh = &newlist->hooks[i];
    332 
    333 		if (pfh->pfil_func != func || pfh->pfil_arg != arg) {
    334 			continue;
    335 		}
    336 		if ((last = &newlist->hooks[nhooks - 1]) != pfh) {
    337 			memcpy(pfh, last, sizeof(pfil_hook_t));
    338 		}
    339 		newlist->nhooks--;
    340 
    341 		/* switch from oldlist to newlist */
    342 		atomic_store_release(&phlistset->active, newlist);
    343 #ifdef NET_MPSAFE
    344 		pserialize_perform(pfil_psz);
    345 #endif
    346 		mutex_exit(&pfil_mtx);
    347 
    348 		/* Wait for all readers */
    349 #ifdef NET_MPSAFE
    350 		psref_target_destroy(&oldlist->psref, pfil_psref_class);
    351 #endif
    352 
    353 		return 0;
    354 	}
    355 	mutex_exit(&pfil_mtx);
    356 	return ENOENT;
    357 }
    358 
    359 /*
    360  * pfil_remove_hook: remove the hook from the packet filter head.
    361  */
    362 int
    363 pfil_remove_hook(pfil_func_t func, void *arg, int flags, pfil_head_t *ph)
    364 {
    365 	KASSERT((flags & ~PFIL_ALL) == 0);
    366 
    367 	for (u_int i = 0; i < __arraycount(pfil_flag_cases); i++) {
    368 		const int fcase = pfil_flag_cases[i];
    369 		pfil_listset_t *pflistset;
    370 
    371 		if ((flags & fcase) == 0) {
    372 			continue;
    373 		}
    374 		pflistset = pfil_hook_get(fcase, ph);
    375 		(void)pfil_list_remove(pflistset, (pfil_polyfunc_t)func, arg);
    376 	}
    377 	return 0;
    378 }
    379 
    380 int
    381 pfil_remove_ihook(pfil_ifunc_t func, void *arg, int flags, pfil_head_t *ph)
    382 {
    383 	pfil_listset_t *pflistset;
    384 
    385 	KASSERT(flags == PFIL_IFADDR || flags == PFIL_IFNET);
    386 	pflistset = pfil_hook_get(flags, ph);
    387 	(void)pfil_list_remove(pflistset, (pfil_polyfunc_t)func, arg);
    388 	return 0;
    389 }
    390 
    391 /*
    392  * pfil_run_hooks: run the specified packet filter hooks.
    393  */
    394 int
    395 pfil_run_hooks(pfil_head_t *ph, struct mbuf **mp, ifnet_t *ifp, int dir)
    396 {
    397 	struct mbuf *m = mp ? *mp : NULL;
    398 	pfil_listset_t *phlistset;
    399 	pfil_list_t *phlist;
    400 	struct psref psref;
    401 	int s, bound;
    402 	int ret = 0;
    403 
    404 	KASSERT(dir == PFIL_IN || dir == PFIL_OUT);
    405 
    406 	if (ph == NULL) {
    407 		return ret;
    408 	}
    409 
    410 	if (__predict_false((phlistset = pfil_hook_get(dir, ph)) == NULL)) {
    411 		return ret;
    412 	}
    413 
    414 	bound = curlwp_bind();
    415 	s = pserialize_read_enter();
    416 	phlist = atomic_load_consume(&phlistset->active);
    417 	psref_acquire(&psref, &phlist->psref, pfil_psref_class);
    418 	pserialize_read_exit(s);
    419 	for (u_int i = 0; i < phlist->nhooks; i++) {
    420 		pfil_hook_t *pfh = &phlist->hooks[i];
    421 		pfil_func_t func = (pfil_func_t)pfh->pfil_func;
    422 
    423 		ret = (*func)(pfh->pfil_arg, &m, ifp, dir);
    424 		if (m == NULL || ret)
    425 			break;
    426 	}
    427 	psref_release(&psref, &phlist->psref, pfil_psref_class);
    428 	curlwp_bindx(bound);
    429 
    430 	if (mp) {
    431 		*mp = m;
    432 	}
    433 	return ret;
    434 }
    435 
    436 static void
    437 pfil_run_arg(pfil_listset_t *phlistset, u_long cmd, void *arg)
    438 {
    439 	pfil_list_t *phlist;
    440 	struct psref psref;
    441 	int s, bound;
    442 
    443 	bound = curlwp_bind();
    444 	s = pserialize_read_enter();
    445 	phlist = atomic_load_consume(&phlistset->active);
    446 	psref_acquire(&psref, &phlist->psref, pfil_psref_class);
    447 	pserialize_read_exit(s);
    448 	for (u_int i = 0; i < phlist->nhooks; i++) {
    449 		pfil_hook_t *pfh = &phlist->hooks[i];
    450 		pfil_ifunc_t func = (pfil_ifunc_t)pfh->pfil_func;
    451 		(*func)(pfh->pfil_arg, cmd, arg);
    452 	}
    453 	psref_release(&psref, &phlist->psref, pfil_psref_class);
    454 	curlwp_bindx(bound);
    455 }
    456 
    457 void
    458 pfil_run_addrhooks(pfil_head_t *ph, u_long cmd, struct ifaddr *ifa)
    459 {
    460 	pfil_run_arg(&ph->ph_ifaddr, cmd, ifa);
    461 }
    462 
    463 void
    464 pfil_run_ifhooks(pfil_head_t *ph, u_long cmd, struct ifnet *ifp)
    465 {
    466 	pfil_run_arg(&ph->ph_ifevent, cmd, ifp);
    467 }
    468