vnic_dev.c revision 1a41ca239310955ae95b2569b707432432a58580
/*
* 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
* 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 (c) 2014, Joyent, Inc. All rights reserved.
*/
#include <sys/sysmacros.h>
#include <sys/mac_provider.h>
#include <sys/mac_client.h>
#include <sys/mac_client_priv.h>
#include <sys/mac_ether.h>
#include <sys/vnic_impl.h>
#include <sys/mac_flow_impl.h>
/*
* Note that for best performance, the VNIC is a passthrough design.
* For each VNIC corresponds a MAC client of the underlying MAC (lower MAC).
* This MAC client is opened by the VNIC driver at VNIC creation,
* and closed when the VNIC is deleted.
* When a MAC client of the VNIC itself opens a VNIC, the MAC layer
* (upper MAC) detects that the MAC being opened is a VNIC. Instead
* of allocating a new MAC client, it asks the VNIC driver to return
* the lower MAC client handle associated with the VNIC, and that handle
* is returned to the upper MAC client directly. This allows access
* by upper MAC clients of the VNIC to have direct access to the lower
* MAC client for the control path and data path.
*
* Due to this passthrough, some of the entry points exported by the
* VNIC driver are never directly invoked. These entry points include
* vnic_m_start, vnic_m_stop, vnic_m_promisc, vnic_m_multicst, etc.
*
* VNICs support multiple upper mac clients to enable support for
* multiple MAC addresses on the VNIC. When the VNIC is created the
* initial mac client is the primary upper mac. Any additional mac
* clients are secondary macs.
*/
static int vnic_m_start(void *);
static void vnic_m_stop(void *);
static int vnic_m_promisc(void *, boolean_t);
static int vnic_m_unicst(void *, const uint8_t *);
const void *);
static void vnic_m_propinfo(void *, const char *, mac_prop_id_t,
static void vnic_notify_cb(void *, mac_notify_type_t);
static void vnic_cleanup_secondary_macs(vnic_t *, int);
static kmem_cache_t *vnic_cache;
static uint_t vnic_count;
#define ANCHOR_VNIC_MIN_MTU 576
#define ANCHOR_VNIC_MAX_MTU 9000
/* hash of VNICs (vnic_t's), keyed by VNIC id */
static mod_hash_t *vnic_hash;
#define VNIC_HASHSZ 64
#define VNIC_M_CALLBACK_FLAGS \
static mac_callbacks_t vnic_m_callbacks = {
NULL,
NULL,
NULL,
};
void
vnic_dev_init(void)
{
vnic_count = 0;
}
void
vnic_dev_fini(void)
{
ASSERT(vnic_count == 0);
}
vnic_dev_count(void)
{
return (vnic_count);
}
static vnic_ioc_diag_t
{
switch (diag) {
case MAC_DIAG_MACADDR_NIC:
return (VNIC_IOC_DIAG_MACADDR_NIC);
case MAC_DIAG_MACADDR_INUSE:
return (VNIC_IOC_DIAG_MACADDR_INUSE);
case MAC_DIAG_MACADDR_INVALID:
return (VNIC_IOC_DIAG_MACADDR_INVALID);
return (VNIC_IOC_DIAG_MACADDRLEN_INVALID);
return (VNIC_IOC_DIAG_MACFACTORYSLOTINVALID);
return (VNIC_IOC_DIAG_MACFACTORYSLOTUSED);
return (VNIC_IOC_DIAG_MACFACTORYSLOTALLUSED);
return (VNIC_IOC_DIAG_MACFACTORYNOTSUP);
return (VNIC_IOC_DIAG_MACPREFIX_INVALID);
return (VNIC_IOC_DIAG_MACPREFIXLEN_INVALID);
case MAC_DIAG_MACNO_HWRINGS:
return (VNIC_IOC_DIAG_NO_HWRINGS);
default:
return (VNIC_IOC_DIAG_NONE);
}
}
static int
{
int err;
if (flags & VNIC_IOC_CREATE_NODUPCHECK)
switch (vnic_addr_type) {
case VNIC_MAC_ADDR_TYPE_FIXED:
case VNIC_MAC_ADDR_TYPE_VRID:
/*
* The MAC address value to assign to the VNIC
* is already provided in mac_addr_arg. addr_len_ptr_arg
* already contains the MAC address length.
*/
break;
/*
* Random MAC address. There are two sub-cases:
*
* 1 - If mac_len == 0, a new MAC address is generated.
* The length of the MAC address to generated depends
* on the type of MAC used. The prefix to use for the MAC
* address is stored in the most significant bytes
* of the mac_addr argument, and its length is specified
* by the mac_prefix_len argument. This prefix can
* correspond to a IEEE OUI in the case of Ethernet,
* for example.
*
* 2 - If mac_len > 0, the address was already picked
* randomly, and is now passed back during VNIC
* re-creation. The mac_addr argument contains the MAC
* address that was generated. We distinguish this
* case from the fixed MAC address case, since we
* want the user consumers to know, when they query
* the list of VNICs, that a VNIC was assigned a
* random MAC address vs assigned a fixed address
* specified by the user.
*/
/*
* If it's a pre-generated address, we're done. mac_addr_arg
* and addr_len_ptr_arg already contain the MAC address
* value and length.
*/
if (*addr_len_ptr_arg > 0)
break;
/* generate a new random MAC address */
return (err);
}
break;
if (err != 0) {
return (err);
}
break;
case VNIC_MAC_ADDR_TYPE_AUTO:
/* first try to allocate a factory MAC address */
if (err == 0) {
break;
}
/*
* Allocating a factory MAC address failed, generate a
* random MAC address instead.
*/
return (err);
}
break;
/*
* We get the address here since we copy it in the
* vnic's vn_addr.
* We can't ask for hardware resources since we
* don't currently support hardware classification
* for these MAC clients.
*/
if (req_hwgrp_flag) {
return (ENOTSUP);
}
break;
}
if (err != 0) {
if (vnic_addr_type == VNIC_MAC_ADDR_TYPE_FACTORY) {
/* release factory MAC address */
}
}
return (err);
}
/*
* Create a new VNIC upon request from administrator.
* Returns 0 on success, an errno on failure.
*/
/* ARGSUSED */
int
{
int err;
char vnic_name[MAXNAMELEN];
const mac_info_t *minfop;
/* does a VNIC with the same id already exist? */
(mod_hash_val_t *)&vnic);
if (err == 0) {
return (EEXIST);
}
return (ENOMEM);
}
if (!is_anchor) {
if (linkid == DATALINK_INVALID_LINKID) {
goto bail;
}
/*
* Open the lower MAC and assign its initial bandwidth and
* MAC address. We do this here during VNIC creation and
* do not wait until the upper MAC client open so that we
* can validate the VNIC creation parameters (bandwidth,
* MAC address, etc) and reserve a factory MAC address if
* one was requested.
*/
if (err != 0)
goto bail;
/*
* VNIC(vlan) over VNICs(vlans) is not supported.
*/
goto bail;
}
/* only ethernet support for now */
goto bail;
}
NULL);
if (err != 0)
goto bail;
/* assign a MAC address to the VNIC */
if (err != 0) {
goto bail;
}
/* register to receive notification from underlying MAC */
vnic);
/*
* Set the initial VNIC capabilities. If the VNIC is created
* over MACs which does not support nactive vlan, disable
* VNIC's hardware checksum capability if its VID is not 0,
* since the underlying MAC would get the hardware checksum
* offset wrong in case of VLAN packets.
*/
vnic->vn_hcksum_txflags = 0;
} else {
vnic->vn_hcksum_txflags = 0;
}
}
/* register with the MAC module */
goto bail;
if (!is_anchor) {
/*
* If this is a VNIC based VLAN, then we check for the
* margin unless it has been created with the force
* flag. If we are configuring a VLAN over an etherstub,
* we don't check the margin even if force is not set.
*/
if (vid != VLAN_ID_NONE)
/*
* As the current margin size of the underlying mac is
* used to determine the margin size of the VNIC
* itself, request the underlying mac not to change
* to a smaller margin size.
*/
} else {
if (err != 0) {
goto bail;
}
}
} else {
}
if (err != 0) {
goto bail;
}
/* Set the VNIC's MAC in the client */
if (!is_anchor) {
}
if (err != 0) {
goto bail;
}
}
}
if (err != 0) {
goto bail;
}
/* add new VNIC to hash table */
vnic_count++;
return (0);
bail:
if (!is_anchor) {
}
return (err);
}
/*
* Modify the properties of an existing VNIC.
*/
/* ARGSUSED */
int
{
(mod_hash_val_t *)&vnic) != 0) {
return (ENOENT);
}
return (0);
}
/* ARGSUSED */
int
{
int rc;
(mod_hash_val_t *)&vnic) != 0) {
return (ENOENT);
}
return (rc);
}
/*
* We cannot unregister the MAC yet. Unregistering would
* free up mac_impl_t which should not happen at this time.
* So disable mac_impl_t by calling mac_disable(). This will prevent
* any new claims on mac_impl_t.
*/
crgetzoneid(credp));
return (rc);
}
vnic_count--;
/*
* XXX-nicolas shouldn't have a void cast here, if it's
* expected that the function will never fail, then we should
* have an ASSERT().
*/
/*
* Check if MAC address for the vnic was obtained from the
* factory MAC addresses. If yes, release it.
*/
vnic->vn_slot_id);
}
}
return (0);
}
/* ARGSUSED */
mblk_t *
{
/*
* This function could be invoked for an anchor VNIC when sending
* broadcast and multicast packets, and unicast packets which did
* not match any local known destination.
*/
return (NULL);
}
/*ARGSUSED*/
static void
{
}
/*
* This entry point cannot be passed-through, since it is invoked
* for the per-VNIC kstats which must be exported independently
* of the existence of VNIC MAC clients.
*/
static int
{
int rval = 0;
/*
* It's an anchor VNIC, which does not have any
* statistics in itself.
*/
return (ENOTSUP);
}
/*
* ENOTSUP must be reported for unsupported stats, the VNIC
* driver reports a subset of the stats that would
* be returned by a real piece of hardware.
*/
switch (stat) {
case MAC_STAT_LINK_STATE:
case MAC_STAT_LINK_UP:
case MAC_STAT_PROMISC:
case MAC_STAT_IFSPEED:
case MAC_STAT_MULTIRCV:
case MAC_STAT_MULTIXMT:
case MAC_STAT_BRDCSTRCV:
case MAC_STAT_BRDCSTXMT:
case MAC_STAT_OPACKETS:
case MAC_STAT_OBYTES:
case MAC_STAT_IERRORS:
case MAC_STAT_OERRORS:
case MAC_STAT_RBYTES:
case MAC_STAT_IPACKETS:
break;
default:
}
return (rval);
}
/*
* Invoked by the upper MAC to retrieve the lower MAC client handle
* corresponding to a VNIC. A pointer to this function is obtained
* by the upper MAC via capability query.
*
* XXX-nicolas Note: this currently causes all VNIC MAC clients to
* receive the same MAC client handle for the same VNIC. This is ok
* as long as we have only one VNIC MAC client which sends and
* receives data, but we don't currently enforce this at the MAC layer.
*/
static void *
vnic_mac_client_handle(void *vnic_arg)
{
}
/*
* Invoked when updating the primary MAC so that the secondary MACs are
* kept in sync.
*/
static void
{
int i;
}
}
/*
* Return information about the specified capability.
*/
/* ARGSUSED */
static boolean_t
{
switch (cap) {
case MAC_CAPAB_HCKSUM: {
break;
}
case MAC_CAPAB_VNIC: {
/*
* It's an anchor VNIC, we don't have an underlying
* NIC and MAC client handle.
*/
return (B_FALSE);
}
if (vnic_capab != NULL) {
}
break;
}
case MAC_CAPAB_ANCHOR_VNIC: {
/* since it's an anchor VNIC we don't have lower mac handle */
return (B_TRUE);
}
return (B_FALSE);
}
case MAC_CAPAB_NO_NATIVEVLAN:
return (B_FALSE);
case MAC_CAPAB_NO_ZCOPY:
return (B_TRUE);
case MAC_CAPAB_VRRP: {
if (vrrp_capab != NULL)
return (B_TRUE);
}
return (B_FALSE);
}
default:
return (B_FALSE);
}
return (B_TRUE);
}
/* ARGSUSED */
static int
vnic_m_start(void *arg)
{
return (0);
}
/* ARGSUSED */
static void
vnic_m_stop(void *arg)
{
}
/* ARGSUSED */
static int
{
return (0);
}
/* ARGSUSED */
static int
{
return (0);
}
static int
{
}
static void
{
int i;
/* Remove existing secondaries (primary is at 0) */
for (i = 1; i <= cnt; i++) {
/* unicast handle might not have been set yet */
vn->vn_mu_handles[i]);
}
vn->vn_nhandles = 0;
}
/*
* Setup secondary MAC addresses on the vnic. Due to limitations in the mac
* code, each mac address must be associated with a mac_client (and the
* flow that goes along with the client) so we need to create those clients
* here.
*/
static int
{
int i, err;
char primary_name[MAXNAMELEN];
/* First, remove pre-existing secondaries */
msa->ms_addrcnt = 0;
/*
* Now add the new secondary MACs
* Recall that the primary MAC address is the first element.
* The secondary clients are named after the primary with their
* index to distinguish them.
*/
char secondary_name[MAXNAMELEN];
"%s%02d", primary_name, i);
if (err != 0) {
/* Remove any that we successfully added */
vnic_cleanup_secondary_macs(vn, --i);
return (err);
}
/*
* Assign a MAC address to the VNIC
*
* Normally this would be done with vnic_unicast_add but since
* we know these are fixed adddresses, and since we need to
* save this in the proper array slot, we bypass that function
* and go direct.
*/
if (err != 0) {
/* Remove any that we successfully added */
return (err);
}
/*
* Setup the secondary the same way as the primary (i.e.
* etc.), the promisc list, and the resource controls).
*/
}
return (0);
}
static int
{
int i;
if (pr_valsize < sizeof (msa))
return (EINVAL);
/* Get existing addresses (primary is at 0) */
}
return (0);
}
/*
*/
/*ARGSUSED*/
static int
{
int err = 0;
switch (pr_num) {
case MAC_PROP_MTU: {
/* allow setting MTU only on an etherstub */
return (err);
if (pr_valsize < sizeof (mtu)) {
break;
}
break;
}
break;
}
case MAC_PROP_SECONDARY_ADDRS: {
break;
}
default:
break;
}
return (err);
}
/* ARGSUSED */
static int
{
int ret = 0;
switch (pr_num) {
case MAC_PROP_SECONDARY_ADDRS:
break;
default:
break;
}
return (ret);
}
/* ARGSUSED */
{
/* MTU setting allowed only on an etherstub */
return;
switch (pr_num) {
case MAC_PROP_MTU:
break;
}
}
int
{
int err;
/* Make sure that the VNIC link is visible from the caller's zone. */
return (ENOENT);
(mod_hash_val_t *)&vnic);
if (err != 0) {
return (ENOENT);
}
info->vn_mac_prefix_len = 0;
return (0);
}
static void
{
/*
* Do not deliver notifications if the vnic is not fully initialized
* or is in process of being torn down.
*/
if (!vnic->vn_enabled)
return;
switch (type) {
case MAC_NOTE_UNICST:
/*
* Only the VLAN VNIC needs to be notified with primary MAC
* address change.
*/
return;
/* the unicast MAC address value */
/* notify its upper layer MAC about MAC address change */
break;
case MAC_NOTE_LINK:
break;
default:
break;
}
}