diff --git a/sys/netgraph/netgraph.h b/sys/netgraph/netgraph.h index d0be5a88b088..25863784840c 100644 --- a/sys/netgraph/netgraph.h +++ b/sys/netgraph/netgraph.h @@ -1,1223 +1,1217 @@ /* * netgraph.h */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Julian Elischer * * $FreeBSD$ * $Whistle: netgraph.h,v 1.29 1999/11/01 07:56:13 julian Exp $ */ #ifndef _NETGRAPH_NETGRAPH_H_ #define _NETGRAPH_NETGRAPH_H_ #ifndef _KERNEL #error "This file should not be included in user level programs" #endif #include #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_netgraph.h" #include "opt_kdb.h" #endif /* debugging options */ #define NG_SEPARATE_MALLOC /* make modules use their own malloc types */ /* * This defines the in-kernel binary interface version. * It is possible to change this but leave the external message * API the same. Each type also has it's own cookies for versioning as well. * Change it for NETGRAPH_DEBUG version so we cannot mix debug and non debug * modules. */ #define _NG_ABI_VERSION 12 #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ #define NG_ABI_VERSION (_NG_ABI_VERSION + 0x10000) #else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NG_ABI_VERSION _NG_ABI_VERSION #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ /* * Forward references for the basic structures so we can * define the typedefs and use them in the structures themselves. */ struct ng_hook ; struct ng_node ; struct ng_item ; typedef struct ng_item *item_p; typedef struct ng_node *node_p; typedef struct ng_hook *hook_p; typedef struct ng_item const *item_cp; typedef struct ng_hook const *hook_cp; #ifdef NETGRAPH_DEBUG typedef struct ng_node *node_cp; /* annotated during debug */ #else /* NETGRAPH_DEBUG */ typedef struct ng_node const *node_cp; #endif /* NETGRAPH_DEBUG */ /* node method definitions */ typedef int ng_constructor_t(node_p node); typedef int ng_close_t(node_p node); typedef int ng_shutdown_t(node_p node); typedef int ng_newhook_t(node_p node, hook_p hook, const char *name); typedef hook_p ng_findhook_t(node_p node, const char *name); typedef int ng_connect_t(hook_p hook); typedef int ng_rcvmsg_t(node_p node, item_p item, hook_p lasthook); typedef int ng_rcvdata_t(hook_p hook, item_p item); typedef int ng_disconnect_t(hook_p hook); typedef int ng_rcvitem (node_p node, hook_p hook, item_p item); /*********************************************************************** ***************** Hook Structure and Methods ************************** *********************************************************************** * * Structure of a hook */ struct ng_hook { char hk_name[NG_HOOKSIZ]; /* what this node knows this link as */ void *hk_private; /* node dependent ID for this hook */ int hk_flags; /* info about this hook/link */ int hk_type; /* tbd: hook data link type */ struct ng_hook *hk_peer; /* the other end of this link */ struct ng_node *hk_node; /* The node this hook is attached to */ LIST_ENTRY(ng_hook) hk_hooks; /* linked list of all hooks on node */ ng_rcvmsg_t *hk_rcvmsg; /* control messages come here */ ng_rcvdata_t *hk_rcvdata; /* data comes here */ int hk_refs; /* dont actually free this till 0 */ #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ #define HK_MAGIC 0x78573011 int hk_magic; char *lastfile; int lastline; SLIST_ENTRY(ng_hook) hk_all; /* all existing items */ #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ }; /* Flags for a hook */ #define HK_INVALID 0x0001 /* don't trust it! */ #define HK_QUEUE 0x0002 /* queue for later delivery */ #define HK_FORCE_WRITER 0x0004 /* Incoming data queued as a writer */ #define HK_DEAD 0x0008 /* This is the dead hook.. don't free */ #define HK_HI_STACK 0x0010 /* Hook has hi stack usage */ #define HK_TO_INBOUND 0x0020 /* Hook on ntw. stack inbound path. */ /* * Public Methods for hook * If you can't do it with these you probably shouldn;t be doing it. */ void ng_unref_hook(hook_p hook); /* don't move this */ #define _NG_HOOK_REF(hook) refcount_acquire(&(hook)->hk_refs) #define _NG_HOOK_NAME(hook) ((hook)->hk_name) #define _NG_HOOK_UNREF(hook) ng_unref_hook(hook) #define _NG_HOOK_SET_PRIVATE(hook, val) do {(hook)->hk_private = val;} while (0) #define _NG_HOOK_SET_RCVMSG(hook, val) do {(hook)->hk_rcvmsg = val;} while (0) #define _NG_HOOK_SET_RCVDATA(hook, val) do {(hook)->hk_rcvdata = val;} while (0) #define _NG_HOOK_PRIVATE(hook) ((hook)->hk_private) #define _NG_HOOK_NOT_VALID(hook) ((hook)->hk_flags & HK_INVALID) #define _NG_HOOK_IS_VALID(hook) (!((hook)->hk_flags & HK_INVALID)) #define _NG_HOOK_NODE(hook) ((hook)->hk_node) /* only rvalue! */ #define _NG_HOOK_PEER(hook) ((hook)->hk_peer) /* only rvalue! */ #define _NG_HOOK_FORCE_WRITER(hook) \ do { hook->hk_flags |= HK_FORCE_WRITER; } while (0) #define _NG_HOOK_FORCE_QUEUE(hook) do { hook->hk_flags |= HK_QUEUE; } while (0) #define _NG_HOOK_SET_TO_INBOUND(hook) \ do { hook->hk_flags |= HK_TO_INBOUND; } while (0) #define _NG_HOOK_HI_STACK(hook) do { hook->hk_flags |= HK_HI_STACK; } while (0) /* Some shortcuts */ #define NG_PEER_NODE(hook) NG_HOOK_NODE(NG_HOOK_PEER(hook)) #define NG_PEER_HOOK_NAME(hook) NG_HOOK_NAME(NG_HOOK_PEER(hook)) #define NG_PEER_NODE_NAME(hook) NG_NODE_NAME(NG_PEER_NODE(hook)) #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ #define _NN_ __FILE__,__LINE__ void dumphook (hook_p hook, char *file, int line); static __inline void _chkhook(hook_p hook, char *file, int line); static __inline void _ng_hook_ref(hook_p hook, char * file, int line); static __inline char * _ng_hook_name(hook_p hook, char * file, int line); static __inline void _ng_hook_unref(hook_p hook, char * file, int line); static __inline void _ng_hook_set_private(hook_p hook, void * val, char * file, int line); static __inline void _ng_hook_set_rcvmsg(hook_p hook, ng_rcvmsg_t *val, char * file, int line); static __inline void _ng_hook_set_rcvdata(hook_p hook, ng_rcvdata_t *val, char * file, int line); static __inline void * _ng_hook_private(hook_p hook, char * file, int line); static __inline int _ng_hook_not_valid(hook_p hook, char * file, int line); static __inline int _ng_hook_is_valid(hook_p hook, char * file, int line); static __inline node_p _ng_hook_node(hook_p hook, char * file, int line); static __inline hook_p _ng_hook_peer(hook_p hook, char * file, int line); static __inline void _ng_hook_force_writer(hook_p hook, char * file, int line); static __inline void _ng_hook_force_queue(hook_p hook, char * file, int line); static __inline void _ng_hook_set_to_inbound(hook_p hook, char * file, int line); static __inline void _chkhook(hook_p hook, char *file, int line) { if (hook->hk_magic != HK_MAGIC) { printf("Accessing freed "); dumphook(hook, file, line); } hook->lastline = line; hook->lastfile = file; } static __inline void _ng_hook_ref(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_REF(hook); } static __inline char * _ng_hook_name(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_NAME(hook)); } static __inline void _ng_hook_unref(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_UNREF(hook); } static __inline void _ng_hook_set_private(hook_p hook, void *val, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_SET_PRIVATE(hook, val); } static __inline void _ng_hook_set_rcvmsg(hook_p hook, ng_rcvmsg_t *val, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_SET_RCVMSG(hook, val); } static __inline void _ng_hook_set_rcvdata(hook_p hook, ng_rcvdata_t *val, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_SET_RCVDATA(hook, val); } static __inline void * _ng_hook_private(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_PRIVATE(hook)); } static __inline int _ng_hook_not_valid(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_NOT_VALID(hook)); } static __inline int _ng_hook_is_valid(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_IS_VALID(hook)); } static __inline node_p _ng_hook_node(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_NODE(hook)); } static __inline hook_p _ng_hook_peer(hook_p hook, char * file, int line) { _chkhook(hook, file, line); return (_NG_HOOK_PEER(hook)); } static __inline void _ng_hook_force_writer(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_FORCE_WRITER(hook); } static __inline void _ng_hook_force_queue(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_FORCE_QUEUE(hook); } static __inline void _ng_hook_set_to_inbound(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_SET_TO_INBOUND(hook); } static __inline void _ng_hook_hi_stack(hook_p hook, char * file, int line) { _chkhook(hook, file, line); _NG_HOOK_HI_STACK(hook); } #define NG_HOOK_REF(hook) _ng_hook_ref(hook, _NN_) #define NG_HOOK_NAME(hook) _ng_hook_name(hook, _NN_) #define NG_HOOK_UNREF(hook) _ng_hook_unref(hook, _NN_) #define NG_HOOK_SET_PRIVATE(hook, val) _ng_hook_set_private(hook, val, _NN_) #define NG_HOOK_SET_RCVMSG(hook, val) _ng_hook_set_rcvmsg(hook, val, _NN_) #define NG_HOOK_SET_RCVDATA(hook, val) _ng_hook_set_rcvdata(hook, val, _NN_) #define NG_HOOK_PRIVATE(hook) _ng_hook_private(hook, _NN_) #define NG_HOOK_NOT_VALID(hook) _ng_hook_not_valid(hook, _NN_) #define NG_HOOK_IS_VALID(hook) _ng_hook_is_valid(hook, _NN_) #define NG_HOOK_NODE(hook) _ng_hook_node(hook, _NN_) #define NG_HOOK_PEER(hook) _ng_hook_peer(hook, _NN_) #define NG_HOOK_FORCE_WRITER(hook) _ng_hook_force_writer(hook, _NN_) #define NG_HOOK_FORCE_QUEUE(hook) _ng_hook_force_queue(hook, _NN_) #define NG_HOOK_SET_TO_INBOUND(hook) _ng_hook_set_to_inbound(hook, _NN_) #define NG_HOOK_HI_STACK(hook) _ng_hook_hi_stack(hook, _NN_) #else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NG_HOOK_REF(hook) _NG_HOOK_REF(hook) #define NG_HOOK_NAME(hook) _NG_HOOK_NAME(hook) #define NG_HOOK_UNREF(hook) _NG_HOOK_UNREF(hook) #define NG_HOOK_SET_PRIVATE(hook, val) _NG_HOOK_SET_PRIVATE(hook, val) #define NG_HOOK_SET_RCVMSG(hook, val) _NG_HOOK_SET_RCVMSG(hook, val) #define NG_HOOK_SET_RCVDATA(hook, val) _NG_HOOK_SET_RCVDATA(hook, val) #define NG_HOOK_PRIVATE(hook) _NG_HOOK_PRIVATE(hook) #define NG_HOOK_NOT_VALID(hook) _NG_HOOK_NOT_VALID(hook) #define NG_HOOK_IS_VALID(hook) _NG_HOOK_IS_VALID(hook) #define NG_HOOK_NODE(hook) _NG_HOOK_NODE(hook) #define NG_HOOK_PEER(hook) _NG_HOOK_PEER(hook) #define NG_HOOK_FORCE_WRITER(hook) _NG_HOOK_FORCE_WRITER(hook) #define NG_HOOK_FORCE_QUEUE(hook) _NG_HOOK_FORCE_QUEUE(hook) #define NG_HOOK_SET_TO_INBOUND(hook) _NG_HOOK_SET_TO_INBOUND(hook) #define NG_HOOK_HI_STACK(hook) _NG_HOOK_HI_STACK(hook) #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ /*********************************************************************** ***************** Node Structure and Methods ************************** *********************************************************************** * Structure of a node * including the eembedded queue structure. * * The structure for queueing Netgraph request items * embedded in the node structure */ struct ng_queue { u_int q_flags; /* Current r/w/q lock flags */ u_int q_flags2; /* Other queue flags */ struct mtx q_mtx; STAILQ_ENTRY(ng_node) q_work; /* nodes with work to do */ STAILQ_HEAD(, ng_item) queue; /* actually items queue */ }; struct ng_node { char nd_name[NG_NODESIZ]; /* optional globally unique name */ struct ng_type *nd_type; /* the installed 'type' */ int nd_flags; /* see below for bit definitions */ int nd_numhooks; /* number of hooks */ void *nd_private; /* node type dependent node ID */ ng_ID_t nd_ID; /* Unique per node */ LIST_HEAD(hooks, ng_hook) nd_hooks; /* linked list of node hooks */ LIST_ENTRY(ng_node) nd_nodes; /* name hash collision list */ LIST_ENTRY(ng_node) nd_idnodes; /* ID hash collision list */ struct ng_queue nd_input_queue; /* input queue for locking */ int nd_refs; /* # of references to this node */ struct vnet *nd_vnet; /* network stack instance */ #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ #define ND_MAGIC 0x59264837 int nd_magic; char *lastfile; int lastline; SLIST_ENTRY(ng_node) nd_all; /* all existing nodes */ #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ }; /* Flags for a node */ #define NGF_INVALID 0x00000001 /* free when refs go to 0 */ #define NG_INVALID NGF_INVALID /* compat for old code */ #define NGF_FORCE_WRITER 0x00000004 /* Never multithread this node */ #define NG_FORCE_WRITER NGF_FORCE_WRITER /* compat for old code */ #define NGF_CLOSING 0x00000008 /* ng_rmnode() at work */ #define NG_CLOSING NGF_CLOSING /* compat for old code */ #define NGF_REALLY_DIE 0x00000010 /* "persistent" node is unloading */ #define NG_REALLY_DIE NGF_REALLY_DIE /* compat for old code */ #define NGF_HI_STACK 0x00000020 /* node has hi stack usage */ #define NGF_TYPE1 0x10000000 /* reserved for type specific storage */ #define NGF_TYPE2 0x20000000 /* reserved for type specific storage */ #define NGF_TYPE3 0x40000000 /* reserved for type specific storage */ #define NGF_TYPE4 0x80000000 /* reserved for type specific storage */ /* * Public methods for nodes. * If you can't do it with these you probably shouldn't be doing it. */ void ng_unref_node(node_p node); /* don't move this */ #define _NG_NODE_NAME(node) ((node)->nd_name + 0) #define _NG_NODE_HAS_NAME(node) ((node)->nd_name[0] + 0) #define _NG_NODE_ID(node) ((node)->nd_ID + 0) #define _NG_NODE_REF(node) refcount_acquire(&(node)->nd_refs) #define _NG_NODE_UNREF(node) ng_unref_node(node) #define _NG_NODE_SET_PRIVATE(node, val) do {(node)->nd_private = val;} while (0) #define _NG_NODE_PRIVATE(node) ((node)->nd_private) #define _NG_NODE_IS_VALID(node) (!((node)->nd_flags & NGF_INVALID)) #define _NG_NODE_NOT_VALID(node) ((node)->nd_flags & NGF_INVALID) #define _NG_NODE_NUMHOOKS(node) ((node)->nd_numhooks + 0) /* rvalue */ #define _NG_NODE_FORCE_WRITER(node) \ do{ node->nd_flags |= NGF_FORCE_WRITER; }while (0) #define _NG_NODE_HI_STACK(node) \ do{ node->nd_flags |= NGF_HI_STACK; }while (0) #define _NG_NODE_REALLY_DIE(node) \ do{ node->nd_flags |= (NGF_REALLY_DIE|NGF_INVALID); }while (0) #define _NG_NODE_REVIVE(node) \ do { node->nd_flags &= ~NGF_INVALID; } while (0) /* * The hook iterator. * This macro will call a function of type ng_fn_eachhook for each * hook attached to the node. If the function returns 0, then the * iterator will stop and return a pointer to the hook that returned 0. */ typedef int ng_fn_eachhook(hook_p hook, void* arg); -#define _NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) \ +#define _NG_NODE_FOREACH_HOOK(node, fn, arg) \ do { \ hook_p _hook; \ - (rethook) = NULL; \ LIST_FOREACH(_hook, &((node)->nd_hooks), hk_hooks) { \ if ((fn)(_hook, arg) == 0) { \ - (rethook) = _hook; \ break; \ } \ } \ } while (0) #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ void dumpnode(node_p node, char *file, int line); static __inline void _chknode(node_p node, char *file, int line); static __inline char * _ng_node_name(node_p node, char *file, int line); static __inline int _ng_node_has_name(node_p node, char *file, int line); static __inline ng_ID_t _ng_node_id(node_p node, char *file, int line); static __inline void _ng_node_ref(node_p node, char *file, int line); static __inline void _ng_node_unref(node_p node, char *file, int line); static __inline void _ng_node_set_private(node_p node, void * val, char *file, int line); static __inline void * _ng_node_private(node_p node, char *file, int line); static __inline int _ng_node_is_valid(node_p node, char *file, int line); static __inline int _ng_node_not_valid(node_p node, char *file, int line); static __inline int _ng_node_numhooks(node_p node, char *file, int line); static __inline void _ng_node_force_writer(node_p node, char *file, int line); -static __inline hook_p _ng_node_foreach_hook(node_p node, +static __inline void _ng_node_foreach_hook(node_p node, ng_fn_eachhook *fn, void *arg, char *file, int line); static __inline void _ng_node_revive(node_p node, char *file, int line); static __inline void _chknode(node_p node, char *file, int line) { if (node->nd_magic != ND_MAGIC) { printf("Accessing freed "); dumpnode(node, file, line); } node->lastline = line; node->lastfile = file; } static __inline char * _ng_node_name(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_NAME(node)); } static __inline int _ng_node_has_name(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_HAS_NAME(node)); } static __inline ng_ID_t _ng_node_id(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_ID(node)); } static __inline void _ng_node_ref(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_REF(node); } static __inline void _ng_node_unref(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_UNREF(node); } static __inline void _ng_node_set_private(node_p node, void * val, char *file, int line) { _chknode(node, file, line); _NG_NODE_SET_PRIVATE(node, val); } static __inline void * _ng_node_private(node_p node, char *file, int line) { _chknode(node, file, line); return (_NG_NODE_PRIVATE(node)); } static __inline int _ng_node_is_valid(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_IS_VALID(node)); } static __inline int _ng_node_not_valid(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_NOT_VALID(node)); } static __inline int _ng_node_numhooks(node_p node, char *file, int line) { _chknode(node, file, line); return(_NG_NODE_NUMHOOKS(node)); } static __inline void _ng_node_force_writer(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_FORCE_WRITER(node); } static __inline void _ng_node_hi_stack(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_HI_STACK(node); } static __inline void _ng_node_really_die(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_REALLY_DIE(node); } static __inline void _ng_node_revive(node_p node, char *file, int line) { _chknode(node, file, line); _NG_NODE_REVIVE(node); } -static __inline hook_p +static __inline void _ng_node_foreach_hook(node_p node, ng_fn_eachhook *fn, void *arg, char *file, int line) { - hook_p hook; _chknode(node, file, line); - _NG_NODE_FOREACH_HOOK(node, fn, arg, hook); - return (hook); + _NG_NODE_FOREACH_HOOK(node, fn, arg); } #define NG_NODE_NAME(node) _ng_node_name(node, _NN_) #define NG_NODE_HAS_NAME(node) _ng_node_has_name(node, _NN_) #define NG_NODE_ID(node) _ng_node_id(node, _NN_) #define NG_NODE_REF(node) _ng_node_ref(node, _NN_) #define NG_NODE_UNREF(node) _ng_node_unref(node, _NN_) #define NG_NODE_SET_PRIVATE(node, val) _ng_node_set_private(node, val, _NN_) #define NG_NODE_PRIVATE(node) _ng_node_private(node, _NN_) #define NG_NODE_IS_VALID(node) _ng_node_is_valid(node, _NN_) #define NG_NODE_NOT_VALID(node) _ng_node_not_valid(node, _NN_) #define NG_NODE_FORCE_WRITER(node) _ng_node_force_writer(node, _NN_) #define NG_NODE_HI_STACK(node) _ng_node_hi_stack(node, _NN_) #define NG_NODE_REALLY_DIE(node) _ng_node_really_die(node, _NN_) #define NG_NODE_NUMHOOKS(node) _ng_node_numhooks(node, _NN_) #define NG_NODE_REVIVE(node) _ng_node_revive(node, _NN_) -#define NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) \ - do { \ - rethook = _ng_node_foreach_hook(node, fn, (void *)arg, _NN_); \ - } while (0) +#define NG_NODE_FOREACH_HOOK(node, fn, arg) \ + _ng_node_foreach_hook(node, fn, (void *)arg, _NN_) #else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NG_NODE_NAME(node) _NG_NODE_NAME(node) #define NG_NODE_HAS_NAME(node) _NG_NODE_HAS_NAME(node) #define NG_NODE_ID(node) _NG_NODE_ID(node) #define NG_NODE_REF(node) _NG_NODE_REF(node) #define NG_NODE_UNREF(node) _NG_NODE_UNREF(node) #define NG_NODE_SET_PRIVATE(node, val) _NG_NODE_SET_PRIVATE(node, val) #define NG_NODE_PRIVATE(node) _NG_NODE_PRIVATE(node) #define NG_NODE_IS_VALID(node) _NG_NODE_IS_VALID(node) #define NG_NODE_NOT_VALID(node) _NG_NODE_NOT_VALID(node) #define NG_NODE_FORCE_WRITER(node) _NG_NODE_FORCE_WRITER(node) #define NG_NODE_HI_STACK(node) _NG_NODE_HI_STACK(node) #define NG_NODE_REALLY_DIE(node) _NG_NODE_REALLY_DIE(node) #define NG_NODE_NUMHOOKS(node) _NG_NODE_NUMHOOKS(node) #define NG_NODE_REVIVE(node) _NG_NODE_REVIVE(node) -#define NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) \ - _NG_NODE_FOREACH_HOOK(node, fn, arg, rethook) +#define NG_NODE_FOREACH_HOOK(node, fn, arg) \ + _NG_NODE_FOREACH_HOOK(node, fn, arg) #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ /*********************************************************************** ************* Node Queue and Item Structures and Methods ************** *********************************************************************** * */ typedef void ng_item_fn(node_p node, hook_p hook, void *arg1, int arg2); typedef int ng_item_fn2(node_p node, struct ng_item *item, hook_p hook); typedef void ng_apply_t(void *context, int error); struct ng_apply_info { ng_apply_t *apply; void *context; int refs; int error; }; struct ng_item { u_long el_flags; STAILQ_ENTRY(ng_item) el_next; node_p el_dest; /* The node it will be applied against (or NULL) */ hook_p el_hook; /* Entering hook. Optional in Control messages */ union { struct mbuf *da_m; struct { struct ng_mesg *msg_msg; ng_ID_t msg_retaddr; } msg; struct { union { ng_item_fn *fn_fn; ng_item_fn2 *fn_fn2; } fn_fn; void *fn_arg1; int fn_arg2; } fn; } body; /* * Optional callback called when item is being applied, * and its context. */ struct ng_apply_info *apply; u_int depth; #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ char *lastfile; int lastline; TAILQ_ENTRY(ng_item) all; /* all existing items */ #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ }; #define NGQF_TYPE 0x03 /* MASK of content definition */ #define NGQF_MESG 0x00 /* the queue element is a message */ #define NGQF_DATA 0x01 /* the queue element is data */ #define NGQF_FN 0x02 /* the queue element is a function */ #define NGQF_FN2 0x03 /* the queue element is a new function */ #define NGQF_RW 0x04 /* MASK for wanted queue mode */ #define NGQF_READER 0x04 /* wants to be a reader */ #define NGQF_WRITER 0x00 /* wants to be a writer */ #define NGQF_QMODE 0x08 /* MASK for how it was queued */ #define NGQF_QREADER 0x08 /* was queued as a reader */ #define NGQF_QWRITER 0x00 /* was queued as a writer */ /* * Get the mbuf (etc) out of an item. * Sets the value in the item to NULL in case we need to call NG_FREE_ITEM() * with it, (to avoid freeing the things twice). * If you don't want to zero out the item then realise that the * item still owns it. * Retaddr is different. There are no references on that. It's just a number. * The debug versions must be either all used everywhere or not at all. */ #define _NGI_M(i) ((i)->body.da_m) #define _NGI_MSG(i) ((i)->body.msg.msg_msg) #define _NGI_RETADDR(i) ((i)->body.msg.msg_retaddr) #define _NGI_FN(i) ((i)->body.fn.fn_fn.fn_fn) #define _NGI_FN2(i) ((i)->body.fn.fn_fn.fn_fn2) #define _NGI_ARG1(i) ((i)->body.fn.fn_arg1) #define _NGI_ARG2(i) ((i)->body.fn.fn_arg2) #define _NGI_NODE(i) ((i)->el_dest) #define _NGI_HOOK(i) ((i)->el_hook) #define _NGI_SET_HOOK(i,h) do { _NGI_HOOK(i) = h; h = NULL;} while (0) #define _NGI_CLR_HOOK(i) do { \ hook_p _hook = _NGI_HOOK(i); \ if (_hook) { \ _NG_HOOK_UNREF(_hook); \ _NGI_HOOK(i) = NULL; \ } \ } while (0) #define _NGI_SET_NODE(i,n) do { _NGI_NODE(i) = n; n = NULL;} while (0) #define _NGI_CLR_NODE(i) do { \ node_p _node = _NGI_NODE(i); \ if (_node) { \ _NG_NODE_UNREF(_node); \ _NGI_NODE(i) = NULL; \ } \ } while (0) #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ void dumpitem(item_p item, char *file, int line); static __inline void _ngi_check(item_p item, char *file, int line) ; static __inline struct mbuf ** _ngi_m(item_p item, char *file, int line) ; static __inline ng_ID_t * _ngi_retaddr(item_p item, char *file, int line); static __inline struct ng_mesg ** _ngi_msg(item_p item, char *file, int line) ; static __inline ng_item_fn ** _ngi_fn(item_p item, char *file, int line) ; static __inline ng_item_fn2 ** _ngi_fn2(item_p item, char *file, int line) ; static __inline void ** _ngi_arg1(item_p item, char *file, int line) ; static __inline int * _ngi_arg2(item_p item, char *file, int line) ; static __inline node_p _ngi_node(item_p item, char *file, int line); static __inline hook_p _ngi_hook(item_p item, char *file, int line); static __inline void _ngi_check(item_p item, char *file, int line) { (item)->lastline = line; (item)->lastfile = file; } static __inline struct mbuf ** _ngi_m(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_M(item)); } static __inline struct ng_mesg ** _ngi_msg(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_MSG(item)); } static __inline ng_ID_t * _ngi_retaddr(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_RETADDR(item)); } static __inline ng_item_fn ** _ngi_fn(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_FN(item)); } static __inline ng_item_fn2 ** _ngi_fn2(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_FN2(item)); } static __inline void ** _ngi_arg1(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_ARG1(item)); } static __inline int * _ngi_arg2(item_p item, char *file, int line) { _ngi_check(item, file, line); return (&_NGI_ARG2(item)); } static __inline node_p _ngi_node(item_p item, char *file, int line) { _ngi_check(item, file, line); return (_NGI_NODE(item)); } static __inline hook_p _ngi_hook(item_p item, char *file, int line) { _ngi_check(item, file, line); return (_NGI_HOOK(item)); } #define NGI_M(i) (*_ngi_m(i, _NN_)) #define NGI_MSG(i) (*_ngi_msg(i, _NN_)) #define NGI_RETADDR(i) (*_ngi_retaddr(i, _NN_)) #define NGI_FN(i) (*_ngi_fn(i, _NN_)) #define NGI_FN2(i) (*_ngi_fn2(i, _NN_)) #define NGI_ARG1(i) (*_ngi_arg1(i, _NN_)) #define NGI_ARG2(i) (*_ngi_arg2(i, _NN_)) #define NGI_HOOK(i) _ngi_hook(i, _NN_) #define NGI_NODE(i) _ngi_node(i, _NN_) #define NGI_SET_HOOK(i,h) \ do { _ngi_check(i, _NN_); _NGI_SET_HOOK(i, h); } while (0) #define NGI_CLR_HOOK(i) \ do { _ngi_check(i, _NN_); _NGI_CLR_HOOK(i); } while (0) #define NGI_SET_NODE(i,n) \ do { _ngi_check(i, _NN_); _NGI_SET_NODE(i, n); } while (0) #define NGI_CLR_NODE(i) \ do { _ngi_check(i, _NN_); _NGI_CLR_NODE(i); } while (0) #define NG_FREE_ITEM(item) \ do { \ _ngi_check(item, _NN_); \ ng_free_item((item)); \ } while (0) #define SAVE_LINE(item) \ do { \ (item)->lastline = __LINE__; \ (item)->lastfile = __FILE__; \ } while (0) #else /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NGI_M(i) _NGI_M(i) #define NGI_MSG(i) _NGI_MSG(i) #define NGI_RETADDR(i) _NGI_RETADDR(i) #define NGI_FN(i) _NGI_FN(i) #define NGI_FN2(i) _NGI_FN2(i) #define NGI_ARG1(i) _NGI_ARG1(i) #define NGI_ARG2(i) _NGI_ARG2(i) #define NGI_NODE(i) _NGI_NODE(i) #define NGI_HOOK(i) _NGI_HOOK(i) #define NGI_SET_HOOK(i,h) _NGI_SET_HOOK(i,h) #define NGI_CLR_HOOK(i) _NGI_CLR_HOOK(i) #define NGI_SET_NODE(i,n) _NGI_SET_NODE(i,n) #define NGI_CLR_NODE(i) _NGI_CLR_NODE(i) #define NG_FREE_ITEM(item) ng_free_item((item)) #define SAVE_LINE(item) do {} while (0) #endif /* NETGRAPH_DEBUG */ /*----------------------------------------------*/ #define NGI_GET_M(i,m) \ do { \ (m) = NGI_M(i); \ _NGI_M(i) = NULL; \ } while (0) #define NGI_GET_MSG(i,m) \ do { \ (m) = NGI_MSG(i); \ _NGI_MSG(i) = NULL; \ } while (0) #define NGI_GET_NODE(i,n) /* YOU NOW HAVE THE REFERENCE */ \ do { \ (n) = NGI_NODE(i); \ _NGI_NODE(i) = NULL; \ } while (0) #define NGI_GET_HOOK(i,h) \ do { \ (h) = NGI_HOOK(i); \ _NGI_HOOK(i) = NULL; \ } while (0) #define NGI_SET_WRITER(i) ((i)->el_flags &= ~NGQF_QMODE) #define NGI_SET_READER(i) ((i)->el_flags |= NGQF_QREADER) #define NGI_QUEUED_READER(i) ((i)->el_flags & NGQF_QREADER) #define NGI_QUEUED_WRITER(i) (((i)->el_flags & NGQF_QMODE) == NGQF_QWRITER) /********************************************************************** * Data macros. Send, manipulate and free. **********************************************************************/ /* * Assuming the data is already ok, just set the new address and send */ #define NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, flags) \ do { \ (error) = \ ng_address_hook(NULL, (item), (hook), NG_NOFLAGS); \ if (error == 0) { \ SAVE_LINE(item); \ (error) = ng_snd_item((item), (flags)); \ } \ (item) = NULL; \ } while (0) #define NG_FWD_ITEM_HOOK(error, item, hook) \ NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, NG_NOFLAGS) /* * Forward a data packet. Mbuf pointer is updated to new value. We * presume you dealt with the old one when you update it to the new one * (or it maybe the old one). We got a packet and possibly had to modify * the mbuf. You should probably use NGI_GET_M() if you are going to use * this too. */ #define NG_FWD_NEW_DATA_FLAGS(error, item, hook, m, flags) \ do { \ NGI_M(item) = (m); \ (m) = NULL; \ NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, flags); \ } while (0) #define NG_FWD_NEW_DATA(error, item, hook, m) \ NG_FWD_NEW_DATA_FLAGS(error, item, hook, m, NG_NOFLAGS) /* Send a previously unpackaged mbuf. XXX: This should be called * NG_SEND_DATA in future, but this name is kept for compatibility * reasons. */ #define NG_SEND_DATA_FLAGS(error, hook, m, flags) \ do { \ item_p _item; \ if ((_item = ng_package_data((m), flags))) { \ NG_FWD_ITEM_HOOK_FLAGS(error, _item, hook, flags);\ } else { \ (error) = ENOMEM; \ } \ (m) = NULL; \ } while (0) #define NG_SEND_DATA_ONLY(error, hook, m) \ NG_SEND_DATA_FLAGS(error, hook, m, NG_NOFLAGS) /* NG_SEND_DATA() compat for meta-data times */ #define NG_SEND_DATA(error, hook, m, x) \ NG_SEND_DATA_FLAGS(error, hook, m, NG_NOFLAGS) #define NG_FREE_MSG(msg) \ do { \ if ((msg)) { \ free((msg), M_NETGRAPH_MSG); \ (msg) = NULL; \ } \ } while (0) #define NG_FREE_M(m) \ do { \ if ((m)) { \ m_freem((m)); \ (m) = NULL; \ } \ } while (0) /***************************************** * Message macros *****************************************/ #define NG_SEND_MSG_HOOK(error, here, msg, hook, retaddr) \ do { \ item_p _item; \ if ((_item = ng_package_msg(msg, NG_NOFLAGS)) == NULL) {\ (msg) = NULL; \ (error) = ENOMEM; \ break; \ } \ if (((error) = ng_address_hook((here), (_item), \ (hook), (retaddr))) == 0) { \ SAVE_LINE(_item); \ (error) = ng_snd_item((_item), 0); \ } \ (msg) = NULL; \ } while (0) #define NG_SEND_MSG_PATH(error, here, msg, path, retaddr) \ do { \ item_p _item; \ if ((_item = ng_package_msg(msg, NG_NOFLAGS)) == NULL) {\ (msg) = NULL; \ (error) = ENOMEM; \ break; \ } \ if (((error) = ng_address_path((here), (_item), \ (path), (retaddr))) == 0) { \ SAVE_LINE(_item); \ (error) = ng_snd_item((_item), 0); \ } \ (msg) = NULL; \ } while (0) #define NG_SEND_MSG_ID(error, here, msg, ID, retaddr) \ do { \ item_p _item; \ if ((_item = ng_package_msg(msg, NG_NOFLAGS)) == NULL) {\ (msg) = NULL; \ (error) = ENOMEM; \ break; \ } \ if (((error) = ng_address_ID((here), (_item), \ (ID), (retaddr))) == 0) { \ SAVE_LINE(_item); \ (error) = ng_snd_item((_item), 0); \ } \ (msg) = NULL; \ } while (0) /* * Redirect the message to the next hop using the given hook. * ng_retarget_msg() frees the item if there is an error * and returns an error code. It returns 0 on success. */ #define NG_FWD_MSG_HOOK(error, here, item, hook, retaddr) \ do { \ if (((error) = ng_address_hook((here), (item), \ (hook), (retaddr))) == 0) { \ SAVE_LINE(item); \ (error) = ng_snd_item((item), 0); \ } \ (item) = NULL; \ } while (0) /* * Send a queue item back to it's originator with a response message. * Assume original message was removed and freed separatly. */ #define NG_RESPOND_MSG(error, here, item, resp) \ do { \ if (resp) { \ ng_ID_t _dest = NGI_RETADDR(item); \ NGI_RETADDR(item) = 0; \ NGI_MSG(item) = resp; \ if ((error = ng_address_ID((here), (item), \ _dest, 0)) == 0) { \ SAVE_LINE(item); \ (error) = ng_snd_item((item), NG_QUEUE);\ } \ } else \ NG_FREE_ITEM(item); \ (item) = NULL; \ } while (0) /*********************************************************************** ******** Structures Definitions and Macros for defining a node ******* *********************************************************************** * * Here we define the structures needed to actually define a new node * type. */ /* * Command list -- each node type specifies the command that it knows * how to convert between ASCII and binary using an array of these. * The last element in the array must be a terminator with cookie=0. */ struct ng_cmdlist { u_int32_t cookie; /* command typecookie */ int cmd; /* command number */ const char *name; /* command name */ const struct ng_parse_type *mesgType; /* args if !NGF_RESP */ const struct ng_parse_type *respType; /* args if NGF_RESP */ }; /* * Structure of a node type * If data is sent to the "rcvdata()" entrypoint then the system * may decide to defer it until later by queing it with the normal netgraph * input queuing system. This is decidde by the HK_QUEUE flag being set in * the flags word of the peer (receiving) hook. The dequeuing mechanism will * ensure it is not requeued again. * Note the input queueing system is to allow modules * to 'release the stack' or to pass data across spl layers. * The data will be redelivered as soon as the NETISR code runs * which may be almost immediately. A node may also do it's own queueing * for other reasons (e.g. device output queuing). */ struct ng_type { u_int32_t version; /* must equal NG_API_VERSION */ const char *name; /* Unique type name */ modeventhand_t mod_event; /* Module event handler (optional) */ ng_constructor_t *constructor; /* Node constructor */ ng_rcvmsg_t *rcvmsg; /* control messages come here */ ng_close_t *close; /* warn about forthcoming shutdown */ ng_shutdown_t *shutdown; /* reset, and free resources */ ng_newhook_t *newhook; /* first notification of new hook */ ng_findhook_t *findhook; /* only if you have lots of hooks */ ng_connect_t *connect; /* final notification of new hook */ ng_rcvdata_t *rcvdata; /* data comes here */ ng_disconnect_t *disconnect; /* notify on disconnect */ const struct ng_cmdlist *cmdlist; /* commands we can convert */ /* R/W data private to the base netgraph code DON'T TOUCH! */ LIST_ENTRY(ng_type) types; /* linked list of all types */ int refs; /* number of instances */ }; /* * Use the NETGRAPH_INIT() macro to link a node type into the * netgraph system. This works for types compiled into the kernel * as well as KLD modules. The first argument should be the type * name (eg, echo) and the second a pointer to the type struct. * * If a different link time is desired, e.g., a device driver that * needs to install its netgraph type before probing, use the * NETGRAPH_INIT_ORDERED() macro instead. Device drivers probably * want to use SI_SUB_DRIVERS/SI_ORDER_FIRST. */ #define NETGRAPH_INIT_ORDERED(typename, typestructp, sub, order) \ static moduledata_t ng_##typename##_mod = { \ "ng_" #typename, \ ng_mod_event, \ (typestructp) \ }; \ DECLARE_MODULE(ng_##typename, ng_##typename##_mod, sub, order); \ MODULE_DEPEND(ng_##typename, netgraph, NG_ABI_VERSION, \ NG_ABI_VERSION, \ NG_ABI_VERSION) #define NETGRAPH_INIT(tn, tp) \ NETGRAPH_INIT_ORDERED(tn, tp, SI_SUB_PSEUDO, SI_ORDER_MIDDLE) /* Special malloc() type for netgraph structs and ctrl messages */ /* Only these two types should be visible to nodes */ MALLOC_DECLARE(M_NETGRAPH); MALLOC_DECLARE(M_NETGRAPH_MSG); /* declare the base of the netgraph sysclt hierarchy */ /* but only if this file cares about sysctls */ #ifdef SYSCTL_DECL SYSCTL_DECL(_net_graph); #endif /* * Methods that the nodes can use. * Many of these methods should usually NOT be used directly but via * Macros above. */ int ng_address_ID(node_p here, item_p item, ng_ID_t ID, ng_ID_t retaddr); int ng_address_hook(node_p here, item_p item, hook_p hook, ng_ID_t retaddr); int ng_address_path(node_p here, item_p item, const char *address, ng_ID_t raddr); int ng_bypass(hook_p hook1, hook_p hook2); hook_p ng_findhook(node_p node, const char *name); struct ng_type *ng_findtype(const char *type); int ng_make_node_common(struct ng_type *typep, node_p *nodep); int ng_name_node(node_p node, const char *name); node_p ng_name2noderef(node_p node, const char *name); int ng_newtype(struct ng_type *tp); ng_ID_t ng_node2ID(node_cp node); item_p ng_package_data(struct mbuf *m, int flags); item_p ng_package_msg(struct ng_mesg *msg, int flags); item_p ng_package_msg_self(node_p here, hook_p hook, struct ng_mesg *msg); void ng_replace_retaddr(node_p here, item_p item, ng_ID_t retaddr); int ng_rmhook_self(hook_p hook); /* if a node wants to kill a hook */ int ng_rmnode_self(node_p here); /* if a node wants to suicide */ int ng_rmtype(struct ng_type *tp); int ng_snd_item(item_p item, int queue); int ng_send_fn(node_p node, hook_p hook, ng_item_fn *fn, void *arg1, int arg2); int ng_send_fn1(node_p node, hook_p hook, ng_item_fn *fn, void *arg1, int arg2, int flags); int ng_send_fn2(node_p node, hook_p hook, item_p pitem, ng_item_fn2 *fn, void *arg1, int arg2, int flags); int ng_uncallout(struct callout *c, node_p node); int ng_uncallout_drain(struct callout *c, node_p node); int ng_callout(struct callout *c, node_p node, hook_p hook, int ticks, ng_item_fn *fn, void * arg1, int arg2); #define ng_callout_init(c) callout_init(c, 1) /* Flags for netgraph functions. */ #define NG_NOFLAGS 0x00000000 /* no special options */ #define NG_QUEUE 0x00000001 /* enqueue item, don't dispatch */ #define NG_WAITOK 0x00000002 /* use M_WAITOK, etc. */ /* XXXGL: NG_PROGRESS unused since ng_base.c rev. 1.136. Should be deleted? */ #define NG_PROGRESS 0x00000004 /* return EINPROGRESS if queued */ #define NG_REUSE_ITEM 0x00000008 /* supplied item should be reused */ /* * prototypes the user should DEFINITELY not use directly */ void ng_free_item(item_p item); /* Use NG_FREE_ITEM instead */ int ng_mod_event(module_t mod, int what, void *arg); /* * Tag definitions and constants */ #define NG_TAG_PRIO 1 struct ng_tag_prio { struct m_tag tag; char priority; char discardability; }; #define NG_PRIO_CUTOFF 32 #define NG_PRIO_LINKSTATE 64 /* Macros and declarations to keep compatibility with metadata, which * is obsoleted now. To be deleted. */ typedef void *meta_p; #define _NGI_META(i) NULL #define NGI_META(i) NULL #define NG_FREE_META(meta) #define NGI_GET_META(i,m) #define ng_copy_meta(meta) NULL /* * Mark the current thread when called from the outbound path of the * network stack, in order to enforce queuing on ng nodes calling into * the inbound network stack path. */ #define NG_OUTBOUND_THREAD_REF() \ curthread->td_ng_outbound++ #define NG_OUTBOUND_THREAD_UNREF() \ do { \ curthread->td_ng_outbound--; \ KASSERT(curthread->td_ng_outbound >= 0, \ ("%s: negative td_ng_outbound", __func__)); \ } while (0) #endif /* _NETGRAPH_NETGRAPH_H_ */ diff --git a/sys/netgraph/ng_bpf.c b/sys/netgraph/ng_bpf.c index b9535c7c124d..26e4c1f64d5b 100644 --- a/sys/netgraph/ng_bpf.c +++ b/sys/netgraph/ng_bpf.c @@ -1,593 +1,591 @@ /* * ng_bpf.c */ /*- * Copyright (c) 1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ * $Whistle: ng_bpf.c,v 1.3 1999/12/03 20:30:23 archie Exp $ */ /* * BPF NETGRAPH NODE TYPE * * This node type accepts any number of hook connections. With each hook * is associated a bpf(4) filter program, and two hook names (each possibly * the empty string). Incoming packets are compared against the filter; * matching packets are delivered out the first named hook (or dropped if * the empty string), and non-matching packets are delivered out the second * named hook (or dropped if the empty string). * * Each hook also keeps statistics about how many packets have matched, etc. */ #include "opt_bpf.h" #include #include #include #include #include #include #include #ifdef BPF_JITTER #include #endif #include #include #include #include #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_BPF, "netgraph_bpf", "netgraph bpf node"); #else #define M_NETGRAPH_BPF M_NETGRAPH #endif #define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) #define ERROUT(x) do { error = (x); goto done; } while (0) /* Per hook private info */ struct ng_bpf_hookinfo { hook_p hook; hook_p match; hook_p nomatch; struct ng_bpf_hookprog *prog; #ifdef BPF_JITTER bpf_jit_filter *jit_prog; #endif struct ng_bpf_hookstat stats; }; typedef struct ng_bpf_hookinfo *hinfo_p; /* Netgraph methods */ static ng_constructor_t ng_bpf_constructor; static ng_rcvmsg_t ng_bpf_rcvmsg; static ng_shutdown_t ng_bpf_shutdown; static ng_newhook_t ng_bpf_newhook; static ng_rcvdata_t ng_bpf_rcvdata; static ng_disconnect_t ng_bpf_disconnect; /* Maximum bpf program instructions */ extern int bpf_maxinsns; /* Internal helper functions */ static int ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp); /* Parse type for one struct bfp_insn */ static const struct ng_parse_struct_field ng_bpf_insn_type_fields[] = { { "code", &ng_parse_hint16_type }, { "jt", &ng_parse_uint8_type }, { "jf", &ng_parse_uint8_type }, { "k", &ng_parse_uint32_type }, { NULL } }; static const struct ng_parse_type ng_bpf_insn_type = { &ng_parse_struct_type, &ng_bpf_insn_type_fields }; /* Parse type for the field 'bpf_prog' in struct ng_bpf_hookprog */ static int ng_bpf_hookprogary_getLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ng_bpf_hookprog *hp; hp = (const struct ng_bpf_hookprog *) (buf - OFFSETOF(struct ng_bpf_hookprog, bpf_prog)); return hp->bpf_prog_len; } static const struct ng_parse_array_info ng_bpf_hookprogary_info = { &ng_bpf_insn_type, &ng_bpf_hookprogary_getLength, NULL }; static const struct ng_parse_type ng_bpf_hookprogary_type = { &ng_parse_array_type, &ng_bpf_hookprogary_info }; /* Parse type for struct ng_bpf_hookprog */ static const struct ng_parse_struct_field ng_bpf_hookprog_type_fields[] = NG_BPF_HOOKPROG_TYPE_INFO(&ng_bpf_hookprogary_type); static const struct ng_parse_type ng_bpf_hookprog_type = { &ng_parse_struct_type, &ng_bpf_hookprog_type_fields }; /* Parse type for struct ng_bpf_hookstat */ static const struct ng_parse_struct_field ng_bpf_hookstat_type_fields[] = NG_BPF_HOOKSTAT_TYPE_INFO; static const struct ng_parse_type ng_bpf_hookstat_type = { &ng_parse_struct_type, &ng_bpf_hookstat_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_bpf_cmdlist[] = { { NGM_BPF_COOKIE, NGM_BPF_SET_PROGRAM, "setprogram", &ng_bpf_hookprog_type, NULL }, { NGM_BPF_COOKIE, NGM_BPF_GET_PROGRAM, "getprogram", &ng_parse_hookbuf_type, &ng_bpf_hookprog_type }, { NGM_BPF_COOKIE, NGM_BPF_GET_STATS, "getstats", &ng_parse_hookbuf_type, &ng_bpf_hookstat_type }, { NGM_BPF_COOKIE, NGM_BPF_CLR_STATS, "clrstats", &ng_parse_hookbuf_type, NULL }, { NGM_BPF_COOKIE, NGM_BPF_GETCLR_STATS, "getclrstats", &ng_parse_hookbuf_type, &ng_bpf_hookstat_type }, { 0 } }; /* Netgraph type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_BPF_NODE_TYPE, .constructor = ng_bpf_constructor, .rcvmsg = ng_bpf_rcvmsg, .shutdown = ng_bpf_shutdown, .newhook = ng_bpf_newhook, .rcvdata = ng_bpf_rcvdata, .disconnect = ng_bpf_disconnect, .cmdlist = ng_bpf_cmdlist, }; NETGRAPH_INIT(bpf, &typestruct); /* Default BPF program for a hook that matches nothing */ static const struct ng_bpf_hookprog ng_bpf_default_prog = { { '\0' }, /* to be filled in at hook creation time */ { '\0' }, { '\0' }, 1, { BPF_STMT(BPF_RET+BPF_K, 0) } }; /* * Node constructor * * We don't keep any per-node private data * We go via the hooks. */ static int ng_bpf_constructor(node_p node) { NG_NODE_SET_PRIVATE(node, NULL); return (0); } /* * Callback functions to be used by NG_NODE_FOREACH_HOOK() macro. */ static int ng_bpf_addrefs(hook_p hook, void* arg) { hinfo_p hip = NG_HOOK_PRIVATE(hook); hook_p h = (hook_p)arg; if (strcmp(hip->prog->ifMatch, NG_HOOK_NAME(h)) == 0) hip->match = h; if (strcmp(hip->prog->ifNotMatch, NG_HOOK_NAME(h)) == 0) hip->nomatch = h; return (1); } static int ng_bpf_remrefs(hook_p hook, void* arg) { hinfo_p hip = NG_HOOK_PRIVATE(hook); hook_p h = (hook_p)arg; if (hip->match == h) hip->match = NULL; if (hip->nomatch == h) hip->nomatch = NULL; return (1); } /* * Add a hook */ static int ng_bpf_newhook(node_p node, hook_p hook, const char *name) { hinfo_p hip; - hook_p tmp; int error; /* Create hook private structure */ hip = malloc(sizeof(*hip), M_NETGRAPH_BPF, M_NOWAIT | M_ZERO); if (hip == NULL) return (ENOMEM); hip->hook = hook; NG_HOOK_SET_PRIVATE(hook, hip); /* Add our reference into other hooks data. */ - NG_NODE_FOREACH_HOOK(node, ng_bpf_addrefs, hook, tmp); + NG_NODE_FOREACH_HOOK(node, ng_bpf_addrefs, hook); /* Attach the default BPF program */ if ((error = ng_bpf_setprog(hook, &ng_bpf_default_prog)) != 0) { free(hip, M_NETGRAPH_BPF); NG_HOOK_SET_PRIVATE(hook, NULL); return (error); } /* Set hook name */ strlcpy(hip->prog->thisHook, name, sizeof(hip->prog->thisHook)); return (0); } /* * Receive a control message */ static int ng_bpf_rcvmsg(node_p node, item_p item, hook_p lasthook) { struct ng_mesg *msg; struct ng_mesg *resp = NULL; int error = 0; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_BPF_COOKIE: switch (msg->header.cmd) { case NGM_BPF_SET_PROGRAM: { struct ng_bpf_hookprog *const hp = (struct ng_bpf_hookprog *)msg->data; hook_p hook; /* Sanity check */ if (msg->header.arglen < sizeof(*hp) || msg->header.arglen != NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) ERROUT(EINVAL); /* Find hook */ if ((hook = ng_findhook(node, hp->thisHook)) == NULL) ERROUT(ENOENT); /* Set new program */ if ((error = ng_bpf_setprog(hook, hp)) != 0) ERROUT(error); break; } case NGM_BPF_GET_PROGRAM: { struct ng_bpf_hookprog *hp; hook_p hook; /* Sanity check */ if (msg->header.arglen == 0) ERROUT(EINVAL); msg->data[msg->header.arglen - 1] = '\0'; /* Find hook */ if ((hook = ng_findhook(node, msg->data)) == NULL) ERROUT(ENOENT); /* Build response */ hp = ((hinfo_p)NG_HOOK_PRIVATE(hook))->prog; NG_MKRESPONSE(resp, msg, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(hp, resp->data, NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)); break; } case NGM_BPF_GET_STATS: case NGM_BPF_CLR_STATS: case NGM_BPF_GETCLR_STATS: { struct ng_bpf_hookstat *stats; hook_p hook; /* Sanity check */ if (msg->header.arglen == 0) ERROUT(EINVAL); msg->data[msg->header.arglen - 1] = '\0'; /* Find hook */ if ((hook = ng_findhook(node, msg->data)) == NULL) ERROUT(ENOENT); stats = &((hinfo_p)NG_HOOK_PRIVATE(hook))->stats; /* Build response (if desired) */ if (msg->header.cmd != NGM_BPF_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); if (resp == NULL) ERROUT(ENOMEM); bcopy(stats, resp->data, sizeof(*stats)); } /* Clear stats (if desired) */ if (msg->header.cmd != NGM_BPF_GET_STATS) bzero(stats, sizeof(*stats)); break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook * * Apply the filter, and then drop or forward packet as appropriate. */ static int ng_bpf_rcvdata(hook_p hook, item_p item) { const hinfo_p hip = NG_HOOK_PRIVATE(hook); int totlen; int needfree = 0, error = 0, usejit = 0; u_char *data = NULL; hinfo_p dhip; hook_p dest; u_int len; struct mbuf *m; m = NGI_M(item); /* 'item' still owns it.. we are peeking */ totlen = m->m_pkthdr.len; /* Update stats on incoming hook. XXX Can we do 64 bits atomically? */ /* atomic_add_int64(&hip->stats.recvFrames, 1); */ /* atomic_add_int64(&hip->stats.recvOctets, totlen); */ hip->stats.recvFrames++; hip->stats.recvOctets += totlen; /* Don't call bpf_filter() with totlen == 0! */ if (totlen == 0) { len = 0; goto ready; } #ifdef BPF_JITTER if (bpf_jitter_enable != 0 && hip->jit_prog != NULL) usejit = 1; #endif /* Need to put packet in contiguous memory for bpf */ if (m->m_next != NULL && totlen > MHLEN) { if (usejit) { data = malloc(totlen, M_NETGRAPH_BPF, M_NOWAIT); if (data == NULL) { NG_FREE_ITEM(item); return (ENOMEM); } needfree = 1; m_copydata(m, 0, totlen, (caddr_t)data); } } else { if (m->m_next != NULL) { NGI_M(item) = m = m_pullup(m, totlen); if (m == NULL) { NG_FREE_ITEM(item); return (ENOBUFS); } } data = mtod(m, u_char *); } /* Run packet through filter */ #ifdef BPF_JITTER if (usejit) len = (*(hip->jit_prog->func))(data, totlen, totlen); else #endif if (data) len = bpf_filter(hip->prog->bpf_prog, data, totlen, totlen); else len = bpf_filter(hip->prog->bpf_prog, (u_char *)m, totlen, 0); if (needfree) free(data, M_NETGRAPH_BPF); ready: /* See if we got a match and find destination hook */ if (len > 0) { /* Update stats */ /* XXX atomically? */ hip->stats.recvMatchFrames++; hip->stats.recvMatchOctets += totlen; /* Truncate packet length if required by the filter */ /* Assume this never changes m */ if (len < totlen) { m_adj(m, -(totlen - len)); totlen = len; } dest = hip->match; } else dest = hip->nomatch; if (dest == NULL) { NG_FREE_ITEM(item); return (0); } /* Deliver frame out destination hook */ dhip = NG_HOOK_PRIVATE(dest); dhip->stats.xmitOctets += totlen; dhip->stats.xmitFrames++; NG_FWD_ITEM_HOOK(error, item, dest); return (error); } /* * Shutdown processing */ static int ng_bpf_shutdown(node_p node) { NG_NODE_UNREF(node); return (0); } /* * Hook disconnection */ static int ng_bpf_disconnect(hook_p hook) { const node_p node = NG_HOOK_NODE(hook); const hinfo_p hip = NG_HOOK_PRIVATE(hook); - hook_p tmp; KASSERT(hip != NULL, ("%s: null info", __func__)); /* Remove our reference from other hooks data. */ - NG_NODE_FOREACH_HOOK(node, ng_bpf_remrefs, hook, tmp); + NG_NODE_FOREACH_HOOK(node, ng_bpf_remrefs, hook); free(hip->prog, M_NETGRAPH_BPF); #ifdef BPF_JITTER if (hip->jit_prog != NULL) bpf_destroy_jit_filter(hip->jit_prog); #endif free(hip, M_NETGRAPH_BPF); if ((NG_NODE_NUMHOOKS(node) == 0) && (NG_NODE_IS_VALID(node))) { ng_rmnode_self(node); } return (0); } /************************************************************************ HELPER STUFF ************************************************************************/ /* * Set the BPF program associated with a hook */ static int ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp0) { const hinfo_p hip = NG_HOOK_PRIVATE(hook); struct ng_bpf_hookprog *hp; #ifdef BPF_JITTER bpf_jit_filter *jit_prog; #endif int size; /* Check program for validity */ if (hp0->bpf_prog_len > bpf_maxinsns || !bpf_validate(hp0->bpf_prog, hp0->bpf_prog_len)) return (EINVAL); /* Make a copy of the program */ size = NG_BPF_HOOKPROG_SIZE(hp0->bpf_prog_len); hp = malloc(size, M_NETGRAPH_BPF, M_NOWAIT); if (hp == NULL) return (ENOMEM); bcopy(hp0, hp, size); #ifdef BPF_JITTER jit_prog = bpf_jitter(hp->bpf_prog, hp->bpf_prog_len); #endif /* Free previous program, if any, and assign new one */ if (hip->prog != NULL) free(hip->prog, M_NETGRAPH_BPF); hip->prog = hp; #ifdef BPF_JITTER if (hip->jit_prog != NULL) bpf_destroy_jit_filter(hip->jit_prog); hip->jit_prog = jit_prog; #endif /* Prepare direct references on target hooks. */ hip->match = ng_findhook(NG_HOOK_NODE(hook), hip->prog->ifMatch); hip->nomatch = ng_findhook(NG_HOOK_NODE(hook), hip->prog->ifNotMatch); return (0); } diff --git a/sys/netgraph/ng_bridge.c b/sys/netgraph/ng_bridge.c index 9f1d33ba4c85..14068878736d 100644 --- a/sys/netgraph/ng_bridge.c +++ b/sys/netgraph/ng_bridge.c @@ -1,1222 +1,1217 @@ /*- * Copyright (c) 2000 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ */ /* * ng_bridge(4) netgraph node type * * The node performs standard intelligent Ethernet bridging over * each of its connected hooks, or links. A simple loop detection * algorithm is included which disables a link for priv->conf.loopTimeout * seconds when a host is seen to have jumped from one link to * another within priv->conf.minStableAge seconds. * * We keep a hashtable that maps Ethernet addresses to host info, * which is contained in struct ng_bridge_host's. These structures * tell us on which link the host may be found. A host's entry will * expire after priv->conf.maxStaleness seconds. * * This node is optimzed for stable networks, where machines jump * from one port to the other only rarely. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 /* not used yet */ #include #endif #include #include #include #include #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_BRIDGE, "netgraph_bridge", "netgraph bridge node"); #else #define M_NETGRAPH_BRIDGE M_NETGRAPH #endif /* Counter based stats */ struct ng_bridge_link_kernel_stats { counter_u64_t recvOctets; /* total octets rec'd on link */ counter_u64_t recvPackets; /* total pkts rec'd on link */ counter_u64_t recvMulticasts; /* multicast pkts rec'd on link */ counter_u64_t recvBroadcasts; /* broadcast pkts rec'd on link */ counter_u64_t recvUnknown; /* pkts rec'd with unknown dest addr */ counter_u64_t recvRunts; /* pkts rec'd less than 14 bytes */ counter_u64_t recvInvalid; /* pkts rec'd with bogus source addr */ counter_u64_t xmitOctets; /* total octets xmit'd on link */ counter_u64_t xmitPackets; /* total pkts xmit'd on link */ counter_u64_t xmitMulticasts; /* multicast pkts xmit'd on link */ counter_u64_t xmitBroadcasts; /* broadcast pkts xmit'd on link */ counter_u64_t loopDrops; /* pkts dropped due to loopback */ u_int64_t loopDetects; /* number of loop detections */ counter_u64_t memoryFailures; /* times couldn't get mem or mbuf */ }; /* Per-link private data */ struct ng_bridge_link { hook_p hook; /* netgraph hook */ u_int16_t loopCount; /* loop ignore timer */ unsigned int learnMac : 1, /* autolearn macs */ sendUnknown : 1;/* send unknown macs out */ struct ng_bridge_link_kernel_stats stats; /* link stats */ }; typedef struct ng_bridge_link const *link_cp; /* read only access */ /* Per-node private data */ struct ng_bridge_private { struct ng_bridge_bucket *tab; /* hash table bucket array */ struct ng_bridge_config conf; /* node configuration */ node_p node; /* netgraph node */ u_int numHosts; /* num entries in table */ u_int numBuckets; /* num buckets in table */ u_int hashMask; /* numBuckets - 1 */ int numLinks; /* num connected links */ unsigned int persistent : 1, /* can exist w/o hooks */ sendUnknown : 1;/* links receive unknowns by default */ struct callout timer; /* one second periodic timer */ }; typedef struct ng_bridge_private *priv_p; typedef struct ng_bridge_private const *priv_cp; /* read only access */ /* Information about a host, stored in a hash table entry */ struct ng_bridge_host { u_char addr[6]; /* ethernet address */ link_p link; /* link where addr can be found */ u_int16_t age; /* seconds ago entry was created */ u_int16_t staleness; /* seconds ago host last heard from */ SLIST_ENTRY(ng_bridge_host) next; /* next entry in bucket */ }; /* Hash table bucket declaration */ SLIST_HEAD(ng_bridge_bucket, ng_bridge_host); /* Netgraph node methods */ static ng_constructor_t ng_bridge_constructor; static ng_rcvmsg_t ng_bridge_rcvmsg; static ng_shutdown_t ng_bridge_shutdown; static ng_newhook_t ng_bridge_newhook; static ng_rcvdata_t ng_bridge_rcvdata; static ng_disconnect_t ng_bridge_disconnect; /* Other internal functions */ static void ng_bridge_free_link(link_p link); static struct ng_bridge_host *ng_bridge_get(priv_cp priv, const u_char *addr); static int ng_bridge_put(priv_p priv, const u_char *addr, link_p link); static void ng_bridge_rehash(priv_p priv); static void ng_bridge_remove_hosts(priv_p priv, link_p link); static void ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2); static const char *ng_bridge_nodename(node_cp node); /* Ethernet broadcast */ static const u_char ng_bridge_bcast_addr[ETHER_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; /* Compare Ethernet addresses using 32 and 16 bit words instead of bytewise */ #define ETHER_EQUAL(a,b) (((const u_int32_t *)(a))[0] \ == ((const u_int32_t *)(b))[0] \ && ((const u_int16_t *)(a))[2] \ == ((const u_int16_t *)(b))[2]) /* Minimum and maximum number of hash buckets. Must be a power of two. */ #define MIN_BUCKETS (1 << 5) /* 32 */ #define MAX_BUCKETS (1 << 14) /* 16384 */ /* Configuration default values */ #define DEFAULT_LOOP_TIMEOUT 60 #define DEFAULT_MAX_STALENESS (15 * 60) /* same as ARP timeout */ #define DEFAULT_MIN_STABLE_AGE 1 /****************************************************************** NETGRAPH PARSE TYPES ******************************************************************/ /* * How to determine the length of the table returned by NGM_BRIDGE_GET_TABLE */ static int ng_bridge_getTableLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ng_bridge_host_ary *const hary = (const struct ng_bridge_host_ary *)(buf - sizeof(u_int32_t)); return hary->numHosts; } /* Parse type for struct ng_bridge_host_ary */ static const struct ng_parse_struct_field ng_bridge_host_type_fields[] = NG_BRIDGE_HOST_TYPE_INFO(&ng_parse_enaddr_type); static const struct ng_parse_type ng_bridge_host_type = { &ng_parse_struct_type, &ng_bridge_host_type_fields }; static const struct ng_parse_array_info ng_bridge_hary_type_info = { &ng_bridge_host_type, ng_bridge_getTableLength }; static const struct ng_parse_type ng_bridge_hary_type = { &ng_parse_array_type, &ng_bridge_hary_type_info }; static const struct ng_parse_struct_field ng_bridge_host_ary_type_fields[] = NG_BRIDGE_HOST_ARY_TYPE_INFO(&ng_bridge_hary_type); static const struct ng_parse_type ng_bridge_host_ary_type = { &ng_parse_struct_type, &ng_bridge_host_ary_type_fields }; /* Parse type for struct ng_bridge_config */ static const struct ng_parse_struct_field ng_bridge_config_type_fields[] = NG_BRIDGE_CONFIG_TYPE_INFO; static const struct ng_parse_type ng_bridge_config_type = { &ng_parse_struct_type, &ng_bridge_config_type_fields }; /* Parse type for struct ng_bridge_link_stat */ static const struct ng_parse_struct_field ng_bridge_stats_type_fields[] = NG_BRIDGE_STATS_TYPE_INFO; static const struct ng_parse_type ng_bridge_stats_type = { &ng_parse_struct_type, &ng_bridge_stats_type_fields }; /* Parse type for struct ng_bridge_move_host */ static const struct ng_parse_struct_field ng_bridge_move_host_type_fields[] = NG_BRIDGE_MOVE_HOST_TYPE_INFO(&ng_parse_enaddr_type); static const struct ng_parse_type ng_bridge_move_host_type = { &ng_parse_struct_type, &ng_bridge_move_host_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_bridge_cmdlist[] = { { NGM_BRIDGE_COOKIE, NGM_BRIDGE_SET_CONFIG, "setconfig", &ng_bridge_config_type, NULL }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GET_CONFIG, "getconfig", NULL, &ng_bridge_config_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_RESET, "reset", NULL, NULL }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GET_STATS, "getstats", &ng_parse_uint32_type, &ng_bridge_stats_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_CLR_STATS, "clrstats", &ng_parse_uint32_type, NULL }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GETCLR_STATS, "getclrstats", &ng_parse_uint32_type, &ng_bridge_stats_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_GET_TABLE, "gettable", NULL, &ng_bridge_host_ary_type }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_SET_PERSISTENT, "setpersistent", NULL, NULL }, { NGM_BRIDGE_COOKIE, NGM_BRIDGE_MOVE_HOST, "movehost", &ng_bridge_move_host_type, NULL }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_bridge_typestruct = { .version = NG_ABI_VERSION, .name = NG_BRIDGE_NODE_TYPE, .constructor = ng_bridge_constructor, .rcvmsg = ng_bridge_rcvmsg, .shutdown = ng_bridge_shutdown, .newhook = ng_bridge_newhook, .rcvdata = ng_bridge_rcvdata, .disconnect = ng_bridge_disconnect, .cmdlist = ng_bridge_cmdlist, }; NETGRAPH_INIT(bridge, &ng_bridge_typestruct); /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Node constructor */ static int ng_bridge_constructor(node_p node) { priv_p priv; /* Allocate and initialize private info */ priv = malloc(sizeof(*priv), M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO); ng_callout_init(&priv->timer); /* Allocate and initialize hash table, etc. */ priv->tab = malloc(MIN_BUCKETS * sizeof(*priv->tab), M_NETGRAPH_BRIDGE, M_WAITOK | M_ZERO); priv->numBuckets = MIN_BUCKETS; priv->hashMask = MIN_BUCKETS - 1; priv->conf.debugLevel = 1; priv->conf.loopTimeout = DEFAULT_LOOP_TIMEOUT; priv->conf.maxStaleness = DEFAULT_MAX_STALENESS; priv->conf.minStableAge = DEFAULT_MIN_STABLE_AGE; priv->sendUnknown = 1; /* classic bridge */ NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* Start timer; timer is always running while node is alive */ ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0); /* Done */ return (0); } /* * Method for attaching a new hook */ static int ng_bridge_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); char linkName[NG_HOOKSIZ]; u_int32_t linkNum; link_p link; const char *prefix = NG_BRIDGE_HOOK_LINK_PREFIX; bool isUplink; /* Check for a link hook */ if (strlen(name) <= strlen(prefix)) return (EINVAL); /* Unknown hook name */ isUplink = (name[0] == 'u'); if (isUplink) prefix = NG_BRIDGE_HOOK_UPLINK_PREFIX; /* primitive parsing */ linkNum = strtoul(name + strlen(prefix), NULL, 10); /* validation by comparing against the reconstucted name */ snprintf(linkName, sizeof(linkName), "%s%u", prefix, linkNum); if (strcmp(linkName, name) != 0) return (EINVAL); if (linkNum == 0 && isUplink) return (EINVAL); if(NG_PEER_NODE(hook) == node) return (ELOOP); link = malloc(sizeof(*link), M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO); if (link == NULL) return (ENOMEM); #define NG_BRIDGE_COUNTER_ALLOC(f) do { \ link->stats.f = counter_u64_alloc(M_NOWAIT); \ if (link->stats.f == NULL) \ goto nomem; \ } while (0) NG_BRIDGE_COUNTER_ALLOC(recvOctets); NG_BRIDGE_COUNTER_ALLOC(recvPackets); NG_BRIDGE_COUNTER_ALLOC(recvMulticasts); NG_BRIDGE_COUNTER_ALLOC(recvBroadcasts); NG_BRIDGE_COUNTER_ALLOC(recvUnknown); NG_BRIDGE_COUNTER_ALLOC(recvRunts); NG_BRIDGE_COUNTER_ALLOC(recvInvalid); NG_BRIDGE_COUNTER_ALLOC(xmitOctets); NG_BRIDGE_COUNTER_ALLOC(xmitPackets); NG_BRIDGE_COUNTER_ALLOC(xmitMulticasts); NG_BRIDGE_COUNTER_ALLOC(xmitBroadcasts); NG_BRIDGE_COUNTER_ALLOC(loopDrops); NG_BRIDGE_COUNTER_ALLOC(memoryFailures); #undef NG_BRIDGE_COUNTER_ALLOC link->hook = hook; if (isUplink) { link->learnMac = 0; link->sendUnknown = 1; if (priv->numLinks == 0) /* if the first link is an uplink */ priv->sendUnknown = 0; /* switch to restrictive mode */ } else { link->learnMac = 1; link->sendUnknown = priv->sendUnknown; } NG_HOOK_SET_PRIVATE(hook, link); priv->numLinks++; return (0); nomem: ng_bridge_free_link(link); return (ENOMEM); } /* * Receive a control message */ static void ng_bridge_clear_link_stats(struct ng_bridge_link_kernel_stats *p) { counter_u64_zero(p->recvOctets); counter_u64_zero(p->recvPackets); counter_u64_zero(p->recvMulticasts); counter_u64_zero(p->recvBroadcasts); counter_u64_zero(p->recvUnknown); counter_u64_zero(p->recvRunts); counter_u64_zero(p->recvInvalid); counter_u64_zero(p->xmitOctets); counter_u64_zero(p->xmitPackets); counter_u64_zero(p->xmitMulticasts); counter_u64_zero(p->xmitBroadcasts); counter_u64_zero(p->loopDrops); p->loopDetects = 0; counter_u64_zero(p->memoryFailures); } static void ng_bridge_free_link(link_p link) { counter_u64_free(link->stats.recvOctets); counter_u64_free(link->stats.recvPackets); counter_u64_free(link->stats.recvMulticasts); counter_u64_free(link->stats.recvBroadcasts); counter_u64_free(link->stats.recvUnknown); counter_u64_free(link->stats.recvRunts); counter_u64_free(link->stats.recvInvalid); counter_u64_free(link->stats.xmitOctets); counter_u64_free(link->stats.xmitPackets); counter_u64_free(link->stats.xmitMulticasts); counter_u64_free(link->stats.xmitBroadcasts); counter_u64_free(link->stats.loopDrops); counter_u64_free(link->stats.memoryFailures); free(link, M_NETGRAPH_BRIDGE); } static int ng_bridge_reset_link(hook_p hook, void *arg __unused) { link_p priv = NG_HOOK_PRIVATE(hook); priv->loopCount = 0; ng_bridge_clear_link_stats(&priv->stats); return (1); } static int ng_bridge_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; int error = 0; struct ng_mesg *msg; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_BRIDGE_COOKIE: switch (msg->header.cmd) { case NGM_BRIDGE_GET_CONFIG: { struct ng_bridge_config *conf; NG_MKRESPONSE(resp, msg, sizeof(struct ng_bridge_config), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_bridge_config *)resp->data; *conf = priv->conf; /* no sanity checking needed */ break; } case NGM_BRIDGE_SET_CONFIG: { struct ng_bridge_config *conf; if (msg->header.arglen != sizeof(struct ng_bridge_config)) { error = EINVAL; break; } conf = (struct ng_bridge_config *)msg->data; priv->conf = *conf; break; } case NGM_BRIDGE_RESET: { - hook_p rethook; - /* Flush all entries in the hash table */ ng_bridge_remove_hosts(priv, NULL); /* Reset all loop detection counters and stats */ - NG_NODE_FOREACH_HOOK(node, ng_bridge_reset_link, NULL, - rethook); + NG_NODE_FOREACH_HOOK(node, ng_bridge_reset_link, NULL); break; } case NGM_BRIDGE_GET_STATS: case NGM_BRIDGE_CLR_STATS: case NGM_BRIDGE_GETCLR_STATS: { hook_p hook; link_p link; char linkName[NG_HOOKSIZ]; int linkNum; /* Get link number */ if (msg->header.arglen != sizeof(u_int32_t)) { error = EINVAL; break; } linkNum = *((int32_t *)msg->data); if (linkNum < 0) snprintf(linkName, sizeof(linkName), "%s%u", NG_BRIDGE_HOOK_UPLINK_PREFIX, -linkNum); else snprintf(linkName, sizeof(linkName), "%s%u", NG_BRIDGE_HOOK_LINK_PREFIX, linkNum); if ((hook = ng_findhook(node, linkName)) == NULL) { error = ENOTCONN; break; } link = NG_HOOK_PRIVATE(hook); /* Get/clear stats */ if (msg->header.cmd != NGM_BRIDGE_CLR_STATS) { struct ng_bridge_link_stats *rs; NG_MKRESPONSE(resp, msg, sizeof(link->stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } rs = (struct ng_bridge_link_stats *)resp->data; #define FETCH(x) rs->x = counter_u64_fetch(link->stats.x) FETCH(recvOctets); FETCH(recvPackets); FETCH(recvMulticasts); FETCH(recvBroadcasts); FETCH(recvUnknown); FETCH(recvRunts); FETCH(recvInvalid); FETCH(xmitOctets); FETCH(xmitPackets); FETCH(xmitMulticasts); FETCH(xmitBroadcasts); FETCH(loopDrops); rs->loopDetects = link->stats.loopDetects; FETCH(memoryFailures); #undef FETCH } if (msg->header.cmd != NGM_BRIDGE_GET_STATS) ng_bridge_clear_link_stats(&link->stats); break; } case NGM_BRIDGE_GET_TABLE: { struct ng_bridge_host_ary *ary; struct ng_bridge_host *host; int i = 0, bucket; NG_MKRESPONSE(resp, msg, sizeof(*ary) + (priv->numHosts * sizeof(*ary->hosts)), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } ary = (struct ng_bridge_host_ary *)resp->data; ary->numHosts = priv->numHosts; for (bucket = 0; bucket < priv->numBuckets; bucket++) { SLIST_FOREACH(host, &priv->tab[bucket], next) { memcpy(ary->hosts[i].addr, host->addr, sizeof(ary->hosts[i].addr)); ary->hosts[i].age = host->age; ary->hosts[i].staleness = host->staleness; strncpy(ary->hosts[i].hook, NG_HOOK_NAME(host->link->hook), sizeof(ary->hosts[i].hook)); i++; } } break; } case NGM_BRIDGE_SET_PERSISTENT: { priv->persistent = 1; break; } case NGM_BRIDGE_MOVE_HOST: { struct ng_bridge_move_host *mh; hook_p hook; if (msg->header.arglen < sizeof(*mh)) { error = EINVAL; break; } mh = (struct ng_bridge_move_host *)msg->data; hook = (mh->hook[0] == 0) ? lasthook : ng_findhook(node, mh->hook); if (hook == NULL) { error = ENOENT; break; } error = ng_bridge_put(priv, mh->addr, NG_HOOK_PRIVATE(hook)); break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } /* Done */ NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive data on a hook */ struct ng_bridge_send_ctx { link_p foundFirst, incoming; struct mbuf * m; int manycast, error; }; /* * Update stats and send out */ static inline int ng_bridge_send_data(link_cp dst, int manycast, struct mbuf *m, item_p item) { int error = 0; size_t len = m->m_pkthdr.len; if(item != NULL) NG_FWD_NEW_DATA(error, item, dst->hook, m); else NG_SEND_DATA_ONLY(error, dst->hook, m); if (error) { if (error == ENOMEM) counter_u64_add(dst->stats.memoryFailures, 1); /* The packet is still ours */ if (item != NULL) NG_FREE_ITEM(item); if (m != NULL) NG_FREE_M(m); return (error); } counter_u64_add(dst->stats.xmitPackets, 1); counter_u64_add(dst->stats.xmitOctets, len); switch (manycast) { default: /* unknown unicast */ break; case 1: /* multicast */ counter_u64_add(dst->stats.xmitMulticasts, 1); break; case 2: /* broadcast */ counter_u64_add(dst->stats.xmitBroadcasts, 1); break; } return (0); } /* * Loop body for sending to multiple destinations * return 0 to stop looping */ static int ng_bridge_send_ctx(hook_p dst, void *arg) { struct ng_bridge_send_ctx *ctx = arg; link_p destLink = NG_HOOK_PRIVATE(dst); struct mbuf *m2 = NULL; int error = 0; /* Skip incoming link */ if (destLink == ctx->incoming) { return (1); } /* Skip sending unknowns to undesired links */ if (!ctx->manycast && !destLink->sendUnknown) return (1); if (ctx->foundFirst == NULL) { /* * This is the first usable link we have found. * Reserve it for the originals. * If we never find another we save a copy. */ ctx->foundFirst = destLink; return (1); } /* * It's usable link but not the reserved (first) one. * Copy mbuf info for sending. */ m2 = m_dup(ctx->m, M_NOWAIT); if (m2 == NULL) { counter_u64_add(ctx->incoming->stats.memoryFailures, 1); ctx->error = ENOBUFS; return (0); /* abort loop, do not try again and again */ } /* Send packet */ error = ng_bridge_send_data(destLink, ctx->manycast, m2, NULL); if (error) ctx->error = error; return (1); } static int ng_bridge_rcvdata(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct ng_bridge_host *host; struct ether_header *eh; struct ng_bridge_send_ctx ctx = { 0 }; - hook_p ret; NGI_GET_M(item, ctx.m); ctx.incoming = NG_HOOK_PRIVATE(hook); /* Sanity check packet and pull up header */ if (ctx.m->m_pkthdr.len < ETHER_HDR_LEN) { counter_u64_add(ctx.incoming->stats.recvRunts, 1); NG_FREE_ITEM(item); NG_FREE_M(ctx.m); return (EINVAL); } if (ctx.m->m_len < ETHER_HDR_LEN && !(ctx.m = m_pullup(ctx.m, ETHER_HDR_LEN))) { counter_u64_add(ctx.incoming->stats.memoryFailures, 1); NG_FREE_ITEM(item); return (ENOBUFS); } eh = mtod(ctx.m, struct ether_header *); if ((eh->ether_shost[0] & 1) != 0) { counter_u64_add(ctx.incoming->stats.recvInvalid, 1); NG_FREE_ITEM(item); NG_FREE_M(ctx.m); return (EINVAL); } /* Is link disabled due to a loopback condition? */ if (ctx.incoming->loopCount != 0) { counter_u64_add(ctx.incoming->stats.loopDrops, 1); NG_FREE_ITEM(item); NG_FREE_M(ctx.m); return (ELOOP); } /* Update stats */ counter_u64_add(ctx.incoming->stats.recvPackets, 1); counter_u64_add(ctx.incoming->stats.recvOctets, ctx.m->m_pkthdr.len); if ((ctx.manycast = (eh->ether_dhost[0] & 1)) != 0) { if (ETHER_EQUAL(eh->ether_dhost, ng_bridge_bcast_addr)) { counter_u64_add(ctx.incoming->stats.recvBroadcasts, 1); ctx.manycast = 2; } else counter_u64_add(ctx.incoming->stats.recvMulticasts, 1); } /* Look up packet's source Ethernet address in hashtable */ if ((host = ng_bridge_get(priv, eh->ether_shost)) != NULL) /* Update time since last heard from this host. * This is safe without locking, because it's * the only operation during shared access. */ if (__predict_false(host->staleness > 0)) host->staleness = 0; if ((host == NULL && ctx.incoming->learnMac) || (host != NULL && host->link != ctx.incoming)) { struct ng_mesg *msg; struct ng_bridge_move_host *mh; int error = 0; NG_MKMESSAGE(msg, NGM_BRIDGE_COOKIE, NGM_BRIDGE_MOVE_HOST, sizeof(*mh), M_NOWAIT); if (msg == NULL) { counter_u64_add(ctx.incoming->stats.memoryFailures, 1); NG_FREE_ITEM(item); NG_FREE_M(ctx.m); return (ENOMEM); } mh = (struct ng_bridge_move_host *)msg->data; strncpy(mh->hook, NG_HOOK_NAME(ctx.incoming->hook), sizeof(mh->hook)); memcpy(mh->addr, eh->ether_shost, sizeof(mh->addr)); NG_SEND_MSG_ID(error, node, msg, NG_NODE_ID(node), NG_NODE_ID(node)); if (error) counter_u64_add(ctx.incoming->stats.memoryFailures, 1); } if (host != NULL && host->link != ctx.incoming) { if (host->age < priv->conf.minStableAge) { /* Drop packet on instable links */ counter_u64_add(ctx.incoming->stats.loopDrops, 1); NG_FREE_ITEM(item); NG_FREE_M(ctx.m); return (ELOOP); } } /* Run packet through ipfw processing, if enabled */ #if 0 if (priv->conf.ipfw[linkNum] && V_fw_enable && V_ip_fw_chk_ptr != NULL) { /* XXX not implemented yet */ } #endif /* * If unicast and destination host known, deliver to host's link, * unless it is the same link as the packet came in on. */ if (!ctx.manycast) { /* Determine packet destination link */ if ((host = ng_bridge_get(priv, eh->ether_dhost)) != NULL) { link_p destLink = host->link; /* If destination same as incoming link, do nothing */ if (destLink == ctx.incoming) { NG_FREE_ITEM(item); NG_FREE_M(ctx.m); return (0); } /* Deliver packet out the destination link */ return (ng_bridge_send_data(destLink, ctx.manycast, ctx.m, item)); } /* Destination host is not known */ counter_u64_add(ctx.incoming->stats.recvUnknown, 1); } /* Distribute unknown, multicast, broadcast pkts to all other links */ - NG_NODE_FOREACH_HOOK(node, ng_bridge_send_ctx, &ctx, ret); + NG_NODE_FOREACH_HOOK(node, ng_bridge_send_ctx, &ctx); /* Finally send out on the first link found */ if (ctx.foundFirst != NULL) { int error = ng_bridge_send_data(ctx.foundFirst, ctx.manycast, ctx.m, item); if (error) ctx.error = error; } else { /* nothing to send at all */ NG_FREE_ITEM(item); NG_FREE_M(ctx.m); } return (ctx.error); } /* * Shutdown node */ static int ng_bridge_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); /* * Shut down everything including the timer. Even if the * callout has already been dequeued and is about to be * run, ng_bridge_timeout() won't be fired as the node * is already marked NGF_INVALID, so we're safe to free * the node now. */ KASSERT(priv->numLinks == 0 && priv->numHosts == 0, ("%s: numLinks=%d numHosts=%d", __func__, priv->numLinks, priv->numHosts)); ng_uncallout(&priv->timer, node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); free(priv->tab, M_NETGRAPH_BRIDGE); free(priv, M_NETGRAPH_BRIDGE); return (0); } /* * Hook disconnection. */ static int ng_bridge_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); link_p link = NG_HOOK_PRIVATE(hook); /* Remove all hosts associated with this link */ ng_bridge_remove_hosts(priv, link); /* Free associated link information */ ng_bridge_free_link(link); priv->numLinks--; /* If no more hooks, go away */ if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))) && !priv->persistent) { ng_rmnode_self(NG_HOOK_NODE(hook)); } return (0); } /****************************************************************** HASH TABLE FUNCTIONS ******************************************************************/ /* * Hash algorithm */ #define HASH(addr,mask) ( (((const u_int16_t *)(addr))[0] \ ^ ((const u_int16_t *)(addr))[1] \ ^ ((const u_int16_t *)(addr))[2]) & (mask) ) /* * Find a host entry in the table. */ static struct ng_bridge_host * ng_bridge_get(priv_cp priv, const u_char *addr) { const int bucket = HASH(addr, priv->hashMask); struct ng_bridge_host *host; SLIST_FOREACH(host, &priv->tab[bucket], next) { if (ETHER_EQUAL(host->addr, addr)) return (host); } return (NULL); } /* * Add a host entry to the table. If it already exists, move it * to the new link. Returns 0 on success. */ static int ng_bridge_put(priv_p priv, const u_char *addr, link_p link) { const int bucket = HASH(addr, priv->hashMask); struct ng_bridge_host *host; if ((host = ng_bridge_get(priv, addr)) != NULL) { /* Host already on the correct link? */ if (host->link == link) return 0; /* Move old host over to new link */ if (host->age >= priv->conf.minStableAge) { host->link = link; host->age = 0; return (0); } /* * If the host was recently moved to the old link and * it's now jumping to a new link, declare a loopback * condition. */ if (priv->conf.debugLevel >= 2) log(LOG_WARNING, "ng_bridge: %s:" " loopback detected on %s\n", ng_bridge_nodename(priv->node), NG_HOOK_NAME(link->hook)); /* Mark link as linka non grata */ link->loopCount = priv->conf.loopTimeout; link->stats.loopDetects++; /* Forget all hosts on this link */ ng_bridge_remove_hosts(priv, link); return (ELOOP); } /* Allocate and initialize new hashtable entry */ host = malloc(sizeof(*host), M_NETGRAPH_BRIDGE, M_NOWAIT); if (host == NULL) return (ENOMEM); bcopy(addr, host->addr, ETHER_ADDR_LEN); host->link = link; host->staleness = 0; host->age = 0; /* Add new element to hash bucket */ SLIST_INSERT_HEAD(&priv->tab[bucket], host, next); priv->numHosts++; /* Resize table if necessary */ ng_bridge_rehash(priv); return (0); } /* * Resize the hash table. We try to maintain the number of buckets * such that the load factor is in the range 0.25 to 1.0. * * If we can't get the new memory then we silently fail. This is OK * because things will still work and we'll try again soon anyway. */ static void ng_bridge_rehash(priv_p priv) { struct ng_bridge_bucket *newTab; int oldBucket, newBucket; int newNumBuckets; u_int newMask; /* Is table too full or too empty? */ if (priv->numHosts > priv->numBuckets && (priv->numBuckets << 1) <= MAX_BUCKETS) newNumBuckets = priv->numBuckets << 1; else if (priv->numHosts < (priv->numBuckets >> 2) && (priv->numBuckets >> 2) >= MIN_BUCKETS) newNumBuckets = priv->numBuckets >> 2; else return; newMask = newNumBuckets - 1; /* Allocate and initialize new table */ newTab = malloc(newNumBuckets * sizeof(*newTab), M_NETGRAPH_BRIDGE, M_NOWAIT | M_ZERO); if (newTab == NULL) return; /* Move all entries from old table to new table */ for (oldBucket = 0; oldBucket < priv->numBuckets; oldBucket++) { struct ng_bridge_bucket *const oldList = &priv->tab[oldBucket]; while (!SLIST_EMPTY(oldList)) { struct ng_bridge_host *const host = SLIST_FIRST(oldList); SLIST_REMOVE_HEAD(oldList, next); newBucket = HASH(host->addr, newMask); SLIST_INSERT_HEAD(&newTab[newBucket], host, next); } } /* Replace old table with new one */ if (priv->conf.debugLevel >= 3) { log(LOG_INFO, "ng_bridge: %s: table size %d -> %d\n", ng_bridge_nodename(priv->node), priv->numBuckets, newNumBuckets); } free(priv->tab, M_NETGRAPH_BRIDGE); priv->numBuckets = newNumBuckets; priv->hashMask = newMask; priv->tab = newTab; return; } /****************************************************************** MISC FUNCTIONS ******************************************************************/ /* * Remove all hosts associated with a specific link from the hashtable. * If linkNum == -1, then remove all hosts in the table. */ static void ng_bridge_remove_hosts(priv_p priv, link_p link) { int bucket; for (bucket = 0; bucket < priv->numBuckets; bucket++) { struct ng_bridge_host **hptr = &SLIST_FIRST(&priv->tab[bucket]); while (*hptr != NULL) { struct ng_bridge_host *const host = *hptr; if (link == NULL || host->link == link) { *hptr = SLIST_NEXT(host, next); free(host, M_NETGRAPH_BRIDGE); priv->numHosts--; } else hptr = &SLIST_NEXT(host, next); } } } /* * Handle our once-per-second timeout event. We do two things: * we decrement link->loopCount for those links being muted due to * a detected loopback condition, and we remove any hosts from * the hashtable whom we haven't heard from in a long while. */ static int ng_bridge_unmute(hook_p hook, void *arg) { link_p link = NG_HOOK_PRIVATE(hook); node_p node = NG_HOOK_NODE(hook); priv_p priv = NG_NODE_PRIVATE(node); int *counter = arg; if (link->loopCount != 0) { link->loopCount--; if (link->loopCount == 0 && priv->conf.debugLevel >= 2) { log(LOG_INFO, "ng_bridge: %s:" " restoring looped back %s\n", ng_bridge_nodename(node), NG_HOOK_NAME(hook)); } } (*counter)++; return (1); } static void ng_bridge_timeout(node_p node, hook_p hook, void *arg1, int arg2) { const priv_p priv = NG_NODE_PRIVATE(node); int bucket; int counter = 0; - hook_p ret; /* Update host time counters and remove stale entries */ for (bucket = 0; bucket < priv->numBuckets; bucket++) { struct ng_bridge_host **hptr = &SLIST_FIRST(&priv->tab[bucket]); while (*hptr != NULL) { struct ng_bridge_host *const host = *hptr; /* Remove hosts we haven't heard from in a while */ if (++host->staleness >= priv->conf.maxStaleness) { *hptr = SLIST_NEXT(host, next); free(host, M_NETGRAPH_BRIDGE); priv->numHosts--; } else { if (host->age < 0xffff) host->age++; hptr = &SLIST_NEXT(host, next); counter++; } } } KASSERT(priv->numHosts == counter, ("%s: hosts: %d != %d", __func__, priv->numHosts, counter)); /* Decrease table size if necessary */ ng_bridge_rehash(priv); /* Decrease loop counter on muted looped back links */ counter = 0; - NG_NODE_FOREACH_HOOK(node, ng_bridge_unmute, &counter, ret); + NG_NODE_FOREACH_HOOK(node, ng_bridge_unmute, &counter); KASSERT(priv->numLinks == counter, ("%s: links: %d != %d", __func__, priv->numLinks, counter)); /* Register a new timeout, keeping the existing node reference */ ng_callout(&priv->timer, node, NULL, hz, ng_bridge_timeout, NULL, 0); } /* * Return node's "name", even if it doesn't have one. */ static const char * ng_bridge_nodename(node_cp node) { static char name[NG_NODESIZ]; if (NG_NODE_HAS_NAME(node)) snprintf(name, sizeof(name), "%s", NG_NODE_NAME(node)); else snprintf(name, sizeof(name), "[%x]", ng_node2ID(node)); return name; } diff --git a/sys/netgraph/ng_l2tp.c b/sys/netgraph/ng_l2tp.c index 94617fa4dad9..edb38e4540da 100644 --- a/sys/netgraph/ng_l2tp.c +++ b/sys/netgraph/ng_l2tp.c @@ -1,1607 +1,1606 @@ /*- * Copyright (c) 2001-2002 Packet Design, LLC. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, * use and redistribution of this software, in source or object code * forms, with or without modifications are expressly permitted by * Packet Design; provided, however, that: * * (i) Any and all reproductions of the source or object code * must include the copyright notice above and the following * disclaimer of warranties; and * (ii) No rights are granted, in any manner or form, to use * Packet Design trademarks, including the mark "PACKET DESIGN" * on advertising, endorsements, or otherwise except as such * appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, * OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE, * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, * RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 PACKET DESIGN IS ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. * * Author: Archie Cobbs * * $FreeBSD$ */ /* * L2TP netgraph node type. * * This node type implements the lower layer of the * L2TP protocol as specified in RFC 2661. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NG_SEPARATE_MALLOC static MALLOC_DEFINE(M_NETGRAPH_L2TP, "netgraph_l2tp", "netgraph l2tp node"); #else #define M_NETGRAPH_L2TP M_NETGRAPH #endif /* L2TP header format (first 2 bytes only) */ #define L2TP_HDR_CTRL 0x8000 /* control packet */ #define L2TP_HDR_LEN 0x4000 /* has length field */ #define L2TP_HDR_SEQ 0x0800 /* has ns, nr fields */ #define L2TP_HDR_OFF 0x0200 /* has offset field */ #define L2TP_HDR_PRIO 0x0100 /* give priority */ #define L2TP_HDR_VERS_MASK 0x000f /* version field mask */ #define L2TP_HDR_VERSION 0x0002 /* version field */ /* Bits that must be zero or one in first two bytes of header */ #define L2TP_CTRL_0BITS 0x030d /* ctrl: must be 0 */ #define L2TP_CTRL_1BITS 0xc802 /* ctrl: must be 1 */ #define L2TP_DATA_0BITS 0x800d /* data: must be 0 */ #define L2TP_DATA_1BITS 0x0002 /* data: must be 1 */ /* Standard xmit ctrl and data header bits */ #define L2TP_CTRL_HDR (L2TP_HDR_CTRL | L2TP_HDR_LEN \ | L2TP_HDR_SEQ | L2TP_HDR_VERSION) #define L2TP_DATA_HDR (L2TP_HDR_VERSION) /* optional: len, seq */ /* Some hard coded values */ #define L2TP_MAX_XWIN 128 /* my max xmit window */ #define L2TP_MAX_REXMIT 5 /* default max rexmit */ #define L2TP_MAX_REXMIT_TO 30 /* default rexmit to */ #define L2TP_DELAYED_ACK ((hz + 19) / 20) /* delayed ack: 50 ms */ /* Default data sequence number configuration for new sessions */ #define L2TP_CONTROL_DSEQ 1 /* we are the lns */ #define L2TP_ENABLE_DSEQ 1 /* enable data seq # */ /* Compare sequence numbers using circular math */ #define L2TP_SEQ_DIFF(x, y) ((int16_t)((x) - (y))) #define SESSHASHSIZE 0x0020 #define SESSHASH(x) (((x) ^ ((x) >> 8)) & (SESSHASHSIZE - 1)) /* Hook private data (data session hooks only) */ struct ng_l2tp_hook_private { struct ng_l2tp_sess_config conf; /* hook/session config */ struct ng_l2tp_session_stats stats; /* per sessions statistics */ hook_p hook; /* hook reference */ u_int16_t ns; /* data ns sequence number */ u_int16_t nr; /* data nr sequence number */ LIST_ENTRY(ng_l2tp_hook_private) sessions; }; typedef struct ng_l2tp_hook_private *hookpriv_p; /* * Sequence number state * * Invariants: * - If cwnd < ssth, we're doing slow start, otherwise congestion avoidance * - The number of unacknowledged xmit packets is (ns - rack) <= seq->wmax * - The first (ns - rack) mbuf's in xwin[] array are copies of these * unacknowledged packets; the remainder of xwin[] consists first of * zero or more further untransmitted packets in the transmit queue * - We try to keep the peer's receive window as full as possible. * Therefore, (i < cwnd && xwin[i] != NULL) implies (ns - rack) > i. * - rack_timer is running iff (ns - rack) > 0 (unack'd xmit'd pkts) * - If xack != nr, there are unacknowledged recv packet(s) (delayed ack) * - xack_timer is running iff xack != nr (unack'd rec'd pkts) */ struct l2tp_seq { u_int16_t ns; /* next xmit seq we send */ u_int16_t nr; /* next recv seq we expect */ u_int16_t inproc; /* packet is in processing */ u_int16_t rack; /* last 'nr' we rec'd */ u_int16_t xack; /* last 'nr' we sent */ u_int16_t wmax; /* peer's max recv window */ u_int16_t cwnd; /* current congestion window */ u_int16_t ssth; /* slow start threshold */ u_int16_t acks; /* # consecutive acks rec'd */ u_int16_t rexmits; /* # retransmits sent */ struct callout rack_timer; /* retransmit timer */ struct callout xack_timer; /* delayed ack timer */ struct mbuf *xwin[L2TP_MAX_XWIN]; /* transmit window */ struct mtx mtx; /* seq mutex */ }; /* Node private data */ struct ng_l2tp_private { node_p node; /* back pointer to node */ hook_p ctrl; /* hook to upper layers */ hook_p lower; /* hook to lower layers */ struct ng_l2tp_config conf; /* node configuration */ struct ng_l2tp_stats stats; /* node statistics */ struct l2tp_seq seq; /* ctrl sequence number state */ ng_ID_t ftarget; /* failure message target */ LIST_HEAD(, ng_l2tp_hook_private) sesshash[SESSHASHSIZE]; }; typedef struct ng_l2tp_private *priv_p; /* Netgraph node methods */ static ng_constructor_t ng_l2tp_constructor; static ng_rcvmsg_t ng_l2tp_rcvmsg; static ng_shutdown_t ng_l2tp_shutdown; static ng_newhook_t ng_l2tp_newhook; static ng_rcvdata_t ng_l2tp_rcvdata; static ng_rcvdata_t ng_l2tp_rcvdata_lower; static ng_rcvdata_t ng_l2tp_rcvdata_ctrl; static ng_disconnect_t ng_l2tp_disconnect; /* Internal functions */ static int ng_l2tp_xmit_ctrl(priv_p priv, struct mbuf *m, u_int16_t ns); static void ng_l2tp_seq_init(priv_p priv); static int ng_l2tp_seq_set(priv_p priv, const struct ng_l2tp_seq_config *conf); static int ng_l2tp_seq_adjust(priv_p priv, const struct ng_l2tp_config *conf); static void ng_l2tp_seq_reset(priv_p priv); static void ng_l2tp_seq_failure(priv_p priv); static void ng_l2tp_seq_recv_nr(priv_p priv, u_int16_t nr); static void ng_l2tp_seq_xack_timeout(void *); static void ng_l2tp_seq_rack_timeout(void *); static hookpriv_p ng_l2tp_find_session(priv_p privp, u_int16_t sid); static ng_fn_eachhook ng_l2tp_reset_session; /* Parse type for struct ng_l2tp_seq_config. */ static const struct ng_parse_struct_field ng_l2tp_seq_config_fields[] = NG_L2TP_SEQ_CONFIG_TYPE_INFO; static const struct ng_parse_type ng_l2tp_seq_config_type = { &ng_parse_struct_type, &ng_l2tp_seq_config_fields }; /* Parse type for struct ng_l2tp_config */ static const struct ng_parse_struct_field ng_l2tp_config_type_fields[] = NG_L2TP_CONFIG_TYPE_INFO; static const struct ng_parse_type ng_l2tp_config_type = { &ng_parse_struct_type, &ng_l2tp_config_type_fields, }; /* Parse type for struct ng_l2tp_sess_config */ static const struct ng_parse_struct_field ng_l2tp_sess_config_type_fields[] = NG_L2TP_SESS_CONFIG_TYPE_INFO; static const struct ng_parse_type ng_l2tp_sess_config_type = { &ng_parse_struct_type, &ng_l2tp_sess_config_type_fields, }; /* Parse type for struct ng_l2tp_stats */ static const struct ng_parse_struct_field ng_l2tp_stats_type_fields[] = NG_L2TP_STATS_TYPE_INFO; static const struct ng_parse_type ng_l2tp_stats_type = { &ng_parse_struct_type, &ng_l2tp_stats_type_fields }; /* Parse type for struct ng_l2tp_session_stats. */ static const struct ng_parse_struct_field ng_l2tp_session_stats_type_fields[] = NG_L2TP_SESSION_STATS_TYPE_INFO; static const struct ng_parse_type ng_l2tp_session_stats_type = { &ng_parse_struct_type, &ng_l2tp_session_stats_type_fields }; /* List of commands and how to convert arguments to/from ASCII */ static const struct ng_cmdlist ng_l2tp_cmdlist[] = { { NGM_L2TP_COOKIE, NGM_L2TP_SET_CONFIG, "setconfig", &ng_l2tp_config_type, NULL }, { NGM_L2TP_COOKIE, NGM_L2TP_GET_CONFIG, "getconfig", NULL, &ng_l2tp_config_type }, { NGM_L2TP_COOKIE, NGM_L2TP_SET_SESS_CONFIG, "setsessconfig", &ng_l2tp_sess_config_type, NULL }, { NGM_L2TP_COOKIE, NGM_L2TP_GET_SESS_CONFIG, "getsessconfig", &ng_parse_hint16_type, &ng_l2tp_sess_config_type }, { NGM_L2TP_COOKIE, NGM_L2TP_GET_STATS, "getstats", NULL, &ng_l2tp_stats_type }, { NGM_L2TP_COOKIE, NGM_L2TP_CLR_STATS, "clrstats", NULL, NULL }, { NGM_L2TP_COOKIE, NGM_L2TP_GETCLR_STATS, "getclrstats", NULL, &ng_l2tp_stats_type }, { NGM_L2TP_COOKIE, NGM_L2TP_GET_SESSION_STATS, "getsessstats", &ng_parse_int16_type, &ng_l2tp_session_stats_type }, { NGM_L2TP_COOKIE, NGM_L2TP_CLR_SESSION_STATS, "clrsessstats", &ng_parse_int16_type, NULL }, { NGM_L2TP_COOKIE, NGM_L2TP_GETCLR_SESSION_STATS, "getclrsessstats", &ng_parse_int16_type, &ng_l2tp_session_stats_type }, { NGM_L2TP_COOKIE, NGM_L2TP_ACK_FAILURE, "ackfailure", NULL, NULL }, { NGM_L2TP_COOKIE, NGM_L2TP_SET_SEQ, "setsequence", &ng_l2tp_seq_config_type, NULL }, { 0 } }; /* Node type descriptor */ static struct ng_type ng_l2tp_typestruct = { .version = NG_ABI_VERSION, .name = NG_L2TP_NODE_TYPE, .constructor = ng_l2tp_constructor, .rcvmsg = ng_l2tp_rcvmsg, .shutdown = ng_l2tp_shutdown, .newhook = ng_l2tp_newhook, .rcvdata = ng_l2tp_rcvdata, .disconnect = ng_l2tp_disconnect, .cmdlist = ng_l2tp_cmdlist, }; NETGRAPH_INIT(l2tp, &ng_l2tp_typestruct); /* Sequence number state locking & sanity checking */ #ifdef INVARIANTS static void ng_l2tp_seq_check(struct l2tp_seq *seq); #define SEQ_LOCK(seq) do { \ mtx_lock(&(seq)->mtx); \ ng_l2tp_seq_check(seq); \ } while (0) #define SEQ_UNLOCK(seq) do { \ ng_l2tp_seq_check(seq); \ mtx_unlock(&(seq)->mtx); \ } while (0) #else #define SEQ_LOCK(seq) mtx_lock(&(seq)->mtx) #define SEQ_UNLOCK(seq) mtx_unlock(&(seq)->mtx) #endif #define SEQ_LOCK_ASSERT(seq) mtx_assert(&(seq)->mtx, MA_OWNED) /* Whether to use m_copypacket() or m_dup() */ #define L2TP_COPY_MBUF m_copypacket #define ERROUT(x) do { error = (x); goto done; } while (0) /************************************************************************ NETGRAPH NODE STUFF ************************************************************************/ /* * Node type constructor */ static int ng_l2tp_constructor(node_p node) { priv_p priv; int i; /* Allocate private structure */ priv = malloc(sizeof(*priv), M_NETGRAPH_L2TP, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, priv); priv->node = node; /* Apply a semi-reasonable default configuration */ priv->conf.peer_win = 1; priv->conf.rexmit_max = L2TP_MAX_REXMIT; priv->conf.rexmit_max_to = L2TP_MAX_REXMIT_TO; /* Initialize sequence number state */ ng_l2tp_seq_init(priv); for (i = 0; i < SESSHASHSIZE; i++) LIST_INIT(&priv->sesshash[i]); /* Done */ return (0); } /* * Give our OK for a hook to be added. */ static int ng_l2tp_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); /* Check hook name */ if (strcmp(name, NG_L2TP_HOOK_CTRL) == 0) { if (priv->ctrl != NULL) return (EISCONN); priv->ctrl = hook; NG_HOOK_SET_RCVDATA(hook, ng_l2tp_rcvdata_ctrl); } else if (strcmp(name, NG_L2TP_HOOK_LOWER) == 0) { if (priv->lower != NULL) return (EISCONN); priv->lower = hook; NG_HOOK_SET_RCVDATA(hook, ng_l2tp_rcvdata_lower); } else { static const char hexdig[16] = "0123456789abcdef"; u_int16_t session_id; hookpriv_p hpriv; uint16_t hash; const char *hex; int i; int j; /* Parse hook name to get session ID */ if (strncmp(name, NG_L2TP_HOOK_SESSION_P, sizeof(NG_L2TP_HOOK_SESSION_P) - 1) != 0) return (EINVAL); hex = name + sizeof(NG_L2TP_HOOK_SESSION_P) - 1; for (session_id = i = 0; i < 4; i++) { for (j = 0; j < 16 && hex[i] != hexdig[j]; j++); if (j == 16) return (EINVAL); session_id = (session_id << 4) | j; } if (hex[i] != '\0') return (EINVAL); /* Create hook private structure */ hpriv = malloc(sizeof(*hpriv), M_NETGRAPH_L2TP, M_NOWAIT | M_ZERO); if (hpriv == NULL) return (ENOMEM); hpriv->conf.session_id = session_id; hpriv->conf.control_dseq = L2TP_CONTROL_DSEQ; hpriv->conf.enable_dseq = L2TP_ENABLE_DSEQ; hpriv->hook = hook; NG_HOOK_SET_PRIVATE(hook, hpriv); hash = SESSHASH(hpriv->conf.session_id); LIST_INSERT_HEAD(&priv->sesshash[hash], hpriv, sessions); } /* Done */ return (0); } /* * Receive a control message. */ static int ng_l2tp_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); struct ng_mesg *resp = NULL; struct ng_mesg *msg; int error = 0; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_L2TP_COOKIE: switch (msg->header.cmd) { case NGM_L2TP_SET_CONFIG: { struct ng_l2tp_config *const conf = (struct ng_l2tp_config *)msg->data; /* Check for invalid or illegal config */ if (msg->header.arglen != sizeof(*conf)) { error = EINVAL; break; } conf->enabled = !!conf->enabled; conf->match_id = !!conf->match_id; if (priv->conf.enabled && ((priv->conf.tunnel_id != 0 && conf->tunnel_id != priv->conf.tunnel_id) || ((priv->conf.peer_id != 0 && conf->peer_id != priv->conf.peer_id)))) { error = EBUSY; break; } /* Save calling node as failure target */ priv->ftarget = NGI_RETADDR(item); /* Adjust sequence number state */ if ((error = ng_l2tp_seq_adjust(priv, conf)) != 0) break; /* Update node's config */ priv->conf = *conf; break; } case NGM_L2TP_GET_CONFIG: { struct ng_l2tp_config *conf; NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_l2tp_config *)resp->data; *conf = priv->conf; break; } case NGM_L2TP_SET_SESS_CONFIG: { struct ng_l2tp_sess_config *const conf = (struct ng_l2tp_sess_config *)msg->data; hookpriv_p hpriv; /* Check for invalid or illegal config. */ if (msg->header.arglen != sizeof(*conf)) { error = EINVAL; break; } /* Find matching hook */ hpriv = ng_l2tp_find_session(priv, conf->session_id); if (hpriv == NULL) { error = ENOENT; break; } /* Update hook's config */ hpriv->conf = *conf; break; } case NGM_L2TP_GET_SESS_CONFIG: { struct ng_l2tp_sess_config *conf; u_int16_t session_id; hookpriv_p hpriv; /* Get session ID */ if (msg->header.arglen != sizeof(session_id)) { error = EINVAL; break; } memcpy(&session_id, msg->data, 2); /* Find matching hook */ hpriv = ng_l2tp_find_session(priv, session_id); if (hpriv == NULL) { error = ENOENT; break; } /* Send response */ NG_MKRESPONSE(resp, msg, sizeof(hpriv->conf), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } conf = (struct ng_l2tp_sess_config *)resp->data; *conf = hpriv->conf; break; } case NGM_L2TP_GET_STATS: case NGM_L2TP_CLR_STATS: case NGM_L2TP_GETCLR_STATS: { if (msg->header.cmd != NGM_L2TP_CLR_STATS) { NG_MKRESPONSE(resp, msg, sizeof(priv->stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } memcpy(resp->data, &priv->stats, sizeof(priv->stats)); } if (msg->header.cmd != NGM_L2TP_GET_STATS) memset(&priv->stats, 0, sizeof(priv->stats)); break; } case NGM_L2TP_GET_SESSION_STATS: case NGM_L2TP_CLR_SESSION_STATS: case NGM_L2TP_GETCLR_SESSION_STATS: { uint16_t session_id; hookpriv_p hpriv; /* Get session ID. */ if (msg->header.arglen != sizeof(session_id)) { error = EINVAL; break; } bcopy(msg->data, &session_id, sizeof(uint16_t)); /* Find matching hook. */ hpriv = ng_l2tp_find_session(priv, session_id); if (hpriv == NULL) { error = ENOENT; break; } if (msg->header.cmd != NGM_L2TP_CLR_SESSION_STATS) { NG_MKRESPONSE(resp, msg, sizeof(hpriv->stats), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } bcopy(&hpriv->stats, resp->data, sizeof(hpriv->stats)); } if (msg->header.cmd != NGM_L2TP_GET_SESSION_STATS) bzero(&hpriv->stats, sizeof(hpriv->stats)); break; } case NGM_L2TP_SET_SEQ: { struct ng_l2tp_seq_config *const conf = (struct ng_l2tp_seq_config *)msg->data; /* Check for invalid or illegal seq config. */ if (msg->header.arglen != sizeof(*conf)) { error = EINVAL; break; } conf->ns = htons(conf->ns); conf->nr = htons(conf->nr); conf->rack = htons(conf->rack); conf->xack = htons(conf->xack); /* Set sequence numbers. */ error = ng_l2tp_seq_set(priv, conf); break; } default: error = EINVAL; break; } break; default: error = EINVAL; break; } /* Done */ NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Destroy node */ static int ng_l2tp_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); struct l2tp_seq *const seq = &priv->seq; /* Reset sequence number state */ SEQ_LOCK(seq); ng_l2tp_seq_reset(priv); SEQ_UNLOCK(seq); /* Free private data if neither timer is running */ callout_drain(&seq->rack_timer); callout_drain(&seq->xack_timer); mtx_destroy(&seq->mtx); free(priv, M_NETGRAPH_L2TP); /* Unref node */ NG_NODE_UNREF(node); return (0); } /* * Hook disconnection */ static int ng_l2tp_disconnect(hook_p hook) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); /* Zero out hook pointer */ if (hook == priv->ctrl) priv->ctrl = NULL; else if (hook == priv->lower) priv->lower = NULL; else { const hookpriv_p hpriv = NG_HOOK_PRIVATE(hook); LIST_REMOVE(hpriv, sessions); free(hpriv, M_NETGRAPH_L2TP); NG_HOOK_SET_PRIVATE(hook, NULL); } /* Go away if no longer connected to anything */ if (NG_NODE_NUMHOOKS(node) == 0 && NG_NODE_IS_VALID(node)) ng_rmnode_self(node); return (0); } /************************************************************************* INTERNAL FUNCTIONS *************************************************************************/ /* * Find the hook with a given session ID. */ static hookpriv_p ng_l2tp_find_session(priv_p privp, u_int16_t sid) { uint16_t hash = SESSHASH(sid); hookpriv_p hpriv = NULL; LIST_FOREACH(hpriv, &privp->sesshash[hash], sessions) { if (hpriv->conf.session_id == sid) break; } return (hpriv); } /* * Reset a hook's session state. */ static int ng_l2tp_reset_session(hook_p hook, void *arg) { const hookpriv_p hpriv = NG_HOOK_PRIVATE(hook); if (hpriv != NULL) { hpriv->conf.control_dseq = 0; hpriv->conf.enable_dseq = 0; bzero(&hpriv->stats, sizeof(struct ng_l2tp_session_stats)); hpriv->nr = 0; hpriv->ns = 0; } return (-1); } /* * Handle an incoming frame from below. */ static int ng_l2tp_rcvdata_lower(hook_p h, item_p item) { static const u_int16_t req_bits[2][2] = { { L2TP_DATA_0BITS, L2TP_DATA_1BITS }, { L2TP_CTRL_0BITS, L2TP_CTRL_1BITS }, }; const node_p node = NG_HOOK_NODE(h); const priv_p priv = NG_NODE_PRIVATE(node); hookpriv_p hpriv = NULL; hook_p hook = NULL; struct mbuf *m; u_int16_t tid, sid; u_int16_t hdr; u_int16_t ns, nr; int is_ctrl; int error; int len, plen; /* If not configured, reject */ if (!priv->conf.enabled) { NG_FREE_ITEM(item); ERROUT(ENXIO); } /* Grab mbuf */ NGI_GET_M(item, m); /* Remember full packet length; needed for per session accounting. */ plen = m->m_pkthdr.len; /* Update stats */ priv->stats.recvPackets++; priv->stats.recvOctets += plen; /* Get initial header */ if (m->m_pkthdr.len < 6) { priv->stats.recvRunts++; NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(EINVAL); } if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); ERROUT(EINVAL); } hdr = (mtod(m, uint8_t *)[0] << 8) + mtod(m, uint8_t *)[1]; m_adj(m, 2); /* Check required header bits and minimum length */ is_ctrl = (hdr & L2TP_HDR_CTRL) != 0; if ((hdr & req_bits[is_ctrl][0]) != 0 || (~hdr & req_bits[is_ctrl][1]) != 0) { priv->stats.recvInvalid++; NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(EINVAL); } if (m->m_pkthdr.len < 4 /* tunnel, session id */ + (2 * ((hdr & L2TP_HDR_LEN) != 0)) /* length field */ + (4 * ((hdr & L2TP_HDR_SEQ) != 0)) /* seq # fields */ + (2 * ((hdr & L2TP_HDR_OFF) != 0))) { /* offset field */ priv->stats.recvRunts++; NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(EINVAL); } /* Get and validate length field if present */ if ((hdr & L2TP_HDR_LEN) != 0) { if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); ERROUT(EINVAL); } len = (mtod(m, uint8_t *)[0] << 8) + mtod(m, uint8_t *)[1] - 4; m_adj(m, 2); if (len < 0 || len > m->m_pkthdr.len) { priv->stats.recvInvalid++; NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(EINVAL); } if (len < m->m_pkthdr.len) /* trim extra bytes */ m_adj(m, -(m->m_pkthdr.len - len)); } /* Get tunnel ID and session ID */ if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); ERROUT(EINVAL); } tid = (mtod(m, u_int8_t *)[0] << 8) + mtod(m, u_int8_t *)[1]; sid = (mtod(m, u_int8_t *)[2] << 8) + mtod(m, u_int8_t *)[3]; m_adj(m, 4); /* Check tunnel ID */ if (tid != priv->conf.tunnel_id && (priv->conf.match_id || tid != 0)) { priv->stats.recvWrongTunnel++; NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(EADDRNOTAVAIL); } /* Check session ID (for data packets only) */ if ((hdr & L2TP_HDR_CTRL) == 0) { hpriv = ng_l2tp_find_session(priv, sid); if (hpriv == NULL) { priv->stats.recvUnknownSID++; NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(ENOTCONN); } hook = hpriv->hook; } /* Get Ns, Nr fields if present */ if ((hdr & L2TP_HDR_SEQ) != 0) { if (m->m_len < 4 && (m = m_pullup(m, 4)) == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); ERROUT(EINVAL); } ns = (mtod(m, u_int8_t *)[0] << 8) + mtod(m, u_int8_t *)[1]; nr = (mtod(m, u_int8_t *)[2] << 8) + mtod(m, u_int8_t *)[3]; m_adj(m, 4); } else ns = nr = 0; /* Strip offset padding if present */ if ((hdr & L2TP_HDR_OFF) != 0) { u_int16_t offset; /* Get length of offset padding */ if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); ERROUT(EINVAL); } offset = (mtod(m, u_int8_t *)[0] << 8) + mtod(m, u_int8_t *)[1]; /* Trim offset padding */ if ((2+offset) > m->m_pkthdr.len) { priv->stats.recvInvalid++; NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(EINVAL); } m_adj(m, 2+offset); } /* Handle control packets */ if ((hdr & L2TP_HDR_CTRL) != 0) { struct l2tp_seq *const seq = &priv->seq; SEQ_LOCK(seq); /* Handle receive ack sequence number Nr */ ng_l2tp_seq_recv_nr(priv, nr); /* Discard ZLB packets */ if (m->m_pkthdr.len == 0) { SEQ_UNLOCK(seq); priv->stats.recvZLBs++; NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(0); } /* * If not what we expect or we are busy, drop packet and * send an immediate ZLB ack. */ if (ns != seq->nr || seq->inproc) { if (L2TP_SEQ_DIFF(ns, seq->nr) <= 0) priv->stats.recvDuplicates++; else priv->stats.recvOutOfOrder++; ng_l2tp_xmit_ctrl(priv, NULL, seq->ns); NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(0); } /* Prepend session ID to packet. */ M_PREPEND(m, 2, M_NOWAIT); if (m == NULL) { SEQ_UNLOCK(seq); priv->stats.memoryFailures++; NG_FREE_ITEM(item); ERROUT(ENOBUFS); } mtod(m, u_int8_t *)[0] = sid >> 8; mtod(m, u_int8_t *)[1] = sid & 0xff; /* * Until we deliver this packet we can't receive next one as * we have no information for sending ack. */ seq->inproc = 1; SEQ_UNLOCK(seq); /* Deliver packet to upper layers */ NG_FWD_NEW_DATA(error, item, priv->ctrl, m); SEQ_LOCK(seq); /* Ready to process next packet. */ seq->inproc = 0; /* If packet was successfully delivered send ack. */ if (error == 0) { /* Update recv sequence number */ seq->nr++; /* Start receive ack timer, if not already running */ if (!callout_active(&seq->xack_timer)) { callout_reset(&seq->xack_timer, L2TP_DELAYED_ACK, ng_l2tp_seq_xack_timeout, node); } } SEQ_UNLOCK(seq); ERROUT(error); } /* Per session packet, account it. */ hpriv->stats.recvPackets++; hpriv->stats.recvOctets += plen; /* Follow peer's lead in data sequencing, if configured to do so */ if (!hpriv->conf.control_dseq) hpriv->conf.enable_dseq = ((hdr & L2TP_HDR_SEQ) != 0); /* Handle data sequence numbers if present and enabled */ if ((hdr & L2TP_HDR_SEQ) != 0) { if (hpriv->conf.enable_dseq && L2TP_SEQ_DIFF(ns, hpriv->nr) < 0) { NG_FREE_ITEM(item); /* duplicate or out of order */ NG_FREE_M(m); priv->stats.recvDataDrops++; ERROUT(0); } hpriv->nr = ns + 1; } /* Drop empty data packets */ if (m->m_pkthdr.len == 0) { NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(0); } /* Deliver data */ NG_FWD_NEW_DATA(error, item, hook, m); done: return (error); } /* * Handle an outgoing control frame. */ static int ng_l2tp_rcvdata_ctrl(hook_p hook, item_p item) { const node_p node = NG_HOOK_NODE(hook); const priv_p priv = NG_NODE_PRIVATE(node); struct l2tp_seq *const seq = &priv->seq; struct mbuf *m; int error; int i; u_int16_t ns; /* If not configured, reject */ if (!priv->conf.enabled) { NG_FREE_ITEM(item); ERROUT(ENXIO); } /* Grab mbuf and discard other stuff XXX */ NGI_GET_M(item, m); NG_FREE_ITEM(item); /* Packet should have session ID prepended */ if (m->m_pkthdr.len < 2) { priv->stats.xmitInvalid++; m_freem(m); ERROUT(EINVAL); } /* Check max length */ if (m->m_pkthdr.len >= 0x10000 - 14) { priv->stats.xmitTooBig++; m_freem(m); ERROUT(EOVERFLOW); } SEQ_LOCK(seq); /* Find next empty slot in transmit queue */ for (i = 0; i < L2TP_MAX_XWIN && seq->xwin[i] != NULL; i++); if (i == L2TP_MAX_XWIN) { SEQ_UNLOCK(seq); priv->stats.xmitDrops++; m_freem(m); ERROUT(ENOBUFS); } seq->xwin[i] = m; /* If peer's receive window is already full, nothing else to do */ if (i >= seq->cwnd) { SEQ_UNLOCK(seq); ERROUT(0); } /* Start retransmit timer if not already running */ if (!callout_active(&seq->rack_timer)) callout_reset(&seq->rack_timer, hz, ng_l2tp_seq_rack_timeout, node); ns = seq->ns++; /* Copy packet */ if ((m = L2TP_COPY_MBUF(m, M_NOWAIT)) == NULL) { SEQ_UNLOCK(seq); priv->stats.memoryFailures++; ERROUT(ENOBUFS); } /* Send packet and increment xmit sequence number */ error = ng_l2tp_xmit_ctrl(priv, m, ns); done: return (error); } /* * Handle an outgoing data frame. */ static int ng_l2tp_rcvdata(hook_p hook, item_p item) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); const hookpriv_p hpriv = NG_HOOK_PRIVATE(hook); struct mbuf *m; uint8_t *p; u_int16_t hdr; int error; int i = 2; /* If not configured, reject */ if (!priv->conf.enabled) { NG_FREE_ITEM(item); ERROUT(ENXIO); } /* Get mbuf */ NGI_GET_M(item, m); /* Check max length */ if (m->m_pkthdr.len >= 0x10000 - 12) { priv->stats.xmitDataTooBig++; NG_FREE_ITEM(item); NG_FREE_M(m); ERROUT(EOVERFLOW); } /* Prepend L2TP header */ M_PREPEND(m, 6 + (2 * (hpriv->conf.include_length != 0)) + (4 * (hpriv->conf.enable_dseq != 0)), M_NOWAIT); if (m == NULL) { priv->stats.memoryFailures++; NG_FREE_ITEM(item); ERROUT(ENOBUFS); } p = mtod(m, uint8_t *); hdr = L2TP_DATA_HDR; if (hpriv->conf.include_length) { hdr |= L2TP_HDR_LEN; p[i++] = m->m_pkthdr.len >> 8; p[i++] = m->m_pkthdr.len & 0xff; } p[i++] = priv->conf.peer_id >> 8; p[i++] = priv->conf.peer_id & 0xff; p[i++] = hpriv->conf.peer_id >> 8; p[i++] = hpriv->conf.peer_id & 0xff; if (hpriv->conf.enable_dseq) { hdr |= L2TP_HDR_SEQ; p[i++] = hpriv->ns >> 8; p[i++] = hpriv->ns & 0xff; p[i++] = hpriv->nr >> 8; p[i++] = hpriv->nr & 0xff; hpriv->ns++; } p[0] = hdr >> 8; p[1] = hdr & 0xff; /* Update per session stats. */ hpriv->stats.xmitPackets++; hpriv->stats.xmitOctets += m->m_pkthdr.len; /* And the global one. */ priv->stats.xmitPackets++; priv->stats.xmitOctets += m->m_pkthdr.len; /* Send packet */ NG_FWD_NEW_DATA(error, item, priv->lower, m); done: return (error); } /* * Send a message to our controlling node that we've failed. */ static void ng_l2tp_seq_failure(priv_p priv) { struct ng_mesg *msg; int error; NG_MKMESSAGE(msg, NGM_L2TP_COOKIE, NGM_L2TP_ACK_FAILURE, 0, M_NOWAIT); if (msg == NULL) return; NG_SEND_MSG_ID(error, priv->node, msg, priv->ftarget, 0); } /************************************************************************ SEQUENCE NUMBER HANDLING ************************************************************************/ /* * Initialize sequence number state. */ static void ng_l2tp_seq_init(priv_p priv) { struct l2tp_seq *const seq = &priv->seq; KASSERT(priv->conf.peer_win >= 1, ("%s: peer_win is zero", __func__)); memset(seq, 0, sizeof(*seq)); seq->cwnd = 1; seq->wmax = priv->conf.peer_win; if (seq->wmax > L2TP_MAX_XWIN) seq->wmax = L2TP_MAX_XWIN; seq->ssth = seq->wmax; mtx_init(&seq->mtx, "ng_l2tp", NULL, MTX_DEF); callout_init_mtx(&seq->rack_timer, &seq->mtx, CALLOUT_RETURNUNLOCKED); callout_init_mtx(&seq->xack_timer, &seq->mtx, CALLOUT_RETURNUNLOCKED); } /* * Set sequence number state as given from user. */ static int ng_l2tp_seq_set(priv_p priv, const struct ng_l2tp_seq_config *conf) { struct l2tp_seq *const seq = &priv->seq; /* If node is enabled, deny update to sequence numbers. */ if (priv->conf.enabled) return (EBUSY); /* We only can handle the simple cases. */ if (conf->xack != conf->nr || conf->ns != conf->rack) return (EINVAL); /* Set ns,nr,rack,xack parameters. */ seq->ns = conf->ns; seq->nr = conf->nr; seq->rack = conf->rack; seq->xack = conf->xack; return (0); } /* * Adjust sequence number state accordingly after reconfiguration. */ static int ng_l2tp_seq_adjust(priv_p priv, const struct ng_l2tp_config *conf) { struct l2tp_seq *const seq = &priv->seq; u_int16_t new_wmax; int error = 0; SEQ_LOCK(seq); /* If disabling node, reset state sequence number */ if (!conf->enabled) { ng_l2tp_seq_reset(priv); SEQ_UNLOCK(seq); return (0); } /* Adjust peer's max recv window; it can only increase */ new_wmax = conf->peer_win; if (new_wmax > L2TP_MAX_XWIN) new_wmax = L2TP_MAX_XWIN; if (new_wmax == 0) ERROUT(EINVAL); if (new_wmax < seq->wmax) ERROUT(EBUSY); seq->wmax = new_wmax; done: SEQ_UNLOCK(seq); return (error); } /* * Reset sequence number state. */ static void ng_l2tp_seq_reset(priv_p priv) { struct l2tp_seq *const seq = &priv->seq; - hook_p hook; int i; SEQ_LOCK_ASSERT(seq); /* Stop timers */ (void )callout_stop(&seq->rack_timer); (void )callout_stop(&seq->xack_timer); /* Free retransmit queue */ for (i = 0; i < L2TP_MAX_XWIN; i++) { if (seq->xwin[i] == NULL) break; m_freem(seq->xwin[i]); } /* Reset session hooks' sequence number states */ - NG_NODE_FOREACH_HOOK(priv->node, ng_l2tp_reset_session, NULL, hook); + NG_NODE_FOREACH_HOOK(priv->node, ng_l2tp_reset_session, NULL); /* Reset node's sequence number state */ seq->ns = 0; seq->nr = 0; seq->rack = 0; seq->xack = 0; seq->wmax = L2TP_MAX_XWIN; seq->cwnd = 1; seq->ssth = seq->wmax; seq->acks = 0; seq->rexmits = 0; bzero(seq->xwin, sizeof(seq->xwin)); } /* * Handle receipt of an acknowledgement value (Nr) from peer. */ static void ng_l2tp_seq_recv_nr(priv_p priv, u_int16_t nr) { struct l2tp_seq *const seq = &priv->seq; struct mbuf *xwin[L2TP_MAX_XWIN]; /* partial local copy */ int nack; int i, j; uint16_t ns; SEQ_LOCK_ASSERT(seq); /* Verify peer's ACK is in range */ if ((nack = L2TP_SEQ_DIFF(nr, seq->rack)) <= 0) return; /* duplicate ack */ if (L2TP_SEQ_DIFF(nr, seq->ns) > 0) { priv->stats.recvBadAcks++; /* ack for packet not sent */ return; } KASSERT(nack <= L2TP_MAX_XWIN, ("%s: nack=%d > %d", __func__, nack, L2TP_MAX_XWIN)); /* Update receive ack stats */ seq->rack = nr; seq->rexmits = 0; /* Free acknowledged packets and shift up packets in the xmit queue */ for (i = 0; i < nack; i++) m_freem(seq->xwin[i]); memmove(seq->xwin, seq->xwin + nack, (L2TP_MAX_XWIN - nack) * sizeof(*seq->xwin)); memset(seq->xwin + (L2TP_MAX_XWIN - nack), 0, nack * sizeof(*seq->xwin)); /* * Do slow-start/congestion avoidance windowing algorithm described * in RFC 2661, Appendix A. Here we handle a multiple ACK as if each * ACK had arrived separately. */ if (seq->cwnd < seq->wmax) { /* Handle slow start phase */ if (seq->cwnd < seq->ssth) { seq->cwnd += nack; nack = 0; if (seq->cwnd > seq->ssth) { /* into cg.av. phase */ nack = seq->cwnd - seq->ssth; seq->cwnd = seq->ssth; } } /* Handle congestion avoidance phase */ if (seq->cwnd >= seq->ssth) { seq->acks += nack; while (seq->acks >= seq->cwnd) { seq->acks -= seq->cwnd; if (seq->cwnd < seq->wmax) seq->cwnd++; } } } /* Stop xmit timer */ if (callout_active(&seq->rack_timer)) (void )callout_stop(&seq->rack_timer); /* If transmit queue is empty, we're done for now */ if (seq->xwin[0] == NULL) return; /* Start restransmit timer again */ callout_reset(&seq->rack_timer, hz, ng_l2tp_seq_rack_timeout, priv->node); /* * Send more packets, trying to keep peer's receive window full. * Make copy of everything we need before lock release. */ ns = seq->ns; j = 0; while ((i = L2TP_SEQ_DIFF(seq->ns, seq->rack)) < seq->cwnd && seq->xwin[i] != NULL) { xwin[j++] = seq->xwin[i]; seq->ns++; } /* * Send prepared. * If there is a memory error, pretend packet was sent, as it * will get retransmitted later anyway. */ for (i = 0; i < j; i++) { struct mbuf *m; if ((m = L2TP_COPY_MBUF(xwin[i], M_NOWAIT)) == NULL) priv->stats.memoryFailures++; else { ng_l2tp_xmit_ctrl(priv, m, ns); SEQ_LOCK(seq); } ns++; } } /* * Handle an ack timeout. We have an outstanding ack that we * were hoping to piggy-back, but haven't, so send a ZLB. */ static void ng_l2tp_seq_xack_timeout(void *arg) { const node_p node = arg; const priv_p priv = NG_NODE_PRIVATE(node); struct epoch_tracker et; struct l2tp_seq *const seq = &priv->seq; SEQ_LOCK_ASSERT(seq); MPASS(!callout_pending(&seq->xack_timer)); MPASS(callout_active(&seq->xack_timer)); NET_EPOCH_ENTER(et); CURVNET_SET(node->nd_vnet); /* Send a ZLB */ ng_l2tp_xmit_ctrl(priv, NULL, seq->ns); CURVNET_RESTORE(); NET_EPOCH_EXIT(et); /* callout_deactivate() is not needed here as callout_stop() was called by ng_l2tp_xmit_ctrl() */ } /* * Handle a transmit timeout. The peer has failed to respond * with an ack for our packet, so retransmit it. */ static void ng_l2tp_seq_rack_timeout(void *arg) { const node_p node = arg; const priv_p priv = NG_NODE_PRIVATE(node); struct epoch_tracker et; struct l2tp_seq *const seq = &priv->seq; struct mbuf *m; u_int delay; SEQ_LOCK_ASSERT(seq); MPASS(seq->xwin[0]); MPASS(!callout_pending(&seq->rack_timer)); MPASS(callout_active(&seq->rack_timer)); NET_EPOCH_ENTER(et); CURVNET_SET(node->nd_vnet); priv->stats.xmitRetransmits++; /* Have we reached the retransmit limit? If so, notify owner. */ if (seq->rexmits++ >= priv->conf.rexmit_max) ng_l2tp_seq_failure(priv); /* Restart timer, this time with an increased delay */ delay = (seq->rexmits > 12) ? (1 << 12) : (1 << seq->rexmits); if (delay > priv->conf.rexmit_max_to) delay = priv->conf.rexmit_max_to; callout_reset(&seq->rack_timer, hz * delay, ng_l2tp_seq_rack_timeout, node); /* Do slow-start/congestion algorithm windowing algorithm */ seq->ns = seq->rack; seq->ssth = (seq->cwnd + 1) / 2; seq->cwnd = 1; seq->acks = 0; /* Retransmit oldest unack'd packet */ m = L2TP_COPY_MBUF(seq->xwin[0], M_NOWAIT); if (m == NULL) { SEQ_UNLOCK(seq); priv->stats.memoryFailures++; } else ng_l2tp_xmit_ctrl(priv, m, seq->ns++); CURVNET_RESTORE(); NET_EPOCH_EXIT(et); /* callout_deactivate() is not needed here as ng_callout() is getting called each time */ } /* * Transmit a control stream packet, payload optional. * The transmit sequence number is not incremented. * Requires seq lock, returns unlocked. */ static int ng_l2tp_xmit_ctrl(priv_p priv, struct mbuf *m, u_int16_t ns) { struct l2tp_seq *const seq = &priv->seq; uint8_t *p; uint16_t nr, session_id = 0; int error; SEQ_LOCK_ASSERT(seq); /* Stop ack timer: we're sending an ack with this packet. Doing this before to keep state predictable after error. */ if (callout_active(&seq->xack_timer)) (void )callout_stop(&seq->xack_timer); nr = seq->xack = seq->nr; SEQ_UNLOCK(seq); /* If no mbuf passed, send an empty packet (ZLB) */ if (m == NULL) { /* Create a new mbuf for ZLB packet */ MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { priv->stats.memoryFailures++; return (ENOBUFS); } m->m_len = m->m_pkthdr.len = 12; m->m_pkthdr.rcvif = NULL; priv->stats.xmitZLBs++; } else { /* Strip off session ID */ if (m->m_len < 2 && (m = m_pullup(m, 2)) == NULL) { priv->stats.memoryFailures++; return (ENOBUFS); } session_id = (mtod(m, u_int8_t *)[0] << 8) + mtod(m, u_int8_t *)[1]; /* Make room for L2TP header */ M_PREPEND(m, 10, M_NOWAIT); /* - 2 + 12 = 10 */ if (m == NULL) { priv->stats.memoryFailures++; return (ENOBUFS); } /* * The below requires 12 contiguous bytes for the L2TP header * to be written into. */ m = m_pullup(m, 12); if (m == NULL) { priv->stats.memoryFailures++; return (ENOBUFS); } } /* Fill in L2TP header */ p = mtod(m, u_int8_t *); p[0] = L2TP_CTRL_HDR >> 8; p[1] = L2TP_CTRL_HDR & 0xff; p[2] = m->m_pkthdr.len >> 8; p[3] = m->m_pkthdr.len & 0xff; p[4] = priv->conf.peer_id >> 8; p[5] = priv->conf.peer_id & 0xff; p[6] = session_id >> 8; p[7] = session_id & 0xff; p[8] = ns >> 8; p[9] = ns & 0xff; p[10] = nr >> 8; p[11] = nr & 0xff; /* Update sequence number info and stats */ priv->stats.xmitPackets++; priv->stats.xmitOctets += m->m_pkthdr.len; /* Send packet */ NG_SEND_DATA_ONLY(error, priv->lower, m); return (error); } #ifdef INVARIANTS /* * Sanity check sequence number state. */ static void ng_l2tp_seq_check(struct l2tp_seq *seq) { int self_unack, peer_unack; int i; #define CHECK(p) KASSERT((p), ("%s: not: %s", __func__, #p)) SEQ_LOCK_ASSERT(seq); self_unack = L2TP_SEQ_DIFF(seq->nr, seq->xack); peer_unack = L2TP_SEQ_DIFF(seq->ns, seq->rack); CHECK(seq->wmax <= L2TP_MAX_XWIN); CHECK(seq->cwnd >= 1); CHECK(seq->cwnd <= seq->wmax); CHECK(seq->ssth >= 1); CHECK(seq->ssth <= seq->wmax); if (seq->cwnd < seq->ssth) CHECK(seq->acks == 0); else CHECK(seq->acks <= seq->cwnd); CHECK(self_unack >= 0); CHECK(peer_unack >= 0); CHECK(peer_unack <= seq->wmax); CHECK((self_unack == 0) ^ callout_active(&seq->xack_timer)); CHECK((peer_unack == 0) ^ callout_active(&seq->rack_timer)); for (i = 0; i < peer_unack; i++) CHECK(seq->xwin[i] != NULL); for ( ; i < seq->cwnd; i++) /* verify peer's recv window full */ CHECK(seq->xwin[i] == NULL); #undef CHECK } #endif /* INVARIANTS */