/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2016 Joyent, Inc. * Copyright 2022 MNX Cloud, Inc. */ /* * Overlay Devices * * Overlay devices provide a means for creating overlay networks, a means of * multiplexing multiple logical, isolated, and discrete layer two and layer * three networks on top of one physical network. * * In general, these overlay devices encapsulate the logic to answer two * different questions: * * 1) How should I transform a packet to put it on the wire? * 2) Where should I send a transformed packet? * * Each overlay device is presented to the user as a GLDv3 device. While the * link itself cannot have an IP interface created on top of it, it allows for * additional GLDv3 devices, such as a VNIC, to be created on top of it which * can be plumbed up with IP interfaces. * * * -------------------- * General Architecture * -------------------- * * The logical overlay device that a user sees in dladm(8) is a combination of * two different components that work together. The first component is this * kernel module, which is responsible for answering question one -- how should * I transform a packet to put it on the wire. * * The second component is what we call the virtual ARP daemon, or varpd. It is * a userland component that is responsible for answering the second question -- * Where should I send a transformed packet. Instances of the kernel overlay * GLDv3 device ask varpd the question of where should a packet go. * * The split was done for a few reasons. Importantly, we wanted to keep the act * of generating encapsulated packets in the kernel so as to ensure that the * general data path was fast and also kept simple. On the flip side, while the * question of where should something go may be simple, it may often be * complicated and need to interface with several different external or * distributed systems. In those cases, it's simpler to allow for the full * flexibility of userland to be brought to bear to solve that problem and in * general, the path isn't very common. * * The following is what makes up the logical overlay device that a user would * create with dladm(8). * * Kernel Userland * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * . +--------+ +--------+ +--------+ . . . * . | VNIC 0 | | VNIC 1 | | VNIC 2 | . . . * . +--------+ +--------+ +--------+ . . . * . | | | . . . * . | | | . . . * . +------------+-----------+ . . . * . | . . /dev/overlay . * . +--------------+ . . . +------------+ . * . | | . . . | | . * . | Overlay |======*=================| Virtual | . * . | GLDv3 Device |========================| ARP Daemon | . * . | | . . | | . * . +--------------+ . . +------------+ . * . | . . | . * . | . . | . * . +----------------+ . . +--------+ . * . | Overlay | . . | varpd | . * . | Encapsulation | . . | Lookup | . * . | Plugin | . . | Plugin | . * . +----------------+ . . +--------+ . * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * * * This image shows the two different components and where they live. * Importantly, it also shows that both the kernel overlay device and the * userland varpd both support plugins. The plugins actually implement the * things that users care about and the APIs have been designed to try to * minimize the amount of things that a module writer needs to worry about it. * * IDENTIFIERS * * Every overlay device is defined by a unique identifier which is the overlay * identifier. Its purpose is similar to that of a VLAN identifier, it's a * unique number that is used to differentiate between different entries on the * wire. * * ENCAPSULATION * * An overlay encapsulation plugin is a kernel miscellaneous module whose * purpose is to contain knowledge about how to transform packets to put them * onto the wire and to take them off. An example of an encapsulation plugin is * vxlan. It's also how support for things like nvgre or geneve would be brought * into the system. * * Each encapsulation plugins defines a series of operation vectors and * properties. For the full details on everything they should provide, please * read uts/common/sys/overlay_plugin.h. The encapsulation plugin is responsible * for telling the system what information is required to send a packet. For * example, vxlan is defined to send everything over a UDP packet and therefore * requires a port and an IP address, while nvgre on the other hand is its own * IP type and therefore just requires an IP address. In addition, it also * provides information about the kind of socket that should be created. This is * used by the kernel multiplexor, more of that in the Kernel Components * section. * * LOOKUPS * * The kernel communicates requests for lookups over the character device * /dev/overlay. varpd is responsible for listening for requests on that device * and answering them. The character device is specific to the target path and * varpd. * * Much as the kernel overlay module handles the bulk of the scaffolding but * leaves the important work to the encapsulation plugin, varpd provides a * similar role and leaves the full brunt of lookups to a userland dynamic * shared object which implements the logic of lookups. * * Each lookup plugin defines a series of operation vectors and properties. For * the full details on everything that they should provide, please read * lib/varpd/libvarpd/libvarpd_provider.h. Essentially, they are given a MAC * address and asked to give an address on the physical network that it should * be sent to. In addition, they handle questions related to how to handle * things like broadcast and multicast traffic, etc. * * ---------- * Properties * ---------- * * A device from a dladm perspective has a unique set of properties that are * combined from three different sources: * * 1) Generic properties that every overlay device has * 2) Properties that are specific to the encapsulation plugin * 3) Properties that are specific to the lookup plugin * * All of these are exposed in a single set of properties in dladm. Note that * these are not necessarily traditional link properties. However, if something * is both a traditional GLDv3 link property, say the MTU of a device, and a * specific property here, than the driver ensures that all existing GLDv3 * specific means of manipulating it are used and wraps up its private property * interfaces to ensure that works. * * Properties in the second and third category are prefixed with the name of * their module. For example, the vxlan encapsulation module has a property * called the 'listen_ip'. This property would show up in dladm as * 'vxlan/listen_ip'. This allows different plugins to both use similar names * for similar properties and to also have independent name spaces so that * overlapping names do not conflict with anything else. * * While the kernel combines both sets one and two into a single coherent view, * it does not do anything with respect to the properties that are owned by the * lookup plugin -- those are owned wholly by varpd. Instead, libdladm is in * charge of bridging these two worlds into one magical experience for the user. * It carries the burden of knowing about both overlay specific and varpd * specific properties. Importantly, we want to maintain this distinction. We * don't want to treat the kernel as an arbitrary key/value store for varpd and * we want the kernel to own its own data and not have to ask userland for * information that it owns. * * Every property in the system has the following attributes: * * o A name * o A type * o A size * o Permissions * o Default value * o Valid value ranges * o A value * * Everything except for the value is obtained by callers through the propinfo * callbacks and a property has a maximum size of OVERLAY_PROP_SIZEMAX, * currently 256 bytes. * * The following are the supported types of properties: * * OVERLAY_PROP_T_INT * * A signed integer, its length is 8 bytes, corresponding to a * int64_t. * * OVERLAY_PROP_T_UINT * * An unsigned integer, its length is 8 bytes, corresponding to a * uint64_t. * * OVERLAY_PROP_T_IP * * A struct in6_addr, it has a fixed size. * * OVERLAY_PROP_T_STRING * * A null-terminated character string encoded in either ASCII or * UTF-8. Note that the size of the string includes the null * terminator. * * The next thing that we apply to a property is its permission. The permissions * are put together by the bitwise or of the following flags and values. * * OVERLAY_PROP_PERM_REQ * * This indicates a required property. A property that is required * must be set by a consumer before the device can be created. If a * required property has a default property, this constraint is * loosened because the default property defines the value. * * OVERLAY_PORP_PERM_READ * * This indicates that a property can be read. All properties will * have this value set. * * OVERLAY_PROP_PERM_WRITE * * This indicates that a property can be written to and thus * updated by userland. Properties that are only intended to * display information, will not have OVERLAY_PROP_PERM_WRITE set. * * In addition, a few additional values are defined as a convenience to * consumers. The first, OVERLAY_PROP_PERM_RW, is a combination of * OVERLAY_PROP_PERM_READ and OVERLAY_PERM_PROP_WRITE. The second, * OVERLAY_PROP_PERM_RRW, is a combination of OVERLAY_PROP_PERM_REQ, * OVERLAY_PROP_PERM_READ, and OVERLAY_PROP_PERM_WRITE. The protection mode of a * property should generally be a constant across its lifetime. * * A property may optionally have a default value. If it does have a default * value, and that property is not set to be a different value, then the default * value is inherited automatically. It also means that if the default value is * acceptable, there is no need to set the value for a required property. For * example, the vxlan module has the vxlan/listen_port property which is * required, but has a default value of 4789 (the IANA assigned port). Because * of that default value, there is no need for it to be set. * * Finally, a property may declare a list of valid values. These valid values * are used for display purposes, they are not enforced by the broader system, * but merely allow a means for the information to be communicated to the user * through dladm(8). Like a default value, this is optional. * * The general scaffolding does not do very much with respect to the getting and * setting of properties. That is really owned by the individual plugins * themselves. * * ----------------------------- * Destinations and Plugin Types * ----------------------------- * * Both encapsulation and lookup plugins define the kinds of destinations that * they know how to support. There are three different pieces of information * that can be used to address to a destination currently, all of which is * summarized in the type overlay_point_t. Any combination of these is * supported. * * OVERLAY_PLUGIN_D_ETHERNET * * An Ethernet MAC address is required. * * OVERLAY_PLUGIN_D_IP * * An IP address is required. All IP addresses used by the overlay * system are transmitted as IPv6 addresses. IPv4 addresses can be * represented by using IPv4-mapped IPv6 addresses. * * OVERLAY_PLUGIN_D_PORT * * A TCP/UDP port is required. * * A kernel encapsulation plugin declares which of these that it requires, it's * a static set. On the other hand, a userland lookup plugin can be built to * support all of these or any combination thereof. It gets passed the required * destination type, based on the kernel encapsulation method, and then it makes * the determination as to whether or not it supports it. For example, the * direct plugin can support either an IP or both an IP and a port, it simply * doesn't display the direct/dest_port property in the cases where a port is * not required to support this. * * The user lookup plugins have two different modes of operation which * determines how they interact with the broader system and how look ups are * performed. These types are: * * OVERLAY_TARGET_POINT * * A point to point plugin has a single static definition for where * to send all traffic. Every packet in the system always gets sent * to the exact same destination which is programmed into the * kernel when the general device is activated. * * OVERLAY_TARGET_DYNAMIC * * A dynamic plugin does not have a single static definition. * Instead, for each destination, the kernel makes an asynchronous * request to varpd to determine where the packet should be routed, * and if a specific destination is found, then that destination is * cached in the overlay device's target cache. * * This distinction, while important for the general overlay device's operation, * is not important to the encapsulation plugins. They don't need to know about * any of these pieces. It's just a concern for varpd, the userland plugin, and * the general overlay scaffolding. * * When an overlay device is set to OVERLAY_TARGET_POINT, then it does not * maintain a target cache, and instead just keeps track of the destination and * always sends encapsulated packets to that address. When the target type is of * OVERLAY_TARGET_DYNAMIC, then the kernel maintains a cache of all such * destinations. These destinations are kept around in an instance of a * reference hash that is specific to the given overlay device. Entries in the * cache can be invalidated and replaced by varpd and its lookup plugins. * * ---------------------------------- * Kernel Components and Architecture * ---------------------------------- * * There are multiple pieces inside the kernel that work together, there is the * general overlay_dev_t structure, which is the logical GLDv3 device, but it * itself has references to things like an instance of an encapsulation plugin, * a pointer to a mux and a target cache. It can roughly be summarized in the * following image: * * +------------------+ * | global | * | overlay list | * | overlay_dev_list | * +------------------+ * | * | +-----------------------+ +---------------+ * +->| GLDv3 Device |----------->| GLDv3 Device | -> ... * | overlay_dev_t | | overlay_dev_t | * | | +---------------+ * | | * | mac_handle_t -----+---> GLDv3 handle to MAC * | datalink_id_t -----+---> Datalink ID used by DLS * | overlay_dev_flag_t ---+---> Device state * | uint_t -----+---> Current device MTU * | uint_t -----+---> In-progress RX operations * | uint_t -----+---> In-progress TX operations * | char[] -----+---> FMA degraded message * | void * -----+---> plugin private data * | overlay_target_t * ---+---------------------+ * | overlay_plugin_t * ---+---------+ | * +-----------------------+ | | * ^ | | * +--------------------+ | | | * | Kernel Socket | | | | * | Multiplexor | | | | * | overlay_mux_t | | | | * | | | | | * | avl_tree_t -+--+ | | * | uint_t -+--> socket family | | * | uint_t -+--> socket type | | * | uint_t -+--> socket protocol | | * | ksocket_t -+--> I/O socket | | * | struct sockaddr * -+--> ksocket address | | * | overlay_plugin_t --+--------+ | | * +--------------------+ | | | * | | | * +-------------------------+ | | | * | Encap Plugin |<--+-----------+ | * | overlay_plugin_t | | * | | | * | char * ---+--> plugin name | * | overlay_plugin_ops_t * -+--> plugin downcalls | * | char ** (props) ---+--> property list | * | uint_t ---+--> id length | * | overlay_plugin_flags_t -+--> plugin flags | * | overlay_plugin_dest_t --+--> destination type v * +-------------------------+ +-------------------------+ * | Target Cache | * | overlay_target_t | * | | * cache mode <--+- overlay_target_mode_t | * dest type <--+- overlay_plugin_dest_t | * cache flags <--+- overlay_target_flag_t | * varpd id <--+- uint64_t | * outstanding varpd reqs. <--+- uint_t | * OVERLAY_TARGET_POINT state <--+- overlay_target_point_t | * OVERLAY_TARGET_DYNAMIC state <-+---+- overlay_target_dyn_t | * | +-------------------------+ * +-----------------------+ * | * v * +-------------------------------+ +------------------------+ * | Target Entry |-->| Target Entry |--> ... * | overlay_target_entry_t | | overlay_target_entry_t | * | | +------------------------+ * | | * | overlay_target_entry_flags_t -+--> Entry flags * | uint8_t[ETHERADDRL] ---+--> Target MAC address * | overlay_target_point_t ---+--> Target underlay address * | mblk_t * ---+--> outstanding mblk head * | mblk_t * ---+--> outstanding mblk tail * | size_t ---+--> outstanding mblk size * +-------------------------------+ * * The primary entries that we care about are the overlay_dev_t, which * correspond to each overlay device that is created with dladm(8). Globally, * these devices are maintained in a simple list_t which is protected with a * lock. Hence, these include important information such as the mac_handle_t * and a datalink_id_t which is used to interact with the broader MAC and DLS * ecosystem. We also maintain additional information such as the current state, * outstanding operations, the mtu, and importantly, the plugin's private data. * This is the instance of an encapsulation plugin that gets created as part of * creating an overlay device. Another aspect of this is that the overlay_dev_t * also includes information with respect to FMA. For more information, see the * FMA section. * * Each overlay_dev_t has a pointer to a plugin, a mux, and a target. The plugin * is the encapsulation plugin. This allows the device to make downcalls into it * based on doing things like getting and setting properties. Otherwise, the * plugin itself is a fairly straightforward entity. They are maintained in an * (not pictured above) list. The plugins themselves mostly maintain things like * the static list of properties, what kind of destination they require, and the * operations vector. A given module may contain more if necessary. * * The next piece of the puzzle is the mux, or a multiplexor. The mux itself * maintains a ksocket and it is through the mux that we send and receive * message blocks. The mux represents a socket type and address, as well as a * plugin. Multiple overlay_dev_t devices may then share the same mux. For * example, consider the case where you have different instances of vxlan all on * the same underlay network. These would all logically share the same IP * address and port that packets are sent and received on; however, what differs * is the decapuslation ID. * * Each mux maintains a ksocket_t which is similar to a socket(3SOCKET). Unlike * a socket, we enable a direct callback on the ksocket. This means that * whenever a message block chain is received, rather than sitting there and * getting a callback in a context and kicking that back out to a taskq. Instead * data comes into the callback function overlay_mux_recv(). * * The mux is given encapsulated packets (via overlay_m_tx, the GLDv3 tx * function) to transmit. It receives encapsulated packets, decapsulates them to * determine the overlay identifier, looks up the given device that matches that * identifier, and then causes the broader MAC world to receive the packet with * a call to mac_rx(). * * Today, we don't do too much that's special with the ksocket; however, as * hardware is gaining understanding for these encapsulation protocols, we'll * probably want to think of better ways to get those capabilities passed down * and potentially better ways to program receive filters so they get directly * to us. Though, that's all fantasy future land. * * The next part of the puzzle is the target cache. The purpose of the target * cache is to cache where we should send a packet on the underlay network, * given its mac address. The target cache operates in two modes depending on * whether the lookup module was declared to OVERLAY_TARGET_POINT or * OVERLAY_TARGET_DYANMIC. * * In the case where the target cache has been programmed to be * OVERLAY_TARGET_POINT, then we only maintain a single overlay_target_point_t * which has the destination that we send everything, no matter the destination * mac address. * * On the other hand, when we have an instance of OVERLAY_TARGET_DYNAMIC, things * are much more interesting and as a result, more complicated. We primarily * store lists of overlay_target_entry_t's which are stored in both an avl tree * and a refhash_t. The primary look up path uses the refhash_t and the avl tree * is only used for a few of the target ioctls used to dump data such that we * can get a consistent iteration order for things like dladm show-overlay -t. * The key that we use for the reference hashtable is based on the mac address * in the cache and currently we just do a simple CRC32 to transform it into a * hash. * * Each entry maintains a set of flags to indicate the current status of the * request. The flags may indicate one of three states: that current cache entry * is valid, that the current cache entry has been directed to drop all output, * and that the current cache entry is invalid and may be being looked up. In * the case where it's valid, we just take the destination address and run with * it. * * If it's invalid and a lookup has not been made, then we start the process * that prepares a query that will make its way up to varpd. The cache entry * entry maintains a message block chain of outstanding message blocks and a * size. These lists are populated only when we don't know the answer as to * where should these be sent. The size entry is used to cap the amount of * outstanding data that we don't know the answer to. If we exceed a cap on the * amount of outstanding data (currently 1 Mb), then we'll drop any additional * packets. Once we get an answer indicating a valid destination, we transmit * any outstanding data to that place. For the full story on how we look that up * will be discussed in the section on the Target Cache Lifecycle. * * ------------------------ * FMA and Degraded Devices * ------------------------ * * Every kernel overlay device keeps track of its FMA state. Today in FMA we * cannot represent partitions between resources nor can we represent that a * given minor node of a pseudo device has failed -- if we degrade the overlay * device, then the entire dev_info_t is degraded. However, we still want to be * able to indicate to administrators that things may go wrong. * * To this end, we've added a notion of a degraded state to every overlay * device. This state is primarily dictated by userland and it can happen for * various reasons. Generally, because a userland lookup plugin has been * partitioned, or something has gone wrong such that there is no longer any * userland lookup module for a device, then we'll mark it degraded. * * As long as any of our minor instances is degraded, then we'll fire off the * FMA event to note that. Once the last degraded instance is no longer * degraded, then we'll end up telling FMA that we're all clean. * * To help administrators get a better sense of which of the various minor * devices is wrong, we store the odd_fmamsg[] character array. This character * array can be fetched with doing a dladm show-overlay -f. * * Note, that it's important that we do not update the link status of the * devices. We want to remain up as much as possible. By changing the link in a * degraded state, this may end up making things worse. We may still actually * have information in the target cache and if we mark the link down, that'll * result in not being able to use it. The reason being that this'll mark all * the downstream VNICs down which will go to IP and from there we end up * dealing with sadness. * * ----------------------- * Target Cache Life Cycle * ----------------------- * * This section only applies when we have a lookup plugin of * OVERLAY_TARGET_DYNAMIC. None of this applies to those of type * OVERLAY_TARGET_POINT. * * While we got into the target cache in the general architecture section, it's * worth going into more details as to how this actually works and showing some * examples and state machines. Recall that a target cache entry basically has * the following state transition diagram: * * Initial state * . . . . . . first access . . . varpd lookup enqueued * . . . * . . . * +-------+ . +----------+ . * | No |------*---->| Invalid |-------*----+ * | Entry | | Entry | | * +-------+ +----------+ | * varpd ^ ^ varpd | * invalidate | | drop | * . . . * * . . v * +-------+ | | +---------+ * | Entry |--->-----+ +----<----| Entry | * | Valid |<----------*---------<----| Pending |->-+ varpd * +-------+ . +---------+ * . . drop, but * . varpd ^ | other queued * . success | | entries * +-----+ * * When the table is first created, it is empty. As we attempt to lookup entries * and we find there is no entry at all, we'll create a new table entry for it. * At that point the entry is technically in an invalid state, that means that * we have no valid data from varpd. In that case, we'll go ahead and queue the * packet into the entry's pending chain, and queue a varpd lookup, setting the * OVERLAY_ENTRY_F_PENDING flag in the progress. * * If additional mblk_t's come in for this entry, we end up appending them to * the tail of the chain, if and only if, we don't exceed the threshold for the * amount of space they can take up. An entry remains pending until we get a * varpd reply. If varpd replies with a valid results, we move to the valid * entry state, and remove the OVERLAY_ENTRY_F_PENDING flag and set it with one * of OVERLAY_ENTRY_F_VALID or OVERLAY_ENTRY_F_DROP as appropriate. * * Once an entry is valid, it stays valid until user land tells us to invalidate * it with an ioctl or replace it, OVERLAY_TARG_CACHE_REMOE and * OVERLAY_TARG_CACHE_SET respectively. * * If the lookup fails with a call to drop the packet, then the next state is * determined by the state of the queue. If the set of outstanding entries is * empty, then we just transition back to the invalid state. If instead, the * set of outstanding entries is not empty, then we'll queue another entry and * stay in the same state, repeating this until the number of requests is * drained. * * The following images describes the flow of a given lookup and where the * overlay_target_entry_t is at any given time. * * +-------------------+ * | Invalid Entry | An entry starts off as an invalid entry * | de:ad:be:ef:00:00 | and only exists in the target cache. * +-------------------+ * * ~~~~ * * +---------------------+ * | Global list_t | A mblk_t comes in for an entry. We * | overlay_target_list | append it to the overlay_target_list. * +---------------------+ * | * v * +-------------------+ +-------------------+ * | Pending Entry |----->| Pending Entry |--->... * | 42:5e:1a:10:d6:2d | | de:ad:be:ef:00:00 | * +-------------------+ +-------------------+ * * ~~~~ * * +--------------------------+ * | /dev/overlay minor state | User land said that it would look up an * | overlay_target_hdl_t | entry for us. We remove it from the * +--------------------------+ global list and add it to the handle's * | outstanding list. * | * v * +-------------------+ +-------------------+ * | Pending Entry |----->| Pending Entry | * | 90:b8:d0:79:02:dd | | de:ad:be:ef:00:00 | * +-------------------+ +-------------------+ * * ~~~~ * * +-------------------+ * | Valid Entry | varpd returned an answer with * | de:ad:be:ef:00:00 | OVERLAY_IOC_RESPOND and the target cache * | 10.169.23.42:4789 | entry is now populated with a * +-------------------+ destination and marked as valid * * * The lookup mechanism is performed via a series of operations on the character * pseudo-device /dev/overlay. The only thing that uses this device is the * userland daemon varpd. /dev/overlay is a cloneable device, each open of it * granting a new minor number which maintains its own state. We maintain this * state so that way if an outstanding lookup was queued to something that * crashed or closed its handle without responding, we can know about this and * thus handle it appropriately. * * When a lookup is first created it's added to our global list of outstanding * lookups. To service requests, userland is required to perform an ioctl to ask * for a request. We will block it in the kernel a set amount of time waiting * for a request. When we give a request to a given minor instance of the * device, we remove it from the global list and append the request to the * device's list of outstanding entries, for the reasons we discussed above. * When a lookup comes in, we give user land a smaller amount of information * specific to that packet, the overlay_targ_lookup_t. It includes a request id * to identify this, and then the overlay id, the varpd id, the header and * packet size, the source and destination mac address, the SAP, and any * potential VLAN header. * * At that point, it stays in that outstanding list until one of two ioctls are * returned: OVERLAY_TARG_RESPOND or OVERLAY_TARG_DROP. During this time, * userland may also perform other operations. For example, it may use * OVERLAY_TARG_PKT to get a copy of this packet so it can perform more in-depth * analysis of what to do beyond what we gave it initially. This is useful for * providing proxy arp and the like. Finally, there are two other ioctls that * varpd can then do. The first is OVERLAY_TARG_INJECT which injects the * non-jumbo frame packet up into that mac device and OVERLAY_TARG_RESEND which * causes us to encapsulate and send out the packet they've given us. * * * Finally, through the target cache, several ioctls are provided to allow for * interrogation and management of the cache. They allow for individual entries * to be retrieved, set, or have the entire table flushed. For the full set of * ioctls here and what they do, take a look at uts/common/sys/overlay_target.h. * * ------------------ * Sample Packet Flow * ------------------ * * There's a lot of pieces here, hopefully an example of how this all fits * together will help clarify and elucidate what's going on. We're going to * first track an outgoing packet, eg. one that is sent from an IP interface on * a VNIC on top of an overlay device, and then we'll look at what it means to * respond to that. * * * +----------------+ +--------------+ +------------------+ * | IP/DLS send |------->| MAC sends it |----------->| mblk_t reaches | * | packet to MAC | | to the GLDv3 | | overlay GLDv3 tx | * +----------------+ | VNIC device | | overlay_m_tx() | * +--------------+ +------------------+ * | * . lookup . cache | * . drop . miss v * +---------+ . +--------+ . +------------------+ * | freemsg |<-----*-------| varpd |<---*------| Lookup each mblk | * | mblk_t | | lookup | | in the target | * +---------+ | queued | | cache | * ^ +--------+ +------------------+ * on send | | | cache * error . . * *. . lookup * . . hit * | | success v * | | +------------------+ * +-----------------+ +--------------->| call plugin | * | Send out | | ovpo_encap() to | * | overlay_mux_t's |<----------------------------------| get encap mblk_t | * | ksocket | +------------------+ * +-----------------+ * * The receive end point looks a little different and looks more like: * * +------------------+ +----------------+ +-----------+ * | mblk_t comes off |---->| enter netstack |--->| delivered |---+ * | the physical | | IP stack | | to | * . . direct * | device | +----------------+ | ksocket | | callback * +------------------+ +-----------+ | * . overlay id | * . not found v * +-----------+ . +-----------------+ +--------------------+ * | freemsg |<--*------| call plugin |<------| overlay_mux_recv() | * | mblk_t | | ovpo_decap() to | +--------------------+ * +-----------+ | decap mblk_t | * +-----------------+ * | * * . . overlay id * v found * +--------+ +----------------+ * | adjust |----->| call mac_rx | * | mblk_t | | on original | * +--------+ | decaped packet | * +----------------+ * * ------------------ * Netstack Awareness * ------------------ * * In the above image we note that this enters a netstack. Today the only * netstack that can be is the global zone as the overlay driver itself is not * exactly netstack aware. What this really means is that varpd cannot run in a * non-global zone and an overlay device cannot belong to a non-global zone. * Non-global zones can still have a VNIC assigned to them that's been created * over the overlay device the same way they would if it had been created over * an etherstub or a physical device. * * The majority of the work to make it netstack aware is straightforward and the * biggest thing is to create a netstack module that allows us to hook into * netstack (and thus zone) creation and destruction. From there, we need to * amend the target cache lookup routines that we discussed earlier to not have * a global outstanding list and a global list of handles, but rather, one per * netstack. * * For the mux, we'll need to open the ksocket in the context of the zone, we * can likely do this with a properly composed credential, but we'll need to do * some more work on that path. Finally, we'll want to make sure the dld ioctls * are aware of the zoneid of the caller and we use that appropriately and store * it in the overlay_dev_t. * * ----------- * GLDv3 Notes * ----------- * * The overlay driver implements a GLDv3 device. Parts of GLDv3 are more * relevant and other parts are much less relevant for us. For example, the * GLDv3 is used to toggle the device being put into and out of promiscuous * mode, to program MAC addresses for unicast and multicast hardware filters. * Today, an overlay device doesn't have a notion of promiscuous mode nor does * it have a notion of unicast and multicast addresses programmed into the * device. Instead, for the purposes of the hardware filter, we don't do * anything and just always accept new addresses being added and removed. * * If the GLDv3 start function has not been called, then we will not use this * device for I/O purposes. Any calls to transmit or receive should be dropped, * though the GLDv3 guarantees us that transmit will not be called without * calling start. Similarly, once stop is called, then no packets can be dealt * with. * * Today we don't support the stat interfaces, though there's no good reason * that we shouldn't assemble some of the stats based on what we have in the * future. * * When it comes to link properties, many of the traditional link properties do * not apply and many others MAC handles for us. For example, we don't need to * implement anything for overlay_m_getprop() to deal with returning the MTU, as * MAC never calls into us for that. As such, there isn't much of anything to * support in terms of properties. * * Today, we don't support any notion of hardware capabilities. However, if * future NIC hardware or other changes to the system cause it to make sense for * us to emulate logical groups, then we should do that. However, we still do * implement a capab function so that we can identify ourselves as an overlay * device to the broader MAC framework. This is done mostly so that a device * created on top of us can have fanout rings as we don't try to lie about a * speed for our device. * * The other question is what should be done for a device's MTU and margin. We * set our minimum supported MTU to be the minimum value that an IP network may * be set to 576 -- which mimics what an etherstub does. On the flip side, we * have our upper bound set to 8900. This value comes from the fact that a lot * of jumbo networks use their maximum as 9000. As such, we want to reserve 100 * bytes, which isn't exactly the most accurate number, but it'll be good enough * for now. Because of that, our default MTU off of these devices is 1400, as * the default MTU for everything is usually 1500 or whatever the underlying * device is at; however, this is a bit simpler than asking the netstack what * are all the IP interfaces at. It also calls into question how PMTU and PMTU * discovery should work here. The challenge, especially for * OVERLAY_TARG_DYNAMIC is that the MTU to any of the places will vary and it's * not clear that if you have a single bad entry that the overall MTU should be * lowered. Instead, we should figure out a better way of determining these * kinds of PMTU errors and appropriately alerting the administrator via FMA. * * Regarding margin, we allow a margin of up to VLAN_TAGSZ depending on whether * or not the underlying encapsulation device supports VLAN tags. If it does, * then we'll set the margin to allow for it, otherwise, we will not. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include dev_info_t *overlay_dip; static kmutex_t overlay_dev_lock; static list_t overlay_dev_list; static uint8_t overlay_macaddr[ETHERADDRL] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; typedef enum overlay_dev_prop { OVERLAY_DEV_P_MTU = 0, OVERLAY_DEV_P_VNETID, OVERLAY_DEV_P_ENCAP, OVERLAY_DEV_P_VARPDID } overlay_dev_prop_t; #define OVERLAY_DEV_NPROPS 4 static const char *overlay_dev_props[] = { "mtu", "vnetid", "encap", "varpd/id" }; #define OVERLAY_MTU_MIN 576 #define OVERLAY_MTU_DEF 1400 #define OVERLAY_MTU_MAX 8900 overlay_dev_t * overlay_hold_by_dlid(datalink_id_t id) { overlay_dev_t *o; mutex_enter(&overlay_dev_lock); for (o = list_head(&overlay_dev_list); o != NULL; o = list_next(&overlay_dev_list, o)) { if (id == o->odd_linkid) { mutex_enter(&o->odd_lock); o->odd_ref++; mutex_exit(&o->odd_lock); mutex_exit(&overlay_dev_lock); return (o); } } mutex_exit(&overlay_dev_lock); return (NULL); } void overlay_hold_rele(overlay_dev_t *odd) { mutex_enter(&odd->odd_lock); ASSERT(odd->odd_ref > 0); odd->odd_ref--; mutex_exit(&odd->odd_lock); } void overlay_io_start(overlay_dev_t *odd, overlay_dev_flag_t flag) { ASSERT(flag == OVERLAY_F_IN_RX || flag == OVERLAY_F_IN_TX); ASSERT(MUTEX_HELD(&odd->odd_lock)); if (flag & OVERLAY_F_IN_RX) odd->odd_rxcount++; if (flag & OVERLAY_F_IN_TX) odd->odd_txcount++; odd->odd_flags |= flag; } void overlay_io_done(overlay_dev_t *odd, overlay_dev_flag_t flag) { boolean_t signal = B_FALSE; ASSERT(flag == OVERLAY_F_IN_RX || flag == OVERLAY_F_IN_TX); ASSERT(MUTEX_HELD(&odd->odd_lock)); if (flag & OVERLAY_F_IN_RX) { ASSERT(odd->odd_rxcount > 0); odd->odd_rxcount--; if (odd->odd_rxcount == 0) { signal = B_TRUE; odd->odd_flags &= ~OVERLAY_F_IN_RX; } } if (flag & OVERLAY_F_IN_TX) { ASSERT(odd->odd_txcount > 0); odd->odd_txcount--; if (odd->odd_txcount == 0) { signal = B_TRUE; odd->odd_flags &= ~OVERLAY_F_IN_TX; } } if (signal == B_TRUE) cv_broadcast(&odd->odd_iowait); } static void overlay_io_wait(overlay_dev_t *odd, overlay_dev_flag_t flag) { ASSERT((flag & ~OVERLAY_F_IOMASK) == 0); ASSERT(MUTEX_HELD(&odd->odd_lock)); while (odd->odd_flags & flag) { cv_wait(&odd->odd_iowait, &odd->odd_lock); } } void overlay_dev_iter(overlay_dev_iter_f func, void *arg) { overlay_dev_t *odd; mutex_enter(&overlay_dev_lock); for (odd = list_head(&overlay_dev_list); odd != NULL; odd = list_next(&overlay_dev_list, odd)) { if (func(odd, arg) != 0) { mutex_exit(&overlay_dev_lock); return; } } mutex_exit(&overlay_dev_lock); } /* ARGSUSED */ static int overlay_m_stat(void *arg, uint_t stat, uint64_t *val) { return (ENOTSUP); } static int overlay_m_start(void *arg) { overlay_dev_t *odd = arg; overlay_mux_t *mux; int ret, domain, family, prot; struct sockaddr_storage storage; socklen_t slen; mutex_enter(&odd->odd_lock); if ((odd->odd_flags & OVERLAY_F_ACTIVATED) == 0) { mutex_exit(&odd->odd_lock); return (EAGAIN); } mutex_exit(&odd->odd_lock); ret = odd->odd_plugin->ovp_ops->ovpo_socket(odd->odd_pvoid, &domain, &family, &prot, (struct sockaddr *)&storage, &slen); if (ret != 0) return (ret); mux = overlay_mux_open(odd->odd_plugin, domain, family, prot, (struct sockaddr *)&storage, slen, &ret); if (mux == NULL) return (ret); overlay_mux_add_dev(mux, odd); odd->odd_mux = mux; mutex_enter(&odd->odd_lock); ASSERT(!(odd->odd_flags & OVERLAY_F_IN_MUX)); odd->odd_flags |= OVERLAY_F_IN_MUX; mutex_exit(&odd->odd_lock); return (0); } static void overlay_m_stop(void *arg) { overlay_dev_t *odd = arg; /* * The MAC Perimeter is held here, so we don't have to worry about * synchronizing this with respect to metadata operations. */ mutex_enter(&odd->odd_lock); VERIFY(odd->odd_flags & OVERLAY_F_IN_MUX); VERIFY(!(odd->odd_flags & OVERLAY_F_MDDROP)); odd->odd_flags |= OVERLAY_F_MDDROP; overlay_io_wait(odd, OVERLAY_F_IOMASK); mutex_exit(&odd->odd_lock); overlay_mux_remove_dev(odd->odd_mux, odd); overlay_mux_close(odd->odd_mux); odd->odd_mux = NULL; mutex_enter(&odd->odd_lock); odd->odd_flags &= ~OVERLAY_F_IN_MUX; odd->odd_flags &= ~OVERLAY_F_MDDROP; VERIFY((odd->odd_flags & OVERLAY_F_STOPMASK) == 0); mutex_exit(&odd->odd_lock); } /* * For more info on this, see the big theory statement. */ /* ARGSUSED */ static int overlay_m_promisc(void *arg, boolean_t on) { return (0); } /* * For more info on this, see the big theory statement. */ /* ARGSUSED */ static int overlay_m_multicast(void *arg, boolean_t add, const uint8_t *addrp) { return (0); } /* * For more info on this, see the big theory statement. */ /* ARGSUSED */ static int overlay_m_unicast(void *arg, const uint8_t *macaddr) { return (0); } mblk_t * overlay_m_tx(void *arg, mblk_t *mp_chain) { overlay_dev_t *odd = arg; mblk_t *mp, *ep; int ret; ovep_encap_info_t einfo; struct msghdr hdr; mutex_enter(&odd->odd_lock); if ((odd->odd_flags & OVERLAY_F_MDDROP) || !(odd->odd_flags & OVERLAY_F_IN_MUX)) { mutex_exit(&odd->odd_lock); freemsgchain(mp_chain); return (NULL); } overlay_io_start(odd, OVERLAY_F_IN_TX); mutex_exit(&odd->odd_lock); bzero(&hdr, sizeof (struct msghdr)); bzero(&einfo, sizeof (ovep_encap_info_t)); einfo.ovdi_id = odd->odd_vid; mp = mp_chain; while (mp != NULL) { socklen_t slen; struct sockaddr_storage storage; mp_chain = mp->b_next; mp->b_next = NULL; ep = NULL; ret = overlay_target_lookup(odd, mp, (struct sockaddr *)&storage, &slen); if (ret != OVERLAY_TARGET_OK) { if (ret == OVERLAY_TARGET_DROP) freemsg(mp); mp = mp_chain; continue; } hdr.msg_name = &storage; hdr.msg_namelen = slen; ret = odd->odd_plugin->ovp_ops->ovpo_encap(odd->odd_mh, mp, &einfo, &ep); if (ret != 0 || ep == NULL) { freemsg(mp); goto out; } ASSERT(ep->b_cont == mp || ep == mp); ret = overlay_mux_tx(odd->odd_mux, &hdr, ep); if (ret != 0) goto out; mp = mp_chain; } out: mutex_enter(&odd->odd_lock); overlay_io_done(odd, OVERLAY_F_IN_TX); mutex_exit(&odd->odd_lock); return (mp_chain); } /* ARGSUSED */ static void overlay_m_ioctl(void *arg, queue_t *q, mblk_t *mp) { miocnak(q, mp, 0, ENOTSUP); } /* ARGSUSED */ static boolean_t overlay_m_getcapab(void *arg, mac_capab_t cap, void *cap_data) { /* * Tell MAC we're an overlay. */ if (cap == MAC_CAPAB_OVERLAY) return (B_TRUE); return (B_FALSE); } /* ARGSUSED */ static int overlay_m_setprop(void *arg, const char *pr_name, mac_prop_id_t pr_num, uint_t pr_valsize, const void *pr_val) { uint32_t mtu, old; int err; overlay_dev_t *odd = arg; if (pr_num != MAC_PROP_MTU) return (ENOTSUP); bcopy(pr_val, &mtu, sizeof (mtu)); if (mtu < OVERLAY_MTU_MIN || mtu > OVERLAY_MTU_MAX) return (EINVAL); mutex_enter(&odd->odd_lock); old = odd->odd_mtu; odd->odd_mtu = mtu; err = mac_maxsdu_update(odd->odd_mh, mtu); if (err != 0) odd->odd_mtu = old; mutex_exit(&odd->odd_lock); return (err); } /* ARGSUSED */ static int overlay_m_getprop(void *arg, const char *pr_name, mac_prop_id_t pr_num, uint_t pr_valsize, void *pr_val) { return (ENOTSUP); } /* ARGSUSED */ static void overlay_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t pr_num, mac_prop_info_handle_t prh) { if (pr_num != MAC_PROP_MTU) return; mac_prop_info_set_default_uint32(prh, OVERLAY_MTU_DEF); mac_prop_info_set_range_uint32(prh, OVERLAY_MTU_MIN, OVERLAY_MTU_MAX); } static mac_callbacks_t overlay_m_callbacks = { .mc_callbacks = (MC_IOCTL | MC_GETCAPAB | MC_SETPROP | MC_GETPROP | MC_PROPINFO), .mc_getstat = overlay_m_stat, .mc_start = overlay_m_start, .mc_stop = overlay_m_stop, .mc_setpromisc = overlay_m_promisc, .mc_multicst = overlay_m_multicast, .mc_unicst = overlay_m_unicast, .mc_tx = overlay_m_tx, .mc_ioctl = overlay_m_ioctl, .mc_getcapab = overlay_m_getcapab, .mc_getprop = overlay_m_getprop, .mc_setprop = overlay_m_setprop, .mc_propinfo = overlay_m_propinfo }; static boolean_t overlay_valid_name(const char *name, size_t buflen) { size_t actlen; int err, i; for (i = 0; i < buflen; i++) { if (name[i] == '\0') break; } if (i == 0 || i == buflen) return (B_FALSE); actlen = i; if (strchr(name, '/') != NULL) return (B_FALSE); if (u8_validate((char *)name, actlen, NULL, U8_VALIDATE_ENTIRE, &err) < 0) return (B_FALSE); return (B_TRUE); } /* ARGSUSED */ static int overlay_i_create(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { int err; uint64_t maxid; overlay_dev_t *odd, *o; mac_register_t *mac; overlay_ioc_create_t *oicp = karg; if (overlay_valid_name(oicp->oic_encap, MAXLINKNAMELEN) == B_FALSE) return (EINVAL); odd = kmem_zalloc(sizeof (overlay_dev_t), KM_SLEEP); odd->odd_linkid = oicp->oic_linkid; odd->odd_plugin = overlay_plugin_lookup(oicp->oic_encap); if (odd->odd_plugin == NULL) { kmem_free(odd, sizeof (overlay_dev_t)); return (ENOENT); } err = odd->odd_plugin->ovp_ops->ovpo_init((overlay_handle_t)odd, &odd->odd_pvoid); if (err != 0) { odd->odd_plugin->ovp_ops->ovpo_fini(odd->odd_pvoid); overlay_plugin_rele(odd->odd_plugin); kmem_free(odd, sizeof (overlay_dev_t)); return (EINVAL); } /* * Make sure that our virtual network id is valid for the given plugin * that we're working with. */ ASSERT(odd->odd_plugin->ovp_id_size <= 8); maxid = UINT64_MAX; if (odd->odd_plugin->ovp_id_size != 8) maxid = (1ULL << (odd->odd_plugin->ovp_id_size * 8)) - 1ULL; if (oicp->oic_vnetid > maxid) { odd->odd_plugin->ovp_ops->ovpo_fini(odd->odd_pvoid); overlay_plugin_rele(odd->odd_plugin); kmem_free(odd, sizeof (overlay_dev_t)); return (EINVAL); } odd->odd_vid = oicp->oic_vnetid; mac = mac_alloc(MAC_VERSION); if (mac == NULL) { mutex_exit(&overlay_dev_lock); odd->odd_plugin->ovp_ops->ovpo_fini(odd->odd_pvoid); overlay_plugin_rele(odd->odd_plugin); kmem_free(odd, sizeof (overlay_dev_t)); return (EINVAL); } mac->m_type_ident = MAC_PLUGIN_IDENT_ETHER; mac->m_driver = odd; mac->m_dip = overlay_dip; mac->m_dst_addr = NULL; mac->m_callbacks = &overlay_m_callbacks; mac->m_pdata = NULL; mac->m_pdata_size = 0; mac->m_priv_props = NULL; /* Let mac handle this itself. */ mac->m_instance = (uint_t)-1; /* * There is no real source address that should be used here, but saying * that we're not ethernet is going to cause its own problems. At the * end of the say, this is fine. */ mac->m_src_addr = overlay_macaddr; /* * Start with the default MTU as the max SDU. If the MTU is changed, the * SDU will be changed to reflect that. */ mac->m_min_sdu = 1; mac->m_max_sdu = OVERLAY_MTU_DEF; mac->m_multicast_sdu = 0; /* * The underlying device doesn't matter, instead this comes from the * encapsulation protocol and whether or not they allow VLAN tags. */ if (odd->odd_plugin->ovp_flags & OVEP_F_VLAN_TAG) { mac->m_margin = VLAN_TAGSZ; } else { mac->m_margin = 0; } /* * Today, we have no MAC virtualization, it may make sense in the future * to go ahead and emulate some subset of this, but it doesn't today. */ mac->m_v12n = MAC_VIRT_NONE; mutex_enter(&overlay_dev_lock); for (o = list_head(&overlay_dev_list); o != NULL; o = list_next(&overlay_dev_list, o)) { if (o->odd_linkid == oicp->oic_linkid) { mutex_exit(&overlay_dev_lock); odd->odd_plugin->ovp_ops->ovpo_fini(odd->odd_pvoid); overlay_plugin_rele(odd->odd_plugin); kmem_free(odd, sizeof (overlay_dev_t)); return (EEXIST); } if (o->odd_vid == oicp->oic_vnetid && o->odd_plugin == odd->odd_plugin) { mutex_exit(&overlay_dev_lock); odd->odd_plugin->ovp_ops->ovpo_fini(odd->odd_pvoid); overlay_plugin_rele(odd->odd_plugin); kmem_free(odd, sizeof (overlay_dev_t)); return (EEXIST); } } err = mac_register(mac, &odd->odd_mh); mac_free(mac); if (err != 0) { mutex_exit(&overlay_dev_lock); odd->odd_plugin->ovp_ops->ovpo_fini(odd->odd_pvoid); overlay_plugin_rele(odd->odd_plugin); kmem_free(odd, sizeof (overlay_dev_t)); return (err); } err = dls_devnet_create(odd->odd_mh, odd->odd_linkid, crgetzoneid(cred)); if (err != 0) { mutex_exit(&overlay_dev_lock); (void) mac_unregister(odd->odd_mh); odd->odd_plugin->ovp_ops->ovpo_fini(odd->odd_pvoid); overlay_plugin_rele(odd->odd_plugin); kmem_free(odd, sizeof (overlay_dev_t)); return (err); } mutex_init(&odd->odd_lock, NULL, MUTEX_DRIVER, NULL); cv_init(&odd->odd_iowait, NULL, CV_DRIVER, NULL); odd->odd_ref = 0; odd->odd_flags = 0; list_insert_tail(&overlay_dev_list, odd); mutex_exit(&overlay_dev_lock); return (0); } /* ARGSUSED */ static int overlay_i_activate(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { int i, ret; overlay_dev_t *odd; mac_perim_handle_t mph; overlay_ioc_activate_t *oiap = karg; overlay_ioc_propinfo_t *infop; overlay_ioc_prop_t *oip; overlay_prop_handle_t phdl; odd = overlay_hold_by_dlid(oiap->oia_linkid); if (odd == NULL) return (ENOENT); infop = kmem_alloc(sizeof (overlay_ioc_propinfo_t), KM_SLEEP); oip = kmem_alloc(sizeof (overlay_ioc_prop_t), KM_SLEEP); phdl = (overlay_prop_handle_t)infop; mac_perim_enter_by_mh(odd->odd_mh, &mph); mutex_enter(&odd->odd_lock); if (odd->odd_flags & OVERLAY_F_ACTIVATED) { mutex_exit(&odd->odd_lock); mac_perim_exit(mph); overlay_hold_rele(odd); kmem_free(infop, sizeof (overlay_ioc_propinfo_t)); kmem_free(oip, sizeof (overlay_ioc_prop_t)); return (EEXIST); } mutex_exit(&odd->odd_lock); for (i = 0; i < odd->odd_plugin->ovp_nprops; i++) { const char *pname = odd->odd_plugin->ovp_props[i]; bzero(infop, sizeof (overlay_ioc_propinfo_t)); overlay_prop_init(phdl); ret = odd->odd_plugin->ovp_ops->ovpo_propinfo(pname, phdl); if (ret != 0) { mac_perim_exit(mph); overlay_hold_rele(odd); kmem_free(infop, sizeof (overlay_ioc_propinfo_t)); kmem_free(oip, sizeof (overlay_ioc_prop_t)); return (ret); } if ((infop->oipi_prot & OVERLAY_PROP_PERM_REQ) == 0) continue; bzero(oip, sizeof (overlay_ioc_prop_t)); oip->oip_size = sizeof (oip->oip_value); ret = odd->odd_plugin->ovp_ops->ovpo_getprop(odd->odd_pvoid, pname, oip->oip_value, &oip->oip_size); if (ret != 0) { mac_perim_exit(mph); overlay_hold_rele(odd); kmem_free(infop, sizeof (overlay_ioc_propinfo_t)); kmem_free(oip, sizeof (overlay_ioc_prop_t)); return (ret); } if (oip->oip_size == 0) { mac_perim_exit(mph); overlay_hold_rele(odd); kmem_free(infop, sizeof (overlay_ioc_propinfo_t)); kmem_free(oip, sizeof (overlay_ioc_prop_t)); return (EINVAL); } } mutex_enter(&odd->odd_lock); if ((odd->odd_flags & OVERLAY_F_VARPD) == 0) { mutex_exit(&odd->odd_lock); mac_perim_exit(mph); overlay_hold_rele(odd); kmem_free(infop, sizeof (overlay_ioc_propinfo_t)); kmem_free(oip, sizeof (overlay_ioc_prop_t)); return (ENXIO); } ASSERT((odd->odd_flags & OVERLAY_F_ACTIVATED) == 0); odd->odd_flags |= OVERLAY_F_ACTIVATED; /* * Now that we've activated ourselves, we should indicate to the world * that we're up. Note that we may not be able to perform lookups at * this time, but our notion of being 'up' isn't dependent on that * ability. */ mac_link_update(odd->odd_mh, LINK_STATE_UP); mutex_exit(&odd->odd_lock); mac_perim_exit(mph); overlay_hold_rele(odd); kmem_free(infop, sizeof (overlay_ioc_propinfo_t)); kmem_free(oip, sizeof (overlay_ioc_prop_t)); return (0); } /* ARGSUSED */ static int overlay_i_delete(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { overlay_ioc_delete_t *oidp = karg; overlay_dev_t *odd; datalink_id_t tid; int ret; odd = overlay_hold_by_dlid(oidp->oid_linkid); if (odd == NULL) { return (ENOENT); } mutex_enter(&odd->odd_lock); /* If we're not the only hold, we're busy */ if (odd->odd_ref != 1) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (EBUSY); } if (odd->odd_flags & OVERLAY_F_IN_MUX) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (EBUSY); } /* * To remove this, we need to first remove it from dls and then remove * it from mac. The act of removing it from mac will check if there are * devices on top of this, eg. vnics. If there are, then that will fail * and we'll have to go through and recreate the dls entry. Only after * mac_unregister has succeeded, then we'll go through and actually free * everything and drop the dev lock. */ ret = dls_devnet_destroy(odd->odd_mh, &tid, B_TRUE); if (ret != 0) { overlay_hold_rele(odd); return (ret); } ASSERT(oidp->oid_linkid == tid); ret = mac_disable(odd->odd_mh); if (ret != 0) { (void) dls_devnet_create(odd->odd_mh, odd->odd_linkid, crgetzoneid(cred)); overlay_hold_rele(odd); return (ret); } overlay_target_quiesce(odd->odd_target); mutex_enter(&overlay_dev_lock); list_remove(&overlay_dev_list, odd); mutex_exit(&overlay_dev_lock); cv_destroy(&odd->odd_iowait); mutex_destroy(&odd->odd_lock); overlay_target_free(odd); odd->odd_plugin->ovp_ops->ovpo_fini(odd->odd_pvoid); overlay_plugin_rele(odd->odd_plugin); kmem_free(odd, sizeof (overlay_dev_t)); return (0); } /* ARGSUSED */ static int overlay_i_nprops(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { overlay_dev_t *odd; overlay_ioc_nprops_t *on = karg; odd = overlay_hold_by_dlid(on->oipn_linkid); if (odd == NULL) return (ENOENT); on->oipn_nprops = odd->odd_plugin->ovp_nprops + OVERLAY_DEV_NPROPS; overlay_hold_rele(odd); return (0); } static int overlay_propinfo_plugin_cb(overlay_plugin_t *opp, void *arg) { overlay_prop_handle_t phdl = arg; overlay_prop_set_range_str(phdl, opp->ovp_name); return (0); } static int overlay_i_name_to_propid(overlay_dev_t *odd, const char *name, uint_t *id) { int i; for (i = 0; i < OVERLAY_DEV_NPROPS; i++) { if (strcmp(overlay_dev_props[i], name) == 0) { *id = i; return (0); } } for (i = 0; i < odd->odd_plugin->ovp_nprops; i++) { if (strcmp(odd->odd_plugin->ovp_props[i], name) == 0) { *id = i + OVERLAY_DEV_NPROPS; return (0); } } return (ENOENT); } static void overlay_i_propinfo_mtu(overlay_dev_t *odd, overlay_prop_handle_t phdl) { uint32_t def; mac_propval_range_t range; uint_t perm; ASSERT(MAC_PERIM_HELD(odd->odd_mh)); bzero(&range, sizeof (mac_propval_range_t)); range.mpr_count = 1; if (mac_prop_info(odd->odd_mh, MAC_PROP_MTU, "mtu", &def, sizeof (def), &range, &perm) != 0) return; if (perm == MAC_PROP_PERM_READ) overlay_prop_set_prot(phdl, OVERLAY_PROP_PERM_READ); else if (perm == MAC_PROP_PERM_WRITE) overlay_prop_set_prot(phdl, OVERLAY_PROP_PERM_WRITE); else if (perm == MAC_PROP_PERM_RW) overlay_prop_set_prot(phdl, OVERLAY_PROP_PERM_RW); overlay_prop_set_type(phdl, OVERLAY_PROP_T_UINT); overlay_prop_set_default(phdl, &def, sizeof (def)); overlay_prop_set_range_uint32(phdl, range.mpr_range_uint32[0].mpur_min, range.mpr_range_uint32[0].mpur_max); } /* ARGSUSED */ static int overlay_i_propinfo(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { overlay_dev_t *odd; int ret; mac_perim_handle_t mph; uint_t propid = UINT_MAX; overlay_ioc_propinfo_t *oip = karg; overlay_prop_handle_t phdl = (overlay_prop_handle_t)oip; odd = overlay_hold_by_dlid(oip->oipi_linkid); if (odd == NULL) return (ENOENT); overlay_prop_init(phdl); mac_perim_enter_by_mh(odd->odd_mh, &mph); /* * If the id is -1, then the property that we're looking for is named in * oipi_name and we should fill in its id. Otherwise, we've been given * an id and we need to turn that into a name for our plugin's sake. The * id is our own fabrication for property discovery. */ if (oip->oipi_id == -1) { /* * Determine if it's a known generic property or it belongs to a * module by checking against the list of known names. */ oip->oipi_name[OVERLAY_PROP_NAMELEN-1] = '\0'; if ((ret = overlay_i_name_to_propid(odd, oip->oipi_name, &propid)) != 0) { overlay_hold_rele(odd); mac_perim_exit(mph); return (ret); } oip->oipi_id = propid; if (propid >= OVERLAY_DEV_NPROPS) { ret = odd->odd_plugin->ovp_ops->ovpo_propinfo( oip->oipi_name, phdl); overlay_hold_rele(odd); mac_perim_exit(mph); return (ret); } } else if (oip->oipi_id >= OVERLAY_DEV_NPROPS) { uint_t id = oip->oipi_id - OVERLAY_DEV_NPROPS; if (id >= odd->odd_plugin->ovp_nprops) { overlay_hold_rele(odd); mac_perim_exit(mph); return (EINVAL); } ret = odd->odd_plugin->ovp_ops->ovpo_propinfo( odd->odd_plugin->ovp_props[id], phdl); overlay_hold_rele(odd); mac_perim_exit(mph); return (ret); } else if (oip->oipi_id < -1) { overlay_hold_rele(odd); mac_perim_exit(mph); return (EINVAL); } else { ASSERT(oip->oipi_id < OVERLAY_DEV_NPROPS); ASSERT(oip->oipi_id >= 0); propid = oip->oipi_id; (void) strlcpy(oip->oipi_name, overlay_dev_props[propid], sizeof (oip->oipi_name)); } switch (propid) { case OVERLAY_DEV_P_MTU: overlay_i_propinfo_mtu(odd, phdl); break; case OVERLAY_DEV_P_VNETID: overlay_prop_set_prot(phdl, OVERLAY_PROP_PERM_RW); overlay_prop_set_type(phdl, OVERLAY_PROP_T_UINT); overlay_prop_set_nodefault(phdl); break; case OVERLAY_DEV_P_ENCAP: overlay_prop_set_prot(phdl, OVERLAY_PROP_PERM_READ); overlay_prop_set_type(phdl, OVERLAY_PROP_T_STRING); overlay_prop_set_nodefault(phdl); overlay_plugin_walk(overlay_propinfo_plugin_cb, phdl); break; case OVERLAY_DEV_P_VARPDID: overlay_prop_set_prot(phdl, OVERLAY_PROP_PERM_READ); overlay_prop_set_type(phdl, OVERLAY_PROP_T_UINT); overlay_prop_set_nodefault(phdl); break; default: overlay_hold_rele(odd); mac_perim_exit(mph); return (ENOENT); } overlay_hold_rele(odd); mac_perim_exit(mph); return (0); } /* ARGSUSED */ static int overlay_i_getprop(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { int ret; overlay_dev_t *odd; mac_perim_handle_t mph; overlay_ioc_prop_t *oip = karg; uint_t propid, mtu; odd = overlay_hold_by_dlid(oip->oip_linkid); if (odd == NULL) return (ENOENT); mac_perim_enter_by_mh(odd->odd_mh, &mph); oip->oip_size = OVERLAY_PROP_SIZEMAX; oip->oip_name[OVERLAY_PROP_NAMELEN-1] = '\0'; if (oip->oip_id == -1) { int i; for (i = 0; i < OVERLAY_DEV_NPROPS; i++) { if (strcmp(overlay_dev_props[i], oip->oip_name) == 0) break; if (i == OVERLAY_DEV_NPROPS) { ret = odd->odd_plugin->ovp_ops->ovpo_getprop( odd->odd_pvoid, oip->oip_name, oip->oip_value, &oip->oip_size); overlay_hold_rele(odd); mac_perim_exit(mph); return (ret); } } propid = i; } else if (oip->oip_id >= OVERLAY_DEV_NPROPS) { uint_t id = oip->oip_id - OVERLAY_DEV_NPROPS; if (id > odd->odd_plugin->ovp_nprops) { overlay_hold_rele(odd); mac_perim_exit(mph); return (EINVAL); } ret = odd->odd_plugin->ovp_ops->ovpo_getprop(odd->odd_pvoid, odd->odd_plugin->ovp_props[id], oip->oip_value, &oip->oip_size); overlay_hold_rele(odd); mac_perim_exit(mph); return (ret); } else if (oip->oip_id < -1) { overlay_hold_rele(odd); mac_perim_exit(mph); return (EINVAL); } else { ASSERT(oip->oip_id < OVERLAY_DEV_NPROPS); ASSERT(oip->oip_id >= 0); propid = oip->oip_id; } ret = 0; switch (propid) { case OVERLAY_DEV_P_MTU: /* * The MTU is always set and retrieved through MAC, to allow for * MAC to do whatever it wants, as really that property belongs * to MAC. This is important for things where vnics have hold on * the MTU. */ mac_sdu_get(odd->odd_mh, NULL, &mtu); bcopy(&mtu, oip->oip_value, sizeof (uint_t)); oip->oip_size = sizeof (uint_t); break; case OVERLAY_DEV_P_VNETID: /* * While it's read-only while inside of a mux, we're not in a * context that can guarantee that. Therefore we always grab the * overlay_dev_t's odd_lock. */ mutex_enter(&odd->odd_lock); bcopy(&odd->odd_vid, oip->oip_value, sizeof (uint64_t)); mutex_exit(&odd->odd_lock); oip->oip_size = sizeof (uint64_t); break; case OVERLAY_DEV_P_ENCAP: oip->oip_size = strlcpy((char *)oip->oip_value, odd->odd_plugin->ovp_name, oip->oip_size); break; case OVERLAY_DEV_P_VARPDID: mutex_enter(&odd->odd_lock); if (odd->odd_flags & OVERLAY_F_VARPD) { const uint64_t val = odd->odd_target->ott_id; bcopy(&val, oip->oip_value, sizeof (uint64_t)); oip->oip_size = sizeof (uint64_t); } else { oip->oip_size = 0; } mutex_exit(&odd->odd_lock); break; default: ret = ENOENT; } overlay_hold_rele(odd); mac_perim_exit(mph); return (ret); } static void overlay_setprop_vnetid(overlay_dev_t *odd, uint64_t vnetid) { mutex_enter(&odd->odd_lock); /* Simple case, not active */ if (!(odd->odd_flags & OVERLAY_F_IN_MUX)) { odd->odd_vid = vnetid; mutex_exit(&odd->odd_lock); return; } /* * In the hard case, we need to set the drop flag, quiesce I/O and then * we can go ahead and do everything. */ odd->odd_flags |= OVERLAY_F_MDDROP; overlay_io_wait(odd, OVERLAY_F_IOMASK); mutex_exit(&odd->odd_lock); overlay_mux_remove_dev(odd->odd_mux, odd); mutex_enter(&odd->odd_lock); odd->odd_vid = vnetid; mutex_exit(&odd->odd_lock); overlay_mux_add_dev(odd->odd_mux, odd); mutex_enter(&odd->odd_lock); ASSERT(odd->odd_flags & OVERLAY_F_IN_MUX); odd->odd_flags &= ~OVERLAY_F_MDDROP; mutex_exit(&odd->odd_lock); } /* ARGSUSED */ static int overlay_i_setprop(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { int ret; overlay_dev_t *odd; overlay_ioc_prop_t *oip = karg; uint_t propid = UINT_MAX; mac_perim_handle_t mph; uint64_t maxid, *vidp; if (oip->oip_size > OVERLAY_PROP_SIZEMAX) return (EINVAL); odd = overlay_hold_by_dlid(oip->oip_linkid); if (odd == NULL) return (ENOENT); oip->oip_name[OVERLAY_PROP_NAMELEN-1] = '\0'; mac_perim_enter_by_mh(odd->odd_mh, &mph); mutex_enter(&odd->odd_lock); if (odd->odd_flags & OVERLAY_F_ACTIVATED) { mac_perim_exit(mph); mutex_exit(&odd->odd_lock); return (ENOTSUP); } mutex_exit(&odd->odd_lock); if (oip->oip_id == -1) { int i; for (i = 0; i < OVERLAY_DEV_NPROPS; i++) { if (strcmp(overlay_dev_props[i], oip->oip_name) == 0) break; if (i == OVERLAY_DEV_NPROPS) { ret = odd->odd_plugin->ovp_ops->ovpo_setprop( odd->odd_pvoid, oip->oip_name, oip->oip_value, oip->oip_size); overlay_hold_rele(odd); mac_perim_exit(mph); return (ret); } } propid = i; } else if (oip->oip_id >= OVERLAY_DEV_NPROPS) { uint_t id = oip->oip_id - OVERLAY_DEV_NPROPS; if (id > odd->odd_plugin->ovp_nprops) { mac_perim_exit(mph); overlay_hold_rele(odd); return (EINVAL); } ret = odd->odd_plugin->ovp_ops->ovpo_setprop(odd->odd_pvoid, odd->odd_plugin->ovp_props[id], oip->oip_value, oip->oip_size); mac_perim_exit(mph); overlay_hold_rele(odd); return (ret); } else if (oip->oip_id < -1) { mac_perim_exit(mph); overlay_hold_rele(odd); return (EINVAL); } else { ASSERT(oip->oip_id < OVERLAY_DEV_NPROPS); ASSERT(oip->oip_id >= 0); propid = oip->oip_id; } ret = 0; switch (propid) { case OVERLAY_DEV_P_MTU: ret = mac_set_prop(odd->odd_mh, MAC_PROP_MTU, "mtu", oip->oip_value, oip->oip_size); break; case OVERLAY_DEV_P_VNETID: if (oip->oip_size != sizeof (uint64_t)) { ret = EINVAL; break; } vidp = (uint64_t *)oip->oip_value; ASSERT(odd->odd_plugin->ovp_id_size <= 8); maxid = UINT64_MAX; if (odd->odd_plugin->ovp_id_size != 8) maxid = (1ULL << (odd->odd_plugin->ovp_id_size * 8)) - 1ULL; if (*vidp >= maxid) { ret = EINVAL; break; } overlay_setprop_vnetid(odd, *vidp); break; case OVERLAY_DEV_P_ENCAP: case OVERLAY_DEV_P_VARPDID: ret = EPERM; break; default: ret = ENOENT; } mac_perim_exit(mph); overlay_hold_rele(odd); return (ret); } /* ARGSUSED */ static int overlay_i_status(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp) { overlay_dev_t *odd; overlay_ioc_status_t *os = karg; odd = overlay_hold_by_dlid(os->ois_linkid); if (odd == NULL) return (ENOENT); mutex_enter(&odd->odd_lock); if ((odd->odd_flags & OVERLAY_F_DEGRADED) != 0) { os->ois_status = OVERLAY_I_DEGRADED; (void) strlcpy(os->ois_message, odd->odd_fmamsg, OVERLAY_STATUS_BUFLEN); } else { os->ois_status = OVERLAY_I_OK; os->ois_message[0] = '\0'; } mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (0); } static dld_ioc_info_t overlay_ioc_list[] = { { OVERLAY_IOC_CREATE, DLDCOPYIN, sizeof (overlay_ioc_create_t), overlay_i_create, secpolicy_dl_config }, { OVERLAY_IOC_ACTIVATE, DLDCOPYIN, sizeof (overlay_ioc_activate_t), overlay_i_activate, secpolicy_dl_config }, { OVERLAY_IOC_DELETE, DLDCOPYIN, sizeof (overlay_ioc_delete_t), overlay_i_delete, secpolicy_dl_config }, { OVERLAY_IOC_PROPINFO, DLDCOPYIN | DLDCOPYOUT, sizeof (overlay_ioc_propinfo_t), overlay_i_propinfo, secpolicy_dl_config }, { OVERLAY_IOC_GETPROP, DLDCOPYIN | DLDCOPYOUT, sizeof (overlay_ioc_prop_t), overlay_i_getprop, secpolicy_dl_config }, { OVERLAY_IOC_SETPROP, DLDCOPYIN, sizeof (overlay_ioc_prop_t), overlay_i_setprop, secpolicy_dl_config }, { OVERLAY_IOC_NPROPS, DLDCOPYIN | DLDCOPYOUT, sizeof (overlay_ioc_nprops_t), overlay_i_nprops, secpolicy_dl_config }, { OVERLAY_IOC_STATUS, DLDCOPYIN | DLDCOPYOUT, sizeof (overlay_ioc_status_t), overlay_i_status, NULL } }; static int overlay_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int fmcap = DDI_FM_EREPORT_CAPABLE; if (cmd != DDI_ATTACH) return (DDI_FAILURE); if (overlay_dip != NULL || ddi_get_instance(dip) != 0) return (DDI_FAILURE); ddi_fm_init(dip, &fmcap, NULL); if (ddi_create_minor_node(dip, OVERLAY_CTL, S_IFCHR, ddi_get_instance(dip), DDI_PSEUDO, 0) == DDI_FAILURE) return (DDI_FAILURE); if (dld_ioc_register(OVERLAY_IOC, overlay_ioc_list, DLDIOCCNT(overlay_ioc_list)) != 0) { ddi_remove_minor_node(dip, OVERLAY_CTL); return (DDI_FAILURE); } overlay_dip = dip; return (DDI_SUCCESS); } /* ARGSUSED */ static int overlay_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resp) { int error; switch (cmd) { case DDI_INFO_DEVT2DEVINFO: *resp = (void *)overlay_dip; error = DDI_SUCCESS; break; case DDI_INFO_DEVT2INSTANCE: *resp = (void *)0; error = DDI_SUCCESS; break; default: error = DDI_FAILURE; break; } return (error); } static int overlay_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { if (cmd != DDI_DETACH) return (DDI_FAILURE); mutex_enter(&overlay_dev_lock); if (!list_is_empty(&overlay_dev_list) || overlay_target_busy()) { mutex_exit(&overlay_dev_lock); return (EBUSY); } mutex_exit(&overlay_dev_lock); dld_ioc_unregister(OVERLAY_IOC); ddi_remove_minor_node(dip, OVERLAY_CTL); ddi_fm_fini(dip); overlay_dip = NULL; return (DDI_SUCCESS); } static struct cb_ops overlay_cbops = { overlay_target_open, /* cb_open */ overlay_target_close, /* cb_close */ nodev, /* cb_strategy */ nodev, /* cb_print */ nodev, /* cb_dump */ nodev, /* cb_read */ nodev, /* cb_write */ overlay_target_ioctl, /* cb_ioctl */ nodev, /* cb_devmap */ nodev, /* cb_mmap */ nodev, /* cb_segmap */ nochpoll, /* cb_chpoll */ ddi_prop_op, /* cb_prop_op */ NULL, /* cb_stream */ D_MP, /* cb_flag */ CB_REV, /* cb_rev */ nodev, /* cb_aread */ nodev, /* cb_awrite */ }; static struct dev_ops overlay_dev_ops = { DEVO_REV, /* devo_rev */ 0, /* devo_refcnt */ overlay_getinfo, /* devo_getinfo */ nulldev, /* devo_identify */ nulldev, /* devo_probe */ overlay_attach, /* devo_attach */ overlay_detach, /* devo_detach */ nulldev, /* devo_reset */ &overlay_cbops, /* devo_cb_ops */ NULL, /* devo_bus_ops */ NULL, /* devo_power */ ddi_quiesce_not_supported /* devo_quiesce */ }; static struct modldrv overlay_modldrv = { &mod_driverops, "Overlay Network Driver", &overlay_dev_ops }; static struct modlinkage overlay_linkage = { MODREV_1, &overlay_modldrv }; static int overlay_init(void) { mutex_init(&overlay_dev_lock, NULL, MUTEX_DRIVER, NULL); list_create(&overlay_dev_list, sizeof (overlay_dev_t), offsetof(overlay_dev_t, odd_link)); overlay_mux_init(); overlay_plugin_init(); overlay_target_init(); return (DDI_SUCCESS); } static void overlay_fini(void) { overlay_target_fini(); overlay_plugin_fini(); overlay_mux_fini(); mutex_destroy(&overlay_dev_lock); list_destroy(&overlay_dev_list); } int _init(void) { int err; if ((err = overlay_init()) != DDI_SUCCESS) return (err); mac_init_ops(NULL, "overlay"); err = mod_install(&overlay_linkage); if (err != DDI_SUCCESS) { overlay_fini(); return (err); } return (0); } int _info(struct modinfo *modinfop) { return (mod_info(&overlay_linkage, modinfop)); } int _fini(void) { int err; err = mod_remove(&overlay_linkage); if (err != 0) return (err); overlay_fini(); return (0); } # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2015, Joyent, Inc. # name="overlay" parent="pseudo" instance=0; # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2015 Joyent, Inc. # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 SYMBOL_VERSION ILLUMOSprivate { global: # DDI Interfaces _fini; _init; _info; # Encapsualation Plugin interfaces overlay_plugin_alloc; overlay_plugin_free; overlay_plugin_register; overlay_plugin_unregister; local: *; }; /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2016 Joyent, Inc. */ /* * Overlay device FMA operations. * * For more information, see the big theory statement in * uts/common/io/overlay/overlay.c */ #include #include kmutex_t overlay_fm_lock; uint_t overlay_fm_count; void overlay_fm_init(void) { overlay_fm_count = 0; mutex_init(&overlay_fm_lock, NULL, MUTEX_DRIVER, NULL); } void overlay_fm_fini(void) { VERIFY(overlay_fm_count == 0); mutex_destroy(&overlay_fm_lock); } void overlay_fm_degrade(overlay_dev_t *odd, const char *msg) { mutex_enter(&overlay_fm_lock); mutex_enter(&odd->odd_lock); if (msg != NULL) (void) strlcpy(odd->odd_fmamsg, msg, OVERLAY_STATUS_BUFLEN); if (odd->odd_flags & OVERLAY_F_DEGRADED) goto out; odd->odd_flags |= OVERLAY_F_DEGRADED; overlay_fm_count++; if (overlay_fm_count == 1) { ddi_fm_service_impact(overlay_dip, DDI_SERVICE_DEGRADED); } out: mutex_exit(&odd->odd_lock); mutex_exit(&overlay_fm_lock); } void overlay_fm_restore(overlay_dev_t *odd) { mutex_enter(&overlay_fm_lock); mutex_enter(&odd->odd_lock); if (!(odd->odd_flags & OVERLAY_F_DEGRADED)) goto out; odd->odd_fmamsg[0] = '\0'; odd->odd_flags &= ~OVERLAY_F_DEGRADED; overlay_fm_count--; if (overlay_fm_count == 0) { ddi_fm_service_impact(overlay_dip, DDI_SERVICE_RESTORED); } out: mutex_exit(&odd->odd_lock); mutex_exit(&overlay_fm_lock); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2019 Joyent, Inc. */ /* * Overlay device ksocket multiplexer. * * For more information, see the big theory statement in * uts/common/io/overlay/overlay.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include static list_t overlay_mux_list; static kmutex_t overlay_mux_lock; void overlay_mux_init(void) { list_create(&overlay_mux_list, sizeof (overlay_mux_t), offsetof(overlay_mux_t, omux_lnode)); mutex_init(&overlay_mux_lock, NULL, MUTEX_DRIVER, NULL); } void overlay_mux_fini(void) { mutex_destroy(&overlay_mux_lock); list_destroy(&overlay_mux_list); } static int overlay_mux_comparator(const void *a, const void *b) { const overlay_dev_t *odl, *odr; odl = a; odr = b; if (odl->odd_vid > odr->odd_vid) return (1); else if (odl->odd_vid < odr->odd_vid) return (-1); else return (0); } /* * This is the central receive data path. We need to decode the packet, if we * can, and then deliver it to the appropriate overlay. */ /* ARGSUSED */ static boolean_t overlay_mux_recv(ksocket_t ks, mblk_t *mpchain, size_t msgsize, int oob, void *arg) { mblk_t *mp, *nmp, *fmp; overlay_mux_t *mux = arg; /* * We may have a received a chain of messages. Each message in the * chain will likely have a T_unitdata_ind attached to it as an M_PROTO. * If we aren't getting that, we should probably drop that for the * moment. */ for (mp = mpchain; mp != NULL; mp = nmp) { struct T_unitdata_ind *tudi; ovep_encap_info_t infop; overlay_dev_t od, *odd; int ret; nmp = mp->b_next; mp->b_next = NULL; if (DB_TYPE(mp) != M_PROTO) { OVERLAY_FREEMSG(mp, "first one isn't M_PROTO"); freemsg(mp); continue; } if (mp->b_cont == NULL) { OVERLAY_FREEMSG(mp, "missing a b_cont"); freemsg(mp); continue; } tudi = (struct T_unitdata_ind *)mp->b_rptr; if (tudi->PRIM_type != T_UNITDATA_IND) { OVERLAY_FREEMSG(mp, "Not a T_unitdata_ind *"); freemsg(mp); continue; } /* * In the future, we'll care about the source information * for purposes of telling varpd for oob invalidation. But for * now, just drop that block. */ fmp = mp; mp = fmp->b_cont; freeb(fmp); /* * Until we have VXLAN-or-other-decap HW acceleration support * (e.g. we support NICs that reach into VXLAN-encapsulated * packets and check the inside-VXLAN IP packets' checksums, * or do LSO with VXLAN), we should clear any HW-accelerated- * performed bits. */ DB_CKSUMFLAGS(mp) = 0; /* * Decap and deliver. */ bzero(&infop, sizeof (ovep_encap_info_t)); ret = mux->omux_plugin->ovp_ops->ovpo_decap(NULL, mp, &infop); if (ret != 0) { OVERLAY_FREEMSG(mp, "decap failed"); freemsg(mp); continue; } if (MBLKL(mp) > infop.ovdi_hdr_size) { mp->b_rptr += infop.ovdi_hdr_size; } else { while (infop.ovdi_hdr_size != 0) { size_t rem, blkl; if (mp == NULL) break; blkl = MBLKL(mp); rem = MIN(infop.ovdi_hdr_size, blkl); infop.ovdi_hdr_size -= rem; mp->b_rptr += rem; if (rem == blkl) { fmp = mp; mp = fmp->b_cont; fmp->b_cont = NULL; OVERLAY_FREEMSG(mp, "freed a fmp block"); freemsg(fmp); } } if (mp == NULL) { OVERLAY_FREEMSG(mp, "freed it all..."); continue; } } od.odd_vid = infop.ovdi_id; mutex_enter(&mux->omux_lock); odd = avl_find(&mux->omux_devices, &od, NULL); if (odd == NULL) { mutex_exit(&mux->omux_lock); OVERLAY_FREEMSG(mp, "no matching vid"); freemsg(mp); continue; } mutex_enter(&odd->odd_lock); if ((odd->odd_flags & OVERLAY_F_MDDROP) || !(odd->odd_flags & OVERLAY_F_IN_MUX)) { mutex_exit(&odd->odd_lock); mutex_exit(&mux->omux_lock); OVERLAY_FREEMSG(mp, "dev dropped"); freemsg(mp); continue; } overlay_io_start(odd, OVERLAY_F_IN_RX); mutex_exit(&odd->odd_lock); mutex_exit(&mux->omux_lock); mac_rx(odd->odd_mh, NULL, mp); mutex_enter(&odd->odd_lock); overlay_io_done(odd, OVERLAY_F_IN_RX); mutex_exit(&odd->odd_lock); } return (B_TRUE); } /* * Register a given device with a socket backend. If no such device socket * exists, create a new one. */ overlay_mux_t * overlay_mux_open(overlay_plugin_t *opp, int domain, int family, int protocol, struct sockaddr *addr, socklen_t len, int *errp) { int err; overlay_mux_t *mux; ksocket_t ksock; if (errp == NULL) errp = &err; mutex_enter(&overlay_mux_lock); for (mux = list_head(&overlay_mux_list); mux != NULL; mux = list_next(&overlay_mux_list, mux)) { if (domain == mux->omux_domain && family == mux->omux_family && protocol == mux->omux_protocol && len == mux->omux_alen && bcmp(addr, mux->omux_addr, len) == 0) { if (opp != mux->omux_plugin) { *errp = EEXIST; return (NULL); } mutex_enter(&mux->omux_lock); mux->omux_count++; mutex_exit(&mux->omux_lock); mutex_exit(&overlay_mux_lock); *errp = 0; return (mux); } } /* * Today we aren't zone-aware and only exist in the global zone. When we * allow for things to exist in the non-global zone, we'll want to use a * credential that's actually specific to the zone. */ *errp = ksocket_socket(&ksock, domain, family, protocol, KSOCKET_SLEEP, kcred); if (*errp != 0) { mutex_exit(&overlay_mux_lock); return (NULL); } *errp = ksocket_bind(ksock, addr, len, kcred); if (*errp != 0) { mutex_exit(&overlay_mux_lock); ksocket_close(ksock, kcred); return (NULL); } /* * Ask our lower layer to optionally toggle anything they need on this * socket. Because a socket is owned by a single type of plugin, we can * then ask it to perform any additional socket set up it'd like to do. */ if (opp->ovp_ops->ovpo_sockopt != NULL && (*errp = opp->ovp_ops->ovpo_sockopt(ksock)) != 0) { mutex_exit(&overlay_mux_lock); ksocket_close(ksock, kcred); return (NULL); } mux = kmem_alloc(sizeof (overlay_mux_t), KM_SLEEP); list_link_init(&mux->omux_lnode); mux->omux_ksock = ksock; mux->omux_plugin = opp; mux->omux_domain = domain; mux->omux_family = family; mux->omux_protocol = protocol; mux->omux_addr = kmem_alloc(len, KM_SLEEP); bcopy(addr, mux->omux_addr, len); mux->omux_alen = len; mux->omux_count = 1; avl_create(&mux->omux_devices, overlay_mux_comparator, sizeof (overlay_dev_t), offsetof(overlay_dev_t, odd_muxnode)); mutex_init(&mux->omux_lock, NULL, MUTEX_DRIVER, NULL); /* Once this is called, we need to expect to rx data */ *errp = ksocket_krecv_set(ksock, overlay_mux_recv, mux); if (*errp != 0) { ksocket_close(ksock, kcred); mutex_destroy(&mux->omux_lock); avl_destroy(&mux->omux_devices); kmem_free(mux->omux_addr, len); kmem_free(mux, sizeof (overlay_mux_t)); return (NULL); } list_insert_tail(&overlay_mux_list, mux); mutex_exit(&overlay_mux_lock); *errp = 0; return (mux); } void overlay_mux_close(overlay_mux_t *mux) { mutex_enter(&overlay_mux_lock); mutex_enter(&mux->omux_lock); mux->omux_count--; if (mux->omux_count != 0) { mutex_exit(&mux->omux_lock); mutex_exit(&overlay_mux_lock); return; } list_remove(&overlay_mux_list, mux); mutex_exit(&mux->omux_lock); mutex_exit(&overlay_mux_lock); ksocket_close(mux->omux_ksock, kcred); avl_destroy(&mux->omux_devices); kmem_free(mux->omux_addr, mux->omux_alen); kmem_free(mux, sizeof (overlay_mux_t)); } void overlay_mux_add_dev(overlay_mux_t *mux, overlay_dev_t *odd) { mutex_enter(&mux->omux_lock); avl_add(&mux->omux_devices, odd); mutex_exit(&mux->omux_lock); } void overlay_mux_remove_dev(overlay_mux_t *mux, overlay_dev_t *odd) { mutex_enter(&mux->omux_lock); avl_remove(&mux->omux_devices, odd); mutex_exit(&mux->omux_lock); } int overlay_mux_tx(overlay_mux_t *mux, struct msghdr *hdr, mblk_t *mp) { int ret; /* * It'd be nice to be able to use MSG_MBLK_QUICKRELE, unfortunately, * that isn't actually supported by UDP at this time. */ ret = ksocket_sendmblk(mux->omux_ksock, hdr, 0, &mp, kcred); if (ret != 0) freemsg(mp); return (ret); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2015 Joyent, Inc. */ /* * Overlay device encapsulation plugin management * * For more information, see the big theory statement in * uts/common/io/overlay/overlay.c */ #include #include #include #include #include #include #include #include static kmem_cache_t *overlay_plugin_cache; static kmutex_t overlay_plugin_lock; static list_t overlay_plugin_list; #define OVERLAY_MODDIR "overlay" /* ARGSUSED */ static int overlay_plugin_cache_constructor(void *buf, void *arg, int kmflags) { overlay_plugin_t *opp = buf; mutex_init(&opp->ovp_mutex, NULL, MUTEX_DRIVER, NULL); list_link_init(&opp->ovp_link); return (0); } /* ARGSUSED */ static void overlay_plugin_cache_destructor(void *buf, void *arg) { overlay_plugin_t *opp = buf; ASSERT(list_link_active(&opp->ovp_link) == 0); mutex_destroy(&opp->ovp_mutex); } void overlay_plugin_init(void) { mutex_init(&overlay_plugin_lock, NULL, MUTEX_DRIVER, 0); /* * In the future we may want to have a reaper to unload unused modules * to help the kernel be able to reclaim memory. */ overlay_plugin_cache = kmem_cache_create("overlay_plugin_cache", sizeof (overlay_plugin_t), 0, overlay_plugin_cache_constructor, overlay_plugin_cache_destructor, NULL, NULL, NULL, 0); list_create(&overlay_plugin_list, sizeof (overlay_plugin_t), offsetof(overlay_plugin_t, ovp_link)); } void overlay_plugin_fini(void) { mutex_enter(&overlay_plugin_lock); VERIFY(list_is_empty(&overlay_plugin_list)); mutex_exit(&overlay_plugin_lock); list_destroy(&overlay_plugin_list); kmem_cache_destroy(overlay_plugin_cache); mutex_destroy(&overlay_plugin_lock); } overlay_plugin_register_t * overlay_plugin_alloc(uint_t version) { overlay_plugin_register_t *ovrp; /* Version 1 is the only one that exists */ if (version != OVEP_VERSION_ONE) return (NULL); ovrp = kmem_zalloc(sizeof (overlay_plugin_register_t), KM_SLEEP); ovrp->ovep_version = version; return (ovrp); } void overlay_plugin_free(overlay_plugin_register_t *ovrp) { kmem_free(ovrp, sizeof (overlay_plugin_register_t)); } int overlay_plugin_register(overlay_plugin_register_t *ovrp) { overlay_plugin_t *opp, *ipp; /* Sanity check parameters of the registration */ if (ovrp->ovep_version != OVEP_VERSION_ONE) return (EINVAL); if (ovrp->ovep_name == NULL || ovrp->ovep_ops == NULL) return (EINVAL); if ((ovrp->ovep_flags & ~(OVEP_F_VLAN_TAG)) != 0) return (EINVAL); if (ovrp->ovep_id_size < 1) return (EINVAL); /* Don't support anything that has an id size larger than 8 bytes */ if (ovrp->ovep_id_size > 8) return (ENOTSUP); if (ovrp->ovep_dest == OVERLAY_PLUGIN_D_INVALID) return (EINVAL); if ((ovrp->ovep_dest & ~OVERLAY_PLUGIN_D_MASK) != 0) return (EINVAL); if (ovrp->ovep_ops->ovpo_callbacks != 0) return (EINVAL); if (ovrp->ovep_ops->ovpo_init == NULL) return (EINVAL); if (ovrp->ovep_ops->ovpo_fini == NULL) return (EINVAL); if (ovrp->ovep_ops->ovpo_encap == NULL) return (EINVAL); if (ovrp->ovep_ops->ovpo_decap == NULL) return (EINVAL); if (ovrp->ovep_ops->ovpo_socket == NULL) return (EINVAL); if (ovrp->ovep_ops->ovpo_getprop == NULL) return (EINVAL); if (ovrp->ovep_ops->ovpo_setprop == NULL) return (EINVAL); if (ovrp->ovep_ops->ovpo_propinfo == NULL) return (EINVAL); opp = kmem_cache_alloc(overlay_plugin_cache, KM_SLEEP); opp->ovp_active = 0; opp->ovp_name = ovrp->ovep_name; opp->ovp_ops = ovrp->ovep_ops; opp->ovp_props = ovrp->ovep_props; opp->ovp_id_size = ovrp->ovep_id_size; opp->ovp_flags = ovrp->ovep_flags; opp->ovp_dest = ovrp->ovep_dest; opp->ovp_nprops = 0; if (ovrp->ovep_props != NULL) { while (ovrp->ovep_props[opp->ovp_nprops] != NULL) { if (strlen(ovrp->ovep_props[opp->ovp_nprops]) >= OVERLAY_PROP_NAMELEN) { mutex_exit(&overlay_plugin_lock); kmem_cache_free(overlay_plugin_cache, opp); return (EINVAL); } opp->ovp_nprops++; } } mutex_enter(&overlay_plugin_lock); for (ipp = list_head(&overlay_plugin_list); ipp != NULL; ipp = list_next(&overlay_plugin_list, ipp)) { if (strcmp(ipp->ovp_name, opp->ovp_name) == 0) { mutex_exit(&overlay_plugin_lock); kmem_cache_free(overlay_plugin_cache, opp); return (EEXIST); } } list_insert_tail(&overlay_plugin_list, opp); mutex_exit(&overlay_plugin_lock); return (0); } int overlay_plugin_unregister(const char *name) { overlay_plugin_t *opp; mutex_enter(&overlay_plugin_lock); for (opp = list_head(&overlay_plugin_list); opp != NULL; opp = list_next(&overlay_plugin_list, opp)) { if (strcmp(opp->ovp_name, name) == 0) break; } if (opp == NULL) { mutex_exit(&overlay_plugin_lock); return (ENOENT); } mutex_enter(&opp->ovp_mutex); if (opp->ovp_active > 0) { mutex_exit(&opp->ovp_mutex); mutex_exit(&overlay_plugin_lock); return (EBUSY); } mutex_exit(&opp->ovp_mutex); list_remove(&overlay_plugin_list, opp); mutex_exit(&overlay_plugin_lock); kmem_cache_free(overlay_plugin_cache, opp); return (0); } overlay_plugin_t * overlay_plugin_lookup(const char *name) { overlay_plugin_t *opp; boolean_t trymodload = B_FALSE; for (;;) { mutex_enter(&overlay_plugin_lock); for (opp = list_head(&overlay_plugin_list); opp != NULL; opp = list_next(&overlay_plugin_list, opp)) { if (strcmp(name, opp->ovp_name) == 0) { mutex_enter(&opp->ovp_mutex); opp->ovp_active++; mutex_exit(&opp->ovp_mutex); mutex_exit(&overlay_plugin_lock); return (opp); } } mutex_exit(&overlay_plugin_lock); if (trymodload == B_TRUE) return (NULL); /* * If we didn't find it, it may still exist, but just not have * been a loaded module. In that case, we'll do one attempt to * load it. */ if (modload(OVERLAY_MODDIR, (char *)name) == -1) return (NULL); trymodload = B_TRUE; } } void overlay_plugin_rele(overlay_plugin_t *opp) { mutex_enter(&opp->ovp_mutex); ASSERT(opp->ovp_active > 0); opp->ovp_active--; mutex_exit(&opp->ovp_mutex); } void overlay_plugin_walk(overlay_plugin_walk_f func, void *arg) { overlay_plugin_t *opp; mutex_enter(&overlay_plugin_lock); for (opp = list_head(&overlay_plugin_list); opp != NULL; opp = list_next(&overlay_plugin_list, opp)) { if (func(opp, arg) != 0) { mutex_exit(&overlay_plugin_lock); return; } } mutex_exit(&overlay_plugin_lock); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2015, Joyent, Inc. */ /* * Routines for manipulating property information structures. * * For more information, see the big theory statement in * uts/common/io/overlay/overlay.c */ #include void overlay_prop_init(overlay_prop_handle_t phdl) { overlay_ioc_propinfo_t *infop = (overlay_ioc_propinfo_t *)phdl; mac_propval_range_t *rangep = (mac_propval_range_t *)infop->oipi_poss; infop->oipi_posssize = sizeof (mac_propval_range_t); bzero(rangep, sizeof (mac_propval_range_t)); } void overlay_prop_set_name(overlay_prop_handle_t phdl, const char *name) { overlay_ioc_propinfo_t *infop = (overlay_ioc_propinfo_t *)phdl; (void) strlcpy(infop->oipi_name, name, OVERLAY_PROP_NAMELEN); } void overlay_prop_set_prot(overlay_prop_handle_t phdl, overlay_prop_prot_t prot) { overlay_ioc_propinfo_t *infop = (overlay_ioc_propinfo_t *)phdl; infop->oipi_prot = prot; } void overlay_prop_set_type(overlay_prop_handle_t phdl, overlay_prop_type_t type) { overlay_ioc_propinfo_t *infop = (overlay_ioc_propinfo_t *)phdl; infop->oipi_type = type; } int overlay_prop_set_default(overlay_prop_handle_t phdl, void *def, ssize_t len) { overlay_ioc_propinfo_t *infop = (overlay_ioc_propinfo_t *)phdl; if (len > OVERLAY_PROP_SIZEMAX) return (E2BIG); if (len < 0) return (EOVERFLOW); bcopy(def, infop->oipi_default, len); infop->oipi_defsize = (uint32_t)len; return (0); } void overlay_prop_set_nodefault(overlay_prop_handle_t phdl) { overlay_ioc_propinfo_t *infop = (overlay_ioc_propinfo_t *)phdl; infop->oipi_default[0] = '\0'; infop->oipi_defsize = 0; } void overlay_prop_set_range_uint32(overlay_prop_handle_t phdl, uint32_t min, uint32_t max) { overlay_ioc_propinfo_t *infop = (overlay_ioc_propinfo_t *)phdl; mac_propval_range_t *rangep = (mac_propval_range_t *)infop->oipi_poss; if (rangep->mpr_count != 0 && rangep->mpr_type != MAC_PROPVAL_UINT32) return; if (infop->oipi_posssize + sizeof (mac_propval_uint32_range_t) > sizeof (infop->oipi_poss)) return; infop->oipi_posssize += sizeof (mac_propval_uint32_range_t); rangep->mpr_count++; rangep->mpr_type = MAC_PROPVAL_UINT32; rangep->u.mpr_uint32[rangep->mpr_count-1].mpur_min = min; rangep->u.mpr_uint32[rangep->mpr_count-1].mpur_max = max; } void overlay_prop_set_range_str(overlay_prop_handle_t phdl, const char *str) { size_t len = strlen(str) + 1; /* Account for a null terminator */ overlay_ioc_propinfo_t *infop = (overlay_ioc_propinfo_t *)phdl; mac_propval_range_t *rangep = (mac_propval_range_t *)infop->oipi_poss; mac_propval_str_range_t *pstr = &rangep->u.mpr_str; if (rangep->mpr_count != 0 && rangep->mpr_type != MAC_PROPVAL_STR) return; if (infop->oipi_posssize + len > sizeof (infop->oipi_poss)) return; rangep->mpr_count++; rangep->mpr_type = MAC_PROPVAL_STR; strlcpy((char *)&pstr->mpur_data[pstr->mpur_nextbyte], str, sizeof (infop->oipi_poss) - infop->oipi_posssize); pstr->mpur_nextbyte += len; infop->oipi_posssize += len; } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2016 Joyent, Inc. * Copyright 2022 MNX Cloud, Inc. */ /* * Overlay device target cache management * * For more information, see the big theory statement in * uts/common/io/overlay/overlay.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * This is total straw man, but at least it's a prime number. Here we're * going to have to go through and do a lot of evaluation and understanding as * to how these target caches should grow and shrink, as well as, memory * pressure and evictions. This just gives us a starting point that'll be 'good * enough', until it's not. */ #define OVERLAY_HSIZE 823 /* * We use this data structure to keep track of what requests have been actively * allocated to a given instance so we know what to put back on the pending * list. */ typedef struct overlay_target_hdl { minor_t oth_minor; /* RO */ zoneid_t oth_zoneid; /* RO */ int oth_oflags; /* RO */ list_node_t oth_link; /* overlay_target_lock */ kmutex_t oth_lock; list_t oth_outstanding; /* oth_lock */ } overlay_target_hdl_t; typedef int (*overlay_target_copyin_f)(const void *, void **, size_t *, int); typedef int (*overlay_target_ioctl_f)(overlay_target_hdl_t *, void *); typedef int (*overlay_target_copyout_f)(void *, void *, size_t, int); typedef struct overlay_target_ioctl { int oti_cmd; /* ioctl id */ boolean_t oti_write; /* ioctl requires FWRITE */ boolean_t oti_ncopyout; /* copyout data? */ overlay_target_copyin_f oti_copyin; /* copyin func */ overlay_target_ioctl_f oti_func; /* function to call */ overlay_target_copyout_f oti_copyout; /* copyin func */ size_t oti_size; /* size of user level structure */ } overlay_target_ioctl_t; static kmem_cache_t *overlay_target_cache; static kmem_cache_t *overlay_entry_cache; static id_space_t *overlay_thdl_idspace; static void *overlay_thdl_state; /* * When we support overlay devices in the NGZ, then all of these need to become * zone aware, by plugging into the netstack engine and becoming per-netstack * data. */ static list_t overlay_thdl_list; static kmutex_t overlay_target_lock; static kcondvar_t overlay_target_condvar; static list_t overlay_target_list; static boolean_t overlay_target_excl; /* * Outstanding data per hash table entry. */ static int overlay_ent_size = 128 * 1024; /* ARGSUSED */ static int overlay_target_cache_constructor(void *buf, void *arg, int kmflgs) { overlay_target_t *ott = buf; mutex_init(&ott->ott_lock, NULL, MUTEX_DRIVER, NULL); cv_init(&ott->ott_cond, NULL, CV_DRIVER, NULL); return (0); } /* ARGSUSED */ static void overlay_target_cache_destructor(void *buf, void *arg) { overlay_target_t *ott = buf; cv_destroy(&ott->ott_cond); mutex_destroy(&ott->ott_lock); } /* ARGSUSED */ static int overlay_entry_cache_constructor(void *buf, void *arg, int kmflgs) { overlay_target_entry_t *ote = buf; bzero(ote, sizeof (overlay_target_entry_t)); mutex_init(&ote->ote_lock, NULL, MUTEX_DRIVER, NULL); return (0); } /* ARGSUSED */ static void overlay_entry_cache_destructor(void *buf, void *arg) { overlay_target_entry_t *ote = buf; mutex_destroy(&ote->ote_lock); } static uint64_t overlay_mac_hash(const void *v) { uint32_t crc; CRC32(crc, v, ETHERADDRL, -1U, crc32_table); return (crc); } static int overlay_mac_cmp(const void *a, const void *b) { return (bcmp(a, b, ETHERADDRL)); } /* ARGSUSED */ static void overlay_target_entry_dtor(void *arg) { overlay_target_entry_t *ote = arg; ote->ote_flags = 0; bzero(ote->ote_addr, ETHERADDRL); ote->ote_ott = NULL; ote->ote_odd = NULL; freemsgchain(ote->ote_chead); ote->ote_chead = ote->ote_ctail = NULL; ote->ote_mbsize = 0; ote->ote_vtime = 0; kmem_cache_free(overlay_entry_cache, ote); } static int overlay_mac_avl(const void *a, const void *b) { int i; const overlay_target_entry_t *l, *r; l = a; r = b; for (i = 0; i < ETHERADDRL; i++) { if (l->ote_addr[i] > r->ote_addr[i]) return (1); else if (l->ote_addr[i] < r->ote_addr[i]) return (-1); } return (0); } void overlay_target_init(void) { int ret; ret = ddi_soft_state_init(&overlay_thdl_state, sizeof (overlay_target_hdl_t), 1); VERIFY(ret == 0); overlay_target_cache = kmem_cache_create("overlay_target", sizeof (overlay_target_t), 0, overlay_target_cache_constructor, overlay_target_cache_destructor, NULL, NULL, NULL, 0); overlay_entry_cache = kmem_cache_create("overlay_entry", sizeof (overlay_target_entry_t), 0, overlay_entry_cache_constructor, overlay_entry_cache_destructor, NULL, NULL, NULL, 0); mutex_init(&overlay_target_lock, NULL, MUTEX_DRIVER, NULL); cv_init(&overlay_target_condvar, NULL, CV_DRIVER, NULL); list_create(&overlay_target_list, sizeof (overlay_target_entry_t), offsetof(overlay_target_entry_t, ote_qlink)); list_create(&overlay_thdl_list, sizeof (overlay_target_hdl_t), offsetof(overlay_target_hdl_t, oth_link)); overlay_thdl_idspace = id_space_create("overlay_target_minors", 1, INT32_MAX); } void overlay_target_fini(void) { id_space_destroy(overlay_thdl_idspace); list_destroy(&overlay_thdl_list); list_destroy(&overlay_target_list); cv_destroy(&overlay_target_condvar); mutex_destroy(&overlay_target_lock); kmem_cache_destroy(overlay_entry_cache); kmem_cache_destroy(overlay_target_cache); ddi_soft_state_fini(&overlay_thdl_state); } void overlay_target_free(overlay_dev_t *odd) { if (odd->odd_target == NULL) return; if (odd->odd_target->ott_mode == OVERLAY_TARGET_DYNAMIC) { refhash_t *rp = odd->odd_target->ott_u.ott_dyn.ott_dhash; avl_tree_t *ap = &odd->odd_target->ott_u.ott_dyn.ott_tree; overlay_target_entry_t *ote; /* * Our AVL tree and hashtable contain the same elements, * therefore we should just remove it from the tree, but then * delete the entries when we remove them from the hash table * (which happens through the refhash dtor). */ while ((ote = avl_first(ap)) != NULL) avl_remove(ap, ote); avl_destroy(ap); for (ote = refhash_first(rp); ote != NULL; ote = refhash_next(rp, ote)) { refhash_remove(rp, ote); } refhash_destroy(rp); } ASSERT(odd->odd_target->ott_ocount == 0); kmem_cache_free(overlay_target_cache, odd->odd_target); } int overlay_target_busy() { int ret; mutex_enter(&overlay_target_lock); ret = !list_is_empty(&overlay_thdl_list); mutex_exit(&overlay_target_lock); return (ret); } static void overlay_target_queue(overlay_target_entry_t *entry) { mutex_enter(&overlay_target_lock); mutex_enter(&entry->ote_ott->ott_lock); if (entry->ote_ott->ott_flags & OVERLAY_T_TEARDOWN) { mutex_exit(&entry->ote_ott->ott_lock); mutex_exit(&overlay_target_lock); return; } entry->ote_ott->ott_ocount++; mutex_exit(&entry->ote_ott->ott_lock); list_insert_tail(&overlay_target_list, entry); cv_signal(&overlay_target_condvar); mutex_exit(&overlay_target_lock); } void overlay_target_quiesce(overlay_target_t *ott) { if (ott == NULL) return; mutex_enter(&ott->ott_lock); ott->ott_flags |= OVERLAY_T_TEARDOWN; while (ott->ott_ocount != 0) cv_wait(&ott->ott_cond, &ott->ott_lock); mutex_exit(&ott->ott_lock); } /* * This functions assumes that the destination mode is OVERLAY_PLUGIN_D_IP | * OVERLAY_PLUGIN_D_PORT. As we don't have an implementation of anything else at * this time, say for NVGRE, we drop all packets that mcuh this. */ int overlay_target_lookup(overlay_dev_t *odd, mblk_t *mp, struct sockaddr *sock, socklen_t *slenp) { int ret; struct sockaddr_in6 *v6; overlay_target_t *ott; mac_header_info_t mhi; overlay_target_entry_t *entry; ASSERT(odd->odd_target != NULL); /* * At this point, the overlay device is in a mux which means that it's * been activated. At this point, parts of the target, such as the mode * and the destination are now read-only and we don't have to worry * about synchronization for them. */ ott = odd->odd_target; if (ott->ott_dest != (OVERLAY_PLUGIN_D_IP | OVERLAY_PLUGIN_D_PORT)) return (OVERLAY_TARGET_DROP); v6 = (struct sockaddr_in6 *)sock; bzero(v6, sizeof (struct sockaddr_in6)); v6->sin6_family = AF_INET6; if (ott->ott_mode == OVERLAY_TARGET_POINT) { mutex_enter(&ott->ott_lock); bcopy(&ott->ott_u.ott_point.otp_ip, &v6->sin6_addr, sizeof (struct in6_addr)); v6->sin6_port = htons(ott->ott_u.ott_point.otp_port); mutex_exit(&ott->ott_lock); *slenp = sizeof (struct sockaddr_in6); return (OVERLAY_TARGET_OK); } ASSERT(ott->ott_mode == OVERLAY_TARGET_DYNAMIC); /* * Note we only want the MAC address here, therefore we won't bother * using mac_vlan_header_info(). If any caller needs the vlan info at * this point, this should change to a call to mac_vlan_header_info(). */ if (mac_header_info(odd->odd_mh, mp, &mhi) != 0) return (OVERLAY_TARGET_DROP); mutex_enter(&ott->ott_lock); entry = refhash_lookup(ott->ott_u.ott_dyn.ott_dhash, mhi.mhi_daddr); if (entry == NULL) { entry = kmem_cache_alloc(overlay_entry_cache, KM_NOSLEEP_LAZY); if (entry == NULL) { mutex_exit(&ott->ott_lock); return (OVERLAY_TARGET_DROP); } bcopy(mhi.mhi_daddr, entry->ote_addr, ETHERADDRL); entry->ote_chead = entry->ote_ctail = mp; entry->ote_mbsize = msgsize(mp); entry->ote_flags |= OVERLAY_ENTRY_F_PENDING; entry->ote_ott = ott; entry->ote_odd = odd; refhash_insert(ott->ott_u.ott_dyn.ott_dhash, entry); avl_add(&ott->ott_u.ott_dyn.ott_tree, entry); mutex_exit(&ott->ott_lock); overlay_target_queue(entry); return (OVERLAY_TARGET_ASYNC); } refhash_hold(ott->ott_u.ott_dyn.ott_dhash, entry); mutex_exit(&ott->ott_lock); mutex_enter(&entry->ote_lock); if (entry->ote_flags & OVERLAY_ENTRY_F_DROP) { ret = OVERLAY_TARGET_DROP; } else if (entry->ote_flags & OVERLAY_ENTRY_F_VALID) { bcopy(&entry->ote_dest.otp_ip, &v6->sin6_addr, sizeof (struct in6_addr)); v6->sin6_port = htons(entry->ote_dest.otp_port); *slenp = sizeof (struct sockaddr_in6); ret = OVERLAY_TARGET_OK; } else { size_t mlen = msgsize(mp); if (mlen + entry->ote_mbsize > overlay_ent_size) { ret = OVERLAY_TARGET_DROP; } else { if (entry->ote_ctail != NULL) { ASSERT(entry->ote_ctail->b_next == NULL); entry->ote_ctail->b_next = mp; entry->ote_ctail = mp; } else { entry->ote_chead = mp; entry->ote_ctail = mp; } entry->ote_mbsize += mlen; if ((entry->ote_flags & OVERLAY_ENTRY_F_PENDING) == 0) { entry->ote_flags |= OVERLAY_ENTRY_F_PENDING; overlay_target_queue(entry); } ret = OVERLAY_TARGET_ASYNC; } } mutex_exit(&entry->ote_lock); mutex_enter(&ott->ott_lock); refhash_rele(ott->ott_u.ott_dyn.ott_dhash, entry); mutex_exit(&ott->ott_lock); return (ret); } /* ARGSUSED */ static int overlay_target_info(overlay_target_hdl_t *thdl, void *arg) { overlay_dev_t *odd; overlay_targ_info_t *oti = arg; odd = overlay_hold_by_dlid(oti->oti_linkid); if (odd == NULL) return (ENOENT); mutex_enter(&odd->odd_lock); oti->oti_flags = 0; oti->oti_needs = odd->odd_plugin->ovp_dest; if (odd->odd_flags & OVERLAY_F_DEGRADED) oti->oti_flags |= OVERLAY_TARG_INFO_F_DEGRADED; if (odd->odd_flags & OVERLAY_F_ACTIVATED) oti->oti_flags |= OVERLAY_TARG_INFO_F_ACTIVE; oti->oti_vnetid = odd->odd_vid; mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (0); } /* ARGSUSED */ static int overlay_target_associate(overlay_target_hdl_t *thdl, void *arg) { overlay_dev_t *odd; overlay_target_t *ott; overlay_targ_associate_t *ota = arg; odd = overlay_hold_by_dlid(ota->ota_linkid); if (odd == NULL) return (ENOENT); if (ota->ota_id == 0) { overlay_hold_rele(odd); return (EINVAL); } if (ota->ota_mode != OVERLAY_TARGET_POINT && ota->ota_mode != OVERLAY_TARGET_DYNAMIC) { overlay_hold_rele(odd); return (EINVAL); } if (ota->ota_provides != odd->odd_plugin->ovp_dest) { overlay_hold_rele(odd); return (EINVAL); } if (ota->ota_mode == OVERLAY_TARGET_POINT) { if (ota->ota_provides & OVERLAY_PLUGIN_D_IP) { if (IN6_IS_ADDR_UNSPECIFIED(&ota->ota_point.otp_ip) || IN6_IS_ADDR_V4COMPAT(&ota->ota_point.otp_ip) || IN6_IS_ADDR_V4MAPPED_ANY(&ota->ota_point.otp_ip)) { overlay_hold_rele(odd); return (EINVAL); } } if (ota->ota_provides & OVERLAY_PLUGIN_D_PORT) { if (ota->ota_point.otp_port == 0) { overlay_hold_rele(odd); return (EINVAL); } } } ott = kmem_cache_alloc(overlay_target_cache, KM_SLEEP); ott->ott_flags = 0; ott->ott_ocount = 0; ott->ott_mode = ota->ota_mode; ott->ott_dest = ota->ota_provides; ott->ott_id = ota->ota_id; if (ott->ott_mode == OVERLAY_TARGET_POINT) { bcopy(&ota->ota_point, &ott->ott_u.ott_point, sizeof (overlay_target_point_t)); } else { ott->ott_u.ott_dyn.ott_dhash = refhash_create(OVERLAY_HSIZE, overlay_mac_hash, overlay_mac_cmp, overlay_target_entry_dtor, sizeof (overlay_target_entry_t), offsetof(overlay_target_entry_t, ote_reflink), offsetof(overlay_target_entry_t, ote_addr), KM_SLEEP); avl_create(&ott->ott_u.ott_dyn.ott_tree, overlay_mac_avl, sizeof (overlay_target_entry_t), offsetof(overlay_target_entry_t, ote_avllink)); } mutex_enter(&odd->odd_lock); if (odd->odd_flags & OVERLAY_F_VARPD) { mutex_exit(&odd->odd_lock); kmem_cache_free(overlay_target_cache, ott); overlay_hold_rele(odd); return (EEXIST); } odd->odd_flags |= OVERLAY_F_VARPD; odd->odd_target = ott; mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (0); } /* ARGSUSED */ static int overlay_target_degrade(overlay_target_hdl_t *thdl, void *arg) { overlay_dev_t *odd; overlay_targ_degrade_t *otd = arg; odd = overlay_hold_by_dlid(otd->otd_linkid); if (odd == NULL) return (ENOENT); overlay_fm_degrade(odd, otd->otd_buf); overlay_hold_rele(odd); return (0); } /* ARGSUSED */ static int overlay_target_restore(overlay_target_hdl_t *thdl, void *arg) { overlay_dev_t *odd; overlay_targ_id_t *otid = arg; odd = overlay_hold_by_dlid(otid->otid_linkid); if (odd == NULL) return (ENOENT); overlay_fm_restore(odd); overlay_hold_rele(odd); return (0); } /* ARGSUSED */ static int overlay_target_disassociate(overlay_target_hdl_t *thdl, void *arg) { overlay_dev_t *odd; overlay_targ_id_t *otid = arg; odd = overlay_hold_by_dlid(otid->otid_linkid); if (odd == NULL) return (ENOENT); mutex_enter(&odd->odd_lock); odd->odd_flags &= ~OVERLAY_F_VARPD; mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (0); } static int overlay_target_lookup_request(overlay_target_hdl_t *thdl, void *arg) { overlay_targ_lookup_t *otl = arg; overlay_target_entry_t *entry; clock_t ret, timeout; mac_header_info_t mhi; timeout = ddi_get_lbolt() + drv_usectohz(MICROSEC); again: mutex_enter(&overlay_target_lock); while (list_is_empty(&overlay_target_list)) { ret = cv_timedwait(&overlay_target_condvar, &overlay_target_lock, timeout); if (ret == -1) { mutex_exit(&overlay_target_lock); return (ETIME); } } entry = list_remove_head(&overlay_target_list); mutex_exit(&overlay_target_lock); mutex_enter(&entry->ote_lock); if (entry->ote_flags & OVERLAY_ENTRY_F_VALID) { ASSERT(entry->ote_chead == NULL); mutex_exit(&entry->ote_lock); goto again; } ASSERT(entry->ote_chead != NULL); /* * If we have a bogon that doesn't have a valid mac header, drop it and * try again. */ if (mac_vlan_header_info(entry->ote_odd->odd_mh, entry->ote_chead, &mhi) != 0) { boolean_t queue = B_FALSE; mblk_t *mp = entry->ote_chead; entry->ote_chead = mp->b_next; mp->b_next = NULL; if (entry->ote_ctail == mp) entry->ote_ctail = entry->ote_chead; entry->ote_mbsize -= msgsize(mp); if (entry->ote_chead != NULL) queue = B_TRUE; mutex_exit(&entry->ote_lock); if (queue == B_TRUE) overlay_target_queue(entry); freemsg(mp); goto again; } otl->otl_dlid = entry->ote_odd->odd_linkid; otl->otl_reqid = (uintptr_t)entry; otl->otl_varpdid = entry->ote_ott->ott_id; otl->otl_vnetid = entry->ote_odd->odd_vid; otl->otl_hdrsize = mhi.mhi_hdrsize; otl->otl_pktsize = msgsize(entry->ote_chead) - otl->otl_hdrsize; bcopy(mhi.mhi_daddr, otl->otl_dstaddr, ETHERADDRL); bcopy(mhi.mhi_saddr, otl->otl_srcaddr, ETHERADDRL); otl->otl_dsttype = mhi.mhi_dsttype; otl->otl_sap = mhi.mhi_bindsap; otl->otl_vlan = VLAN_ID(mhi.mhi_tci); mutex_exit(&entry->ote_lock); mutex_enter(&thdl->oth_lock); list_insert_tail(&thdl->oth_outstanding, entry); mutex_exit(&thdl->oth_lock); return (0); } static int overlay_target_lookup_respond(overlay_target_hdl_t *thdl, void *arg) { const overlay_targ_resp_t *otr = arg; overlay_target_entry_t *entry; mblk_t *mp; mutex_enter(&thdl->oth_lock); for (entry = list_head(&thdl->oth_outstanding); entry != NULL; entry = list_next(&thdl->oth_outstanding, entry)) { if ((uintptr_t)entry == otr->otr_reqid) break; } if (entry == NULL) { mutex_exit(&thdl->oth_lock); return (EINVAL); } list_remove(&thdl->oth_outstanding, entry); mutex_exit(&thdl->oth_lock); mutex_enter(&entry->ote_lock); bcopy(&otr->otr_answer, &entry->ote_dest, sizeof (overlay_target_point_t)); entry->ote_flags &= ~OVERLAY_ENTRY_F_PENDING; entry->ote_flags |= OVERLAY_ENTRY_F_VALID; mp = entry->ote_chead; entry->ote_chead = NULL; entry->ote_ctail = NULL; entry->ote_mbsize = 0; entry->ote_vtime = gethrtime(); mutex_exit(&entry->ote_lock); /* * For now do an in-situ drain. */ mp = overlay_m_tx(entry->ote_odd, mp); freemsgchain(mp); mutex_enter(&entry->ote_ott->ott_lock); entry->ote_ott->ott_ocount--; cv_signal(&entry->ote_ott->ott_cond); mutex_exit(&entry->ote_ott->ott_lock); return (0); } static int overlay_target_lookup_drop(overlay_target_hdl_t *thdl, void *arg) { const overlay_targ_resp_t *otr = arg; overlay_target_entry_t *entry; mblk_t *mp; boolean_t queue = B_FALSE; mutex_enter(&thdl->oth_lock); for (entry = list_head(&thdl->oth_outstanding); entry != NULL; entry = list_next(&thdl->oth_outstanding, entry)) { if ((uintptr_t)entry == otr->otr_reqid) break; } if (entry == NULL) { mutex_exit(&thdl->oth_lock); return (EINVAL); } list_remove(&thdl->oth_outstanding, entry); mutex_exit(&thdl->oth_lock); mutex_enter(&entry->ote_lock); /* Safeguard against a confused varpd */ if (entry->ote_flags & OVERLAY_ENTRY_F_VALID) { entry->ote_flags &= ~OVERLAY_ENTRY_F_PENDING; DTRACE_PROBE1(overlay__target__valid__drop, overlay_target_entry_t *, entry); mutex_exit(&entry->ote_lock); goto done; } mp = entry->ote_chead; if (mp != NULL) { entry->ote_chead = mp->b_next; mp->b_next = NULL; if (entry->ote_ctail == mp) entry->ote_ctail = entry->ote_chead; entry->ote_mbsize -= msgsize(mp); } if (entry->ote_chead != NULL) { queue = B_TRUE; entry->ote_flags |= OVERLAY_ENTRY_F_PENDING; } else { entry->ote_flags &= ~OVERLAY_ENTRY_F_PENDING; } mutex_exit(&entry->ote_lock); if (queue == B_TRUE) overlay_target_queue(entry); freemsg(mp); done: mutex_enter(&entry->ote_ott->ott_lock); entry->ote_ott->ott_ocount--; cv_signal(&entry->ote_ott->ott_cond); mutex_exit(&entry->ote_ott->ott_lock); return (0); } /* ARGSUSED */ static int overlay_target_pkt_copyin(const void *ubuf, void **outp, size_t *bsize, int flags) { overlay_targ_pkt_t *pkt; overlay_targ_pkt32_t *pkt32; pkt = kmem_alloc(sizeof (overlay_targ_pkt_t), KM_SLEEP); *outp = pkt; *bsize = sizeof (overlay_targ_pkt_t); if (ddi_model_convert_from(flags & FMODELS) == DDI_MODEL_ILP32) { uintptr_t addr; if (ddi_copyin(ubuf, pkt, sizeof (overlay_targ_pkt32_t), flags & FKIOCTL) != 0) { kmem_free(pkt, *bsize); return (EFAULT); } pkt32 = (overlay_targ_pkt32_t *)pkt; addr = pkt32->otp_buf; pkt->otp_buf = (void *)addr; } else { if (ddi_copyin(ubuf, pkt, *bsize, flags & FKIOCTL) != 0) { kmem_free(pkt, *bsize); return (EFAULT); } } return (0); } static int overlay_target_pkt_copyout(void *ubuf, void *buf, size_t bufsize, int flags) { if (ddi_model_convert_from(flags & FMODELS) == DDI_MODEL_ILP32) { overlay_targ_pkt_t *pkt = buf; overlay_targ_pkt32_t *pkt32 = buf; uintptr_t addr = (uintptr_t)pkt->otp_buf; pkt32->otp_buf = (caddr32_t)addr; if (ddi_copyout(buf, ubuf, sizeof (overlay_targ_pkt32_t), flags & FKIOCTL) != 0) return (EFAULT); } else { if (ddi_copyout(buf, ubuf, bufsize, flags & FKIOCTL) != 0) return (EFAULT); } return (0); } static int overlay_target_packet(overlay_target_hdl_t *thdl, void *arg) { overlay_targ_pkt_t *pkt = arg; overlay_target_entry_t *entry; mblk_t *mp; size_t mlen; size_t boff; mutex_enter(&thdl->oth_lock); for (entry = list_head(&thdl->oth_outstanding); entry != NULL; entry = list_next(&thdl->oth_outstanding, entry)) { if ((uintptr_t)entry == pkt->otp_reqid) break; } if (entry == NULL) { mutex_exit(&thdl->oth_lock); return (EINVAL); } mutex_enter(&entry->ote_lock); mutex_exit(&thdl->oth_lock); mp = entry->ote_chead; /* Protect against a rogue varpd */ if (mp == NULL) { mutex_exit(&entry->ote_lock); return (EINVAL); } mlen = MIN(msgsize(mp), pkt->otp_size); pkt->otp_size = mlen; boff = 0; while (mlen > 0) { size_t wlen = MIN(MBLKL(mp), mlen); if (ddi_copyout(mp->b_rptr, (void *)((uintptr_t)pkt->otp_buf + boff), wlen, 0) != 0) { mutex_exit(&entry->ote_lock); return (EFAULT); } mlen -= wlen; boff += wlen; mp = mp->b_cont; } mutex_exit(&entry->ote_lock); return (0); } static int overlay_target_inject(overlay_target_hdl_t *thdl, void *arg) { overlay_targ_pkt_t *pkt = arg; overlay_target_entry_t *entry; overlay_dev_t *odd; mblk_t *mp; if (pkt->otp_size > ETHERMAX + VLAN_TAGSZ) return (EINVAL); mp = allocb(pkt->otp_size, 0); if (mp == NULL) return (ENOMEM); if (ddi_copyin(pkt->otp_buf, mp->b_rptr, pkt->otp_size, 0) != 0) { freeb(mp); return (EFAULT); } mp->b_wptr += pkt->otp_size; if (pkt->otp_linkid != UINT64_MAX) { odd = overlay_hold_by_dlid(pkt->otp_linkid); if (odd == NULL) { freeb(mp); return (ENOENT); } } else { mutex_enter(&thdl->oth_lock); for (entry = list_head(&thdl->oth_outstanding); entry != NULL; entry = list_next(&thdl->oth_outstanding, entry)) { if ((uintptr_t)entry == pkt->otp_reqid) break; } if (entry == NULL) { mutex_exit(&thdl->oth_lock); freeb(mp); return (ENOENT); } odd = entry->ote_odd; mutex_exit(&thdl->oth_lock); } mutex_enter(&odd->odd_lock); if ((odd->odd_flags & OVERLAY_F_MDDROP) || !(odd->odd_flags & OVERLAY_F_IN_MUX)) { /* Can't do receive... */ mutex_exit(&odd->odd_lock); OVERLAY_FREEMSG(mp, "dev dropped"); freeb(mp); return (EBUSY); } overlay_io_start(odd, OVERLAY_F_IN_RX); mutex_exit(&odd->odd_lock); mac_rx(odd->odd_mh, NULL, mp); mutex_enter(&odd->odd_lock); overlay_io_done(odd, OVERLAY_F_IN_RX); mutex_exit(&odd->odd_lock); return (0); } static int overlay_target_resend(overlay_target_hdl_t *thdl, void *arg) { overlay_targ_pkt_t *pkt = arg; overlay_target_entry_t *entry; overlay_dev_t *odd; mblk_t *mp; if (pkt->otp_size > ETHERMAX + VLAN_TAGSZ) return (EINVAL); mp = allocb(pkt->otp_size, 0); if (mp == NULL) return (ENOMEM); if (ddi_copyin(pkt->otp_buf, mp->b_rptr, pkt->otp_size, 0) != 0) { freeb(mp); return (EFAULT); } mp->b_wptr += pkt->otp_size; if (pkt->otp_linkid != UINT64_MAX) { odd = overlay_hold_by_dlid(pkt->otp_linkid); if (odd == NULL) { freeb(mp); return (ENOENT); } } else { mutex_enter(&thdl->oth_lock); for (entry = list_head(&thdl->oth_outstanding); entry != NULL; entry = list_next(&thdl->oth_outstanding, entry)) { if ((uintptr_t)entry == pkt->otp_reqid) break; } if (entry == NULL) { mutex_exit(&thdl->oth_lock); freeb(mp); return (ENOENT); } odd = entry->ote_odd; mutex_exit(&thdl->oth_lock); } mp = overlay_m_tx(odd, mp); freemsgchain(mp); return (0); } typedef struct overlay_targ_list_int { boolean_t otli_count; uint32_t otli_cur; uint32_t otli_nents; uint32_t otli_ents[]; } overlay_targ_list_int_t; static int overlay_target_list_copyin(const void *ubuf, void **outp, size_t *bsize, int flags) { overlay_targ_list_t n; overlay_targ_list_int_t *otl; if (ddi_copyin(ubuf, &n, sizeof (overlay_targ_list_t), flags & FKIOCTL) != 0) return (EFAULT); /* */ if (n.otl_nents >= INT32_MAX / sizeof (uint32_t)) return (EINVAL); *bsize = sizeof (overlay_targ_list_int_t) + sizeof (uint32_t) * n.otl_nents; otl = kmem_zalloc(*bsize, KM_SLEEP); otl->otli_cur = 0; otl->otli_nents = n.otl_nents; if (otl->otli_nents != 0) { otl->otli_count = B_FALSE; if (ddi_copyin((void *)((uintptr_t)ubuf + offsetof(overlay_targ_list_t, otl_ents)), otl->otli_ents, n.otl_nents * sizeof (uint32_t), flags & FKIOCTL) != 0) { kmem_free(otl, *bsize); return (EFAULT); } } else { otl->otli_count = B_TRUE; } *outp = otl; return (0); } static int overlay_target_ioctl_list_cb(overlay_dev_t *odd, void *arg) { overlay_targ_list_int_t *otl = arg; if (otl->otli_cur < otl->otli_nents) otl->otli_ents[otl->otli_cur] = odd->odd_linkid; otl->otli_cur++; return (0); } /* ARGSUSED */ static int overlay_target_ioctl_list(overlay_target_hdl_t *thdl, void *arg) { overlay_dev_iter(overlay_target_ioctl_list_cb, arg); return (0); } /* ARGSUSED */ static int overlay_target_list_copyout(void *ubuf, void *buf, size_t bufsize, int flags) { overlay_targ_list_int_t *otl = buf; if (ddi_copyout(&otl->otli_cur, ubuf, sizeof (uint32_t), flags & FKIOCTL) != 0) return (EFAULT); if (otl->otli_count == B_FALSE) { if (ddi_copyout(otl->otli_ents, (void *)((uintptr_t)ubuf + offsetof(overlay_targ_list_t, otl_ents)), sizeof (uint32_t) * otl->otli_nents, flags & FKIOCTL) != 0) return (EFAULT); } return (0); } /* ARGSUSED */ static int overlay_target_cache_get(overlay_target_hdl_t *thdl, void *arg) { int ret = 0; overlay_dev_t *odd; overlay_target_t *ott; overlay_targ_cache_t *otc = arg; odd = overlay_hold_by_dlid(otc->otc_linkid); if (odd == NULL) return (ENOENT); mutex_enter(&odd->odd_lock); if (!(odd->odd_flags & OVERLAY_F_VARPD)) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (ENXIO); } ott = odd->odd_target; if (ott->ott_mode != OVERLAY_TARGET_POINT && ott->ott_mode != OVERLAY_TARGET_DYNAMIC) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (ENOTSUP); } mutex_enter(&ott->ott_lock); mutex_exit(&odd->odd_lock); if (ott->ott_mode == OVERLAY_TARGET_POINT) { otc->otc_entry.otce_flags = 0; bcopy(&ott->ott_u.ott_point, &otc->otc_entry.otce_dest, sizeof (overlay_target_point_t)); } else { overlay_target_entry_t *ote; ote = refhash_lookup(ott->ott_u.ott_dyn.ott_dhash, otc->otc_entry.otce_mac); if (ote != NULL) { mutex_enter(&ote->ote_lock); if ((ote->ote_flags & OVERLAY_ENTRY_F_VALID_MASK) != 0) { if (ote->ote_flags & OVERLAY_ENTRY_F_DROP) { otc->otc_entry.otce_flags = OVERLAY_TARGET_CACHE_DROP; } else { otc->otc_entry.otce_flags = 0; bcopy(&ote->ote_dest, &otc->otc_entry.otce_dest, sizeof (overlay_target_point_t)); } ret = 0; } else { ret = ENOENT; } mutex_exit(&ote->ote_lock); } else { ret = ENOENT; } } mutex_exit(&ott->ott_lock); overlay_hold_rele(odd); return (ret); } /* ARGSUSED */ static int overlay_target_cache_set(overlay_target_hdl_t *thdl, void *arg) { overlay_dev_t *odd; overlay_target_t *ott; overlay_target_entry_t *ote; overlay_targ_cache_t *otc = arg; mblk_t *mp = NULL; if (otc->otc_entry.otce_flags & ~OVERLAY_TARGET_CACHE_DROP) return (EINVAL); odd = overlay_hold_by_dlid(otc->otc_linkid); if (odd == NULL) return (ENOENT); mutex_enter(&odd->odd_lock); if (!(odd->odd_flags & OVERLAY_F_VARPD)) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (ENXIO); } ott = odd->odd_target; if (ott->ott_mode != OVERLAY_TARGET_DYNAMIC) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (ENOTSUP); } mutex_enter(&ott->ott_lock); mutex_exit(&odd->odd_lock); ote = refhash_lookup(ott->ott_u.ott_dyn.ott_dhash, otc->otc_entry.otce_mac); if (ote == NULL) { ote = kmem_cache_alloc(overlay_entry_cache, KM_SLEEP); bcopy(otc->otc_entry.otce_mac, ote->ote_addr, ETHERADDRL); ote->ote_chead = ote->ote_ctail = NULL; ote->ote_mbsize = 0; ote->ote_ott = ott; ote->ote_odd = odd; mutex_enter(&ote->ote_lock); refhash_insert(ott->ott_u.ott_dyn.ott_dhash, ote); avl_add(&ott->ott_u.ott_dyn.ott_tree, ote); } else { mutex_enter(&ote->ote_lock); } if (otc->otc_entry.otce_flags & OVERLAY_TARGET_CACHE_DROP) { ote->ote_flags |= OVERLAY_ENTRY_F_DROP; } else { ote->ote_flags |= OVERLAY_ENTRY_F_VALID; bcopy(&otc->otc_entry.otce_dest, &ote->ote_dest, sizeof (overlay_target_point_t)); mp = ote->ote_chead; ote->ote_chead = NULL; ote->ote_ctail = NULL; ote->ote_mbsize = 0; ote->ote_vtime = gethrtime(); } mutex_exit(&ote->ote_lock); mutex_exit(&ott->ott_lock); if (mp != NULL) { mp = overlay_m_tx(ote->ote_odd, mp); freemsgchain(mp); } overlay_hold_rele(odd); return (0); } /* ARGSUSED */ static int overlay_target_cache_remove(overlay_target_hdl_t *thdl, void *arg) { int ret = 0; overlay_dev_t *odd; overlay_target_t *ott; overlay_target_entry_t *ote; overlay_targ_cache_t *otc = arg; odd = overlay_hold_by_dlid(otc->otc_linkid); if (odd == NULL) return (ENOENT); mutex_enter(&odd->odd_lock); if (!(odd->odd_flags & OVERLAY_F_VARPD)) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (ENXIO); } ott = odd->odd_target; if (ott->ott_mode != OVERLAY_TARGET_DYNAMIC) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (ENOTSUP); } mutex_enter(&ott->ott_lock); mutex_exit(&odd->odd_lock); ote = refhash_lookup(ott->ott_u.ott_dyn.ott_dhash, otc->otc_entry.otce_mac); if (ote != NULL) { mutex_enter(&ote->ote_lock); ote->ote_flags &= ~OVERLAY_ENTRY_F_VALID_MASK; mutex_exit(&ote->ote_lock); ret = 0; } else { ret = ENOENT; } mutex_exit(&ott->ott_lock); overlay_hold_rele(odd); return (ret); } /* ARGSUSED */ static int overlay_target_cache_flush(overlay_target_hdl_t *thdl, void *arg) { avl_tree_t *avl; overlay_dev_t *odd; overlay_target_t *ott; overlay_target_entry_t *ote; overlay_targ_cache_t *otc = arg; odd = overlay_hold_by_dlid(otc->otc_linkid); if (odd == NULL) return (ENOENT); mutex_enter(&odd->odd_lock); if (!(odd->odd_flags & OVERLAY_F_VARPD)) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (ENXIO); } ott = odd->odd_target; if (ott->ott_mode != OVERLAY_TARGET_DYNAMIC) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (ENOTSUP); } mutex_enter(&ott->ott_lock); mutex_exit(&odd->odd_lock); avl = &ott->ott_u.ott_dyn.ott_tree; for (ote = avl_first(avl); ote != NULL; ote = AVL_NEXT(avl, ote)) { mutex_enter(&ote->ote_lock); ote->ote_flags &= ~OVERLAY_ENTRY_F_VALID_MASK; mutex_exit(&ote->ote_lock); } ote = refhash_lookup(ott->ott_u.ott_dyn.ott_dhash, otc->otc_entry.otce_mac); mutex_exit(&ott->ott_lock); overlay_hold_rele(odd); return (0); } static int overlay_target_cache_iter_copyin(const void *ubuf, void **outp, size_t *bsize, int flags) { overlay_targ_cache_iter_t base, *iter; if (ddi_copyin(ubuf, &base, sizeof (overlay_targ_cache_iter_t), flags & FKIOCTL) != 0) return (EFAULT); if (base.otci_count > OVERLAY_TARGET_ITER_MAX) return (E2BIG); if (base.otci_count == 0) return (EINVAL); *bsize = sizeof (overlay_targ_cache_iter_t) + base.otci_count * sizeof (overlay_targ_cache_entry_t); iter = kmem_alloc(*bsize, KM_SLEEP); bcopy(&base, iter, sizeof (overlay_targ_cache_iter_t)); *outp = iter; return (0); } typedef struct overlay_targ_cache_marker { uint8_t otcm_mac[ETHERADDRL]; uint16_t otcm_done; } overlay_targ_cache_marker_t; /* ARGSUSED */ static int overlay_target_cache_iter(overlay_target_hdl_t *thdl, void *arg) { overlay_dev_t *odd; overlay_target_t *ott; overlay_target_entry_t lookup, *ent; overlay_targ_cache_marker_t *mark; avl_index_t where; avl_tree_t *avl; uint16_t written = 0; overlay_targ_cache_iter_t *iter = arg; mark = (void *)&iter->otci_marker; if (mark->otcm_done != 0) { iter->otci_count = 0; return (0); } odd = overlay_hold_by_dlid(iter->otci_linkid); if (odd == NULL) return (ENOENT); mutex_enter(&odd->odd_lock); if (!(odd->odd_flags & OVERLAY_F_VARPD)) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (ENXIO); } ott = odd->odd_target; if (ott->ott_mode != OVERLAY_TARGET_DYNAMIC && ott->ott_mode != OVERLAY_TARGET_POINT) { mutex_exit(&odd->odd_lock); overlay_hold_rele(odd); return (ENOTSUP); } /* * Holding this lock across the entire iteration probably isn't very * good. We should perhaps add an r/w lock for the avl tree. But we'll * wait until we now it's necessary before we do more. */ mutex_enter(&ott->ott_lock); mutex_exit(&odd->odd_lock); if (ott->ott_mode == OVERLAY_TARGET_POINT) { overlay_targ_cache_entry_t *out = &iter->otci_ents[0]; bzero(out->otce_mac, ETHERADDRL); out->otce_flags = 0; bcopy(&ott->ott_u.ott_point, &out->otce_dest, sizeof (overlay_target_point_t)); written++; mark->otcm_done = 1; } avl = &ott->ott_u.ott_dyn.ott_tree; bcopy(mark->otcm_mac, lookup.ote_addr, ETHERADDRL); ent = avl_find(avl, &lookup, &where); /* * NULL ent means that the entry does not exist, so we want to start * with the closest node in the tree. This means that we implicitly rely * on the tree's order and the first node will be the mac 00:00:00:00:00 * and the last will be ff:ff:ff:ff:ff:ff. */ if (ent == NULL) { ent = avl_nearest(avl, where, AVL_AFTER); if (ent == NULL) { mark->otcm_done = 1; goto done; } } for (; ent != NULL && written < iter->otci_count; ent = AVL_NEXT(avl, ent)) { overlay_targ_cache_entry_t *out = &iter->otci_ents[written]; mutex_enter(&ent->ote_lock); if ((ent->ote_flags & OVERLAY_ENTRY_F_VALID_MASK) == 0) { mutex_exit(&ent->ote_lock); continue; } bcopy(ent->ote_addr, out->otce_mac, ETHERADDRL); out->otce_flags = 0; if (ent->ote_flags & OVERLAY_ENTRY_F_DROP) out->otce_flags |= OVERLAY_TARGET_CACHE_DROP; if (ent->ote_flags & OVERLAY_ENTRY_F_VALID) bcopy(&ent->ote_dest, &out->otce_dest, sizeof (overlay_target_point_t)); written++; mutex_exit(&ent->ote_lock); } if (ent != NULL) { bcopy(ent->ote_addr, mark->otcm_mac, ETHERADDRL); } else { mark->otcm_done = 1; } done: iter->otci_count = written; mutex_exit(&ott->ott_lock); overlay_hold_rele(odd); return (0); } /* ARGSUSED */ static int overlay_target_cache_iter_copyout(void *ubuf, void *buf, size_t bufsize, int flags) { size_t outsize; const overlay_targ_cache_iter_t *iter = buf; outsize = sizeof (overlay_targ_cache_iter_t) + iter->otci_count * sizeof (overlay_targ_cache_entry_t); if (ddi_copyout(buf, ubuf, outsize, flags & FKIOCTL) != 0) return (EFAULT); return (0); } static overlay_target_ioctl_t overlay_target_ioctab[] = { { OVERLAY_TARG_INFO, B_TRUE, B_TRUE, NULL, overlay_target_info, NULL, sizeof (overlay_targ_info_t) }, { OVERLAY_TARG_ASSOCIATE, B_TRUE, B_FALSE, NULL, overlay_target_associate, NULL, sizeof (overlay_targ_associate_t) }, { OVERLAY_TARG_DISASSOCIATE, B_TRUE, B_FALSE, NULL, overlay_target_disassociate, NULL, sizeof (overlay_targ_id_t) }, { OVERLAY_TARG_DEGRADE, B_TRUE, B_FALSE, NULL, overlay_target_degrade, NULL, sizeof (overlay_targ_degrade_t) }, { OVERLAY_TARG_RESTORE, B_TRUE, B_FALSE, NULL, overlay_target_restore, NULL, sizeof (overlay_targ_id_t) }, { OVERLAY_TARG_LOOKUP, B_FALSE, B_TRUE, NULL, overlay_target_lookup_request, NULL, sizeof (overlay_targ_lookup_t) }, { OVERLAY_TARG_RESPOND, B_TRUE, B_FALSE, NULL, overlay_target_lookup_respond, NULL, sizeof (overlay_targ_resp_t) }, { OVERLAY_TARG_DROP, B_TRUE, B_FALSE, NULL, overlay_target_lookup_drop, NULL, sizeof (overlay_targ_resp_t) }, { OVERLAY_TARG_PKT, B_TRUE, B_TRUE, overlay_target_pkt_copyin, overlay_target_packet, overlay_target_pkt_copyout, sizeof (overlay_targ_pkt_t) }, { OVERLAY_TARG_INJECT, B_TRUE, B_FALSE, overlay_target_pkt_copyin, overlay_target_inject, NULL, sizeof (overlay_targ_pkt_t) }, { OVERLAY_TARG_RESEND, B_TRUE, B_FALSE, overlay_target_pkt_copyin, overlay_target_resend, NULL, sizeof (overlay_targ_pkt_t) }, { OVERLAY_TARG_LIST, B_FALSE, B_TRUE, overlay_target_list_copyin, overlay_target_ioctl_list, overlay_target_list_copyout, sizeof (overlay_targ_list_t) }, { OVERLAY_TARG_CACHE_GET, B_FALSE, B_TRUE, NULL, overlay_target_cache_get, NULL, sizeof (overlay_targ_cache_t) }, { OVERLAY_TARG_CACHE_SET, B_TRUE, B_TRUE, NULL, overlay_target_cache_set, NULL, sizeof (overlay_targ_cache_t) }, { OVERLAY_TARG_CACHE_REMOVE, B_TRUE, B_TRUE, NULL, overlay_target_cache_remove, NULL, sizeof (overlay_targ_cache_t) }, { OVERLAY_TARG_CACHE_FLUSH, B_TRUE, B_TRUE, NULL, overlay_target_cache_flush, NULL, sizeof (overlay_targ_cache_t) }, { OVERLAY_TARG_CACHE_ITER, B_FALSE, B_TRUE, overlay_target_cache_iter_copyin, overlay_target_cache_iter, overlay_target_cache_iter_copyout, sizeof (overlay_targ_cache_iter_t) }, { 0 } }; int overlay_target_open(dev_t *devp, int flags, int otype, cred_t *credp) { minor_t mid; overlay_target_hdl_t *thdl; if (secpolicy_dl_config(credp) != 0) return (EPERM); if (getminor(*devp) != 0) return (ENXIO); if (otype & OTYP_BLK) return (EINVAL); if (flags & ~(FREAD | FWRITE | FEXCL)) return (EINVAL); if ((flags & FWRITE) && !(flags & FEXCL)) return (EINVAL); if (!(flags & FREAD) && !(flags & FWRITE)) return (EINVAL); if (crgetzoneid(credp) != GLOBAL_ZONEID) return (EPERM); mid = id_alloc(overlay_thdl_idspace); if (ddi_soft_state_zalloc(overlay_thdl_state, mid) != 0) { id_free(overlay_thdl_idspace, mid); return (ENXIO); } thdl = ddi_get_soft_state(overlay_thdl_state, mid); VERIFY(thdl != NULL); thdl->oth_minor = mid; thdl->oth_zoneid = crgetzoneid(credp); thdl->oth_oflags = flags; mutex_init(&thdl->oth_lock, NULL, MUTEX_DRIVER, NULL); list_create(&thdl->oth_outstanding, sizeof (overlay_target_entry_t), offsetof(overlay_target_entry_t, ote_qlink)); *devp = makedevice(getmajor(*devp), mid); mutex_enter(&overlay_target_lock); if ((flags & FEXCL) && overlay_target_excl == B_TRUE) { mutex_exit(&overlay_target_lock); list_destroy(&thdl->oth_outstanding); mutex_destroy(&thdl->oth_lock); ddi_soft_state_free(overlay_thdl_state, mid); id_free(overlay_thdl_idspace, mid); return (EEXIST); } else if ((flags & FEXCL) != 0) { VERIFY(overlay_target_excl == B_FALSE); overlay_target_excl = B_TRUE; } list_insert_tail(&overlay_thdl_list, thdl); mutex_exit(&overlay_target_lock); return (0); } /* ARGSUSED */ int overlay_target_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { overlay_target_ioctl_t *ioc; overlay_target_hdl_t *thdl; if (secpolicy_dl_config(credp) != 0) return (EPERM); if ((thdl = ddi_get_soft_state(overlay_thdl_state, getminor(dev))) == NULL) return (ENXIO); for (ioc = &overlay_target_ioctab[0]; ioc->oti_cmd != 0; ioc++) { int ret; caddr_t buf; size_t bufsize; if (ioc->oti_cmd != cmd) continue; if (ioc->oti_write == B_TRUE && !(mode & FWRITE)) return (EBADF); if (ioc->oti_copyin == NULL) { bufsize = ioc->oti_size; buf = kmem_alloc(bufsize, KM_SLEEP); if (ddi_copyin((void *)(uintptr_t)arg, buf, bufsize, mode & FKIOCTL) != 0) { kmem_free(buf, bufsize); return (EFAULT); } } else { if ((ret = ioc->oti_copyin((void *)(uintptr_t)arg, (void **)&buf, &bufsize, mode)) != 0) return (ret); } ret = ioc->oti_func(thdl, buf); if (ret == 0 && ioc->oti_size != 0 && ioc->oti_ncopyout == B_TRUE) { if (ioc->oti_copyout == NULL) { if (ddi_copyout(buf, (void *)(uintptr_t)arg, bufsize, mode & FKIOCTL) != 0) ret = EFAULT; } else { ret = ioc->oti_copyout((void *)(uintptr_t)arg, buf, bufsize, mode); } } kmem_free(buf, bufsize); return (ret); } return (ENOTTY); } /* ARGSUSED */ int overlay_target_close(dev_t dev, int flags, int otype, cred_t *credp) { overlay_target_hdl_t *thdl; overlay_target_entry_t *entry; minor_t mid = getminor(dev); if ((thdl = ddi_get_soft_state(overlay_thdl_state, mid)) == NULL) return (ENXIO); mutex_enter(&overlay_target_lock); list_remove(&overlay_thdl_list, thdl); mutex_enter(&thdl->oth_lock); while ((entry = list_remove_head(&thdl->oth_outstanding)) != NULL) list_insert_tail(&overlay_target_list, entry); cv_signal(&overlay_target_condvar); mutex_exit(&thdl->oth_lock); if ((thdl->oth_oflags & FEXCL) != 0) { VERIFY(overlay_target_excl == B_TRUE); overlay_target_excl = B_FALSE; } mutex_exit(&overlay_target_lock); list_destroy(&thdl->oth_outstanding); mutex_destroy(&thdl->oth_lock); mid = thdl->oth_minor; ddi_soft_state_free(overlay_thdl_state, mid); id_free(overlay_thdl_idspace, mid); return (0); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2018 Joyent, Inc. * Copyright 2022 MNX Cloud, Inc. */ /* * VXLAN encapsulation module * * * The VXLAN header looks as follows in network byte order: * * |0 3| 4 |5 31| * +----------+---+------------------------+ * | Reserved | I | Reserved | * +---------------------------------------+ * | Virtual Network ID | Reserved | * +----------------------------+----------+ * |0 23|24 31| * * All reserved values must be 0. The I bit must be 1. We call the top * word the VXLAN magic field for the time being. The second word is * definitely not the most friendly way to operate. Specifically, the ID * is a 24-bit big endian value, but we have to make sure not to use the * reserved byte. * * For us, VXLAN encapsulation is a fairly straightforward implementation. It * only has two properties, a listen_ip and a listen_port. These determine on * what address we should be listening on. While we do not have a default * address to listen upon, we do have a default port, which is the IANA assigned * port for VXLAN -- 4789. */ #include #include #include #include #include #include #include #include #include static const char *vxlan_ident = "vxlan"; static uint16_t vxlan_defport = IPPORT_VXLAN; /* * Should we enable UDP source port hashing for fanout. */ boolean_t vxlan_fanout = B_TRUE; /* * This represents the size in bytes that we want to allocate when allocating a * vxlan header block. This is intended such that lower levels can try and use * the message block that we allocate for the IP and UPD header. The hope is * that even if this is tunneled, that this is enough space. * * The vxlan_noalloc_min value represents the minimum amount of space we need to * consider not allocating a message block and just passing it down the stack in * this form. This number assumes that we have a VLAN tag, so 18 byte Ethernet * header, 20 byte IP header, 8 byte UDP header, and 8 byte VXLAN header. */ uint_t vxlan_alloc_size = 128; uint_t vxlan_noalloc_min = 54; static const char *vxlan_props[] = { "vxlan/listen_ip", "vxlan/listen_port", NULL }; typedef struct vxlan { kmutex_t vxl_lock; overlay_handle_t vxl_oh; uint16_t vxl_lport; boolean_t vxl_hladdr; struct in6_addr vxl_laddr; } vxlan_t; static int vxlan_o_init(overlay_handle_t oh, void **outp) { vxlan_t *vxl; vxl = kmem_alloc(sizeof (vxlan_t), KM_SLEEP); *outp = vxl; mutex_init(&vxl->vxl_lock, NULL, MUTEX_DRIVER, NULL); vxl->vxl_oh = oh; vxl->vxl_lport = vxlan_defport; vxl->vxl_hladdr = B_FALSE; return (0); } static void vxlan_o_fini(void *arg) { vxlan_t *vxl = arg; mutex_destroy(&vxl->vxl_lock); kmem_free(arg, sizeof (vxlan_t)); } static int vxlan_o_socket(void *arg, int *dp, int *fp, int *pp, struct sockaddr *addr, socklen_t *slenp) { vxlan_t *vxl = arg; struct sockaddr_in6 *in; in = (struct sockaddr_in6 *)addr; *dp = AF_INET6; *fp = SOCK_DGRAM; *pp = 0; bzero(in, sizeof (struct sockaddr_in6)); in->sin6_family = AF_INET6; /* * We should consider a more expressive private errno set that * provider's can use. */ mutex_enter(&vxl->vxl_lock); if (vxl->vxl_hladdr == B_FALSE) { mutex_exit(&vxl->vxl_lock); return (EINVAL); } in->sin6_port = htons(vxl->vxl_lport); in->sin6_addr = vxl->vxl_laddr; mutex_exit(&vxl->vxl_lock); *slenp = sizeof (struct sockaddr_in6); return (0); } static int vxlan_o_sockopt(ksocket_t ksock) { int val, err; if (vxlan_fanout == B_FALSE) return (0); val = UDP_HASH_VXLAN; err = ksocket_setsockopt(ksock, IPPROTO_UDP, UDP_SRCPORT_HASH, &val, sizeof (val), kcred); return (err); } /* ARGSUSED */ static int vxlan_o_encap(void *arg, mblk_t *mp, ovep_encap_info_t *einfop, mblk_t **outp) { mblk_t *ob; vxlan_hdr_t *vxh; ASSERT(einfop->ovdi_id < (1 << 24)); if (DB_REF(mp) != 1 || mp->b_rptr - vxlan_noalloc_min < DB_BASE(mp)) { /* * This allocation could get hot. We may want to have a good * way to cache and handle this allocation the same way that IP * does with keeping around a message block per entry, or * basically treating this as an immutable message block in the * system. Basically freemsg() will be a nop, but we'll do the * right thing with respect to the rest of the chain. */ ob = allocb(vxlan_alloc_size, 0); if (ob == NULL) return (ENOMEM); ob->b_wptr = DB_LIM(ob); ob->b_rptr = ob->b_wptr; ob->b_cont = mp; } else { ob = mp; } ob->b_rptr -= VXLAN_HDR_LEN; vxh = (vxlan_hdr_t *)ob->b_rptr; vxh->vxlan_flags = ntohl(VXLAN_F_VDI); vxh->vxlan_id = htonl((uint32_t)einfop->ovdi_id << VXLAN_ID_SHIFT); *outp = ob; return (0); } /* ARGSUSED */ static int vxlan_o_decap(void *arg, mblk_t *mp, ovep_encap_info_t *dinfop) { vxlan_hdr_t *vxh; if (MBLKL(mp) < sizeof (vxlan_hdr_t)) return (EINVAL); vxh = (vxlan_hdr_t *)mp->b_rptr; if ((ntohl(vxh->vxlan_flags) & VXLAN_F_VDI) == 0) return (EINVAL); dinfop->ovdi_id = ntohl(vxh->vxlan_id) >> VXLAN_ID_SHIFT; dinfop->ovdi_hdr_size = VXLAN_HDR_LEN; return (0); } static int vxlan_o_getprop(void *arg, const char *pr_name, void *buf, uint32_t *bufsize) { vxlan_t *vxl = arg; /* vxlan/listen_ip */ if (strcmp(pr_name, vxlan_props[0]) == 0) { if (*bufsize < sizeof (struct in6_addr)) return (EOVERFLOW); mutex_enter(&vxl->vxl_lock); if (vxl->vxl_hladdr == B_FALSE) { *bufsize = 0; } else { bcopy(&vxl->vxl_laddr, buf, sizeof (struct in6_addr)); *bufsize = sizeof (struct in6_addr); } mutex_exit(&vxl->vxl_lock); return (0); } /* vxlan/listen_port */ if (strcmp(pr_name, vxlan_props[1]) == 0) { uint64_t val; if (*bufsize < sizeof (uint64_t)) return (EOVERFLOW); mutex_enter(&vxl->vxl_lock); val = vxl->vxl_lport; bcopy(&val, buf, sizeof (uint64_t)); *bufsize = sizeof (uint64_t); mutex_exit(&vxl->vxl_lock); return (0); } return (EINVAL); } static int vxlan_o_setprop(void *arg, const char *pr_name, const void *buf, uint32_t bufsize) { vxlan_t *vxl = arg; /* vxlan/listen_ip */ if (strcmp(pr_name, vxlan_props[0]) == 0) { const struct in6_addr *ipv6 = buf; if (bufsize != sizeof (struct in6_addr)) return (EINVAL); if (IN6_IS_ADDR_V4COMPAT(ipv6)) return (EINVAL); if (IN6_IS_ADDR_MULTICAST(ipv6)) return (EINVAL); if (IN6_IS_ADDR_6TO4(ipv6)) return (EINVAL); if (IN6_IS_ADDR_V4MAPPED(ipv6)) { ipaddr_t v4; IN6_V4MAPPED_TO_IPADDR(ipv6, v4); if (IN_MULTICAST(ntohl(v4))) return (EINVAL); } mutex_enter(&vxl->vxl_lock); vxl->vxl_hladdr = B_TRUE; bcopy(ipv6, &vxl->vxl_laddr, sizeof (struct in6_addr)); mutex_exit(&vxl->vxl_lock); return (0); } /* vxlan/listen_port */ if (strcmp(pr_name, vxlan_props[1]) == 0) { const uint64_t *valp = buf; if (bufsize != 8) return (EINVAL); if (*valp == 0 || *valp > UINT16_MAX) return (EINVAL); mutex_enter(&vxl->vxl_lock); vxl->vxl_lport = *valp; mutex_exit(&vxl->vxl_lock); return (0); } return (EINVAL); } static int vxlan_o_propinfo(const char *pr_name, overlay_prop_handle_t phdl) { /* vxlan/listen_ip */ if (strcmp(pr_name, vxlan_props[0]) == 0) { overlay_prop_set_name(phdl, vxlan_props[0]); overlay_prop_set_prot(phdl, OVERLAY_PROP_PERM_RRW); overlay_prop_set_type(phdl, OVERLAY_PROP_T_IP); overlay_prop_set_nodefault(phdl); return (0); } if (strcmp(pr_name, vxlan_props[1]) == 0) { overlay_prop_set_name(phdl, vxlan_props[1]); overlay_prop_set_prot(phdl, OVERLAY_PROP_PERM_RRW); overlay_prop_set_type(phdl, OVERLAY_PROP_T_UINT); (void) overlay_prop_set_default(phdl, &vxlan_defport, sizeof (vxlan_defport)); overlay_prop_set_range_uint32(phdl, 1, UINT16_MAX); return (0); } return (EINVAL); } static struct overlay_plugin_ops vxlan_o_ops = { 0, vxlan_o_init, vxlan_o_fini, vxlan_o_encap, vxlan_o_decap, vxlan_o_socket, vxlan_o_sockopt, vxlan_o_getprop, vxlan_o_setprop, vxlan_o_propinfo }; static struct modlmisc vxlan_modlmisc = { &mod_miscops, "VXLAN encap plugin" }; static struct modlinkage vxlan_modlinkage = { MODREV_1, &vxlan_modlmisc }; int _init(void) { int err; overlay_plugin_register_t *ovrp; ovrp = overlay_plugin_alloc(OVEP_VERSION); if (ovrp == NULL) return (ENOTSUP); ovrp->ovep_name = vxlan_ident; ovrp->ovep_ops = &vxlan_o_ops; ovrp->ovep_id_size = VXLAN_ID_LEN; ovrp->ovep_flags = OVEP_F_VLAN_TAG; ovrp->ovep_dest = OVERLAY_PLUGIN_D_IP | OVERLAY_PLUGIN_D_PORT; ovrp->ovep_props = vxlan_props; if ((err = overlay_plugin_register(ovrp)) == 0) { if ((err = mod_install(&vxlan_modlinkage)) != 0) { (void) overlay_plugin_unregister(vxlan_ident); } } overlay_plugin_free(ovrp); return (err); } int _info(struct modinfo *modinfop) { return (mod_info(&vxlan_modlinkage, modinfop)); } int _fini(void) { int err; if ((err = overlay_plugin_unregister(vxlan_ident)) != 0) return (err); return (mod_remove(&vxlan_modlinkage)); }