#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ctype.h>
#include <sys/hash.h>
#include <sys/kdb.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/ktr.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <sys/epoch.h>
#include <sys/queue.h>
#include <sys/refcount.h>
#include <sys/rwlock.h>
#include <sys/smp.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/unistd.h>
#include <machine/cpu.h>
#include <vm/uma.h>
#include <machine/stack.h>
#include <net/netisr.h>
#include <net/vnet.h>
#include <netgraph/ng_message.h>
#include <netgraph/netgraph.h>
#include <netgraph/ng_parse.h>
MODULE_VERSION(netgraph, NG_ABI_VERSION);
static struct rwlock ng_topo_lock;
#define TOPOLOGY_RLOCK() rw_rlock(&ng_topo_lock)
#define TOPOLOGY_RUNLOCK() rw_runlock(&ng_topo_lock)
#define TOPOLOGY_WLOCK() rw_wlock(&ng_topo_lock)
#define TOPOLOGY_WUNLOCK() rw_wunlock(&ng_topo_lock)
#define TOPOLOGY_NOTOWNED() rw_assert(&ng_topo_lock, RA_UNLOCKED)
#ifdef NETGRAPH_DEBUG
static struct mtx ng_nodelist_mtx;
static struct mtx ngq_mtx;
static SLIST_HEAD(, ng_node) ng_allnodes;
static LIST_HEAD(, ng_node) ng_freenodes;
static SLIST_HEAD(, ng_hook) ng_allhooks;
static LIST_HEAD(, ng_hook) ng_freehooks;
static void ng_dumpitems(void);
static void ng_dumpnodes(void);
static void ng_dumphooks(void);
#endif
struct ng_type ng_deadtype = {
NG_ABI_VERSION,
"dead",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
struct ng_node ng_deadnode = {
"dead",
&ng_deadtype,
NGF_INVALID,
0,
NULL,
0,
LIST_HEAD_INITIALIZER(ng_deadnode.nd_hooks),
{},
{},
{ 0,
0,
{},
{},
STAILQ_HEAD_INITIALIZER(ng_deadnode.nd_input_queue.queue),
},
1,
NULL,
#ifdef NETGRAPH_DEBUG
ND_MAGIC,
__FILE__,
__LINE__,
{NULL}
#endif
};
struct ng_hook ng_deadhook = {
"dead",
NULL,
HK_INVALID | HK_DEAD,
0,
&ng_deadhook,
&ng_deadnode,
{},
NULL,
NULL,
1,
#ifdef NETGRAPH_DEBUG
HK_MAGIC,
__FILE__,
__LINE__,
{NULL}
#endif
};
static STAILQ_HEAD(, ng_node) ng_worklist = STAILQ_HEAD_INITIALIZER(ng_worklist);
static struct mtx ng_worklist_mtx;
static LIST_HEAD(, ng_type) ng_typelist;
static struct rwlock ng_typelist_lock;
#define TYPELIST_RLOCK() rw_rlock(&ng_typelist_lock)
#define TYPELIST_RUNLOCK() rw_runlock(&ng_typelist_lock)
#define TYPELIST_WLOCK() rw_wlock(&ng_typelist_lock)
#define TYPELIST_WUNLOCK() rw_wunlock(&ng_typelist_lock)
LIST_HEAD(nodehash, ng_node);
VNET_DEFINE_STATIC(struct nodehash *, ng_ID_hash);
VNET_DEFINE_STATIC(u_long, ng_ID_hmask);
VNET_DEFINE_STATIC(u_long, ng_nodes);
VNET_DEFINE_STATIC(struct nodehash *, ng_name_hash);
VNET_DEFINE_STATIC(u_long, ng_name_hmask);
VNET_DEFINE_STATIC(u_long, ng_named_nodes);
#define V_ng_ID_hash VNET(ng_ID_hash)
#define V_ng_ID_hmask VNET(ng_ID_hmask)
#define V_ng_nodes VNET(ng_nodes)
#define V_ng_name_hash VNET(ng_name_hash)
#define V_ng_name_hmask VNET(ng_name_hmask)
#define V_ng_named_nodes VNET(ng_named_nodes)
static struct rwlock ng_idhash_lock;
#define IDHASH_RLOCK() rw_rlock(&ng_idhash_lock)
#define IDHASH_RUNLOCK() rw_runlock(&ng_idhash_lock)
#define IDHASH_WLOCK() rw_wlock(&ng_idhash_lock)
#define IDHASH_WUNLOCK() rw_wunlock(&ng_idhash_lock)
#define NG_IDHASH_FN(ID) ((ID) % (V_ng_ID_hmask + 1))
#define NG_IDHASH_FIND(ID, node) \
do { \
rw_assert(&ng_idhash_lock, RA_LOCKED); \
LIST_FOREACH(node, &V_ng_ID_hash[NG_IDHASH_FN(ID)], \
nd_idnodes) { \
if (NG_NODE_IS_VALID(node) \
&& (NG_NODE_ID(node) == ID)) { \
break; \
} \
} \
} while (0)
static struct rwlock ng_namehash_lock;
#define NAMEHASH_RLOCK() rw_rlock(&ng_namehash_lock)
#define NAMEHASH_RUNLOCK() rw_runlock(&ng_namehash_lock)
#define NAMEHASH_WLOCK() rw_wlock(&ng_namehash_lock)
#define NAMEHASH_WUNLOCK() rw_wunlock(&ng_namehash_lock)
static int ng_add_hook(node_p node, const char *name, hook_p * hookp);
static int ng_generic_msg(node_p here, item_p item, hook_p lasthook);
static ng_ID_t ng_decodeidname(const char *name);
static int ngb_mod_event(module_t mod, int event, void *data);
static void ng_worklist_add(node_p node);
static void ngthread(void *);
static int ng_apply_item(node_p node, item_p item, int rw);
static void ng_flush_input_queue(node_p node);
static node_p ng_ID2noderef(ng_ID_t ID);
static int ng_con_nodes(item_p item, node_p node, const char *name,
node_p node2, const char *name2);
static int ng_con_part2(node_p node, item_p item, hook_p hook);
static int ng_con_part3(node_p node, item_p item, hook_p hook);
static int ng_mkpeer(node_p node, const char *name, const char *name2,
char *type);
static void ng_name_rehash(void);
static void ng_ID_rehash(void);
void ng_destroy_hook(hook_p hook);
int ng_path2noderef(node_p here, const char *path,
node_p *dest, hook_p *lasthook);
int ng_make_node(const char *type, node_p *nodepp);
int ng_path_parse(char *addr, char **node, char **path, char **hook);
void ng_rmnode(node_p node, hook_p dummy1, void *dummy2, int dummy3);
void ng_unname(node_p node);
MALLOC_DEFINE(M_NETGRAPH, "netgraph", "netgraph structures and ctrl messages");
MALLOC_DEFINE(M_NETGRAPH_MSG, "netgraph_msg", "netgraph name storage");
static MALLOC_DEFINE(M_NETGRAPH_HOOK, "netgraph_hook",
"netgraph hook structures");
static MALLOC_DEFINE(M_NETGRAPH_NODE, "netgraph_node",
"netgraph node structures");
static MALLOC_DEFINE(M_NETGRAPH_ITEM, "netgraph_item",
"netgraph item structures");
#define _NG_ALLOC_HOOK(hook) \
hook = malloc(sizeof(*hook), M_NETGRAPH_HOOK, M_NOWAIT | M_ZERO)
#define _NG_ALLOC_NODE(node) \
node = malloc(sizeof(*node), M_NETGRAPH_NODE, M_NOWAIT | M_ZERO)
#define NG_QUEUE_LOCK_INIT(n) \
mtx_init(&(n)->q_mtx, "ng_node", NULL, MTX_DEF)
#define NG_QUEUE_LOCK(n) \
mtx_lock(&(n)->q_mtx)
#define NG_QUEUE_UNLOCK(n) \
mtx_unlock(&(n)->q_mtx)
#define NG_WORKLIST_LOCK_INIT() \
mtx_init(&ng_worklist_mtx, "ng_worklist", NULL, MTX_DEF)
#define NG_WORKLIST_LOCK() \
mtx_lock(&ng_worklist_mtx)
#define NG_WORKLIST_UNLOCK() \
mtx_unlock(&ng_worklist_mtx)
#define NG_WORKLIST_SLEEP() \
mtx_sleep(&ng_worklist, &ng_worklist_mtx, PI_NET, "sleep", 0)
#define NG_WORKLIST_WAKEUP() \
wakeup_one(&ng_worklist)
#ifdef NETGRAPH_DEBUG
static __inline hook_p
ng_alloc_hook(void)
{
hook_p hook;
SLIST_ENTRY(ng_hook) temp;
mtx_lock(&ng_nodelist_mtx);
hook = LIST_FIRST(&ng_freehooks);
if (hook) {
LIST_REMOVE(hook, hk_hooks);
bcopy(&hook->hk_all, &temp, sizeof(temp));
bzero(hook, sizeof(struct ng_hook));
bcopy(&temp, &hook->hk_all, sizeof(temp));
mtx_unlock(&ng_nodelist_mtx);
hook->hk_magic = HK_MAGIC;
} else {
mtx_unlock(&ng_nodelist_mtx);
_NG_ALLOC_HOOK(hook);
if (hook) {
hook->hk_magic = HK_MAGIC;
mtx_lock(&ng_nodelist_mtx);
SLIST_INSERT_HEAD(&ng_allhooks, hook, hk_all);
mtx_unlock(&ng_nodelist_mtx);
}
}
return (hook);
}
static __inline node_p
ng_alloc_node(void)
{
node_p node;
SLIST_ENTRY(ng_node) temp;
mtx_lock(&ng_nodelist_mtx);
node = LIST_FIRST(&ng_freenodes);
if (node) {
LIST_REMOVE(node, nd_nodes);
bcopy(&node->nd_all, &temp, sizeof(temp));
bzero(node, sizeof(struct ng_node));
bcopy(&temp, &node->nd_all, sizeof(temp));
mtx_unlock(&ng_nodelist_mtx);
node->nd_magic = ND_MAGIC;
} else {
mtx_unlock(&ng_nodelist_mtx);
_NG_ALLOC_NODE(node);
if (node) {
node->nd_magic = ND_MAGIC;
mtx_lock(&ng_nodelist_mtx);
SLIST_INSERT_HEAD(&ng_allnodes, node, nd_all);
mtx_unlock(&ng_nodelist_mtx);
}
}
return (node);
}
#define NG_ALLOC_HOOK(hook) do { (hook) = ng_alloc_hook(); } while (0)
#define NG_ALLOC_NODE(node) do { (node) = ng_alloc_node(); } while (0)
#define NG_FREE_HOOK(hook) \
do { \
mtx_lock(&ng_nodelist_mtx); \
LIST_INSERT_HEAD(&ng_freehooks, hook, hk_hooks); \
hook->hk_magic = 0; \
mtx_unlock(&ng_nodelist_mtx); \
} while (0)
#define NG_FREE_NODE(node) \
do { \
mtx_lock(&ng_nodelist_mtx); \
LIST_INSERT_HEAD(&ng_freenodes, node, nd_nodes); \
node->nd_magic = 0; \
mtx_unlock(&ng_nodelist_mtx); \
} while (0)
#else
#define NG_ALLOC_HOOK(hook) _NG_ALLOC_HOOK(hook)
#define NG_ALLOC_NODE(node) _NG_ALLOC_NODE(node)
#define NG_FREE_HOOK(hook) do { free((hook), M_NETGRAPH_HOOK); } while (0)
#define NG_FREE_NODE(node) do { free((node), M_NETGRAPH_NODE); } while (0)
#endif
#ifndef TRAP_ERROR
#define TRAP_ERROR()
#endif
VNET_DEFINE_STATIC(ng_ID_t, nextID) = 1;
#define V_nextID VNET(nextID)
#ifdef INVARIANTS
#define CHECK_DATA_MBUF(m) do { \
struct mbuf *n; \
int total; \
\
M_ASSERTPKTHDR(m); \
for (total = 0, n = (m); n != NULL; n = n->m_next) { \
total += n->m_len; \
if (n->m_nextpkt != NULL) \
panic("%s: m_nextpkt", __func__); \
} \
\
if ((m)->m_pkthdr.len != total) { \
panic("%s: %d != %d", \
__func__, (m)->m_pkthdr.len, total); \
} \
} while (0)
#else
#define CHECK_DATA_MBUF(m)
#endif
#define ERROUT(x) do { error = (x); goto done; } while (0)
#define DEFINE_PARSE_STRUCT_TYPE(lo, up, args) \
static const struct ng_parse_struct_field \
ng_ ## lo ## _type_fields[] = NG_GENERIC_ ## up ## _INFO args; \
static const struct ng_parse_type ng_generic_ ## lo ## _type = { \
&ng_parse_struct_type, \
&ng_ ## lo ## _type_fields \
}
DEFINE_PARSE_STRUCT_TYPE(mkpeer, MKPEER, ());
DEFINE_PARSE_STRUCT_TYPE(connect, CONNECT, ());
DEFINE_PARSE_STRUCT_TYPE(name, NAME, ());
DEFINE_PARSE_STRUCT_TYPE(rmhook, RMHOOK, ());
DEFINE_PARSE_STRUCT_TYPE(nodeinfo, NODEINFO, ());
DEFINE_PARSE_STRUCT_TYPE(typeinfo, TYPEINFO, ());
DEFINE_PARSE_STRUCT_TYPE(linkinfo, LINKINFO, (&ng_generic_nodeinfo_type));
static int
ng_generic_list_getLength(const struct ng_parse_type *type,
const u_char *start, const u_char *buf)
{
return *((const u_int32_t *)(buf - 4));
}
static int
ng_generic_linkinfo_getLength(const struct ng_parse_type *type,
const u_char *start, const u_char *buf)
{
const struct hooklist *hl = (const struct hooklist *)start;
return hl->nodeinfo.hooks;
}
static const struct ng_parse_array_info ng_nodeinfoarray_type_info = {
&ng_generic_nodeinfo_type,
&ng_generic_list_getLength
};
static const struct ng_parse_type ng_generic_nodeinfoarray_type = {
&ng_parse_array_type,
&ng_nodeinfoarray_type_info
};
static const struct ng_parse_array_info ng_typeinfoarray_type_info = {
&ng_generic_typeinfo_type,
&ng_generic_list_getLength
};
static const struct ng_parse_type ng_generic_typeinfoarray_type = {
&ng_parse_array_type,
&ng_typeinfoarray_type_info
};
static const struct ng_parse_array_info ng_generic_linkinfo_array_type_info = {
&ng_generic_linkinfo_type,
&ng_generic_linkinfo_getLength
};
static const struct ng_parse_type ng_generic_linkinfo_array_type = {
&ng_parse_array_type,
&ng_generic_linkinfo_array_type_info
};
DEFINE_PARSE_STRUCT_TYPE(typelist, TYPELIST, (&ng_generic_typeinfoarray_type));
DEFINE_PARSE_STRUCT_TYPE(hooklist, HOOKLIST,
(&ng_generic_nodeinfo_type, &ng_generic_linkinfo_array_type));
DEFINE_PARSE_STRUCT_TYPE(listnodes, LISTNODES,
(&ng_generic_nodeinfoarray_type));
static const struct ng_cmdlist ng_generic_cmds[] = {
{
NGM_GENERIC_COOKIE,
NGM_SHUTDOWN,
"shutdown",
NULL,
NULL
},
{
NGM_GENERIC_COOKIE,
NGM_MKPEER,
"mkpeer",
&ng_generic_mkpeer_type,
NULL
},
{
NGM_GENERIC_COOKIE,
NGM_CONNECT,
"connect",
&ng_generic_connect_type,
NULL
},
{
NGM_GENERIC_COOKIE,
NGM_NAME,
"name",
&ng_generic_name_type,
NULL
},
{
NGM_GENERIC_COOKIE,
NGM_RMHOOK,
"rmhook",
&ng_generic_rmhook_type,
NULL
},
{
NGM_GENERIC_COOKIE,
NGM_NODEINFO,
"nodeinfo",
NULL,
&ng_generic_nodeinfo_type
},
{
NGM_GENERIC_COOKIE,
NGM_LISTHOOKS,
"listhooks",
NULL,
&ng_generic_hooklist_type
},
{
NGM_GENERIC_COOKIE,
NGM_LISTNAMES,
"listnames",
NULL,
&ng_generic_listnodes_type
},
{
NGM_GENERIC_COOKIE,
NGM_LISTNODES,
"listnodes",
NULL,
&ng_generic_listnodes_type
},
{
NGM_GENERIC_COOKIE,
NGM_LISTTYPES,
"listtypes",
NULL,
&ng_generic_typelist_type
},
{
NGM_GENERIC_COOKIE,
NGM_TEXT_CONFIG,
"textconfig",
NULL,
&ng_parse_string_type
},
{
NGM_GENERIC_COOKIE,
NGM_TEXT_STATUS,
"textstatus",
NULL,
&ng_parse_string_type
},
{
NGM_GENERIC_COOKIE,
NGM_ASCII2BINARY,
"ascii2binary",
&ng_parse_ng_mesg_type,
&ng_parse_ng_mesg_type
},
{
NGM_GENERIC_COOKIE,
NGM_BINARY2ASCII,
"binary2ascii",
&ng_parse_ng_mesg_type,
&ng_parse_ng_mesg_type
},
{ 0 }
};
int
ng_make_node(const char *typename, node_p *nodepp)
{
struct ng_type *type;
int error;
if (typename == NULL) {
TRAP_ERROR();
return (EINVAL);
}
if ((type = ng_findtype(typename)) == NULL)
return (ENXIO);
if (type->constructor != NULL) {
if ((error = ng_make_node_common(type, nodepp)) == 0) {
if ((error = ((*type->constructor)(*nodepp))) != 0) {
NG_NODE_UNREF(*nodepp);
}
}
} else {
TRAP_ERROR();
error = EINVAL;
}
return (error);
}
int
ng_make_node_common(struct ng_type *type, node_p *nodepp)
{
node_p node;
if (ng_findtype(type->name) == NULL) {
TRAP_ERROR();
return (EINVAL);
}
NG_ALLOC_NODE(node);
if (node == NULL) {
TRAP_ERROR();
return (ENOMEM);
}
node->nd_type = type;
#ifdef VIMAGE
node->nd_vnet = curvnet;
#endif
NG_NODE_REF(node);
type->refs++;
NG_QUEUE_LOCK_INIT(&node->nd_input_queue);
STAILQ_INIT(&node->nd_input_queue.queue);
node->nd_input_queue.q_flags = 0;
LIST_INIT(&node->nd_hooks);
IDHASH_WLOCK();
for (;;) {
node_p node2 = NULL;
node->nd_ID = V_nextID++;
NG_IDHASH_FIND(node->nd_ID, node2);
if ((node->nd_ID != 0) && (node2 == NULL)) {
break;
}
}
V_ng_nodes++;
if (V_ng_nodes * 2 > V_ng_ID_hmask)
ng_ID_rehash();
LIST_INSERT_HEAD(&V_ng_ID_hash[NG_IDHASH_FN(node->nd_ID)], node,
nd_idnodes);
IDHASH_WUNLOCK();
*nodepp = node;
return (0);
}
void
ng_rmnode(node_p node, hook_p dummy1, void *dummy2, int dummy3)
{
hook_p hook;
if ((node->nd_flags & NGF_CLOSING) != 0)
return;
if (node == &ng_deadnode) {
printf ("shutdown called on deadnode\n");
return;
}
NG_NODE_REF(node);
node->nd_flags |= NGF_INVALID|NGF_CLOSING;
if (node->nd_type && node->nd_type->close)
(*node->nd_type->close)(node);
while ((hook = LIST_FIRST(&node->nd_hooks)) != NULL)
ng_destroy_hook(hook);
ng_flush_input_queue(node);
if (node->nd_type && node->nd_type->shutdown) {
(*node->nd_type->shutdown)(node);
if (NG_NODE_IS_VALID(node)) {
node->nd_flags &= ~(NGF_INVALID|NGF_CLOSING);
NG_NODE_UNREF(node);
return;
}
} else {
NG_NODE_UNREF(node);
}
ng_unname(node);
NG_NODE_UNREF(node);
}
void
ng_unref_node(node_p node)
{
if (node == &ng_deadnode)
return;
CURVNET_SET(node->nd_vnet);
if (refcount_release(&node->nd_refs)) {
node->nd_type->refs--;
NAMEHASH_WLOCK();
if (NG_NODE_HAS_NAME(node)) {
V_ng_named_nodes--;
LIST_REMOVE(node, nd_nodes);
}
NAMEHASH_WUNLOCK();
IDHASH_WLOCK();
V_ng_nodes--;
LIST_REMOVE(node, nd_idnodes);
IDHASH_WUNLOCK();
mtx_destroy(&node->nd_input_queue.q_mtx);
NG_FREE_NODE(node);
}
CURVNET_RESTORE();
}
static node_p
ng_ID2noderef(ng_ID_t ID)
{
node_p node;
IDHASH_RLOCK();
NG_IDHASH_FIND(ID, node);
if (node)
NG_NODE_REF(node);
IDHASH_RUNLOCK();
return(node);
}
ng_ID_t
ng_node2ID(node_cp node)
{
return (node ? NG_NODE_ID(node) : 0);
}
int
ng_name_node(node_p node, const char *name)
{
uint32_t hash;
node_p node2;
int i;
if (strcmp(NG_NODE_NAME(node), name) == 0)
return (0);
for (i = 0; i < NG_NODESIZ; i++) {
if (name[i] == '\0' || name[i] == '.' || name[i] == ':')
break;
}
if (i == 0 || name[i] != '\0') {
TRAP_ERROR();
return (EINVAL);
}
if (ng_decodeidname(name) != 0) {
TRAP_ERROR();
return (EINVAL);
}
NAMEHASH_WLOCK();
if (V_ng_named_nodes * 2 > V_ng_name_hmask)
ng_name_rehash();
hash = hash32_str(name, HASHINIT) & V_ng_name_hmask;
LIST_FOREACH(node2, &V_ng_name_hash[hash], nd_nodes)
if (NG_NODE_IS_VALID(node2) &&
(strcmp(NG_NODE_NAME(node2), name) == 0)) {
NAMEHASH_WUNLOCK();
return (EADDRINUSE);
}
if (NG_NODE_HAS_NAME(node))
LIST_REMOVE(node, nd_nodes);
else
V_ng_named_nodes++;
strlcpy(NG_NODE_NAME(node), name, NG_NODESIZ);
LIST_INSERT_HEAD(&V_ng_name_hash[hash], node, nd_nodes);
NAMEHASH_WUNLOCK();
return (0);
}
node_p
ng_name2noderef(node_p here, const char *name)
{
node_p node;
ng_ID_t temp;
int hash;
if (strcmp(name, ".") == 0) {
NG_NODE_REF(here);
return(here);
}
if ((temp = ng_decodeidname(name)) != 0) {
return (ng_ID2noderef(temp));
}
hash = hash32_str(name, HASHINIT) & V_ng_name_hmask;
NAMEHASH_RLOCK();
LIST_FOREACH(node, &V_ng_name_hash[hash], nd_nodes)
if (NG_NODE_IS_VALID(node) &&
(strcmp(NG_NODE_NAME(node), name) == 0)) {
NG_NODE_REF(node);
break;
}
NAMEHASH_RUNLOCK();
return (node);
}
static ng_ID_t
ng_decodeidname(const char *name)
{
const int len = strlen(name);
char *eptr;
u_long val;
if ((len < 3) || (name[0] != '[') || (name[len - 1] != ']') ||
(!isxdigit(name[1])))
return ((ng_ID_t)0);
val = strtoul(name + 1, &eptr, 16);
if ((eptr - name != len - 1) || (val == ULONG_MAX) || (val == 0))
return ((ng_ID_t)0);
return ((ng_ID_t)val);
}
void
ng_unname(node_p node)
{
}
static void
ng_name_rehash(void)
{
struct nodehash *new;
uint32_t hash;
u_long hmask;
node_p node, node2;
int i;
new = hashinit_flags((V_ng_name_hmask + 1) * 2, M_NETGRAPH_NODE, &hmask,
HASH_NOWAIT);
if (new == NULL)
return;
for (i = 0; i <= V_ng_name_hmask; i++)
LIST_FOREACH_SAFE(node, &V_ng_name_hash[i], nd_nodes, node2) {
#ifdef INVARIANTS
LIST_REMOVE(node, nd_nodes);
#endif
hash = hash32_str(NG_NODE_NAME(node), HASHINIT) & hmask;
LIST_INSERT_HEAD(&new[hash], node, nd_nodes);
}
hashdestroy(V_ng_name_hash, M_NETGRAPH_NODE, V_ng_name_hmask);
V_ng_name_hash = new;
V_ng_name_hmask = hmask;
}
static void
ng_ID_rehash(void)
{
struct nodehash *new;
uint32_t hash;
u_long hmask;
node_p node, node2;
int i;
new = hashinit_flags((V_ng_ID_hmask + 1) * 2, M_NETGRAPH_NODE, &hmask,
HASH_NOWAIT);
if (new == NULL)
return;
for (i = 0; i <= V_ng_ID_hmask; i++)
LIST_FOREACH_SAFE(node, &V_ng_ID_hash[i], nd_idnodes, node2) {
#ifdef INVARIANTS
LIST_REMOVE(node, nd_idnodes);
#endif
hash = (node->nd_ID % (hmask + 1));
LIST_INSERT_HEAD(&new[hash], node, nd_idnodes);
}
hashdestroy(V_ng_ID_hash, M_NETGRAPH_NODE, V_ng_name_hmask);
V_ng_ID_hash = new;
V_ng_ID_hmask = hmask;
}
void
ng_unref_hook(hook_p hook)
{
if (hook == &ng_deadhook)
return;
if (refcount_release(&hook->hk_refs)) {
if (_NG_HOOK_NODE(hook))
_NG_NODE_UNREF((_NG_HOOK_NODE(hook)));
NG_FREE_HOOK(hook);
}
}
static int
ng_add_hook(node_p node, const char *name, hook_p *hookp)
{
hook_p hook;
int error = 0;
if (name == NULL) {
TRAP_ERROR();
return (EINVAL);
}
if (ng_findhook(node, name) != NULL) {
TRAP_ERROR();
return (EEXIST);
}
NG_ALLOC_HOOK(hook);
if (hook == NULL) {
TRAP_ERROR();
return (ENOMEM);
}
hook->hk_refs = 1;
hook->hk_flags = HK_INVALID;
hook->hk_peer = &ng_deadhook;
hook->hk_node = node;
NG_NODE_REF(node);
strlcpy(NG_HOOK_NAME(hook), name, NG_HOOKSIZ);
if (node->nd_type->newhook != NULL) {
if ((error = (*node->nd_type->newhook)(node, hook, name))) {
NG_HOOK_UNREF(hook);
return (error);
}
}
LIST_INSERT_HEAD(&node->nd_hooks, hook, hk_hooks);
node->nd_numhooks++;
NG_HOOK_REF(hook);
if (hookp)
*hookp = hook;
return (0);
}
hook_p
ng_findhook(node_p node, const char *name)
{
hook_p hook;
if (node->nd_type->findhook != NULL)
return (*node->nd_type->findhook)(node, name);
LIST_FOREACH(hook, &node->nd_hooks, hk_hooks) {
if (NG_HOOK_IS_VALID(hook) &&
(strcmp(NG_HOOK_NAME(hook), name) == 0))
return (hook);
}
return (NULL);
}
void
ng_destroy_hook(hook_p hook)
{
hook_p peer;
node_p node;
if (hook == &ng_deadhook) {
printf("ng_destroy_hook called on deadhook\n");
return;
}
TOPOLOGY_WLOCK();
hook->hk_flags |= HK_INVALID;
peer = NG_HOOK_PEER(hook);
node = NG_HOOK_NODE(hook);
if (peer && (peer != &ng_deadhook)) {
peer->hk_peer = &ng_deadhook;
hook->hk_peer = &ng_deadhook;
if (NG_HOOK_NODE(peer) == &ng_deadnode) {
TOPOLOGY_WUNLOCK();
} else {
TOPOLOGY_WUNLOCK();
ng_rmhook_self(peer);
}
NG_HOOK_UNREF(peer);
NG_HOOK_UNREF(hook);
} else
TOPOLOGY_WUNLOCK();
TOPOLOGY_NOTOWNED();
if (node == &ng_deadnode) {
return;
}
LIST_REMOVE(hook, hk_hooks);
node->nd_numhooks--;
if (node->nd_type->disconnect) {
(*node->nd_type->disconnect) (hook);
}
_NG_HOOK_NODE(hook) = &ng_deadnode;
NG_NODE_UNREF(node);
NG_HOOK_UNREF(hook);
}
int
ng_bypass(hook_p hook1, hook_p hook2)
{
if (hook1->hk_node != hook2->hk_node) {
TRAP_ERROR();
return (EINVAL);
}
TOPOLOGY_WLOCK();
if (NG_HOOK_NOT_VALID(hook1) || NG_HOOK_NOT_VALID(hook2)) {
TOPOLOGY_WUNLOCK();
return (EINVAL);
}
hook1->hk_peer->hk_peer = hook2->hk_peer;
hook2->hk_peer->hk_peer = hook1->hk_peer;
hook1->hk_peer = &ng_deadhook;
hook2->hk_peer = &ng_deadhook;
TOPOLOGY_WUNLOCK();
NG_HOOK_UNREF(hook1);
NG_HOOK_UNREF(hook2);
ng_destroy_hook(hook1);
ng_destroy_hook(hook2);
return (0);
}
int
ng_newtype(struct ng_type *tp)
{
const size_t namelen = strlen(tp->name);
if ((tp->version != NG_ABI_VERSION) || (namelen == 0) ||
(namelen >= NG_TYPESIZ)) {
TRAP_ERROR();
if (tp->version != NG_ABI_VERSION) {
printf("Netgraph: Node type rejected. ABI mismatch. "
"Suggest recompile\n");
}
return (EINVAL);
}
if (ng_findtype(tp->name) != NULL) {
TRAP_ERROR();
return (EEXIST);
}
TYPELIST_WLOCK();
LIST_INSERT_HEAD(&ng_typelist, tp, types);
tp->refs = 1;
TYPELIST_WUNLOCK();
return (0);
}
int
ng_rmtype(struct ng_type *tp)
{
if (tp->refs != 1) {
TRAP_ERROR();
return (EBUSY);
}
TYPELIST_WLOCK();
LIST_REMOVE(tp, types);
TYPELIST_WUNLOCK();
return (0);
}
struct ng_type *
ng_findtype(const char *typename)
{
struct ng_type *type;
TYPELIST_RLOCK();
LIST_FOREACH(type, &ng_typelist, types) {
if (strcmp(type->name, typename) == 0)
break;
}
TYPELIST_RUNLOCK();
return (type);
}
static int
ng_con_part3(node_p node, item_p item, hook_p hook)
{
int error = 0;
if (NG_HOOK_NODE(hook) == &ng_deadnode) {
ERROUT(ENOENT);
}
if (hook->hk_node->nd_type->connect) {
if ((error = (*hook->hk_node->nd_type->connect) (hook))) {
ng_destroy_hook(hook);
printf("failed in ng_con_part3()\n");
ERROUT(error);
}
}
hook->hk_flags &= ~HK_INVALID;
done:
NG_FREE_ITEM(item);
return (error);
}
static int
ng_con_part2(node_p node, item_p item, hook_p hook)
{
hook_p peer;
int error = 0;
if (ng_findhook(node, NG_HOOK_NAME(hook)) != NULL) {
TRAP_ERROR();
ng_destroy_hook(hook);
printf("failed in ng_con_part2()\n");
ERROUT(EEXIST);
}
if (node->nd_type->newhook != NULL) {
if ((error = (*node->nd_type->newhook)(node, hook,
hook->hk_name))) {
ng_destroy_hook(hook);
printf("failed in ng_con_part2()\n");
ERROUT(error);
}
}
hook->hk_node = node;
NG_NODE_REF(node);
LIST_INSERT_HEAD(&node->nd_hooks, hook, hk_hooks);
node->nd_numhooks++;
NG_HOOK_REF(hook);
if (hook->hk_node->nd_type->connect) {
if ((error = (*hook->hk_node->nd_type->connect) (hook))) {
ng_destroy_hook(hook);
printf("failed in ng_con_part2(A)\n");
ERROUT(error);
}
}
TOPOLOGY_RLOCK();
peer = hook->hk_peer;
if (peer == &ng_deadhook) {
TOPOLOGY_RUNLOCK();
printf("failed in ng_con_part2(B)\n");
ng_destroy_hook(hook);
ERROUT(ENOENT);
}
TOPOLOGY_RUNLOCK();
if ((error = ng_send_fn2(peer->hk_node, peer, item, &ng_con_part3,
NULL, 0, NG_REUSE_ITEM))) {
printf("failed in ng_con_part2(C)\n");
ng_destroy_hook(hook);
return (error);
}
hook->hk_flags &= ~HK_INVALID;
return (0);
done:
NG_FREE_ITEM(item);
return (error);
}
static int
ng_con_nodes(item_p item, node_p node, const char *name,
node_p node2, const char *name2)
{
int error;
hook_p hook;
hook_p hook2;
if (ng_findhook(node2, name2) != NULL) {
return(EEXIST);
}
if ((error = ng_add_hook(node, name, &hook)))
return (error);
NG_ALLOC_HOOK(hook2);
if (hook2 == NULL) {
TRAP_ERROR();
ng_destroy_hook(hook);
NG_HOOK_UNREF(hook);
return (ENOMEM);
}
hook2->hk_refs = 1;
hook2->hk_flags = HK_INVALID;
hook2->hk_peer = hook;
hook->hk_peer = hook2;
NG_HOOK_REF(hook);
NG_HOOK_REF(hook2);
hook2->hk_node = &ng_deadnode;
strlcpy(NG_HOOK_NAME(hook2), name2, NG_HOOKSIZ);
if ((error = ng_send_fn2(node2, hook2, item, &ng_con_part2, NULL, 0,
NG_NOFLAGS))) {
printf("failed in ng_con_nodes(): %d\n", error);
ng_destroy_hook(hook);
}
NG_HOOK_UNREF(hook);
NG_HOOK_UNREF(hook2);
return (error);
}
static int
ng_mkpeer(node_p node, const char *name, const char *name2, char *type)
{
node_p node2;
hook_p hook1, hook2;
int error;
if ((error = ng_make_node(type, &node2))) {
return (error);
}
if ((error = ng_add_hook(node, name, &hook1))) {
ng_rmnode(node2, NULL, NULL, 0);
return (error);
}
if ((error = ng_add_hook(node2, name2, &hook2))) {
ng_rmnode(node2, NULL, NULL, 0);
ng_destroy_hook(hook1);
NG_HOOK_UNREF(hook1);
return (error);
}
hook1->hk_peer = hook2;
hook2->hk_peer = hook1;
NG_HOOK_REF(hook1);
NG_HOOK_REF(hook2);
if (hook1->hk_node->nd_type->connect) {
error = (*hook1->hk_node->nd_type->connect) (hook1);
}
if ((error == 0) && hook2->hk_node->nd_type->connect) {
error = (*hook2->hk_node->nd_type->connect) (hook2);
}
if (error) {
ng_destroy_hook(hook2);
ng_rmnode(node2, NULL, NULL, 0);
} else {
hook1->hk_flags &= ~HK_INVALID;
hook2->hk_flags &= ~HK_INVALID;
}
NG_HOOK_UNREF(hook1);
NG_HOOK_UNREF(hook2);
return (error);
}
int
ng_rmnode_self(node_p node)
{
int error;
if (node == &ng_deadnode)
return (0);
node->nd_flags |= NGF_INVALID;
if (node->nd_flags & NGF_CLOSING)
return (0);
error = ng_send_fn(node, NULL, &ng_rmnode, NULL, 0);
return (error);
}
static void
ng_rmhook_part2(node_p node, hook_p hook, void *arg1, int arg2)
{
ng_destroy_hook(hook);
return ;
}
int
ng_rmhook_self(hook_p hook)
{
int error;
node_p node = NG_HOOK_NODE(hook);
if (node == &ng_deadnode)
return (0);
error = ng_send_fn(node, hook, &ng_rmhook_part2, NULL, 0);
return (error);
}
int
ng_path_parse(char *addr, char **nodep, char **pathp, char **hookp)
{
char *node, *path, *hook;
int k;
for (path = addr; *path && *path != ':'; path++);
if (*path) {
node = addr;
*path++ = '\0';
if (!*node)
return -1;
if (strcmp(node, ".") != 0) {
for (k = 0; node[k]; k++)
if (node[k] == '.')
return -1;
}
} else {
node = NULL;
path = addr;
}
for (k = 0; path[k]; k++)
if (path[k] == ':')
return -1;
for (k = 0; path[k]; k++)
if (path[k] == '.' && path[k + 1] == '.')
return -1;
if (path[0] == '.')
path++;
if (*path && path[strlen(path) - 1] == '.')
path[strlen(path) - 1] = 0;
if (*path) {
for (hook = path, k = 0; path[k]; k++)
if (path[k] == '.') {
hook = NULL;
break;
}
} else
path = hook = NULL;
if (nodep)
*nodep = node;
if (pathp)
*pathp = path;
if (hookp)
*hookp = hook;
return (0);
}
int
ng_path2noderef(node_p here, const char *address, node_p *destp,
hook_p *lasthook)
{
char fullpath[NG_PATHSIZ];
char *nodename, *path;
node_p node, oldnode;
if (destp == NULL) {
TRAP_ERROR();
return EINVAL;
}
*destp = NULL;
strncpy(fullpath, address, sizeof(fullpath) - 1);
fullpath[sizeof(fullpath) - 1] = '\0';
if (ng_path_parse(fullpath, &nodename, &path, NULL) < 0) {
TRAP_ERROR();
return EINVAL;
}
if (nodename) {
node = ng_name2noderef(here, nodename);
if (node == NULL) {
TRAP_ERROR();
return (ENOENT);
}
} else {
if (here == NULL) {
TRAP_ERROR();
return (EINVAL);
}
node = here;
NG_NODE_REF(node);
}
if (path == NULL) {
if (lasthook != NULL)
*lasthook = NULL;
*destp = node;
return (0);
}
for (;;) {
hook_p hook;
char *segment;
for (segment = path; *path != '\0'; path++) {
if (*path == '.') {
*path++ = '\0';
break;
}
}
hook = ng_findhook(node, segment);
TOPOLOGY_WLOCK();
if (hook == NULL || NG_HOOK_PEER(hook) == NULL ||
NG_HOOK_NOT_VALID(hook) ||
NG_HOOK_NOT_VALID(NG_HOOK_PEER(hook))) {
TRAP_ERROR();
NG_NODE_UNREF(node);
TOPOLOGY_WUNLOCK();
return (ENOENT);
}
oldnode = node;
if ((node = NG_PEER_NODE(hook)))
NG_NODE_REF(node);
NG_NODE_UNREF(oldnode);
if (NG_NODE_NOT_VALID(node)) {
NG_NODE_UNREF(node);
TOPOLOGY_WUNLOCK();
TRAP_ERROR();
return (ENXIO);
}
if (*path == '\0') {
if (lasthook != NULL) {
if (hook != NULL) {
*lasthook = NG_HOOK_PEER(hook);
NG_HOOK_REF(*lasthook);
} else
*lasthook = NULL;
}
TOPOLOGY_WUNLOCK();
*destp = node;
return (0);
}
TOPOLOGY_WUNLOCK();
}
}
static __inline void ng_queue_rw(node_p node, item_p item, int rw);
static __inline item_p ng_dequeue(node_p node, int *rw);
static __inline item_p ng_acquire_read(node_p node, item_p item);
static __inline item_p ng_acquire_write(node_p node, item_p item);
static __inline void ng_leave_read(node_p node);
static __inline void ng_leave_write(node_p node);
#define WRITER_ACTIVE 0x00000001
#define OP_PENDING 0x00000002
#define READER_INCREMENT 0x00000004
#define READER_MASK 0xfffffffc
#define SAFETY_BARRIER 0x00100000
#define NGQ_RMASK (WRITER_ACTIVE|OP_PENDING)
#define NGQ_WMASK (NGQ_RMASK|READER_MASK)
#define QUEUE_ACTIVE(QP) ((QP)->q_flags & OP_PENDING)
#define HEAD_IS_READER(QP) NGI_QUEUED_READER(STAILQ_FIRST(&(QP)->queue))
#define HEAD_IS_WRITER(QP) NGI_QUEUED_WRITER(STAILQ_FIRST(&(QP)->queue))
#define QUEUED_READER_CAN_PROCEED(QP) \
(((QP)->q_flags & (NGQ_RMASK & ~OP_PENDING)) == 0)
#define QUEUED_WRITER_CAN_PROCEED(QP) \
(((QP)->q_flags & (NGQ_WMASK & ~OP_PENDING)) == 0)
#define NEXT_QUEUED_ITEM_CAN_PROCEED(QP) \
((HEAD_IS_READER(QP)) ? QUEUED_READER_CAN_PROCEED(QP) : \
QUEUED_WRITER_CAN_PROCEED(QP))
#define NGQRW_R 0
#define NGQRW_W 1
#define NGQ2_WORKQ 0x00000001
static __inline item_p
ng_dequeue(node_p node, int *rw)
{
item_p item;
struct ng_queue *ngq = &node->nd_input_queue;
mtx_assert(&ngq->q_mtx, MA_OWNED);
if (!QUEUE_ACTIVE(ngq)) {
CTR4(KTR_NET, "%20s: node [%x] (%p) queue empty; "
"queue flags 0x%lx", __func__,
node->nd_ID, node, ngq->q_flags);
return (NULL);
}
if (HEAD_IS_READER(ngq)) {
while (1) {
long t = ngq->q_flags;
if (t & WRITER_ACTIVE) {
CTR4(KTR_NET, "%20s: node [%x] (%p) queued "
"reader can't proceed; queue flags 0x%lx",
__func__, node->nd_ID, node, t);
return (NULL);
}
if (atomic_cmpset_acq_int(&ngq->q_flags, t,
t + READER_INCREMENT))
break;
cpu_spinwait();
}
*rw = NGQRW_R;
} else if (atomic_cmpset_acq_int(&ngq->q_flags, OP_PENDING,
OP_PENDING + WRITER_ACTIVE)) {
*rw = NGQRW_W;
} else {
CTR4(KTR_NET, "%20s: node [%x] (%p) queued writer can't "
"proceed; queue flags 0x%lx", __func__, node->nd_ID, node,
ngq->q_flags);
return (NULL);
}
item = STAILQ_FIRST(&ngq->queue);
STAILQ_REMOVE_HEAD(&ngq->queue, el_next);
if (STAILQ_EMPTY(&ngq->queue))
atomic_clear_int(&ngq->q_flags, OP_PENDING);
CTR6(KTR_NET, "%20s: node [%x] (%p) returning item %p as %s; queue "
"flags 0x%lx", __func__, node->nd_ID, node, item, *rw ? "WRITER" :
"READER", ngq->q_flags);
return (item);
}
static __inline void
ng_queue_rw(node_p node, item_p item, int rw)
{
struct ng_queue *ngq = &node->nd_input_queue;
if (rw == NGQRW_W)
NGI_SET_WRITER(item);
else
NGI_SET_READER(item);
item->depth = 1;
NG_QUEUE_LOCK(ngq);
atomic_set_int(&ngq->q_flags, OP_PENDING);
STAILQ_INSERT_TAIL(&ngq->queue, item, el_next);
CTR5(KTR_NET, "%20s: node [%x] (%p) queued item %p as %s", __func__,
node->nd_ID, node, item, rw ? "WRITER" : "READER" );
if (NEXT_QUEUED_ITEM_CAN_PROCEED(ngq))
ng_worklist_add(node);
NG_QUEUE_UNLOCK(ngq);
}
static __inline item_p
ng_acquire_read(node_p node, item_p item)
{
KASSERT(node != &ng_deadnode,
("%s: working on deadnode", __func__));
for (;;) {
long t = node->nd_input_queue.q_flags;
if (t & NGQ_RMASK)
break;
if (atomic_cmpset_acq_int(&node->nd_input_queue.q_flags, t,
t + READER_INCREMENT)) {
CTR4(KTR_NET, "%20s: node [%x] (%p) acquired item %p",
__func__, node->nd_ID, node, item);
return (item);
}
cpu_spinwait();
}
ng_queue_rw(node, item, NGQRW_R);
return (NULL);
}
static __inline item_p
ng_acquire_write(node_p node, item_p item)
{
KASSERT(node != &ng_deadnode,
("%s: working on deadnode", __func__));
if (atomic_cmpset_acq_int(&node->nd_input_queue.q_flags, 0,
WRITER_ACTIVE)) {
CTR4(KTR_NET, "%20s: node [%x] (%p) acquired item %p",
__func__, node->nd_ID, node, item);
return (item);
}
ng_queue_rw(node, item, NGQRW_W);
return (NULL);
}
#if 0
static __inline item_p
ng_upgrade_write(node_p node, item_p item)
{
struct ng_queue *ngq = &node->nd_input_queue;
KASSERT(node != &ng_deadnode,
("%s: working on deadnode", __func__));
NGI_SET_WRITER(item);
NG_QUEUE_LOCK(ngq);
atomic_add_int(&ngq->q_flags, WRITER_ACTIVE - READER_INCREMENT);
if ((ngq->q_flags & (NGQ_WMASK & ~OP_PENDING)) == WRITER_ACTIVE) {
NG_QUEUE_UNLOCK(ngq);
ng_apply_item(node, item, 0);
atomic_add_int(&ngq->q_flags, READER_INCREMENT - WRITER_ACTIVE);
return;
}
if (STAILQ_EMPTY(&ngq->queue)) {
atomic_set_int(&ngq->q_flags, OP_PENDING);
CTR3(KTR_NET, "%20s: node [%x] (%p) set OP_PENDING", __func__,
node->nd_ID, node);
};
STAILQ_INSERT_HEAD(&ngq->queue, item, el_next);
CTR4(KTR_NET, "%20s: node [%x] (%p) requeued item %p as WRITER",
__func__, node->nd_ID, node, item );
atomic_add_int(&ngq->q_flags, READER_INCREMENT - WRITER_ACTIVE);
if (QUEUE_ACTIVE(ngq) && NEXT_QUEUED_ITEM_CAN_PROCEED(ngq))
ng_worklist_add(node);
NG_QUEUE_UNLOCK(ngq);
return;
}
#endif
static __inline void
ng_leave_read(node_p node)
{
atomic_subtract_rel_int(&node->nd_input_queue.q_flags, READER_INCREMENT);
}
static __inline void
ng_leave_write(node_p node)
{
atomic_clear_rel_int(&node->nd_input_queue.q_flags, WRITER_ACTIVE);
}
static void
ng_flush_input_queue(node_p node)
{
struct ng_queue *ngq = &node->nd_input_queue;
item_p item;
NG_QUEUE_LOCK(ngq);
while ((item = STAILQ_FIRST(&ngq->queue)) != NULL) {
STAILQ_REMOVE_HEAD(&ngq->queue, el_next);
if (STAILQ_EMPTY(&ngq->queue))
atomic_clear_int(&ngq->q_flags, OP_PENDING);
NG_QUEUE_UNLOCK(ngq);
if (item->apply != NULL) {
if (item->depth == 1)
item->apply->error = ENOENT;
if (refcount_release(&item->apply->refs)) {
(*item->apply->apply)(item->apply->context,
item->apply->error);
}
}
NG_FREE_ITEM(item);
NG_QUEUE_LOCK(ngq);
}
NG_QUEUE_UNLOCK(ngq);
}
int
ng_snd_item(item_p item, int flags)
{
hook_p hook;
node_p node;
int queue, rw;
struct ng_queue *ngq;
int error = 0;
KASSERT(item != NULL, ("ng_snd_item: item is NULL"));
#ifdef NETGRAPH_DEBUG
_ngi_check(item, __FILE__, __LINE__);
#endif
if (item->apply)
refcount_acquire(&item->apply->refs);
node = NGI_NODE(item);
KASSERT(node != NULL, ("ng_snd_item: node is NULL"));
hook = NGI_HOOK(item);
if ((item->el_flags & NGQF_TYPE) == NGQF_DATA) {
KASSERT(hook != NULL, ("ng_snd_item: hook for data is NULL"));
if (NGI_M(item) == NULL)
ERROUT(EINVAL);
CHECK_DATA_MBUF(NGI_M(item));
}
if (((item->el_flags & NGQF_RW) == NGQF_WRITER) ||
(node->nd_flags & NGF_FORCE_WRITER) ||
(hook && (hook->hk_flags & HK_FORCE_WRITER))) {
rw = NGQRW_W;
} else {
rw = NGQRW_R;
}
if ((flags & NG_QUEUE) || (hook && (hook->hk_flags & HK_QUEUE))) {
queue = 1;
} else if (hook && (hook->hk_flags & HK_TO_INBOUND) &&
curthread->td_ng_outbound) {
queue = 1;
} else {
queue = 0;
size_t st, su, sl;
GET_STACK_USAGE(st, su);
sl = st - su;
if ((sl * 4 < st) || ((sl * 2 < st) &&
((node->nd_flags & NGF_HI_STACK) || (hook &&
(hook->hk_flags & HK_HI_STACK)))))
queue = 1;
}
if (queue) {
ng_queue_rw(node, item, rw);
return ((flags & NG_PROGRESS) ? EINPROGRESS : 0);
}
if (rw == NGQRW_R)
item = ng_acquire_read(node, item);
else
item = ng_acquire_write(node, item);
if (item == NULL)
return ((flags & NG_PROGRESS) ? EINPROGRESS : 0);
NGI_GET_NODE(item, node);
item->depth++;
error = ng_apply_item(node, item, rw);
ngq = &node->nd_input_queue;
if (QUEUE_ACTIVE(ngq)) {
NG_QUEUE_LOCK(ngq);
if (QUEUE_ACTIVE(ngq) && NEXT_QUEUED_ITEM_CAN_PROCEED(ngq))
ng_worklist_add(node);
NG_QUEUE_UNLOCK(ngq);
}
NG_NODE_UNREF(node);
return (error);
done:
if (item->apply != NULL) {
if (item->depth == 0 && error != 0)
item->apply->error = error;
if (refcount_release(&item->apply->refs)) {
(*item->apply->apply)(item->apply->context,
item->apply->error);
}
}
NG_FREE_ITEM(item);
return (error);
}
static int
ng_apply_item(node_p node, item_p item, int rw)
{
hook_p hook;
ng_rcvdata_t *rcvdata;
ng_rcvmsg_t *rcvmsg;
struct ng_apply_info *apply;
int error = 0, depth;
KASSERT(node != NULL, ("ng_apply_item: node is NULL"));
KASSERT(item != NULL, ("ng_apply_item: item is NULL"));
NGI_GET_HOOK(item, hook);
#ifdef NETGRAPH_DEBUG
_ngi_check(item, __FILE__, __LINE__);
#endif
apply = item->apply;
depth = item->depth;
switch (item->el_flags & NGQF_TYPE) {
case NGQF_DATA:
KASSERT(hook != NULL, ("ng_apply_item: hook for data is NULL"));
if (NG_HOOK_NOT_VALID(hook) ||
NG_NODE_NOT_VALID(node)) {
error = EIO;
NG_FREE_ITEM(item);
break;
}
if ((!(rcvdata = hook->hk_rcvdata)) &&
(!(rcvdata = NG_HOOK_NODE(hook)->nd_type->rcvdata))) {
error = 0;
NG_FREE_ITEM(item);
break;
}
error = (*rcvdata)(hook, item);
break;
case NGQF_MESG:
if (hook && NG_HOOK_NOT_VALID(hook)) {
NG_HOOK_UNREF(hook);
hook = NULL;
}
if (NG_NODE_NOT_VALID(node)) {
TRAP_ERROR();
error = EINVAL;
NG_FREE_ITEM(item);
break;
}
if ((NGI_MSG(item)->header.typecookie == NGM_GENERIC_COOKIE) &&
((NGI_MSG(item)->header.flags & NGF_RESP) == 0)) {
error = ng_generic_msg(node, item, hook);
break;
}
if (((!hook) || (!(rcvmsg = hook->hk_rcvmsg))) &&
(!(rcvmsg = node->nd_type->rcvmsg))) {
TRAP_ERROR();
error = 0;
NG_FREE_ITEM(item);
break;
}
error = (*rcvmsg)(node, item, hook);
break;
case NGQF_FN:
case NGQF_FN2:
if (NG_NODE_NOT_VALID(node) &&
NGI_FN(item) != &ng_rmnode) {
TRAP_ERROR();
error = EINVAL;
NG_FREE_ITEM(item);
break;
}
if (hook && NG_HOOK_NOT_VALID(hook) &&
NGI_FN2(item) != &ng_con_part2 &&
NGI_FN2(item) != &ng_con_part3 &&
NGI_FN(item) != &ng_rmhook_part2) {
TRAP_ERROR();
error = EINVAL;
NG_FREE_ITEM(item);
break;
}
if ((item->el_flags & NGQF_TYPE) == NGQF_FN) {
(*NGI_FN(item))(node, hook, NGI_ARG1(item),
NGI_ARG2(item));
NG_FREE_ITEM(item);
} else
error = (*NGI_FN2(item))(node, item, hook);
break;
}
if (hook)
NG_HOOK_UNREF(hook);
if (rw == NGQRW_R)
ng_leave_read(node);
else
ng_leave_write(node);
if (apply != NULL) {
if (depth == 1 && error != 0)
apply->error = error;
if (refcount_release(&apply->refs))
(*apply->apply)(apply->context, apply->error);
}
return (error);
}
static int
ng_generic_msg(node_p here, item_p item, hook_p lasthook)
{
int error = 0;
struct ng_mesg *msg;
struct ng_mesg *resp = NULL;
NGI_GET_MSG(item, msg);
if (msg->header.typecookie != NGM_GENERIC_COOKIE) {
TRAP_ERROR();
error = EINVAL;
goto out;
}
switch (msg->header.cmd) {
case NGM_SHUTDOWN:
ng_rmnode(here, NULL, NULL, 0);
break;
case NGM_MKPEER:
{
struct ngm_mkpeer *const mkp = (struct ngm_mkpeer *) msg->data;
if (msg->header.arglen != sizeof(*mkp)) {
TRAP_ERROR();
error = EINVAL;
break;
}
mkp->type[sizeof(mkp->type) - 1] = '\0';
mkp->ourhook[sizeof(mkp->ourhook) - 1] = '\0';
mkp->peerhook[sizeof(mkp->peerhook) - 1] = '\0';
error = ng_mkpeer(here, mkp->ourhook, mkp->peerhook, mkp->type);
break;
}
case NGM_CONNECT:
{
struct ngm_connect *const con =
(struct ngm_connect *) msg->data;
node_p node2;
if (msg->header.arglen != sizeof(*con)) {
TRAP_ERROR();
error = EINVAL;
break;
}
con->path[sizeof(con->path) - 1] = '\0';
con->ourhook[sizeof(con->ourhook) - 1] = '\0';
con->peerhook[sizeof(con->peerhook) - 1] = '\0';
error = ng_path2noderef(here, con->path, &node2, NULL);
if (error)
break;
error = ng_con_nodes(item, here, con->ourhook,
node2, con->peerhook);
NG_NODE_UNREF(node2);
break;
}
case NGM_NAME:
{
struct ngm_name *const nam = (struct ngm_name *) msg->data;
if (msg->header.arglen != sizeof(*nam)) {
TRAP_ERROR();
error = EINVAL;
break;
}
nam->name[sizeof(nam->name) - 1] = '\0';
error = ng_name_node(here, nam->name);
break;
}
case NGM_RMHOOK:
{
struct ngm_rmhook *const rmh = (struct ngm_rmhook *) msg->data;
hook_p hook;
if (msg->header.arglen != sizeof(*rmh)) {
TRAP_ERROR();
error = EINVAL;
break;
}
rmh->ourhook[sizeof(rmh->ourhook) - 1] = '\0';
if ((hook = ng_findhook(here, rmh->ourhook)) != NULL)
ng_destroy_hook(hook);
break;
}
case NGM_NODEINFO:
{
struct nodeinfo *ni;
NG_MKRESPONSE(resp, msg, sizeof(*ni), M_NOWAIT);
if (resp == NULL) {
error = ENOMEM;
break;
}
ni = (struct nodeinfo *) resp->data;
if (NG_NODE_HAS_NAME(here))
strcpy(ni->name, NG_NODE_NAME(here));
strcpy(ni->type, here->nd_type->name);
ni->id = ng_node2ID(here);
ni->hooks = here->nd_numhooks;
break;
}
case NGM_LISTHOOKS:
{
const int nhooks = here->nd_numhooks;
struct hooklist *hl;
struct nodeinfo *ni;
hook_p hook;
NG_MKRESPONSE(resp, msg, sizeof(*hl) +
(nhooks * sizeof(struct linkinfo)), M_NOWAIT);
if (resp == NULL) {
error = ENOMEM;
break;
}
hl = (struct hooklist *) resp->data;
ni = &hl->nodeinfo;
if (NG_NODE_HAS_NAME(here))
strcpy(ni->name, NG_NODE_NAME(here));
strcpy(ni->type, here->nd_type->name);
ni->id = ng_node2ID(here);
ni->hooks = 0;
LIST_FOREACH(hook, &here->nd_hooks, hk_hooks) {
struct linkinfo *const link = &hl->link[ni->hooks];
if (ni->hooks >= nhooks) {
log(LOG_ERR, "%s: number of %s changed\n",
__func__, "hooks");
break;
}
if (NG_HOOK_NOT_VALID(hook))
continue;
strcpy(link->ourhook, NG_HOOK_NAME(hook));
strcpy(link->peerhook, NG_PEER_HOOK_NAME(hook));
if (NG_PEER_NODE_NAME(hook)[0] != '\0')
strcpy(link->nodeinfo.name,
NG_PEER_NODE_NAME(hook));
strcpy(link->nodeinfo.type,
NG_PEER_NODE(hook)->nd_type->name);
link->nodeinfo.id = ng_node2ID(NG_PEER_NODE(hook));
link->nodeinfo.hooks = NG_PEER_NODE(hook)->nd_numhooks;
ni->hooks++;
}
break;
}
case NGM_LISTNODES:
{
struct namelist *nl;
node_p node;
int i;
IDHASH_RLOCK();
NG_MKRESPONSE(resp, msg, sizeof(*nl) +
(V_ng_nodes * sizeof(struct nodeinfo)), M_NOWAIT);
if (resp == NULL) {
IDHASH_RUNLOCK();
error = ENOMEM;
break;
}
nl = (struct namelist *) resp->data;
nl->numnames = 0;
for (i = 0; i <= V_ng_ID_hmask; i++) {
LIST_FOREACH(node, &V_ng_ID_hash[i], nd_idnodes) {
struct nodeinfo *const np =
&nl->nodeinfo[nl->numnames];
if (NG_NODE_NOT_VALID(node))
continue;
if (NG_NODE_HAS_NAME(node))
strcpy(np->name, NG_NODE_NAME(node));
strcpy(np->type, node->nd_type->name);
np->id = ng_node2ID(node);
np->hooks = node->nd_numhooks;
KASSERT(nl->numnames < V_ng_nodes,
("%s: no space", __func__));
nl->numnames++;
}
}
IDHASH_RUNLOCK();
break;
}
case NGM_LISTNAMES:
{
struct namelist *nl;
node_p node;
int i;
NAMEHASH_RLOCK();
NG_MKRESPONSE(resp, msg, sizeof(*nl) +
(V_ng_named_nodes * sizeof(struct nodeinfo)), M_NOWAIT);
if (resp == NULL) {
NAMEHASH_RUNLOCK();
error = ENOMEM;
break;
}
nl = (struct namelist *) resp->data;
nl->numnames = 0;
for (i = 0; i <= V_ng_name_hmask; i++) {
LIST_FOREACH(node, &V_ng_name_hash[i], nd_nodes) {
struct nodeinfo *const np =
&nl->nodeinfo[nl->numnames];
if (NG_NODE_NOT_VALID(node))
continue;
strcpy(np->name, NG_NODE_NAME(node));
strcpy(np->type, node->nd_type->name);
np->id = ng_node2ID(node);
np->hooks = node->nd_numhooks;
KASSERT(nl->numnames < V_ng_named_nodes,
("%s: no space", __func__));
nl->numnames++;
}
}
NAMEHASH_RUNLOCK();
break;
}
case NGM_LISTTYPES:
{
struct typelist *tl;
struct ng_type *type;
int num = 0;
TYPELIST_RLOCK();
LIST_FOREACH(type, &ng_typelist, types)
num++;
NG_MKRESPONSE(resp, msg, sizeof(*tl) +
(num * sizeof(struct typeinfo)), M_NOWAIT);
if (resp == NULL) {
TYPELIST_RUNLOCK();
error = ENOMEM;
break;
}
tl = (struct typelist *) resp->data;
tl->numtypes = 0;
LIST_FOREACH(type, &ng_typelist, types) {
struct typeinfo *const tp = &tl->typeinfo[tl->numtypes];
strcpy(tp->type_name, type->name);
tp->numnodes = type->refs - 1;
KASSERT(tl->numtypes < num, ("%s: no space", __func__));
tl->numtypes++;
}
TYPELIST_RUNLOCK();
break;
}
case NGM_BINARY2ASCII:
{
int bufSize = 1024;
const struct ng_parse_type *argstype;
const struct ng_cmdlist *c;
struct ng_mesg *binary, *ascii;
binary = (struct ng_mesg *)msg->data;
if (msg->header.arglen < sizeof(struct ng_mesg) ||
(msg->header.arglen - sizeof(struct ng_mesg) <
binary->header.arglen)) {
TRAP_ERROR();
error = EINVAL;
break;
}
retry_b2a:
NG_MKRESPONSE(resp, msg, sizeof(*ascii) + bufSize, M_NOWAIT);
if (resp == NULL) {
error = ENOMEM;
break;
}
ascii = (struct ng_mesg *)resp->data;
bcopy(binary, ascii, sizeof(*binary));
for (c = here->nd_type->cmdlist; c != NULL && c->name != NULL;
c++) {
if (binary->header.typecookie == c->cookie &&
binary->header.cmd == c->cmd)
break;
}
if (c == NULL || c->name == NULL) {
for (c = ng_generic_cmds; c->name != NULL; c++) {
if (binary->header.typecookie == c->cookie &&
binary->header.cmd == c->cmd)
break;
}
if (c->name == NULL) {
NG_FREE_MSG(resp);
error = ENOSYS;
break;
}
}
snprintf(ascii->header.cmdstr, sizeof(ascii->header.cmdstr),
"%s", c->name);
argstype = (binary->header.flags & NGF_RESP) ?
c->respType : c->mesgType;
if (argstype == NULL) {
*ascii->data = '\0';
} else {
error = ng_unparse(argstype, (u_char *)binary->data,
ascii->data, bufSize);
if (error == ERANGE) {
NG_FREE_MSG(resp);
bufSize *= 2;
goto retry_b2a;
} else if (error) {
NG_FREE_MSG(resp);
break;
}
}
bufSize = strlen(ascii->data) + 1;
ascii->header.arglen = bufSize;
resp->header.arglen = sizeof(*ascii) + bufSize;
break;
}
case NGM_ASCII2BINARY:
{
int bufSize = 20 * 1024;
const struct ng_cmdlist *c;
const struct ng_parse_type *argstype;
struct ng_mesg *ascii, *binary;
int off = 0;
ascii = (struct ng_mesg *)msg->data;
if ((msg->header.arglen < sizeof(*ascii) + 1) ||
(ascii->header.arglen < 1) ||
(msg->header.arglen < sizeof(*ascii) +
ascii->header.arglen)) {
TRAP_ERROR();
error = EINVAL;
break;
}
ascii->data[ascii->header.arglen - 1] = '\0';
NG_MKRESPONSE(resp, msg, sizeof(*binary) + bufSize, M_NOWAIT);
if (resp == NULL) {
error = ENOMEM;
break;
}
binary = (struct ng_mesg *)resp->data;
bcopy(ascii, binary, sizeof(*ascii));
for (c = here->nd_type->cmdlist;
c != NULL && c->name != NULL; c++) {
if (strcmp(ascii->header.cmdstr, c->name) == 0)
break;
}
if (c == NULL || c->name == NULL) {
for (c = ng_generic_cmds; c->name != NULL; c++) {
if (strcmp(ascii->header.cmdstr, c->name) == 0)
break;
}
if (c->name == NULL) {
NG_FREE_MSG(resp);
error = ENOSYS;
break;
}
}
binary->header.cmd = c->cmd;
binary->header.typecookie = c->cookie;
argstype = (binary->header.flags & NGF_RESP) ?
c->respType : c->mesgType;
if (argstype == NULL) {
bufSize = 0;
} else {
if ((error = ng_parse(argstype, ascii->data, &off,
(u_char *)binary->data, &bufSize)) != 0) {
NG_FREE_MSG(resp);
break;
}
}
binary->header.arglen = bufSize;
resp->header.arglen = sizeof(*binary) + bufSize;
break;
}
case NGM_TEXT_CONFIG:
case NGM_TEXT_STATUS:
if (here->nd_type->rcvmsg != NULL) {
NGI_MSG(item) = msg;
return((*here->nd_type->rcvmsg)(here, item, lasthook));
}
default:
TRAP_ERROR();
error = EINVAL;
}
out:
NG_RESPOND_MSG(error, here, item, resp);
NG_FREE_MSG(msg);
return (error);
}
uma_zone_t ng_qzone;
uma_zone_t ng_qdzone;
static int numthreads = 0;
static int maxalloc = 4096;
static int maxdata = 4096;
SYSCTL_INT(_net_graph, OID_AUTO, threads, CTLFLAG_RDTUN, &numthreads,
0, "Number of queue processing threads");
SYSCTL_INT(_net_graph, OID_AUTO, maxalloc, CTLFLAG_RDTUN, &maxalloc,
0, "Maximum number of non-data queue items to allocate");
SYSCTL_INT(_net_graph, OID_AUTO, maxdata, CTLFLAG_RDTUN, &maxdata,
0, "Maximum number of data queue items to allocate");
#ifdef NETGRAPH_DEBUG
static TAILQ_HEAD(, ng_item) ng_itemlist = TAILQ_HEAD_INITIALIZER(ng_itemlist);
static int allocated;
#endif
static __inline item_p
ng_alloc_item(int type, int flags)
{
item_p item;
KASSERT(((type & ~NGQF_TYPE) == 0),
("%s: incorrect item type: %d", __func__, type));
item = uma_zalloc((type == NGQF_DATA) ? ng_qdzone : ng_qzone,
((flags & NG_WAITOK) ? M_WAITOK : M_NOWAIT) | M_ZERO);
if (item) {
item->el_flags = type;
#ifdef NETGRAPH_DEBUG
mtx_lock(&ngq_mtx);
TAILQ_INSERT_TAIL(&ng_itemlist, item, all);
allocated++;
mtx_unlock(&ngq_mtx);
#endif
}
return (item);
}
void
ng_free_item(item_p item)
{
switch (item->el_flags & NGQF_TYPE) {
case NGQF_DATA:
NG_FREE_M(_NGI_M(item));
break;
case NGQF_MESG:
_NGI_RETADDR(item) = 0;
NG_FREE_MSG(_NGI_MSG(item));
break;
case NGQF_FN:
case NGQF_FN2:
_NGI_FN(item) = NULL;
_NGI_ARG1(item) = NULL;
_NGI_ARG2(item) = 0;
break;
}
_NGI_CLR_NODE(item);
_NGI_CLR_HOOK(item);
#ifdef NETGRAPH_DEBUG
mtx_lock(&ngq_mtx);
TAILQ_REMOVE(&ng_itemlist, item, all);
allocated--;
mtx_unlock(&ngq_mtx);
#endif
uma_zfree(((item->el_flags & NGQF_TYPE) == NGQF_DATA) ?
ng_qdzone : ng_qzone, item);
}
static __inline item_p
ng_realloc_item(item_p pitem, int type, int flags)
{
item_p item;
int from, to;
KASSERT((pitem != NULL), ("%s: can't reallocate NULL", __func__));
KASSERT(((type & ~NGQF_TYPE) == 0),
("%s: incorrect item type: %d", __func__, type));
from = ((pitem->el_flags & NGQF_TYPE) == NGQF_DATA);
to = (type == NGQF_DATA);
if (from != to) {
if ((item = ng_alloc_item(type, flags)) == NULL) {
ng_free_item(pitem);
return (NULL);
}
*item = *pitem;
ng_free_item(pitem);
} else
item = pitem;
item->el_flags = (item->el_flags & ~NGQF_TYPE) | type;
return (item);
}
int
ng_mod_event(module_t mod, int event, void *data)
{
struct ng_type *const type = data;
int error = 0;
switch (event) {
case MOD_LOAD:
if ((error = ng_newtype(type)) != 0)
break;
if (type->mod_event != NULL)
if ((error = (*type->mod_event)(mod, event, data))) {
TYPELIST_WLOCK();
type->refs--;
LIST_REMOVE(type, types);
TYPELIST_WUNLOCK();
}
break;
case MOD_UNLOAD:
if (type->refs > 1) {
error = EBUSY;
} else {
if (type->refs == 0)
break;
if (type->mod_event != NULL) {
error = (*type->mod_event)(mod, event, data);
if (error != 0)
break;
}
TYPELIST_WLOCK();
LIST_REMOVE(type, types);
TYPELIST_WUNLOCK();
}
break;
default:
if (type->mod_event != NULL)
error = (*type->mod_event)(mod, event, data);
else
error = EOPNOTSUPP;
break;
}
return (error);
}
static void
vnet_netgraph_init(const void *unused __unused)
{
V_ng_ID_hash = hashinit(16, M_NETGRAPH_NODE, &V_ng_ID_hmask);
V_ng_name_hash = hashinit(16, M_NETGRAPH_NODE, &V_ng_name_hmask);
}
VNET_SYSINIT(vnet_netgraph_init, SI_SUB_NETGRAPH, SI_ORDER_FIRST,
vnet_netgraph_init, NULL);
#ifdef VIMAGE
static void
vnet_netgraph_uninit(const void *unused __unused)
{
node_p node = NULL, last_killed = NULL;
int i;
do {
IDHASH_RLOCK();
for (i = 0; i <= V_ng_ID_hmask; i++) {
LIST_FOREACH(node, &V_ng_ID_hash[i], nd_idnodes) {
if (node != &ng_deadnode) {
NG_NODE_REF(node);
break;
}
}
if (node != NULL)
break;
}
IDHASH_RUNLOCK();
if (node != NULL) {
if (node == last_killed) {
if (node->nd_flags & NGF_REALLY_DIE)
panic("ng node %s won't die",
node->nd_name);
node->nd_flags |= NGF_REALLY_DIE;
}
ng_rmnode(node, NULL, NULL, 0);
NG_NODE_UNREF(node);
last_killed = node;
}
} while (node != NULL);
hashdestroy(V_ng_name_hash, M_NETGRAPH_NODE, V_ng_name_hmask);
hashdestroy(V_ng_ID_hash, M_NETGRAPH_NODE, V_ng_ID_hmask);
}
VNET_SYSUNINIT(vnet_netgraph_uninit, SI_SUB_NETGRAPH, SI_ORDER_FIRST,
vnet_netgraph_uninit, NULL);
#endif
static int
ngb_mod_event(module_t mod, int event, void *data)
{
struct proc *p;
struct thread *td;
int i, error = 0;
switch (event) {
case MOD_LOAD:
NG_WORKLIST_LOCK_INIT();
rw_init(&ng_typelist_lock, "netgraph types");
rw_init(&ng_idhash_lock, "netgraph idhash");
rw_init(&ng_namehash_lock, "netgraph namehash");
rw_init(&ng_topo_lock, "netgraph topology mutex");
#ifdef NETGRAPH_DEBUG
mtx_init(&ng_nodelist_mtx, "netgraph nodelist mutex", NULL,
MTX_DEF);
mtx_init(&ngq_mtx, "netgraph item list mutex", NULL,
MTX_DEF);
#endif
ng_qzone = uma_zcreate("NetGraph items", sizeof(struct ng_item),
NULL, NULL, NULL, NULL, UMA_ALIGN_CACHE, 0);
uma_zone_set_max(ng_qzone, maxalloc);
ng_qdzone = uma_zcreate("NetGraph data items",
sizeof(struct ng_item), NULL, NULL, NULL, NULL,
UMA_ALIGN_CACHE, 0);
uma_zone_set_max(ng_qdzone, maxdata);
if (numthreads <= 0)
numthreads = mp_ncpus;
p = NULL;
for (i = 0; i < numthreads; i++) {
if (kproc_kthread_add(ngthread, NULL, &p, &td,
RFHIGHPID, 0, "ng_queue", "ng_queue%d", i)) {
numthreads = i;
break;
}
}
break;
case MOD_UNLOAD:
error = EBUSY;
break;
default:
error = EOPNOTSUPP;
break;
}
return (error);
}
static moduledata_t netgraph_mod = {
"netgraph",
ngb_mod_event,
(NULL)
};
DECLARE_MODULE(netgraph, netgraph_mod, SI_SUB_NETGRAPH, SI_ORDER_FIRST);
SYSCTL_NODE(_net, OID_AUTO, graph, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
"netgraph Family");
SYSCTL_INT(_net_graph, OID_AUTO, abi_version, CTLFLAG_RD, SYSCTL_NULL_INT_PTR, NG_ABI_VERSION,"");
SYSCTL_INT(_net_graph, OID_AUTO, msg_version, CTLFLAG_RD, SYSCTL_NULL_INT_PTR, NG_VERSION, "");
#ifdef NETGRAPH_DEBUG
void
dumphook (hook_p hook, char *file, int line)
{
printf("hook: name %s, %d refs, Last touched:\n",
_NG_HOOK_NAME(hook), hook->hk_refs);
printf(" Last active @ %s, line %d\n",
hook->lastfile, hook->lastline);
if (line) {
printf(" problem discovered at file %s, line %d\n", file, line);
#ifdef KDB
kdb_backtrace();
#endif
}
}
void
dumpnode(node_p node, char *file, int line)
{
printf("node: ID [%x]: type '%s', %d hooks, flags 0x%x, %d refs, %s:\n",
_NG_NODE_ID(node), node->nd_type->name,
node->nd_numhooks, node->nd_flags,
node->nd_refs, node->nd_name);
printf(" Last active @ %s, line %d\n",
node->lastfile, node->lastline);
if (line) {
printf(" problem discovered at file %s, line %d\n", file, line);
#ifdef KDB
kdb_backtrace();
#endif
}
}
void
dumpitem(item_p item, char *file, int line)
{
printf(" ACTIVE item, last used at %s, line %d",
item->lastfile, item->lastline);
switch(item->el_flags & NGQF_TYPE) {
case NGQF_DATA:
printf(" - [data]\n");
break;
case NGQF_MESG:
printf(" - retaddr[%d]:\n", _NGI_RETADDR(item));
break;
case NGQF_FN:
printf(" - fn@%p (%p, %p, %p, %d (%x))\n",
_NGI_FN(item),
_NGI_NODE(item),
_NGI_HOOK(item),
item->body.fn.fn_arg1,
item->body.fn.fn_arg2,
item->body.fn.fn_arg2);
break;
case NGQF_FN2:
printf(" - fn2@%p (%p, %p, %p, %d (%x))\n",
_NGI_FN2(item),
_NGI_NODE(item),
_NGI_HOOK(item),
item->body.fn.fn_arg1,
item->body.fn.fn_arg2,
item->body.fn.fn_arg2);
break;
}
if (line) {
printf(" problem discovered at file %s, line %d\n", file, line);
if (_NGI_NODE(item)) {
printf("node %p ([%x])\n",
_NGI_NODE(item), ng_node2ID(_NGI_NODE(item)));
}
}
}
static void
ng_dumpitems(void)
{
item_p item;
int i = 1;
TAILQ_FOREACH(item, &ng_itemlist, all) {
printf("[%d] ", i++);
dumpitem(item, NULL, 0);
}
}
static void
ng_dumpnodes(void)
{
node_p node;
int i = 1;
mtx_lock(&ng_nodelist_mtx);
SLIST_FOREACH(node, &ng_allnodes, nd_all) {
printf("[%d] ", i++);
dumpnode(node, NULL, 0);
}
mtx_unlock(&ng_nodelist_mtx);
}
static void
ng_dumphooks(void)
{
hook_p hook;
int i = 1;
mtx_lock(&ng_nodelist_mtx);
SLIST_FOREACH(hook, &ng_allhooks, hk_all) {
printf("[%d] ", i++);
dumphook(hook, NULL, 0);
}
mtx_unlock(&ng_nodelist_mtx);
}
static int
sysctl_debug_ng_dump_items(SYSCTL_HANDLER_ARGS)
{
int error;
int val;
val = allocated;
error = sysctl_handle_int(oidp, &val, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (val == 42) {
ng_dumpitems();
ng_dumpnodes();
ng_dumphooks();
}
return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, ng_dump_items,
CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, 0, sizeof(int),
sysctl_debug_ng_dump_items, "I",
"Number of allocated items");
#endif
static void
ngthread(void *arg)
{
for (;;) {
struct epoch_tracker et;
node_p node;
NG_WORKLIST_LOCK();
while ((node = STAILQ_FIRST(&ng_worklist)) == NULL)
NG_WORKLIST_SLEEP();
STAILQ_REMOVE_HEAD(&ng_worklist, nd_input_queue.q_work);
NG_WORKLIST_UNLOCK();
CURVNET_SET(node->nd_vnet);
CTR3(KTR_NET, "%20s: node [%x] (%p) taken off worklist",
__func__, node->nd_ID, node);
NET_EPOCH_ENTER(et);
for (;;) {
item_p item;
int rw;
NG_QUEUE_LOCK(&node->nd_input_queue);
item = ng_dequeue(node, &rw);
if (item == NULL) {
node->nd_input_queue.q_flags2 &= ~NGQ2_WORKQ;
NG_QUEUE_UNLOCK(&node->nd_input_queue);
break;
} else {
NG_QUEUE_UNLOCK(&node->nd_input_queue);
NGI_GET_NODE(item, node);
if ((item->el_flags & NGQF_TYPE) != NGQF_DATA) {
NET_EPOCH_EXIT(et);
ng_apply_item(node, item, rw);
NET_EPOCH_ENTER(et);
} else {
ng_apply_item(node, item, rw);
}
NG_NODE_UNREF(node);
}
}
NET_EPOCH_EXIT(et);
NG_NODE_UNREF(node);
CURVNET_RESTORE();
}
}
static void
ng_worklist_add(node_p node)
{
mtx_assert(&node->nd_input_queue.q_mtx, MA_OWNED);
if ((node->nd_input_queue.q_flags2 & NGQ2_WORKQ) == 0) {
node->nd_input_queue.q_flags2 |= NGQ2_WORKQ;
NG_NODE_REF(node);
NG_WORKLIST_LOCK();
STAILQ_INSERT_TAIL(&ng_worklist, node, nd_input_queue.q_work);
NG_WORKLIST_UNLOCK();
CTR3(KTR_NET, "%20s: node [%x] (%p) put on worklist", __func__,
node->nd_ID, node);
NG_WORKLIST_WAKEUP();
} else {
CTR3(KTR_NET, "%20s: node [%x] (%p) already on worklist",
__func__, node->nd_ID, node);
}
}
#ifdef NETGRAPH_DEBUG
#define ITEM_DEBUG_CHECKS \
do { \
if (NGI_NODE(item) ) { \
printf("item already has node"); \
kdb_enter(KDB_WHY_NETGRAPH, "has node"); \
NGI_CLR_NODE(item); \
} \
if (NGI_HOOK(item) ) { \
printf("item already has hook"); \
kdb_enter(KDB_WHY_NETGRAPH, "has hook"); \
NGI_CLR_HOOK(item); \
} \
} while (0)
#else
#define ITEM_DEBUG_CHECKS
#endif
item_p
ng_package_data(struct mbuf *m, int flags)
{
item_p item;
if ((item = ng_alloc_item(NGQF_DATA, flags)) == NULL) {
NG_FREE_M(m);
return (NULL);
}
ITEM_DEBUG_CHECKS;
item->el_flags |= NGQF_READER;
NGI_M(item) = m;
return (item);
}
item_p
ng_package_msg(struct ng_mesg *msg, int flags)
{
item_p item;
if ((item = ng_alloc_item(NGQF_MESG, flags)) == NULL) {
NG_FREE_MSG(msg);
return (NULL);
}
ITEM_DEBUG_CHECKS;
if (msg->header.cmd & NGM_READONLY)
item->el_flags |= NGQF_READER;
else
item->el_flags |= NGQF_WRITER;
NGI_MSG(item) = msg;
NGI_RETADDR(item) = 0;
return (item);
}
#define SET_RETADDR(item, here, retaddr) \
do { \
if ((item->el_flags & NGQF_TYPE) == NGQF_MESG) { \
if (retaddr) { \
NGI_RETADDR(item) = retaddr; \
} else { \
\
if (NGI_RETADDR(item) == 0) { \
NGI_RETADDR(item) \
= ng_node2ID(here); \
} \
} \
} \
} while (0)
int
ng_address_hook(node_p here, item_p item, hook_p hook, ng_ID_t retaddr)
{
hook_p peer;
node_p peernode;
ITEM_DEBUG_CHECKS;
TOPOLOGY_RLOCK();
if ((hook == NULL) || NG_HOOK_NOT_VALID(hook) ||
NG_HOOK_NOT_VALID(peer = NG_HOOK_PEER(hook)) ||
NG_NODE_NOT_VALID(peernode = NG_PEER_NODE(hook))) {
NG_FREE_ITEM(item);
TRAP_ERROR();
TOPOLOGY_RUNLOCK();
return (ENETDOWN);
}
NG_HOOK_REF(peer);
NG_NODE_REF(peernode);
NGI_SET_HOOK(item, peer);
NGI_SET_NODE(item, peernode);
SET_RETADDR(item, here, retaddr);
TOPOLOGY_RUNLOCK();
return (0);
}
int
ng_address_path(node_p here, item_p item, const char *address, ng_ID_t retaddr)
{
node_p dest = NULL;
hook_p hook = NULL;
int error;
ITEM_DEBUG_CHECKS;
error = ng_path2noderef(here, address, &dest, &hook);
if (error) {
NG_FREE_ITEM(item);
return (error);
}
NGI_SET_NODE(item, dest);
if (hook)
NGI_SET_HOOK(item, hook);
SET_RETADDR(item, here, retaddr);
return (0);
}
int
ng_address_ID(node_p here, item_p item, ng_ID_t ID, ng_ID_t retaddr)
{
node_p dest;
ITEM_DEBUG_CHECKS;
dest = ng_ID2noderef(ID);
if (dest == NULL) {
NG_FREE_ITEM(item);
TRAP_ERROR();
return(EINVAL);
}
NGI_SET_NODE(item, dest);
NGI_CLR_HOOK(item);
SET_RETADDR(item, here, retaddr);
return (0);
}
item_p
ng_package_msg_self(node_p here, hook_p hook, struct ng_mesg *msg)
{
item_p item;
if ((item = ng_alloc_item(NGQF_MESG, NG_NOFLAGS)) == NULL) {
NG_FREE_MSG(msg);
return (NULL);
}
item->el_flags |= NGQF_WRITER;
NG_NODE_REF(here);
NGI_SET_NODE(item, here);
if (hook) {
NG_HOOK_REF(hook);
NGI_SET_HOOK(item, hook);
}
NGI_MSG(item) = msg;
NGI_RETADDR(item) = ng_node2ID(here);
return (item);
}
int
ng_send_fn(node_p node, hook_p hook, ng_item_fn *fn, void * arg1, int arg2)
{
return ng_send_fn1(node, hook, fn, arg1, arg2, NG_NOFLAGS);
}
int
ng_send_fn1(node_p node, hook_p hook, ng_item_fn *fn, void * arg1, int arg2,
int flags)
{
item_p item;
if ((item = ng_alloc_item(NGQF_FN, flags)) == NULL) {
return (ENOMEM);
}
item->el_flags |= NGQF_WRITER;
NG_NODE_REF(node);
NGI_SET_NODE(item, node);
if (hook) {
NG_HOOK_REF(hook);
NGI_SET_HOOK(item, hook);
}
NGI_FN(item) = fn;
NGI_ARG1(item) = arg1;
NGI_ARG2(item) = arg2;
return(ng_snd_item(item, flags));
}
int
ng_send_fn2(node_p node, hook_p hook, item_p pitem, ng_item_fn2 *fn, void *arg1,
int arg2, int flags)
{
item_p item;
KASSERT((pitem != NULL || (flags & NG_REUSE_ITEM) == 0),
("%s: NG_REUSE_ITEM but no pitem", __func__));
if (pitem == NULL || (flags & NG_REUSE_ITEM) == 0) {
if ((item = ng_alloc_item(NGQF_FN2, flags)) == NULL)
return (ENOMEM);
if (pitem != NULL)
item->apply = pitem->apply;
} else {
if ((item = ng_realloc_item(pitem, NGQF_FN2, flags)) == NULL)
return (ENOMEM);
}
item->el_flags = (item->el_flags & ~NGQF_RW) | NGQF_WRITER;
NG_NODE_REF(node);
NGI_SET_NODE(item, node);
if (hook) {
NG_HOOK_REF(hook);
NGI_SET_HOOK(item, hook);
}
NGI_FN2(item) = fn;
NGI_ARG1(item) = arg1;
NGI_ARG2(item) = arg2;
return(ng_snd_item(item, flags));
}
static void
ng_callout_trampoline(void *arg)
{
struct epoch_tracker et;
item_p item = arg;
NET_EPOCH_ENTER(et);
CURVNET_SET(NGI_NODE(item)->nd_vnet);
ng_snd_item(item, 0);
CURVNET_RESTORE();
NET_EPOCH_EXIT(et);
}
int
ng_callout(struct callout *c, node_p node, hook_p hook, int ticks,
ng_item_fn *fn, void * arg1, int arg2)
{
item_p item, oitem;
if ((item = ng_alloc_item(NGQF_FN, NG_NOFLAGS)) == NULL)
return (ENOMEM);
item->el_flags |= NGQF_WRITER;
NG_NODE_REF(node);
NGI_SET_NODE(item, node);
if (hook) {
NG_HOOK_REF(hook);
NGI_SET_HOOK(item, hook);
}
NGI_FN(item) = fn;
NGI_ARG1(item) = arg1;
NGI_ARG2(item) = arg2;
oitem = c->c_arg;
if (callout_reset(c, ticks, &ng_callout_trampoline, item) == 1 &&
oitem != NULL)
NG_FREE_ITEM(oitem);
return (0);
}
static void
ng_uncallout_internal(struct callout *c, node_p node)
{
item_p item;
item = c->c_arg;
if ((c->c_func == &ng_callout_trampoline) &&
(item != NULL) && (NGI_NODE(item) == node)) {
NG_FREE_ITEM(item);
}
c->c_arg = NULL;
}
int
ng_uncallout(struct callout *c, node_p node)
{
int rval;
rval = callout_stop(c);
if (rval > 0)
ng_uncallout_internal(c, node);
return (rval);
}
int
ng_uncallout_drain(struct callout *c, node_p node)
{
int rval;
rval = callout_drain(c);
if (rval > 0)
ng_uncallout_internal(c, node);
return (rval);
}
void
ng_replace_retaddr(node_p here, item_p item, ng_ID_t retaddr)
{
if (retaddr) {
NGI_RETADDR(item) = retaddr;
} else {
NGI_RETADDR(item) = ng_node2ID(here);
}
}