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. */ 49typedef void (*pfil_polyfunc_t)(void); 50 51typedef struct { 52 pfil_polyfunc_t pfil_func; 53 void * pfil_arg; 54} pfil_hook_t; 55 56typedef struct { 57 pfil_hook_t hooks[MAX_HOOKS]; 58 u_int nhooks; 59 struct psref_target psref; 60} pfil_list_t; 61 62typedef struct { 63 pfil_list_t *active; /* lists[0] or lists[1] */ 64 pfil_list_t lists[2]; 65} pfil_listset_t; 66 67CTASSERT(PFIL_IN == 1); 68CTASSERT(PFIL_OUT == 2); 69 70struct 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 80static const int pfil_flag_cases[] = { 81 PFIL_IN, PFIL_OUT 82}; 83 84static LIST_HEAD(, pfil_head) pfil_head_list __read_mostly = 85 LIST_HEAD_INITIALIZER(&pfil_head_list); 86 87static kmutex_t pfil_mtx __cacheline_aligned; 88static struct psref_class *pfil_psref_class __read_mostly; 89#ifdef NET_MPSAFE 90static pserialize_t pfil_psz; 91#endif 92 93void 94pfil_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 103static inline void 104pfil_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 */ 113pfil_head_t * 114pfil_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 */ 137void 138pfil_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 */ 153pfil_head_t * 154pfil_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 165static pfil_listset_t * 166pfil_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 181static int 182pfil_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 */ 261int 262pfil_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 */ 295int 296pfil_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 */ 310static int 311pfil_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 */ 362int 363pfil_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 380int 381pfil_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 */ 394int 395pfil_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 436static void 437pfil_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 457void 458pfil_run_addrhooks(pfil_head_t *ph, u_long cmd, struct ifaddr *ifa) 459{ 460 pfil_run_arg(&ph->ph_ifaddr, cmd, ifa); 461} 462 463void 464pfil_run_ifhooks(pfil_head_t *ph, u_long cmd, struct ifnet *ifp) 465{ 466 pfil_run_arg(&ph->ph_ifevent, cmd, ifp); 467} 468