1e9c7bebfSDarren Reed /* $FreeBSD$ */ 2134ea224SSam Leffler /* $NetBSD: pfil.c,v 1.20 2001/11/12 23:49:46 lukem Exp $ */ 3e9c7bebfSDarren Reed 4e9c7bebfSDarren Reed /* 5e9c7bebfSDarren Reed * Copyright (c) 1996 Matthew R. Green 6e9c7bebfSDarren Reed * All rights reserved. 7e9c7bebfSDarren Reed * 8e9c7bebfSDarren Reed * Redistribution and use in source and binary forms, with or without 9e9c7bebfSDarren Reed * modification, are permitted provided that the following conditions 10e9c7bebfSDarren Reed * are met: 11e9c7bebfSDarren Reed * 1. Redistributions of source code must retain the above copyright 12e9c7bebfSDarren Reed * notice, this list of conditions and the following disclaimer. 13e9c7bebfSDarren Reed * 2. Redistributions in binary form must reproduce the above copyright 14e9c7bebfSDarren Reed * notice, this list of conditions and the following disclaimer in the 15e9c7bebfSDarren Reed * documentation and/or other materials provided with the distribution. 16e9c7bebfSDarren Reed * 3. The name of the author may not be used to endorse or promote products 17e9c7bebfSDarren Reed * derived from this software without specific prior written permission. 18e9c7bebfSDarren Reed * 19e9c7bebfSDarren Reed * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20e9c7bebfSDarren Reed * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21e9c7bebfSDarren Reed * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22e9c7bebfSDarren Reed * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23e9c7bebfSDarren Reed * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24e9c7bebfSDarren Reed * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25e9c7bebfSDarren Reed * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26e9c7bebfSDarren Reed * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27e9c7bebfSDarren Reed * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28e9c7bebfSDarren Reed * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29e9c7bebfSDarren Reed * SUCH DAMAGE. 30e9c7bebfSDarren Reed */ 31e9c7bebfSDarren Reed 32e9c7bebfSDarren Reed #include <sys/param.h> 33134ea224SSam Leffler #include <sys/kernel.h> 34e9c7bebfSDarren Reed #include <sys/errno.h> 35e9c7bebfSDarren Reed #include <sys/malloc.h> 36e9c7bebfSDarren Reed #include <sys/socket.h> 37e9c7bebfSDarren Reed #include <sys/socketvar.h> 38e9c7bebfSDarren Reed #include <sys/systm.h> 39134ea224SSam Leffler #include <sys/condvar.h> 40134ea224SSam Leffler #include <sys/lock.h> 41134ea224SSam Leffler #include <sys/mutex.h> 42134ea224SSam Leffler #include <sys/proc.h> 43e9c7bebfSDarren Reed #include <sys/queue.h> 44e9c7bebfSDarren Reed 45e9c7bebfSDarren Reed #include <net/if.h> 46e9c7bebfSDarren Reed #include <net/pfil.h> 47e9c7bebfSDarren Reed 48134ea224SSam Leffler static struct mtx pfil_global_lock; 49e9c7bebfSDarren Reed 50134ea224SSam Leffler MTX_SYSINIT(pfil_heads_lock, &pfil_global_lock, "pfil_head_list lock", MTX_DEF); 51134ea224SSam Leffler 52134ea224SSam Leffler static int pfil_list_add(pfil_list_t *, struct packet_filter_hook *, int); 53134ea224SSam Leffler 54134ea224SSam Leffler static int pfil_list_remove(pfil_list_t *, 55134ea224SSam Leffler int (*)(void *, struct mbuf **, struct ifnet *, int), void *); 56134ea224SSam Leffler 57134ea224SSam Leffler LIST_HEAD(, pfil_head) pfil_head_list = 58134ea224SSam Leffler LIST_HEAD_INITIALIZER(&pfil_head_list); 59134ea224SSam Leffler 60134ea224SSam Leffler static __inline void 61134ea224SSam Leffler PFIL_RLOCK(struct pfil_head *ph) 62e9c7bebfSDarren Reed { 63134ea224SSam Leffler mtx_lock(&ph->ph_mtx); 64134ea224SSam Leffler ph->ph_busy_count++; 65134ea224SSam Leffler mtx_unlock(&ph->ph_mtx); 66134ea224SSam Leffler } 67134ea224SSam Leffler 68134ea224SSam Leffler static __inline void 69134ea224SSam Leffler PFIL_RUNLOCK(struct pfil_head *ph) 70134ea224SSam Leffler { 71134ea224SSam Leffler mtx_lock(&ph->ph_mtx); 72134ea224SSam Leffler ph->ph_busy_count--; 73134ea224SSam Leffler if (ph->ph_busy_count == 0 && ph->ph_want_write) 74134ea224SSam Leffler cv_signal(&ph->ph_cv); 75134ea224SSam Leffler mtx_unlock(&ph->ph_mtx); 76134ea224SSam Leffler } 77134ea224SSam Leffler 78134ea224SSam Leffler static __inline void 79134ea224SSam Leffler PFIL_WLOCK(struct pfil_head *ph) 80134ea224SSam Leffler { 81134ea224SSam Leffler mtx_lock(&ph->ph_mtx); 82134ea224SSam Leffler ph->ph_want_write = 1; 83134ea224SSam Leffler while (ph->ph_busy_count > 0) 84134ea224SSam Leffler cv_wait(&ph->ph_cv, &ph->ph_mtx); 85134ea224SSam Leffler } 86134ea224SSam Leffler 87134ea224SSam Leffler static __inline int 88134ea224SSam Leffler PFIL_TRY_WLOCK(struct pfil_head *ph) 89134ea224SSam Leffler { 90134ea224SSam Leffler mtx_lock(&ph->ph_mtx); 91134ea224SSam Leffler ph->ph_want_write = 1; 92134ea224SSam Leffler if (ph->ph_busy_count > 0) { 93134ea224SSam Leffler ph->ph_want_write = 0; 94134ea224SSam Leffler mtx_unlock(&ph->ph_mtx); 95134ea224SSam Leffler return EBUSY; 96134ea224SSam Leffler } 97134ea224SSam Leffler return 0; 98134ea224SSam Leffler } 99134ea224SSam Leffler 100134ea224SSam Leffler static __inline void 101134ea224SSam Leffler PFIL_WUNLOCK(struct pfil_head *ph) 102134ea224SSam Leffler { 103134ea224SSam Leffler ph->ph_want_write = 0; \ 104134ea224SSam Leffler mtx_unlock(&ph->ph_mtx); 105134ea224SSam Leffler cv_signal(&ph->ph_cv); 106134ea224SSam Leffler } 107134ea224SSam Leffler 108134ea224SSam Leffler #define PFIL_LIST_LOCK() mtx_lock(&pfil_global_lock) 109134ea224SSam Leffler #define PFIL_LIST_UNLOCK() mtx_unlock(&pfil_global_lock) 110134ea224SSam Leffler 111134ea224SSam Leffler /* 112134ea224SSam Leffler * pfil_run_hooks() runs the specified packet filter hooks. 113134ea224SSam Leffler */ 114134ea224SSam Leffler int 115134ea224SSam Leffler pfil_run_hooks(struct pfil_head *ph, struct mbuf **mp, struct ifnet *ifp, 116134ea224SSam Leffler int dir) 117134ea224SSam Leffler { 118134ea224SSam Leffler struct packet_filter_hook *pfh; 119134ea224SSam Leffler struct mbuf *m = *mp; 120134ea224SSam Leffler int rv = 0; 121134ea224SSam Leffler 122134ea224SSam Leffler if (ph->ph_busy_count == -1 || ph->ph_want_write) 123134ea224SSam Leffler return (0); 124134ea224SSam Leffler 125134ea224SSam Leffler PFIL_RLOCK(ph); 126134ea224SSam Leffler for (pfh = pfil_hook_get(dir, ph); pfh != NULL; 127134ea224SSam Leffler pfh = TAILQ_NEXT(pfh, pfil_link)) { 128134ea224SSam Leffler if (pfh->pfil_func != NULL) { 129134ea224SSam Leffler rv = (*pfh->pfil_func)(pfh->pfil_arg, &m, ifp, dir); 130134ea224SSam Leffler if (rv != 0 || m == NULL) 131134ea224SSam Leffler break; 132134ea224SSam Leffler } 133134ea224SSam Leffler } 134134ea224SSam Leffler PFIL_RUNLOCK(ph); 135134ea224SSam Leffler 136134ea224SSam Leffler *mp = m; 137134ea224SSam Leffler return (rv); 138134ea224SSam Leffler } 139134ea224SSam Leffler 140134ea224SSam Leffler /* 141134ea224SSam Leffler * pfil_head_register() registers a pfil_head with the packet filter 142134ea224SSam Leffler * hook mechanism. 143134ea224SSam Leffler */ 144134ea224SSam Leffler int 145134ea224SSam Leffler pfil_head_register(struct pfil_head *ph) 146134ea224SSam Leffler { 147134ea224SSam Leffler struct pfil_head *lph; 148134ea224SSam Leffler 149134ea224SSam Leffler PFIL_LIST_LOCK(); 150134ea224SSam Leffler LIST_FOREACH(lph, &pfil_head_list, ph_list) 151134ea224SSam Leffler if (ph->ph_type == lph->ph_type && 152134ea224SSam Leffler ph->ph_un.phu_val == lph->ph_un.phu_val) { 153134ea224SSam Leffler PFIL_LIST_UNLOCK(); 154134ea224SSam Leffler return EEXIST; 155134ea224SSam Leffler } 156134ea224SSam Leffler PFIL_LIST_UNLOCK(); 157134ea224SSam Leffler 158134ea224SSam Leffler if (mtx_initialized(&ph->ph_mtx)) { /* should not happen */ 159134ea224SSam Leffler KASSERT((0), ("%s: allready initialized!", __func__)); 160134ea224SSam Leffler return EBUSY; 161134ea224SSam Leffler } else { 162134ea224SSam Leffler ph->ph_busy_count = -1; 163134ea224SSam Leffler ph->ph_want_write = 1; 164134ea224SSam Leffler mtx_init(&ph->ph_mtx, "pfil_head_mtx", NULL, MTX_DEF); 165134ea224SSam Leffler cv_init(&ph->ph_cv, "pfil_head_cv"); 166134ea224SSam Leffler mtx_lock(&ph->ph_mtx); /* XXX: race? */ 167134ea224SSam Leffler } 168e9c7bebfSDarren Reed 169e9c7bebfSDarren Reed TAILQ_INIT(&ph->ph_in); 170e9c7bebfSDarren Reed TAILQ_INIT(&ph->ph_out); 171134ea224SSam Leffler 172134ea224SSam Leffler PFIL_LIST_LOCK(); 173134ea224SSam Leffler LIST_INSERT_HEAD(&pfil_head_list, ph, ph_list); 174134ea224SSam Leffler PFIL_LIST_UNLOCK(); 175134ea224SSam Leffler 176134ea224SSam Leffler PFIL_WUNLOCK(ph); 177134ea224SSam Leffler 178134ea224SSam Leffler return (0); 179134ea224SSam Leffler } 180134ea224SSam Leffler 181134ea224SSam Leffler /* 182134ea224SSam Leffler * pfil_head_unregister() removes a pfil_head from the packet filter 183134ea224SSam Leffler * hook mechanism. 184134ea224SSam Leffler */ 185134ea224SSam Leffler int 186134ea224SSam Leffler pfil_head_unregister(struct pfil_head *ph) 187134ea224SSam Leffler { 188134ea224SSam Leffler struct packet_filter_hook *pfh, *pfnext; 189134ea224SSam Leffler 190134ea224SSam Leffler PFIL_LIST_LOCK(); 191134ea224SSam Leffler /* 192134ea224SSam Leffler * LIST_REMOVE is safe for unlocked pfil_heads in ph_list. 193134ea224SSam Leffler * No need to WLOCK all of them. 194134ea224SSam Leffler */ 195134ea224SSam Leffler LIST_REMOVE(ph, ph_list); 196134ea224SSam Leffler PFIL_LIST_UNLOCK(); 197134ea224SSam Leffler 198134ea224SSam Leffler PFIL_WLOCK(ph); /* XXX: may sleep (cv_wait)! */ 199134ea224SSam Leffler 200134ea224SSam Leffler TAILQ_FOREACH_SAFE(pfh, &ph->ph_in, pfil_link, pfnext) 201134ea224SSam Leffler free(pfh, M_IFADDR); 202134ea224SSam Leffler TAILQ_FOREACH_SAFE(pfh, &ph->ph_out, pfil_link, pfnext) 203134ea224SSam Leffler free(pfh, M_IFADDR); 204134ea224SSam Leffler cv_destroy(&ph->ph_cv); 205134ea224SSam Leffler mtx_destroy(&ph->ph_mtx); 206134ea224SSam Leffler 207134ea224SSam Leffler return (0); 208134ea224SSam Leffler } 209134ea224SSam Leffler 210134ea224SSam Leffler /* 211134ea224SSam Leffler * pfil_head_get() returns the pfil_head for a given key/dlt. 212134ea224SSam Leffler */ 213134ea224SSam Leffler struct pfil_head * 214134ea224SSam Leffler pfil_head_get(int type, u_long val) 215134ea224SSam Leffler { 216134ea224SSam Leffler struct pfil_head *ph; 217134ea224SSam Leffler 218134ea224SSam Leffler PFIL_LIST_LOCK(); 219134ea224SSam Leffler LIST_FOREACH(ph, &pfil_head_list, ph_list) 220134ea224SSam Leffler if (ph->ph_type == type && ph->ph_un.phu_val == val) 221134ea224SSam Leffler break; 222134ea224SSam Leffler PFIL_LIST_UNLOCK(); 223134ea224SSam Leffler 224134ea224SSam Leffler return (ph); 225e9c7bebfSDarren Reed } 226e9c7bebfSDarren Reed 227e9c7bebfSDarren Reed /* 228e9c7bebfSDarren Reed * pfil_add_hook() adds a function to the packet filter hook. the 229e9c7bebfSDarren Reed * flags are: 230e9c7bebfSDarren Reed * PFIL_IN call me on incoming packets 231e9c7bebfSDarren Reed * PFIL_OUT call me on outgoing packets 232e9c7bebfSDarren Reed * PFIL_ALL call me on all of the above 233a163d034SWarner Losh * PFIL_WAITOK OK to call malloc with M_WAITOK. 234e9c7bebfSDarren Reed */ 235e9c7bebfSDarren Reed int 236134ea224SSam Leffler pfil_add_hook(int (*func)(void *, struct mbuf **, struct ifnet *, int), 237134ea224SSam Leffler void *arg, int flags, struct pfil_head *ph) 238e9c7bebfSDarren Reed { 239134ea224SSam Leffler struct packet_filter_hook *pfh1 = NULL; 240134ea224SSam Leffler struct packet_filter_hook *pfh2 = NULL; 241134ea224SSam Leffler int err; 242e9c7bebfSDarren Reed 243134ea224SSam Leffler /* Get memory */ 244134ea224SSam Leffler if (flags & PFIL_IN) { 245134ea224SSam Leffler pfh1 = (struct packet_filter_hook *)malloc(sizeof(*pfh1), 246134ea224SSam Leffler M_IFADDR, (flags & PFIL_WAITOK) ? M_WAITOK : M_NOWAIT); 247134ea224SSam Leffler if (pfh1 == NULL) { 248134ea224SSam Leffler err = ENOMEM; 249134ea224SSam Leffler goto error; 250134ea224SSam Leffler } 251134ea224SSam Leffler } 252134ea224SSam Leffler if (flags & PFIL_OUT) { 253134ea224SSam Leffler pfh2 = (struct packet_filter_hook *)malloc(sizeof(*pfh1), 254134ea224SSam Leffler M_IFADDR, (flags & PFIL_WAITOK) ? M_WAITOK : M_NOWAIT); 255134ea224SSam Leffler if (pfh2 == NULL) { 256134ea224SSam Leffler err = ENOMEM; 257134ea224SSam Leffler goto error; 258134ea224SSam Leffler } 259134ea224SSam Leffler } 260e9c7bebfSDarren Reed 261134ea224SSam Leffler /* Lock */ 262134ea224SSam Leffler if (flags & PFIL_WAITOK) 263134ea224SSam Leffler PFIL_WLOCK(ph); 264134ea224SSam Leffler else { 265134ea224SSam Leffler err = PFIL_TRY_WLOCK(ph); 266e9c7bebfSDarren Reed if (err) 267134ea224SSam Leffler goto error; 268134ea224SSam Leffler } 269134ea224SSam Leffler 270134ea224SSam Leffler /* Add */ 271134ea224SSam Leffler if (flags & PFIL_IN) { 272134ea224SSam Leffler pfh1->pfil_func = func; 273134ea224SSam Leffler pfh1->pfil_arg = arg; 274134ea224SSam Leffler err = pfil_list_add(&ph->ph_in, pfh1, flags & ~PFIL_OUT); 275134ea224SSam Leffler if (err) 276134ea224SSam Leffler goto done; 277134ea224SSam Leffler } 278134ea224SSam Leffler if (flags & PFIL_OUT) { 279134ea224SSam Leffler pfh2->pfil_func = func; 280134ea224SSam Leffler pfh2->pfil_arg = arg; 281134ea224SSam Leffler err = pfil_list_add(&ph->ph_out, pfh2, flags & ~PFIL_IN); 282e9c7bebfSDarren Reed if (err) { 283e9c7bebfSDarren Reed if (flags & PFIL_IN) 284134ea224SSam Leffler pfil_list_remove(&ph->ph_in, func, arg); 285134ea224SSam Leffler goto done; 286134ea224SSam Leffler } 287134ea224SSam Leffler } 288134ea224SSam Leffler 289134ea224SSam Leffler ph->ph_busy_count = 0; 290134ea224SSam Leffler PFIL_WUNLOCK(ph); 291134ea224SSam Leffler 292134ea224SSam Leffler return 0; 293134ea224SSam Leffler done: 294134ea224SSam Leffler PFIL_WUNLOCK(ph); 295134ea224SSam Leffler error: 296134ea224SSam Leffler if (pfh1 != NULL) 297134ea224SSam Leffler free(pfh1, M_IFADDR); 298134ea224SSam Leffler if (pfh2 != NULL) 299134ea224SSam Leffler free(pfh2, M_IFADDR); 300e9c7bebfSDarren Reed return err; 301e9c7bebfSDarren Reed } 302e9c7bebfSDarren Reed 303e9c7bebfSDarren Reed /* 304e9c7bebfSDarren Reed * pfil_remove_hook removes a specific function from the packet filter 305e9c7bebfSDarren Reed * hook list. 306e9c7bebfSDarren Reed */ 307e9c7bebfSDarren Reed int 308134ea224SSam Leffler pfil_remove_hook(int (*func)(void *, struct mbuf **, struct ifnet *, int), 309134ea224SSam Leffler void *arg, int flags, struct pfil_head *ph) 310e9c7bebfSDarren Reed { 311e9c7bebfSDarren Reed int err = 0; 312e9c7bebfSDarren Reed 313134ea224SSam Leffler if (flags & PFIL_WAITOK) 314134ea224SSam Leffler PFIL_WLOCK(ph); 315134ea224SSam Leffler else { 316134ea224SSam Leffler err = PFIL_TRY_WLOCK(ph); 317134ea224SSam Leffler if (err) 318134ea224SSam Leffler return err; 319134ea224SSam Leffler } 320e9c7bebfSDarren Reed 321e9c7bebfSDarren Reed if (flags & PFIL_IN) 322134ea224SSam Leffler err = pfil_list_remove(&ph->ph_in, func, arg); 323e9c7bebfSDarren Reed if ((err == 0) && (flags & PFIL_OUT)) 324134ea224SSam Leffler err = pfil_list_remove(&ph->ph_out, func, arg); 325134ea224SSam Leffler 326134ea224SSam Leffler if (TAILQ_EMPTY(&ph->ph_in) && TAILQ_EMPTY(&ph->ph_out)) 327134ea224SSam Leffler ph->ph_busy_count = -1; 328134ea224SSam Leffler 329134ea224SSam Leffler PFIL_WUNLOCK(ph); 330134ea224SSam Leffler 331e9c7bebfSDarren Reed return err; 332e9c7bebfSDarren Reed } 333e9c7bebfSDarren Reed 334134ea224SSam Leffler static int 335134ea224SSam Leffler pfil_list_add(pfil_list_t *list, struct packet_filter_hook *pfh1, int flags) 336134ea224SSam Leffler { 337134ea224SSam Leffler struct packet_filter_hook *pfh; 338134ea224SSam Leffler 339134ea224SSam Leffler /* 340134ea224SSam Leffler * First make sure the hook is not already there. 341134ea224SSam Leffler */ 342134ea224SSam Leffler TAILQ_FOREACH(pfh, list, pfil_link) 343134ea224SSam Leffler if (pfh->pfil_func == pfh1->pfil_func && 344134ea224SSam Leffler pfh->pfil_arg == pfh1->pfil_arg) 345134ea224SSam Leffler return EEXIST; 346134ea224SSam Leffler /* 347134ea224SSam Leffler * insert the input list in reverse order of the output list 348134ea224SSam Leffler * so that the same path is followed in or out of the kernel. 349134ea224SSam Leffler */ 350134ea224SSam Leffler if (flags & PFIL_IN) 351134ea224SSam Leffler TAILQ_INSERT_HEAD(list, pfh1, pfil_link); 352134ea224SSam Leffler else 353134ea224SSam Leffler TAILQ_INSERT_TAIL(list, pfh1, pfil_link); 354134ea224SSam Leffler 355134ea224SSam Leffler return 0; 356134ea224SSam Leffler } 357134ea224SSam Leffler 358e9c7bebfSDarren Reed /* 359e9c7bebfSDarren Reed * pfil_list_remove is an internal function that takes a function off the 360e9c7bebfSDarren Reed * specified list. 361e9c7bebfSDarren Reed */ 362e9c7bebfSDarren Reed static int 363134ea224SSam Leffler pfil_list_remove(pfil_list_t *list, 364134ea224SSam Leffler int (*func)(void *, struct mbuf **, struct ifnet *, int), void *arg) 365e9c7bebfSDarren Reed { 366e9c7bebfSDarren Reed struct packet_filter_hook *pfh; 367e9c7bebfSDarren Reed 368fc2ffbe6SPoul-Henning Kamp TAILQ_FOREACH(pfh, list, pfil_link) 369134ea224SSam Leffler if (pfh->pfil_func == func && pfh->pfil_arg == arg) { 370e9c7bebfSDarren Reed TAILQ_REMOVE(list, pfh, pfil_link); 371e9c7bebfSDarren Reed free(pfh, M_IFADDR); 372e9c7bebfSDarren Reed return 0; 373e9c7bebfSDarren Reed } 374e9c7bebfSDarren Reed return ENOENT; 375e9c7bebfSDarren Reed } 376