diff options
Diffstat (limited to 'usr/src/uts/common/io/mac/mac_protect.c')
-rw-r--r-- | usr/src/uts/common/io/mac/mac_protect.c | 2049 |
1 files changed, 1986 insertions, 63 deletions
diff --git a/usr/src/uts/common/io/mac/mac_protect.c b/usr/src/uts/common/io/mac/mac_protect.c index 8bd527c8d5..c923bcdbe2 100644 --- a/usr/src/uts/common/io/mac/mac_protect.c +++ b/usr/src/uts/common/io/mac/mac_protect.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -33,68 +33,1668 @@ #include <sys/ethernet.h> #include <sys/vlan.h> #include <sys/dlpi.h> +#include <sys/avl.h> #include <inet/ip.h> #include <inet/ip6.h> #include <inet/arp.h> +#include <netinet/arp.h> +#include <netinet/udp.h> +#include <netinet/dhcp.h> +#include <netinet/dhcp6.h> /* - * Check if ipaddr is in the 'allowed-ips' list. + * Implementation overview for DHCP address detection + * + * The purpose of DHCP address detection is to relieve the user of having to + * manually configure static IP addresses when ip-nospoof protection is turned + * on. To achieve this, the mac layer needs to intercept DHCP packets to + * determine the assigned IP addresses. + * + * A DHCP handshake between client and server typically requires at least + * 4 messages: + * + * 1. DISCOVER - client attempts to locate DHCP servers via a + * broadcast message to its subnet. + * 2. OFFER - server responds to client with an IP address and + * other parameters. + * 3. REQUEST - client requests the offered address. + * 4. ACK - server verifies that the requested address matches + * the one it offered. + * + * DHCPv6 behaves pretty much the same way aside from different message names. + * + * Address information is embedded in either the OFFER or REQUEST message. + * We chose to intercept REQUEST because this is at the last part of the + * handshake and it indicates that the client intends to keep the address. + * Intercepting OFFERs is unreliable because the client may receive multiple + * offers from different servers, and we can't tell which address the client + * will keep. + * + * Each DHCP message has a transaction ID. We use this transaction ID to match + * REQUESTs with ACKs received from servers. + * + * For IPv4, the process to acquire a DHCP-assigned address is as follows: + * + * 1. Client sends REQUEST. a new dhcpv4_txn_t object is created and inserted + * in the the mci_v4_pending_txn table (keyed by xid). This object represents + * a new transaction. It contains the xid, the client ID and requested IP + * address. + * + * 2. Server responds with an ACK. The xid from this ACK is used to lookup the + * pending transaction from the mci_v4_pending_txn table. Once the object is + * found, it is removed from the pending table and inserted into the + * completed table (mci_v4_completed_txn, keyed by client ID) and the dynamic + * IP table (mci_v4_dyn_ip, keyed by IP address). + * + * 3. An outgoing packet that goes through the ip-nospoof path will be checked + * against the dynamic IP table. Packets that have the assigned DHCP address + * as the source IP address will pass the check and be admitted onto the + * network. + * + * IPv4 notes: + * + * If the server never responds with an ACK, there is a timer that is set after + * the insertion of the transaction into the pending table. When the timer + * fires, it will check whether the transaction is old (by comparing current + * time and the txn's timestamp), if so the transaction will be freed. along + * with this, any transaction in the completed/dyn-ip tables matching the client + * ID of this stale transaction will also be freed. If the client fails to + * extend a lease, we want to stop the client from using any IP addresses that + * were granted previously. + * + * A RELEASE message from the client will not cause a transaction to be created. + * The client ID in the RELEASE message will be used for finding and removing + * transactions in the completed and dyn-ip tables. + * + * + * For IPv6, the process to acquire a DHCPv6-assigned address is as follows: + * + * 1. Client sends REQUEST. The DUID is extracted and stored into a dhcpv6_cid_t + * structure. A new transaction structure (dhcpv6_txn_t) is also created and + * it will point to the dhcpv6_cid_t. If an existing transaction with a + * matching xid is not found, this dhcpv6_txn_t will be inserted into the + * mci_v6_pending_txn table (keyed by xid). + * + * 2. Server responds with a REPLY. If a pending transaction is found, the + * addresses in the reply will be placed into the dhcpv6_cid_t pointed to by + * the transaction. The dhcpv6_cid_t will then be moved to the mci_v6_cid + * table (keyed by cid). The associated addresses will be added to the + * mci_v6_dyn_ip table (while still being pointed to by the dhcpv6_cid_t). + * + * 3. IPv6 ip-nospoof will now check mci_v6_dyn_ip for matching packets. + * Packets with a source address matching one of the DHCPv6-assigned + * addresses will be allowed through. + * + * IPv6 notes: + * + * The v6 code shares the same timer as v4 for scrubbing stale transactions. + * Just like v4, as part of removing an expired transaction, a RELEASE will be + * be triggered on the cid associated with the expired transaction. + * + * The data structures used for v6 are slightly different because a v6 client + * may have multiple addresses associated with it. + */ + +/* + * These are just arbitrary limits meant for preventing abuse (e.g. a user + * flooding the network with bogus transactions). They are not meant to be + * user-modifiable so they are not exposed as linkprops. + */ +static ulong_t dhcp_max_pending_txn = 512; +static ulong_t dhcp_max_completed_txn = 512; +static time_t txn_cleanup_interval = 60; + +/* + * DHCPv4 transaction. It may be added to three different tables + * (keyed by different fields). + */ +typedef struct dhcpv4_txn { + uint32_t dt_xid; + time_t dt_timestamp; + uint8_t dt_cid[DHCP_MAX_OPT_SIZE]; + uint8_t dt_cid_len; + ipaddr_t dt_ipaddr; + avl_node_t dt_node; + avl_node_t dt_ipnode; + struct dhcpv4_txn *dt_next; +} dhcpv4_txn_t; + +/* + * DHCPv6 address. May be added to mci_v6_dyn_ip. + * It is always pointed to by its parent dhcpv6_cid_t structure. + */ +typedef struct dhcpv6_addr { + in6_addr_t da_addr; + avl_node_t da_node; + struct dhcpv6_addr *da_next; +} dhcpv6_addr_t; + +/* + * DHCPv6 client ID. May be added to mci_v6_cid. + * No dhcpv6_txn_t should be pointing to it after it is added to mci_v6_cid. + */ +typedef struct dhcpv6_cid { + uchar_t *dc_cid; + uint_t dc_cid_len; + dhcpv6_addr_t *dc_addr; + uint_t dc_addrcnt; + avl_node_t dc_node; +} dhcpv6_cid_t; + +/* + * DHCPv6 transaction. Unlike its v4 counterpart, this object gets freed up + * as soon as the transaction completes or expires. + */ +typedef struct dhcpv6_txn { + uint32_t dt_xid; + time_t dt_timestamp; + dhcpv6_cid_t *dt_cid; + avl_node_t dt_node; + struct dhcpv6_txn *dt_next; +} dhcpv6_txn_t; + +static void start_txn_cleanup_timer(mac_client_impl_t *); + +#define BUMP_STAT(m, s) (m)->mci_misc_stat.mms_##s++ + +/* + * Comparison functions for the 3 AVL trees used: + * mci_v4_pending_txn, mci_v4_completed_txn, mci_v4_dyn_ip + */ +static int +compare_dhcpv4_xid(const void *arg1, const void *arg2) +{ + const dhcpv4_txn_t *txn1 = arg1, *txn2 = arg2; + + if (txn1->dt_xid < txn2->dt_xid) + return (-1); + else if (txn1->dt_xid > txn2->dt_xid) + return (1); + else + return (0); +} + +static int +compare_dhcpv4_cid(const void *arg1, const void *arg2) +{ + const dhcpv4_txn_t *txn1 = arg1, *txn2 = arg2; + int ret; + + if (txn1->dt_cid_len < txn2->dt_cid_len) + return (-1); + else if (txn1->dt_cid_len > txn2->dt_cid_len) + return (1); + + if (txn1->dt_cid_len == 0) + return (0); + + ret = memcmp(txn1->dt_cid, txn2->dt_cid, txn1->dt_cid_len); + if (ret < 0) + return (-1); + else if (ret > 0) + return (1); + else + return (0); +} + +static int +compare_dhcpv4_ip(const void *arg1, const void *arg2) +{ + const dhcpv4_txn_t *txn1 = arg1, *txn2 = arg2; + + if (txn1->dt_ipaddr < txn2->dt_ipaddr) + return (-1); + else if (txn1->dt_ipaddr > txn2->dt_ipaddr) + return (1); + else + return (0); +} + +/* + * Find the specified DHCPv4 option. + */ +static int +get_dhcpv4_option(struct dhcp *dh4, uchar_t *end, uint8_t type, + uchar_t **opt, uint8_t *opt_len) +{ + uchar_t *start = (uchar_t *)dh4->options; + uint8_t otype, olen; + + while (start < end) { + if (*start == CD_PAD) { + start++; + continue; + } + if (*start == CD_END) + break; + + otype = *start++; + olen = *start++; + if (otype == type && olen > 0) { + *opt = start; + *opt_len = olen; + return (0); + } + start += olen; + } + return (ENOENT); +} + +/* + * Locate the start of a DHCPv4 header. + * The possible return values and associated meanings are: + * 0 - packet is DHCP and has a DHCP header. + * EINVAL - packet is not DHCP. the recommended action is to let it pass. + * ENOSPC - packet is a initial fragment that is DHCP or is unidentifiable. + * the recommended action is to drop it. + */ +static int +get_dhcpv4_info(ipha_t *ipha, uchar_t *end, struct dhcp **dh4) +{ + uint16_t offset_and_flags, client, server; + boolean_t first_frag = B_FALSE; + struct udphdr *udph; + uchar_t *dh; + + if (ipha->ipha_protocol != IPPROTO_UDP) + return (EINVAL); + + offset_and_flags = ntohs(ipha->ipha_fragment_offset_and_flags); + if ((offset_and_flags & (IPH_MF | IPH_OFFSET)) != 0) { + /* + * All non-initial fragments may pass because we cannot + * identify their type. It's safe to let them through + * because reassembly will fail if we decide to drop the + * initial fragment. + */ + if (((offset_and_flags << 3) & 0xffff) != 0) + return (EINVAL); + first_frag = B_TRUE; + } + /* drop packets without a udp header */ + udph = (struct udphdr *)((uchar_t *)ipha + IPH_HDR_LENGTH(ipha)); + if ((uchar_t *)&udph[1] > end) + return (ENOSPC); + + client = htons(IPPORT_BOOTPC); + server = htons(IPPORT_BOOTPS); + if (udph->uh_sport != client && udph->uh_sport != server && + udph->uh_dport != client && udph->uh_dport != server) + return (EINVAL); + + /* drop dhcp fragments */ + if (first_frag) + return (ENOSPC); + + dh = (uchar_t *)&udph[1]; + if (dh + BASE_PKT_SIZE > end) + return (EINVAL); + + *dh4 = (struct dhcp *)dh; + return (0); +} + +/* + * Wrappers for accesses to avl trees to improve readability. + * Their purposes are fairly self-explanatory. + */ +static dhcpv4_txn_t * +find_dhcpv4_pending_txn(mac_client_impl_t *mcip, uint32_t xid) +{ + dhcpv4_txn_t tmp_txn; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + tmp_txn.dt_xid = xid; + return (avl_find(&mcip->mci_v4_pending_txn, &tmp_txn, NULL)); +} + +static int +insert_dhcpv4_pending_txn(mac_client_impl_t *mcip, dhcpv4_txn_t *txn) +{ + avl_index_t where; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + if (avl_find(&mcip->mci_v4_pending_txn, txn, &where) != NULL) + return (EEXIST); + + if (avl_numnodes(&mcip->mci_v4_pending_txn) >= dhcp_max_pending_txn) { + BUMP_STAT(mcip, dhcpdropped); + return (EAGAIN); + } + avl_insert(&mcip->mci_v4_pending_txn, txn, where); + return (0); +} + +static void +remove_dhcpv4_pending_txn(mac_client_impl_t *mcip, dhcpv4_txn_t *txn) +{ + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + avl_remove(&mcip->mci_v4_pending_txn, txn); +} + +static dhcpv4_txn_t * +find_dhcpv4_completed_txn(mac_client_impl_t *mcip, uint8_t *cid, + uint8_t cid_len) +{ + dhcpv4_txn_t tmp_txn; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + if (cid_len > 0) + bcopy(cid, tmp_txn.dt_cid, cid_len); + tmp_txn.dt_cid_len = cid_len; + return (avl_find(&mcip->mci_v4_completed_txn, &tmp_txn, NULL)); +} + +/* + * After a pending txn is removed from the pending table, it is inserted + * into both the completed and dyn-ip tables. These two insertions are + * done together because a client ID must have 1:1 correspondence with + * an IP address and IP addresses must be unique in the dyn-ip table. + */ +static int +insert_dhcpv4_completed_txn(mac_client_impl_t *mcip, dhcpv4_txn_t *txn) +{ + avl_index_t where; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + if (avl_find(&mcip->mci_v4_completed_txn, txn, &where) != NULL) + return (EEXIST); + + if (avl_numnodes(&mcip->mci_v4_completed_txn) >= + dhcp_max_completed_txn) { + BUMP_STAT(mcip, dhcpdropped); + return (EAGAIN); + } + + avl_insert(&mcip->mci_v4_completed_txn, txn, where); + if (avl_find(&mcip->mci_v4_dyn_ip, txn, &where) != NULL) { + avl_remove(&mcip->mci_v4_completed_txn, txn); + return (EEXIST); + } + avl_insert(&mcip->mci_v4_dyn_ip, txn, where); + return (0); +} + +static void +remove_dhcpv4_completed_txn(mac_client_impl_t *mcip, dhcpv4_txn_t *txn) +{ + dhcpv4_txn_t *ctxn; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + if ((ctxn = avl_find(&mcip->mci_v4_dyn_ip, txn, NULL)) != NULL && + ctxn == txn) + avl_remove(&mcip->mci_v4_dyn_ip, txn); + + avl_remove(&mcip->mci_v4_completed_txn, txn); +} + +/* + * Check whether an IP address is in the dyn-ip table. */ static boolean_t -ipnospoof_check_ips(mac_protect_t *protect, ipaddr_t ipaddr) +check_dhcpv4_dyn_ip(mac_client_impl_t *mcip, ipaddr_t ipaddr) +{ + dhcpv4_txn_t tmp_txn, *txn; + + mutex_enter(&mcip->mci_protect_lock); + tmp_txn.dt_ipaddr = ipaddr; + txn = avl_find(&mcip->mci_v4_dyn_ip, &tmp_txn, NULL); + mutex_exit(&mcip->mci_protect_lock); + return (txn != NULL); +} + +/* + * Create/destroy a DHCPv4 transaction. + */ +static dhcpv4_txn_t * +create_dhcpv4_txn(uint32_t xid, uint8_t *cid, uint8_t cid_len, ipaddr_t ipaddr) +{ + dhcpv4_txn_t *txn; + + if ((txn = kmem_zalloc(sizeof (*txn), KM_NOSLEEP)) == NULL) + return (NULL); + + txn->dt_xid = xid; + txn->dt_timestamp = ddi_get_time(); + if (cid_len > 0) + bcopy(cid, &txn->dt_cid, cid_len); + txn->dt_cid_len = cid_len; + txn->dt_ipaddr = ipaddr; + return (txn); +} + +static void +free_dhcpv4_txn(dhcpv4_txn_t *txn) +{ + kmem_free(txn, sizeof (*txn)); +} + +/* + * Clean up all v4 tables. + */ +static void +flush_dhcpv4(mac_client_impl_t *mcip) +{ + void *cookie = NULL; + dhcpv4_txn_t *txn; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + while ((txn = avl_destroy_nodes(&mcip->mci_v4_dyn_ip, + &cookie)) != NULL) { + /* + * No freeing needed here because the same txn exists + * in the mci_v4_completed_txn table as well. + */ + } + cookie = NULL; + while ((txn = avl_destroy_nodes(&mcip->mci_v4_completed_txn, + &cookie)) != NULL) { + free_dhcpv4_txn(txn); + } + cookie = NULL; + while ((txn = avl_destroy_nodes(&mcip->mci_v4_pending_txn, + &cookie)) != NULL) { + free_dhcpv4_txn(txn); + } +} + +/* + * Cleanup stale DHCPv4 transactions. + */ +static void +txn_cleanup_v4(mac_client_impl_t *mcip) { - uint_t i; + dhcpv4_txn_t *txn, *ctxn, *next, *txn_list = NULL; /* - * unspecified addresses are harmless and are used by ARP,DHCP..etc. + * Find stale pending transactions and place them on a list + * to be removed. */ - if (ipaddr == INADDR_ANY) - return (B_TRUE); + for (txn = avl_first(&mcip->mci_v4_pending_txn); txn != NULL; + txn = avl_walk(&mcip->mci_v4_pending_txn, txn, AVL_AFTER)) { + if (ddi_get_time() - txn->dt_timestamp > + txn_cleanup_interval) { + DTRACE_PROBE2(found__expired__txn, + mac_client_impl_t *, mcip, + dhcpv4_txn_t *, txn); - for (i = 0; i < protect->mp_ipaddrcnt; i++) { - if (protect->mp_ipaddrs[i] == ipaddr) - return (B_TRUE); + txn->dt_next = txn_list; + txn_list = txn; + } } - return (B_FALSE); + + /* + * Remove and free stale pending transactions and completed + * transactions with the same client IDs as the stale transactions. + */ + for (txn = txn_list; txn != NULL; txn = next) { + avl_remove(&mcip->mci_v4_pending_txn, txn); + + ctxn = find_dhcpv4_completed_txn(mcip, txn->dt_cid, + txn->dt_cid_len); + if (ctxn != NULL) { + DTRACE_PROBE2(removing__completed__txn, + mac_client_impl_t *, mcip, + dhcpv4_txn_t *, ctxn); + + remove_dhcpv4_completed_txn(mcip, ctxn); + free_dhcpv4_txn(ctxn); + } + next = txn->dt_next; + txn->dt_next = NULL; + + DTRACE_PROBE2(freeing__txn, mac_client_impl_t *, mcip, + dhcpv4_txn_t *, txn); + free_dhcpv4_txn(txn); + } +} + +/* + * Core logic for intercepting outbound DHCPv4 packets. + */ +static void +intercept_dhcpv4_outbound(mac_client_impl_t *mcip, ipha_t *ipha, uchar_t *end) +{ + struct dhcp *dh4; + uchar_t *opt; + dhcpv4_txn_t *txn, *ctxn; + ipaddr_t ipaddr; + uint8_t opt_len, mtype, cid[DHCP_MAX_OPT_SIZE], cid_len; + + if (get_dhcpv4_info(ipha, end, &dh4) != 0) + return; + + if (get_dhcpv4_option(dh4, end, CD_DHCP_TYPE, &opt, &opt_len) != 0 || + opt_len != 1) { + DTRACE_PROBE2(mtype__not__found, mac_client_impl_t *, mcip, + struct dhcp *, dh4); + return; + } + mtype = *opt; + if (mtype != REQUEST && mtype != RELEASE) { + DTRACE_PROBE3(ignored__mtype, mac_client_impl_t *, mcip, + struct dhcp *, dh4, uint8_t, mtype); + return; + } + + /* client ID is optional for IPv4 */ + if (get_dhcpv4_option(dh4, end, CD_CLIENT_ID, &opt, &opt_len) == 0 && + opt_len >= 2) { + bcopy(opt, cid, opt_len); + cid_len = opt_len; + } else { + bzero(cid, DHCP_MAX_OPT_SIZE); + cid_len = 0; + } + + mutex_enter(&mcip->mci_protect_lock); + if (mtype == RELEASE) { + DTRACE_PROBE2(release, mac_client_impl_t *, mcip, + struct dhcp *, dh4); + + /* flush any completed txn with this cid */ + ctxn = find_dhcpv4_completed_txn(mcip, cid, cid_len); + if (ctxn != NULL) { + DTRACE_PROBE2(release__successful, mac_client_impl_t *, + mcip, struct dhcp *, dh4); + + remove_dhcpv4_completed_txn(mcip, ctxn); + free_dhcpv4_txn(ctxn); + } + goto done; + } + + /* + * If a pending txn already exists, we'll update its timestamp so + * it won't get flushed by the timer. We don't need to create new + * txns for retransmissions. + */ + if ((txn = find_dhcpv4_pending_txn(mcip, dh4->xid)) != NULL) { + DTRACE_PROBE2(update, mac_client_impl_t *, mcip, + dhcpv4_txn_t *, txn); + txn->dt_timestamp = ddi_get_time(); + goto done; + } + + if (get_dhcpv4_option(dh4, end, CD_REQUESTED_IP_ADDR, + &opt, &opt_len) != 0 || opt_len != sizeof (ipaddr)) { + DTRACE_PROBE2(ipaddr__not__found, mac_client_impl_t *, mcip, + struct dhcp *, dh4); + goto done; + } + bcopy(opt, &ipaddr, sizeof (ipaddr)); + if ((txn = create_dhcpv4_txn(dh4->xid, cid, cid_len, ipaddr)) == NULL) + goto done; + + if (insert_dhcpv4_pending_txn(mcip, txn) != 0) { + DTRACE_PROBE2(insert__failed, mac_client_impl_t *, mcip, + dhcpv4_txn_t *, txn); + free_dhcpv4_txn(txn); + goto done; + } + start_txn_cleanup_timer(mcip); + + DTRACE_PROBE2(txn__pending, mac_client_impl_t *, mcip, + dhcpv4_txn_t *, txn); + +done: + mutex_exit(&mcip->mci_protect_lock); } /* - * Enforce ip-nospoof protection. Only IPv4 is supported for now. + * Core logic for intercepting inbound DHCPv4 packets. + */ +static void +intercept_dhcpv4_inbound(mac_client_impl_t *mcip, ipha_t *ipha, uchar_t *end) +{ + uchar_t *opt; + struct dhcp *dh4; + dhcpv4_txn_t *txn, *ctxn; + uint8_t opt_len, mtype; + + if (get_dhcpv4_info(ipha, end, &dh4) != 0) + return; + + if (get_dhcpv4_option(dh4, end, CD_DHCP_TYPE, &opt, &opt_len) != 0 || + opt_len != 1) { + DTRACE_PROBE2(mtype__not__found, mac_client_impl_t *, mcip, + struct dhcp *, dh4); + return; + } + mtype = *opt; + if (mtype != ACK && mtype != NAK) { + DTRACE_PROBE3(ignored__mtype, mac_client_impl_t *, mcip, + struct dhcp *, dh4, uint8_t, mtype); + return; + } + + mutex_enter(&mcip->mci_protect_lock); + if ((txn = find_dhcpv4_pending_txn(mcip, dh4->xid)) == NULL) { + DTRACE_PROBE2(txn__not__found, mac_client_impl_t *, mcip, + struct dhcp *, dh4); + goto done; + } + remove_dhcpv4_pending_txn(mcip, txn); + + /* + * We're about to move a txn from the pending table to the completed/ + * dyn-ip tables. If there is an existing completed txn with the + * same cid as our txn, we need to remove and free it. + */ + ctxn = find_dhcpv4_completed_txn(mcip, txn->dt_cid, txn->dt_cid_len); + if (ctxn != NULL) { + DTRACE_PROBE2(replacing__old__txn, mac_client_impl_t *, mcip, + dhcpv4_txn_t *, ctxn); + remove_dhcpv4_completed_txn(mcip, ctxn); + free_dhcpv4_txn(ctxn); + } + if (mtype == NAK) { + DTRACE_PROBE2(nak__received, mac_client_impl_t *, mcip, + dhcpv4_txn_t *, txn); + free_dhcpv4_txn(txn); + goto done; + } + if (insert_dhcpv4_completed_txn(mcip, txn) != 0) { + DTRACE_PROBE2(insert__failed, mac_client_impl_t *, mcip, + dhcpv4_txn_t *, txn); + free_dhcpv4_txn(txn); + goto done; + } + DTRACE_PROBE2(txn__completed, mac_client_impl_t *, mcip, + dhcpv4_txn_t *, txn); + +done: + mutex_exit(&mcip->mci_protect_lock); +} + + +/* + * Comparison functions for the DHCPv6 AVL trees. */ static int -ipnospoof_check(mac_client_impl_t *mcip, mac_protect_t *protect, - mblk_t *mp, mac_header_info_t *mhip) +compare_dhcpv6_xid(const void *arg1, const void *arg2) { - uint32_t sap = mhip->mhi_bindsap; - uchar_t *start = mp->b_rptr + mhip->mhi_hdrsize; - int err = EINVAL; + const dhcpv6_txn_t *txn1 = arg1, *txn2 = arg2; + + if (txn1->dt_xid < txn2->dt_xid) + return (-1); + else if (txn1->dt_xid > txn2->dt_xid) + return (1); + else + return (0); +} + +static int +compare_dhcpv6_ip(const void *arg1, const void *arg2) +{ + const dhcpv6_addr_t *ip1 = arg1, *ip2 = arg2; + int ret; + + ret = memcmp(&ip1->da_addr, &ip2->da_addr, sizeof (in6_addr_t)); + if (ret < 0) + return (-1); + else if (ret > 0) + return (1); + else + return (0); +} + +static int +compare_dhcpv6_cid(const void *arg1, const void *arg2) +{ + const dhcpv6_cid_t *cid1 = arg1, *cid2 = arg2; + int ret; + + if (cid1->dc_cid_len < cid2->dc_cid_len) + return (-1); + else if (cid1->dc_cid_len > cid2->dc_cid_len) + return (1); + + if (cid1->dc_cid_len == 0) + return (0); + + ret = memcmp(cid1->dc_cid, cid2->dc_cid, cid1->dc_cid_len); + if (ret < 0) + return (-1); + else if (ret > 0) + return (1); + else + return (0); +} + +/* + * Locate the start of a DHCPv6 header. + * The possible return values and associated meanings are: + * 0 - packet is DHCP and has a DHCP header. + * EINVAL - packet is not DHCP. the recommended action is to let it pass. + * ENOSPC - packet is a initial fragment that is DHCP or is unidentifiable. + * the recommended action is to drop it. + */ +static int +get_dhcpv6_info(ip6_t *ip6h, uchar_t *end, dhcpv6_message_t **dh6) +{ + uint16_t hdrlen, client, server; + boolean_t first_frag = B_FALSE; + ip6_frag_t *frag = NULL; + uint8_t proto; + struct udphdr *udph; + uchar_t *dh; + + if (!mac_ip_hdr_length_v6(ip6h, end, &hdrlen, &proto, &frag)) + return (ENOSPC); + + if (proto != IPPROTO_UDP) + return (EINVAL); + + if (frag != NULL) { + /* + * All non-initial fragments may pass because we cannot + * identify their type. It's safe to let them through + * because reassembly will fail if we decide to drop the + * initial fragment. + */ + if ((ntohs(frag->ip6f_offlg) & ~7) != 0) + return (EINVAL); + first_frag = B_TRUE; + } + /* drop packets without a udp header */ + udph = (struct udphdr *)((uchar_t *)ip6h + hdrlen); + if ((uchar_t *)&udph[1] > end) + return (ENOSPC); + + client = htons(IPPORT_DHCPV6C); + server = htons(IPPORT_DHCPV6S); + if (udph->uh_sport != client && udph->uh_sport != server && + udph->uh_dport != client && udph->uh_dport != server) + return (EINVAL); + + /* drop dhcp fragments */ + if (first_frag) + return (ENOSPC); + + dh = (uchar_t *)&udph[1]; + if (dh + sizeof (dhcpv6_message_t) > end) + return (EINVAL); + + *dh6 = (dhcpv6_message_t *)dh; + return (0); +} + +/* + * Find the specified DHCPv6 option. + */ +static dhcpv6_option_t * +get_dhcpv6_option(void *buf, size_t buflen, dhcpv6_option_t *oldopt, + uint16_t codenum, uint_t *retlenp) +{ + uchar_t *bp; + dhcpv6_option_t d6o; + uint_t olen; + + codenum = htons(codenum); + bp = buf; + while (buflen >= sizeof (dhcpv6_option_t)) { + bcopy(bp, &d6o, sizeof (d6o)); + olen = ntohs(d6o.d6o_len) + sizeof (d6o); + if (olen > buflen) + break; + if (d6o.d6o_code != codenum || d6o.d6o_len == 0 || + (oldopt != NULL && bp <= (uchar_t *)oldopt)) { + bp += olen; + buflen -= olen; + continue; + } + if (retlenp != NULL) + *retlenp = olen; + /* LINTED : alignment */ + return ((dhcpv6_option_t *)bp); + } + return (NULL); +} + +/* + * Get the status code from a reply message. + */ +static int +get_dhcpv6_status(dhcpv6_message_t *dh6, uchar_t *end, uint16_t *status) +{ + dhcpv6_option_t *d6o; + uint_t olen; + uint16_t s; + + d6o = get_dhcpv6_option(&dh6[1], end - (uchar_t *)&dh6[1], NULL, + DHCPV6_OPT_STATUS_CODE, &olen); + + /* Success is implied if status code is missing */ + if (d6o == NULL) { + *status = DHCPV6_STAT_SUCCESS; + return (0); + } + if ((uchar_t *)d6o + olen > end) + return (EINVAL); + + olen -= sizeof (*d6o); + if (olen < sizeof (s)) + return (EINVAL); + + bcopy(&d6o[1], &s, sizeof (s)); + *status = ntohs(s); + return (0); +} + +/* + * Get the addresses from a reply message. + */ +static int +get_dhcpv6_addrs(dhcpv6_message_t *dh6, uchar_t *end, dhcpv6_cid_t *cid) +{ + dhcpv6_option_t *d6o; + dhcpv6_addr_t *next; + uint_t olen; + + d6o = NULL; + while ((d6o = get_dhcpv6_option(&dh6[1], end - (uchar_t *)&dh6[1], + d6o, DHCPV6_OPT_IA_NA, &olen)) != NULL) { + dhcpv6_option_t *d6so; + dhcpv6_iaaddr_t d6ia; + dhcpv6_addr_t **addrp; + uchar_t *obase; + uint_t solen; + + if (olen < sizeof (dhcpv6_ia_na_t) || + (uchar_t *)d6o + olen > end) + goto fail; + + obase = (uchar_t *)d6o + sizeof (dhcpv6_ia_na_t); + olen -= sizeof (dhcpv6_ia_na_t); + d6so = NULL; + while ((d6so = get_dhcpv6_option(obase, olen, d6so, + DHCPV6_OPT_IAADDR, &solen)) != NULL) { + if (solen < sizeof (dhcpv6_iaaddr_t) || + (uchar_t *)d6so + solen > end) + goto fail; + + bcopy(d6so, &d6ia, sizeof (d6ia)); + for (addrp = &cid->dc_addr; *addrp != NULL; + addrp = &(*addrp)->da_next) { + if (bcmp(&(*addrp)->da_addr, &d6ia.d6ia_addr, + sizeof (in6_addr_t)) == 0) + goto fail; + } + if ((*addrp = kmem_zalloc(sizeof (dhcpv6_addr_t), + KM_NOSLEEP)) == NULL) + goto fail; + + bcopy(&d6ia.d6ia_addr, &(*addrp)->da_addr, + sizeof (in6_addr_t)); + cid->dc_addrcnt++; + } + } + if (cid->dc_addrcnt == 0) + return (ENOENT); + + return (0); + +fail: + for (; cid->dc_addr != NULL; cid->dc_addr = next) { + next = cid->dc_addr->da_next; + kmem_free(cid->dc_addr, sizeof (dhcpv6_addr_t)); + cid->dc_addrcnt--; + } + ASSERT(cid->dc_addrcnt == 0); + return (EINVAL); +} + +/* + * Free a cid. + * Before this gets called the caller must ensure that all the + * addresses are removed from the mci_v6_dyn_ip table. + */ +static void +free_dhcpv6_cid(dhcpv6_cid_t *cid) +{ + dhcpv6_addr_t *addr, *next; + uint_t cnt = 0; + + kmem_free(cid->dc_cid, cid->dc_cid_len); + for (addr = cid->dc_addr; addr != NULL; addr = next) { + next = addr->da_next; + kmem_free(addr, sizeof (*addr)); + cnt++; + } + ASSERT(cnt == cid->dc_addrcnt); + kmem_free(cid, sizeof (*cid)); +} + +/* + * Extract the DUID from a message. The associated addresses will be + * extracted later from the reply message. + */ +static dhcpv6_cid_t * +create_dhcpv6_cid(dhcpv6_message_t *dh6, uchar_t *end) +{ + dhcpv6_option_t *d6o; + dhcpv6_cid_t *cid; + uchar_t *rawcid; + uint_t olen, rawcidlen; + + d6o = get_dhcpv6_option(&dh6[1], end - (uchar_t *)&dh6[1], NULL, + DHCPV6_OPT_CLIENTID, &olen); + if (d6o == NULL || (uchar_t *)d6o + olen > end) + return (NULL); + + rawcidlen = olen - sizeof (*d6o); + if ((rawcid = kmem_zalloc(rawcidlen, KM_NOSLEEP)) == NULL) + return (NULL); + bcopy(d6o + 1, rawcid, rawcidlen); + + if ((cid = kmem_zalloc(sizeof (*cid), KM_NOSLEEP)) == NULL) { + kmem_free(rawcid, rawcidlen); + return (NULL); + } + cid->dc_cid = rawcid; + cid->dc_cid_len = rawcidlen; + return (cid); +} + +/* + * Remove a cid from mci_v6_cid. The addresses owned by the cid + * are also removed from mci_v6_dyn_ip. + */ +static void +remove_dhcpv6_cid(mac_client_impl_t *mcip, dhcpv6_cid_t *cid) +{ + dhcpv6_addr_t *addr, *tmp_addr; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + avl_remove(&mcip->mci_v6_cid, cid); + for (addr = cid->dc_addr; addr != NULL; addr = addr->da_next) { + tmp_addr = avl_find(&mcip->mci_v6_dyn_ip, addr, NULL); + if (tmp_addr == addr) + avl_remove(&mcip->mci_v6_dyn_ip, addr); + } +} + +/* + * Find and remove a matching cid and associated addresses from + * their respective tables. + */ +static void +release_dhcpv6_cid(mac_client_impl_t *mcip, dhcpv6_cid_t *cid) +{ + dhcpv6_cid_t *oldcid; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + if ((oldcid = avl_find(&mcip->mci_v6_cid, cid, NULL)) == NULL) + return; + + /* + * Since cid belongs to a pending txn, it can't possibly be in + * mci_v6_cid. Anything that's found must be an existing cid. + */ + ASSERT(oldcid != cid); + remove_dhcpv6_cid(mcip, oldcid); + free_dhcpv6_cid(oldcid); +} + +/* + * Insert cid into mci_v6_cid. + */ +static int +insert_dhcpv6_cid(mac_client_impl_t *mcip, dhcpv6_cid_t *cid) +{ + avl_index_t where; + dhcpv6_addr_t *addr; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + if (avl_find(&mcip->mci_v6_cid, cid, &where) != NULL) + return (EEXIST); + + if (avl_numnodes(&mcip->mci_v6_cid) >= dhcp_max_completed_txn) { + BUMP_STAT(mcip, dhcpdropped); + return (EAGAIN); + } + avl_insert(&mcip->mci_v6_cid, cid, where); + for (addr = cid->dc_addr; addr != NULL; addr = addr->da_next) { + if (avl_find(&mcip->mci_v6_dyn_ip, addr, &where) != NULL) + goto fail; + + avl_insert(&mcip->mci_v6_dyn_ip, addr, where); + } + return (0); + +fail: + remove_dhcpv6_cid(mcip, cid); + return (EEXIST); +} + +/* + * Check whether an IP address is in the dyn-ip table. + */ +static boolean_t +check_dhcpv6_dyn_ip(mac_client_impl_t *mcip, in6_addr_t *addr) +{ + dhcpv6_addr_t tmp_addr, *a; + + mutex_enter(&mcip->mci_protect_lock); + bcopy(addr, &tmp_addr.da_addr, sizeof (in6_addr_t)); + a = avl_find(&mcip->mci_v6_dyn_ip, &tmp_addr, NULL); + mutex_exit(&mcip->mci_protect_lock); + return (a != NULL); +} + +static dhcpv6_txn_t * +find_dhcpv6_pending_txn(mac_client_impl_t *mcip, uint32_t xid) +{ + dhcpv6_txn_t tmp_txn; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + tmp_txn.dt_xid = xid; + return (avl_find(&mcip->mci_v6_pending_txn, &tmp_txn, NULL)); +} + +static void +remove_dhcpv6_pending_txn(mac_client_impl_t *mcip, dhcpv6_txn_t *txn) +{ + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + avl_remove(&mcip->mci_v6_pending_txn, txn); +} + +static dhcpv6_txn_t * +create_dhcpv6_txn(uint32_t xid, dhcpv6_cid_t *cid) +{ + dhcpv6_txn_t *txn; + + if ((txn = kmem_zalloc(sizeof (dhcpv6_txn_t), KM_NOSLEEP)) == NULL) + return (NULL); + + txn->dt_xid = xid; + txn->dt_cid = cid; + txn->dt_timestamp = ddi_get_time(); + return (txn); +} + +static void +free_dhcpv6_txn(dhcpv6_txn_t *txn) +{ + if (txn->dt_cid != NULL) + free_dhcpv6_cid(txn->dt_cid); + kmem_free(txn, sizeof (dhcpv6_txn_t)); +} + +static int +insert_dhcpv6_pending_txn(mac_client_impl_t *mcip, dhcpv6_txn_t *txn) +{ + avl_index_t where; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + if (avl_find(&mcip->mci_v6_pending_txn, txn, &where) != NULL) + return (EEXIST); + + if (avl_numnodes(&mcip->mci_v6_pending_txn) >= dhcp_max_pending_txn) { + BUMP_STAT(mcip, dhcpdropped); + return (EAGAIN); + } + avl_insert(&mcip->mci_v6_pending_txn, txn, where); + return (0); +} + +/* + * Clean up all v6 tables. + */ +static void +flush_dhcpv6(mac_client_impl_t *mcip) +{ + void *cookie = NULL; + dhcpv6_cid_t *cid; + dhcpv6_txn_t *txn; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + while (avl_destroy_nodes(&mcip->mci_v6_dyn_ip, &cookie) != NULL) { + } + cookie = NULL; + while ((cid = avl_destroy_nodes(&mcip->mci_v6_cid, &cookie)) != NULL) { + free_dhcpv6_cid(cid); + } + cookie = NULL; + while ((txn = avl_destroy_nodes(&mcip->mci_v6_pending_txn, + &cookie)) != NULL) { + free_dhcpv6_txn(txn); + } +} + +/* + * Cleanup stale DHCPv6 transactions. + */ +static void +txn_cleanup_v6(mac_client_impl_t *mcip) +{ + dhcpv6_txn_t *txn, *next, *txn_list = NULL; /* - * This handles the case where the mac header is not in - * the same mblk as the IP header. + * Find stale pending transactions and place them on a list + * to be removed. */ - if (start == mp->b_wptr) { - mp = mp->b_cont; + for (txn = avl_first(&mcip->mci_v6_pending_txn); txn != NULL; + txn = avl_walk(&mcip->mci_v6_pending_txn, txn, AVL_AFTER)) { + if (ddi_get_time() - txn->dt_timestamp > + txn_cleanup_interval) { + DTRACE_PROBE2(found__expired__txn, + mac_client_impl_t *, mcip, + dhcpv6_txn_t *, txn); + txn->dt_next = txn_list; + txn_list = txn; + } + } + + /* + * Remove and free stale pending transactions. + * Release any existing cids matching the stale transactions. + */ + for (txn = txn_list; txn != NULL; txn = next) { + avl_remove(&mcip->mci_v6_pending_txn, txn); + release_dhcpv6_cid(mcip, txn->dt_cid); + next = txn->dt_next; + txn->dt_next = NULL; + + DTRACE_PROBE2(freeing__txn, mac_client_impl_t *, mcip, + dhcpv6_txn_t *, txn); + free_dhcpv6_txn(txn); + } + +} + +/* + * Core logic for intercepting outbound DHCPv6 packets. + */ +static void +intercept_dhcpv6_outbound(mac_client_impl_t *mcip, ip6_t *ip6h, uchar_t *end) +{ + dhcpv6_message_t *dh6; + dhcpv6_txn_t *txn; + dhcpv6_cid_t *cid = NULL; + uint32_t xid; + uint8_t mtype; + + if (get_dhcpv6_info(ip6h, end, &dh6) != 0) + return; + + mtype = dh6->d6m_msg_type; + if (mtype != DHCPV6_MSG_REQUEST && mtype != DHCPV6_MSG_RENEW && + mtype != DHCPV6_MSG_REBIND && mtype != DHCPV6_MSG_RELEASE) + return; + + if ((cid = create_dhcpv6_cid(dh6, end)) == NULL) + return; + + mutex_enter(&mcip->mci_protect_lock); + if (mtype == DHCPV6_MSG_RELEASE) { + release_dhcpv6_cid(mcip, cid); + goto done; + } + xid = DHCPV6_GET_TRANSID(dh6); + if ((txn = find_dhcpv6_pending_txn(mcip, xid)) != NULL) { + DTRACE_PROBE2(update, mac_client_impl_t *, mcip, + dhcpv6_txn_t *, txn); + txn->dt_timestamp = ddi_get_time(); + goto done; + } + if ((txn = create_dhcpv6_txn(xid, cid)) == NULL) + goto done; + + cid = NULL; + if (insert_dhcpv6_pending_txn(mcip, txn) != 0) { + DTRACE_PROBE2(insert__failed, mac_client_impl_t *, mcip, + dhcpv6_txn_t *, txn); + free_dhcpv6_txn(txn); + goto done; + } + start_txn_cleanup_timer(mcip); + + DTRACE_PROBE2(txn__pending, mac_client_impl_t *, mcip, + dhcpv6_txn_t *, txn); + +done: + if (cid != NULL) + free_dhcpv6_cid(cid); + + mutex_exit(&mcip->mci_protect_lock); +} + +/* + * Core logic for intercepting inbound DHCPv6 packets. + */ +static void +intercept_dhcpv6_inbound(mac_client_impl_t *mcip, ip6_t *ip6h, uchar_t *end) +{ + dhcpv6_message_t *dh6; + dhcpv6_txn_t *txn; + uint32_t xid; + uint8_t mtype; + uint16_t status; + + if (get_dhcpv6_info(ip6h, end, &dh6) != 0) + return; + + mtype = dh6->d6m_msg_type; + if (mtype != DHCPV6_MSG_REPLY) + return; + + mutex_enter(&mcip->mci_protect_lock); + xid = DHCPV6_GET_TRANSID(dh6); + if ((txn = find_dhcpv6_pending_txn(mcip, xid)) == NULL) { + DTRACE_PROBE2(txn__not__found, mac_client_impl_t *, mcip, + dhcpv6_message_t *, dh6); + goto done; + } + remove_dhcpv6_pending_txn(mcip, txn); + release_dhcpv6_cid(mcip, txn->dt_cid); + + if (get_dhcpv6_status(dh6, end, &status) != 0 || + status != DHCPV6_STAT_SUCCESS) { + DTRACE_PROBE2(error__status, mac_client_impl_t *, mcip, + dhcpv6_txn_t *, txn); + goto done; + } + if (get_dhcpv6_addrs(dh6, end, txn->dt_cid) != 0) { + DTRACE_PROBE2(no__addrs, mac_client_impl_t *, mcip, + dhcpv6_txn_t *, txn); + goto done; + } + if (insert_dhcpv6_cid(mcip, txn->dt_cid) != 0) { + DTRACE_PROBE2(insert__failed, mac_client_impl_t *, mcip, + dhcpv6_txn_t *, txn); + goto done; + } + DTRACE_PROBE2(txn__completed, mac_client_impl_t *, mcip, + dhcpv6_txn_t *, txn); + + txn->dt_cid = NULL; + +done: + if (txn != NULL) + free_dhcpv6_txn(txn); + mutex_exit(&mcip->mci_protect_lock); +} + +/* + * Timer for cleaning up stale transactions. + */ +static void +txn_cleanup_timer(void *arg) +{ + mac_client_impl_t *mcip = arg; + + mutex_enter(&mcip->mci_protect_lock); + if (mcip->mci_txn_cleanup_tid == 0) { + /* do nothing if timer got cancelled */ + mutex_exit(&mcip->mci_protect_lock); + return; + } + mcip->mci_txn_cleanup_tid = 0; + + txn_cleanup_v4(mcip); + txn_cleanup_v6(mcip); + + /* + * Restart timer if pending transactions still exist. + */ + if (!avl_is_empty(&mcip->mci_v4_pending_txn) || + !avl_is_empty(&mcip->mci_v6_pending_txn)) { + DTRACE_PROBE1(restarting__timer, mac_client_impl_t *, mcip); + + mcip->mci_txn_cleanup_tid = timeout(txn_cleanup_timer, mcip, + drv_usectohz(txn_cleanup_interval * 1000000)); + } + mutex_exit(&mcip->mci_protect_lock); +} + +static void +start_txn_cleanup_timer(mac_client_impl_t *mcip) +{ + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + if (mcip->mci_txn_cleanup_tid == 0) { + mcip->mci_txn_cleanup_tid = timeout(txn_cleanup_timer, mcip, + drv_usectohz(txn_cleanup_interval * 1000000)); + } +} + +static void +cancel_txn_cleanup_timer(mac_client_impl_t *mcip) +{ + timeout_id_t tid; + + ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); + + /* + * This needs to be a while loop because the timer could get + * rearmed during untimeout(). + */ + while ((tid = mcip->mci_txn_cleanup_tid) != 0) { + mcip->mci_txn_cleanup_tid = 0; + mutex_exit(&mcip->mci_protect_lock); + (void) untimeout(tid); + mutex_enter(&mcip->mci_protect_lock); + } +} + +/* + * Get the start/end pointers of an L3 packet and also do pullup if needed. + * pulled-up packet needs to be freed by the caller. + */ +static int +get_l3_info(mblk_t *mp, size_t hdrsize, uchar_t **start, uchar_t **end, + mblk_t **nmp) +{ + uchar_t *s, *e; + mblk_t *newmp = NULL; + + /* + * Pullup if necessary but reject packets that do not have + * a proper mac header. + */ + s = mp->b_rptr + hdrsize; + e = mp->b_wptr; + + if (s > mp->b_wptr) + return (EINVAL); + + if (!OK_32PTR(s) || mp->b_cont != NULL) { /* - * IP header missing. Let the packet through. + * Temporarily adjust mp->b_rptr to ensure proper + * alignment of IP header in newmp. */ - if (mp == NULL) - return (0); + DTRACE_PROBE1(pullup__needed, mblk_t *, mp); + + mp->b_rptr += hdrsize; + newmp = msgpullup(mp, -1); + mp->b_rptr -= hdrsize; + + if (newmp == NULL) + return (ENOMEM); + + s = newmp->b_rptr; + e = newmp->b_wptr; + } + + *start = s; + *end = e; + *nmp = newmp; + return (0); +} + +void +mac_protect_intercept_dhcp_one(mac_client_impl_t *mcip, mblk_t *mp) +{ + mac_impl_t *mip = mcip->mci_mip; + uchar_t *start, *end; + mblk_t *nmp = NULL; + mac_header_info_t mhi; + int err; + + err = mac_vlan_header_info((mac_handle_t)mip, mp, &mhi); + if (err != 0) { + DTRACE_PROBE2(invalid__header, mac_client_impl_t *, mcip, + mblk_t *, mp); + return; + } + + err = get_l3_info(mp, mhi.mhi_hdrsize, &start, &end, &nmp); + if (err != 0) { + DTRACE_PROBE2(invalid__l3, mac_client_impl_t *, mcip, + mblk_t *, mp); + return; + } + + switch (mhi.mhi_bindsap) { + case ETHERTYPE_IP: { + ipha_t *ipha = (ipha_t *)start; + + if (start + sizeof (ipha_t) > end) + return; + + intercept_dhcpv4_inbound(mcip, ipha, end); + break; + } + case ETHERTYPE_IPV6: { + ip6_t *ip6h = (ip6_t *)start; + + if (start + sizeof (ip6_t) > end) + return; + + intercept_dhcpv6_inbound(mcip, ip6h, end); + break; + } + } + freemsg(nmp); +} + +void +mac_protect_intercept_dhcp(mac_client_impl_t *mcip, mblk_t *mp) +{ + /* + * Skip checks if we are part of an aggr. + */ + if ((mcip->mci_state_flags & MCIS_IS_AGGR_PORT) != 0) + return; + + for (; mp != NULL; mp = mp->b_next) + mac_protect_intercept_dhcp_one(mcip, mp); +} + +void +mac_protect_flush_dhcp(mac_client_impl_t *mcip) +{ + mutex_enter(&mcip->mci_protect_lock); + flush_dhcpv4(mcip); + flush_dhcpv6(mcip); + mutex_exit(&mcip->mci_protect_lock); +} + +void +mac_protect_cancel_timer(mac_client_impl_t *mcip) +{ + mutex_enter(&mcip->mci_protect_lock); + cancel_txn_cleanup_timer(mcip); + mutex_exit(&mcip->mci_protect_lock); +} + +/* + * Check if addr is in the 'allowed-ips' list. + */ + +/* ARGSUSED */ +static boolean_t +ipnospoof_check_v4(mac_client_impl_t *mcip, mac_protect_t *protect, + ipaddr_t *addr) +{ + uint_t i; + + /* + * The unspecified address is allowed. + */ + if (*addr == INADDR_ANY) + return (B_TRUE); + + for (i = 0; i < protect->mp_ipaddrcnt; i++) { + mac_ipaddr_t *v4addr = &protect->mp_ipaddrs[i]; + + if (v4addr->ip_version == IPV4_VERSION && + V4_PART_OF_V6(v4addr->ip_addr) == *addr) + return (B_TRUE); + } + return (check_dhcpv4_dyn_ip(mcip, *addr)); +} + +static boolean_t +ipnospoof_check_v6(mac_client_impl_t *mcip, mac_protect_t *protect, + in6_addr_t *addr) +{ + uint_t i; + + /* + * The unspecified address and the v6 link local address are allowed. + */ + if (IN6_IS_ADDR_UNSPECIFIED(addr) || + ((mcip->mci_protect_flags & MPT_FLAG_V6_LOCAL_ADDR_SET) != 0 && + IN6_ARE_ADDR_EQUAL(&mcip->mci_v6_local_addr, addr))) + return (B_TRUE); + + + for (i = 0; i < protect->mp_ipaddrcnt; i++) { + mac_ipaddr_t *v6addr = &protect->mp_ipaddrs[i]; + + if (v6addr->ip_version == IPV6_VERSION && + IN6_ARE_ADDR_EQUAL(&v6addr->ip_addr, addr)) + return (B_TRUE); + } + return (check_dhcpv6_dyn_ip(mcip, addr)); +} + +/* + * Checks various fields within an IPv6 NDP packet. + */ +static boolean_t +ipnospoof_check_ndp(mac_client_impl_t *mcip, mac_protect_t *protect, + ip6_t *ip6h, uchar_t *end) +{ + icmp6_t *icmp_nd = (icmp6_t *)&ip6h[1]; + int hdrlen, optlen, opttype, len; + uint_t addrlen, maclen; + uint8_t type; + nd_opt_hdr_t *opt; + struct nd_opt_lla *lla = NULL; + + /* + * NDP packets do not have extension headers so the ICMPv6 header + * must immediately follow the IPv6 header. + */ + if (ip6h->ip6_nxt != IPPROTO_ICMPV6) + return (B_TRUE); + + /* ICMPv6 header missing */ + if ((uchar_t *)&icmp_nd[1] > end) + return (B_FALSE); + + len = end - (uchar_t *)icmp_nd; + type = icmp_nd->icmp6_type; + + switch (type) { + case ND_ROUTER_SOLICIT: + hdrlen = sizeof (nd_router_solicit_t); + break; + case ND_ROUTER_ADVERT: + hdrlen = sizeof (nd_router_advert_t); + break; + case ND_NEIGHBOR_SOLICIT: + hdrlen = sizeof (nd_neighbor_solicit_t); + break; + case ND_NEIGHBOR_ADVERT: + hdrlen = sizeof (nd_neighbor_advert_t); + break; + case ND_REDIRECT: + hdrlen = sizeof (nd_redirect_t); + break; + default: + return (B_TRUE); + } + + if (len < hdrlen) + return (B_FALSE); + + /* SLLA option checking is needed for RS/RA/NS */ + opttype = ND_OPT_SOURCE_LINKADDR; + + switch (type) { + case ND_NEIGHBOR_ADVERT: { + nd_neighbor_advert_t *na = (nd_neighbor_advert_t *)icmp_nd; + + if (!ipnospoof_check_v6(mcip, protect, &na->nd_na_target)) { + DTRACE_PROBE2(ndp__na__fail, + mac_client_impl_t *, mcip, ip6_t *, ip6h); + return (B_FALSE); + } + + /* TLLA option for NA */ + opttype = ND_OPT_TARGET_LINKADDR; + break; + } + case ND_REDIRECT: { + /* option checking not needed for RD */ + return (B_TRUE); + } + default: + break; + } - start = mp->b_rptr; + if (len == hdrlen) { + /* no options, we're done */ + return (B_TRUE); } + opt = (nd_opt_hdr_t *)((uchar_t *)icmp_nd + hdrlen); + optlen = len - hdrlen; + + /* find the option header we need */ + while (optlen > sizeof (nd_opt_hdr_t)) { + if (opt->nd_opt_type == opttype) { + lla = (struct nd_opt_lla *)opt; + break; + } + optlen -= 8 * opt->nd_opt_len; + opt = (nd_opt_hdr_t *) + ((uchar_t *)opt + 8 * opt->nd_opt_len); + } + if (lla == NULL) + return (B_TRUE); + + addrlen = lla->nd_opt_lla_len * 8 - sizeof (nd_opt_hdr_t); + maclen = mcip->mci_mip->mi_info.mi_addr_length; + + if (addrlen != maclen || + bcmp(mcip->mci_unicast->ma_addr, + lla->nd_opt_lla_hdw_addr, maclen) != 0) { + DTRACE_PROBE2(ndp__lla__fail, + mac_client_impl_t *, mcip, ip6_t *, ip6h); + return (B_FALSE); + } + + DTRACE_PROBE2(ndp__lla__ok, mac_client_impl_t *, mcip, ip6_t *, ip6h); + return (B_TRUE); +} + +/* + * Enforce ip-nospoof protection. + */ +static int +ipnospoof_check(mac_client_impl_t *mcip, mac_protect_t *protect, + mblk_t *mp, mac_header_info_t *mhip) +{ + size_t hdrsize = mhip->mhi_hdrsize; + uint32_t sap = mhip->mhi_bindsap; + uchar_t *start, *end; + mblk_t *nmp = NULL; + int err; + + err = get_l3_info(mp, hdrsize, &start, &end, &nmp); + if (err != 0) { + DTRACE_PROBE2(invalid__l3, mac_client_impl_t *, mcip, + mblk_t *, mp); + return (err); + } + err = EINVAL; switch (sap) { case ETHERTYPE_IP: { ipha_t *ipha = (ipha_t *)start; - if (start + sizeof (ipha_t) > mp->b_wptr || !OK_32PTR(start)) + if (start + sizeof (ipha_t) > end) goto fail; - if (!ipnospoof_check_ips(protect, ipha->ipha_src)) + if (!ipnospoof_check_v4(mcip, protect, &ipha->ipha_src)) goto fail; + intercept_dhcpv4_outbound(mcip, ipha, end); break; } case ETHERTYPE_ARP: { @@ -103,7 +1703,7 @@ ipnospoof_check(mac_client_impl_t *mcip, mac_protect_t *protect, ipaddr_t spaddr; uchar_t *shaddr; - if (start + sizeof (arh_t) > mp->b_wptr) + if (start + sizeof (arh_t) > end) goto fail; maclen = mcip->mci_mip->mi_info.mi_addr_length; @@ -114,7 +1714,7 @@ ipnospoof_check(mac_client_impl_t *mcip, mac_protect_t *protect, goto fail; arplen = sizeof (arh_t) + 2 * hlen + 2 * plen; - if (start + arplen > mp->b_wptr) + if (start + arplen > end) goto fail; shaddr = start + sizeof (arh_t); @@ -123,20 +1723,230 @@ ipnospoof_check(mac_client_impl_t *mcip, mac_protect_t *protect, goto fail; bcopy(shaddr + hlen, &spaddr, sizeof (spaddr)); - if (!ipnospoof_check_ips(protect, spaddr)) + if (!ipnospoof_check_v4(mcip, protect, &spaddr)) goto fail; break; } - default: + case ETHERTYPE_IPV6: { + ip6_t *ip6h = (ip6_t *)start; + + if (start + sizeof (ip6_t) > end) + goto fail; + + if (!ipnospoof_check_v6(mcip, protect, &ip6h->ip6_src)) + goto fail; + + if (!ipnospoof_check_ndp(mcip, protect, ip6h, end)) + goto fail; + + intercept_dhcpv6_outbound(mcip, ip6h, end); break; } + } + freemsg(nmp); return (0); fail: - /* increment ipnospoof stat here */ + freemsg(nmp); return (err); } +static boolean_t +dhcpnospoof_check_cid(mac_protect_t *p, uchar_t *cid, uint_t cidlen) +{ + int i; + + for (i = 0; i < p->mp_cidcnt; i++) { + mac_dhcpcid_t *dcid = &p->mp_cids[i]; + + if (dcid->dc_len == cidlen && + bcmp(dcid->dc_id, cid, cidlen) == 0) + return (B_TRUE); + } + return (B_FALSE); +} + +static boolean_t +dhcpnospoof_check_v4(mac_client_impl_t *mcip, mac_protect_t *p, + ipha_t *ipha, uchar_t *end) +{ + struct dhcp *dh4; + uchar_t *cid; + uint_t maclen, cidlen = 0; + uint8_t optlen; + int err; + + if ((err = get_dhcpv4_info(ipha, end, &dh4)) != 0) + return (err == EINVAL); + + maclen = mcip->mci_mip->mi_info.mi_addr_length; + if (dh4->hlen == maclen && + bcmp(mcip->mci_unicast->ma_addr, dh4->chaddr, maclen) != 0) { + return (B_FALSE); + } + if (get_dhcpv4_option(dh4, end, CD_CLIENT_ID, &cid, &optlen) == 0) + cidlen = optlen; + + if (cidlen == 0) + return (B_TRUE); + + if (*cid == ARPHRD_ETHER && cidlen - 1 == maclen && + bcmp(mcip->mci_unicast->ma_addr, cid + 1, maclen) == 0) + return (B_TRUE); + + return (dhcpnospoof_check_cid(p, cid, cidlen)); +} + +static boolean_t +dhcpnospoof_check_v6(mac_client_impl_t *mcip, mac_protect_t *p, + ip6_t *ip6h, uchar_t *end) +{ + dhcpv6_message_t *dh6; + dhcpv6_option_t *d6o; + uint8_t mtype; + uchar_t *cid, *lladdr = NULL; + uint_t cidlen, maclen, addrlen = 0; + uint16_t cidtype; + int err; + + if ((err = get_dhcpv6_info(ip6h, end, &dh6)) != 0) + return (err == EINVAL); + + /* + * We only check client-generated messages. + */ + mtype = dh6->d6m_msg_type; + if (mtype == DHCPV6_MSG_ADVERTISE || mtype == DHCPV6_MSG_REPLY || + mtype == DHCPV6_MSG_RECONFIGURE) + return (B_TRUE); + + d6o = get_dhcpv6_option(&dh6[1], end - (uchar_t *)&dh6[1], NULL, + DHCPV6_OPT_CLIENTID, &cidlen); + if (d6o == NULL || (uchar_t *)d6o + cidlen > end) + return (B_TRUE); + + cid = (uchar_t *)&d6o[1]; + cidlen -= sizeof (*d6o); + if (cidlen < sizeof (cidtype)) + return (B_TRUE); + + bcopy(cid, &cidtype, sizeof (cidtype)); + cidtype = ntohs(cidtype); + if (cidtype == DHCPV6_DUID_LLT && cidlen >= sizeof (duid_llt_t)) { + lladdr = cid + sizeof (duid_llt_t); + addrlen = cidlen - sizeof (duid_llt_t); + } + if (cidtype == DHCPV6_DUID_LL && cidlen >= sizeof (duid_ll_t)) { + lladdr = cid + sizeof (duid_ll_t); + addrlen = cidlen - sizeof (duid_ll_t); + } + maclen = mcip->mci_mip->mi_info.mi_addr_length; + if (lladdr != NULL && addrlen == maclen && + bcmp(mcip->mci_unicast->ma_addr, lladdr, maclen) == 0) { + return (B_TRUE); + } + return (dhcpnospoof_check_cid(p, cid, cidlen)); +} + +/* + * Enforce dhcp-nospoof protection. + */ +static int +dhcpnospoof_check(mac_client_impl_t *mcip, mac_protect_t *protect, + mblk_t *mp, mac_header_info_t *mhip) +{ + size_t hdrsize = mhip->mhi_hdrsize; + uint32_t sap = mhip->mhi_bindsap; + uchar_t *start, *end; + mblk_t *nmp = NULL; + int err; + + err = get_l3_info(mp, hdrsize, &start, &end, &nmp); + if (err != 0) { + DTRACE_PROBE2(invalid__l3, mac_client_impl_t *, mcip, + mblk_t *, mp); + return (err); + } + err = EINVAL; + + switch (sap) { + case ETHERTYPE_IP: { + ipha_t *ipha = (ipha_t *)start; + + if (start + sizeof (ipha_t) > end) + goto fail; + + if (!dhcpnospoof_check_v4(mcip, protect, ipha, end)) + goto fail; + + break; + } + case ETHERTYPE_IPV6: { + ip6_t *ip6h = (ip6_t *)start; + + if (start + sizeof (ip6_t) > end) + goto fail; + + if (!dhcpnospoof_check_v6(mcip, protect, ip6h, end)) + goto fail; + + break; + } + } + freemsg(nmp); + return (0); + +fail: + /* increment dhcpnospoof stat here */ + freemsg(nmp); + return (err); +} + +/* + * This needs to be called whenever the mac client's mac address changes. + */ +void +mac_protect_update_v6_local_addr(mac_client_impl_t *mcip) +{ + uint8_t *p, *macaddr = mcip->mci_unicast->ma_addr; + uint_t i, media = mcip->mci_mip->mi_info.mi_media; + in6_addr_t token, *v6addr = &mcip->mci_v6_local_addr; + in6_addr_t ll_template = {(uint32_t)V6_LINKLOCAL, 0x0, 0x0, 0x0}; + + + bzero(&token, sizeof (token)); + p = (uint8_t *)&token.s6_addr32[2]; + + switch (media) { + case DL_ETHER: + bcopy(macaddr, p, 3); + p[0] ^= 0x2; + p[3] = 0xff; + p[4] = 0xfe; + bcopy(macaddr + 3, p + 5, 3); + break; + case DL_IB: + ASSERT(mcip->mci_mip->mi_info.mi_addr_length == 20); + bcopy(macaddr + 12, p, 8); + p[0] |= 2; + break; + default: + /* + * We do not need to generate the local address for link types + * that do not support link protection. Wifi pretends to be + * ethernet so it is covered by the DL_ETHER case (note the + * use of mi_media instead of mi_nativemedia). + */ + return; + } + + for (i = 0; i < 4; i++) { + v6addr->s6_addr32[i] = token.s6_addr32[i] | + ll_template.s6_addr32[i]; + } + mcip->mci_protect_flags |= MPT_FLAG_V6_LOCAL_ADDR_SET; +} + /* * Enforce link protection on one packet. */ @@ -159,7 +1969,6 @@ mac_protect_check_one(mac_client_impl_t *mcip, mblk_t *mp) mblk_t *, mp); return (err); } - protect = &mrp->mrp_protect; types = protect->mp_types; @@ -167,12 +1976,12 @@ mac_protect_check_one(mac_client_impl_t *mcip, mblk_t *mp) if (mhi.mhi_saddr != NULL && bcmp(mcip->mci_unicast->ma_addr, mhi.mhi_saddr, mip->mi_info.mi_addr_length) != 0) { + BUMP_STAT(mcip, macspoofed); DTRACE_PROBE2(mac__nospoof__fail, mac_client_impl_t *, mcip, mblk_t *, mp); return (EINVAL); } } - if ((types & MPT_RESTRICTED) != 0) { uint32_t vid = VLAN_ID(mhi.mhi_tci); uint32_t sap = mhi.mhi_bindsap; @@ -182,6 +1991,7 @@ mac_protect_check_one(mac_client_impl_t *mcip, mblk_t *mp) * the vid is not spoofed. */ if (vid != 0 && !mac_client_check_flow_vid(mcip, vid)) { + BUMP_STAT(mcip, restricted); DTRACE_PROBE2(restricted__vid__invalid, mac_client_impl_t *, mcip, mblk_t *, mp); return (EINVAL); @@ -189,20 +1999,28 @@ mac_protect_check_one(mac_client_impl_t *mcip, mblk_t *mp) if (sap != ETHERTYPE_IP && sap != ETHERTYPE_IPV6 && sap != ETHERTYPE_ARP) { + BUMP_STAT(mcip, restricted); DTRACE_PROBE2(restricted__fail, mac_client_impl_t *, mcip, mblk_t *, mp); return (EINVAL); } } - if ((types & MPT_IPNOSPOOF) != 0) { - if ((err = ipnospoof_check(mcip, protect, - mp, &mhi)) != 0) { + if ((err = ipnospoof_check(mcip, protect, mp, &mhi)) != 0) { + BUMP_STAT(mcip, ipspoofed); DTRACE_PROBE2(ip__nospoof__fail, mac_client_impl_t *, mcip, mblk_t *, mp); return (err); } } + if ((types & MPT_DHCPNOSPOOF) != 0) { + if ((err = dhcpnospoof_check(mcip, protect, mp, &mhi)) != 0) { + BUMP_STAT(mcip, dhcpspoofed); + DTRACE_PROBE2(dhcp__nospoof__fail, + mac_client_impl_t *, mcip, mblk_t *, mp); + return (err); + } + } return (0); } @@ -242,11 +2060,89 @@ mac_protect_check(mac_client_handle_t mch, mblk_t *mp) boolean_t mac_protect_enabled(mac_client_handle_t mch, uint32_t type) { - mac_client_impl_t *mcip = (mac_client_impl_t *)mch; - mac_resource_props_t *mrp = MCIP_RESOURCE_PROPS(mcip); + return (MAC_PROTECT_ENABLED((mac_client_impl_t *)mch, type)); +} - ASSERT(mrp != NULL); - return ((mrp->mrp_protect.mp_types & type) != 0); +static int +validate_ips(mac_protect_t *p) +{ + uint_t i, j; + + if (p->mp_ipaddrcnt == MPT_RESET) + return (0); + + if (p->mp_ipaddrcnt > MPT_MAXIPADDR) + return (EINVAL); + + for (i = 0; i < p->mp_ipaddrcnt; i++) { + mac_ipaddr_t *addr = &p->mp_ipaddrs[i]; + + /* + * The unspecified address is implicitly allowed + * so there's no need to add it to the list. + */ + if (addr->ip_version == IPV4_VERSION) { + if (V4_PART_OF_V6(addr->ip_addr) == INADDR_ANY) + return (EINVAL); + } else if (addr->ip_version == IPV6_VERSION) { + if (IN6_IS_ADDR_UNSPECIFIED(&addr->ip_addr)) + return (EINVAL); + } else { + /* invalid ip version */ + return (EINVAL); + } + + for (j = 0; j < p->mp_ipaddrcnt; j++) { + mac_ipaddr_t *addr1 = &p->mp_ipaddrs[j]; + + if (i == j || addr->ip_version != addr1->ip_version) + continue; + + /* found a duplicate */ + if ((addr->ip_version == IPV4_VERSION && + V4_PART_OF_V6(addr->ip_addr) == + V4_PART_OF_V6(addr1->ip_addr)) || + IN6_ARE_ADDR_EQUAL(&addr->ip_addr, + &addr1->ip_addr)) + return (EINVAL); + } + } + return (0); +} + +/* ARGSUSED */ +static int +validate_cids(mac_protect_t *p) +{ + uint_t i, j; + + if (p->mp_cidcnt == MPT_RESET) + return (0); + + if (p->mp_cidcnt > MPT_MAXCID) + return (EINVAL); + + for (i = 0; i < p->mp_cidcnt; i++) { + mac_dhcpcid_t *cid = &p->mp_cids[i]; + + if (cid->dc_len > MPT_MAXCIDLEN || + (cid->dc_form != CIDFORM_TYPED && + cid->dc_form != CIDFORM_HEX && + cid->dc_form != CIDFORM_STR)) + return (EINVAL); + + for (j = 0; j < p->mp_cidcnt; j++) { + mac_dhcpcid_t *cid1 = &p->mp_cids[j]; + + if (i == j || cid->dc_len != cid1->dc_len) + continue; + + /* found a duplicate */ + if (bcmp(cid->dc_id, cid1->dc_id, cid->dc_len) == 0) + return (EINVAL); + } + } + return (0); } /* @@ -256,33 +2152,18 @@ int mac_protect_validate(mac_resource_props_t *mrp) { mac_protect_t *p = &mrp->mrp_protect; + int err; /* check for invalid types */ if (p->mp_types != MPT_RESET && (p->mp_types & ~MPT_ALL) != 0) return (EINVAL); - if (p->mp_ipaddrcnt != MPT_RESET) { - uint_t i, j; - - if (p->mp_ipaddrcnt > MPT_MAXIPADDR) - return (EINVAL); + if ((err = validate_ips(p)) != 0) + return (err); - for (i = 0; i < p->mp_ipaddrcnt; i++) { - /* - * The unspecified address is implicitly allowed - * so there's no need to add it to the list. - */ - if (p->mp_ipaddrs[i] == INADDR_ANY) - return (EINVAL); + if ((err = validate_cids(p)) != 0) + return (err); - for (j = 0; j < p->mp_ipaddrcnt; j++) { - /* found a duplicate */ - if (i != j && - p->mp_ipaddrs[i] == p->mp_ipaddrs[j]) - return (EINVAL); - } - } - } return (0); } @@ -326,9 +2207,8 @@ mac_protect_update(mac_resource_props_t *new, mac_resource_props_t *curr) curr->mrp_mask |= MRP_PROTECT; } } - if (np->mp_ipaddrcnt != 0) { - if (np->mp_ipaddrcnt < MPT_MAXIPADDR) { + if (np->mp_ipaddrcnt <= MPT_MAXIPADDR) { bcopy(np->mp_ipaddrs, cp->mp_ipaddrs, sizeof (cp->mp_ipaddrs)); cp->mp_ipaddrcnt = np->mp_ipaddrcnt; @@ -337,4 +2217,47 @@ mac_protect_update(mac_resource_props_t *new, mac_resource_props_t *curr) cp->mp_ipaddrcnt = 0; } } + if (np->mp_cidcnt != 0) { + if (np->mp_cidcnt <= MPT_MAXCID) { + bcopy(np->mp_cids, cp->mp_cids, sizeof (cp->mp_cids)); + cp->mp_cidcnt = np->mp_cidcnt; + } else if (np->mp_cidcnt == MPT_RESET) { + bzero(cp->mp_cids, sizeof (cp->mp_cids)); + cp->mp_cidcnt = 0; + } + } +} + +void +mac_protect_init(mac_client_impl_t *mcip) +{ + mutex_init(&mcip->mci_protect_lock, NULL, MUTEX_DRIVER, NULL); + mcip->mci_protect_flags = 0; + mcip->mci_txn_cleanup_tid = 0; + avl_create(&mcip->mci_v4_pending_txn, compare_dhcpv4_xid, + sizeof (dhcpv4_txn_t), offsetof(dhcpv4_txn_t, dt_node)); + avl_create(&mcip->mci_v4_completed_txn, compare_dhcpv4_cid, + sizeof (dhcpv4_txn_t), offsetof(dhcpv4_txn_t, dt_node)); + avl_create(&mcip->mci_v4_dyn_ip, compare_dhcpv4_ip, + sizeof (dhcpv4_txn_t), offsetof(dhcpv4_txn_t, dt_ipnode)); + avl_create(&mcip->mci_v6_pending_txn, compare_dhcpv6_xid, + sizeof (dhcpv6_txn_t), offsetof(dhcpv6_txn_t, dt_node)); + avl_create(&mcip->mci_v6_cid, compare_dhcpv6_cid, + sizeof (dhcpv6_cid_t), offsetof(dhcpv6_cid_t, dc_node)); + avl_create(&mcip->mci_v6_dyn_ip, compare_dhcpv6_ip, + sizeof (dhcpv6_addr_t), offsetof(dhcpv6_addr_t, da_node)); +} + +void +mac_protect_fini(mac_client_impl_t *mcip) +{ + avl_destroy(&mcip->mci_v6_dyn_ip); + avl_destroy(&mcip->mci_v6_cid); + avl_destroy(&mcip->mci_v6_pending_txn); + avl_destroy(&mcip->mci_v4_dyn_ip); + avl_destroy(&mcip->mci_v4_completed_txn); + avl_destroy(&mcip->mci_v4_pending_txn); + mcip->mci_txn_cleanup_tid = 0; + mcip->mci_protect_flags = 0; + mutex_destroy(&mcip->mci_protect_lock); } |