185722Sjulian/*- 285722Sjulian * ng_etf.c Ethertype filter 3139823Simp */ 4139823Simp 5139823Simp/*- 685722Sjulian * Copyright (c) 2001, FreeBSD Incorporated 785722Sjulian * All rights reserved. 885722Sjulian * 985722Sjulian * Redistribution and use in source and binary forms, with or without 1085722Sjulian * modification, are permitted provided that the following conditions 1185722Sjulian * are met: 1285722Sjulian * 1. Redistributions of source code must retain the above copyright 1385722Sjulian * notice unmodified, this list of conditions, and the following 1485722Sjulian * disclaimer. 1585722Sjulian * 2. Redistributions in binary form must reproduce the above copyright 1685722Sjulian * notice, this list of conditions and the following disclaimer in the 1785722Sjulian * documentation and/or other materials provided with the distribution. 1885722Sjulian * 1985722Sjulian * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 2085722Sjulian * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2185722Sjulian * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2285722Sjulian * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 2385722Sjulian * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2485722Sjulian * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2585722Sjulian * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2685722Sjulian * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2785722Sjulian * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2885722Sjulian * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2985722Sjulian * SUCH DAMAGE. 3085722Sjulian * 3185722Sjulian * Author: Julian Elischer <julian@freebsd.org> 3285722Sjulian * 3385722Sjulian * $FreeBSD$ 3485722Sjulian */ 3585722Sjulian 3685722Sjulian#include <sys/param.h> 3785722Sjulian#include <sys/systm.h> 3885722Sjulian#include <sys/kernel.h> 3985722Sjulian#include <sys/mbuf.h> 4085722Sjulian#include <sys/malloc.h> 4185722Sjulian#include <sys/ctype.h> 4285722Sjulian#include <sys/errno.h> 4385722Sjulian#include <sys/queue.h> 4485722Sjulian#include <sys/syslog.h> 4585722Sjulian 4685722Sjulian#include <net/ethernet.h> 4785722Sjulian 4885722Sjulian#include <netgraph/ng_message.h> 4985722Sjulian#include <netgraph/ng_parse.h> 5085722Sjulian#include <netgraph/ng_etf.h> 5185722Sjulian#include <netgraph/netgraph.h> 5285722Sjulian 5385722Sjulian/* If you do complicated mallocs you may want to do this */ 5485722Sjulian/* and use it for your mallocs */ 5585722Sjulian#ifdef NG_SEPARATE_MALLOC 56141635Sphkstatic MALLOC_DEFINE(M_NETGRAPH_ETF, "netgraph_etf", "netgraph etf node "); 5785722Sjulian#else 5885722Sjulian#define M_NETGRAPH_ETF M_NETGRAPH 5985722Sjulian#endif 6085722Sjulian 6185722Sjulian/* 6285722Sjulian * This section contains the netgraph method declarations for the 6385722Sjulian * etf node. These methods define the netgraph 'type'. 6485722Sjulian */ 6585722Sjulian 6685722Sjulianstatic ng_constructor_t ng_etf_constructor; 6785722Sjulianstatic ng_rcvmsg_t ng_etf_rcvmsg; 6885722Sjulianstatic ng_shutdown_t ng_etf_shutdown; 6985722Sjulianstatic ng_newhook_t ng_etf_newhook; 7085722Sjulianstatic ng_rcvdata_t ng_etf_rcvdata; /* note these are both ng_rcvdata_t */ 7185722Sjulianstatic ng_disconnect_t ng_etf_disconnect; 7285722Sjulian 7385722Sjulian/* Parse type for struct ng_etfstat */ 7497685Sarchiestatic const struct ng_parse_struct_field ng_etf_stat_type_fields[] 7597685Sarchie = NG_ETF_STATS_TYPE_INFO; 7685722Sjulianstatic const struct ng_parse_type ng_etf_stat_type = { 7785722Sjulian &ng_parse_struct_type, 7897685Sarchie &ng_etf_stat_type_fields 7985722Sjulian}; 8085722Sjulian/* Parse type for struct ng_setfilter */ 8197685Sarchiestatic const struct ng_parse_struct_field ng_etf_filter_type_fields[] 8297685Sarchie = NG_ETF_FILTER_TYPE_INFO; 8385722Sjulianstatic const struct ng_parse_type ng_etf_filter_type = { 8485722Sjulian &ng_parse_struct_type, 8597685Sarchie &ng_etf_filter_type_fields 8685722Sjulian}; 8785722Sjulian 8885722Sjulian/* List of commands and how to convert arguments to/from ASCII */ 8985722Sjulianstatic const struct ng_cmdlist ng_etf_cmdlist[] = { 9085722Sjulian { 9185722Sjulian NGM_ETF_COOKIE, 9285722Sjulian NGM_ETF_GET_STATUS, 9385722Sjulian "getstatus", 9485722Sjulian NULL, 9585722Sjulian &ng_etf_stat_type, 9685722Sjulian }, 9785722Sjulian { 9885722Sjulian NGM_ETF_COOKIE, 9985722Sjulian NGM_ETF_SET_FLAG, 10085722Sjulian "setflag", 10185722Sjulian &ng_parse_int32_type, 10285722Sjulian NULL 10385722Sjulian }, 10485722Sjulian { 10585722Sjulian NGM_ETF_COOKIE, 10685722Sjulian NGM_ETF_SET_FILTER, 10785722Sjulian "setfilter", 10885722Sjulian &ng_etf_filter_type, 10985722Sjulian NULL 11085722Sjulian }, 11185722Sjulian { 0 } 11285722Sjulian}; 11385722Sjulian 11485722Sjulian/* Netgraph node type descriptor */ 11585722Sjulianstatic struct ng_type typestruct = { 116129823Sjulian .version = NG_ABI_VERSION, 117129823Sjulian .name = NG_ETF_NODE_TYPE, 118129823Sjulian .constructor = ng_etf_constructor, 119129823Sjulian .rcvmsg = ng_etf_rcvmsg, 120129823Sjulian .shutdown = ng_etf_shutdown, 121129823Sjulian .newhook = ng_etf_newhook, 122129823Sjulian .rcvdata = ng_etf_rcvdata, 123129823Sjulian .disconnect = ng_etf_disconnect, 124129823Sjulian .cmdlist = ng_etf_cmdlist, 12585722Sjulian}; 12685722SjulianNETGRAPH_INIT(etf, &typestruct); 12785722Sjulian 12885722Sjulian/* Information we store for each hook on each node */ 12985722Sjulianstruct ETF_hookinfo { 13085722Sjulian hook_p hook; 13185722Sjulian}; 13285722Sjulian 13385722Sjulianstruct filter { 13485722Sjulian LIST_ENTRY(filter) next; 13585722Sjulian u_int16_t ethertype; /* network order ethertype */ 13685722Sjulian hook_p match_hook; /* Hook to use on a match */ 13785722Sjulian}; 13885722Sjulian 13985722Sjulian#define HASHSIZE 16 /* Dont change this without changing HASH() */ 14085722Sjulian#define HASH(et) ((((et)>>12)+((et)>>8)+((et)>>4)+(et)) & 0x0f) 14185722SjulianLIST_HEAD(filterhead, filter); 14285722Sjulian 14385722Sjulian/* Information we store for each node */ 14485722Sjulianstruct ETF { 14585722Sjulian struct ETF_hookinfo downstream_hook; 14685722Sjulian struct ETF_hookinfo nomatch_hook; 14785722Sjulian node_p node; /* back pointer to node */ 14885722Sjulian u_int packets_in; /* packets in from downstream */ 14985722Sjulian u_int packets_out; /* packets out towards downstream */ 15085722Sjulian u_int32_t flags; 15185722Sjulian struct filterhead hashtable[HASHSIZE]; 15285722Sjulian}; 15385722Sjuliantypedef struct ETF *etf_p; 15485722Sjulian 15585722Sjulianstatic struct filter * 15685722Sjulianng_etf_findentry(etf_p etfp, u_int16_t ethertype) 15785722Sjulian{ 15885722Sjulian struct filterhead *chain = etfp->hashtable + HASH(ethertype); 15985722Sjulian struct filter *fil; 16085722Sjulian 16185722Sjulian 16285722Sjulian LIST_FOREACH(fil, chain, next) { 16385722Sjulian if (fil->ethertype == ethertype) { 16485722Sjulian return (fil); 16585722Sjulian } 16685722Sjulian } 16785722Sjulian return (NULL); 16885722Sjulian} 16985722Sjulian 17085722Sjulian 17185722Sjulian/* 17285722Sjulian * Allocate the private data structure. The generic node has already 17385722Sjulian * been created. Link them together. We arrive with a reference to the node 17485722Sjulian * i.e. the reference count is incremented for us already. 17585722Sjulian */ 17685722Sjulianstatic int 17785722Sjulianng_etf_constructor(node_p node) 17885722Sjulian{ 17985722Sjulian etf_p privdata; 18085722Sjulian int i; 18185722Sjulian 18285722Sjulian /* Initialize private descriptor */ 183220768Sglebius privdata = malloc(sizeof(*privdata), M_NETGRAPH_ETF, M_WAITOK | M_ZERO); 18485722Sjulian for (i = 0; i < HASHSIZE; i++) { 18585722Sjulian LIST_INIT((privdata->hashtable + i)); 18685722Sjulian } 18785722Sjulian 18885722Sjulian /* Link structs together; this counts as our one reference to node */ 18985722Sjulian NG_NODE_SET_PRIVATE(node, privdata); 19085722Sjulian privdata->node = node; 19185722Sjulian return (0); 19285722Sjulian} 19385722Sjulian 19485722Sjulian/* 19585722Sjulian * Give our ok for a hook to be added... 19685722Sjulian * All names are ok. Two names are special. 19785722Sjulian */ 19885722Sjulianstatic int 19985722Sjulianng_etf_newhook(node_p node, hook_p hook, const char *name) 20085722Sjulian{ 20185722Sjulian const etf_p etfp = NG_NODE_PRIVATE(node); 20285722Sjulian struct ETF_hookinfo *hpriv; 20385722Sjulian 20485722Sjulian if (strcmp(name, NG_ETF_HOOK_DOWNSTREAM) == 0) { 20585722Sjulian etfp->downstream_hook.hook = hook; 20685722Sjulian NG_HOOK_SET_PRIVATE(hook, &etfp->downstream_hook); 20785722Sjulian etfp->packets_in = 0; 20885722Sjulian etfp->packets_out = 0; 20985722Sjulian } else if (strcmp(name, NG_ETF_HOOK_NOMATCH) == 0) { 21085722Sjulian etfp->nomatch_hook.hook = hook; 21185722Sjulian NG_HOOK_SET_PRIVATE(hook, &etfp->nomatch_hook); 21285722Sjulian } else { 21385722Sjulian /* 21485722Sjulian * Any other hook name is valid and can 21585722Sjulian * later be associated with a filter rule. 21685722Sjulian */ 217184205Sdes hpriv = malloc(sizeof(*hpriv), 21885722Sjulian M_NETGRAPH_ETF, M_NOWAIT | M_ZERO); 21985722Sjulian if (hpriv == NULL) { 22085722Sjulian return (ENOMEM); 22185722Sjulian } 22285722Sjulian 22385722Sjulian NG_HOOK_SET_PRIVATE(hook, hpriv); 22485722Sjulian hpriv->hook = hook; 22585722Sjulian } 22685722Sjulian return(0); 22785722Sjulian} 22885722Sjulian 22985722Sjulian/* 23085722Sjulian * Get a netgraph control message. 23185722Sjulian * We actually recieve a queue item that has a pointer to the message. 23285722Sjulian * If we free the item, the message will be freed too, unless we remove 23385722Sjulian * it from the item using NGI_GET_MSG(); 23485722Sjulian * The return address is also stored in the item, as an ng_ID_t, 23585722Sjulian * accessible as NGI_RETADDR(item); 23685722Sjulian * Check it is one we understand. If needed, send a response. 23785722Sjulian * We could save the address for an async action later, but don't here. 23885722Sjulian * Always free the message. 23985722Sjulian * The response should be in a malloc'd region that the caller can 'free'. 24085722Sjulian * The NG_MKRESPONSE macro does all this for us. 24185722Sjulian * A response is not required. 24285722Sjulian * Theoretically you could respond defferently to old message types if 24385722Sjulian * the cookie in the header didn't match what we consider to be current 24485722Sjulian * (so that old userland programs could continue to work). 24585722Sjulian */ 24685722Sjulianstatic int 24785722Sjulianng_etf_rcvmsg(node_p node, item_p item, hook_p lasthook) 24885722Sjulian{ 24985722Sjulian const etf_p etfp = NG_NODE_PRIVATE(node); 25085722Sjulian struct ng_mesg *resp = NULL; 25185722Sjulian int error = 0; 25285722Sjulian struct ng_mesg *msg; 25385722Sjulian 25485722Sjulian NGI_GET_MSG(item, msg); 25585722Sjulian /* Deal with message according to cookie and command */ 25685722Sjulian switch (msg->header.typecookie) { 25785722Sjulian case NGM_ETF_COOKIE: 25885722Sjulian switch (msg->header.cmd) { 25985722Sjulian case NGM_ETF_GET_STATUS: 26085722Sjulian { 26185722Sjulian struct ng_etfstat *stats; 26285722Sjulian 26385722Sjulian NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); 26485722Sjulian if (!resp) { 26585722Sjulian error = ENOMEM; 26685722Sjulian break; 26785722Sjulian } 26885722Sjulian stats = (struct ng_etfstat *) resp->data; 26985722Sjulian stats->packets_in = etfp->packets_in; 27085722Sjulian stats->packets_out = etfp->packets_out; 27185722Sjulian break; 27285722Sjulian } 27385722Sjulian case NGM_ETF_SET_FLAG: 27485722Sjulian if (msg->header.arglen != sizeof(u_int32_t)) { 27585722Sjulian error = EINVAL; 27685722Sjulian break; 27785722Sjulian } 27885722Sjulian etfp->flags = *((u_int32_t *) msg->data); 27985722Sjulian break; 28085722Sjulian case NGM_ETF_SET_FILTER: 28185722Sjulian { 28285722Sjulian struct ng_etffilter *f; 28385722Sjulian struct filter *fil; 28485722Sjulian hook_p hook; 28585722Sjulian 28685722Sjulian /* Check message long enough for this command */ 28785722Sjulian if (msg->header.arglen != sizeof(*f)) { 28885722Sjulian error = EINVAL; 28985722Sjulian break; 29085722Sjulian } 29185722Sjulian 29285722Sjulian /* Make sure hook referenced exists */ 29385722Sjulian f = (struct ng_etffilter *)msg->data; 29485722Sjulian hook = ng_findhook(node, f->matchhook); 29585722Sjulian if (hook == NULL) { 29685722Sjulian error = ENOENT; 29785722Sjulian break; 29885722Sjulian } 29985722Sjulian 30085722Sjulian /* and is not the downstream hook */ 30185722Sjulian if (hook == etfp->downstream_hook.hook) { 30285722Sjulian error = EINVAL; 30385722Sjulian break; 30485722Sjulian } 30585722Sjulian 30685722Sjulian /* Check we don't already trap this ethertype */ 30785722Sjulian if (ng_etf_findentry(etfp, 30885722Sjulian htons(f->ethertype))) { 30985722Sjulian error = EEXIST; 31085722Sjulian break; 31185722Sjulian } 31285722Sjulian 31385722Sjulian /* 31485722Sjulian * Ok, make the filter and put it in the 31585722Sjulian * hashtable ready for matching. 31685722Sjulian */ 317184205Sdes fil = malloc(sizeof(*fil), 31885722Sjulian M_NETGRAPH_ETF, M_NOWAIT | M_ZERO); 31985722Sjulian if (fil == NULL) { 320122865Sru error = ENOMEM; 321122865Sru break; 32285722Sjulian } 32385722Sjulian 32485722Sjulian fil->match_hook = hook; 32585722Sjulian fil->ethertype = htons(f->ethertype); 32685722Sjulian LIST_INSERT_HEAD( etfp->hashtable 32785722Sjulian + HASH(fil->ethertype), 32885722Sjulian fil, next); 32985722Sjulian } 33085722Sjulian break; 33185722Sjulian default: 33285722Sjulian error = EINVAL; /* unknown command */ 33385722Sjulian break; 33485722Sjulian } 33585722Sjulian break; 33685722Sjulian default: 33785722Sjulian error = EINVAL; /* unknown cookie type */ 33885722Sjulian break; 33985722Sjulian } 34085722Sjulian 34185722Sjulian /* Take care of synchronous response, if any */ 34285722Sjulian NG_RESPOND_MSG(error, node, item, resp); 34385722Sjulian /* Free the message and return */ 34485722Sjulian NG_FREE_MSG(msg); 34585722Sjulian return(error); 34685722Sjulian} 34785722Sjulian 34885722Sjulian/* 34985722Sjulian * Receive data, and do something with it. 35085722Sjulian * Actually we receive a queue item which holds the data. 351131155Sjulian * If we free the item it will also free the data unless we have previously 352131155Sjulian * disassociated it using the NGI_GET_etf() macro. 35385722Sjulian * Possibly send it out on another link after processing. 35485722Sjulian * Possibly do something different if it comes from different 355131155Sjulian * hooks. The caller will never free m , so if we use up this data 356131155Sjulian * or abort we must free it. 35785722Sjulian * 35885722Sjulian * If we want, we may decide to force this data to be queued and reprocessed 35985722Sjulian * at the netgraph NETISR time. 36085722Sjulian * We would do that by setting the HK_QUEUE flag on our hook. We would do that 36185722Sjulian * in the connect() method. 36285722Sjulian */ 36385722Sjulianstatic int 36485722Sjulianng_etf_rcvdata(hook_p hook, item_p item ) 36585722Sjulian{ 36685722Sjulian const etf_p etfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); 36785722Sjulian struct ether_header *eh; 36885722Sjulian int error = 0; 36985722Sjulian struct mbuf *m; 37085722Sjulian u_int16_t ethertype; 37185722Sjulian struct filter *fil; 37285722Sjulian 37385722Sjulian if (NG_HOOK_PRIVATE(hook) == NULL) { /* Shouldn't happen but.. */ 37485722Sjulian NG_FREE_ITEM(item); 37585722Sjulian } 37685722Sjulian 37785722Sjulian /* 37885722Sjulian * Everything not from the downstream hook goes to the 37985722Sjulian * downstream hook. But only if it matches the ethertype 38085722Sjulian * of the source hook. Un matching must go to/from 'nomatch'. 38185722Sjulian */ 38285722Sjulian 38385722Sjulian /* Make sure we have an entire header */ 38485722Sjulian NGI_GET_M(item, m); 38585722Sjulian if (m->m_len < sizeof(*eh) ) { 38685722Sjulian m = m_pullup(m, sizeof(*eh)); 38785722Sjulian if (m == NULL) { 38885722Sjulian NG_FREE_ITEM(item); 38985722Sjulian return(EINVAL); 39085722Sjulian } 39185722Sjulian } 39285722Sjulian 39385722Sjulian eh = mtod(m, struct ether_header *); 39485722Sjulian ethertype = eh->ether_type; 39585722Sjulian fil = ng_etf_findentry(etfp, ethertype); 39685722Sjulian 39785722Sjulian /* 39885722Sjulian * if from downstream, select between a match hook or 39985722Sjulian * the nomatch hook 40085722Sjulian */ 40185722Sjulian if (hook == etfp->downstream_hook.hook) { 40285722Sjulian etfp->packets_in++; 40385722Sjulian if (fil && fil->match_hook) { 40485722Sjulian NG_FWD_NEW_DATA(error, item, fil->match_hook, m); 40585722Sjulian } else { 40685722Sjulian NG_FWD_NEW_DATA(error, item,etfp->nomatch_hook.hook, m); 40785722Sjulian } 40885722Sjulian } else { 40985722Sjulian /* 41085722Sjulian * It must be heading towards the downstream. 41185722Sjulian * Check that it's ethertype matches 41285722Sjulian * the filters for it's input hook. 41385722Sjulian * If it doesn't have one, check it's from nomatch. 41485722Sjulian */ 41585722Sjulian if ((fil && (fil->match_hook != hook)) 41685722Sjulian || ((fil == NULL) && (hook != etfp->nomatch_hook.hook))) { 41785722Sjulian NG_FREE_ITEM(item); 41885722Sjulian NG_FREE_M(m); 41985722Sjulian return (EPROTOTYPE); 42085722Sjulian } 42185722Sjulian NG_FWD_NEW_DATA( error, item, etfp->downstream_hook.hook, m); 42285722Sjulian if (error == 0) { 42385722Sjulian etfp->packets_out++; 42485722Sjulian } 42585722Sjulian } 42685722Sjulian return (error); 42785722Sjulian} 42885722Sjulian 42985722Sjulian/* 43085722Sjulian * Do local shutdown processing.. 43185722Sjulian * All our links and the name have already been removed. 43285722Sjulian */ 43385722Sjulianstatic int 43485722Sjulianng_etf_shutdown(node_p node) 43585722Sjulian{ 43685722Sjulian const etf_p privdata = NG_NODE_PRIVATE(node); 43785722Sjulian 43885722Sjulian NG_NODE_SET_PRIVATE(node, NULL); 43985722Sjulian NG_NODE_UNREF(privdata->node); 440184205Sdes free(privdata, M_NETGRAPH_ETF); 44185722Sjulian return (0); 44285722Sjulian} 44385722Sjulian 44485722Sjulian/* 44585722Sjulian * Hook disconnection 44685722Sjulian * 44785722Sjulian * For this type, removal of the last link destroys the node 44885722Sjulian */ 44985722Sjulianstatic int 45085722Sjulianng_etf_disconnect(hook_p hook) 45185722Sjulian{ 45285722Sjulian const etf_p etfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); 45385722Sjulian int i; 454123670Sru struct filter *fil1, *fil2; 45585722Sjulian 45685722Sjulian /* purge any rules that refer to this filter */ 45785722Sjulian for (i = 0; i < HASHSIZE; i++) { 458123670Sru fil1 = LIST_FIRST(&etfp->hashtable[i]); 459123670Sru while (fil1 != NULL) { 460123670Sru fil2 = LIST_NEXT(fil1, next); 461123670Sru if (fil1->match_hook == hook) { 462123670Sru LIST_REMOVE(fil1, next); 463184205Sdes free(fil1, M_NETGRAPH_ETF); 46485722Sjulian } 465123670Sru fil1 = fil2; 46685722Sjulian } 46785722Sjulian } 46885722Sjulian 46985722Sjulian /* If it's not one of the special hooks, then free it */ 47085722Sjulian if (hook == etfp->downstream_hook.hook) { 47185722Sjulian etfp->downstream_hook.hook = NULL; 47285722Sjulian } else if (hook == etfp->nomatch_hook.hook) { 47385722Sjulian etfp->nomatch_hook.hook = NULL; 47485722Sjulian } else { 47585722Sjulian if (NG_HOOK_PRIVATE(hook)) /* Paranoia */ 476184205Sdes free(NG_HOOK_PRIVATE(hook), M_NETGRAPH_ETF); 47785722Sjulian } 47885722Sjulian 47985722Sjulian NG_HOOK_SET_PRIVATE(hook, NULL); 48085722Sjulian 48185722Sjulian if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) 48285722Sjulian && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) /* already shutting down? */ 48385722Sjulian ng_rmnode_self(NG_HOOK_NODE(hook)); 48485722Sjulian return (0); 48585722Sjulian} 48685722Sjulian 487