/* * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2002 Ericsson Research & Pekka Nikander * Copyright (c) 2020 Nick Hibma * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * MACFILTER NETGRAPH NODE TYPE * * This node type routes packets from the ether hook to either the default hook * if sender MAC address is not in the MAC table, or out over the specified * hook if it is. * * Other node types can then be used to apply specific processing to the * packets on each hook. * * If compiled with NG_MACFILTER_DEBUG the flow and resizing of the MAC table * are logged to the console. * * If compiled with NG_MACFILTER_DEBUG_RECVDATA every packet handled is logged * on the console. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ng_macfilter.h" #ifdef NG_SEPARATE_MALLOC MALLOC_DEFINE(M_NETGRAPH_MACFILTER, "netgraph_macfilter", "netgraph macfilter node "); #else #define M_NETGRAPH_MACFILTER M_NETGRAPH #endif #define MACTABLE_BLOCKSIZE 128 /* block size for incrementing table */ #ifdef NG_MACFILTER_DEBUG #define MACFILTER_DEBUG(fmt, ...) printf("%s:%d: " fmt "\n", __FUNCTION__, __LINE__, __VA_ARGS__) #else #define MACFILTER_DEBUG(fmt, ...) #endif #define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" #define MAC_S_ARGS(v) (v)[0], (v)[1], (v)[2], (v)[3], (v)[4], (v)[5] /* * Parse type for struct ngm_macfilter_direct */ static const struct ng_parse_struct_field macfilter_direct_fields[] = NGM_MACFILTER_DIRECT_FIELDS; static const struct ng_parse_type ng_macfilter_direct_type = { &ng_parse_struct_type, &macfilter_direct_fields }; /* * Parse type for struct ngm_macfilter_direct_hookid. */ static const struct ng_parse_struct_field macfilter_direct_ndx_fields[] = NGM_MACFILTER_DIRECT_NDX_FIELDS; static const struct ng_parse_type ng_macfilter_direct_hookid_type = { &ng_parse_struct_type, &macfilter_direct_ndx_fields }; /* * Parse types for struct ngm_macfilter_get_macs. */ static int macfilter_get_macs_count(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ngm_macfilter_macs *const ngm_macs = (const struct ngm_macfilter_macs *)(buf - OFFSETOF(struct ngm_macfilter_macs, macs)); return ngm_macs->n; } static const struct ng_parse_struct_field ng_macfilter_mac_fields[] = NGM_MACFILTER_MAC_FIELDS; static const struct ng_parse_type ng_macfilter_mac_type = { &ng_parse_struct_type, ng_macfilter_mac_fields, }; static const struct ng_parse_array_info ng_macfilter_macs_array_info = { &ng_macfilter_mac_type, macfilter_get_macs_count }; static const struct ng_parse_type ng_macfilter_macs_array_type = { &ng_parse_array_type, &ng_macfilter_macs_array_info }; static const struct ng_parse_struct_field ng_macfilter_macs_fields[] = NGM_MACFILTER_MACS_FIELDS; static const struct ng_parse_type ng_macfilter_macs_type = { &ng_parse_struct_type, &ng_macfilter_macs_fields }; /* * Parse types for struct ngm_macfilter_get_hooks. */ static int macfilter_get_upper_hook_count(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ngm_macfilter_hooks *const ngm_hooks = (const struct ngm_macfilter_hooks *)(buf - OFFSETOF(struct ngm_macfilter_hooks, hooks)); MACFILTER_DEBUG("buf %p, ngm_hooks %p, n %d", buf, ngm_hooks, ngm_hooks->n); return ngm_hooks->n; } static const struct ng_parse_struct_field ng_macfilter_hook_fields[] = NGM_MACFILTER_HOOK_FIELDS; static const struct ng_parse_type ng_macfilter_hook_type = { &ng_parse_struct_type, ng_macfilter_hook_fields, }; static const struct ng_parse_array_info ng_macfilter_hooks_array_info = { &ng_macfilter_hook_type, macfilter_get_upper_hook_count }; static const struct ng_parse_type ng_macfilter_hooks_array_type = { &ng_parse_array_type, &ng_macfilter_hooks_array_info }; static const struct ng_parse_struct_field ng_macfilter_hooks_fields[] = NGM_MACFILTER_HOOKS_FIELDS; static const struct ng_parse_type ng_macfilter_hooks_type = { &ng_parse_struct_type, &ng_macfilter_hooks_fields }; /* * List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_macfilter_cmdlist[] = { { NGM_MACFILTER_COOKIE, NGM_MACFILTER_RESET, "reset", NULL, NULL }, { NGM_MACFILTER_COOKIE, NGM_MACFILTER_DIRECT, "direct", &ng_macfilter_direct_type, NULL }, { NGM_MACFILTER_COOKIE, NGM_MACFILTER_DIRECT_HOOKID, "directi", &ng_macfilter_direct_hookid_type, NULL }, { NGM_MACFILTER_COOKIE, NGM_MACFILTER_GET_MACS, "getmacs", NULL, &ng_macfilter_macs_type }, { NGM_MACFILTER_COOKIE, NGM_MACFILTER_GETCLR_MACS, "getclrmacs", NULL, &ng_macfilter_macs_type }, { NGM_MACFILTER_COOKIE, NGM_MACFILTER_CLR_MACS, "clrmacs", NULL, NULL, }, { NGM_MACFILTER_COOKIE, NGM_MACFILTER_GET_HOOKS, "gethooks", NULL, &ng_macfilter_hooks_type }, { 0 } }; /* * Netgraph node type descriptor */ static ng_constructor_t ng_macfilter_constructor; static ng_rcvmsg_t ng_macfilter_rcvmsg; static ng_shutdown_t ng_macfilter_shutdown; static ng_newhook_t ng_macfilter_newhook; static ng_rcvdata_t ng_macfilter_rcvdata; static ng_disconnect_t ng_macfilter_disconnect; static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_MACFILTER_NODE_TYPE, .constructor = ng_macfilter_constructor, .rcvmsg = ng_macfilter_rcvmsg, .shutdown = ng_macfilter_shutdown, .newhook = ng_macfilter_newhook, .rcvdata = ng_macfilter_rcvdata, .disconnect = ng_macfilter_disconnect, .cmdlist = ng_macfilter_cmdlist }; NETGRAPH_INIT(macfilter, &typestruct); /* * Per MAC address info: the hook where to send to, the address * Note: We use the same struct as in the netgraph message, so we can bcopy the * array. */ typedef struct ngm_macfilter_mac *mf_mac_p; /* * Node info */ typedef struct { hook_p mf_ether_hook; /* Ethernet hook */ hook_p *mf_upper; /* Upper hooks */ u_int mf_upper_cnt; /* Allocated # of upper slots */ struct mtx mtx; /* Mutex for MACs table */ mf_mac_p mf_macs; /* MAC info: dynamically allocated */ u_int mf_mac_allocated;/* Allocated # of MAC slots */ u_int mf_mac_used; /* Used # of MAC slots */ } *macfilter_p; /* * Resize the MAC table to accommodate at least mfp->mf_mac_used + 1 entries. * * Note: mtx already held */ static int macfilter_mactable_resize(macfilter_p mfp) { int error = 0; int n = mfp->mf_mac_allocated; if (mfp->mf_mac_used < 2*MACTABLE_BLOCKSIZE-1) /* minimum size */ n = 2*MACTABLE_BLOCKSIZE-1; else if (mfp->mf_mac_used + 2*MACTABLE_BLOCKSIZE < mfp->mf_mac_allocated) /* reduce size */ n = mfp->mf_mac_allocated - MACTABLE_BLOCKSIZE; else if (mfp->mf_mac_used == mfp->mf_mac_allocated) /* increase size */ n = mfp->mf_mac_allocated + MACTABLE_BLOCKSIZE; if (n != mfp->mf_mac_allocated) { MACFILTER_DEBUG("used=%d allocated=%d->%d", mfp->mf_mac_used, mfp->mf_mac_allocated, n); mf_mac_p mfp_new = realloc(mfp->mf_macs, sizeof(mfp->mf_macs[0])*n, M_NETGRAPH, M_NOWAIT | M_ZERO); if (mfp_new == NULL) { error = -1; } else { mfp->mf_macs = mfp_new; mfp->mf_mac_allocated = n; } } return error; } /* * Resets the macfilter to pass all received packets * to the default hook. * * Note: mtx already held */ static void macfilter_reset(macfilter_p mfp) { mfp->mf_mac_used = 0; macfilter_mactable_resize(mfp); } /* * Resets the counts for each MAC address. * * Note: mtx already held */ static void macfilter_reset_stats(macfilter_p mfp) { int i; for (i = 0; i < mfp->mf_mac_used; i++) { mf_mac_p p = &mfp->mf_macs[i]; p->packets_in = p->packets_out = 0; p->bytes_in = p->bytes_out = 0; } } /* * Count the number of matching macs routed to this hook. * * Note: mtx already held */ static int macfilter_mac_count(macfilter_p mfp, int hookid) { int i; int cnt = 0; for (i = 0; i < mfp->mf_mac_used; i++) if (mfp->mf_macs[i].hookid == hookid) cnt++; return cnt; } /* * Find a MAC address in the mac table. * * Returns 0 on failure with *ri set to index before which to insert a new * element. Or returns 1 on success with *ri set to the index of the element * that matches. * * Note: mtx already held. */ static u_int macfilter_find_mac(macfilter_p mfp, const u_char *ether, u_int *ri) { mf_mac_p mf_macs = mfp->mf_macs; u_int base = 0; u_int range = mfp->mf_mac_used; while (range > 0) { u_int middle = base + (range >> 1); /* middle */ int d = bcmp(ether, mf_macs[middle].ether, ETHER_ADDR_LEN); if (d == 0) { /* match */ *ri = middle; return 1; } else if (d > 0) { /* move right */ range -= middle - base + 1; base = middle + 1; } else { /* move left */ range = middle - base; } } *ri = base; return 0; } /* * Change the upper hook for the given MAC address. If the hook id is zero (the * default hook), the MAC address is removed from the table. Otherwise it is * inserted to the table at a proper location, and the id of the hook is * marked. * * Note: mtx already held. */ static int macfilter_mactable_change(macfilter_p mfp, u_char *ether, int hookid) { u_int i; int found = macfilter_find_mac(mfp, ether, &i); mf_mac_p mf_macs = mfp->mf_macs; MACFILTER_DEBUG("ether=" MAC_FMT " found=%d i=%d ether=" MAC_FMT " hookid=%d->%d used=%d allocated=%d", MAC_S_ARGS(ether), found, i, MAC_S_ARGS(mf_macs[i].ether), (found? mf_macs[i].hookid:NG_MACFILTER_HOOK_DEFAULT_ID), hookid, mfp->mf_mac_used, mfp->mf_mac_allocated); if (found) { if (hookid == NG_MACFILTER_HOOK_DEFAULT_ID) { /* drop */ /* Compress table */ mfp->mf_mac_used--; size_t len = (mfp->mf_mac_used - i) * sizeof(mf_macs[0]); if (len > 0) bcopy(&mf_macs[i+1], &mf_macs[i], len); macfilter_mactable_resize(mfp); } else { /* modify */ mf_macs[i].hookid = hookid; } } else { if (hookid == NG_MACFILTER_HOOK_DEFAULT_ID) { /* not found */ /* not present and not inserted */ return 0; } else { /* add */ if (macfilter_mactable_resize(mfp) == -1) { return ENOMEM; } else { mf_macs = mfp->mf_macs; /* reassign; might have moved during resize */ /* make room for new entry, unless appending */ size_t len = (mfp->mf_mac_used - i) * sizeof(mf_macs[0]); if (len > 0) bcopy(&mf_macs[i], &mf_macs[i+1], len); mf_macs[i].hookid = hookid; bcopy(ether, mf_macs[i].ether, ETHER_ADDR_LEN); mfp->mf_mac_used++; } } } return 0; } static int macfilter_mactable_remove_by_hookid(macfilter_p mfp, int hookid) { int i, j; for (i = 0, j = 0; i < mfp->mf_mac_used; i++) { if (mfp->mf_macs[i].hookid != hookid) { if (i != j) bcopy(&mfp->mf_macs[i], &mfp->mf_macs[j], sizeof(mfp->mf_macs[0])); j++; } } int removed = i - j; mfp->mf_mac_used = j; macfilter_mactable_resize(mfp); return removed; } static int macfilter_find_hook(macfilter_p mfp, const char *hookname) { int hookid; for (hookid = 0; hookid < mfp->mf_upper_cnt; hookid++) { if (mfp->mf_upper[hookid]) { if (strncmp(NG_HOOK_NAME(mfp->mf_upper[hookid]), hookname, NG_HOOKSIZ) == 0) { return hookid; } } } return 0; } static int macfilter_direct(macfilter_p mfp, struct ngm_macfilter_direct *md) { MACFILTER_DEBUG("ether=" MAC_FMT " hook=%s", MAC_S_ARGS(md->ether), md->hookname); int hookid = macfilter_find_hook(mfp, md->hookname); if (hookid < 0) return ENOENT; return macfilter_mactable_change(mfp, md->ether, hookid); } static int macfilter_direct_hookid(macfilter_p mfp, struct ngm_macfilter_direct_hookid *mdi) { MACFILTER_DEBUG("ether=" MAC_FMT " hookid=%d", MAC_S_ARGS(mdi->ether), mdi->hookid); if (mdi->hookid >= mfp->mf_upper_cnt) return EINVAL; else if (mfp->mf_upper[mdi->hookid] == NULL) return EINVAL; return macfilter_mactable_change(mfp, mdi->ether, mdi->hookid); } /* * Packet handling */ /* * Pass packets received from any upper hook to * a lower hook */ static int macfilter_ether_output(hook_p hook, macfilter_p mfp, struct mbuf *m, hook_p *next_hook) { struct ether_header *ether_header = mtod(m, struct ether_header *); u_char *ether = ether_header->ether_dhost; *next_hook = mfp->mf_ether_hook; mtx_lock(&mfp->mtx); u_int i; int found = macfilter_find_mac(mfp, ether, &i); if (found) { mf_mac_p mf_macs = mfp->mf_macs; mf_macs[i].packets_out++; if (m->m_len > ETHER_HDR_LEN) mf_macs[i].bytes_out += m->m_len - ETHER_HDR_LEN; #ifdef NG_MACFILTER_DEBUG_RECVDATA MACFILTER_DEBUG("ether=" MAC_FMT " len=%db->%lldb: bytes: %s -> %s", MAC_S_ARGS(ether), m->m_len - ETHER_HDR_LEN, mf_macs[i].bytes_out, NG_HOOK_NAME(hook), NG_HOOK_NAME(*next_hook)); #endif } else { #ifdef NG_MACFILTER_DEBUG_RECVDATA MACFILTER_DEBUG("ether=" MAC_FMT " len=%db->?b: bytes: %s->%s", MAC_S_ARGS(ether), m->m_len - ETHER_HDR_LEN, NG_HOOK_NAME(hook), NG_HOOK_NAME(*next_hook)); #endif } mtx_unlock(&mfp->mtx); return 0; } /* * Search for the right upper hook, based on the source ethernet * address. If not found, pass to the default upper hook. */ static int macfilter_ether_input(hook_p hook, macfilter_p mfp, struct mbuf *m, hook_p *next_hook) { struct ether_header *ether_header = mtod(m, struct ether_header *); u_char *ether = ether_header->ether_shost; int hookid = NG_MACFILTER_HOOK_DEFAULT_ID; mtx_lock(&mfp->mtx); u_int i; int found = macfilter_find_mac(mfp, ether, &i); if (found) { mf_mac_p mf_macs = mfp->mf_macs; mf_macs[i].packets_in++; if (m->m_len > ETHER_HDR_LEN) mf_macs[i].bytes_in += m->m_len - ETHER_HDR_LEN; hookid = mf_macs[i].hookid; #ifdef NG_MACFILTER_DEBUG_RECVDATA MACFILTER_DEBUG("ether=" MAC_FMT " len=%db->%lldb: bytes: %s->%s", MAC_S_ARGS(ether), m->m_len - ETHER_HDR_LEN, mf_macs[i].bytes_in, NG_HOOK_NAME(hook), NG_HOOK_NAME(*next_hook)); #endif } else { #ifdef NG_MACFILTER_DEBUG_RECVDATA MACFILTER_DEBUG("ether=" MAC_FMT " len=%db->?b: bytes: %s->%s", MAC_S_ARGS(ether), m->m_len - ETHER_HDR_LEN, NG_HOOK_NAME(hook), NG_HOOK_NAME(*next_hook)); #endif } if (hookid >= mfp->mf_upper_cnt) *next_hook = NULL; else *next_hook = mfp->mf_upper[hookid]; mtx_unlock(&mfp->mtx); return 0; } /* * ====================================================================== * Netgraph hooks * ====================================================================== */ /* * See basic netgraph code for comments on the individual functions. */ static int ng_macfilter_constructor(node_p node) { macfilter_p mfp = malloc(sizeof(*mfp), M_NETGRAPH, M_NOWAIT | M_ZERO); if (mfp == NULL) return ENOMEM; int error = macfilter_mactable_resize(mfp); if (error) return error; NG_NODE_SET_PRIVATE(node, mfp); mtx_init(&mfp->mtx, "Macfilter table", NULL, MTX_DEF); return (0); } static int ng_macfilter_newhook(node_p node, hook_p hook, const char *hookname) { const macfilter_p mfp = NG_NODE_PRIVATE(node); MACFILTER_DEBUG("%s", hookname); if (strcmp(hookname, NG_MACFILTER_HOOK_ETHER) == 0) { mfp->mf_ether_hook = hook; } else { int hookid; if (strcmp(hookname, NG_MACFILTER_HOOK_DEFAULT) == 0) { hookid = NG_MACFILTER_HOOK_DEFAULT_ID; } else { for (hookid = 1; hookid < mfp->mf_upper_cnt; hookid++) if (mfp->mf_upper[hookid] == NULL) break; } if (hookid >= mfp->mf_upper_cnt) { MACFILTER_DEBUG("upper cnt %d -> %d", mfp->mf_upper_cnt, hookid + 1); mfp->mf_upper_cnt = hookid + 1; mfp->mf_upper = realloc(mfp->mf_upper, sizeof(mfp->mf_upper[0])*mfp->mf_upper_cnt, M_NETGRAPH, M_NOWAIT | M_ZERO); } mfp->mf_upper[hookid] = hook; } return(0); } static int ng_macfilter_rcvmsg(node_p node, item_p item, hook_p lasthook) { const macfilter_p mfp = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; struct ng_mesg *msg; int error = 0; struct ngm_macfilter_macs *ngm_macs; struct ngm_macfilter_hooks *ngm_hooks; struct ngm_macfilter_direct *md; struct ngm_macfilter_direct_hookid *mdi; int n = 0, i = 0; int hookid = 0; int resplen; NGI_GET_MSG(item, msg); mtx_lock(&mfp->mtx); switch (msg->header.typecookie) { case NGM_MACFILTER_COOKIE: switch (msg->header.cmd) { case NGM_MACFILTER_RESET: macfilter_reset(mfp); break; case NGM_MACFILTER_DIRECT: if (msg->header.arglen != sizeof(struct ngm_macfilter_direct)) { MACFILTER_DEBUG("direct: wrong type length (%d, expected %zu)", msg->header.arglen, sizeof(struct ngm_macfilter_direct)); error = EINVAL; break; } md = (struct ngm_macfilter_direct *)msg->data; error = macfilter_direct(mfp, md); break; case NGM_MACFILTER_DIRECT_HOOKID: if (msg->header.arglen != sizeof(struct ngm_macfilter_direct_hookid)) { MACFILTER_DEBUG("direct hookid: wrong type length (%d, expected %zu)", msg->header.arglen, sizeof(struct ngm_macfilter_direct)); error = EINVAL; break; } mdi = (struct ngm_macfilter_direct_hookid *)msg->data; error = macfilter_direct_hookid(mfp, mdi); break; case NGM_MACFILTER_GET_MACS: case NGM_MACFILTER_GETCLR_MACS: n = mfp->mf_mac_used; resplen = sizeof(struct ngm_macfilter_macs) + n * sizeof(struct ngm_macfilter_mac); NG_MKRESPONSE(resp, msg, resplen, M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } ngm_macs = (struct ngm_macfilter_macs *)resp->data; ngm_macs->n = n; bcopy(mfp->mf_macs, &ngm_macs->macs[0], n * sizeof(struct ngm_macfilter_mac)); if (msg->header.cmd == NGM_MACFILTER_GETCLR_MACS) macfilter_reset_stats(mfp); break; case NGM_MACFILTER_CLR_MACS: macfilter_reset_stats(mfp); break; case NGM_MACFILTER_GET_HOOKS: for (hookid = 0; hookid < mfp->mf_upper_cnt; hookid++) if (mfp->mf_upper[hookid] != NULL) n++; resplen = sizeof(struct ngm_macfilter_hooks) + n * sizeof(struct ngm_macfilter_hook); NG_MKRESPONSE(resp, msg, resplen, M_NOWAIT | M_ZERO); if (resp == NULL) { error = ENOMEM; break; } ngm_hooks = (struct ngm_macfilter_hooks *)resp->data; ngm_hooks->n = n; for (hookid = 0; hookid < mfp->mf_upper_cnt; hookid++) { if (mfp->mf_upper[hookid] != NULL) { struct ngm_macfilter_hook *ngm_hook = &ngm_hooks->hooks[i++]; strlcpy(ngm_hook->hookname, NG_HOOK_NAME(mfp->mf_upper[hookid]), NG_HOOKSIZ); ngm_hook->hookid = hookid; ngm_hook->maccnt = macfilter_mac_count(mfp, hookid); } } break; default: error = EINVAL; /* unknown command */ break; } break; default: error = EINVAL; /* unknown cookie type */ break; } mtx_unlock(&mfp->mtx); NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return error; } static int ng_macfilter_rcvdata(hook_p hook, item_p item) { const macfilter_p mfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); int error; hook_p next_hook = NULL; struct mbuf *m; m = NGI_M(item); /* 'item' still owns it. We are peeking */ MACFILTER_DEBUG("%s", NG_HOOK_NAME(hook)); if (hook == mfp->mf_ether_hook) error = macfilter_ether_input(hook, mfp, m, &next_hook); else if (mfp->mf_ether_hook != NULL) error = macfilter_ether_output(hook, mfp, m, &next_hook); if (next_hook == NULL) { NG_FREE_ITEM(item); return (0); } NG_FWD_ITEM_HOOK(error, item, next_hook); return error; } static int ng_macfilter_disconnect(hook_p hook) { const macfilter_p mfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); mtx_lock(&mfp->mtx); if (mfp->mf_ether_hook == hook) { mfp->mf_ether_hook = NULL; MACFILTER_DEBUG("%s", NG_HOOK_NAME(hook)); } else { int hookid; for (hookid = 0; hookid < mfp->mf_upper_cnt; hookid++) { if (mfp->mf_upper[hookid] == hook) { mfp->mf_upper[hookid] = NULL; #ifndef NG_MACFILTER_DEBUG macfilter_mactable_remove_by_hookid(mfp, hookid); #else int cnt = macfilter_mactable_remove_by_hookid(mfp, hookid); MACFILTER_DEBUG("%s: removed %d MACs", NG_HOOK_NAME(hook), cnt); #endif break; } } if (hookid == mfp->mf_upper_cnt - 1) { /* Reduce the size of the array when the last element was removed */ for (--hookid; hookid >= 0 && mfp->mf_upper[hookid] == NULL; hookid--) ; MACFILTER_DEBUG("upper cnt %d -> %d", mfp->mf_upper_cnt, hookid + 1); mfp->mf_upper_cnt = hookid + 1; mfp->mf_upper = realloc(mfp->mf_upper, sizeof(mfp->mf_upper[0])*mfp->mf_upper_cnt, M_NETGRAPH, M_NOWAIT | M_ZERO); } } mtx_unlock(&mfp->mtx); if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) { ng_rmnode_self(NG_HOOK_NODE(hook)); } return (0); } static int ng_macfilter_shutdown(node_p node) { const macfilter_p mfp = NG_NODE_PRIVATE(node); mtx_destroy(&mfp->mtx); free(mfp->mf_upper, M_NETGRAPH); free(mfp->mf_macs, M_NETGRAPH); free(mfp, M_NETGRAPH); NG_NODE_UNREF(node); return (0); }