/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Simulated network device (simnet) driver: simulates a pseudo GLDv3 network * device. Can simulate an Ethernet or WiFi network device. In addition, another * simnet instance can be attached as a peer to create a point-to-point link on * the same system. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "simnet_impl.h" #define SIMNETINFO "Simulated Network Driver" static dev_info_t *simnet_dip; static int simnet_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); static int simnet_attach(dev_info_t *, ddi_attach_cmd_t); static int simnet_detach(dev_info_t *, ddi_detach_cmd_t); static int simnet_ioc_create(void *, intptr_t, int, cred_t *, int *); static int simnet_ioc_delete(void *, intptr_t, int, cred_t *, int *); static int simnet_ioc_info(void *, intptr_t, int, cred_t *, int *); static int simnet_ioc_modify(void *, intptr_t, int, cred_t *, int *); static uint8_t *mcastaddr_lookup(simnet_dev_t *, const uint8_t *); static dld_ioc_info_t simnet_ioc_list[] = { {SIMNET_IOC_CREATE, DLDCOPYINOUT, sizeof (simnet_ioc_create_t), simnet_ioc_create, {PRIV_SYS_DL_CONFIG}}, {SIMNET_IOC_DELETE, DLDCOPYIN, sizeof (simnet_ioc_delete_t), simnet_ioc_delete, {PRIV_SYS_DL_CONFIG}}, {SIMNET_IOC_INFO, DLDCOPYINOUT, sizeof (simnet_ioc_info_t), simnet_ioc_info, {NULL}}, {SIMNET_IOC_MODIFY, DLDCOPYIN, sizeof (simnet_ioc_modify_t), simnet_ioc_modify, {PRIV_SYS_DL_CONFIG}}, }; DDI_DEFINE_STREAM_OPS(simnet_dev_ops, nulldev, nulldev, simnet_attach, simnet_detach, nodev, simnet_getinfo, D_MP, NULL, ddi_quiesce_not_supported); static struct modldrv simnet_modldrv = { &mod_driverops, /* Type of module. This one is a driver */ SIMNETINFO, /* short description */ &simnet_dev_ops /* driver specific ops */ }; static struct modlinkage modlinkage = { MODREV_1, &simnet_modldrv, NULL }; /* MAC callback function declarations */ static int simnet_m_start(void *); static void simnet_m_stop(void *); static int simnet_m_promisc(void *, boolean_t); static int simnet_m_multicst(void *, boolean_t, const uint8_t *); static int simnet_m_unicst(void *, const uint8_t *); static int simnet_m_stat(void *, uint_t, uint64_t *); static void simnet_m_ioctl(void *, queue_t *, mblk_t *); static mblk_t *simnet_m_tx(void *, mblk_t *); static int simnet_m_setprop(void *, const char *, mac_prop_id_t, uint_t, const void *); static int simnet_m_getprop(void *, const char *, mac_prop_id_t, uint_t, uint_t, void *, uint_t *); static mac_callbacks_t simnet_m_callbacks = { (MC_IOCTL | MC_SETPROP | MC_GETPROP), simnet_m_stat, simnet_m_start, simnet_m_stop, simnet_m_promisc, simnet_m_multicst, simnet_m_unicst, simnet_m_tx, simnet_m_ioctl, NULL, NULL, NULL, simnet_m_setprop, simnet_m_getprop }; /* * simnet_dev_lock protects the simnet device list. * sd_instlock in each simnet_dev_t protects access to * a single simnet_dev_t. */ static krwlock_t simnet_dev_lock; static list_t simnet_dev_list; static int simnet_count; /* Num of simnet instances */ int _init(void) { int status; mac_init_ops(&simnet_dev_ops, "simnet"); status = mod_install(&modlinkage); if (status != DDI_SUCCESS) mac_fini_ops(&simnet_dev_ops); return (status); } int _fini(void) { int status; status = mod_remove(&modlinkage); if (status == DDI_SUCCESS) mac_fini_ops(&simnet_dev_ops); return (status); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } static void simnet_init(void) { rw_init(&simnet_dev_lock, NULL, RW_DEFAULT, NULL); list_create(&simnet_dev_list, sizeof (simnet_dev_t), offsetof(simnet_dev_t, sd_listnode)); simnet_count = 0; } static void simnet_fini(void) { ASSERT(simnet_count == 0); rw_destroy(&simnet_dev_lock); list_destroy(&simnet_dev_list); } /*ARGSUSED*/ static int simnet_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: *result = simnet_dip; return (DDI_SUCCESS); case DDI_INFO_DEVT2INSTANCE: *result = NULL; return (DDI_SUCCESS); } return (DDI_FAILURE); } static int simnet_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { switch (cmd) { case DDI_ATTACH: if (ddi_get_instance(dip) != 0) { /* we only allow instance 0 to attach */ return (DDI_FAILURE); } if (dld_ioc_register(SIMNET_IOC, simnet_ioc_list, DLDIOCCNT(simnet_ioc_list)) != 0) return (DDI_FAILURE); simnet_dip = dip; simnet_init(); return (DDI_SUCCESS); case DDI_RESUME: return (DDI_SUCCESS); default: return (DDI_FAILURE); } } /*ARGSUSED*/ static int simnet_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { switch (cmd) { case DDI_DETACH: /* * Allow the simnet instance to be detached only if there * are no simnets configured. */ if (simnet_count > 0) return (DDI_FAILURE); simnet_dip = NULL; simnet_fini(); dld_ioc_unregister(SIMNET_IOC); return (DDI_SUCCESS); case DDI_SUSPEND: return (DDI_SUCCESS); default: return (DDI_FAILURE); } } /* Caller must hold simnet_dev_lock */ static simnet_dev_t * simnet_dev_lookup(datalink_id_t link_id) { simnet_dev_t *sdev; for (sdev = list_head(&simnet_dev_list); sdev != NULL; sdev = list_next(&simnet_dev_list, sdev)) { if (!(sdev->sd_flags & SDF_SHUTDOWN) && (sdev->sd_link_id == link_id)) { atomic_inc_32(&sdev->sd_refcount); return (sdev); } } return (NULL); } static void simnet_wifidev_free(simnet_dev_t *sdev) { simnet_wifidev_t *wdev = sdev->sd_wifidev; int i; for (i = 0; i < wdev->swd_esslist_num; i++) { kmem_free(wdev->swd_esslist[i], sizeof (wl_ess_conf_t)); } kmem_free(wdev, sizeof (simnet_wifidev_t)); } static void simnet_dev_unref(simnet_dev_t *sdev) { ASSERT(sdev->sd_refcount > 0); if (atomic_dec_32_nv(&sdev->sd_refcount) != 0) return; if (sdev->sd_mh != NULL) (void) mac_unregister(sdev->sd_mh); if (sdev->sd_wifidev != NULL) { ASSERT(sdev->sd_type == DL_WIFI); simnet_wifidev_free(sdev); } mutex_destroy(&sdev->sd_instlock); cv_destroy(&sdev->sd_threadwait); kmem_free(sdev->sd_mcastaddrs, ETHERADDRL * sdev->sd_mcastaddr_count); kmem_free(sdev, sizeof (*sdev)); simnet_count--; } static int simnet_init_wifi(simnet_dev_t *sdev, mac_register_t *mac) { wifi_data_t wd = { 0 }; int err; sdev->sd_wifidev = kmem_zalloc(sizeof (simnet_wifidev_t), KM_NOSLEEP); if (sdev->sd_wifidev == NULL) return (ENOMEM); sdev->sd_wifidev->swd_sdev = sdev; sdev->sd_wifidev->swd_linkstatus = WL_NOTCONNECTED; wd.wd_secalloc = WIFI_SEC_NONE; wd.wd_opmode = IEEE80211_M_STA; mac->m_type_ident = MAC_PLUGIN_IDENT_WIFI; mac->m_max_sdu = IEEE80211_MTU; mac->m_pdata = &wd; mac->m_pdata_size = sizeof (wd); err = mac_register(mac, &sdev->sd_mh); return (err); } static int simnet_init_ether(simnet_dev_t *sdev, mac_register_t *mac) { int err; mac->m_type_ident = MAC_PLUGIN_IDENT_ETHER; mac->m_max_sdu = SIMNET_MAX_MTU; mac->m_margin = VLAN_TAGSZ; err = mac_register(mac, &sdev->sd_mh); return (err); } static int simnet_init_mac(simnet_dev_t *sdev) { mac_register_t *mac; int err; if ((mac = mac_alloc(MAC_VERSION)) == NULL) return (ENOMEM); mac->m_driver = sdev; mac->m_dip = simnet_dip; mac->m_instance = (uint_t)-1; mac->m_src_addr = sdev->sd_mac_addr; mac->m_callbacks = &simnet_m_callbacks; mac->m_min_sdu = 0; if (sdev->sd_type == DL_ETHER) err = simnet_init_ether(sdev, mac); else if (sdev->sd_type == DL_WIFI) err = simnet_init_wifi(sdev, mac); else err = EINVAL; mac_free(mac); return (err); } /* ARGSUSED */ static int simnet_ioc_create(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { simnet_ioc_create_t *create_arg = karg; simnet_dev_t *sdev; simnet_dev_t *sdev_tmp; int err = 0; sdev = kmem_zalloc(sizeof (*sdev), KM_NOSLEEP); if (sdev == NULL) return (ENOMEM); rw_enter(&simnet_dev_lock, RW_WRITER); if ((sdev_tmp = simnet_dev_lookup(create_arg->sic_link_id)) != NULL) { simnet_dev_unref(sdev_tmp); rw_exit(&simnet_dev_lock); kmem_free(sdev, sizeof (*sdev)); return (EEXIST); } sdev->sd_type = create_arg->sic_type; sdev->sd_link_id = create_arg->sic_link_id; sdev->sd_refcount++; mutex_init(&sdev->sd_instlock, NULL, MUTEX_DRIVER, NULL); cv_init(&sdev->sd_threadwait, NULL, CV_DRIVER, NULL); simnet_count++; /* Simnets created from configuration on boot pass saved MAC address */ if (create_arg->sic_mac_len == 0) { /* Generate random MAC address */ (void) random_get_pseudo_bytes(sdev->sd_mac_addr, ETHERADDRL); /* Ensure MAC address is not multicast and is local */ sdev->sd_mac_addr[0] = (sdev->sd_mac_addr[0] & ~1) | 2; sdev->sd_mac_len = ETHERADDRL; } else { (void) memcpy(sdev->sd_mac_addr, create_arg->sic_mac_addr, create_arg->sic_mac_len); sdev->sd_mac_len = create_arg->sic_mac_len; } if ((err = simnet_init_mac(sdev)) != 0) { simnet_dev_unref(sdev); goto exit; } if ((err = dls_devnet_create(sdev->sd_mh, sdev->sd_link_id)) != 0) { simnet_dev_unref(sdev); goto exit; } mac_link_update(sdev->sd_mh, LINK_STATE_UP); mac_tx_update(sdev->sd_mh); list_insert_tail(&simnet_dev_list, sdev); /* Always return MAC address back to caller */ (void) memcpy(create_arg->sic_mac_addr, sdev->sd_mac_addr, sdev->sd_mac_len); create_arg->sic_mac_len = sdev->sd_mac_len; exit: rw_exit(&simnet_dev_lock); return (err); } /* Caller must hold writer simnet_dev_lock */ static datalink_id_t simnet_remove_peer(simnet_dev_t *sdev) { simnet_dev_t *sdev_peer; datalink_id_t peer_link_id = DATALINK_INVALID_LINKID; if ((sdev_peer = sdev->sd_peer_dev) != NULL) { ASSERT(sdev == sdev_peer->sd_peer_dev); sdev_peer->sd_peer_dev = NULL; sdev->sd_peer_dev = NULL; peer_link_id = sdev_peer->sd_link_id; /* Release previous references held on both simnets */ simnet_dev_unref(sdev_peer); simnet_dev_unref(sdev); } return (peer_link_id); } /* ARGSUSED */ static int simnet_ioc_modify(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { simnet_ioc_modify_t *modify_arg = karg; simnet_dev_t *sdev; simnet_dev_t *sdev_peer = NULL; rw_enter(&simnet_dev_lock, RW_WRITER); if ((sdev = simnet_dev_lookup(modify_arg->sim_link_id)) == NULL) { rw_exit(&simnet_dev_lock); return (ENOENT); } if (sdev->sd_link_id == modify_arg->sim_peer_link_id) { /* Cannot peer with self */ rw_exit(&simnet_dev_lock); simnet_dev_unref(sdev); return (EINVAL); } if (sdev->sd_peer_dev != NULL && sdev->sd_peer_dev->sd_link_id == modify_arg->sim_peer_link_id) { /* Nothing to modify */ rw_exit(&simnet_dev_lock); simnet_dev_unref(sdev); return (0); } if (modify_arg->sim_peer_link_id != DATALINK_INVALID_LINKID && (sdev_peer = simnet_dev_lookup(modify_arg->sim_peer_link_id)) == NULL) { /* Peer simnet device not available */ rw_exit(&simnet_dev_lock); simnet_dev_unref(sdev); return (ENOENT); } /* First remove any previous peer */ (void) simnet_remove_peer(sdev); if (sdev_peer != NULL) { /* Remove any previous peer of sdev_peer */ (void) simnet_remove_peer(sdev_peer); /* Update both devices with the new peer */ sdev_peer->sd_peer_dev = sdev; sdev->sd_peer_dev = sdev_peer; /* Hold references on both devices */ } else { /* Release sdev lookup reference */ simnet_dev_unref(sdev); } rw_exit(&simnet_dev_lock); return (0); } /* ARGSUSED */ static int simnet_ioc_delete(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { int err; simnet_dev_t *sdev; simnet_dev_t *sdev_peer; simnet_ioc_delete_t *delete_arg = karg; datalink_id_t tmpid; datalink_id_t peerid; rw_enter(&simnet_dev_lock, RW_WRITER); if ((sdev = simnet_dev_lookup(delete_arg->sid_link_id)) == NULL) { rw_exit(&simnet_dev_lock); return (ENOENT); } if ((err = dls_devnet_destroy(sdev->sd_mh, &tmpid, B_TRUE)) != 0) { rw_exit(&simnet_dev_lock); simnet_dev_unref(sdev); return (err); } ASSERT(sdev->sd_link_id == tmpid); /* Remove any attached peer link */ peerid = simnet_remove_peer(sdev); /* Prevent new threads from using the instance */ mutex_enter(&sdev->sd_instlock); sdev->sd_flags |= SDF_SHUTDOWN; /* Wait until all active threads using the instance exit */ while (sdev->sd_threadcount > 0) { if (cv_wait_sig(&sdev->sd_threadwait, &sdev->sd_instlock) == 0) { /* Signaled */ mutex_exit(&sdev->sd_instlock); err = EINTR; goto fail; } } mutex_exit(&sdev->sd_instlock); /* Try disabling the MAC */ if ((err = mac_disable(sdev->sd_mh)) != 0) goto fail; list_remove(&simnet_dev_list, sdev); rw_exit(&simnet_dev_lock); simnet_dev_unref(sdev); /* Release lookup ref */ /* Releasing the last ref performs sdev/mem free */ simnet_dev_unref(sdev); return (err); fail: /* Re-create simnet instance and add any previous peer */ (void) dls_devnet_create(sdev->sd_mh, sdev->sd_link_id); sdev->sd_flags &= ~SDF_SHUTDOWN; ASSERT(sdev->sd_peer_dev == NULL); if (peerid != DATALINK_INVALID_LINKID && ((sdev_peer = simnet_dev_lookup(peerid)) != NULL)) { /* Attach peer device back */ ASSERT(sdev_peer->sd_peer_dev == NULL); sdev_peer->sd_peer_dev = sdev; sdev->sd_peer_dev = sdev_peer; /* Hold reference on both devices */ } else { /* * No previous peer or previous peer no longer * available so release lookup reference. */ simnet_dev_unref(sdev); } rw_exit(&simnet_dev_lock); return (err); } /* ARGSUSED */ static int simnet_ioc_info(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { simnet_ioc_info_t *info_arg = karg; simnet_dev_t *sdev; rw_enter(&simnet_dev_lock, RW_READER); if ((sdev = simnet_dev_lookup(info_arg->sii_link_id)) == NULL) { rw_exit(&simnet_dev_lock); return (ENOENT); } (void) memcpy(info_arg->sii_mac_addr, sdev->sd_mac_addr, sdev->sd_mac_len); info_arg->sii_mac_len = sdev->sd_mac_len; info_arg->sii_type = sdev->sd_type; if (sdev->sd_peer_dev != NULL) info_arg->sii_peer_link_id = sdev->sd_peer_dev->sd_link_id; rw_exit(&simnet_dev_lock); simnet_dev_unref(sdev); return (0); } static void simnet_rx(simnet_dev_t *sdev, mblk_t *mp) { mac_header_info_t hdr_info; /* Check for valid packet header */ if (mac_header_info(sdev->sd_mh, mp, &hdr_info) != 0) { freemsg(mp); sdev->sd_stats.recv_errors++; return; } /* * When we are NOT in promiscuous mode we only receive * unicast packets addressed to us and multicast packets that * MAC clients have requested. */ if (!sdev->sd_promisc && hdr_info.mhi_dsttype != MAC_ADDRTYPE_BROADCAST) { if (hdr_info.mhi_dsttype == MAC_ADDRTYPE_UNICAST && bcmp(hdr_info.mhi_daddr, sdev->sd_mac_addr, ETHERADDRL) != 0) { freemsg(mp); return; } else if (hdr_info.mhi_dsttype == MAC_ADDRTYPE_MULTICAST) { mutex_enter(&sdev->sd_instlock); if (mcastaddr_lookup(sdev, hdr_info.mhi_daddr) == NULL) { mutex_exit(&sdev->sd_instlock); freemsg(mp); return; } mutex_exit(&sdev->sd_instlock); } } sdev->sd_stats.recv_count++; sdev->sd_stats.rbytes += msgdsize(mp); mac_rx(sdev->sd_mh, NULL, mp); } static mblk_t * simnet_m_tx(void *arg, mblk_t *mp_chain) { simnet_dev_t *sdev = arg; simnet_dev_t *sdev_rx; mblk_t *mpnext = mp_chain; mblk_t *mp; rw_enter(&simnet_dev_lock, RW_READER); if ((sdev_rx = sdev->sd_peer_dev) == NULL) { /* Discard packets when no peer exists */ rw_exit(&simnet_dev_lock); freemsgchain(mp_chain); return (NULL); } /* * Discard packets when either device is shutting down or not ready. * Though MAC layer ensures a reference is held on the MAC while we * process the packet chain, there is no guarantee the peer MAC will * remain enabled. So we increment per-instance threadcount to ensure * either MAC instance is not disabled while we handle the chain of * packets. It is okay if the peer device is disconnected while we are * here since we lookup the peer device while holding simnet_dev_lock * (reader lock) and increment the threadcount of the peer, the peer * MAC cannot be disabled in simnet_ioc_delete. */ mutex_enter(&sdev_rx->sd_instlock); if (sdev_rx->sd_flags & SDF_SHUTDOWN || !(sdev_rx->sd_flags & SDF_STARTED)) { mutex_exit(&sdev_rx->sd_instlock); rw_exit(&simnet_dev_lock); freemsgchain(mp_chain); return (NULL); } sdev_rx->sd_threadcount++; mutex_exit(&sdev_rx->sd_instlock); rw_exit(&simnet_dev_lock); mutex_enter(&sdev->sd_instlock); if (sdev->sd_flags & SDF_SHUTDOWN || !(sdev->sd_flags & SDF_STARTED)) { mutex_exit(&sdev->sd_instlock); mutex_enter(&sdev_rx->sd_instlock); if (--sdev_rx->sd_threadcount == 0) cv_broadcast(&sdev_rx->sd_threadwait); mutex_exit(&sdev_rx->sd_instlock); freemsgchain(mp_chain); return (NULL); } sdev->sd_threadcount++; mutex_exit(&sdev->sd_instlock); while ((mp = mpnext) != NULL) { int len; int size; mblk_t *mp_new; mblk_t *mp_tmp; mpnext = mp->b_next; mp->b_next = NULL; len = msgdsize(mp); /* Pad packet to minimum Ethernet frame size */ if (len < ETHERMIN) { size = ETHERMIN - len; mp_new = allocb(size, BPRI_HI); if (mp_new == NULL) { sdev->sd_stats.xmit_errors++; freemsg(mp); continue; } bzero(mp_new->b_wptr, size); mp_new->b_wptr += size; mp_tmp = mp; while (mp_tmp->b_cont != NULL) mp_tmp = mp_tmp->b_cont; mp_tmp->b_cont = mp_new; len += size; } /* Pullup packet into a single mblk */ if (!pullupmsg(mp, -1)) { sdev->sd_stats.xmit_errors++; freemsg(mp); continue; } /* Fix mblk checksum as the pkt dest is local */ if ((mp = mac_fix_cksum(mp)) == NULL) { sdev->sd_stats.xmit_errors++; continue; } sdev->sd_stats.xmit_count++; sdev->sd_stats.obytes += len; simnet_rx(sdev_rx, mp); } mutex_enter(&sdev->sd_instlock); if (--sdev->sd_threadcount == 0) cv_broadcast(&sdev->sd_threadwait); mutex_exit(&sdev->sd_instlock); mutex_enter(&sdev_rx->sd_instlock); if (--sdev_rx->sd_threadcount == 0) cv_broadcast(&sdev_rx->sd_threadwait); mutex_exit(&sdev_rx->sd_instlock); return (NULL); } static int simnet_wifi_ioctl(simnet_dev_t *sdev, mblk_t *mp) { int rc = WL_SUCCESS; simnet_wifidev_t *wdev = sdev->sd_wifidev; /* LINTED E_BAD_PTR_CAST_ALIGN */ switch (((wldp_t *)mp->b_rptr)->wldp_id) { case WL_DISASSOCIATE: wdev->swd_linkstatus = WL_NOTCONNECTED; break; default: break; } return (rc); } static void simnet_m_ioctl(void *arg, queue_t *q, mblk_t *mp) { simnet_dev_t *sdev = arg; struct iocblk *iocp; mblk_t *mp1; uint32_t cmd; int rc; if (sdev->sd_type != DL_WIFI) { miocnak(q, mp, 0, ENOTSUP); return; } /* LINTED E_BAD_PTR_CAST_ALIGN */ iocp = (struct iocblk *)mp->b_rptr; if (iocp->ioc_count == 0) { miocnak(q, mp, 0, EINVAL); return; } /* We only claim support for WiFi operation commands */ cmd = iocp->ioc_cmd; switch (cmd) { default: miocnak(q, mp, 0, EINVAL); return; case WLAN_GET_PARAM: case WLAN_SET_PARAM: case WLAN_COMMAND: break; } mp1 = mp->b_cont; freemsg(mp1->b_cont); mp1->b_cont = NULL; /* overwrite everything */ mp1->b_wptr = mp1->b_rptr; rc = simnet_wifi_ioctl(sdev, mp1); miocack(q, mp, msgdsize(mp1), rc); } static int simnet_m_stat(void *arg, uint_t stat, uint64_t *val) { int rval = 0; simnet_dev_t *sdev = arg; ASSERT(sdev->sd_mh != NULL); switch (stat) { case MAC_STAT_IFSPEED: *val = 100 * 1000000ull; /* 100 Mbps */ break; case MAC_STAT_LINK_STATE: *val = LINK_DUPLEX_FULL; break; case MAC_STAT_LINK_UP: if (sdev->sd_flags & SDF_STARTED) *val = LINK_STATE_UP; else *val = LINK_STATE_DOWN; break; case MAC_STAT_PROMISC: case MAC_STAT_MULTIRCV: case MAC_STAT_MULTIXMT: case MAC_STAT_BRDCSTRCV: case MAC_STAT_BRDCSTXMT: rval = ENOTSUP; break; case MAC_STAT_OPACKETS: *val = sdev->sd_stats.xmit_count; break; case MAC_STAT_OBYTES: *val = sdev->sd_stats.obytes; break; case MAC_STAT_IERRORS: *val = sdev->sd_stats.recv_errors; break; case MAC_STAT_OERRORS: *val = sdev->sd_stats.xmit_errors; break; case MAC_STAT_RBYTES: *val = sdev->sd_stats.rbytes; break; case MAC_STAT_IPACKETS: *val = sdev->sd_stats.recv_count; break; case WIFI_STAT_FCS_ERRORS: case WIFI_STAT_WEP_ERRORS: case WIFI_STAT_TX_FRAGS: case WIFI_STAT_MCAST_TX: case WIFI_STAT_RTS_SUCCESS: case WIFI_STAT_RTS_FAILURE: case WIFI_STAT_ACK_FAILURE: case WIFI_STAT_RX_FRAGS: case WIFI_STAT_MCAST_RX: case WIFI_STAT_RX_DUPS: rval = ENOTSUP; break; default: rval = ENOTSUP; break; } return (rval); } static int simnet_m_start(void *arg) { simnet_dev_t *sdev = arg; sdev->sd_flags |= SDF_STARTED; return (0); } static void simnet_m_stop(void *arg) { simnet_dev_t *sdev = arg; sdev->sd_flags &= ~SDF_STARTED; } static int simnet_m_promisc(void *arg, boolean_t on) { simnet_dev_t *sdev = arg; sdev->sd_promisc = on; return (0); } /* * Returns matching multicast address enabled on the simnet instance. * Assumes simnet instance mutex lock is held. */ static uint8_t * mcastaddr_lookup(simnet_dev_t *sdev, const uint8_t *addrp) { int idx; uint8_t *maddrptr; maddrptr = sdev->sd_mcastaddrs; for (idx = 0; idx < sdev->sd_mcastaddr_count; idx++) { if (bcmp(maddrptr, addrp, ETHERADDRL) == 0) return (maddrptr); maddrptr += ETHERADDRL; } return (NULL); } /* Add or remove Multicast addresses on simnet instance */ static int simnet_m_multicst(void *arg, boolean_t add, const uint8_t *addrp) { simnet_dev_t *sdev = arg; uint8_t *maddrptr; uint8_t *newbuf; size_t prevsize; size_t newsize; ptrdiff_t len; ptrdiff_t len2; alloc_retry: prevsize = sdev->sd_mcastaddr_count * ETHERADDRL; newsize = prevsize + (add ? ETHERADDRL:-ETHERADDRL); newbuf = kmem_alloc(newsize, KM_SLEEP); mutex_enter(&sdev->sd_instlock); if (prevsize != (sdev->sd_mcastaddr_count * ETHERADDRL)) { mutex_exit(&sdev->sd_instlock); kmem_free(newbuf, newsize); goto alloc_retry; } maddrptr = mcastaddr_lookup(sdev, addrp); if (!add && maddrptr != NULL) { /* Removing a Multicast address */ if (newbuf != NULL) { /* LINTED: E_PTRDIFF_OVERFLOW */ len = maddrptr - sdev->sd_mcastaddrs; (void) memcpy(newbuf, sdev->sd_mcastaddrs, len); len2 = prevsize - len - ETHERADDRL; (void) memcpy(newbuf + len, maddrptr + ETHERADDRL, len2); } sdev->sd_mcastaddr_count--; } else if (add && maddrptr == NULL) { /* Adding a new Multicast address */ (void) memcpy(newbuf, sdev->sd_mcastaddrs, prevsize); (void) memcpy(newbuf + prevsize, addrp, ETHERADDRL); sdev->sd_mcastaddr_count++; } else { /* Error: removing a non-existing Multicast address */ mutex_exit(&sdev->sd_instlock); kmem_free(newbuf, newsize); cmn_err(CE_WARN, "simnet: MAC call to remove a " "Multicast address failed"); return (EINVAL); } kmem_free(sdev->sd_mcastaddrs, prevsize); sdev->sd_mcastaddrs = newbuf; mutex_exit(&sdev->sd_instlock); return (0); } static int simnet_m_unicst(void *arg, const uint8_t *macaddr) { simnet_dev_t *sdev = arg; (void) memcpy(sdev->sd_mac_addr, macaddr, ETHERADDRL); return (0); } /* Parse WiFi scan list entry arguments and return the arg count */ static int parse_esslist_args(const void *pr_val, uint_t pr_valsize, char args[][MAX_ESSLIST_ARGLEN]) { char *sep; ptrdiff_t len = pr_valsize; const char *piece = pr_val; const char *end = (const char *)pr_val + pr_valsize - 1; int arg = 0; while (piece < end && (arg < MAX_ESSLIST_ARGS)) { sep = strchr(piece, ','); if (sep == NULL) sep = (char *)end; /* LINTED E_PTRDIFF_OVERFLOW */ len = sep - piece; /* If first arg is zero then return none to delete all */ if (arg == 0 && strnlen(piece, len) == 1 && piece[0] == '0') return (0); if (len > MAX_ESSLIST_ARGLEN) len = MAX_ESSLIST_ARGLEN - 1; (void) memcpy(&args[arg][0], piece, len); args[arg][len] = '\0'; piece = sep + 1; arg++; } return (arg); } /* Set WiFi scan list entry from private property _wl_esslist */ static int set_wl_esslist_priv_prop(simnet_wifidev_t *wdev, uint_t pr_valsize, const void *pr_val) { char essargs[MAX_ESSLIST_ARGS][MAX_ESSLIST_ARGLEN]; wl_ess_conf_t *wls; long result; int i; bzero(essargs, sizeof (essargs)); if (parse_esslist_args(pr_val, pr_valsize, essargs) == 0) { for (i = 0; i < wdev->swd_esslist_num; i++) { kmem_free(wdev->swd_esslist[i], sizeof (wl_ess_conf_t)); wdev->swd_esslist[i] = NULL; } wdev->swd_esslist_num = 0; return (0); } for (i = 0; i < wdev->swd_esslist_num; i++) { wls = wdev->swd_esslist[i]; if (strcasecmp(wls->wl_ess_conf_essid.wl_essid_essid, essargs[0]) == 0) return (EEXIST); } if (wdev->swd_esslist_num >= MAX_SIMNET_ESSCONF) return (EINVAL); wls = kmem_zalloc(sizeof (wl_ess_conf_t), KM_SLEEP); (void) strlcpy(wls->wl_ess_conf_essid.wl_essid_essid, essargs[0], sizeof (wls->wl_ess_conf_essid.wl_essid_essid)); wls->wl_ess_conf_essid.wl_essid_length = strlen(wls->wl_ess_conf_essid.wl_essid_essid); (void) random_get_pseudo_bytes((uint8_t *) &wls->wl_ess_conf_bssid, sizeof (wl_bssid_t)); (void) ddi_strtol(essargs[1], (char **)NULL, 0, &result); wls->wl_ess_conf_sl = (wl_rssi_t) ((result > MAX_RSSI || result < 0) ? 0:result); wdev->swd_esslist[wdev->swd_esslist_num] = wls; wdev->swd_esslist_num++; return (0); } static int simnet_set_priv_prop(simnet_dev_t *sdev, const char *pr_name, uint_t pr_valsize, const void *pr_val) { simnet_wifidev_t *wdev = sdev->sd_wifidev; long result; if (strcmp(pr_name, "_wl_esslist") == 0) { if (pr_val == NULL) return (EINVAL); return (set_wl_esslist_priv_prop(wdev, pr_valsize, pr_val)); } else if (strcmp(pr_name, "_wl_connected") == 0) { if (pr_val == NULL) return (EINVAL); (void) ddi_strtol(pr_val, (char **)NULL, 0, &result); wdev->swd_linkstatus = ((result == 1) ? WL_CONNECTED:WL_NOTCONNECTED); return (0); } return (EINVAL); } static int simnet_m_setprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, uint_t wldp_length, const void *wldp_buf) { simnet_dev_t *sdev = arg; simnet_wifidev_t *wdev = sdev->sd_wifidev; int err = 0; uint32_t mtu; switch (wldp_pr_num) { case MAC_PROP_MTU: (void) memcpy(&mtu, wldp_buf, sizeof (mtu)); if (mtu > ETHERMIN && mtu < SIMNET_MAX_MTU) return (mac_maxsdu_update(sdev->sd_mh, mtu)); else return (EINVAL); default: break; } if (sdev->sd_type == DL_ETHER) return (ENOTSUP); /* mac_prop_id */ switch (wldp_pr_num) { case MAC_PROP_WL_ESSID: { int i; wl_ess_conf_t *wls; (void) memcpy(&wdev->swd_essid, wldp_buf, sizeof (wl_essid_t)); wdev->swd_linkstatus = WL_CONNECTED; /* Lookup the signal strength of the connected ESSID */ for (i = 0; i < wdev->swd_esslist_num; i++) { wls = wdev->swd_esslist[i]; if (strcasecmp(wls->wl_ess_conf_essid.wl_essid_essid, wdev->swd_essid.wl_essid_essid) == 0) { wdev->swd_rssi = wls->wl_ess_conf_sl; break; } } break; } case MAC_PROP_WL_BSSID: { (void) memcpy(&wdev->swd_bssid, wldp_buf, sizeof (wl_bssid_t)); break; } case MAC_PROP_WL_PHY_CONFIG: case MAC_PROP_WL_KEY_TAB: case MAC_PROP_WL_AUTH_MODE: case MAC_PROP_WL_ENCRYPTION: case MAC_PROP_WL_BSSTYPE: case MAC_PROP_WL_DESIRED_RATES: break; case MAC_PROP_PRIVATE: err = simnet_set_priv_prop(sdev, pr_name, wldp_length, wldp_buf); break; default: break; } return (err); } static int simnet_get_priv_prop(simnet_dev_t *sdev, const char *pr_name, uint_t pr_flags, uint_t pr_valsize, void *pr_val) { simnet_wifidev_t *wdev = sdev->sd_wifidev; boolean_t is_default = ((pr_flags & MAC_PROP_DEFAULT) != 0); int err = 0; int value; if (strcmp(pr_name, "_wl_esslist") == 0) { /* Returns num of _wl_ess_conf_t that have been set */ value = (is_default ? 0:wdev->swd_esslist_num); } else if (strcmp(pr_name, "_wl_connected") == 0) { value = ((wdev->swd_linkstatus == WL_CONNECTED) ? 1:0); } else { err = ENOTSUP; } if (err == 0) (void) snprintf(pr_val, pr_valsize, "%d", value); return (err); } static int simnet_m_getprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, uint_t pr_flags, uint_t wldp_length, void *wldp_buf, uint_t *perm) { simnet_dev_t *sdev = arg; simnet_wifidev_t *wdev = sdev->sd_wifidev; int err = 0; int i; if (sdev->sd_type == DL_ETHER) return (ENOTSUP); /* mac_prop_id */ switch (wldp_pr_num) { case MAC_PROP_WL_ESSID: (void) memcpy(wldp_buf, &wdev->swd_essid, sizeof (wl_essid_t)); break; case MAC_PROP_WL_BSSID: (void) memcpy(wldp_buf, &wdev->swd_bssid, sizeof (wl_bssid_t)); break; case MAC_PROP_WL_PHY_CONFIG: case MAC_PROP_WL_AUTH_MODE: case MAC_PROP_WL_ENCRYPTION: break; case MAC_PROP_WL_BSSTYPE: *perm = MAC_PROP_PERM_READ; break; case MAC_PROP_WL_LINKSTATUS: (void) memcpy(wldp_buf, &wdev->swd_linkstatus, sizeof (wdev->swd_linkstatus)); break; case MAC_PROP_WL_ESS_LIST: { wl_ess_conf_t *w_ess_conf; *perm = MAC_PROP_PERM_READ; ((wl_ess_list_t *)wldp_buf)->wl_ess_list_num = wdev->swd_esslist_num; /* LINTED E_BAD_PTR_CAST_ALIGN */ w_ess_conf = (wl_ess_conf_t *)((char *)wldp_buf + offsetof(wl_ess_list_t, wl_ess_list_ess)); for (i = 0; i < wdev->swd_esslist_num; i++) { (void) memcpy(w_ess_conf, wdev->swd_esslist[i], sizeof (wl_ess_conf_t)); w_ess_conf++; } break; } case MAC_PROP_WL_SUPPORTED_RATES: *perm = MAC_PROP_PERM_READ; break; case MAC_PROP_WL_RSSI: *perm = MAC_PROP_PERM_READ; *(wl_rssi_t *)wldp_buf = wdev->swd_rssi; break; case MAC_PROP_WL_RADIO: *(wl_radio_t *)wldp_buf = B_TRUE; break; case MAC_PROP_WL_POWER_MODE: break; case MAC_PROP_WL_DESIRED_RATES: break; case MAC_PROP_PRIVATE: err = simnet_get_priv_prop(sdev, pr_name, pr_flags, wldp_length, wldp_buf); break; default: err = ENOTSUP; break; } return (err); }