/* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2019 Joyent, Inc. */ /* * 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 #include #include "simnet_impl.h" #define SIMNETINFO "Simulated Network Driver" static dev_info_t *simnet_dip; static ddi_taskq_t *simnet_rxq; 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 const struct ether_addr *mcastaddr_lookup(const 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, secpolicy_dl_config}, {SIMNET_IOC_DELETE, DLDCOPYIN, sizeof (simnet_ioc_delete_t), simnet_ioc_delete, secpolicy_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, secpolicy_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, const uint_t, const void *); static int simnet_m_getprop(void *, const char *, mac_prop_id_t, uint_t, void *); static void simnet_m_propinfo(void *, const char *, mac_prop_id_t, mac_prop_info_handle_t); static boolean_t simnet_m_getcapab(void *, mac_capab_t, void *); static mac_callbacks_t simnet_m_callbacks = { (MC_IOCTL | MC_GETCAPAB | MC_SETPROP | MC_GETPROP | MC_PROPINFO), simnet_m_stat, simnet_m_start, simnet_m_stop, simnet_m_promisc, simnet_m_multicst, simnet_m_unicst, simnet_m_tx, NULL, simnet_m_ioctl, simnet_m_getcapab, NULL, NULL, simnet_m_setprop, simnet_m_getprop, simnet_m_propinfo }; /* * 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 boolean_t simnet_init(void) { if ((simnet_rxq = ddi_taskq_create(simnet_dip, "simnet", 1, TASKQ_DEFAULTPRI, 0)) == NULL) return (B_FALSE); rw_init(&simnet_dev_lock, NULL, RW_DEFAULT, NULL); list_create(&simnet_dev_list, sizeof (simnet_dev_t), offsetof(simnet_dev_t, sd_listnode)); return (B_TRUE); } static void simnet_fini(void) { ASSERT(simnet_count == 0); rw_destroy(&simnet_dev_lock); list_destroy(&simnet_dev_list); ddi_taskq_destroy(simnet_rxq); } /*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; if (!simnet_init()) return (DDI_FAILURE); 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); dld_ioc_unregister(SIMNET_IOC); simnet_fini(); simnet_dip = NULL; 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; ASSERT(RW_LOCK_HELD(&simnet_dev_lock)); 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, 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_zoneid = crgetzoneid(cred); 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, crgetzoneid(cred))) != 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; ASSERT(RW_WRITE_HELD(&simnet_dev_lock)); 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_zoneid != crgetzoneid(cred)) { rw_exit(&simnet_dev_lock); simnet_dev_unref(sdev); 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); if (sdev_peer == NULL) { /* Peer simnet device not available */ rw_exit(&simnet_dev_lock); simnet_dev_unref(sdev); return (ENOENT); } if (sdev_peer->sd_zoneid != sdev->sd_zoneid) { /* The two peers must be in the same zone (for now). */ rw_exit(&simnet_dev_lock); simnet_dev_unref(sdev); simnet_dev_unref(sdev_peer); return (EACCES); } } /* 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 (sdev->sd_zoneid != crgetzoneid(cred)) { rw_exit(&simnet_dev_lock); simnet_dev_unref(sdev); 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, crgetzoneid(cred)); 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; /* Make sure that the simnet link is visible from the caller's zone. */ if (!dls_devnet_islinkvisible(info_arg->sii_link_id, crgetzoneid(cred))) return (ENOENT); 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 boolean_t simnet_thread_ref(simnet_dev_t *sdev) { mutex_enter(&sdev->sd_instlock); if (sdev->sd_flags & SDF_SHUTDOWN || !(sdev->sd_flags & SDF_STARTED)) { mutex_exit(&sdev->sd_instlock); return (B_FALSE); } sdev->sd_threadcount++; mutex_exit(&sdev->sd_instlock); return (B_TRUE); } static void simnet_thread_unref(simnet_dev_t *sdev) { mutex_enter(&sdev->sd_instlock); if (--sdev->sd_threadcount == 0) cv_broadcast(&sdev->sd_threadwait); mutex_exit(&sdev->sd_instlock); } /* * TODO: Add properties to set Rx checksum flag behavior. * * o HCK_PARTIALCKSUM. * o HCK_FULLCKSUM_OK. */ static void simnet_rx(void *arg) { mblk_t *mp = arg; mac_header_info_t hdr_info; simnet_dev_t *sdev; sdev = (simnet_dev_t *)mp->b_next; mp->b_next = NULL; /* Check for valid packet header */ if (mac_header_info(sdev->sd_mh, mp, &hdr_info) != 0) { mac_drop_pkt(mp, "invalid L2 header"); sdev->sd_stats.recv_errors++; goto rx_done; } /* * 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); goto rx_done; } 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); goto rx_done; } mutex_exit(&sdev->sd_instlock); } } /* * We don't actually calculate and verify the IP header * checksum because the nature of simnet makes it redundant to * do so. The point is to test the presence of the flags. The * Tx side will have already populated the checksum field. */ if ((sdev->sd_rx_cksum & HCKSUM_IPHDRCKSUM) != 0) { mac_hcksum_set(mp, 0, 0, 0, 0, HCK_IPV4_HDRCKSUM_OK); } sdev->sd_stats.recv_count++; sdev->sd_stats.rbytes += msgdsize(mp); mac_rx(sdev->sd_mh, NULL, mp); rx_done: simnet_thread_unref(sdev); } #define SIMNET_ULP_CKSUM (HCKSUM_INET_FULL_V4 | HCKSUM_INET_PARTIAL) 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, *nmp; mac_emul_t emul = 0; 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); mac_drop_chain(mp_chain, "no peer"); 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. */ if (!simnet_thread_ref(sdev_rx)) { rw_exit(&simnet_dev_lock); mac_drop_chain(mp_chain, "simnet peer dev not ready"); return (NULL); } rw_exit(&simnet_dev_lock); if (!simnet_thread_ref(sdev)) { simnet_thread_unref(sdev_rx); mac_drop_chain(mp_chain, "simnet dev not ready"); return (NULL); } while ((mp = mpnext) != NULL) { size_t len; size_t 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++; mac_drop_pkt(mp, "allocb failed"); 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 ((nmp = msgpullup(mp, -1)) == NULL) { sdev->sd_stats.xmit_errors++; mac_drop_pkt(mp, "msgpullup failed"); continue; } else { mac_hcksum_clone(mp, nmp); freemsg(mp); mp = nmp; } /* Hold reference for taskq receive processing per-pkt */ if (!simnet_thread_ref(sdev_rx)) { mac_drop_pkt(mp, "failed to get thread ref"); mac_drop_chain(mpnext, "failed to get thread ref"); break; } if ((sdev->sd_tx_cksum & HCKSUM_IPHDRCKSUM) != 0) emul |= MAC_IPCKSUM_EMUL; if ((sdev->sd_tx_cksum & SIMNET_ULP_CKSUM) != 0) emul |= MAC_HWCKSUM_EMUL; if (sdev->sd_lso) emul |= MAC_LSO_EMUL; if (emul != 0) mac_hw_emul(&mp, NULL, NULL, emul); if (mp == NULL) { sdev->sd_stats.xmit_errors++; continue; } /* * Remember, we are emulating a real NIC here; the * checksum flags can't make the trip across the link. */ DB_CKSUMFLAGS(mp) = 0; /* Use taskq for pkt receive to avoid kernel stack explosion */ mp->b_next = (mblk_t *)sdev_rx; if (ddi_taskq_dispatch(simnet_rxq, simnet_rx, mp, DDI_NOSLEEP) == DDI_SUCCESS) { sdev->sd_stats.xmit_count++; sdev->sd_stats.obytes += len; } else { simnet_thread_unref(sdev_rx); mp->b_next = NULL; freemsg(mp); sdev_rx->sd_stats.recv_errors++; } } simnet_thread_unref(sdev); simnet_thread_unref(sdev_rx); 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 boolean_t simnet_m_getcapab(void *arg, mac_capab_t cap, void *cap_data) { simnet_dev_t *sdev = arg; const uint_t tcp_cksums = HCKSUM_INET_FULL_V4 | HCKSUM_INET_PARTIAL; switch (cap) { case MAC_CAPAB_HCKSUM: { uint32_t *tx_cksum_flags = cap_data; *tx_cksum_flags = sdev->sd_tx_cksum; break; } case MAC_CAPAB_LSO: { mac_capab_lso_t *cap_lso = cap_data; if (sdev->sd_lso && (sdev->sd_tx_cksum & HCKSUM_IPHDRCKSUM) != 0 && (sdev->sd_tx_cksum & tcp_cksums) != 0) { /* * The LSO configuration is hardwried for now, * but there's no reason we couldn't also make * this configurable in the future. */ cap_lso->lso_flags = LSO_TX_BASIC_TCP_IPV4; cap_lso->lso_basic_tcp_ipv4.lso_max = SD_LSO_MAXLEN; break; } else { return (B_FALSE); } } default: return (B_FALSE); } return (B_TRUE); } 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 const struct ether_addr * mcastaddr_lookup(const simnet_dev_t *sdev, const uint8_t *addrp) { ASSERT(MUTEX_HELD(&sdev->sd_instlock)); for (uint_t i = 0; i < sdev->sd_mcastaddr_count; i++) { const struct ether_addr *maddrp = &sdev->sd_mcastaddrs[i]; if (bcmp(maddrp->ether_addr_octet, addrp, sizeof (maddrp->ether_addr_octet)) == 0) { return (maddrp); } } return (NULL); } static int simnet_multicst_add(simnet_dev_t *sdev, const struct ether_addr *eap) { ASSERT(MUTEX_HELD(&sdev->sd_instlock)); if ((eap->ether_addr_octet[0] & 01) == 0) { return (EINVAL); } if (sdev->sd_mcastaddr_count == SM_MAX_NUM_MCAST_ADDRS) { return (ENOSPC); } bcopy(eap, &sdev->sd_mcastaddrs[sdev->sd_mcastaddr_count], sizeof (*eap)); sdev->sd_mcastaddr_count++; return (0); } static int simnet_multicst_rm(simnet_dev_t *sdev, const struct ether_addr *eap) { ASSERT(MUTEX_HELD(&sdev->sd_instlock)); for (uint_t i = 0; i < sdev->sd_mcastaddr_count; i++) { if (bcmp(eap, &sdev->sd_mcastaddrs[i], sizeof (*eap)) == 0) { for (i++; i < sdev->sd_mcastaddr_count; i++) { sdev->sd_mcastaddrs[i - 1] = sdev->sd_mcastaddrs[i]; } /* Zero-out the last entry as it is no longer valid. */ bzero(&sdev->sd_mcastaddrs[i - 1], sizeof (sdev->sd_mcastaddrs[0])); sdev->sd_mcastaddr_count--; return (0); } } return (EINVAL); } /* 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; struct ether_addr ea; int ret; bcopy(addrp, ea.ether_addr_octet, sizeof (ea.ether_addr_octet)); mutex_enter(&sdev->sd_instlock); if (add) { ret = simnet_multicst_add(sdev, &ea); } else { ret = simnet_multicst_rm(sdev, &ea); } ASSERT3U(sdev->sd_mcastaddr_count, <=, SM_MAX_NUM_MCAST_ADDRS); mutex_exit(&sdev->sd_instlock); return (ret); } 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_wifi(simnet_dev_t *sdev, const char *name, const uint_t len, const void *val) { simnet_wifidev_t *wdev = sdev->sd_wifidev; long result; if (strcmp(name, "_wl_esslist") == 0) { if (val == NULL) return (EINVAL); return (set_wl_esslist_priv_prop(wdev, len, val)); } else if (strcmp(name, "_wl_connected") == 0) { if (val == NULL) return (EINVAL); (void) ddi_strtol(val, (char **)NULL, 0, &result); wdev->swd_linkstatus = ((result == 1) ? WL_CONNECTED:WL_NOTCONNECTED); return (0); } return (EINVAL); } /* ARGSUSED */ static int simnet_set_priv_prop_ether(simnet_dev_t *sdev, const char *name, const uint_t len, const void *val) { if (strcmp(name, SD_PROP_RX_IP_CKSUM) == 0) { if (val == NULL) return (EINVAL); if (strcmp(val, "off") == 0) { sdev->sd_rx_cksum &= ~HCKSUM_IPHDRCKSUM; } else if (strcmp(val, "on") == 0) { sdev->sd_rx_cksum |= HCKSUM_IPHDRCKSUM; } else { return (EINVAL); } return (0); } else if (strcmp(name, SD_PROP_TX_ULP_CKSUM) == 0) { if (val == NULL) return (EINVAL); /* * Remember, full and partial checksum are mutually * exclusive. */ if (strcmp(val, "none") == 0) { sdev->sd_tx_cksum &= ~HCKSUM_INET_FULL_V4; } else if (strcmp(val, "fullv4") == 0) { sdev->sd_tx_cksum &= ~HCKSUM_INET_PARTIAL; sdev->sd_tx_cksum |= HCKSUM_INET_FULL_V4; } else if (strcmp(val, "partial") == 0) { sdev->sd_tx_cksum &= HCKSUM_INET_FULL_V4; sdev->sd_tx_cksum |= HCKSUM_INET_PARTIAL; } else { return (EINVAL); } return (0); } else if (strcmp(name, SD_PROP_TX_IP_CKSUM) == 0) { if (val == NULL) return (EINVAL); if (strcmp(val, "off") == 0) { sdev->sd_tx_cksum &= ~HCKSUM_IPHDRCKSUM; } else if (strcmp(val, "on") == 0) { sdev->sd_tx_cksum |= HCKSUM_IPHDRCKSUM; } else { return (EINVAL); } return (0); } else if (strcmp(name, SD_PROP_LSO) == 0) { if (val == NULL) return (EINVAL); if (strcmp(val, "off") == 0) { sdev->sd_lso = B_FALSE; } else if (strcmp(val, "on") == 0) { sdev->sd_lso = B_TRUE; } else { return (EINVAL); } return (0); } return (ENOTSUP); } static int simnet_setprop_wifi(simnet_dev_t *sdev, const char *name, const mac_prop_id_t num, const uint_t len, const void *val) { int err = 0; simnet_wifidev_t *wdev = sdev->sd_wifidev; switch (num) { case MAC_PROP_WL_ESSID: { int i; wl_ess_conf_t *wls; (void) memcpy(&wdev->swd_essid, val, 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, val, 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_wifi(sdev, name, len, val); break; default: err = EINVAL; break; } return (err); } static int simnet_setprop_ether(simnet_dev_t *sdev, const char *name, const mac_prop_id_t num, const uint_t len, const void *val) { int err = 0; switch (num) { case MAC_PROP_PRIVATE: err = simnet_set_priv_prop_ether(sdev, name, len, val); break; default: err = EINVAL; break; } return (err); } static int simnet_m_setprop(void *arg, const char *name, mac_prop_id_t num, const uint_t len, const void *val) { simnet_dev_t *sdev = arg; int err = 0; uint32_t mtu; switch (num) { case MAC_PROP_MTU: (void) memcpy(&mtu, val, sizeof (mtu)); if (mtu > ETHERMIN && mtu < SIMNET_MAX_MTU) return (mac_maxsdu_update(sdev->sd_mh, mtu)); else return (EINVAL); default: break; } switch (sdev->sd_type) { case DL_ETHER: err = simnet_setprop_ether(sdev, name, num, len, val); break; case DL_WIFI: err = simnet_setprop_wifi(sdev, name, num, len, val); break; default: err = EINVAL; break; } /* * We may have modified the configuration of hardware * offloads. Make sure to renegotiate capabilities with the * upstream clients. */ mac_capab_update(sdev->sd_mh); return (err); } static int simnet_get_priv_prop_wifi(const simnet_dev_t *sdev, const char *name, const uint_t len, void *val) { simnet_wifidev_t *wdev = sdev->sd_wifidev; int ret, value; if (strcmp(name, "_wl_esslist") == 0) { /* Returns num of _wl_ess_conf_t that have been set */ value = wdev->swd_esslist_num; } else if (strcmp(name, "_wl_connected") == 0) { value = ((wdev->swd_linkstatus == WL_CONNECTED) ? 1:0); } else { return (ENOTSUP); } ret = snprintf(val, len, "%d", value); if (ret < 0 || ret >= len) return (EOVERFLOW); return (0); } static int simnet_get_priv_prop_ether(const simnet_dev_t *sdev, const char *name, const uint_t len, void *val) { int ret; char *value; if (strcmp(name, SD_PROP_RX_IP_CKSUM) == 0) { if ((sdev->sd_rx_cksum & HCKSUM_IPHDRCKSUM) != 0) { value = "on"; } else { value = "off"; } } else if (strcmp(name, SD_PROP_TX_ULP_CKSUM) == 0) { if ((sdev->sd_tx_cksum & HCKSUM_INET_FULL_V4) != 0) { value = "fullv4"; } else if ((sdev->sd_tx_cksum & HCKSUM_INET_PARTIAL) != 0) { value = "partial"; } else { value = "none"; } } else if (strcmp(name, SD_PROP_TX_IP_CKSUM) == 0) { if ((sdev->sd_tx_cksum & HCKSUM_IPHDRCKSUM) != 0) { value = "on"; } else { value = "off"; } } else if (strcmp(name, SD_PROP_LSO) == 0) { value = sdev->sd_lso ? "on" : "off"; } else { return (ENOTSUP); } ret = snprintf(val, len, "%s", value); if (ret < 0 || ret >= len) { return (EOVERFLOW); } return (0); } static int simnet_getprop_wifi(const simnet_dev_t *sdev, const char *name, const mac_prop_id_t num, const uint_t len, void *val) { const simnet_wifidev_t *wdev = sdev->sd_wifidev; int err = 0; switch (num) { case MAC_PROP_WL_ESSID: (void) memcpy(val, &wdev->swd_essid, sizeof (wl_essid_t)); break; case MAC_PROP_WL_BSSID: (void) memcpy(val, &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_LINKSTATUS: (void) memcpy(val, &wdev->swd_linkstatus, sizeof (wdev->swd_linkstatus)); break; case MAC_PROP_WL_ESS_LIST: { wl_ess_conf_t *w_ess_conf; ((wl_ess_list_t *)val)->wl_ess_list_num = wdev->swd_esslist_num; /* LINTED E_BAD_PTR_CAST_ALIGN */ w_ess_conf = (wl_ess_conf_t *)((char *)val + offsetof(wl_ess_list_t, wl_ess_list_ess)); for (uint_t 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_RSSI: *(wl_rssi_t *)val = wdev->swd_rssi; break; case MAC_PROP_WL_RADIO: *(wl_radio_t *)val = 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_wifi(sdev, name, len, val); break; default: err = ENOTSUP; break; } return (err); } static int simnet_getprop_ether(const simnet_dev_t *sdev, const char *name, const mac_prop_id_t num, const uint_t len, void *val) { int err = 0; switch (num) { case MAC_PROP_PRIVATE: err = simnet_get_priv_prop_ether(sdev, name, len, val); break; default: err = ENOTSUP; break; } return (err); } static int simnet_m_getprop(void *arg, const char *name, const mac_prop_id_t num, const uint_t len, void *val) { const simnet_dev_t *sdev = arg; int err = 0; switch (sdev->sd_type) { case DL_ETHER: err = simnet_getprop_ether(sdev, name, num, len, val); break; case DL_WIFI: err = simnet_getprop_wifi(sdev, name, num, len, val); break; default: err = EINVAL; break; } return (err); } static void simnet_priv_propinfo_wifi(const char *name, mac_prop_info_handle_t prh) { char valstr[MAXNAMELEN]; bzero(valstr, sizeof (valstr)); if (strcmp(name, "_wl_esslist") == 0) { (void) snprintf(valstr, sizeof (valstr), "%d", 0); } if (strlen(valstr) > 0) mac_prop_info_set_default_str(prh, valstr); } static void simnet_propinfo_wifi(const char *name, const mac_prop_id_t num, mac_prop_info_handle_t prh) { switch (num) { case MAC_PROP_WL_BSSTYPE: case MAC_PROP_WL_ESS_LIST: case MAC_PROP_WL_SUPPORTED_RATES: case MAC_PROP_WL_RSSI: mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); break; case MAC_PROP_PRIVATE: simnet_priv_propinfo_wifi(name, prh); break; } } static void simnet_priv_propinfo_ether(const char *name, mac_prop_info_handle_t prh) { if (strcmp(name, SD_PROP_RX_IP_CKSUM) == 0 || strcmp(name, SD_PROP_TX_ULP_CKSUM) == 0 || strcmp(name, SD_PROP_TX_IP_CKSUM) == 0 || strcmp(name, SD_PROP_LSO) == 0) { mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW); } if (strcmp(name, SD_PROP_TX_ULP_CKSUM) == 0) { mac_prop_info_set_default_str(prh, "none"); } if (strcmp(name, SD_PROP_RX_IP_CKSUM) == 0 || strcmp(name, SD_PROP_TX_IP_CKSUM) == 0 || strcmp(name, SD_PROP_LSO) == 0) { mac_prop_info_set_default_str(prh, "off"); } } static void simnet_propinfo_ether(const char *name, const mac_prop_id_t num, mac_prop_info_handle_t prh) { switch (num) { case MAC_PROP_PRIVATE: simnet_priv_propinfo_ether(name, prh); break; } } static void simnet_m_propinfo(void *arg, const char *name, const mac_prop_id_t num, const mac_prop_info_handle_t prh) { simnet_dev_t *sdev = arg; switch (sdev->sd_type) { case DL_ETHER: simnet_propinfo_ether(name, num, prh); break; case DL_WIFI: simnet_propinfo_wifi(name, num, prh); break; } }