#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ctype.h>
#include <sys/errno.h>
#include <sys/hash.h>
#include <sys/kernel.h>
#include <sys/limits.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/msgport2.h>
#include <sys/mutex2.h>
#include <sys/objcache.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/refcount.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <machine/cpu.h>
#include <netgraph7/netgraph.h>
#include <netgraph7/netgraph2.h>
#include <netgraph7/ng_parse.h>
MODULE_VERSION(netgraph, NG_ABI_VERSION);
static struct lwkt_token ng_topo_token;
#define TOPOLOGY_RLOCK() lwkt_gettoken_shared(&ng_topo_token)
#define TOPOLOGY_RUNLOCK() lwkt_reltoken(&ng_topo_token)
#define TOPOLOGY_WLOCK() lwkt_gettoken(&ng_topo_token)
#define TOPOLOGY_WUNLOCK() lwkt_reltoken(&ng_topo_token)
#define TOPOLOGY_NOTOWNED() KKASSERT(!LWKT_TOKEN_HELD_ANY(&ng_topo_token))
#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.hooks),
{},
{},
{
0,
NULL,
0,
NULL,
},
1,
#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 struct lwkt_port ng_panic_reply_port;
struct lwkt_port *ng_msgport[MAXCPU];
static LIST_HEAD(, ng_type) ng_typelist;
static struct lwkt_token ng_typelist_token;
#define TYPELIST_RLOCK() lwkt_gettoken_shared(&ng_typelist_token)
#define TYPELIST_RUNLOCK() lwkt_reltoken(&ng_typelist_token)
#define TYPELIST_WLOCK() lwkt_gettoken(&ng_typelist_token)
#define TYPELIST_WUNLOCK() lwkt_reltoken(&ng_typelist_token)
LIST_HEAD(nodehash, ng_node);
static struct nodehash *ng_ID_hash;
static u_long ng_ID_hmask;
static u_long ng_nodes;
static struct nodehash *ng_name_hash;
static u_long ng_name_hmask;
static u_long ng_named_nodes;
static struct lwkt_token ng_idhash_token;
#define IDHASH_RLOCK() lwkt_gettoken_shared(&ng_idhash_token)
#define IDHASH_RUNLOCK() lwkt_reltoken(&ng_idhash_token)
#define IDHASH_WLOCK() lwkt_gettoken(&ng_idhash_token)
#define IDHASH_WUNLOCK() lwkt_reltoken(&ng_idhash_token)
#define IDHASH_ASSERT_LOCKED() KKASSERT(LWKT_TOKEN_HELD_ANY(&ng_idhash_token));
#define NG_IDHASH_FN(ID) ((ID) % (ng_ID_hmask + 1))
#define NG_IDHASH_FIND(ID, node) \
do { \
IDHASH_ASSERT_LOCKED(); \
LIST_FOREACH(node, &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 lwkt_token ng_namehash_token;
#define NAMEHASH_RLOCK() lwkt_gettoken_shared(&ng_namehash_token)
#define NAMEHASH_RUNLOCK() lwkt_reltoken(&ng_namehash_token)
#define NAMEHASH_WLOCK() lwkt_gettoken(&ng_namehash_token)
#define NAMEHASH_WUNLOCK() lwkt_reltoken(&ng_namehash_token)
#define NAMEHASH_ASSERT_LOCKED() KKASSERT(LWKT_TOKEN_HELD_ANY(&ng_namehash_token));
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 ngthread(void *dummy);
static int ng_apply_item(item_p item);
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);
static boolean_t bzero_ctor(void *obj, void *private, int ocflags);
void ng_destroy_hook(hook_p hook);
node_p ng_name2noderef(node_p node, const char *name);
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_HOOK, "netgraph_hook", "netgraph hook structures");
MALLOC_DEFINE(M_NETGRAPH_NODE, "netgraph_node", "netgraph node structures");
MALLOC_DEFINE(M_NETGRAPH_MSG, "netgraph_msg", "netgraph name storage");
MALLOC_DEFINE(M_NETGRAPH_APPLY, "netgraph_apply", "netgraph apply_info structures");
#define _NG_ALLOC_HOOK(hook) \
hook = kmalloc(sizeof(*hook), M_NETGRAPH_HOOK, \
M_WAITOK | M_NULLOK | M_ZERO)
#define _NG_ALLOC_NODE(node) \
node = kmalloc(sizeof(*node), M_NETGRAPH_NODE, \
M_WAITOK | M_NULLOK | M_ZERO)
#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 { kfree((hook), M_NETGRAPH_HOOK); } while (0)
#define NG_FREE_NODE(node) do { kfree((node), M_NETGRAPH_NODE); } while (0)
#endif
#ifndef TRAP_ERROR
#define TRAP_ERROR()
#endif
static ng_ID_t nextID = 1;
#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
};
#if 0
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
};
#endif
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
};
#if 0
DEFINE_PARSE_STRUCT_TYPE(typelist, TYPELIST, (&ng_generic_typeinfoarray_type));
#endif
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_typeinfo_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;
NG_NODE_REF(node);
type->refs++;
lwkt_token_init(&node->nd_token, type->name);
LIST_INIT(&node->nd_hooks);
IDHASH_WLOCK();
for (;;) {
node_p node2 = NULL;
node->nd_ID = nextID++;
NG_IDHASH_FIND(node->nd_ID, node2);
if ((node->nd_ID != 0) && (node2 == NULL)) {
break;
}
}
ng_nodes++;
if (ng_nodes * 2 > ng_ID_hmask)
ng_ID_rehash();
LIST_INSERT_HEAD(&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) {
kprintf ("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);
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;
if (refcount_release(&node->nd_refs)) {
node->nd_type->refs--;
NAMEHASH_WLOCK();
if (NG_NODE_HAS_NAME(node)) {
ng_named_nodes--;
LIST_REMOVE(node, nd_nodes);
}
NAMEHASH_WUNLOCK();
IDHASH_WLOCK();
ng_nodes--;
LIST_REMOVE(node, nd_idnodes);
IDHASH_WUNLOCK();
NG_FREE_NODE(node);
}
}
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_p 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;
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 (ng_named_nodes * 2 > ng_name_hmask)
ng_name_rehash();
hash = hash32_str(name, HASHINIT) & ng_name_hmask;
LIST_FOREACH(node2, &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
ng_named_nodes++;
strlcpy(NG_NODE_NAME(node), name, NG_NODESIZ);
LIST_INSERT_HEAD(&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) & ng_name_hmask;
NAMEHASH_RLOCK();
LIST_FOREACH(node, &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;
NAMEHASH_ASSERT_LOCKED();
new = hashinit((ng_name_hmask + 1) * 2, M_NETGRAPH_NODE, &hmask);
if (new == NULL)
return;
for (i = 0; i <= ng_name_hmask; i++) {
LIST_FOREACH_MUTABLE(node, &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(ng_name_hash, M_NETGRAPH_NODE, ng_name_hmask);
ng_name_hash = new;
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;
IDHASH_ASSERT_LOCKED();
new = hashinit((ng_ID_hmask + 1) * 2, M_NETGRAPH_NODE, &hmask);
if (new == NULL)
return;
for (i = 0; i <= ng_ID_hmask; i++) {
LIST_FOREACH_MUTABLE(node, &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(ng_ID_hash, M_NETGRAPH_NODE, ng_name_hmask);
ng_ID_hash = new;
ng_ID_hmask = hmask;
}
void
ng_unref_hook(hook_p hook)
{
int v;
if (hook == &ng_deadhook) {
return;
}
v = atomic_fetchadd_int(&hook->hk_refs, -1);
if (v == 1) {
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) {
kprintf("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);
}
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;
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) {
kprintf("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);
kprintf("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);
kprintf("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);
kprintf("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);
kprintf("failed in ng_con_part2(A)\n");
ERROUT(error);
}
}
TOPOLOGY_RLOCK();
peer = hook->hk_peer;
if (peer == &ng_deadhook) {
TOPOLOGY_RUNLOCK();
kprintf("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))) {
kprintf("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))) {
kprintf("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, pbuf[2];
node_p node, oldnode;
char *cp;
hook_p hook = NULL;
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 (path == NULL) {
pbuf[0] = '.';
pbuf[1] = '\0';
path = pbuf;
}
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);
}
for (cp = path; node != NULL && *cp != '\0'; ) {
char *segment;
for (segment = cp; *cp != '\0'; cp++) {
if (*cp == '.') {
*cp++ = '\0';
break;
}
}
if (*segment == '\0')
continue;
hook = ng_findhook(node, segment);
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);
#if 0
kprintf("hooknotvalid %s %s %d %d %d %d ",
path,
segment,
hook == NULL,
NG_HOOK_PEER(hook) == NULL,
NG_HOOK_NOT_VALID(hook),
NG_HOOK_NOT_VALID(NG_HOOK_PEER(hook)));
#endif
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);
node = NULL;
}
}
if (node == NULL) {
TRAP_ERROR();
return (ENXIO);
}
*destp = node;
if (lasthook != NULL)
*lasthook = (hook ? NG_HOOK_PEER(hook) : NULL);
return (0);
}
static __inline void ng_acquire_read(node_p node);
static __inline void ng_acquire_write(node_p node);
static __inline void ng_leave_readwrite(node_p node);
static __inline void
ng_acquire_read(node_p node)
{
KASSERT(node != &ng_deadnode,
("%s: working on deadnode", __func__));
lwkt_gettoken_shared(&node->nd_token);
}
static __inline void
ng_acquire_write(node_p node)
{
KASSERT(node != &ng_deadnode,
("%s: working on deadnode", __func__));
lwkt_gettoken(&node->nd_token);
}
static __inline void
ng_leave_readwrite(node_p node)
{
lwkt_reltoken(&node->nd_token);
}
static void
ngthread(void *dummy __unused)
{
lwkt_msg_t msg;
while ((msg = lwkt_waitport(&curthread->td_msgport, 0)) != NULL) {
item_p item = (void *)msg;
ng_apply_item(item);
}
}
int
ng_snd_item(item_p item, int flags)
{
hook_p hook;
node_p node;
int error = 0;
KASSERT(item != NULL, ("ng_snd_item: item is NULL"));
#ifdef NETGRAPH_DEBUG
_ngi_check(item, __FILE__, __LINE__);
#endif
ng_ref_item(item);
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->refs == 1) {
struct lwkt_msg *msg = &item->el_lmsg;
lwkt_initmsg(msg, &ng_panic_reply_port, 0);
lwkt_sendmsg(ng_cpuport(0), msg);
return ((flags & NG_PROGRESS) ? EINPROGRESS : 0);
}
error = ng_apply_item(item);
done:
return (error);
}
static int
ng_apply_item(item_p item)
{
hook_p hook;
node_p node;
ng_rcvdata_t *rcvdata;
ng_rcvmsg_t *rcvmsg;
int error = 0;
KASSERT(item != NULL, ("ng_apply_item: item is NULL"));
NGI_GET_NODE(item, node);
KASSERT(node != NULL, ("ng_apply_item: node is NULL"));
NGI_GET_HOOK(item, hook);
if (((item->el_flags & NGQF_RW) == NGQF_WRITER) ||
(node->nd_flags & NGF_FORCE_WRITER) ||
(hook && (hook->hk_flags & HK_FORCE_WRITER))) {
ng_acquire_write(node);
} else {
ng_acquire_read(node);
}
#ifdef NETGRAPH_DEBUG
_ngi_check(item, __FILE__, __LINE__);
#endif
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 ((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);
ng_leave_readwrite(node);
ng_unref_item(item, error);
NG_NODE_UNREF(node);
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_WAITOK | M_NULLOK);
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_WAITOK | M_NULLOK);
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) +
(ng_nodes * sizeof(struct nodeinfo)), M_NOWAIT | M_ZERO);
if (resp == NULL) {
IDHASH_RUNLOCK();
error = ENOMEM;
break;
}
nl = (struct namelist *) resp->data;
nl->numnames = 0;
for (i = 0; i <= ng_ID_hmask; i++) {
LIST_FOREACH(node, &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 < 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) +
(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 <= ng_name_hmask; i++) {
LIST_FOREACH(node, &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 < 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_WAITOK | M_NULLOK);
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 = 20 * 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;
}
NG_MKRESPONSE(resp, msg, sizeof(*ascii) + bufSize, M_WAITOK | M_NULLOK);
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;
}
}
ksnprintf(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 {
if ((error = ng_unparse(argstype,
(u_char *)binary->data,
ascii->data, bufSize)) != 0) {
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 = 2000;
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_WAITOK | M_NULLOK);
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);
if (msg)
NG_FREE_MSG(msg);
return (error);
}
static struct objcache *ng_oc;
static struct objcache *ng_apply_oc;
static int maxalloc = 4096;
TUNABLE_INT("net.graph.maxalloc", &maxalloc);
SYSCTL_INT(_net_graph, OID_AUTO, maxalloc, CTLFLAG_RD, &maxalloc,
0, "Maximum number of 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 = objcache_get(ng_oc,
(flags & NG_WAITOK) ? M_WAITOK : M_NOWAIT );
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)
{
KASSERT(item->apply == NULL, ("freeing item with registered callback"));
KASSERT(item->refs == 0, ("freeing item with non-zero refcount"));
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
bzero(item, sizeof(struct ng_item));
objcache_put(ng_oc, item);
}
static __inline item_p
ng_realloc_item(item_p item, int type, int flags)
{
KASSERT((item != NULL), ("%s: can't reallocate NULL", __func__));
KASSERT(((type & ~NGQF_TYPE) == 0),
("%s: incorrect item type: %d", __func__, type));
item->el_flags = (item->el_flags & ~NGQF_TYPE) | type;
return (item);
}
__inline apply_p
ng_alloc_apply(void)
{
return (objcache_get(ng_apply_oc, M_WAITOK));
}
__inline void
ng_free_apply(apply_p apply)
{
objcache_put(ng_apply_oc, apply);
}
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 int
ngb_mod_event(module_t mod, int event, void *data)
{
int i, error = 0;
switch (event) {
case MOD_LOAD:
lwkt_token_init(&ng_typelist_token, "ng typelist");
lwkt_token_init(&ng_idhash_token, "ng idhash");
lwkt_token_init(&ng_namehash_token, "ng namehash");
lwkt_token_init(&ng_topo_token, "ng topology");
#ifdef NETGRAPH_DEBUG
mtx_init(&ng_nodelist_mtx, "ng nodelist");
mtx_init(&ngq_mtx, "ng queue");
#endif
ng_oc = objcache_create_mbacked(M_NETGRAPH,
sizeof(struct ng_item), maxalloc, 0, bzero_ctor,
NULL, NULL);
ng_apply_oc = objcache_create_mbacked(M_NETGRAPH_APPLY,
sizeof(struct ng_apply_info), 0, 0, bzero_ctor,
NULL, NULL);
ng_ID_hash = hashinit(16, M_NETGRAPH_NODE, &ng_ID_hmask);
ng_name_hash = hashinit(16, M_NETGRAPH_NODE, &ng_name_hmask);
lwkt_initport_panic(&ng_panic_reply_port);
for (i = 0; i < ncpus; ++i) {
thread_t td;
lwkt_create(ngthread, NULL, &td,
NULL, 0, i, "netgraph %d", i);
ng_msgport[i] = &td->td_msgport;
}
break;
case MOD_UNLOAD:
#if 0
hashdestroy(V_ng_name_hash, M_NETGRAPH_NODE, V_ng_name_hmask);
hashdestroy(V_ng_ID_hash, M_NETGRAPH_NODE, V_ng_ID_hmask);
objcache_destroy(ng_apply_oc);
objcache_destroy(ng_oc);
#endif
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_MIDDLE);
SYSCTL_NODE(_net, OID_AUTO, graph, CTLFLAG_RW, 0, "netgraph Family");
SYSCTL_INT(_net_graph, OID_AUTO, abi_version, CTLFLAG_RD, 0, NG_ABI_VERSION,"");
SYSCTL_INT(_net_graph, OID_AUTO, msg_version, CTLFLAG_RD, 0, NG_VERSION, "");
#ifdef NETGRAPH_DEBUG
void
dumphook (hook_p hook, char *file, int line)
{
kprintf("hook: name %s, %d refs, Last touched:\n",
_NG_HOOK_NAME(hook), hook->hk_refs);
kprintf(" Last active @ %s, line %d\n",
hook->lastfile, hook->lastline);
if (line) {
kprintf(" problem discovered at file %s, line %d\n", file, line);
}
}
void
dumpnode(node_p node, char *file, int line)
{
kprintf("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);
kprintf(" Last active @ %s, line %d\n",
node->lastfile, node->lastline);
if (line) {
kprintf(" problem discovered at file %s, line %d\n", file, line);
}
}
void
dumpitem(item_p item, char *file, int line)
{
kprintf(" ACTIVE item, last used at %s, line %d",
item->lastfile, item->lastline);
switch(item->el_flags & NGQF_TYPE) {
case NGQF_DATA:
kprintf(" - [data]\n");
break;
case NGQF_MESG:
kprintf(" - retaddr[%d]:\n", _NGI_RETADDR(item));
break;
case NGQF_FN:
kprintf(" - 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:
kprintf(" - 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) {
kprintf(" problem discovered at file %s, line %d\n", file, line);
if (_NGI_NODE(item)) {
kprintf("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) {
kprintf("[%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) {
kprintf("[%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) {
kprintf("[%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,
0, sizeof(int), sysctl_debug_ng_dump_items, "I", "Number of allocated items");
#endif
#ifdef NETGRAPH_DEBUG
#define ITEM_DEBUG_CHECKS \
do { \
if (NGI_NODE(item) ) { \
kprintf("item already has node"); \
kdb_enter(KDB_WHY_NETGRAPH, "has node"); \
NGI_CLR_NODE(item); \
} \
if (NGI_HOOK(item) ) { \
kprintf("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;
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();
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);
return (0);
}
int
ng_address_path(node_p here, item_p item, 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) {
NG_HOOK_REF(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);
} 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)
{
item_p item = arg;
ng_snd_item(item, 0);
}
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 = callout_arg(c);
callout_reset(c, ticks, &ng_callout_trampoline, item);
return (0);
}
int
ng_uncallout(struct callout *c, node_p node)
{
item_p item;
int rval;
KASSERT(c != NULL, ("ng_uncallout: NULL callout"));
KASSERT(node != NULL, ("ng_uncallout: NULL node"));
rval = callout_stop(c);
item = callout_arg(c);
if ((rval > 0) && (callout_func(c) == &ng_callout_trampoline) &&
(NGI_NODE(item) == node)) {
NG_FREE_ITEM(item);
}
callout_set_arg(c, NULL);
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);
}
}
static boolean_t
bzero_ctor(void *obj, void *private, int ocflags)
{
struct ng_item *i = obj;
bzero(i, sizeof(struct ng_item));
return(TRUE);
}
#define TESTING
#ifdef TESTING
void
ng_macro_test(item_p item);
void
ng_macro_test(item_p item)
{
node_p node = NULL;
hook_p hook = NULL;
struct mbuf *m;
struct ng_mesg *msg;
ng_ID_t retaddr;
int error;
NGI_GET_M(item, m);
NGI_GET_MSG(item, msg);
retaddr = NGI_RETADDR(item);
NG_SEND_DATA(error, hook, m, NULL);
NG_SEND_DATA_ONLY(error, hook, m);
NG_FWD_NEW_DATA(error, item, hook, m);
NG_FWD_ITEM_HOOK(error, item, hook);
NG_SEND_MSG_HOOK(error, node, msg, hook, retaddr);
NG_SEND_MSG_ID(error, node, msg, retaddr, retaddr);
NG_SEND_MSG_PATH(error, node, msg, ".:", retaddr);
NG_FWD_MSG_HOOK(error, node, item, hook, retaddr);
}
#endif