1 /* $NetBSD: pfil.c,v 1.34 2017/01/23 02:32:54 ozaki-r 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.34 2017/01/23 02:32:54 ozaki-r 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 phlistset->active = newlist; 236 membar_producer(); 237 #ifdef NET_MPSAFE 238 pserialize_perform(pfil_psz); 239 #endif 240 mutex_exit(&pfil_mtx); 241 242 /* Wait for all readers */ 243 #ifdef NET_MPSAFE 244 psref_target_destroy(&oldlist->psref, pfil_psref_class); 245 #endif 246 247 return 0; 248 } 249 250 /* 251 * pfil_add_hook: add a function (hook) to the packet filter head. 252 * The possible flags are: 253 * 254 * PFIL_IN call on incoming packets 255 * PFIL_OUT call on outgoing packets 256 * PFIL_ALL call on all of the above 257 */ 258 int 259 pfil_add_hook(pfil_func_t func, void *arg, int flags, pfil_head_t *ph) 260 { 261 int error = 0; 262 263 KASSERT(func != NULL); 264 KASSERT((flags & ~PFIL_ALL) == 0); 265 266 for (u_int i = 0; i < __arraycount(pfil_flag_cases); i++) { 267 const int fcase = pfil_flag_cases[i]; 268 pfil_listset_t *phlistset; 269 270 if ((flags & fcase) == 0) { 271 continue; 272 } 273 phlistset = pfil_hook_get(fcase, ph); 274 error = pfil_list_add(phlistset, (pfil_polyfunc_t)func, arg, 275 flags); 276 if (error && (error != EEXIST)) 277 break; 278 } 279 if (error && (error != EEXIST)) { 280 pfil_remove_hook(func, arg, flags, ph); 281 } 282 return error; 283 } 284 285 /* 286 * pfil_add_ihook: add an interface-event function (hook) to the packet 287 * filter head. The possible flags are: 288 * 289 * PFIL_IFADDR call on interface reconfig (cmd is ioctl #) 290 * PFIL_IFNET call on interface attach/detach (cmd is PFIL_IFNET_*) 291 */ 292 int 293 pfil_add_ihook(pfil_ifunc_t func, void *arg, int flags, pfil_head_t *ph) 294 { 295 pfil_listset_t *phlistset; 296 297 KASSERT(func != NULL); 298 KASSERT(flags == PFIL_IFADDR || flags == PFIL_IFNET); 299 300 phlistset = pfil_hook_get(flags, ph); 301 return pfil_list_add(phlistset, (pfil_polyfunc_t)func, arg, flags); 302 } 303 304 /* 305 * pfil_list_remove: remove the hook from a specified list. 306 */ 307 static int 308 pfil_list_remove(pfil_listset_t *phlistset, pfil_polyfunc_t func, void *arg) 309 { 310 u_int nhooks; 311 pfil_list_t *oldlist, *newlist; 312 313 mutex_enter(&pfil_mtx); 314 315 /* create new pfil_list_t copied from old */ 316 if (phlistset->active == &phlistset->lists[0]) { 317 oldlist = &phlistset->lists[0]; 318 newlist = &phlistset->lists[1]; 319 } else{ 320 oldlist = &phlistset->lists[1]; 321 newlist = &phlistset->lists[0]; 322 } 323 memcpy(newlist, oldlist, sizeof(*newlist)); 324 psref_target_init(&newlist->psref, pfil_psref_class); 325 326 nhooks = newlist->nhooks; 327 for (u_int i = 0; i < nhooks; i++) { 328 pfil_hook_t *last, *pfh = &newlist->hooks[i]; 329 330 if (pfh->pfil_func != func || pfh->pfil_arg != arg) { 331 continue; 332 } 333 if ((last = &newlist->hooks[nhooks - 1]) != pfh) { 334 memcpy(pfh, last, sizeof(pfil_hook_t)); 335 } 336 newlist->nhooks--; 337 338 /* switch from oldlist to newlist */ 339 phlistset->active = newlist; 340 membar_producer(); 341 #ifdef NET_MPSAFE 342 pserialize_perform(pfil_psz); 343 #endif 344 mutex_exit(&pfil_mtx); 345 346 /* Wait for all readers */ 347 #ifdef NET_MPSAFE 348 psref_target_destroy(&oldlist->psref, pfil_psref_class); 349 #endif 350 351 return 0; 352 } 353 mutex_exit(&pfil_mtx); 354 return ENOENT; 355 } 356 357 /* 358 * pfil_remove_hook: remove the hook from the packet filter head. 359 */ 360 int 361 pfil_remove_hook(pfil_func_t func, void *arg, int flags, pfil_head_t *ph) 362 { 363 KASSERT((flags & ~PFIL_ALL) == 0); 364 365 for (u_int i = 0; i < __arraycount(pfil_flag_cases); i++) { 366 const int fcase = pfil_flag_cases[i]; 367 pfil_listset_t *pflistset; 368 369 if ((flags & fcase) == 0) { 370 continue; 371 } 372 pflistset = pfil_hook_get(fcase, ph); 373 (void)pfil_list_remove(pflistset, (pfil_polyfunc_t)func, arg); 374 } 375 return 0; 376 } 377 378 int 379 pfil_remove_ihook(pfil_ifunc_t func, void *arg, int flags, pfil_head_t *ph) 380 { 381 pfil_listset_t *pflistset; 382 383 KASSERT(flags == PFIL_IFADDR || flags == PFIL_IFNET); 384 pflistset = pfil_hook_get(flags, ph); 385 (void)pfil_list_remove(pflistset, (pfil_polyfunc_t)func, arg); 386 return 0; 387 } 388 389 /* 390 * pfil_run_hooks: run the specified packet filter hooks. 391 */ 392 int 393 pfil_run_hooks(pfil_head_t *ph, struct mbuf **mp, ifnet_t *ifp, int dir) 394 { 395 struct mbuf *m = mp ? *mp : NULL; 396 pfil_listset_t *phlistset; 397 pfil_list_t *phlist; 398 struct psref psref; 399 int s, bound; 400 int ret = 0; 401 402 KASSERT(dir == PFIL_IN || dir == PFIL_OUT); 403 if (__predict_false((phlistset = pfil_hook_get(dir, ph)) == NULL)) { 404 return ret; 405 } 406 407 bound = curlwp_bind(); 408 s = pserialize_read_enter(); 409 phlist = phlistset->active; 410 membar_datadep_consumer(); 411 psref_acquire(&psref, &phlist->psref, pfil_psref_class); 412 pserialize_read_exit(s); 413 for (u_int i = 0; i < phlist->nhooks; i++) { 414 pfil_hook_t *pfh = &phlist->hooks[i]; 415 pfil_func_t func = (pfil_func_t)pfh->pfil_func; 416 417 ret = (*func)(pfh->pfil_arg, &m, ifp, dir); 418 if (m == NULL || ret) 419 break; 420 } 421 psref_release(&psref, &phlist->psref, pfil_psref_class); 422 curlwp_bindx(bound); 423 424 if (mp) { 425 *mp = m; 426 } 427 return ret; 428 } 429 430 static void 431 pfil_run_arg(pfil_listset_t *phlistset, u_long cmd, void *arg) 432 { 433 pfil_list_t *phlist; 434 struct psref psref; 435 int s, bound; 436 437 bound = curlwp_bind(); 438 s = pserialize_read_enter(); 439 phlist = phlistset->active; 440 membar_datadep_consumer(); 441 psref_acquire(&psref, &phlist->psref, pfil_psref_class); 442 pserialize_read_exit(s); 443 for (u_int i = 0; i < phlist->nhooks; i++) { 444 pfil_hook_t *pfh = &phlist->hooks[i]; 445 pfil_ifunc_t func = (pfil_ifunc_t)pfh->pfil_func; 446 (*func)(pfh->pfil_arg, cmd, arg); 447 } 448 psref_release(&psref, &phlist->psref, pfil_psref_class); 449 curlwp_bindx(bound); 450 } 451 452 void 453 pfil_run_addrhooks(pfil_head_t *ph, u_long cmd, struct ifaddr *ifa) 454 { 455 pfil_run_arg(&ph->ph_ifaddr, cmd, ifa); 456 } 457 458 void 459 pfil_run_ifhooks(pfil_head_t *ph, u_long cmd, struct ifnet *ifp) 460 { 461 pfil_run_arg(&ph->ph_ifevent, cmd, ifp); 462 } 463