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