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. */ 50typedef void (*pfil_polyfunc_t)(void); 51 52typedef struct { 53 pfil_polyfunc_t pfil_func; 54 void * pfil_arg; 55} pfil_hook_t; 56 57typedef struct { 58 pfil_hook_t hooks[MAX_HOOKS]; 59 u_int nhooks; 60 struct psref_target psref; 61} pfil_list_t; 62 63typedef struct { 64 pfil_list_t *active; /* lists[0] or lists[1] */ 65 pfil_list_t lists[2]; 66} pfil_listset_t; 67 68CTASSERT(PFIL_IN == 1); 69CTASSERT(PFIL_OUT == 2); 70 71struct 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 81static const int pfil_flag_cases[] = { 82 PFIL_IN, PFIL_OUT 83}; 84 85static LIST_HEAD(, pfil_head) pfil_head_list __read_mostly = 86 LIST_HEAD_INITIALIZER(&pfil_head_list); 87 88static kmutex_t pfil_mtx __cacheline_aligned; 89static struct psref_class *pfil_psref_class __read_mostly; 90#ifdef NET_MPSAFE 91static pserialize_t pfil_psz; 92#endif 93 94void 95pfil_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 104static inline void 105pfil_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 */ 114pfil_head_t * 115pfil_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 */ 138void 139pfil_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 */ 154pfil_head_t * 155pfil_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 166static pfil_listset_t * 167pfil_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 182static int 183pfil_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 */ 262int 263pfil_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 */ 298int 299pfil_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 */ 315static int 316pfil_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 */ 367int 368pfil_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 387int 388pfil_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 */ 404int 405pfil_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 452static void 453pfil_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 475void 476pfil_run_addrhooks(pfil_head_t *ph, u_long cmd, struct ifaddr *ifa) 477{ 478 pfil_run_arg(&ph->ph_ifaddr, cmd, ifa); 479} 480 481void 482pfil_run_ifhooks(pfil_head_t *ph, u_long cmd, struct ifnet *ifp) 483{ 484 pfil_run_arg(&ph->ph_ifevent, cmd, ifp); 485} 486