#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/kmem.h>
#include <sys/rwlock.h>
#include <sys/errno.h>
#include <sys/queue.h>
#include <sys/sunddi.h>
#include <inet/common.h>
#include <inet/led.h>
#include <inet/ip.h>
#include <sys/neti.h>
#include <sys/zone.h>
#include <sys/sdt.h>
typedef boolean_t napplyfn_t(neti_stack_t *, void *);
static void *neti_stack_init(netstackid_t stackid, netstack_t *ns);
static void neti_stack_fini(netstackid_t stackid, void *arg);
static net_instance_int_t *net_instance_int_create(net_instance_t *nin,
net_instance_int_t *parent);
static void neti_stack_shutdown(netstackid_t stackid, void *arg);
static void net_instance_int_free(net_instance_int_t *nini);
static boolean_t neti_stack_apply_create(neti_stack_t *, void *);
static boolean_t neti_stack_apply_destroy(neti_stack_t *, void *);
static boolean_t neti_stack_apply_shutdown(neti_stack_t *, void *);
static void neti_apply_all_instances(neti_stack_t *, napplyfn_t *);
static void neti_apply_all_stacks(void *, napplyfn_t *);
static boolean_t wait_for_nini_inprogress(neti_stack_t *,
net_instance_int_t *, uint32_t);
static nini_head_t neti_instance_list;
static neti_stack_head_t neti_stack_list;
static kmutex_t neti_stack_lock;
void
neti_init()
{
mutex_init(&neti_stack_lock, NULL, MUTEX_DRIVER, NULL);
LIST_INIT(&neti_instance_list);
LIST_INIT(&neti_stack_list);
netstack_register(NS_NETI, neti_stack_init, neti_stack_shutdown,
neti_stack_fini);
}
void
neti_fini()
{
ASSERT(LIST_EMPTY(&neti_instance_list));
ASSERT(LIST_EMPTY(&neti_stack_list));
netstack_unregister(NS_NETI);
mutex_destroy(&neti_stack_lock);
}
static void *
neti_stack_init(netstackid_t stackid, netstack_t *ns)
{
net_instance_int_t *dup;
net_instance_int_t *n;
neti_stack_t *nts;
nts = kmem_zalloc(sizeof (*nts), KM_SLEEP);
LIST_INIT(&nts->nts_instances);
nts->nts_id = (netid_t)stackid;
nts->nts_stackid = stackid;
nts->nts_netstack = ns;
nts->nts_zoneid = netstackid_to_zoneid(stackid);
nts->nts_flags = NSF_ZONE_CREATE;
cv_init(&nts->nts_cv, NULL, CV_DRIVER, NULL);
mutex_init(&nts->nts_lock, NULL, MUTEX_DRIVER, NULL);
mutex_enter(&neti_stack_lock);
LIST_INSERT_HEAD(&neti_stack_list, nts, nts_next);
LIST_FOREACH(n, &neti_instance_list, nini_next) {
dup = net_instance_int_create(n->nini_instance, n);
mutex_enter(&nts->nts_lock);
LIST_INSERT_HEAD(&nts->nts_instances, dup, nini_next);
mutex_exit(&nts->nts_lock);
}
neti_apply_all_instances(nts, neti_stack_apply_create);
mutex_enter(&nts->nts_lock);
nts->nts_flags &= ~NSF_ZONE_CREATE;
mutex_exit(&nts->nts_lock);
mutex_exit(&neti_stack_lock);
return (nts);
}
static void
neti_stack_shutdown(netstackid_t stackid, void *arg)
{
neti_stack_t *nts = arg;
net_instance_int_t *n;
struct net_data *nd;
ASSERT(nts != NULL);
mutex_enter(&neti_stack_lock);
mutex_enter(&nts->nts_lock);
LIST_FOREACH(nd, &nts->nts_netd_head, netd_list) {
nd->netd_condemned = 1;
}
LIST_FOREACH(n, &nts->nts_instances, nini_next) {
if (n->nini_instance->nin_shutdown == NULL) {
n->nini_flags |= NSS_SHUTDOWN_COMPLETED;
continue;
}
if ((n->nini_flags & (NSS_DESTROY_ALL|NSS_SHUTDOWN_ALL)) == 0)
n->nini_flags |= NSS_SHUTDOWN_NEEDED;
}
nts->nts_flags |= NSF_ZONE_SHUTDOWN;
mutex_exit(&nts->nts_lock);
neti_apply_all_instances(nts, neti_stack_apply_shutdown);
mutex_enter(&nts->nts_lock);
nts->nts_netstack = NULL;
nts->nts_flags &= ~NSF_ZONE_SHUTDOWN;
mutex_exit(&nts->nts_lock);
mutex_exit(&neti_stack_lock);
ASSERT(nts != NULL);
}
static void
neti_stack_fini(netstackid_t stackid, void *arg)
{
neti_stack_t *nts = arg;
net_instance_int_t *n;
struct net_data *nd;
mutex_enter(&neti_stack_lock);
mutex_enter(&nts->nts_lock);
LIST_REMOVE(nts, nts_next);
LIST_FOREACH(nd, &nts->nts_netd_head, netd_list) {
nd->netd_condemned = 2;
}
LIST_FOREACH(n, &nts->nts_instances, nini_next) {
ASSERT((n->nini_flags & NSS_SHUTDOWN_ALL) != 0);
if ((n->nini_flags & NSS_DESTROY_ALL) == 0)
n->nini_flags |= NSS_DESTROY_NEEDED;
}
mutex_exit(&nts->nts_lock);
neti_apply_all_instances(nts, neti_stack_apply_destroy);
while (!LIST_EMPTY(&nts->nts_instances)) {
n = LIST_FIRST(&nts->nts_instances);
LIST_REMOVE(n, nini_next);
net_instance_int_free(n);
}
mutex_exit(&neti_stack_lock);
ASSERT(LIST_EMPTY(&nts->nts_netd_head));
mutex_destroy(&nts->nts_lock);
cv_destroy(&nts->nts_cv);
kmem_free(nts, sizeof (*nts));
}
static net_instance_int_t *
net_instance_int_create(net_instance_t *nin, net_instance_int_t *parent)
{
net_instance_int_t *nini;
nini = kmem_zalloc(sizeof (net_instance_int_t), KM_SLEEP);
nini->nini_instance = nin;
nini->nini_parent = parent;
if (parent != NULL) {
parent->nini_ref++;
nini->nini_flags |= NSS_CREATE_NEEDED;
}
cv_init(&nini->nini_cv, NULL, CV_DRIVER, NULL);
return (nini);
}
static void
net_instance_int_free(net_instance_int_t *nini)
{
ASSERT(mutex_owned(&neti_stack_lock));
if (nini->nini_parent != NULL) {
if (nini->nini_ref > 0)
nini->nini_condemned = B_TRUE;
nini->nini_parent->nini_ref--;
if (nini->nini_parent->nini_ref == 0)
net_instance_int_free(nini->nini_parent);
nini->nini_parent = NULL;
}
if (nini->nini_ref == 0) {
cv_destroy(&nini->nini_cv);
kmem_free(nini, sizeof (*nini));
}
}
net_instance_t *
net_instance_alloc(const int version)
{
net_instance_t *nin;
if (version != NETINFO_VERSION)
return (NULL);
nin = kmem_zalloc(sizeof (net_instance_t), KM_SLEEP);
nin->nin_version = version;
return (nin);
}
void
net_instance_free(net_instance_t *nin)
{
kmem_free(nin, sizeof (*nin));
}
int
net_instance_register(net_instance_t *nin)
{
net_instance_int_t *parent;
net_instance_int_t *tmp;
neti_stack_t *nts;
ASSERT(nin->nin_name != NULL);
if (nin->nin_create == NULL || nin->nin_destroy == NULL)
return (DDI_FAILURE);
mutex_enter(&neti_stack_lock);
LIST_FOREACH(tmp, &neti_instance_list, nini_next) {
if (strcmp(nin->nin_name, tmp->nini_instance->nin_name) == 0) {
mutex_exit(&neti_stack_lock);
return (DDI_FAILURE);
}
}
parent = net_instance_int_create(nin, NULL);
ASSERT(parent != NULL);
LIST_INSERT_HEAD(&neti_instance_list, parent, nini_next);
LIST_FOREACH(nts, &neti_stack_list, nts_next) {
mutex_enter(&nts->nts_lock);
if ((nts->nts_flags & NSF_ZONE_SHUTDOWN) ||
(nts->nts_netstack == NULL)) {
mutex_exit(&nts->nts_lock);
continue;
}
tmp = net_instance_int_create(nin, parent);
ASSERT(tmp != NULL);
LIST_INSERT_HEAD(&nts->nts_instances, tmp, nini_next);
mutex_exit(&nts->nts_lock);
}
neti_apply_all_stacks(parent, neti_stack_apply_create);
mutex_exit(&neti_stack_lock);
return (DDI_SUCCESS);
}
int
net_instance_unregister(net_instance_t *nin)
{
net_instance_int_t *parent;
net_instance_int_t *tmp;
neti_stack_t *nts;
mutex_enter(&neti_stack_lock);
LIST_FOREACH(tmp, &neti_instance_list, nini_next) {
if (strcmp(tmp->nini_instance->nin_name, nin->nin_name) == 0) {
LIST_REMOVE(tmp, nini_next);
break;
}
}
if (tmp == NULL) {
mutex_exit(&neti_stack_lock);
return (DDI_FAILURE);
}
parent = tmp;
LIST_FOREACH(nts, &neti_stack_list, nts_next) {
mutex_enter(&nts->nts_lock);
LIST_FOREACH(tmp, &nts->nts_instances, nini_next) {
if (tmp->nini_parent != parent)
continue;
if ((tmp->nini_flags & NSS_SHUTDOWN_ALL) == 0)
tmp->nini_flags |= NSS_SHUTDOWN_NEEDED;
if ((tmp->nini_flags & NSS_DESTROY_ALL) == 0)
tmp->nini_flags |= NSS_DESTROY_NEEDED;
break;
}
mutex_exit(&nts->nts_lock);
}
neti_apply_all_stacks(parent, neti_stack_apply_shutdown);
neti_apply_all_stacks(parent, neti_stack_apply_destroy);
LIST_FOREACH(nts, &neti_stack_list, nts_next) {
mutex_enter(&nts->nts_lock);
LIST_FOREACH(tmp, &nts->nts_instances, nini_next) {
if ((tmp->nini_parent == parent) &&
(tmp->nini_flags & NSS_SHUTDOWN_COMPLETED) &&
(tmp->nini_flags & NSS_DESTROY_COMPLETED)) {
LIST_REMOVE(tmp, nini_next);
net_instance_int_free(tmp);
break;
}
}
mutex_exit(&nts->nts_lock);
}
mutex_exit(&neti_stack_lock);
return (DDI_SUCCESS);
}
static void
neti_apply_all_instances(neti_stack_t *nts, napplyfn_t *applyfn)
{
net_instance_int_t *n;
ASSERT(mutex_owned(&neti_stack_lock));
n = LIST_FIRST(&nts->nts_instances);
while (n != NULL) {
if ((applyfn)(nts, n->nini_parent)) {
n = LIST_FIRST(&nts->nts_instances);
} else {
n = LIST_NEXT(n, nini_next);
}
}
}
static void
neti_apply_all_stacks(void *parent, napplyfn_t *applyfn)
{
neti_stack_t *nts;
ASSERT(mutex_owned(&neti_stack_lock));
nts = LIST_FIRST(&neti_stack_list);
while (nts != NULL) {
if ((applyfn)(nts, parent)) {
nts = LIST_FIRST(&neti_stack_list);
} else {
nts = LIST_NEXT(nts, nts_next);
}
}
}
static boolean_t
neti_stack_apply_create(neti_stack_t *nts, void *parent)
{
void *result;
boolean_t dropped = B_FALSE;
net_instance_int_t *tmp;
net_instance_t *nin;
ASSERT(parent != NULL);
ASSERT(mutex_owned(&neti_stack_lock));
mutex_enter(&nts->nts_lock);
LIST_FOREACH(tmp, &nts->nts_instances, nini_next) {
if (tmp->nini_parent == parent)
break;
}
if (tmp == NULL) {
mutex_exit(&nts->nts_lock);
return (dropped);
}
tmp->nini_ref++;
if (wait_for_nini_inprogress(nts, tmp, 0))
dropped = B_TRUE;
if ((tmp->nini_flags & NSS_CREATE_NEEDED) && !tmp->nini_condemned) {
nin = tmp->nini_instance;
tmp->nini_flags &= ~NSS_CREATE_NEEDED;
tmp->nini_flags |= NSS_CREATE_INPROGRESS;
DTRACE_PROBE2(neti__stack__create__inprogress,
neti_stack_t *, nts, net_instance_int_t *, tmp);
mutex_exit(&nts->nts_lock);
mutex_exit(&neti_stack_lock);
dropped = B_TRUE;
ASSERT(tmp->nini_created == NULL);
ASSERT(nin->nin_create != NULL);
DTRACE_PROBE2(neti__stack__create__start,
netstackid_t, nts->nts_id,
neti_stack_t *, nts);
result = (nin->nin_create)(nts->nts_id);
DTRACE_PROBE2(neti__stack__create__end,
void *, result, neti_stack_t *, nts);
ASSERT(result != NULL);
mutex_enter(&neti_stack_lock);
mutex_enter(&nts->nts_lock);
tmp->nini_created = result;
tmp->nini_flags &= ~NSS_CREATE_INPROGRESS;
tmp->nini_flags |= NSS_CREATE_COMPLETED;
cv_broadcast(&tmp->nini_cv);
DTRACE_PROBE2(neti__stack__create__completed,
neti_stack_t *, nts, net_instance_int_t *, tmp);
}
tmp->nini_ref--;
if (tmp->nini_condemned) {
net_instance_int_free(tmp);
dropped = B_TRUE;
}
mutex_exit(&nts->nts_lock);
return (dropped);
}
static boolean_t
neti_stack_apply_shutdown(neti_stack_t *nts, void *parent)
{
boolean_t dropped = B_FALSE;
net_instance_int_t *tmp;
net_instance_t *nin;
ASSERT(parent != NULL);
ASSERT(mutex_owned(&neti_stack_lock));
mutex_enter(&nts->nts_lock);
LIST_FOREACH(tmp, &nts->nts_instances, nini_next) {
if (tmp->nini_parent == parent)
break;
}
if (tmp == NULL) {
mutex_exit(&nts->nts_lock);
return (dropped);
}
ASSERT((tmp->nini_flags & NSS_SHUTDOWN_ALL) != 0);
tmp->nini_ref++;
if (wait_for_nini_inprogress(nts, tmp, NSS_CREATE_NEEDED))
dropped = B_TRUE;
nin = tmp->nini_instance;
if (nin->nin_shutdown == NULL) {
if (tmp->nini_flags & NSS_SHUTDOWN_NEEDED) {
tmp->nini_flags &= ~NSS_SHUTDOWN_NEEDED;
tmp->nini_flags |= NSS_SHUTDOWN_COMPLETED;
}
tmp->nini_ref--;
if (tmp->nini_condemned) {
net_instance_int_free(tmp);
dropped = B_TRUE;
}
mutex_exit(&nts->nts_lock);
return (dropped);
}
if ((tmp->nini_flags & NSS_SHUTDOWN_NEEDED) && !tmp->nini_condemned) {
ASSERT((tmp->nini_flags & NSS_CREATE_COMPLETED) != 0);
tmp->nini_flags &= ~NSS_SHUTDOWN_NEEDED;
tmp->nini_flags |= NSS_SHUTDOWN_INPROGRESS;
DTRACE_PROBE2(neti__stack__shutdown__inprogress,
neti_stack_t *, nts, net_instance_int_t *, tmp);
mutex_exit(&nts->nts_lock);
mutex_exit(&neti_stack_lock);
dropped = B_TRUE;
ASSERT(nin->nin_shutdown != NULL);
DTRACE_PROBE2(neti__stack__shutdown__start,
netstackid_t, nts->nts_id,
neti_stack_t *, nts);
(nin->nin_shutdown)(nts->nts_id, tmp->nini_created);
DTRACE_PROBE1(neti__stack__shutdown__end,
neti_stack_t *, nts);
mutex_enter(&neti_stack_lock);
mutex_enter(&nts->nts_lock);
tmp->nini_flags &= ~NSS_SHUTDOWN_INPROGRESS;
tmp->nini_flags |= NSS_SHUTDOWN_COMPLETED;
cv_broadcast(&tmp->nini_cv);
DTRACE_PROBE2(neti__stack__shutdown__completed,
neti_stack_t *, nts, net_instance_int_t *, tmp);
}
ASSERT((tmp->nini_flags & NSS_SHUTDOWN_COMPLETED) != 0);
tmp->nini_ref--;
if (tmp->nini_condemned) {
net_instance_int_free(tmp);
dropped = B_TRUE;
}
mutex_exit(&nts->nts_lock);
return (dropped);
}
static boolean_t
neti_stack_apply_destroy(neti_stack_t *nts, void *parent)
{
boolean_t dropped = B_FALSE;
net_instance_int_t *tmp;
net_instance_t *nin;
ASSERT(parent != NULL);
ASSERT(mutex_owned(&neti_stack_lock));
mutex_enter(&nts->nts_lock);
LIST_FOREACH(tmp, &nts->nts_instances, nini_next) {
if (tmp->nini_parent == parent)
break;
}
if (tmp == NULL) {
mutex_exit(&nts->nts_lock);
return (dropped);
}
tmp->nini_ref++;
if (wait_for_nini_inprogress(nts, tmp,
NSS_CREATE_NEEDED|NSS_SHUTDOWN_NEEDED))
dropped = B_TRUE;
if ((tmp->nini_flags & NSS_DESTROY_NEEDED) && !tmp->nini_condemned) {
ASSERT((tmp->nini_flags & NSS_SHUTDOWN_COMPLETED) != 0);
nin = tmp->nini_instance;
tmp->nini_flags &= ~NSS_DESTROY_NEEDED;
tmp->nini_flags |= NSS_DESTROY_INPROGRESS;
DTRACE_PROBE2(neti__stack__destroy__inprogress,
neti_stack_t *, nts, net_instance_int_t *, tmp);
mutex_exit(&nts->nts_lock);
mutex_exit(&neti_stack_lock);
dropped = B_TRUE;
ASSERT(nin->nin_destroy != NULL);
DTRACE_PROBE2(neti__stack__destroy__start,
netstackid_t, nts->nts_id,
neti_stack_t *, nts);
(nin->nin_destroy)(nts->nts_id, tmp->nini_created);
DTRACE_PROBE1(neti__stack__destroy__end,
neti_stack_t *, nts);
mutex_enter(&neti_stack_lock);
mutex_enter(&nts->nts_lock);
tmp->nini_flags &= ~NSS_DESTROY_INPROGRESS;
tmp->nini_flags |= NSS_DESTROY_COMPLETED;
cv_broadcast(&tmp->nini_cv);
DTRACE_PROBE2(neti__stack__destroy__completed,
neti_stack_t *, nts, net_instance_int_t *, tmp);
}
tmp->nini_ref--;
if (tmp->nini_condemned) {
net_instance_int_free(tmp);
dropped = B_TRUE;
}
mutex_exit(&nts->nts_lock);
return (dropped);
}
static boolean_t
wait_for_nini_inprogress(neti_stack_t *nts, net_instance_int_t *nini,
uint32_t cmask)
{
boolean_t dropped = B_FALSE;
ASSERT(mutex_owned(&neti_stack_lock));
while (nini->nini_flags & (NSS_ALL_INPROGRESS|cmask)) {
DTRACE_PROBE2(neti__wait__nini__inprogress,
neti_stack_t *, nts, net_instance_int_t *, nini);
dropped = B_TRUE;
mutex_exit(&neti_stack_lock);
cv_wait(&nini->nini_cv, &nts->nts_lock);
mutex_exit(&nts->nts_lock);
DTRACE_PROBE2(wait__nini__inprogress__pause,
neti_stack_t *, nts, net_instance_int_t *, nini);
mutex_enter(&neti_stack_lock);
mutex_enter(&nts->nts_lock);
}
DTRACE_PROBE2(neti__wait__nini__inprogress__complete,
neti_stack_t *, nts, net_instance_int_t *, nini);
return (dropped);
}
netid_t
net_zoneidtonetid(zoneid_t zoneid)
{
neti_stack_t *nts;
mutex_enter(&neti_stack_lock);
LIST_FOREACH(nts, &neti_stack_list, nts_next) {
if (nts->nts_zoneid == zoneid) {
mutex_exit(&neti_stack_lock);
return (nts->nts_id);
}
}
mutex_exit(&neti_stack_lock);
return (-1);
}
zoneid_t
net_getzoneidbynetid(netid_t netid)
{
neti_stack_t *nts;
mutex_enter(&neti_stack_lock);
LIST_FOREACH(nts, &neti_stack_list, nts_next) {
if (nts->nts_id == netid) {
mutex_exit(&neti_stack_lock);
return (nts->nts_zoneid);
}
}
mutex_exit(&neti_stack_lock);
return (-1);
}
netstackid_t
net_getnetstackidbynetid(netid_t netid)
{
neti_stack_t *nts;
mutex_enter(&neti_stack_lock);
LIST_FOREACH(nts, &neti_stack_list, nts_next) {
if (nts->nts_id == netid) {
mutex_exit(&neti_stack_lock);
return (nts->nts_stackid);
}
}
mutex_exit(&neti_stack_lock);
return (-1);
}
netid_t
net_getnetidbynetstackid(netstackid_t netstackid)
{
neti_stack_t *nts;
mutex_enter(&neti_stack_lock);
LIST_FOREACH(nts, &neti_stack_list, nts_next) {
if (nts->nts_stackid == netstackid) {
mutex_exit(&neti_stack_lock);
return (nts->nts_id);
}
}
mutex_exit(&neti_stack_lock);
return (-1);
}
neti_stack_t *
net_getnetistackbyid(netid_t netid)
{
neti_stack_t *nts;
mutex_enter(&neti_stack_lock);
LIST_FOREACH(nts, &neti_stack_list, nts_next) {
if (nts->nts_id == netid) {
mutex_exit(&neti_stack_lock);
return (nts);
}
}
mutex_exit(&neti_stack_lock);
return (NULL);
}
int
net_instance_notify_register(netid_t netid, hook_notify_fn_t callback,
void *arg)
{
return (hook_stack_notify_register(net_getnetstackidbynetid(netid),
callback, arg));
}
int
net_instance_notify_unregister(netid_t netid, hook_notify_fn_t callback)
{
return (hook_stack_notify_unregister(net_getnetstackidbynetid(netid),
callback));
}