#include <sys/types.h>
#include <sys/bitmap.h>
#include <sys/cmn_err.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/errno.h>
#include <sys/kstat.h>
#include <sys/modctl.h>
#include <sys/note.h>
#include <sys/param.h>
#include <sys/pattr.h>
#include <sys/policy.h>
#include <sys/sdt.h>
#include <sys/stat.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/sunddi.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/dlpi.h>
#include <sys/dls.h>
#include <sys/mac_ether.h>
#include <sys/mac_provider.h>
#include <sys/mac_client_priv.h>
#include <sys/mac_impl.h>
#include <sys/vlan.h>
#include <net/bridge.h>
#include <net/bridge_impl.h>
#include <net/trill.h>
#include <sys/dld_ioc.h>
const uint8_t all_isis_rbridges[] = ALL_ISIS_RBRIDGES;
static const uint8_t all_esadi_rbridges[] = ALL_ESADI_RBRIDGES;
const uint8_t bridge_group_address[] = BRIDGE_GROUP_ADDRESS;
static const char *inst_kstats_list[] = { KSINST_NAMES };
static const char *link_kstats_list[] = { KSLINK_NAMES };
#define KREF(p, m, vn) p->m.vn.value.ui64
#define KINCR(p, m, vn) ++KREF(p, m, vn)
#define KDECR(p, m, vn) --KREF(p, m, vn)
#define KIPINCR(p, vn) KINCR(p, bi_kstats, vn)
#define KIPDECR(p, vn) KDECR(p, bi_kstats, vn)
#define KLPINCR(p, vn) KINCR(p, bl_kstats, vn)
#define KIINCR(vn) KIPINCR(bip, vn)
#define KIDECR(vn) KIPDECR(bip, vn)
#define KLINCR(vn) KLPINCR(blp, vn)
#define Dim(x) (sizeof (x) / sizeof (*(x)))
#define VLAN_INCR (sizeof (struct ether_vlan_header) - \
sizeof (struct ether_header))
static dev_info_t *bridge_dev_info;
static major_t bridge_major;
static ddi_taskq_t *bridge_taskq;
static list_t inst_list;
static kcondvar_t inst_cv;
static kmutex_t inst_lock;
static krwlock_t bmac_rwlock;
static list_t bmac_list;
static kcondvar_t stream_ref_cv;
static kmutex_t stream_ref_lock;
static timeout_id_t bridge_timerid;
static clock_t bridge_scan_interval;
static clock_t bridge_fwd_age;
static bridge_inst_t *bridge_find_name(const char *);
static void bridge_timer(void *);
static void bridge_unref(bridge_inst_t *);
static const uint8_t zero_addr[ETHERADDRL] = { 0 };
static trill_recv_pkt_t trill_recv_fn;
static trill_encap_pkt_t trill_encap_fn;
static trill_br_dstr_t trill_brdstr_fn;
static trill_ln_dstr_t trill_lndstr_fn;
static struct module_info bridge_dld_modinfo = {
0,
BRIDGE_DEV_NAME,
0,
INFPSZ,
1,
0
};
static struct qinit bridge_dld_rinit = {
NULL,
NULL,
dld_open,
dld_close,
NULL,
&bridge_dld_modinfo,
NULL
};
static struct qinit bridge_dld_winit = {
dld_wput,
dld_wsrv,
NULL,
NULL,
NULL,
&bridge_dld_modinfo,
NULL
};
static int bridge_ioc_listfwd(void *, intptr_t, int, cred_t *, int *);
static dld_ioc_info_t bridge_ioc_list[] = {
{BRIDGE_IOC_LISTFWD, DLDCOPYINOUT, sizeof (bridge_listfwd_t),
bridge_ioc_listfwd, NULL},
};
static bridge_inst_t *
mac_to_inst(const bridge_mac_t *bmp)
{
bridge_inst_t *bip;
rw_enter(&bmac_rwlock, RW_READER);
if ((bip = bmp->bm_inst) != NULL)
atomic_inc_uint(&bip->bi_refs);
rw_exit(&bmac_rwlock);
return (bip);
}
static void
link_sdu_fail(bridge_link_t *blp, boolean_t failed, mblk_t **mlist)
{
mblk_t *mp;
bridge_ctl_t *bcp;
bridge_link_t *blcmp;
bridge_inst_t *bip;
bridge_mac_t *bmp;
if (failed) {
if (blp->bl_flags & BLF_SDUFAIL)
return;
blp->bl_flags |= BLF_SDUFAIL;
} else {
if (!(blp->bl_flags & BLF_SDUFAIL))
return;
blp->bl_flags &= ~BLF_SDUFAIL;
}
bip = blp->bl_inst;
bmp = bip->bi_mac;
if (blp->bl_linkstate != LINK_STATE_DOWN) {
for (blcmp = list_head(&bip->bi_links); blcmp != NULL;
blcmp = list_next(&bip->bi_links, blcmp)) {
if (blp != blcmp &&
!(blcmp->bl_flags & (BLF_DELETED|BLF_SDUFAIL)) &&
blcmp->bl_linkstate != LINK_STATE_DOWN)
break;
}
if (blcmp == NULL) {
bmp->bm_linkstate = failed ? LINK_STATE_DOWN :
LINK_STATE_UP;
mac_link_redo(bmp->bm_mh, bmp->bm_linkstate);
}
}
if (failed) {
if (bmp->bm_linkstate != blp->bl_linkstate)
mac_link_redo(blp->bl_mh, blp->bl_linkstate);
} else {
mac_link_redo(blp->bl_mh, bmp->bm_linkstate);
}
if ((mp = blp->bl_lfailmp) == NULL &&
(mp = allocb(sizeof (bridge_ctl_t), BPRI_MED)) == NULL)
return;
blp->bl_lfailmp = allocb(sizeof (bridge_ctl_t), BPRI_MED);
if (blp->bl_lfailmp == NULL && !failed) {
blp->bl_lfailmp = mp;
return;
}
bcp = (bridge_ctl_t *)mp->b_rptr;
bcp->bc_linkid = blp->bl_linkid;
bcp->bc_failed = failed;
mp->b_wptr = (uchar_t *)(bcp + 1);
mp->b_next = *mlist;
*mlist = mp;
}
static void
send_up_messages(bridge_inst_t *bip, mblk_t *mp)
{
mblk_t *mnext;
queue_t *rq;
rq = bip->bi_control->bs_wq;
rq = OTHERQ(rq);
while (mp != NULL) {
mnext = mp->b_next;
mp->b_next = NULL;
putnext(rq, mp);
mp = mnext;
}
}
static int
bridge_m_getstat(void *arg, uint_t stat, uint64_t *val)
{
return (ENOTSUP);
}
static int
bridge_m_start(void *arg)
{
bridge_mac_t *bmp = arg;
bmp->bm_flags |= BMF_STARTED;
return (0);
}
static void
bridge_m_stop(void *arg)
{
bridge_mac_t *bmp = arg;
bmp->bm_flags &= ~BMF_STARTED;
}
static int
bridge_m_setpromisc(void *arg, boolean_t on)
{
return (0);
}
static int
bridge_m_multicst(void *arg, boolean_t add, const uint8_t *mca)
{
return (0);
}
static int
bridge_m_unicst(void *arg, const uint8_t *macaddr)
{
return (ENOTSUP);
}
static mblk_t *
bridge_m_tx(void *arg, mblk_t *mp)
{
_NOTE(ARGUNUSED(arg));
freemsgchain(mp);
return (NULL);
}
static int
bridge_ioc_listfwd(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
{
bridge_listfwd_t *blf = karg;
bridge_inst_t *bip;
bridge_fwd_t *bfp, match;
avl_index_t where;
bip = bridge_find_name(blf->blf_name);
if (bip == NULL)
return (ENOENT);
bcopy(blf->blf_dest, match.bf_dest, ETHERADDRL);
match.bf_flags |= BFF_VLANLOCAL;
rw_enter(&bip->bi_rwlock, RW_READER);
if ((bfp = avl_find(&bip->bi_fwd, &match, &where)) == NULL)
bfp = avl_nearest(&bip->bi_fwd, where, AVL_AFTER);
else
bfp = AVL_NEXT(&bip->bi_fwd, bfp);
if (bfp == NULL) {
bzero(blf, sizeof (*blf));
} else {
bcopy(bfp->bf_dest, blf->blf_dest, ETHERADDRL);
blf->blf_trill_nick = bfp->bf_trill_nick;
blf->blf_ms_age =
drv_hztousec(ddi_get_lbolt() - bfp->bf_lastheard) / 1000;
blf->blf_is_local =
(bfp->bf_flags & BFF_LOCALADDR) != 0;
blf->blf_linkid = bfp->bf_links[0]->bl_linkid;
}
rw_exit(&bip->bi_rwlock);
bridge_unref(bip);
return (0);
}
static int
bridge_m_setprop(void *arg, const char *pr_name, mac_prop_id_t pr_num,
uint_t pr_valsize, const void *pr_val)
{
bridge_mac_t *bmp = arg;
bridge_inst_t *bip;
bridge_link_t *blp;
int err;
uint_t maxsdu;
mblk_t *mlist;
_NOTE(ARGUNUSED(pr_name));
switch (pr_num) {
case MAC_PROP_MTU:
if (pr_valsize < sizeof (bmp->bm_maxsdu)) {
err = EINVAL;
break;
}
(void) bcopy(pr_val, &maxsdu, sizeof (maxsdu));
if (maxsdu == bmp->bm_maxsdu) {
err = 0;
} else if ((bip = mac_to_inst(bmp)) == NULL) {
err = ENXIO;
} else {
rw_enter(&bip->bi_rwlock, RW_WRITER);
mlist = NULL;
for (blp = list_head(&bip->bi_links); blp != NULL;
blp = list_next(&bip->bi_links, blp)) {
if (blp->bl_flags & BLF_DELETED)
continue;
if (blp->bl_maxsdu == maxsdu)
link_sdu_fail(blp, B_FALSE, &mlist);
else if (blp->bl_maxsdu == bmp->bm_maxsdu)
link_sdu_fail(blp, B_TRUE, &mlist);
}
rw_exit(&bip->bi_rwlock);
bmp->bm_maxsdu = maxsdu;
(void) mac_maxsdu_update(bmp->bm_mh, maxsdu);
send_up_messages(bip, mlist);
bridge_unref(bip);
err = 0;
}
break;
default:
err = ENOTSUP;
break;
}
return (err);
}
static int
bridge_m_getprop(void *arg, const char *pr_name, mac_prop_id_t pr_num,
uint_t pr_valsize, void *pr_val)
{
bridge_mac_t *bmp = arg;
int err = 0;
_NOTE(ARGUNUSED(pr_name));
switch (pr_num) {
case MAC_PROP_STATUS:
ASSERT(pr_valsize >= sizeof (bmp->bm_linkstate));
bcopy(&bmp->bm_linkstate, pr_val, sizeof (bmp->bm_linkstate));
break;
default:
err = ENOTSUP;
break;
}
return (err);
}
static void
bridge_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t pr_num,
mac_prop_info_handle_t prh)
{
bridge_mac_t *bmp = arg;
_NOTE(ARGUNUSED(pr_name));
switch (pr_num) {
case MAC_PROP_MTU:
mac_prop_info_set_range_uint32(prh, bmp->bm_maxsdu,
bmp->bm_maxsdu);
break;
case MAC_PROP_STATUS:
mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
break;
}
}
static mac_callbacks_t bridge_m_callbacks = {
MC_SETPROP | MC_GETPROP | MC_PROPINFO,
bridge_m_getstat,
bridge_m_start,
bridge_m_stop,
bridge_m_setpromisc,
bridge_m_multicst,
bridge_m_unicst,
bridge_m_tx,
NULL,
NULL,
NULL,
NULL,
NULL,
bridge_m_setprop,
bridge_m_getprop,
bridge_m_propinfo
};
static kstat_t *
kstat_setup(kstat_named_t *knt, const char **names, int nstat,
const char *unitname)
{
kstat_t *ksp;
int i;
for (i = 0; i < nstat; i++)
kstat_named_init(&knt[i], names[i], KSTAT_DATA_UINT64);
ksp = kstat_create_zone(BRIDGE_DEV_NAME, 0, unitname, "net",
KSTAT_TYPE_NAMED, nstat, KSTAT_FLAG_VIRTUAL, GLOBAL_ZONEID);
if (ksp != NULL) {
ksp->ks_data = knt;
kstat_install(ksp);
}
return (ksp);
}
static int
bmac_alloc(bridge_inst_t *bip, bridge_mac_t **bmacp)
{
bridge_mac_t *bmp, *bnew;
mac_register_t *mac;
int err;
*bmacp = NULL;
if ((mac = mac_alloc(MAC_VERSION)) == NULL)
return (EINVAL);
bnew = kmem_zalloc(sizeof (*bnew), KM_SLEEP);
rw_enter(&bmac_rwlock, RW_WRITER);
for (bmp = list_head(&bmac_list); bmp != NULL;
bmp = list_next(&bmac_list, bmp)) {
if (strcmp(bip->bi_name, bmp->bm_name) == 0) {
ASSERT(bmp->bm_inst == NULL);
bmp->bm_inst = bip;
rw_exit(&bmac_rwlock);
kmem_free(bnew, sizeof (*bnew));
mac_free(mac);
*bmacp = bmp;
return (0);
}
}
mac->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
mac->m_driver = bnew;
mac->m_dip = bridge_dev_info;
mac->m_instance = (uint_t)-1;
mac->m_src_addr = (uint8_t *)zero_addr;
mac->m_callbacks = &bridge_m_callbacks;
mac->m_min_sdu = 1;
mac->m_max_sdu = 1500;
err = mac_register(mac, &bnew->bm_mh);
mac_free(mac);
if (err != 0) {
rw_exit(&bmac_rwlock);
kmem_free(bnew, sizeof (*bnew));
return (err);
}
bnew->bm_inst = bip;
(void) strcpy(bnew->bm_name, bip->bi_name);
if (list_is_empty(&bmac_list)) {
bridge_timerid = timeout(bridge_timer, NULL,
bridge_scan_interval);
}
list_insert_tail(&bmac_list, bnew);
rw_exit(&bmac_rwlock);
mac_no_active(bnew->bm_mh);
*bmacp = bnew;
return (0);
}
static void
bmac_disconnect(bridge_mac_t *bmp)
{
bridge_inst_t *bip;
bmp->bm_linkstate = LINK_STATE_DOWN;
mac_link_redo(bmp->bm_mh, LINK_STATE_DOWN);
rw_enter(&bmac_rwlock, RW_READER);
bip = bmp->bm_inst;
bip->bi_mac = NULL;
bmp->bm_inst = NULL;
rw_exit(&bmac_rwlock);
}
static int
fwd_compare(const void *addr1, const void *addr2)
{
const bridge_fwd_t *fwd1 = addr1;
const bridge_fwd_t *fwd2 = addr2;
int diff = memcmp(fwd1->bf_dest, fwd2->bf_dest, ETHERADDRL);
if (diff != 0)
return (diff > 0 ? 1 : -1);
if ((fwd1->bf_flags ^ fwd2->bf_flags) & BFF_VLANLOCAL) {
if (fwd1->bf_vlanid > fwd2->bf_vlanid)
return (1);
else if (fwd1->bf_vlanid < fwd2->bf_vlanid)
return (-1);
}
return (0);
}
static void
inst_free(bridge_inst_t *bip)
{
ASSERT(bip->bi_mac == NULL);
rw_destroy(&bip->bi_rwlock);
list_destroy(&bip->bi_links);
cv_destroy(&bip->bi_linkwait);
avl_destroy(&bip->bi_fwd);
if (bip->bi_ksp != NULL)
kstat_delete(bip->bi_ksp);
kmem_free(bip, sizeof (*bip));
}
static bridge_inst_t *
inst_alloc(const char *bridge)
{
bridge_inst_t *bip;
bip = kmem_zalloc(sizeof (*bip), KM_SLEEP);
bip->bi_refs = 1;
(void) strcpy(bip->bi_name, bridge);
rw_init(&bip->bi_rwlock, NULL, RW_DRIVER, NULL);
list_create(&bip->bi_links, sizeof (bridge_link_t),
offsetof(bridge_link_t, bl_node));
cv_init(&bip->bi_linkwait, NULL, CV_DRIVER, NULL);
avl_create(&bip->bi_fwd, fwd_compare, sizeof (bridge_fwd_t),
offsetof(bridge_fwd_t, bf_node));
return (bip);
}
static bridge_inst_t *
bridge_find_name(const char *bridge)
{
bridge_inst_t *bip;
mutex_enter(&inst_lock);
for (bip = list_head(&inst_list); bip != NULL;
bip = list_next(&inst_list, bip)) {
if (!(bip->bi_flags & BIF_SHUTDOWN) &&
strcmp(bridge, bip->bi_name) == 0) {
atomic_inc_uint(&bip->bi_refs);
break;
}
}
mutex_exit(&inst_lock);
return (bip);
}
static int
bridge_create(datalink_id_t linkid, const char *bridge, bridge_inst_t **bipc,
cred_t *cred)
{
bridge_inst_t *bip, *bipnew;
bridge_mac_t *bmp = NULL;
int err;
*bipc = NULL;
bipnew = inst_alloc(bridge);
mutex_enter(&inst_lock);
lookup_retry:
for (bip = list_head(&inst_list); bip != NULL;
bip = list_next(&inst_list, bip)) {
if (strcmp(bridge, bip->bi_name) == 0)
break;
}
if (bip != NULL && (bip->bi_flags & BIF_SHUTDOWN)) {
cv_wait(&inst_cv, &inst_lock);
goto lookup_retry;
}
if (bip == NULL) {
bip = bipnew;
bipnew = NULL;
list_insert_tail(&inst_list, bip);
}
mutex_exit(&inst_lock);
if (bipnew != NULL) {
inst_free(bipnew);
return (EEXIST);
}
bip->bi_ksp = kstat_setup((kstat_named_t *)&bip->bi_kstats,
inst_kstats_list, Dim(inst_kstats_list), bip->bi_name);
err = bmac_alloc(bip, &bmp);
if ((bip->bi_mac = bmp) == NULL)
goto fail_create;
if (!(bmp->bm_flags & BMF_DLS)) {
err = dls_devnet_create(bmp->bm_mh, linkid, crgetzoneid(cred));
if (err != 0)
goto fail_create;
bmp->bm_flags |= BMF_DLS;
}
bip->bi_dev = makedevice(bridge_major, mac_minor(bmp->bm_mh));
*bipc = bip;
return (0);
fail_create:
ASSERT(bip->bi_trilldata == NULL);
bip->bi_flags |= BIF_SHUTDOWN;
bridge_unref(bip);
return (err);
}
static void
bridge_unref(bridge_inst_t *bip)
{
if (atomic_dec_uint_nv(&bip->bi_refs) == 0) {
ASSERT(bip->bi_flags & BIF_SHUTDOWN);
if (bip->bi_mac != NULL)
bmac_disconnect(bip->bi_mac);
mutex_enter(&inst_lock);
list_remove(&inst_list, bip);
cv_broadcast(&inst_cv);
mutex_exit(&inst_lock);
inst_free(bip);
}
}
static bridge_stream_t *
stream_alloc(void)
{
bridge_stream_t *bsp;
minor_t mn;
if ((mn = mac_minor_hold(B_FALSE)) == 0)
return (NULL);
bsp = kmem_zalloc(sizeof (*bsp), KM_SLEEP);
bsp->bs_minor = mn;
return (bsp);
}
static void
stream_free(bridge_stream_t *bsp)
{
mac_minor_rele(bsp->bs_minor);
kmem_free(bsp, sizeof (*bsp));
}
static void
stream_ref(bridge_stream_t *bsp)
{
mutex_enter(&stream_ref_lock);
bsp->bs_taskq_cnt++;
mutex_exit(&stream_ref_lock);
}
static void
stream_unref(bridge_stream_t *bsp)
{
mutex_enter(&stream_ref_lock);
if (--bsp->bs_taskq_cnt == 0)
cv_broadcast(&stream_ref_cv);
mutex_exit(&stream_ref_lock);
}
static void
link_free(bridge_link_t *blp)
{
bridge_inst_t *bip = blp->bl_inst;
ASSERT(!(blp->bl_flags & BLF_FREED));
blp->bl_flags |= BLF_FREED;
if (blp->bl_ksp != NULL)
kstat_delete(blp->bl_ksp);
if (blp->bl_lfailmp != NULL)
freeb(blp->bl_lfailmp);
cv_destroy(&blp->bl_trillwait);
mutex_destroy(&blp->bl_trilllock);
kmem_free(blp, sizeof (*blp));
bridge_unref(bip);
}
static void
link_unref(bridge_link_t *blp)
{
if (atomic_dec_uint_nv(&blp->bl_refs) == 0) {
bridge_inst_t *bip = blp->bl_inst;
ASSERT(blp->bl_flags & BLF_DELETED);
rw_enter(&bip->bi_rwlock, RW_WRITER);
if (blp->bl_flags & BLF_LINK_ADDED)
list_remove(&bip->bi_links, blp);
rw_exit(&bip->bi_rwlock);
if (bip->bi_trilldata != NULL && list_is_empty(&bip->bi_links))
cv_broadcast(&bip->bi_linkwait);
link_free(blp);
}
}
static bridge_fwd_t *
fwd_alloc(const uint8_t *addr, uint_t nlinks, uint16_t nick)
{
bridge_fwd_t *bfp;
bfp = kmem_zalloc(sizeof (*bfp) + (nlinks * sizeof (bridge_link_t *)),
KM_NOSLEEP);
if (bfp != NULL) {
bcopy(addr, bfp->bf_dest, ETHERADDRL);
bfp->bf_lastheard = ddi_get_lbolt();
bfp->bf_maxlinks = nlinks;
bfp->bf_links = (bridge_link_t **)(bfp + 1);
bfp->bf_trill_nick = nick;
}
return (bfp);
}
static bridge_fwd_t *
fwd_find(bridge_inst_t *bip, const uint8_t *addr, uint16_t vlanid)
{
bridge_fwd_t *bfp, *vbfp;
bridge_fwd_t match;
bcopy(addr, match.bf_dest, ETHERADDRL);
match.bf_flags = 0;
rw_enter(&bip->bi_rwlock, RW_READER);
if ((bfp = avl_find(&bip->bi_fwd, &match, NULL)) != NULL) {
if (bfp->bf_vlanid != vlanid && bfp->bf_vcnt > 0) {
match.bf_vlanid = vlanid;
match.bf_flags = BFF_VLANLOCAL;
vbfp = avl_find(&bip->bi_fwd, &match, NULL);
if (vbfp != NULL)
bfp = vbfp;
}
atomic_inc_uint(&bfp->bf_refs);
}
rw_exit(&bip->bi_rwlock);
return (bfp);
}
static void
fwd_free(bridge_fwd_t *bfp)
{
uint_t i;
bridge_inst_t *bip = bfp->bf_links[0]->bl_inst;
KIDECR(bki_count);
for (i = 0; i < bfp->bf_nlinks; i++)
link_unref(bfp->bf_links[i]);
kmem_free(bfp,
sizeof (*bfp) + bfp->bf_maxlinks * sizeof (bridge_link_t *));
}
static void
fwd_unref(bridge_fwd_t *bfp)
{
if (atomic_dec_uint_nv(&bfp->bf_refs) == 0) {
ASSERT(!(bfp->bf_flags & BFF_INTREE));
fwd_free(bfp);
}
}
static void
fwd_delete(bridge_fwd_t *bfp)
{
bridge_inst_t *bip;
bridge_fwd_t *bfpzero;
if (bfp->bf_flags & BFF_INTREE) {
ASSERT(bfp->bf_nlinks > 0);
bip = bfp->bf_links[0]->bl_inst;
rw_enter(&bip->bi_rwlock, RW_WRITER);
if (bfp->bf_flags & BFF_INTREE) {
avl_remove(&bip->bi_fwd, bfp);
bfp->bf_flags &= ~BFF_INTREE;
if (bfp->bf_flags & BFF_VLANLOCAL) {
bfp->bf_flags &= ~BFF_VLANLOCAL;
bfpzero = avl_find(&bip->bi_fwd, bfp, NULL);
if (bfpzero != NULL && bfpzero->bf_vcnt > 0)
bfpzero->bf_vcnt--;
}
rw_exit(&bip->bi_rwlock);
fwd_unref(bfp);
} else {
rw_exit(&bip->bi_rwlock);
}
}
}
static boolean_t
fwd_insert(bridge_inst_t *bip, bridge_fwd_t *bfp)
{
avl_index_t idx;
boolean_t retv;
rw_enter(&bip->bi_rwlock, RW_WRITER);
if (!(bip->bi_flags & BIF_SHUTDOWN) &&
avl_numnodes(&bip->bi_fwd) < bip->bi_tablemax &&
avl_find(&bip->bi_fwd, bfp, &idx) == NULL) {
avl_insert(&bip->bi_fwd, bfp, idx);
bfp->bf_flags |= BFF_INTREE;
atomic_inc_uint(&bfp->bf_refs);
retv = B_TRUE;
} else {
retv = B_FALSE;
}
rw_exit(&bip->bi_rwlock);
return (retv);
}
static void
fwd_update_local(bridge_link_t *blp, const uint8_t *oldaddr,
const uint8_t *newaddr)
{
bridge_inst_t *bip = blp->bl_inst;
bridge_fwd_t *bfp, *bfnew;
bridge_fwd_t match;
avl_index_t idx;
boolean_t drop_ref = B_FALSE;
if (bcmp(oldaddr, newaddr, ETHERADDRL) == 0)
return;
if (bcmp(oldaddr, zero_addr, ETHERADDRL) == 0)
goto no_old_addr;
bcopy(oldaddr, match.bf_dest, ETHERADDRL);
rw_enter(&bip->bi_rwlock, RW_WRITER);
if ((bfp = avl_find(&bip->bi_fwd, &match, NULL)) != NULL) {
int i;
for (i = 0; i < bfp->bf_nlinks; i++) {
if (bfp->bf_links[i] == blp) {
bfp->bf_nlinks--;
for (; i < bfp->bf_nlinks; i++)
bfp->bf_links[i] = bfp->bf_links[i + 1];
drop_ref = B_TRUE;
break;
}
}
if (bfp->bf_nlinks == 0) {
avl_remove(&bip->bi_fwd, bfp);
bfp->bf_flags &= ~BFF_INTREE;
} else {
bfp = NULL;
}
}
rw_exit(&bip->bi_rwlock);
if (bfp != NULL)
fwd_unref(bfp);
no_old_addr:
bfp = NULL;
if ((bip->bi_flags & BIF_SHUTDOWN) ||
bcmp(newaddr, zero_addr, ETHERADDRL) == 0)
goto no_new_addr;
bcopy(newaddr, match.bf_dest, ETHERADDRL);
rw_enter(&bip->bi_rwlock, RW_WRITER);
if ((bfp = avl_find(&bip->bi_fwd, &match, &idx)) == NULL) {
bfnew = fwd_alloc(newaddr, 1, RBRIDGE_NICKNAME_NONE);
if (bfnew != NULL)
KIINCR(bki_count);
} else if (bfp->bf_nlinks < bfp->bf_maxlinks) {
bfnew = bfp;
} else {
bfnew = fwd_alloc(newaddr, bfp->bf_nlinks + 1,
RBRIDGE_NICKNAME_NONE);
if (bfnew != NULL) {
KIINCR(bki_count);
avl_remove(&bip->bi_fwd, bfp);
bfp->bf_flags &= ~BFF_INTREE;
bfnew->bf_nlinks = bfp->bf_nlinks;
bcopy(bfp->bf_links, bfnew->bf_links,
bfp->bf_nlinks * sizeof (bfp));
(void) avl_find(&bip->bi_fwd, &match, &idx);
}
}
if (bfnew != NULL) {
bfnew->bf_links[bfnew->bf_nlinks++] = blp;
if (drop_ref)
drop_ref = B_FALSE;
else
atomic_inc_uint(&blp->bl_refs);
if (bfnew != bfp) {
avl_insert(&bip->bi_fwd, bfnew, idx);
bfnew->bf_flags |= (BFF_INTREE | BFF_LOCALADDR);
atomic_inc_uint(&bfnew->bf_refs);
}
}
rw_exit(&bip->bi_rwlock);
no_new_addr:
if (bfnew != NULL && bfp != NULL && bfnew != bfp)
fwd_unref(bfp);
if (drop_ref)
link_unref(blp);
}
static void
bridge_new_unicst(bridge_link_t *blp)
{
uint8_t new_mac[ETHERADDRL];
mac_unicast_primary_get(blp->bl_mh, new_mac);
fwd_update_local(blp, blp->bl_local_mac, new_mac);
bcopy(new_mac, blp->bl_local_mac, ETHERADDRL);
}
static void
link_shutdown(void *arg)
{
bridge_link_t *blp = arg;
mac_handle_t mh = blp->bl_mh;
bridge_inst_t *bip;
bridge_fwd_t *bfp, *bfnext;
avl_tree_t fwd_scavenge;
int i;
if (blp->bl_trilldata != NULL)
trill_lndstr_fn(blp->bl_trilldata, blp);
if (blp->bl_flags & BLF_PROM_ADDED)
(void) mac_promisc_remove(blp->bl_mphp);
if (blp->bl_flags & BLF_SET_BRIDGE)
mac_bridge_clear(mh, (mac_handle_t)blp);
if (blp->bl_flags & BLF_MARGIN_ADDED) {
(void) mac_notify_remove(blp->bl_mnh, B_TRUE);
(void) mac_margin_remove(mh, blp->bl_margin);
}
mac_link_redo(blp->bl_mh,
mac_stat_get(blp->bl_mh, MAC_STAT_LOWLINK_STATE));
avl_create(&fwd_scavenge, fwd_compare, sizeof (bridge_fwd_t),
offsetof(bridge_fwd_t, bf_node));
bip = blp->bl_inst;
rw_enter(&bip->bi_rwlock, RW_WRITER);
bfnext = avl_first(&bip->bi_fwd);
while ((bfp = bfnext) != NULL) {
bfnext = AVL_NEXT(&bip->bi_fwd, bfp);
for (i = 0; i < bfp->bf_nlinks; i++) {
if (bfp->bf_links[i] == blp)
break;
}
if (i >= bfp->bf_nlinks)
continue;
if (bfp->bf_nlinks > 1) {
link_unref(blp);
bfp->bf_nlinks--;
for (; i < bfp->bf_nlinks; i++)
bfp->bf_links[i] = bfp->bf_links[i + 1];
} else {
ASSERT(bfp->bf_flags & BFF_INTREE);
avl_remove(&bip->bi_fwd, bfp);
bfp->bf_flags &= ~BFF_INTREE;
avl_add(&fwd_scavenge, bfp);
}
}
rw_exit(&bip->bi_rwlock);
bfnext = avl_first(&fwd_scavenge);
while ((bfp = bfnext) != NULL) {
bfnext = AVL_NEXT(&fwd_scavenge, bfp);
avl_remove(&fwd_scavenge, bfp);
fwd_unref(bfp);
}
avl_destroy(&fwd_scavenge);
if (blp->bl_flags & BLF_CLIENT_OPEN)
mac_client_close(blp->bl_mch, 0);
mac_close(mh);
link_unref(blp);
}
static void
shutdown_inst(bridge_inst_t *bip)
{
bridge_link_t *blp, *blnext;
bridge_fwd_t *bfp;
mutex_enter(&inst_lock);
if (bip->bi_flags & BIF_SHUTDOWN) {
mutex_exit(&inst_lock);
return;
}
bip->bi_flags |= BIF_SHUTDOWN;
mutex_exit(&inst_lock);
bip->bi_control = NULL;
rw_enter(&bip->bi_rwlock, RW_READER);
blnext = list_head(&bip->bi_links);
while ((blp = blnext) != NULL) {
blnext = list_next(&bip->bi_links, blp);
if (!(blp->bl_flags & BLF_DELETED)) {
blp->bl_flags |= BLF_DELETED;
(void) ddi_taskq_dispatch(bridge_taskq, link_shutdown,
blp, DDI_SLEEP);
}
}
while ((bfp = avl_first(&bip->bi_fwd)) != NULL) {
atomic_inc_uint(&bfp->bf_refs);
rw_exit(&bip->bi_rwlock);
fwd_delete(bfp);
fwd_unref(bfp);
rw_enter(&bip->bi_rwlock, RW_READER);
}
rw_exit(&bip->bi_rwlock);
mutex_enter(&inst_lock);
while (bip->bi_trilldata != NULL && !list_is_empty(&bip->bi_links))
cv_wait(&bip->bi_linkwait, &inst_lock);
mutex_exit(&inst_lock);
if (bip->bi_trilldata != NULL)
trill_brdstr_fn(bip->bi_trilldata, bip);
bridge_unref(bip);
}
void
bridge_trill_register_cb(trill_recv_pkt_t recv_fn, trill_encap_pkt_t encap_fn,
trill_br_dstr_t brdstr_fn, trill_ln_dstr_t lndstr_fn)
{
#ifdef DEBUG
if (recv_fn == NULL && trill_recv_fn != NULL) {
bridge_inst_t *bip;
bridge_link_t *blp;
mutex_enter(&inst_lock);
for (bip = list_head(&inst_list); bip != NULL;
bip = list_next(&inst_list, bip)) {
ASSERT(bip->bi_trilldata == NULL);
rw_enter(&bip->bi_rwlock, RW_READER);
for (blp = list_head(&bip->bi_links); blp != NULL;
blp = list_next(&bip->bi_links, blp)) {
ASSERT(blp->bl_trilldata == NULL);
}
rw_exit(&bip->bi_rwlock);
}
mutex_exit(&inst_lock);
}
#endif
trill_recv_fn = recv_fn;
trill_encap_fn = encap_fn;
trill_brdstr_fn = brdstr_fn;
trill_lndstr_fn = lndstr_fn;
}
bridge_inst_t *
bridge_trill_brref(const char *bname, void *ptr)
{
char bridge[MAXLINKNAMELEN];
bridge_inst_t *bip;
(void) snprintf(bridge, MAXLINKNAMELEN, "%s0", bname);
bip = bridge_find_name(bridge);
if (bip != NULL) {
ASSERT(bip->bi_trilldata == NULL && ptr != NULL);
bip->bi_trilldata = ptr;
}
return (bip);
}
void
bridge_trill_brunref(bridge_inst_t *bip)
{
ASSERT(bip->bi_trilldata != NULL);
bip->bi_trilldata = NULL;
bridge_unref(bip);
}
bridge_link_t *
bridge_trill_lnref(bridge_inst_t *bip, datalink_id_t linkid, void *ptr)
{
bridge_link_t *blp;
ASSERT(ptr != NULL);
rw_enter(&bip->bi_rwlock, RW_READER);
for (blp = list_head(&bip->bi_links); blp != NULL;
blp = list_next(&bip->bi_links, blp)) {
if (!(blp->bl_flags & BLF_DELETED) &&
blp->bl_linkid == linkid && blp->bl_trilldata == NULL) {
blp->bl_trilldata = ptr;
blp->bl_flags &= ~BLF_TRILLACTIVE;
(void) memset(blp->bl_afs, 0, sizeof (blp->bl_afs));
atomic_inc_uint(&blp->bl_refs);
break;
}
}
rw_exit(&bip->bi_rwlock);
return (blp);
}
void
bridge_trill_lnunref(bridge_link_t *blp)
{
mutex_enter(&blp->bl_trilllock);
ASSERT(blp->bl_trilldata != NULL);
blp->bl_trilldata = NULL;
blp->bl_flags &= ~BLF_TRILLACTIVE;
while (blp->bl_trillthreads > 0)
cv_wait(&blp->bl_trillwait, &blp->bl_trilllock);
mutex_exit(&blp->bl_trilllock);
(void) memset(blp->bl_afs, 0xff, sizeof (blp->bl_afs));
link_unref(blp);
}
static void
bridge_timer(void *arg)
{
bridge_inst_t *bip;
bridge_fwd_t *bfp, *bfnext;
bridge_mac_t *bmp, *bmnext;
bridge_link_t *blp;
int err;
datalink_id_t tmpid;
avl_tree_t fwd_scavenge;
clock_t age_limit;
uint32_t ldecay;
avl_create(&fwd_scavenge, fwd_compare, sizeof (bridge_fwd_t),
offsetof(bridge_fwd_t, bf_node));
mutex_enter(&inst_lock);
for (bip = list_head(&inst_list); bip != NULL;
bip = list_next(&inst_list, bip)) {
if (bip->bi_flags & BIF_SHUTDOWN)
continue;
rw_enter(&bip->bi_rwlock, RW_WRITER);
if (avl_numnodes(&bip->bi_fwd) > bip->bi_tablemax)
bip->bi_tshift++;
else
bip->bi_tshift = 0;
if ((age_limit = bridge_fwd_age >> bip->bi_tshift) == 0) {
if (bip->bi_tshift != 0)
bip->bi_tshift--;
age_limit = 1;
}
bfnext = avl_first(&bip->bi_fwd);
while ((bfp = bfnext) != NULL) {
bfnext = AVL_NEXT(&bip->bi_fwd, bfp);
if (!(bfp->bf_flags & BFF_LOCALADDR) &&
(ddi_get_lbolt() - bfp->bf_lastheard) > age_limit) {
ASSERT(bfp->bf_flags & BFF_INTREE);
avl_remove(&bip->bi_fwd, bfp);
bfp->bf_flags &= ~BFF_INTREE;
avl_add(&fwd_scavenge, bfp);
}
}
for (blp = list_head(&bip->bi_links); blp != NULL;
blp = list_next(&bip->bi_links, blp)) {
ldecay = mac_get_ldecay(blp->bl_mh);
if (ldecay >= blp->bl_learns)
blp->bl_learns = 0;
else
atomic_add_int(&blp->bl_learns, -(int)ldecay);
}
rw_exit(&bip->bi_rwlock);
bfnext = avl_first(&fwd_scavenge);
while ((bfp = bfnext) != NULL) {
bfnext = AVL_NEXT(&fwd_scavenge, bfp);
avl_remove(&fwd_scavenge, bfp);
KIINCR(bki_expire);
fwd_unref(bfp);
}
}
mutex_exit(&inst_lock);
avl_destroy(&fwd_scavenge);
rw_enter(&bmac_rwlock, RW_WRITER);
bmnext = list_head(&bmac_list);
while ((bmp = bmnext) != NULL) {
bmnext = list_next(&bmac_list, bmp);
if (bmp->bm_inst != NULL)
continue;
if (bmp->bm_flags & BMF_DLS) {
err = dls_devnet_destroy(bmp->bm_mh, &tmpid, B_FALSE);
ASSERT(err == 0 || err == EBUSY);
if (err == 0)
bmp->bm_flags &= ~BMF_DLS;
}
if (!(bmp->bm_flags & BMF_DLS)) {
err = mac_unregister(bmp->bm_mh);
ASSERT(err == 0 || err == EBUSY);
if (err == 0) {
list_remove(&bmac_list, bmp);
kmem_free(bmp, sizeof (*bmp));
}
}
}
if (list_is_empty(&bmac_list)) {
bridge_timerid = 0;
} else {
bridge_timerid = timeout(bridge_timer, NULL,
bridge_scan_interval);
}
rw_exit(&bmac_rwlock);
}
static int
bridge_open(queue_t *rq, dev_t *devp, int oflag, int sflag, cred_t *credp)
{
bridge_stream_t *bsp;
if (rq->q_ptr != NULL)
return (0);
if (sflag & MODOPEN)
return (EINVAL);
if (getminor(*devp) != 0) {
rq->q_qinfo = &bridge_dld_rinit;
OTHERQ(rq)->q_qinfo = &bridge_dld_winit;
return (dld_open(rq, devp, oflag, sflag, credp));
} else {
if ((bsp = stream_alloc()) == NULL)
return (ENOSR);
rq->q_ptr = WR(rq)->q_ptr = (caddr_t)bsp;
bsp->bs_wq = WR(rq);
*devp = makedevice(getmajor(*devp), bsp->bs_minor);
qprocson(rq);
return (0);
}
}
static int
bridge_close(queue_t *rq, int flags __unused, cred_t *credp __unused)
{
bridge_stream_t *bsp = rq->q_ptr;
bridge_inst_t *bip;
mutex_enter(&stream_ref_lock);
while (bsp->bs_taskq_cnt != 0)
cv_wait(&stream_ref_cv, &stream_ref_lock);
mutex_exit(&stream_ref_lock);
qprocsoff(rq);
if ((bip = bsp->bs_inst) != NULL)
shutdown_inst(bip);
rq->q_ptr = WR(rq)->q_ptr = NULL;
stream_free(bsp);
if (bip != NULL)
bridge_unref(bip);
return (0);
}
static void
bridge_learn(bridge_link_t *blp, const uint8_t *saddr, uint16_t ingress_nick,
uint16_t vlanid)
{
bridge_inst_t *bip = blp->bl_inst;
bridge_fwd_t *bfp, *bfpnew;
int i;
boolean_t replaced = B_FALSE;
if (*saddr & 1)
return;
if ((bfp = fwd_find(bip, saddr, vlanid)) != NULL) {
if (bfp->bf_flags & BFF_LOCALADDR) {
fwd_unref(bfp);
return;
}
if (bfp->bf_trill_nick == ingress_nick) {
for (i = 0; i < bfp->bf_nlinks; i++) {
if (bfp->bf_links[i] == blp) {
bfp->bf_lastheard = ddi_get_lbolt();
fwd_unref(bfp);
return;
}
}
}
}
if (blp->bl_learns >= mac_get_llimit(blp->bl_mh)) {
if (bfp != NULL) {
if (bfp->bf_vcnt == 0)
fwd_delete(bfp);
fwd_unref(bfp);
}
return;
}
atomic_inc_uint(&blp->bl_learns);
if ((bfpnew = fwd_alloc(saddr, 1, ingress_nick)) == NULL) {
if (bfp != NULL)
fwd_unref(bfp);
return;
}
KIINCR(bki_count);
if (bfp != NULL) {
if (bfp->bf_vlanid == vlanid) {
bfpnew->bf_vcnt = bfp->bf_vcnt;
atomic_inc_uint(&blp->bl_learns);
fwd_delete(bfp);
replaced = B_TRUE;
KIINCR(bki_moved);
} else {
bfp->bf_vcnt++;
bfpnew->bf_flags |= BFF_VLANLOCAL;
}
fwd_unref(bfp);
}
bfpnew->bf_links[0] = blp;
bfpnew->bf_nlinks = 1;
atomic_inc_uint(&blp->bl_refs);
if (!fwd_insert(bip, bfpnew))
fwd_free(bfpnew);
else if (!replaced)
KIINCR(bki_source);
}
static mblk_t *
reform_vlan_header(mblk_t *mp, uint16_t vlanid, uint16_t tci, uint16_t pvid)
{
boolean_t source_has_tag = (tci != 0xFFFF);
mblk_t *mpcopy;
size_t mlen, minlen;
struct ether_vlan_header *evh;
int pri;
if (mp == NULL)
return (mp);
DB_CKSUMFLAGS(mp) = 0;
if (!source_has_tag && vlanid == pvid)
return (mp);
pri = VLAN_PRI(tci);
if (source_has_tag && mp->b_band == pri) {
if (vlanid != pvid)
return (mp);
if (pri != 0 && VLAN_ID(tci) == 0)
return (mp);
}
if (MBLKL(mp) < sizeof (struct ether_header)) {
mpcopy = msgpullup(mp, sizeof (struct ether_header));
if (mpcopy == NULL) {
freemsg(mp);
return (NULL);
}
mp = mpcopy;
}
if (DB_REF(mp) > 1 || !IS_P2ALIGNED(mp->b_rptr, sizeof (uint16_t)) ||
(!source_has_tag && MBLKTAIL(mp) < VLAN_INCR)) {
minlen = mlen = MBLKL(mp);
if (!source_has_tag)
minlen += VLAN_INCR;
ASSERT(minlen >= sizeof (struct ether_vlan_header));
if (minlen > 256)
minlen = sizeof (struct ether_vlan_header);
mpcopy = allocb(minlen, BPRI_MED);
if (mpcopy == NULL) {
freemsg(mp);
return (NULL);
}
if (mlen <= minlen) {
bcopy(mp->b_rptr, mpcopy->b_rptr, mlen);
mpcopy->b_wptr += mlen;
mpcopy->b_cont = mp->b_cont;
freeb(mp);
} else {
if (!source_has_tag)
minlen = sizeof (struct ether_header);
bcopy(mp->b_rptr, mpcopy->b_rptr, minlen);
mpcopy->b_wptr += minlen;
mpcopy->b_cont = mp;
mp->b_rptr += minlen;
}
mp = mpcopy;
}
evh = (struct ether_vlan_header *)mp->b_rptr;
if (source_has_tag) {
if (mp->b_band == 0 && vlanid == pvid) {
evh->ether_tpid = evh->ether_type;
mlen = MBLKL(mp);
if (mlen > sizeof (struct ether_vlan_header))
ovbcopy(mp->b_rptr +
sizeof (struct ether_vlan_header),
mp->b_rptr + sizeof (struct ether_header),
mlen - sizeof (struct ether_vlan_header));
mp->b_wptr -= VLAN_INCR;
} else {
if (vlanid == pvid)
vlanid = VLAN_ID_NONE;
tci = VLAN_TCI(mp->b_band, ETHER_CFI, vlanid);
evh->ether_tci = htons(tci);
}
} else {
mlen = MBLKL(mp);
if (mlen > sizeof (struct ether_header))
ovbcopy(mp->b_rptr + sizeof (struct ether_header),
mp->b_rptr + sizeof (struct ether_vlan_header),
mlen - sizeof (struct ether_header));
mp->b_wptr += VLAN_INCR;
ASSERT(mp->b_wptr <= DB_LIM(mp));
if (vlanid == pvid)
vlanid = VLAN_ID_NONE;
tci = VLAN_TCI(mp->b_band, ETHER_CFI, vlanid);
evh->ether_type = evh->ether_tpid;
evh->ether_tpid = htons(ETHERTYPE_VLAN);
evh->ether_tci = htons(tci);
}
return (mp);
}
static void
update_header(mblk_t *mp, mac_header_info_t *hdr_info, boolean_t striphdr)
{
if (hdr_info->mhi_bindsap == ETHERTYPE_VLAN) {
struct ether_vlan_header *evhp;
uint16_t ether_type;
evhp = (struct ether_vlan_header *)mp->b_rptr;
hdr_info->mhi_istagged = B_TRUE;
hdr_info->mhi_tci = ntohs(evhp->ether_tci);
if (striphdr) {
ether_type = ntohs(evhp->ether_type);
hdr_info->mhi_origsap = ether_type;
hdr_info->mhi_bindsap = (ether_type > ETHERMTU) ?
ether_type : DLS_SAP_LLC;
mp->b_rptr = (uchar_t *)(evhp + 1);
}
} else {
hdr_info->mhi_istagged = B_FALSE;
hdr_info->mhi_tci = VLAN_ID_NONE;
if (striphdr)
mp->b_rptr += sizeof (struct ether_header);
}
}
static boolean_t
bridge_can_send(bridge_link_t *blp, uint16_t vlanid)
{
ASSERT(vlanid != VLAN_ID_NONE);
if (blp->bl_flags & BLF_DELETED)
return (B_FALSE);
if (blp->bl_trilldata == NULL && blp->bl_state != BLS_FORWARDING)
return (B_FALSE);
return (BRIDGE_VLAN_ISSET(blp, vlanid) && BRIDGE_AF_ISSET(blp, vlanid));
}
static mblk_t *
bridge_forward(bridge_link_t *blp, mac_header_info_t *hdr_info, mblk_t *mp,
uint16_t vlanid, uint16_t tci, boolean_t from_trill, boolean_t is_xmit)
{
mblk_t *mpsend, *mpcopy;
bridge_inst_t *bip = blp->bl_inst;
bridge_link_t *blpsend, *blpnext;
bridge_fwd_t *bfp;
uint_t i;
boolean_t selfseen = B_FALSE;
void *tdp;
const uint8_t *daddr = hdr_info->mhi_daddr;
if (daddr[0] == 1 && daddr[1] == 0x80 && daddr[2] == 0xc2 &&
daddr[3] == 0 && daddr[4] == 0 && (daddr[5] & 0xf0) == 0) {
if (from_trill) {
freemsg(mp);
mp = NULL;
}
return (mp);
}
if ((bfp = fwd_find(bip, daddr, vlanid)) != NULL) {
if (bfp->bf_trill_nick != RBRIDGE_NICKNAME_NONE) {
if (from_trill) {
freemsg(mp);
return (NULL);
}
mutex_enter(&blp->bl_trilllock);
if ((tdp = blp->bl_trilldata) != NULL) {
blp->bl_trillthreads++;
mutex_exit(&blp->bl_trilllock);
update_header(mp, hdr_info, B_FALSE);
mp = reform_vlan_header(mp, vlanid, tci, 0);
if (mp == NULL) {
KIINCR(bki_drops);
goto done;
}
trill_encap_fn(tdp, blp, hdr_info, mp,
bfp->bf_trill_nick);
done:
mutex_enter(&blp->bl_trilllock);
if (--blp->bl_trillthreads == 0 &&
blp->bl_trilldata == NULL)
cv_broadcast(&blp->bl_trillwait);
}
mutex_exit(&blp->bl_trilllock);
if (tdp == NULL) {
freemsg(mp);
fwd_delete(bfp);
}
fwd_unref(bfp);
return (NULL);
}
for (i = 0; i < bfp->bf_nlinks; i++) {
blpsend = bfp->bf_links[i];
if (blpsend == blp)
selfseen = B_TRUE;
else if (bridge_can_send(blpsend, vlanid))
break;
}
while (i < bfp->bf_nlinks) {
blpsend = bfp->bf_links[i];
for (i++; i < bfp->bf_nlinks; i++) {
blpnext = bfp->bf_links[i];
if (blpnext == blp)
selfseen = B_TRUE;
else if (bridge_can_send(blpnext, vlanid))
break;
}
if (i == bfp->bf_nlinks && !selfseen) {
mpsend = mp;
mp = NULL;
} else {
mpsend = copymsg(mp);
}
mpsend = reform_vlan_header(mpsend, vlanid, tci,
blpsend->bl_pvid);
if (mpsend == NULL) {
KIINCR(bki_drops);
continue;
}
KIINCR(bki_forwards);
if (bfp->bf_flags & BFF_LOCALADDR) {
mac_rx_common(blpsend->bl_mh, NULL, mpsend);
} else {
KLPINCR(blpsend, bkl_xmit);
mpsend = mac_ring_tx(blpsend->bl_mh, NULL,
mpsend);
freemsg(mpsend);
}
}
if (mp != NULL && is_xmit && (bfp->bf_flags & BFF_LOCALADDR)) {
mac_rx_common(blp->bl_mh, NULL, mp);
mp = NULL;
}
fwd_unref(bfp);
} else {
if (!from_trill && blp->bl_trilldata != NULL) {
mutex_enter(&blp->bl_trilllock);
if ((tdp = blp->bl_trilldata) != NULL) {
blp->bl_trillthreads++;
mutex_exit(&blp->bl_trilllock);
if ((mpsend = copymsg(mp)) != NULL) {
update_header(mpsend,
hdr_info, B_FALSE);
mpsend = reform_vlan_header(mpsend,
vlanid, tci, 0);
if (mpsend == NULL) {
KIINCR(bki_drops);
} else {
trill_encap_fn(tdp, blp,
hdr_info, mpsend,
RBRIDGE_NICKNAME_NONE);
}
}
mutex_enter(&blp->bl_trilllock);
if (--blp->bl_trillthreads == 0 &&
blp->bl_trilldata == NULL)
cv_broadcast(&blp->bl_trillwait);
}
mutex_exit(&blp->bl_trilllock);
}
rw_enter(&bip->bi_rwlock, RW_READER);
for (blpnext = list_head(&bip->bi_links); blpnext != NULL;
blpnext = list_next(&bip->bi_links, blpnext)) {
if (blpnext == blp)
selfseen = B_TRUE;
else if (bridge_can_send(blpnext, vlanid))
break;
}
if (blpnext != NULL)
atomic_inc_uint(&blpnext->bl_refs);
rw_exit(&bip->bi_rwlock);
while ((blpsend = blpnext) != NULL) {
rw_enter(&bip->bi_rwlock, RW_READER);
for (blpnext = list_next(&bip->bi_links, blpsend);
blpnext != NULL;
blpnext = list_next(&bip->bi_links, blpnext)) {
if (blpnext == blp)
selfseen = B_TRUE;
else if (bridge_can_send(blpnext, vlanid))
break;
}
if (blpnext != NULL)
atomic_inc_uint(&blpnext->bl_refs);
rw_exit(&bip->bi_rwlock);
if (blpnext == NULL && !selfseen) {
mpsend = mp;
mp = NULL;
} else {
mpsend = copymsg(mp);
}
mpsend = reform_vlan_header(mpsend, vlanid, tci,
blpsend->bl_pvid);
if (mpsend == NULL) {
KIINCR(bki_drops);
continue;
}
if (hdr_info->mhi_dsttype == MAC_ADDRTYPE_UNICAST)
KIINCR(bki_unknown);
else
KIINCR(bki_mbcast);
KLPINCR(blpsend, bkl_xmit);
if ((mpcopy = copymsg(mpsend)) != NULL) {
mac_rx_common(blpsend->bl_mh, NULL, mpcopy);
}
mpsend = mac_ring_tx(blpsend->bl_mh, NULL, mpsend);
freemsg(mpsend);
link_unref(blpsend);
}
}
return (mp);
}
static boolean_t
bridge_get_vlan(bridge_link_t *blp, mac_header_info_t *hdr_info, mblk_t *mp,
uint16_t *vlanidp, uint16_t *tcip)
{
uint16_t tci, vlanid;
if (hdr_info->mhi_bindsap == ETHERTYPE_VLAN) {
ptrdiff_t tpos = offsetof(struct ether_vlan_header, ether_tci);
ptrdiff_t mlen;
while (mp != NULL) {
mlen = MBLKL(mp);
if (mlen > tpos && mlen > 0)
break;
tpos -= mlen;
mp = mp->b_cont;
}
if (mp == NULL)
return (B_FALSE);
tci = mp->b_rptr[tpos] << 8;
if (++tpos >= mlen) {
do {
mp = mp->b_cont;
} while (mp != NULL && MBLKL(mp) == 0);
if (mp == NULL)
return (B_FALSE);
tpos = 0;
}
tci |= mp->b_rptr[tpos];
vlanid = VLAN_ID(tci);
if (VLAN_CFI(tci) != ETHER_CFI || vlanid > VLAN_ID_MAX)
return (B_FALSE);
if (vlanid == VLAN_ID_NONE || vlanid == blp->bl_pvid)
goto input_no_vlan;
if (!BRIDGE_VLAN_ISSET(blp, vlanid))
return (B_FALSE);
} else {
tci = 0xFFFF;
input_no_vlan:
if ((vlanid = blp->bl_pvid) == VLAN_ID_NONE)
return (B_FALSE);
}
*tcip = tci;
*vlanidp = vlanid;
return (B_TRUE);
}
static void
bridge_notify_cb(void *arg, mac_notify_type_t note_type)
{
bridge_link_t *blp = arg;
switch (note_type) {
case MAC_NOTE_UNICST:
bridge_new_unicst(blp);
break;
case MAC_NOTE_SDU_SIZE: {
uint_t maxsdu;
bridge_inst_t *bip = blp->bl_inst;
bridge_mac_t *bmp = bip->bi_mac;
boolean_t notify = B_FALSE;
mblk_t *mlist = NULL;
mac_sdu_get(blp->bl_mh, NULL, &maxsdu);
rw_enter(&bip->bi_rwlock, RW_READER);
if (list_prev(&bip->bi_links, blp) == NULL &&
list_next(&bip->bi_links, blp) == NULL) {
notify = (maxsdu != bmp->bm_maxsdu);
bmp->bm_maxsdu = maxsdu;
}
blp->bl_maxsdu = maxsdu;
if (maxsdu != bmp->bm_maxsdu)
link_sdu_fail(blp, B_TRUE, &mlist);
else if (notify)
(void) mac_maxsdu_update(bmp->bm_mh, maxsdu);
rw_exit(&bip->bi_rwlock);
send_up_messages(bip, mlist);
break;
}
}
}
static void
bridge_recv_cb(mac_handle_t mh, mac_resource_handle_t rsrc, mblk_t *mpnext)
{
mblk_t *mp, *mpcopy;
bridge_link_t *blp = (bridge_link_t *)mh;
bridge_inst_t *bip = blp->bl_inst;
bridge_mac_t *bmp = bip->bi_mac;
mac_header_info_t hdr_info;
uint16_t vlanid, tci;
boolean_t trillmode = B_FALSE;
KIINCR(bki_recv);
KLINCR(bkl_recv);
if (blp->bl_trilldata != NULL) {
void *tdp;
mblk_t *newhead;
mblk_t *tail = NULL;
mutex_enter(&blp->bl_trilllock);
if ((tdp = blp->bl_trilldata) != NULL) {
blp->bl_trillthreads++;
mutex_exit(&blp->bl_trilllock);
trillmode = B_TRUE;
newhead = mpnext;
while ((mp = mpnext) != NULL) {
boolean_t raw_isis, bridge_group;
mpnext = mp->b_next;
if (mac_header_info(blp->bl_mh, mp,
&hdr_info) != 0) {
tail = mp;
continue;
}
raw_isis = bridge_group = B_FALSE;
if (hdr_info.mhi_dsttype ==
MAC_ADDRTYPE_MULTICAST) {
if (memcmp(hdr_info.mhi_daddr,
all_isis_rbridges, ETHERADDRL) == 0)
raw_isis = B_TRUE;
else if (memcmp(hdr_info.mhi_daddr,
bridge_group_address, ETHERADDRL) ==
0)
bridge_group = B_TRUE;
}
if (!raw_isis && !bridge_group &&
hdr_info.mhi_bindsap != ETHERTYPE_TRILL &&
(hdr_info.mhi_bindsap != ETHERTYPE_VLAN ||
((struct ether_vlan_header *)mp->b_rptr)->
ether_type != htons(ETHERTYPE_TRILL))) {
tail = mp;
continue;
}
if (tail != NULL)
tail->b_next = mpnext;
mp->b_next = NULL;
if (mp == newhead)
newhead = mpnext;
mac_trill_snoop(blp->bl_mh, mp);
update_header(mp, &hdr_info, B_TRUE);
if (raw_isis || bridge_group) {
size_t msglen = msgdsize(mp);
if (msglen > hdr_info.mhi_origsap) {
(void) adjmsg(mp,
hdr_info.mhi_origsap -
msglen);
} else if (msglen <
hdr_info.mhi_origsap) {
freemsg(mp);
continue;
}
}
trill_recv_fn(tdp, blp, rsrc, mp, &hdr_info);
}
mpnext = newhead;
mutex_enter(&blp->bl_trilllock);
if (--blp->bl_trillthreads == 0 &&
blp->bl_trilldata == NULL)
cv_broadcast(&blp->bl_trillwait);
}
mutex_exit(&blp->bl_trilllock);
if (mpnext == NULL)
return;
}
if (trillmode) {
if (!(blp->bl_flags & BLF_TRILLACTIVE) ||
(blp->bl_flags & BLF_SDUFAIL)) {
mac_rx_common(blp->bl_mh, rsrc, mpnext);
return;
}
} else {
if (blp->bl_state == BLS_BLOCKLISTEN) {
mac_rx_common(blp->bl_mh, rsrc, mpnext);
return;
}
}
if (!trillmode && blp->bl_state == BLS_FORWARDING &&
(bmp->bm_flags & BMF_STARTED) &&
(mp = copymsgchain(mpnext)) != NULL) {
mac_rx(bmp->bm_mh, NULL, mp);
}
while ((mp = mpnext) != NULL) {
mpnext = mp->b_next;
mp->b_next = NULL;
if (mac_header_info(blp->bl_mh, mp, &hdr_info) != 0 ||
(hdr_info.mhi_saddr[0] & 1) != 0) {
KIINCR(bki_drops);
KLINCR(bkl_drops);
mac_rx_common(blp->bl_mh, rsrc, mp);
continue;
}
if (!bridge_get_vlan(blp, &hdr_info, mp, &vlanid, &tci) ||
!BRIDGE_AF_ISSET(blp, vlanid)) {
mac_rx_common(blp->bl_mh, rsrc, mp);
continue;
}
if (trillmode) {
if (memcmp(hdr_info.mhi_daddr, all_esadi_rbridges,
ETHERADDRL) == 0) {
mac_rx_common(blp->bl_mh, rsrc, mp);
continue;
}
if ((bmp->bm_flags & BMF_STARTED) &&
(mpcopy = copymsg(mp)) != NULL)
mac_rx(bmp->bm_mh, NULL, mpcopy);
}
bridge_learn(blp, hdr_info.mhi_saddr, RBRIDGE_NICKNAME_NONE,
vlanid);
if (trillmode || blp->bl_state == BLS_FORWARDING) {
mp = bridge_forward(blp, &hdr_info, mp, vlanid, tci,
B_FALSE, B_FALSE);
}
if (mp != NULL)
mac_rx_common(blp->bl_mh, rsrc, mp);
}
}
static mblk_t *
bridge_xmit_cb(mac_handle_t mh, mac_ring_handle_t rh, mblk_t *mpnext)
{
bridge_link_t *blp = (bridge_link_t *)mh;
bridge_inst_t *bip = blp->bl_inst;
bridge_mac_t *bmp = bip->bi_mac;
mac_header_info_t hdr_info;
uint16_t vlanid, tci;
mblk_t *mp, *mpcopy;
boolean_t trillmode;
trillmode = blp->bl_trilldata != NULL;
if ((!trillmode && blp->bl_state == BLS_BLOCKLISTEN) ||
(trillmode &&
(!(blp->bl_flags & BLF_TRILLACTIVE) ||
(blp->bl_flags & BLF_SDUFAIL)))) {
KIINCR(bki_sent);
KLINCR(bkl_xmit);
mp = mac_ring_tx(blp->bl_mh, rh, mpnext);
return (mp);
}
if (!trillmode && blp->bl_state == BLS_FORWARDING &&
(bmp->bm_flags & BMF_STARTED) &&
(mp = copymsgchain(mpnext)) != NULL) {
mac_rx(bmp->bm_mh, NULL, mp);
}
while ((mp = mpnext) != NULL) {
mpnext = mp->b_next;
mp->b_next = NULL;
if (mac_header_info(blp->bl_mh, mp, &hdr_info) != 0) {
freemsg(mp);
continue;
}
if (!bridge_get_vlan(blp, &hdr_info, mp, &vlanid, &tci) ||
!BRIDGE_AF_ISSET(blp, vlanid)) {
freemsg(mp);
continue;
}
if (trillmode && (bmp->bm_flags & BMF_STARTED) &&
(mpcopy = copymsg(mp)) != NULL) {
mac_rx(bmp->bm_mh, NULL, mpcopy);
}
bridge_learn(blp, hdr_info.mhi_saddr, RBRIDGE_NICKNAME_NONE,
vlanid);
if (trillmode || blp->bl_state == BLS_FORWARDING) {
mp = bridge_forward(blp, &hdr_info, mp, vlanid, tci,
B_FALSE, B_TRUE);
}
if (mp != NULL) {
mp = mac_ring_tx(blp->bl_mh, rh, mp);
if (mp == NULL) {
KIINCR(bki_sent);
KLINCR(bkl_xmit);
}
}
if (mp != NULL) {
mp->b_next = mpnext;
break;
}
}
return (mp);
}
void
bridge_trill_decaps(bridge_link_t *blp, mblk_t *mp, uint16_t ingress_nick)
{
mac_header_info_t hdr_info;
uint16_t vlanid, tci;
bridge_inst_t *bip = blp->bl_inst;
mblk_t *mpcopy;
if (mac_header_info(blp->bl_mh, mp, &hdr_info) != 0) {
freemsg(mp);
return;
}
if (hdr_info.mhi_bindsap == ETHERTYPE_VLAN) {
struct ether_vlan_header *evhp;
evhp = (struct ether_vlan_header *)mp->b_rptr;
tci = ntohs(evhp->ether_tci);
vlanid = VLAN_ID(tci);
} else {
DTRACE_PROBE3(bridge__trill__decaps__novlan, bridge_link_t *,
blp, mblk_t *, mp, uint16_t, ingress_nick);
freemsg(mp);
return;
}
bridge_learn(blp, hdr_info.mhi_saddr, ingress_nick, vlanid);
mp = bridge_forward(blp, &hdr_info, mp, vlanid, tci, B_TRUE, B_TRUE);
if (mp != NULL) {
if (bridge_can_send(blp, vlanid)) {
if ((mpcopy = copymsg(mp)) != NULL)
mac_rx_common(blp->bl_mh, NULL, mpcopy);
mp = mac_ring_tx(blp->bl_mh, NULL, mp);
}
if (mp == NULL) {
KIINCR(bki_sent);
KLINCR(bkl_xmit);
} else {
freemsg(mp);
}
}
}
mblk_t *
bridge_trill_output(bridge_link_t *blp, mblk_t *mp)
{
bridge_inst_t *bip = blp->bl_inst;
mac_trill_snoop(blp->bl_mh, mp);
mp = mac_ring_tx(blp->bl_mh, NULL, mp);
if (mp == NULL) {
KIINCR(bki_sent);
KLINCR(bkl_xmit);
}
return (mp);
}
void
bridge_trill_setvlans(bridge_link_t *blp, const uint8_t *arr)
{
int i;
uint_t newflags = 0;
for (i = 0; i < BRIDGE_VLAN_ARR_SIZE; i++) {
if ((blp->bl_afs[i] = arr[i]) != 0)
newflags = BLF_TRILLACTIVE;
}
blp->bl_flags = (blp->bl_flags & ~BLF_TRILLACTIVE) | newflags;
}
void
bridge_trill_flush(bridge_link_t *blp, uint16_t vlan, boolean_t dotrill)
{
bridge_inst_t *bip = blp->bl_inst;
bridge_fwd_t *bfp, *bfnext;
avl_tree_t fwd_scavenge;
int i;
_NOTE(ARGUNUSED(vlan));
avl_create(&fwd_scavenge, fwd_compare, sizeof (bridge_fwd_t),
offsetof(bridge_fwd_t, bf_node));
rw_enter(&bip->bi_rwlock, RW_WRITER);
bfnext = avl_first(&bip->bi_fwd);
while ((bfp = bfnext) != NULL) {
bfnext = AVL_NEXT(&bip->bi_fwd, bfp);
if (bfp->bf_flags & BFF_LOCALADDR)
continue;
if (dotrill) {
if (bfp->bf_trill_nick == RBRIDGE_NICKNAME_NONE)
continue;
} else {
if (bfp->bf_trill_nick != RBRIDGE_NICKNAME_NONE)
continue;
for (i = 0; i < bfp->bf_nlinks; i++) {
if (bfp->bf_links[i] == blp)
break;
}
if (i >= bfp->bf_nlinks)
continue;
}
ASSERT(bfp->bf_flags & BFF_INTREE);
avl_remove(&bip->bi_fwd, bfp);
bfp->bf_flags &= ~BFF_INTREE;
avl_add(&fwd_scavenge, bfp);
}
rw_exit(&bip->bi_rwlock);
bfnext = avl_first(&fwd_scavenge);
while ((bfp = bfnext) != NULL) {
bfnext = AVL_NEXT(&fwd_scavenge, bfp);
avl_remove(&fwd_scavenge, bfp);
fwd_unref(bfp);
}
avl_destroy(&fwd_scavenge);
}
static void
bridge_ref_cb(mac_handle_t mh, boolean_t hold)
{
bridge_link_t *blp = (bridge_link_t *)mh;
if (hold)
atomic_inc_uint(&blp->bl_refs);
else
link_unref(blp);
}
static link_state_t
bridge_ls_cb(mac_handle_t mh, link_state_t newls)
{
bridge_link_t *blp = (bridge_link_t *)mh;
bridge_link_t *blcmp;
bridge_inst_t *bip;
bridge_mac_t *bmp;
if (newls != LINK_STATE_DOWN && blp->bl_linkstate != LINK_STATE_DOWN ||
(blp->bl_flags & (BLF_DELETED|BLF_SDUFAIL))) {
blp->bl_linkstate = newls;
return (newls);
}
bip = blp->bl_inst;
rw_enter(&bip->bi_rwlock, RW_WRITER);
for (blcmp = list_head(&bip->bi_links); blcmp != NULL;
blcmp = list_next(&bip->bi_links, blcmp)) {
if (blcmp != blp &&
!(blcmp->bl_flags & (BLF_DELETED|BLF_SDUFAIL)) &&
blcmp->bl_linkstate != LINK_STATE_DOWN)
break;
}
if (blcmp != NULL) {
blp->bl_linkstate = newls;
newls = LINK_STATE_UP;
} else if (blp->bl_linkstate != newls) {
blp->bl_linkstate = newls;
for (blcmp = list_head(&bip->bi_links); blcmp != NULL;
blcmp = list_next(&bip->bi_links, blcmp)) {
if (blcmp != blp && !(blcmp->bl_flags & BLF_DELETED))
mac_link_redo(blcmp->bl_mh, newls);
}
bmp = bip->bi_mac;
if ((bmp->bm_linkstate = newls) != LINK_STATE_DOWN)
bmp->bm_linkstate = LINK_STATE_UP;
mac_link_redo(bmp->bm_mh, bmp->bm_linkstate);
}
rw_exit(&bip->bi_rwlock);
return (newls);
}
static void
bridge_add_link(void *arg)
{
mblk_t *mp = arg;
bridge_stream_t *bsp;
bridge_inst_t *bip, *bipt;
bridge_mac_t *bmp;
datalink_id_t linkid;
int err;
mac_handle_t mh;
uint_t maxsdu;
bridge_link_t *blp = NULL, *blpt;
const mac_info_t *mip;
boolean_t macopen = B_FALSE;
char linkname[MAXLINKNAMELEN];
char kstatname[KSTAT_STRLEN];
int i;
link_state_t linkstate;
mblk_t *mlist;
bsp = (bridge_stream_t *)mp->b_next;
mp->b_next = NULL;
bip = bsp->bs_inst;
linkid = *(datalink_id_t *)mp->b_cont->b_rptr;
mutex_enter(&inst_lock);
for (bipt = list_head(&inst_list); bipt != NULL;
bipt = list_next(&inst_list, bipt)) {
rw_enter(&bipt->bi_rwlock, RW_READER);
for (blpt = list_head(&bipt->bi_links); blpt != NULL;
blpt = list_next(&bipt->bi_links, blpt)) {
if (linkid == blpt->bl_linkid)
break;
}
rw_exit(&bipt->bi_rwlock);
if (blpt != NULL)
break;
}
mutex_exit(&inst_lock);
if (bipt != NULL) {
err = EBUSY;
goto fail;
}
if ((err = mac_open_by_linkid(linkid, &mh)) != 0)
goto fail;
macopen = B_TRUE;
mip = mac_info(mh);
if (mip->mi_media != DL_ETHER) {
err = ENOTSUP;
goto fail;
}
mac_sdu_get(mh, NULL, &maxsdu);
bmp = bip->bi_mac;
if (list_is_empty(&bip->bi_links)) {
bmp->bm_maxsdu = maxsdu;
(void) mac_maxsdu_update(bmp->bm_mh, maxsdu);
}
i = MBLKL(mp->b_cont) - sizeof (datalink_id_t);
if (i < 0 || i >= MAXLINKNAMELEN)
i = MAXLINKNAMELEN - 1;
bcopy(mp->b_cont->b_rptr + sizeof (datalink_id_t), linkname, i);
linkname[i] = '\0';
(void) snprintf(kstatname, sizeof (kstatname), "%s-%s", bip->bi_name,
linkname);
if ((blp = kmem_zalloc(sizeof (*blp), KM_NOSLEEP)) == NULL) {
err = ENOMEM;
goto fail;
}
blp->bl_lfailmp = allocb(sizeof (bridge_ctl_t), BPRI_MED);
if (blp->bl_lfailmp == NULL) {
kmem_free(blp, sizeof (*blp));
blp = NULL;
err = ENOMEM;
goto fail;
}
blp->bl_refs = 1;
atomic_inc_uint(&bip->bi_refs);
blp->bl_inst = bip;
blp->bl_mh = mh;
blp->bl_linkid = linkid;
blp->bl_maxsdu = maxsdu;
cv_init(&blp->bl_trillwait, NULL, CV_DRIVER, NULL);
mutex_init(&blp->bl_trilllock, NULL, MUTEX_DRIVER, NULL);
(void) memset(blp->bl_afs, 0xff, sizeof (blp->bl_afs));
err = mac_client_open(mh, &blp->bl_mch, kstatname, 0);
if (err != 0)
goto fail;
blp->bl_flags |= BLF_CLIENT_OPEN;
err = mac_margin_add(mh, &blp->bl_margin, B_TRUE);
if (err != 0)
goto fail;
blp->bl_flags |= BLF_MARGIN_ADDED;
blp->bl_mnh = mac_notify_add(mh, bridge_notify_cb, blp);
err = mac_bridge_set(mh, (mac_handle_t)blp);
if (err != 0)
goto fail;
blp->bl_flags |= BLF_SET_BRIDGE;
err = mac_promisc_add(blp->bl_mch, MAC_CLIENT_PROMISC_ALL, NULL,
blp, &blp->bl_mphp, MAC_PROMISC_FLAGS_NO_TX_LOOP);
if (err != 0)
goto fail;
blp->bl_flags |= BLF_PROM_ADDED;
bridge_new_unicst(blp);
blp->bl_ksp = kstat_setup((kstat_named_t *)&blp->bl_kstats,
link_kstats_list, Dim(link_kstats_list), kstatname);
rw_enter(&bip->bi_rwlock, RW_WRITER);
list_insert_tail(&bip->bi_links, blp);
blp->bl_flags |= BLF_LINK_ADDED;
mlist = NULL;
if (maxsdu != bmp->bm_maxsdu)
link_sdu_fail(blp, B_TRUE, &mlist);
rw_exit(&bip->bi_rwlock);
send_up_messages(bip, mlist);
linkstate = mac_stat_get(mh, MAC_STAT_LOWLINK_STATE);
blp->bl_linkstate = LINK_STATE_DOWN;
mac_link_update(mh, linkstate);
miocack(bsp->bs_wq, mp, 0, 0);
stream_unref(bsp);
return;
fail:
if (blp == NULL) {
if (macopen)
mac_close(mh);
} else {
link_shutdown(blp);
}
miocnak(bsp->bs_wq, mp, 0, err);
stream_unref(bsp);
}
static void
bridge_rem_link(void *arg)
{
mblk_t *mp = arg;
bridge_stream_t *bsp;
bridge_inst_t *bip;
bridge_mac_t *bmp;
datalink_id_t linkid;
bridge_link_t *blp, *blsave;
boolean_t found;
mblk_t *mlist;
bsp = (bridge_stream_t *)mp->b_next;
mp->b_next = NULL;
bip = bsp->bs_inst;
linkid = *(datalink_id_t *)mp->b_cont->b_rptr;
rw_enter(&bip->bi_rwlock, RW_READER);
found = B_FALSE;
for (blp = list_head(&bip->bi_links); blp != NULL;
blp = list_next(&bip->bi_links, blp)) {
if (blp->bl_linkid == linkid &&
!(blp->bl_flags & BLF_DELETED)) {
blp->bl_flags |= BLF_DELETED;
(void) ddi_taskq_dispatch(bridge_taskq, link_shutdown,
blp, DDI_SLEEP);
found = B_TRUE;
break;
}
}
if (blp != NULL && blp->bl_linkstate != LINK_STATE_DOWN) {
for (blp = list_head(&bip->bi_links); blp != NULL;
blp = list_next(&bip->bi_links, blp)) {
if (blp->bl_linkstate != LINK_STATE_DOWN &&
!(blp->bl_flags & (BLF_DELETED|BLF_SDUFAIL)))
break;
}
if (blp == NULL) {
for (blp = list_head(&bip->bi_links); blp != NULL;
blp = list_next(&bip->bi_links, blp)) {
if (!(blp->bl_flags & BLF_DELETED))
mac_link_redo(blp->bl_mh,
LINK_STATE_DOWN);
}
bmp = bip->bi_mac;
bmp->bm_linkstate = LINK_STATE_DOWN;
mac_link_redo(bmp->bm_mh, LINK_STATE_DOWN);
}
}
blsave = NULL;
for (blp = list_head(&bip->bi_links); blp != NULL;
blp = list_next(&bip->bi_links, blp)) {
if (!(blp->bl_flags & BLF_DELETED)) {
if (blsave == NULL)
blsave = blp;
else
break;
}
}
mlist = NULL;
bmp = bip->bi_mac;
if (blsave != NULL && blp == NULL &&
blsave->bl_maxsdu != bmp->bm_maxsdu) {
bmp->bm_maxsdu = blsave->bl_maxsdu;
(void) mac_maxsdu_update(bmp->bm_mh, blsave->bl_maxsdu);
link_sdu_fail(blsave, B_FALSE, &mlist);
}
rw_exit(&bip->bi_rwlock);
send_up_messages(bip, mlist);
if (found)
miocack(bsp->bs_wq, mp, 0, 0);
else
miocnak(bsp->bs_wq, mp, 0, ENOENT);
stream_unref(bsp);
}
static bridge_link_t *
enter_link(bridge_inst_t *bip, datalink_id_t linkid)
{
bridge_link_t *blp;
rw_enter(&bip->bi_rwlock, RW_READER);
for (blp = list_head(&bip->bi_links); blp != NULL;
blp = list_next(&bip->bi_links, blp)) {
if (blp->bl_linkid == linkid && !(blp->bl_flags & BLF_DELETED))
break;
}
return (blp);
}
static void
bridge_ioctl(queue_t *wq, mblk_t *mp)
{
bridge_stream_t *bsp = wq->q_ptr;
bridge_inst_t *bip;
struct iocblk *iop;
int rc = EINVAL;
int len = 0;
bridge_link_t *blp;
cred_t *cr;
iop = (struct iocblk *)mp->b_rptr;
if ((cr = msg_getcred(mp, NULL)) == NULL)
cr = iop->ioc_cr;
if (cr != NULL && secpolicy_net_config(cr, B_FALSE) != 0) {
miocnak(wq, mp, 0, EPERM);
return;
}
switch (iop->ioc_cmd) {
case BRIOC_NEWBRIDGE: {
bridge_newbridge_t *bnb;
if (bsp->bs_inst != NULL ||
(rc = miocpullup(mp, sizeof (bridge_newbridge_t))) != 0)
break;
bnb = (bridge_newbridge_t *)mp->b_cont->b_rptr;
bnb->bnb_name[MAXNAMELEN-1] = '\0';
rc = bridge_create(bnb->bnb_linkid, bnb->bnb_name, &bip, cr);
if (rc != 0)
break;
rw_enter(&bip->bi_rwlock, RW_WRITER);
if (bip->bi_control != NULL) {
rw_exit(&bip->bi_rwlock);
bridge_unref(bip);
rc = EBUSY;
} else {
atomic_inc_uint(&bip->bi_refs);
bsp->bs_inst = bip;
bip->bi_control = bsp;
rw_exit(&bip->bi_rwlock);
rc = 0;
}
break;
}
case BRIOC_ADDLINK:
if ((bip = bsp->bs_inst) == NULL ||
(rc = miocpullup(mp, sizeof (datalink_id_t))) != 0)
break;
mp->b_next = (mblk_t *)bsp;
stream_ref(bsp);
(void) ddi_taskq_dispatch(bridge_taskq, bridge_add_link, mp,
DDI_SLEEP);
return;
case BRIOC_REMLINK:
if ((bip = bsp->bs_inst) == NULL ||
(rc = miocpullup(mp, sizeof (datalink_id_t))) != 0)
break;
mp->b_next = (mblk_t *)bsp;
stream_ref(bsp);
(void) ddi_taskq_dispatch(bridge_taskq, bridge_rem_link, mp,
DDI_SLEEP);
return;
case BRIOC_SETSTATE: {
bridge_setstate_t *bss;
if ((bip = bsp->bs_inst) == NULL ||
(rc = miocpullup(mp, sizeof (*bss))) != 0)
break;
bss = (bridge_setstate_t *)mp->b_cont->b_rptr;
if ((blp = enter_link(bip, bss->bss_linkid)) == NULL) {
rc = ENOENT;
} else {
rc = 0;
blp->bl_state = bss->bss_state;
}
rw_exit(&bip->bi_rwlock);
break;
}
case BRIOC_SETPVID: {
bridge_setpvid_t *bsv;
if ((bip = bsp->bs_inst) == NULL ||
(rc = miocpullup(mp, sizeof (*bsv))) != 0)
break;
bsv = (bridge_setpvid_t *)mp->b_cont->b_rptr;
if (bsv->bsv_vlan > VLAN_ID_MAX)
break;
if ((blp = enter_link(bip, bsv->bsv_linkid)) == NULL) {
rc = ENOENT;
} else if (blp->bl_pvid == bsv->bsv_vlan) {
rc = 0;
} else {
rc = 0;
BRIDGE_VLAN_CLR(blp, blp->bl_pvid);
blp->bl_pvid = bsv->bsv_vlan;
if (blp->bl_pvid != 0)
BRIDGE_VLAN_SET(blp, blp->bl_pvid);
}
rw_exit(&bip->bi_rwlock);
break;
}
case BRIOC_VLANENAB: {
bridge_vlanenab_t *bve;
if ((bip = bsp->bs_inst) == NULL ||
(rc = miocpullup(mp, sizeof (*bve))) != 0)
break;
bve = (bridge_vlanenab_t *)mp->b_cont->b_rptr;
if (bve->bve_vlan > VLAN_ID_MAX)
break;
if ((blp = enter_link(bip, bve->bve_linkid)) == NULL) {
rc = ENOENT;
} else {
rc = 0;
if (bve->bve_vlan == 0) {
(void) memset(blp->bl_vlans,
bve->bve_onoff ? ~0 : 0,
sizeof (blp->bl_vlans));
BRIDGE_VLAN_CLR(blp, 0);
if (blp->bl_pvid != 0)
BRIDGE_VLAN_SET(blp, blp->bl_pvid);
} else if (bve->bve_vlan == blp->bl_pvid) {
rc = EINVAL;
} else if (bve->bve_onoff) {
BRIDGE_VLAN_SET(blp, bve->bve_vlan);
} else {
BRIDGE_VLAN_CLR(blp, bve->bve_vlan);
}
}
rw_exit(&bip->bi_rwlock);
break;
}
case BRIOC_FLUSHFWD: {
bridge_flushfwd_t *bff;
bridge_fwd_t *bfp, *bfnext;
avl_tree_t fwd_scavenge;
int i;
if ((bip = bsp->bs_inst) == NULL ||
(rc = miocpullup(mp, sizeof (*bff))) != 0)
break;
bff = (bridge_flushfwd_t *)mp->b_cont->b_rptr;
rw_enter(&bip->bi_rwlock, RW_WRITER);
if (bff->bff_linkid == DATALINK_INVALID_LINKID) {
blp = NULL;
} else {
for (blp = list_head(&bip->bi_links); blp != NULL;
blp = list_next(&bip->bi_links, blp)) {
if (blp->bl_linkid == bff->bff_linkid &&
!(blp->bl_flags & BLF_DELETED))
break;
}
if (blp == NULL) {
rc = ENOENT;
rw_exit(&bip->bi_rwlock);
break;
}
}
avl_create(&fwd_scavenge, fwd_compare, sizeof (bridge_fwd_t),
offsetof(bridge_fwd_t, bf_node));
bfnext = avl_first(&bip->bi_fwd);
while ((bfp = bfnext) != NULL) {
bfnext = AVL_NEXT(&bip->bi_fwd, bfp);
if (bfp->bf_flags & BFF_LOCALADDR)
continue;
if (blp != NULL) {
for (i = 0; i < bfp->bf_maxlinks; i++) {
if (bfp->bf_links[i] == blp)
break;
}
if ((i < bfp->bf_maxlinks) == bff->bff_exclude)
continue;
}
ASSERT(bfp->bf_flags & BFF_INTREE);
avl_remove(&bip->bi_fwd, bfp);
bfp->bf_flags &= ~BFF_INTREE;
avl_add(&fwd_scavenge, bfp);
}
rw_exit(&bip->bi_rwlock);
bfnext = avl_first(&fwd_scavenge);
while ((bfp = bfnext) != NULL) {
bfnext = AVL_NEXT(&fwd_scavenge, bfp);
avl_remove(&fwd_scavenge, bfp);
fwd_unref(bfp);
}
avl_destroy(&fwd_scavenge);
break;
}
case BRIOC_TABLEMAX:
if ((bip = bsp->bs_inst) == NULL ||
(rc = miocpullup(mp, sizeof (uint32_t))) != 0)
break;
bip->bi_tablemax = *(uint32_t *)mp->b_cont->b_rptr;
break;
}
if (rc == 0)
miocack(wq, mp, len, 0);
else
miocnak(wq, mp, 0, rc);
}
static int
bridge_wput(queue_t *wq, mblk_t *mp)
{
switch (DB_TYPE(mp)) {
case M_IOCTL:
bridge_ioctl(wq, mp);
break;
case M_FLUSH:
if (*mp->b_rptr & FLUSHW)
*mp->b_rptr &= ~FLUSHW;
if (*mp->b_rptr & FLUSHR)
qreply(wq, mp);
else
freemsg(mp);
break;
default:
freemsg(mp);
break;
}
return (0);
}
static void
bridge_inst_init(void)
{
bridge_scan_interval = 5 * drv_usectohz(1000000);
bridge_fwd_age = 25 * drv_usectohz(1000000);
rw_init(&bmac_rwlock, NULL, RW_DRIVER, NULL);
list_create(&bmac_list, sizeof (bridge_mac_t),
offsetof(bridge_mac_t, bm_node));
list_create(&inst_list, sizeof (bridge_inst_t),
offsetof(bridge_inst_t, bi_node));
cv_init(&inst_cv, NULL, CV_DRIVER, NULL);
mutex_init(&inst_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&stream_ref_cv, NULL, CV_DRIVER, NULL);
mutex_init(&stream_ref_lock, NULL, MUTEX_DRIVER, NULL);
mac_bridge_vectors(bridge_xmit_cb, bridge_recv_cb, bridge_ref_cb,
bridge_ls_cb);
}
static void
bridge_inst_fini(void)
{
mac_bridge_vectors(NULL, NULL, NULL, NULL);
if (bridge_timerid != 0)
(void) untimeout(bridge_timerid);
rw_destroy(&bmac_rwlock);
list_destroy(&bmac_list);
list_destroy(&inst_list);
cv_destroy(&inst_cv);
mutex_destroy(&inst_lock);
cv_destroy(&stream_ref_cv);
mutex_destroy(&stream_ref_lock);
}
static int
bridge_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
if (ddi_create_minor_node(dip, BRIDGE_CTL, S_IFCHR, 0, DDI_PSEUDO,
CLONE_DEV) == DDI_FAILURE) {
return (DDI_FAILURE);
}
if (dld_ioc_register(BRIDGE_IOC, bridge_ioc_list,
DLDIOCCNT(bridge_ioc_list)) != 0) {
ddi_remove_minor_node(dip, BRIDGE_CTL);
return (DDI_FAILURE);
}
bridge_dev_info = dip;
bridge_major = ddi_driver_major(dip);
bridge_taskq = ddi_taskq_create(dip, BRIDGE_DEV_NAME, 1,
TASKQ_DEFAULTPRI, 0);
return (DDI_SUCCESS);
}
static int
bridge_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
ddi_remove_minor_node(dip, NULL);
ddi_taskq_destroy(bridge_taskq);
bridge_dev_info = NULL;
return (DDI_SUCCESS);
}
static int
bridge_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result)
{
int rc;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
if (bridge_dev_info == NULL) {
rc = DDI_FAILURE;
} else {
*result = (void *)bridge_dev_info;
rc = DDI_SUCCESS;
}
break;
case DDI_INFO_DEVT2INSTANCE:
*result = NULL;
rc = DDI_SUCCESS;
break;
default:
rc = DDI_FAILURE;
break;
}
return (rc);
}
static struct module_info bridge_modinfo = {
2105,
BRIDGE_DEV_NAME,
0,
16384,
65536,
128
};
static struct qinit bridge_rinit = {
NULL,
NULL,
bridge_open,
bridge_close,
NULL,
&bridge_modinfo,
NULL
};
static struct qinit bridge_winit = {
(int (*)())bridge_wput,
NULL,
NULL,
NULL,
NULL,
&bridge_modinfo,
NULL
};
static struct streamtab bridge_tab = {
&bridge_rinit,
&bridge_winit
};
DDI_DEFINE_STREAM_OPS(bridge_ops, nulldev, nulldev, bridge_attach,
bridge_detach, nodev, bridge_info, D_NEW | D_MP, &bridge_tab,
ddi_quiesce_not_supported);
static struct modldrv modldrv = {
&mod_driverops,
"bridging driver",
&bridge_ops
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modldrv,
NULL
};
int
_init(void)
{
int retv;
mac_init_ops(NULL, BRIDGE_DEV_NAME);
bridge_inst_init();
if ((retv = mod_install(&modlinkage)) != 0)
bridge_inst_fini();
return (retv);
}
int
_fini(void)
{
int retv;
rw_enter(&bmac_rwlock, RW_READER);
retv = list_is_empty(&bmac_list) ? 0 : EBUSY;
rw_exit(&bmac_rwlock);
if (retv == 0 &&
(retv = mod_remove(&modlinkage)) == 0)
bridge_inst_fini();
return (retv);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}