1 /* $NetBSD: pfil.c,v 1.20 2001/11/12 23:49:46 lukem Exp $ */ 2 /* $DragonFly: src/sys/net/pfil.c,v 1.14 2008/09/20 06:08:13 sephe Exp $ */ 3 4 /* 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 * 3. The name of the author may not be used to endorse or promote products 17 * derived from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/param.h> 33 #include <sys/errno.h> 34 #include <sys/malloc.h> 35 #include <sys/socket.h> 36 #include <sys/socketvar.h> 37 #include <sys/systm.h> 38 #include <sys/proc.h> 39 #include <sys/queue.h> 40 41 #include <net/if.h> 42 #include <net/pfil.h> 43 #include <net/netmsg2.h> 44 45 #define PFIL_CFGPORT cpu_portfn(0) 46 47 #define PFIL_GETMPLOCK(pfh) \ 48 do { \ 49 if (((pfh)->pfil_flags & PFIL_MPSAFE) == 0) \ 50 get_mplock(); \ 51 } while (0) 52 53 #define PFIL_RELMPLOCK(pfh) \ 54 do { \ 55 if (((pfh)->pfil_flags & PFIL_MPSAFE) == 0) \ 56 rel_mplock(); \ 57 } while (0) 58 59 /* 60 * The packet filter hooks are designed for anything to call them to 61 * possibly intercept the packet. 62 */ 63 struct packet_filter_hook { 64 TAILQ_ENTRY(packet_filter_hook) pfil_link; 65 pfil_func_t pfil_func; 66 void *pfil_arg; 67 int pfil_flags; 68 }; 69 70 struct netmsg_pfil { 71 struct netmsg pfil_nmsg; 72 pfil_func_t pfil_func; 73 void *pfil_arg; 74 int pfil_flags; 75 struct pfil_head *pfil_ph; 76 }; 77 78 static LIST_HEAD(, pfil_head) pfil_head_list = 79 LIST_HEAD_INITIALIZER(&pfil_head_list); 80 81 static pfil_list_t *pfil_list_alloc(void); 82 static void pfil_list_free(pfil_list_t *); 83 static void pfil_list_dup(const pfil_list_t *, pfil_list_t *, 84 const struct packet_filter_hook *); 85 static void pfil_list_add(pfil_list_t *, pfil_func_t, void *, int); 86 static struct packet_filter_hook * 87 pfil_list_find(const pfil_list_t *, pfil_func_t, 88 const void *); 89 90 static void pfil_remove_hook_dispatch(struct netmsg *); 91 static void pfil_add_hook_dispatch(struct netmsg *); 92 93 /* 94 * pfil_run_hooks() runs the specified packet filter hooks. 95 */ 96 int 97 pfil_run_hooks(struct pfil_head *ph, struct mbuf **mp, struct ifnet *ifp, 98 int dir) 99 { 100 struct packet_filter_hook *pfh; 101 struct mbuf *m = *mp; 102 pfil_list_t *list; 103 int rv = 0; 104 105 if (dir == PFIL_IN) 106 list = ph->ph_in; 107 else if (dir == PFIL_OUT) 108 list = ph->ph_out; 109 else 110 return 0; /* XXX panic? */ 111 112 TAILQ_FOREACH(pfh, list, pfil_link) { 113 if (pfh->pfil_func != NULL) { 114 PFIL_GETMPLOCK(pfh); 115 rv = pfh->pfil_func(pfh->pfil_arg, &m, ifp, dir); 116 PFIL_RELMPLOCK(pfh); 117 118 if (rv != 0 || m == NULL) 119 break; 120 } 121 } 122 123 *mp = m; 124 return (rv); 125 } 126 127 /* 128 * pfil_head_register() registers a pfil_head with the packet filter 129 * hook mechanism. 130 */ 131 int 132 pfil_head_register(struct pfil_head *ph) 133 { 134 struct pfil_head *lph; 135 136 LIST_FOREACH(lph, &pfil_head_list, ph_list) { 137 if (ph->ph_type == lph->ph_type && 138 ph->ph_un.phu_val == lph->ph_un.phu_val) 139 return EEXIST; 140 } 141 142 ph->ph_in = pfil_list_alloc(); 143 ph->ph_out = pfil_list_alloc(); 144 ph->ph_hashooks = 0; 145 146 LIST_INSERT_HEAD(&pfil_head_list, ph, ph_list); 147 148 return (0); 149 } 150 151 /* 152 * pfil_head_unregister() removes a pfil_head from the packet filter 153 * hook mechanism. 154 */ 155 int 156 pfil_head_unregister(struct pfil_head *pfh) 157 { 158 LIST_REMOVE(pfh, ph_list); 159 return (0); 160 } 161 162 /* 163 * pfil_head_get() returns the pfil_head for a given key/dlt. 164 */ 165 struct pfil_head * 166 pfil_head_get(int type, u_long val) 167 { 168 struct pfil_head *ph; 169 170 LIST_FOREACH(ph, &pfil_head_list, ph_list) { 171 if (ph->ph_type == type && ph->ph_un.phu_val == val) 172 break; 173 } 174 return (ph); 175 } 176 177 static void 178 pfil_add_hook_dispatch(struct netmsg *nmsg) 179 { 180 struct netmsg_pfil *pfilmsg = (struct netmsg_pfil *)nmsg; 181 pfil_func_t func = pfilmsg->pfil_func; 182 void *arg = pfilmsg->pfil_arg; 183 int flags = pfilmsg->pfil_flags; 184 struct pfil_head *ph = pfilmsg->pfil_ph; 185 const struct packet_filter_hook *pfh; 186 pfil_list_t *list_in = NULL, *list_out = NULL; 187 pfil_list_t *old_list_in = NULL, *old_list_out = NULL; 188 int err = 0; 189 190 /* This probably should not happen ... */ 191 if ((flags & (PFIL_IN | PFIL_OUT)) == 0) 192 goto reply; /* XXX panic? */ 193 194 /* 195 * If pfil hooks exist on any of the requested lists, 196 * then bail out. 197 */ 198 if (flags & PFIL_IN) { 199 pfh = pfil_list_find(ph->ph_in, func, arg); 200 if (pfh != NULL) { 201 err = EEXIST; 202 goto reply; 203 } 204 } 205 if (flags & PFIL_OUT) { 206 pfh = pfil_list_find(ph->ph_out, func, arg); 207 if (pfh != NULL) { 208 err = EEXIST; 209 goto reply; 210 } 211 } 212 213 /* 214 * Duplicate the requested lists, install new hooks 215 * on the duplication 216 */ 217 if (flags & PFIL_IN) { 218 list_in = pfil_list_alloc(); 219 pfil_list_dup(ph->ph_in, list_in, NULL); 220 pfil_list_add(list_in, func, arg, flags & ~PFIL_OUT); 221 } 222 if (flags & PFIL_OUT) { 223 list_out = pfil_list_alloc(); 224 pfil_list_dup(ph->ph_out, list_out, NULL); 225 pfil_list_add(list_out, func, arg, flags & ~PFIL_IN); 226 } 227 228 /* 229 * Switch list pointers, but keep the old ones 230 */ 231 if (list_in != NULL) { 232 old_list_in = ph->ph_in; 233 ph->ph_in = list_in; 234 } 235 if (list_out != NULL) { 236 old_list_out = ph->ph_out; 237 ph->ph_out = list_out; 238 } 239 240 /* 241 * Wait until everyone has finished the old lists iteration 242 */ 243 netmsg_service_sync(); 244 ph->ph_hashooks = 1; 245 246 /* 247 * Now it is safe to free the old lists, since no one sees it 248 */ 249 if (old_list_in != NULL) 250 pfil_list_free(old_list_in); 251 if (old_list_out != NULL) 252 pfil_list_free(old_list_out); 253 reply: 254 lwkt_replymsg(&nmsg->nm_lmsg, err); 255 } 256 257 /* 258 * pfil_add_hook() adds a function to the packet filter hook. the 259 * flags are: 260 * PFIL_IN call me on incoming packets 261 * PFIL_OUT call me on outgoing packets 262 * PFIL_ALL call me on all of the above 263 * PFIL_MPSAFE call me without BGL 264 */ 265 int 266 pfil_add_hook(pfil_func_t func, void *arg, int flags, struct pfil_head *ph) 267 { 268 struct netmsg_pfil pfilmsg; 269 struct netmsg *nmsg; 270 271 nmsg = &pfilmsg.pfil_nmsg; 272 netmsg_init(nmsg, &curthread->td_msgport, 0, pfil_add_hook_dispatch); 273 pfilmsg.pfil_func = func; 274 pfilmsg.pfil_arg = arg; 275 pfilmsg.pfil_flags = flags; 276 pfilmsg.pfil_ph = ph; 277 278 return lwkt_domsg(PFIL_CFGPORT, &nmsg->nm_lmsg, 0); 279 } 280 281 static void 282 pfil_remove_hook_dispatch(struct netmsg *nmsg) 283 { 284 struct netmsg_pfil *pfilmsg = (struct netmsg_pfil *)nmsg; 285 pfil_func_t func = pfilmsg->pfil_func; 286 void *arg = pfilmsg->pfil_arg; 287 int flags = pfilmsg->pfil_flags; 288 struct pfil_head *ph = pfilmsg->pfil_ph; 289 struct packet_filter_hook *skip_in = NULL, *skip_out = NULL; 290 pfil_list_t *list_in = NULL, *list_out = NULL; 291 pfil_list_t *old_list_in = NULL, *old_list_out = NULL; 292 int err = 0; 293 294 /* This probably should not happen ... */ 295 if ((flags & (PFIL_IN | PFIL_OUT)) == 0) 296 goto reply; /* XXX panic? */ 297 298 /* 299 * The pfil hook should exist on all requested lists, 300 * if not just bail out 301 */ 302 if (flags & PFIL_IN) { 303 skip_in = pfil_list_find(ph->ph_in, func, arg); 304 if (!skip_in) { 305 err = ENOENT; 306 goto reply; 307 } 308 } 309 if (flags & PFIL_OUT) { 310 skip_out = pfil_list_find(ph->ph_out, func, arg); 311 if (!skip_out) { 312 err = ENOENT; 313 goto reply; 314 } 315 } 316 317 /* 318 * Duplicate the requested lists, but the pfil hook to 319 * be deleted is not copied 320 */ 321 if (flags & PFIL_IN) { 322 KKASSERT(skip_in != NULL); 323 list_in = pfil_list_alloc(); 324 pfil_list_dup(ph->ph_in, list_in, skip_in); 325 } 326 if (flags & PFIL_OUT) { 327 KKASSERT(skip_out != NULL); 328 list_out = pfil_list_alloc(); 329 pfil_list_dup(ph->ph_out, list_out, skip_out); 330 } 331 332 /* 333 * Switch list pointers, but keep the old ones 334 */ 335 if (list_in != NULL) { 336 old_list_in = ph->ph_in; 337 ph->ph_in = list_in; 338 } 339 if (list_out != NULL) { 340 old_list_out = ph->ph_out; 341 ph->ph_out = list_out; 342 } 343 344 /* 345 * Wait until everyone has finished the old lists iteration 346 */ 347 if (TAILQ_EMPTY(ph->ph_in) && TAILQ_EMPTY(ph->ph_out)) 348 ph->ph_hashooks = 0; 349 netmsg_service_sync(); 350 351 /* 352 * Now it is safe to free the old lists, since no one sees it 353 */ 354 if (old_list_in != NULL) 355 pfil_list_free(old_list_in); 356 if (old_list_out != NULL) 357 pfil_list_free(old_list_out); 358 reply: 359 lwkt_replymsg(&nmsg->nm_lmsg, err); 360 } 361 362 /* 363 * pfil_remove_hook removes a specific function from the packet filter 364 * hook list. 365 */ 366 int 367 pfil_remove_hook(pfil_func_t func, void *arg, int flags, struct pfil_head *ph) 368 { 369 struct netmsg_pfil pfilmsg; 370 struct netmsg *nmsg; 371 372 nmsg = &pfilmsg.pfil_nmsg; 373 netmsg_init(nmsg, &curthread->td_msgport, 0, pfil_remove_hook_dispatch); 374 pfilmsg.pfil_func = func; 375 pfilmsg.pfil_arg = arg; 376 pfilmsg.pfil_flags = flags; 377 pfilmsg.pfil_ph = ph; 378 379 return lwkt_domsg(PFIL_CFGPORT, &nmsg->nm_lmsg, 0); 380 } 381 382 static void 383 pfil_list_add(pfil_list_t *list, pfil_func_t func, void *arg, int flags) 384 { 385 struct packet_filter_hook *pfh; 386 387 KKASSERT(&curthread->td_msgport == PFIL_CFGPORT); 388 389 pfh = kmalloc(sizeof(*pfh), M_IFADDR, M_WAITOK); 390 391 pfh->pfil_func = func; 392 pfh->pfil_arg = arg; 393 pfh->pfil_flags = flags; 394 395 /* 396 * Insert the input list in reverse order of the output list 397 * so that the same path is followed in or out of the kernel. 398 */ 399 if (flags & PFIL_IN) 400 TAILQ_INSERT_HEAD(list, pfh, pfil_link); 401 else 402 TAILQ_INSERT_TAIL(list, pfh, pfil_link); 403 } 404 405 static void 406 pfil_list_dup(const pfil_list_t *from, pfil_list_t *to, 407 const struct packet_filter_hook *skip) 408 { 409 struct packet_filter_hook *pfh_to, *pfh_from; 410 411 KKASSERT(&curthread->td_msgport == PFIL_CFGPORT); 412 KKASSERT(TAILQ_EMPTY(to)); 413 414 TAILQ_FOREACH(pfh_from, from, pfil_link) { 415 if (pfh_from == skip) 416 continue; 417 418 pfh_to = kmalloc(sizeof(*pfh_to), M_IFADDR, M_WAITOK); 419 bcopy(pfh_from, pfh_to, sizeof(*pfh_to)); 420 421 TAILQ_INSERT_TAIL(to, pfh_to, pfil_link); 422 } 423 } 424 425 static pfil_list_t * 426 pfil_list_alloc(void) 427 { 428 pfil_list_t *list; 429 430 list = kmalloc(sizeof(*list), M_IFADDR, M_WAITOK); 431 TAILQ_INIT(list); 432 return list; 433 } 434 435 static void 436 pfil_list_free(pfil_list_t *list) 437 { 438 struct packet_filter_hook *pfh; 439 440 while ((pfh = TAILQ_FIRST(list)) != NULL) { 441 TAILQ_REMOVE(list, pfh, pfil_link); 442 kfree(pfh, M_IFADDR); 443 } 444 kfree(list, M_IFADDR); 445 } 446 447 static struct packet_filter_hook * 448 pfil_list_find(const pfil_list_t *list, pfil_func_t func, const void *arg) 449 { 450 struct packet_filter_hook *pfh; 451 452 KKASSERT(&curthread->td_msgport == PFIL_CFGPORT); 453 454 TAILQ_FOREACH(pfh, list, pfil_link) { 455 if (pfh->pfil_func == func && pfh->pfil_arg == arg) 456 return pfh; 457 } 458 return NULL; 459 } 460