#include <sys/types.h>
#include <sys/door.h>
#include <sys/zone.h>
#include <sys/modctl.h>
#include <sys/file.h>
#include <sys/modhash.h>
#include <sys/kstat.h>
#include <sys/vnode.h>
#include <sys/cmn_err.h>
#include <sys/softmac.h>
#include <sys/dls.h>
#include <sys/dls_impl.h>
#include <sys/stropts.h>
#include <sys/netstack.h>
#include <inet/iptun/iptun_impl.h>
typedef struct dls_stack {
zoneid_t dlss_zoneid;
} dls_stack_t;
static kmem_cache_t *i_dls_devnet_cachep;
static kmutex_t i_dls_mgmt_lock;
static door_handle_t dls_mgmt_dh = NULL;
static kmutex_t i_dls_devnet_lock;
static kcondvar_t i_dls_devnet_cv;
static kthread_t *i_dls_devnet_own;
static krwlock_t i_dls_devnet_hash_lock;
static mod_hash_t *i_dls_devnet_id_hash;
static mod_hash_t *i_dls_devnet_hash;
static void
i_dls_devnet_lock_enter(void)
{
mutex_enter(&i_dls_devnet_lock);
while (i_dls_devnet_own != NULL) {
cv_wait(&i_dls_devnet_cv, &i_dls_devnet_lock);
}
}
static void
i_dls_devnet_lock_exit(void)
{
VERIFY3P(i_dls_devnet_own, ==, NULL);
cv_broadcast(&i_dls_devnet_cv);
mutex_exit(&i_dls_devnet_lock);
}
static void
i_dls_devnet_lock_upcall_start(void)
{
VERIFY(MUTEX_HELD(&i_dls_devnet_lock));
VERIFY3P(i_dls_devnet_own, ==, NULL);
i_dls_devnet_own = curthread;
mutex_exit(&i_dls_devnet_lock);
}
static void
i_dls_devnet_lock_upcall_end(void)
{
mutex_enter(&i_dls_devnet_lock);
VERIFY3P(i_dls_devnet_own, ==, curthread);
i_dls_devnet_own = NULL;
}
static void
i_dls_devnet_hashmap_write(void)
{
VERIFY(MUTEX_HELD(&i_dls_devnet_lock));
rw_enter(&i_dls_devnet_hash_lock, RW_WRITER);
}
static void
i_dls_devnet_hashmap_read(void)
{
rw_enter(&i_dls_devnet_hash_lock, RW_READER);
}
static void
i_dls_devnet_hashmap_exit(void)
{
rw_exit(&i_dls_devnet_hash_lock);
}
boolean_t devnet_need_rebuild;
#define VLAN_HASHSZ 67
#define IS_IPV4_TUN(name) (strcmp((name), "ip.tun") == 0)
#define IS_IPV6_TUN(name) (strcmp((name), "ip6.tun") == 0)
#define IS_6TO4_TUN(name) (strcmp((name), "ip.6to4tun") == 0)
#define IS_IPTUN_LINK(name) ( \
IS_IPV4_TUN(name) || IS_IPV6_TUN(name) || IS_6TO4_TUN(name))
#define DD_CONDEMNED 0x1
#define DD_IMPLICIT_IPTUN 0x2
#define DD_INITIALIZING 0x4
#define DD_NOT_VISIBLE(flags) ( \
(flags & (DD_CONDEMNED | DD_INITIALIZING)) != 0)
typedef struct dls_devnet_s {
datalink_id_t dd_linkid;
char dd_linkname[MAXLINKNAMELEN];
char dd_mac[MAXNAMELEN];
kstat_t *dd_ksp;
kstat_t *dd_zone_ksp;
uint32_t dd_ref;
kmutex_t dd_mutex;
kcondvar_t dd_cv;
uint32_t dd_tref;
uint_t dd_flags;
zoneid_t dd_owner_zid;
zoneid_t dd_zid;
boolean_t dd_prop_loaded;
taskqid_t dd_prop_taskid;
boolean_t dd_transient;
} dls_devnet_t;
static int i_dls_devnet_create_iptun(const char *, const char *,
datalink_id_t *);
static int i_dls_devnet_destroy_iptun(datalink_id_t);
static int i_dls_devnet_setzid(dls_devnet_t *, zoneid_t, boolean_t, boolean_t);
static int dls_devnet_unset(mac_handle_t, datalink_id_t *, boolean_t);
static int
i_dls_devnet_constructor(void *buf, void *arg, int kmflag)
{
dls_devnet_t *ddp = buf;
bzero(buf, sizeof (dls_devnet_t));
mutex_init(&ddp->dd_mutex, NULL, MUTEX_DEFAULT, NULL);
cv_init(&ddp->dd_cv, NULL, CV_DEFAULT, NULL);
return (0);
}
static void
i_dls_devnet_destructor(void *buf, void *arg)
{
dls_devnet_t *ddp = buf;
VERIFY(ddp->dd_ksp == NULL);
VERIFY(ddp->dd_ref == 0);
VERIFY(ddp->dd_tref == 0);
mutex_destroy(&ddp->dd_mutex);
cv_destroy(&ddp->dd_cv);
}
static int
dls_zone_remove(datalink_id_t linkid, void *arg)
{
dls_devnet_t *ddp;
if (dls_devnet_hold_tmp(linkid, &ddp) == 0) {
if (!ddp->dd_transient)
(void) dls_devnet_setzid(ddp, GLOBAL_ZONEID);
dls_devnet_rele_tmp(ddp);
}
return (0);
}
static void *
dls_stack_init(netstackid_t stackid, netstack_t *ns)
{
dls_stack_t *dlss;
dlss = kmem_zalloc(sizeof (*dlss), KM_SLEEP);
dlss->dlss_zoneid = netstackid_to_zoneid(stackid);
return (dlss);
}
static void
dls_stack_shutdown(netstackid_t stackid, void *arg)
{
dls_stack_t *dlss = (dls_stack_t *)arg;
(void) zone_datalink_walk(dlss->dlss_zoneid, dls_zone_remove, NULL);
}
static void
dls_stack_fini(netstackid_t stackid, void *arg)
{
dls_stack_t *dlss = (dls_stack_t *)arg;
kmem_free(dlss, sizeof (*dlss));
}
void
dls_mgmt_init(void)
{
mutex_init(&i_dls_mgmt_lock, NULL, MUTEX_DEFAULT, NULL);
mutex_init(&i_dls_devnet_lock, NULL, MUTEX_DEFAULT, NULL);
cv_init(&i_dls_devnet_cv, NULL, CV_DEFAULT, NULL);
i_dls_devnet_own = NULL;
rw_init(&i_dls_devnet_hash_lock, NULL, RW_DEFAULT, NULL);
i_dls_devnet_cachep = kmem_cache_create("dls_devnet_cache",
sizeof (dls_devnet_t), 0, i_dls_devnet_constructor,
i_dls_devnet_destructor, NULL, NULL, NULL, 0);
ASSERT(i_dls_devnet_cachep != NULL);
i_dls_devnet_id_hash = mod_hash_create_idhash("dls_devnet_id_hash",
VLAN_HASHSZ, mod_hash_null_valdtor);
i_dls_devnet_hash = mod_hash_create_extended("dls_devnet_hash",
VLAN_HASHSZ, mod_hash_null_keydtor, mod_hash_null_valdtor,
mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP);
devnet_need_rebuild = B_FALSE;
netstack_register(NS_DLS, dls_stack_init, dls_stack_shutdown,
dls_stack_fini);
}
void
dls_mgmt_fini(void)
{
netstack_unregister(NS_DLS);
mod_hash_destroy_hash(i_dls_devnet_hash);
mod_hash_destroy_hash(i_dls_devnet_id_hash);
kmem_cache_destroy(i_dls_devnet_cachep);
rw_destroy(&i_dls_devnet_hash_lock);
cv_destroy(&i_dls_devnet_cv);
mutex_destroy(&i_dls_devnet_lock);
mutex_destroy(&i_dls_mgmt_lock);
}
int
dls_mgmt_door_set(boolean_t start)
{
int err;
mutex_enter(&i_dls_mgmt_lock);
if (dls_mgmt_dh != NULL) {
door_ki_rele(dls_mgmt_dh);
dls_mgmt_dh = NULL;
}
if (start && ((err = door_ki_open(DLMGMT_DOOR, &dls_mgmt_dh)) != 0)) {
mutex_exit(&i_dls_mgmt_lock);
return (err);
}
mutex_exit(&i_dls_mgmt_lock);
if (start)
softmac_recreate();
return (0);
}
static boolean_t
i_dls_mgmt_door_revoked(door_handle_t dh)
{
struct door_info info;
extern int sys_shutdown;
ASSERT(dh != NULL);
if (sys_shutdown) {
cmn_err(CE_NOTE, "dls_mgmt_door: shutdown observed\n");
return (B_TRUE);
}
if (door_ki_info(dh, &info) != 0)
return (B_TRUE);
return ((info.di_attributes & DOOR_REVOKED) != 0);
}
static int
i_dls_mgmt_upcall(void *arg, size_t asize, void *rbuf, size_t rsize)
{
door_arg_t darg, save_arg;
door_handle_t dh;
int err;
int retry = 0;
#define MAXRETRYNUM 3
ASSERT(arg);
darg.data_ptr = arg;
darg.data_size = asize;
darg.desc_ptr = NULL;
darg.desc_num = 0;
darg.rbuf = rbuf;
darg.rsize = rsize;
save_arg = darg;
retry:
mutex_enter(&i_dls_mgmt_lock);
dh = dls_mgmt_dh;
if ((dh == NULL) || i_dls_mgmt_door_revoked(dh)) {
mutex_exit(&i_dls_mgmt_lock);
return (EBADF);
}
door_ki_hold(dh);
mutex_exit(&i_dls_mgmt_lock);
for (;;) {
retry++;
if ((err = door_ki_upcall_limited(dh, &darg, zone_kcred(),
SIZE_MAX, 0)) == 0)
break;
darg = save_arg;
switch (err) {
case EINTR:
goto done;
case EAGAIN:
if (retry < MAXRETRYNUM) {
delay(2 * hz);
break;
}
cmn_err(CE_WARN, "dls: dlmgmtd fatal error %d\n", err);
goto done;
default:
if (i_dls_mgmt_door_revoked(dh)) {
cmn_err(CE_NOTE,
"dls: dlmgmtd door service revoked\n");
if (retry < MAXRETRYNUM) {
door_ki_rele(dh);
goto retry;
}
}
cmn_err(CE_WARN, "dls: dlmgmtd fatal error %d\n", err);
goto done;
}
}
if (darg.rbuf != rbuf) {
kmem_free(darg.rbuf, darg.rsize);
err = ENOSPC;
goto done;
}
if (darg.rsize != rsize) {
err = EINVAL;
goto done;
}
err = ((dlmgmt_retval_t *)rbuf)->lr_err;
done:
door_ki_rele(dh);
return (err);
}
int
dls_mgmt_create(const char *devname, dev_t dev, datalink_class_t class,
uint32_t media, boolean_t persist, datalink_id_t *linkidp)
{
dlmgmt_upcall_arg_create_t create;
dlmgmt_create_retval_t retval;
int err;
create.ld_cmd = DLMGMT_CMD_DLS_CREATE;
create.ld_class = class;
create.ld_media = media;
create.ld_phymaj = getmajor(dev);
create.ld_phyinst = getminor(dev);
create.ld_persist = persist;
if (strlcpy(create.ld_devname, devname, sizeof (create.ld_devname)) >=
sizeof (create.ld_devname))
return (EINVAL);
if ((err = i_dls_mgmt_upcall(&create, sizeof (create), &retval,
sizeof (retval))) == 0) {
*linkidp = retval.lr_linkid;
}
return (err);
}
int
dls_mgmt_destroy(datalink_id_t linkid, boolean_t persist)
{
dlmgmt_upcall_arg_destroy_t destroy;
dlmgmt_destroy_retval_t retval;
destroy.ld_cmd = DLMGMT_CMD_DLS_DESTROY;
destroy.ld_linkid = linkid;
destroy.ld_persist = persist;
return (i_dls_mgmt_upcall(&destroy, sizeof (destroy),
&retval, sizeof (retval)));
}
int
dls_mgmt_update(const char *devname, uint32_t media, boolean_t novanity,
uint32_t *mediap, datalink_id_t *linkidp)
{
dlmgmt_upcall_arg_update_t update;
dlmgmt_update_retval_t retval;
int err;
update.ld_cmd = DLMGMT_CMD_DLS_UPDATE;
if (strlcpy(update.ld_devname, devname, sizeof (update.ld_devname)) >=
sizeof (update.ld_devname))
return (EINVAL);
update.ld_media = media;
update.ld_novanity = novanity;
if ((err = i_dls_mgmt_upcall(&update, sizeof (update), &retval,
sizeof (retval))) == EEXIST) {
*linkidp = retval.lr_linkid;
*mediap = retval.lr_media;
} else if (err == 0) {
*linkidp = retval.lr_linkid;
}
return (err);
}
int
dls_mgmt_get_linkinfo(datalink_id_t linkid, char *link,
datalink_class_t *classp, uint32_t *mediap, uint32_t *flagsp)
{
dlmgmt_door_getname_t getname;
dlmgmt_getname_retval_t retval;
int err, len;
getname.ld_cmd = DLMGMT_CMD_GETNAME;
getname.ld_linkid = linkid;
if ((err = i_dls_mgmt_upcall(&getname, sizeof (getname), &retval,
sizeof (retval))) != 0) {
return (err);
}
len = strlen(retval.lr_link);
if (len <= 1 || len >= MAXLINKNAMELEN)
return (EINVAL);
if (link != NULL)
(void) strlcpy(link, retval.lr_link, MAXLINKNAMELEN);
if (classp != NULL)
*classp = retval.lr_class;
if (mediap != NULL)
*mediap = retval.lr_media;
if (flagsp != NULL)
*flagsp = retval.lr_flags;
return (0);
}
int
dls_mgmt_get_linkid(const char *link, datalink_id_t *linkid)
{
dlmgmt_door_getlinkid_t getlinkid;
dlmgmt_getlinkid_retval_t retval;
int err;
getlinkid.ld_cmd = DLMGMT_CMD_GETLINKID;
(void) strlcpy(getlinkid.ld_link, link, MAXLINKNAMELEN);
if ((err = i_dls_mgmt_upcall(&getlinkid, sizeof (getlinkid), &retval,
sizeof (retval))) == 0) {
*linkid = retval.lr_linkid;
}
return (err);
}
datalink_id_t
dls_mgmt_get_next(datalink_id_t linkid, datalink_class_t class,
datalink_media_t dmedia, uint32_t flags)
{
dlmgmt_door_getnext_t getnext;
dlmgmt_getnext_retval_t retval;
getnext.ld_cmd = DLMGMT_CMD_GETNEXT;
getnext.ld_class = class;
getnext.ld_dmedia = dmedia;
getnext.ld_flags = flags;
getnext.ld_linkid = linkid;
if (i_dls_mgmt_upcall(&getnext, sizeof (getnext), &retval,
sizeof (retval)) != 0) {
return (DATALINK_INVALID_LINKID);
}
return (retval.lr_linkid);
}
static int
i_dls_mgmt_get_linkattr(const datalink_id_t linkid, const char *attr,
void *attrval, size_t *attrszp)
{
dlmgmt_upcall_arg_getattr_t getattr;
dlmgmt_getattr_retval_t retval;
int err;
getattr.ld_cmd = DLMGMT_CMD_DLS_GETATTR;
getattr.ld_linkid = linkid;
(void) strlcpy(getattr.ld_attr, attr, MAXLINKATTRLEN);
if ((err = i_dls_mgmt_upcall(&getattr, sizeof (getattr), &retval,
sizeof (retval))) == 0) {
if (*attrszp < retval.lr_attrsz)
return (EINVAL);
*attrszp = retval.lr_attrsz;
bcopy(retval.lr_attrval, attrval, retval.lr_attrsz);
}
return (err);
}
int
dls_mgmt_get_phydev(datalink_id_t linkid, dev_t *devp)
{
uint64_t maj, inst;
size_t attrsz = sizeof (uint64_t);
if (i_dls_mgmt_get_linkattr(linkid, FPHYMAJ, &maj, &attrsz) != 0 ||
attrsz != sizeof (uint64_t) ||
i_dls_mgmt_get_linkattr(linkid, FPHYINST, &inst, &attrsz) != 0 ||
attrsz != sizeof (uint64_t)) {
return (EINVAL);
}
*devp = makedevice((major_t)maj, (minor_t)inst);
return (0);
}
int
dls_mgmt_linkprop_init(datalink_id_t linkid)
{
dlmgmt_door_linkprop_init_t li;
dlmgmt_linkprop_init_retval_t retval;
int err;
li.ld_cmd = DLMGMT_CMD_LINKPROP_INIT;
li.ld_linkid = linkid;
err = i_dls_mgmt_upcall(&li, sizeof (li), &retval, sizeof (retval));
return (err);
}
static void
dls_devnet_prop_task(void *arg)
{
dls_devnet_t *ddp = arg;
(void) dls_mgmt_linkprop_init(ddp->dd_linkid);
mutex_enter(&ddp->dd_mutex);
ddp->dd_prop_loaded = B_TRUE;
ddp->dd_prop_taskid = 0;
cv_broadcast(&ddp->dd_cv);
mutex_exit(&ddp->dd_mutex);
}
void
dls_devnet_prop_task_wait(dls_dl_handle_t ddp)
{
mutex_enter(&ddp->dd_mutex);
while (ddp->dd_prop_taskid != 0)
cv_wait(&ddp->dd_cv, &ddp->dd_mutex);
mutex_exit(&ddp->dd_mutex);
}
void
dls_devnet_rele_tmp(dls_dl_handle_t dlh)
{
dls_devnet_t *ddp = dlh;
mutex_enter(&ddp->dd_mutex);
ASSERT(ddp->dd_tref != 0);
if (--ddp->dd_tref == 0)
cv_signal(&ddp->dd_cv);
mutex_exit(&ddp->dd_mutex);
}
int
dls_devnet_hold_link(datalink_id_t linkid, dls_dl_handle_t *ddhp,
dls_link_t **dlpp)
{
dls_dl_handle_t dlh;
dls_link_t *dlp;
int err;
if ((err = dls_devnet_hold_tmp(linkid, &dlh)) != 0)
return (err);
if ((err = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0) {
dls_devnet_rele_tmp(dlh);
return (err);
}
ASSERT(MAC_PERIM_HELD(dlp->dl_mh));
*ddhp = dlh;
*dlpp = dlp;
return (0);
}
void
dls_devnet_rele_link(dls_dl_handle_t dlh, dls_link_t *dlp)
{
ASSERT(MAC_PERIM_HELD(dlp->dl_mh));
dls_link_rele(dlp);
dls_devnet_rele_tmp(dlh);
}
static int
dls_devnet_stat_update(kstat_t *ksp, int rw)
{
datalink_id_t linkid = (datalink_id_t)(uintptr_t)ksp->ks_private;
dls_devnet_t *ddp;
dls_link_t *dlp;
int err;
if ((err = dls_devnet_hold_tmp(linkid, &ddp)) != 0) {
return (err);
}
if (mod_hash_find(i_dls_link_hash, (mod_hash_key_t)ddp->dd_mac,
(mod_hash_val_t *)&dlp) != 0) {
dls_devnet_rele_tmp(ddp);
return (ENOENT);
}
err = dls_stat_update(ksp, dlp, rw);
dls_devnet_rele_tmp(ddp);
return (err);
}
static void
dls_devnet_stat_create(dls_devnet_t *ddp, zoneid_t zoneid)
{
kstat_t *ksp;
if (dls_stat_create("link", 0, ddp->dd_linkname, zoneid,
dls_devnet_stat_update, (void *)(uintptr_t)ddp->dd_linkid,
&ksp) == 0) {
ASSERT(ksp != NULL);
if (zoneid == ddp->dd_owner_zid) {
ASSERT(ddp->dd_ksp == NULL);
ddp->dd_ksp = ksp;
} else {
ASSERT(ddp->dd_zone_ksp == NULL);
ddp->dd_zone_ksp = ksp;
}
}
}
static void
dls_devnet_stat_destroy(dls_devnet_t *ddp, zoneid_t zoneid)
{
if (zoneid == ddp->dd_owner_zid) {
if (ddp->dd_ksp != NULL) {
kstat_delete(ddp->dd_ksp);
ddp->dd_ksp = NULL;
}
} else {
if (ddp->dd_zone_ksp != NULL) {
kstat_delete(ddp->dd_zone_ksp);
ddp->dd_zone_ksp = NULL;
}
}
}
static void
dls_devnet_stat_rename(dls_devnet_t *ddp)
{
if (ddp->dd_ksp != NULL) {
kstat_delete(ddp->dd_ksp);
ddp->dd_ksp = NULL;
}
ASSERT(ddp->dd_zone_ksp == NULL);
dls_devnet_stat_create(ddp, ddp->dd_owner_zid);
}
static int
dls_devnet_set(mac_handle_t mh, datalink_id_t linkid, zoneid_t zoneid,
dls_devnet_t **ddpp)
{
const char *macname = mac_name(mh);
dls_devnet_t *ddp = NULL;
datalink_class_t class;
int err;
boolean_t stat_create = B_FALSE;
char linkname[MAXLINKNAMELEN];
i_dls_devnet_lock_enter();
if (linkid != DATALINK_INVALID_LINKID) {
i_dls_devnet_hashmap_read();
if (mod_hash_find(i_dls_devnet_id_hash,
(mod_hash_key_t)(uintptr_t)linkid,
(mod_hash_val_t *)&ddp) == 0) {
err = EEXIST;
goto done;
}
i_dls_devnet_hashmap_exit();
i_dls_devnet_lock_upcall_start();
err = dls_mgmt_get_linkinfo(linkid, linkname, &class,
NULL, NULL);
i_dls_devnet_lock_upcall_end();
if (err != 0)
goto done_rw_unlocked;
}
i_dls_devnet_hashmap_write();
if ((err = mod_hash_find(i_dls_devnet_hash,
(mod_hash_key_t)macname, (mod_hash_val_t *)&ddp)) == 0) {
if (ddp->dd_linkid != DATALINK_INVALID_LINKID) {
err = EEXIST;
goto done;
}
if (linkid == DATALINK_INVALID_LINKID ||
class != DATALINK_CLASS_PHYS) {
err = EINVAL;
goto done;
}
ASSERT(ddp->dd_flags & DD_INITIALIZING);
} else {
ddp = kmem_cache_alloc(i_dls_devnet_cachep, KM_SLEEP);
ddp->dd_flags = DD_INITIALIZING;
ddp->dd_tref = 0;
ddp->dd_ref++;
ddp->dd_owner_zid = zoneid;
if (zoneid != GLOBAL_ZONEID)
ddp->dd_transient = B_TRUE;
(void) strlcpy(ddp->dd_mac, macname, sizeof (ddp->dd_mac));
VERIFY(mod_hash_insert(i_dls_devnet_hash,
(mod_hash_key_t)ddp->dd_mac, (mod_hash_val_t)ddp) == 0);
}
if (linkid != DATALINK_INVALID_LINKID) {
ddp->dd_linkid = linkid;
(void) strlcpy(ddp->dd_linkname, linkname,
sizeof (ddp->dd_linkname));
VERIFY(mod_hash_insert(i_dls_devnet_id_hash,
(mod_hash_key_t)(uintptr_t)linkid,
(mod_hash_val_t)ddp) == 0);
devnet_need_rebuild = B_TRUE;
stat_create = B_TRUE;
}
err = 0;
done:
i_dls_devnet_hashmap_exit();
done_rw_unlocked:
i_dls_devnet_lock_exit();
if (err == 0 && zoneid != GLOBAL_ZONEID) {
err = i_dls_devnet_setzid(ddp, zoneid, B_FALSE, B_TRUE);
if (err != 0) {
ASSERT(ddp->dd_flags & DD_INITIALIZING);
(void) dls_devnet_unset(mh, &linkid, B_FALSE);
return (err);
}
}
if (err == 0) {
if (stat_create)
dls_devnet_stat_create(ddp, zoneid);
if (ddpp != NULL)
*ddpp = ddp;
mutex_enter(&ddp->dd_mutex);
if (linkid != DATALINK_INVALID_LINKID &&
!ddp->dd_prop_loaded && ddp->dd_prop_taskid == 0) {
ddp->dd_prop_taskid = taskq_dispatch(system_taskq,
dls_devnet_prop_task, ddp, TQ_SLEEP);
}
mutex_exit(&ddp->dd_mutex);
}
return (err);
}
static int
dls_devnet_unset(mac_handle_t mh, datalink_id_t *id, boolean_t wait)
{
const char *macname = mac_name(mh);
dls_devnet_t *ddp;
int err;
mod_hash_val_t val;
i_dls_devnet_lock_enter();
i_dls_devnet_hashmap_write();
if ((err = mod_hash_find(i_dls_devnet_hash,
(mod_hash_key_t)macname, (mod_hash_val_t *)&ddp)) != 0) {
ASSERT(err == MH_ERR_NOTFOUND);
i_dls_devnet_hashmap_exit();
return (ENOENT);
}
mutex_enter(&ddp->dd_mutex);
VERIFY(ddp->dd_ref != 0);
if ((ddp->dd_ref != 1) || (!wait &&
(ddp->dd_tref != 0 || ddp->dd_prop_taskid != 0))) {
int zstatus = 0;
if (ddp->dd_ref > 1 && ddp->dd_zid != GLOBAL_ZONEID) {
zone_t *zp;
if ((zp = zone_find_by_id(ddp->dd_zid)) == NULL) {
zstatus = -1;
} else {
if (ddp->dd_transient) {
zone_status_t s = zone_status_get(zp);
if (s >= ZONE_IS_SHUTTING_DOWN)
zstatus = 1;
}
zone_rele(zp);
}
}
if (zstatus == 0) {
mutex_exit(&ddp->dd_mutex);
i_dls_devnet_hashmap_exit();
i_dls_devnet_lock_exit();
return (EBUSY);
}
if (zstatus == -1) {
cmn_err(CE_WARN, "clear orphaned datalink: %s\n",
ddp->dd_linkname);
}
ddp->dd_ref = 1;
}
ddp->dd_flags |= DD_CONDEMNED;
ddp->dd_ref--;
*id = ddp->dd_linkid;
VERIFY(mod_hash_remove(i_dls_devnet_hash,
(mod_hash_key_t)ddp->dd_mac, &val) == 0);
if (ddp->dd_linkid != DATALINK_INVALID_LINKID) {
VERIFY(mod_hash_remove(i_dls_devnet_id_hash,
(mod_hash_key_t)(uintptr_t)ddp->dd_linkid, &val) == 0);
devnet_need_rebuild = B_TRUE;
}
i_dls_devnet_hashmap_exit();
i_dls_devnet_lock_exit();
if (ddp->dd_zid != GLOBAL_ZONEID) {
mutex_exit(&ddp->dd_mutex);
dls_devnet_stat_destroy(ddp, ddp->dd_zid);
mutex_enter(&ddp->dd_mutex);
(void) i_dls_devnet_setzid(ddp, GLOBAL_ZONEID, B_FALSE,
B_FALSE);
}
if (wait) {
ASSERT0(MAC_PERIM_HELD(mh));
while ((ddp->dd_tref != 0) || (ddp->dd_prop_taskid != 0))
cv_wait(&ddp->dd_cv, &ddp->dd_mutex);
} else {
VERIFY(ddp->dd_tref == 0);
VERIFY(ddp->dd_prop_taskid == 0);
}
if (ddp->dd_linkid != DATALINK_INVALID_LINKID)
dls_devnet_stat_destroy(ddp, ddp->dd_owner_zid);
ddp->dd_prop_loaded = B_FALSE;
ddp->dd_linkid = DATALINK_INVALID_LINKID;
ddp->dd_flags = 0;
mutex_exit(&ddp->dd_mutex);
kmem_cache_free(i_dls_devnet_cachep, ddp);
return (0);
}
int
dls_devnet_hold_tmp_by_link(dls_link_t *dlp, dls_dl_handle_t *ddhp)
{
int err;
dls_devnet_t *ddp = NULL;
i_dls_devnet_hashmap_read();
if ((err = mod_hash_find(i_dls_devnet_hash,
(mod_hash_key_t)dlp->dl_name, (mod_hash_val_t *)&ddp)) != 0) {
ASSERT(err == MH_ERR_NOTFOUND);
i_dls_devnet_hashmap_exit();
return (ENOENT);
}
mutex_enter(&ddp->dd_mutex);
VERIFY(ddp->dd_ref > 0);
if (DD_NOT_VISIBLE(ddp->dd_flags)) {
mutex_exit(&ddp->dd_mutex);
i_dls_devnet_hashmap_exit();
return (ENOENT);
}
ddp->dd_tref++;
mutex_exit(&ddp->dd_mutex);
i_dls_devnet_hashmap_exit();
*ddhp = ddp;
return (0);
}
static int
dls_devnet_hold_common(datalink_id_t linkid, dls_devnet_t **ddpp,
boolean_t tmp_hold)
{
dls_devnet_t *ddp;
int err;
i_dls_devnet_hashmap_read();
if ((err = mod_hash_find(i_dls_devnet_id_hash,
(mod_hash_key_t)(uintptr_t)linkid, (mod_hash_val_t *)&ddp)) != 0) {
ASSERT(err == MH_ERR_NOTFOUND);
i_dls_devnet_hashmap_exit();
return (ENOENT);
}
mutex_enter(&ddp->dd_mutex);
VERIFY(ddp->dd_ref > 0);
if (DD_NOT_VISIBLE(ddp->dd_flags)) {
mutex_exit(&ddp->dd_mutex);
i_dls_devnet_hashmap_exit();
return (ENOENT);
}
if (tmp_hold)
ddp->dd_tref++;
else
ddp->dd_ref++;
mutex_exit(&ddp->dd_mutex);
i_dls_devnet_hashmap_exit();
*ddpp = ddp;
return (0);
}
int
dls_devnet_hold(datalink_id_t linkid, dls_devnet_t **ddpp)
{
return (dls_devnet_hold_common(linkid, ddpp, B_FALSE));
}
int
dls_devnet_hold_tmp(datalink_id_t linkid, dls_devnet_t **ddpp)
{
return (dls_devnet_hold_common(linkid, ddpp, B_TRUE));
}
int
dls_devnet_hold_by_dev(dev_t dev, dls_dl_handle_t *ddhp)
{
char name[MAXNAMELEN];
char *drv;
dls_devnet_t *ddp;
int err;
if ((drv = ddi_major_to_name(getmajor(dev))) == NULL)
return (EINVAL);
(void) snprintf(name, sizeof (name), "%s%d", drv,
DLS_MINOR2INST(getminor(dev)));
i_dls_devnet_hashmap_read();
if ((err = mod_hash_find(i_dls_devnet_hash,
(mod_hash_key_t)name, (mod_hash_val_t *)&ddp)) != 0) {
ASSERT(err == MH_ERR_NOTFOUND);
i_dls_devnet_hashmap_exit();
return (ENOENT);
}
mutex_enter(&ddp->dd_mutex);
VERIFY(ddp->dd_ref > 0);
if (DD_NOT_VISIBLE(ddp->dd_flags)) {
mutex_exit(&ddp->dd_mutex);
i_dls_devnet_hashmap_exit();
return (ENOENT);
}
ddp->dd_ref++;
mutex_exit(&ddp->dd_mutex);
i_dls_devnet_hashmap_exit();
*ddhp = ddp;
return (0);
}
void
dls_devnet_rele(dls_devnet_t *ddp)
{
mutex_enter(&ddp->dd_mutex);
VERIFY(ddp->dd_ref > 1);
ddp->dd_ref--;
if ((ddp->dd_flags & DD_IMPLICIT_IPTUN) && ddp->dd_ref == 1) {
mutex_exit(&ddp->dd_mutex);
if (i_dls_devnet_destroy_iptun(ddp->dd_linkid) != 0)
ddp->dd_flags |= DD_IMPLICIT_IPTUN;
return;
}
mutex_exit(&ddp->dd_mutex);
}
static int
dls_devnet_hold_by_name(const char *link, dls_devnet_t **ddpp)
{
char drv[MAXLINKNAMELEN];
uint_t ppa;
major_t major;
dev_t phy_dev, tmp_dev;
datalink_id_t linkid;
dls_dev_handle_t ddh;
int err;
if ((err = dls_mgmt_get_linkid(link, &linkid)) == 0)
return (dls_devnet_hold(linkid, ddpp));
if (err == EBADF)
return (ENOENT);
if (err != ENOENT)
return (err);
if (ddi_parse_dlen(link, drv, MAXLINKNAMELEN, &ppa) != DDI_SUCCESS)
return (ENOENT);
if (IS_IPTUN_LINK(drv)) {
if ((err = i_dls_devnet_create_iptun(link, drv, &linkid)) != 0)
return (err);
err = dls_devnet_hold(linkid, ddpp);
if (err != 0) {
VERIFY(i_dls_devnet_destroy_iptun(linkid) == 0);
return (err);
}
(*ddpp)->dd_flags |= DD_IMPLICIT_IPTUN;
return (0);
}
if ((major = ddi_name_to_major(drv)) == (major_t)-1)
return (ENOENT);
phy_dev = makedevice(major, DLS_PPA2MINOR(ppa));
if (softmac_hold_device(phy_dev, &ddh) != 0)
return (ENOENT);
if ((err = dls_mgmt_get_linkid(link, &linkid)) != 0 ||
(err = dls_mgmt_get_phydev(linkid, &tmp_dev)) != 0) {
softmac_rele_device(ddh);
return (err);
}
if (tmp_dev != phy_dev) {
softmac_rele_device(ddh);
return (ENOENT);
}
err = dls_devnet_hold(linkid, ddpp);
softmac_rele_device(ddh);
return (err);
}
int
dls_devnet_macname2linkid(const char *macname, datalink_id_t *linkidp)
{
dls_devnet_t *ddp;
i_dls_devnet_hashmap_read();
if (mod_hash_find(i_dls_devnet_hash, (mod_hash_key_t)macname,
(mod_hash_val_t *)&ddp) != 0) {
i_dls_devnet_hashmap_exit();
return (ENOENT);
}
*linkidp = ddp->dd_linkid;
i_dls_devnet_hashmap_exit();
return (0);
}
int
dls_devnet_dev2linkid(dev_t dev, datalink_id_t *linkidp)
{
char macname[MAXNAMELEN];
char *drv;
if ((drv = ddi_major_to_name(getmajor(dev))) == NULL)
return (EINVAL);
(void) snprintf(macname, sizeof (macname), "%s%d", drv,
DLS_MINOR2INST(getminor(dev)));
return (dls_devnet_macname2linkid(macname, linkidp));
}
int
dls_devnet_phydev(datalink_id_t vlanid, dev_t *devp)
{
dls_devnet_t *ddp;
int err;
if ((err = dls_devnet_hold_tmp(vlanid, &ddp)) != 0)
return (err);
err = dls_mgmt_get_phydev(ddp->dd_linkid, devp);
dls_devnet_rele_tmp(ddp);
return (err);
}
int
dls_devnet_rename(datalink_id_t id1, datalink_id_t id2, const char *link)
{
dls_dev_handle_t ddh = NULL;
int err = 0;
dev_t phydev = 0;
dls_devnet_t *ddp;
mac_perim_handle_t mph = NULL;
mac_handle_t mh;
mod_hash_val_t val;
if ((id2 != DATALINK_INVALID_LINKID) &&
(dls_mgmt_get_phydev(id2, &phydev) == 0) &&
softmac_hold_device(phydev, &ddh) == 0) {
softmac_rele_device(ddh);
return (EEXIST);
}
if (dls_mgmt_get_phydev(id1, &phydev) == 0)
(void) softmac_hold_device(phydev, &ddh);
if ((err = mac_perim_enter_by_linkid(id1, &mph)) != 0) {
softmac_rele_device(ddh);
return (err);
}
i_dls_devnet_lock_enter();
i_dls_devnet_hashmap_read();
if ((err = mod_hash_find(i_dls_devnet_id_hash,
(mod_hash_key_t)(uintptr_t)id1, (mod_hash_val_t *)&ddp)) != 0) {
ASSERT(err == MH_ERR_NOTFOUND);
err = ENOENT;
goto done;
}
mutex_enter(&ddp->dd_mutex);
if (ddp->dd_ref > 1) {
mutex_exit(&ddp->dd_mutex);
err = EBUSY;
goto done;
}
mutex_exit(&ddp->dd_mutex);
if (id2 == DATALINK_INVALID_LINKID) {
(void) strlcpy(ddp->dd_linkname, link,
sizeof (ddp->dd_linkname));
if ((err = mac_open(ddp->dd_mac, &mh)) != 0)
goto done;
(void) mac_rename_primary(mh, link);
mac_close(mh);
goto done;
}
if (ddh == NULL) {
err = EINVAL;
goto done;
}
if ((err = mac_open(ddp->dd_mac, &mh)) != 0)
goto done;
mac_close(mh);
if ((err = mac_mark_exclusive(mh)) != 0)
goto done;
if ((err = mod_hash_find(i_dls_devnet_id_hash,
(mod_hash_key_t)(uintptr_t)id2, &val)) != MH_ERR_NOTFOUND) {
mac_unmark_exclusive(mh);
err = EEXIST;
goto done;
}
i_dls_devnet_hashmap_exit();
i_dls_devnet_lock_upcall_start();
err = dls_mgmt_get_linkinfo(id2, ddp->dd_linkname, NULL, NULL, NULL);
i_dls_devnet_lock_upcall_end();
if (err != 0) {
mac_unmark_exclusive(mh);
goto done_rw_unlocked;
}
i_dls_devnet_hashmap_write();
(void) mod_hash_remove(i_dls_devnet_id_hash,
(mod_hash_key_t)(uintptr_t)id1, &val);
ddp->dd_linkid = id2;
(void) mod_hash_insert(i_dls_devnet_id_hash,
(mod_hash_key_t)(uintptr_t)ddp->dd_linkid, (mod_hash_val_t)ddp);
mac_unmark_exclusive(mh);
mutex_enter(&ddp->dd_mutex);
ddp->dd_prop_loaded = B_FALSE;
ddp->dd_prop_taskid = taskq_dispatch(system_taskq,
dls_devnet_prop_task, ddp, TQ_SLEEP);
mutex_exit(&ddp->dd_mutex);
done:
i_dls_devnet_hashmap_exit();
done_rw_unlocked:
i_dls_devnet_lock_exit();
if (err == 0)
dls_devnet_stat_rename(ddp);
if (mph != NULL)
mac_perim_exit(mph);
softmac_rele_device(ddh);
return (err);
}
static int
i_dls_devnet_setzid(dls_devnet_t *ddp, zoneid_t new_zoneid, boolean_t setprop,
boolean_t transient)
{
int err;
mac_perim_handle_t mph;
boolean_t upcall_done = B_FALSE;
datalink_id_t linkid = ddp->dd_linkid;
zoneid_t old_zoneid = ddp->dd_zid;
dlmgmt_door_setzoneid_t setzid;
dlmgmt_setzoneid_retval_t retval;
if (old_zoneid == new_zoneid)
return (0);
if ((err = mac_perim_enter_by_macname(ddp->dd_mac, &mph)) != 0)
return (err);
if (setprop) {
setzid.ld_cmd = DLMGMT_CMD_SETZONEID;
setzid.ld_linkid = linkid;
setzid.ld_zoneid = new_zoneid;
err = i_dls_mgmt_upcall(&setzid, sizeof (setzid), &retval,
sizeof (retval));
if (err != 0)
goto done;
upcall_done = B_TRUE;
}
if ((err = dls_link_setzid(ddp->dd_mac, new_zoneid)) == 0) {
ddp->dd_zid = new_zoneid;
ddp->dd_transient = transient;
devnet_need_rebuild = B_TRUE;
}
done:
if (err != 0 && upcall_done) {
setzid.ld_zoneid = old_zoneid;
(void) i_dls_mgmt_upcall(&setzid, sizeof (setzid), &retval,
sizeof (retval));
}
mac_perim_exit(mph);
return (err);
}
int
dls_devnet_setzid(dls_dl_handle_t ddh, zoneid_t new_zid)
{
dls_devnet_t *ddp;
int err;
zoneid_t old_zid;
boolean_t refheld = B_FALSE;
old_zid = ddh->dd_zid;
if (old_zid == new_zid)
return (0);
if (old_zid == GLOBAL_ZONEID && new_zid != GLOBAL_ZONEID) {
if ((err = dls_devnet_hold(ddh->dd_linkid, &ddp)) != 0)
return (err);
refheld = B_TRUE;
}
if ((err = i_dls_devnet_setzid(ddh, new_zid, B_TRUE, B_FALSE)) != 0) {
if (refheld)
dls_devnet_rele(ddp);
return (err);
}
if (old_zid != GLOBAL_ZONEID && new_zid == GLOBAL_ZONEID)
dls_devnet_rele(ddh);
if (old_zid != GLOBAL_ZONEID)
dls_devnet_stat_destroy(ddh, old_zid);
if (new_zid != GLOBAL_ZONEID)
dls_devnet_stat_create(ddh, new_zid);
return (0);
}
zoneid_t
dls_devnet_getzid(dls_dl_handle_t ddh)
{
return (((dls_devnet_t *)ddh)->dd_zid);
}
zoneid_t
dls_devnet_getownerzid(dls_dl_handle_t ddh)
{
return (((dls_devnet_t *)ddh)->dd_owner_zid);
}
boolean_t
dls_devnet_islinkvisible(datalink_id_t linkid, zoneid_t zoneid)
{
dls_devnet_t *ddp;
boolean_t result;
if (dls_devnet_hold_tmp(linkid, &ddp) != 0)
return (B_FALSE);
result = (ddp->dd_owner_zid == zoneid || ddp->dd_zid == zoneid);
dls_devnet_rele_tmp(ddp);
return (result);
}
int
dls_devnet_open(const char *link, dls_dl_handle_t *dhp, dev_t *devp)
{
dls_devnet_t *ddp;
dls_link_t *dlp;
zoneid_t zid = getzoneid();
int err;
mac_perim_handle_t mph;
if ((err = dls_devnet_hold_by_name(link, &ddp)) != 0)
return (err);
dls_devnet_prop_task_wait(ddp);
if (zid != GLOBAL_ZONEID && ddp->dd_zid != zid) {
dls_devnet_rele(ddp);
return (ENOENT);
}
err = mac_perim_enter_by_macname(ddp->dd_mac, &mph);
if (err != 0) {
dls_devnet_rele(ddp);
return (err);
}
err = dls_link_hold_create(ddp->dd_mac, &dlp);
mac_perim_exit(mph);
if (err != 0) {
dls_devnet_rele(ddp);
return (err);
}
*dhp = ddp;
*devp = dls_link_dev(dlp);
return (0);
}
void
dls_devnet_close(dls_dl_handle_t dlh)
{
dls_devnet_t *ddp = dlh;
dls_link_t *dlp;
mac_perim_handle_t mph;
VERIFY(mac_perim_enter_by_macname(ddp->dd_mac, &mph) == 0);
VERIFY(dls_link_hold(ddp->dd_mac, &dlp) == 0);
dls_link_rele(dlp);
dls_link_rele(dlp);
mac_perim_exit(mph);
dls_devnet_rele(ddp);
}
boolean_t
dls_devnet_rebuild()
{
boolean_t updated = devnet_need_rebuild;
devnet_need_rebuild = B_FALSE;
return (updated);
}
int
dls_devnet_create(mac_handle_t mh, datalink_id_t linkid, zoneid_t zoneid)
{
dls_link_t *dlp;
dls_devnet_t *ddp;
int err;
mac_perim_handle_t mph;
mac_perim_enter_by_mh(mh, &mph);
if ((err = dls_devnet_set(mh, linkid, zoneid, &ddp)) == 0) {
if ((err = dls_link_hold_create(mac_name(mh), &dlp)) != 0) {
mac_perim_exit(mph);
(void) dls_devnet_unset(mh, &linkid, B_FALSE);
return (err);
}
mutex_enter(&ddp->dd_mutex);
if (ddp->dd_linkid != DATALINK_INVALID_LINKID)
ddp->dd_flags &= ~DD_INITIALIZING;
mutex_exit(&ddp->dd_mutex);
}
mac_perim_exit(mph);
return (err);
}
int
dls_devnet_recreate(mac_handle_t mh, datalink_id_t linkid)
{
dls_devnet_t *ddp;
int err;
VERIFY(linkid != DATALINK_INVALID_LINKID);
if ((err = dls_devnet_set(mh, linkid, GLOBAL_ZONEID, &ddp)) == 0) {
mutex_enter(&ddp->dd_mutex);
if (ddp->dd_linkid != DATALINK_INVALID_LINKID)
ddp->dd_flags &= ~DD_INITIALIZING;
mutex_exit(&ddp->dd_mutex);
}
return (err);
}
int
dls_devnet_destroy(mac_handle_t mh, datalink_id_t *idp, boolean_t wait)
{
int err;
mac_perim_handle_t mph;
*idp = DATALINK_INVALID_LINKID;
err = dls_devnet_unset(mh, idp, wait);
if (err != 0 && err != ENOENT)
return (err);
mac_perim_enter_by_mh(mh, &mph);
err = dls_link_rele_by_name(mac_name(mh));
mac_perim_exit(mph);
if (err != 0) {
dls_devnet_t *ddp;
(void) dls_devnet_set(mh, *idp, crgetzoneid(CRED()), &ddp);
mutex_enter(&ddp->dd_mutex);
if (ddp->dd_linkid != DATALINK_INVALID_LINKID)
ddp->dd_flags &= ~DD_INITIALIZING;
mutex_exit(&ddp->dd_mutex);
}
return (err);
}
static int
i_dls_devnet_create_iptun(const char *linkname, const char *drvname,
datalink_id_t *linkid)
{
int err;
iptun_kparams_t ik;
uint32_t media;
netstack_t *ns;
major_t iptun_major;
dev_info_t *iptun_dip;
if ((iptun_major = ddi_name_to_major(IPTUN_DRIVER_NAME)) == (major_t)-1)
return (EINVAL);
if ((iptun_dip = ddi_hold_devi_by_instance(iptun_major, 0, 0)) == NULL)
return (EINVAL);
if (IS_IPV4_TUN(drvname)) {
ik.iptun_kparam_type = IPTUN_TYPE_IPV4;
media = DL_IPV4;
} else if (IS_6TO4_TUN(drvname)) {
ik.iptun_kparam_type = IPTUN_TYPE_6TO4;
media = DL_6TO4;
} else if (IS_IPV6_TUN(drvname)) {
ik.iptun_kparam_type = IPTUN_TYPE_IPV6;
media = DL_IPV6;
}
ik.iptun_kparam_flags = (IPTUN_KPARAM_TYPE | IPTUN_KPARAM_IMPLICIT);
err = dls_mgmt_create((char *)linkname, 0, DATALINK_CLASS_IPTUN, media,
B_FALSE, &ik.iptun_kparam_linkid);
if (err != 0) {
ddi_release_devi(iptun_dip);
return (err);
}
ns = netstack_get_current();
err = iptun_create(&ik, CRED());
netstack_rele(ns);
if (err != 0)
VERIFY(dls_mgmt_destroy(ik.iptun_kparam_linkid, B_FALSE) == 0);
else
*linkid = ik.iptun_kparam_linkid;
ddi_release_devi(iptun_dip);
return (err);
}
static int
i_dls_devnet_destroy_iptun(datalink_id_t linkid)
{
int err;
if ((err = iptun_delete(linkid, zone_kcred())) == 0)
(void) dls_mgmt_destroy(linkid, B_FALSE);
return (err);
}
const char *
dls_devnet_link(dls_dl_handle_t ddh)
{
return (ddh->dd_linkname);
}
const char *
dls_devnet_mac(dls_dl_handle_t ddh)
{
return (ddh->dd_mac);
}
datalink_id_t
dls_devnet_linkid(dls_dl_handle_t ddh)
{
return (ddh->dd_linkid);
}