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