| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2015, Joyent, Inc. All rights reserved. |
| */ |
| /* |
| * Copyright 2014 Nexenta Systems, Inc. All rights reserved. |
| */ |
| |
| #include <sys/cmn_err.h> |
| #include <sys/strsun.h> |
| #include <sys/sdt.h> |
| #include <sys/mac.h> |
| #include <sys/mac_impl.h> |
| #include <sys/mac_client_impl.h> |
| #include <sys/mac_client_priv.h> |
| #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> |
| |
| /* |
| * 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 ulong_t slaac_max_allowed = 512; |
| static hrtime_t txn_cleanup_interval = 60 * NANOSEC; |
| |
| /* |
| * DHCPv4 transaction. It may be added to three different tables |
| * (keyed by different fields). |
| */ |
| typedef struct dhcpv4_txn { |
| uint32_t dt_xid; |
| hrtime_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; |
| hrtime_t dt_timestamp; |
| dhcpv6_cid_t *dt_cid; |
| avl_node_t dt_node; |
| struct dhcpv6_txn *dt_next; |
| } dhcpv6_txn_t; |
| |
| /* |
| * Stateless address autoconfiguration (SLAAC) address. May be added to |
| * mci_v6_slaac_ip. |
| */ |
| typedef struct slaac_addr { |
| in6_addr_t sla_prefix; |
| in6_addr_t sla_addr; |
| avl_node_t sla_node; |
| } slaac_addr_t; |
| |
| static void start_txn_cleanup_timer(mac_client_impl_t *); |
| static boolean_t allowed_ips_set(mac_resource_props_t *, uint32_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 |
| 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 = gethrtime(); |
| 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) |
| { |
| dhcpv4_txn_t *txn, *ctxn, *next, *txn_list = NULL; |
| |
| /* |
| * Find stale pending transactions and place them on a list |
| * to be removed. |
| */ |
| for (txn = avl_first(&mcip->mci_v4_pending_txn); txn != NULL; |
| txn = avl_walk(&mcip->mci_v4_pending_txn, txn, AVL_AFTER)) { |
| if (gethrtime() - txn->dt_timestamp > txn_cleanup_interval) { |
| DTRACE_PROBE2(found__expired__txn, |
| mac_client_impl_t *, mcip, |
| dhcpv4_txn_t *, txn); |
| |
| txn->dt_next = txn_list; |
| txn_list = txn; |
| } |
| } |
| |
| /* |
| * 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 boolean_t |
| 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; |
| mac_resource_props_t *mrp = MCIP_RESOURCE_PROPS(mcip); |
| |
| if (get_dhcpv4_info(ipha, end, &dh4) != 0) |
| return (B_TRUE); |
| |
| /* ip_nospoof/allowed-ips and DHCP are mutually exclusive by default */ |
| if (allowed_ips_set(mrp, IPV4_VERSION)) |
| return (B_FALSE); |
| |
| 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 (B_TRUE); |
| } |
| mtype = *opt; |
| if (mtype != REQUEST && mtype != RELEASE) { |
| DTRACE_PROBE3(ignored__mtype, mac_client_impl_t *, mcip, |
| struct dhcp *, dh4, uint8_t, mtype); |
| return (B_TRUE); |
| } |
| |
| /* 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 = gethrtime(); |
| 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); |
| return (B_TRUE); |
| } |
| |
| /* |
| * Core logic for intercepting inbound DHCPv4 packets. |
| */ |
| static void |
| intercept_dhcpv4_inbound(mac_client_impl_t *mcip, uchar_t *end, |
| struct dhcp *dh4) |
| { |
| uchar_t *opt; |
| dhcpv4_txn_t *txn, *ctxn; |
| uint8_t opt_len, mtype; |
| |
| 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 |
| compare_dhcpv6_xid(const void *arg1, const void *arg2) |
| { |
| 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); |
| } |
| |
| static int |
| compare_slaac_ip(const void *arg1, const void *arg2) |
| { |
| const slaac_addr_t *ip1 = arg1, *ip2 = arg2; |
| int ret; |
| |
| ret = memcmp(&ip1->sla_addr, &ip2->sla_addr, sizeof (in6_addr_t)); |
| 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); |
| } |
| |
| static int |
| get_ra_info(ip6_t *ip6h, uchar_t *end, nd_router_advert_t **ra) |
| { |
| uint16_t hdrlen; |
| ip6_frag_t *frag = NULL; |
| uint8_t proto; |
| uchar_t *hdrp; |
| struct icmp6_hdr *icmp; |
| |
| if (!mac_ip_hdr_length_v6(ip6h, end, &hdrlen, &proto, &frag)) |
| return (ENOSPC); |
| |
| if (proto != IPPROTO_ICMPV6) |
| 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); |
| return (ENOSPC); |
| } |
| |
| /* |
| * Ensure that the ICMP header falls w/in packet boundaries, in case |
| * we've received a malicious packet that reports incorrect lengths. |
| */ |
| hdrp = (uchar_t *)ip6h + hdrlen; |
| if ((hdrp + sizeof (struct icmp6_hdr)) > end) { |
| return (EINVAL); |
| } |
| icmp = (struct icmp6_hdr *)hdrp; |
| |
| if (icmp->icmp6_type != ND_ROUTER_ADVERT || |
| icmp->icmp6_code != 0) |
| return (EINVAL); |
| |
| *ra = (nd_router_advert_t *)icmp; |
| 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 = gethrtime(); |
| 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); |
| } |
| } |
| |
| void |
| flush_slaac(mac_client_impl_t *mcip) |
| { |
| void *cookie = NULL; |
| slaac_addr_t *addr = NULL; |
| |
| while ((addr = avl_destroy_nodes(&mcip->mci_v6_slaac_ip, &cookie)) != |
| NULL) { |
| kmem_free(addr, sizeof (slaac_addr_t)); |
| } |
| } |
| |
| /* |
| * Cleanup stale DHCPv6 transactions. |
| */ |
| static void |
| txn_cleanup_v6(mac_client_impl_t *mcip) |
| { |
| dhcpv6_txn_t *txn, *next, *txn_list = NULL; |
| |
| /* |
| * Find stale pending transactions and place them on a list |
| * to be removed. |
| */ |
| for (txn = avl_first(&mcip->mci_v6_pending_txn); txn != NULL; |
| txn = avl_walk(&mcip->mci_v6_pending_txn, txn, AVL_AFTER)) { |
| if (gethrtime() - 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 boolean_t |
| 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; |
| mac_resource_props_t *mrp = MCIP_RESOURCE_PROPS(mcip); |
| |
| if (get_dhcpv6_info(ip6h, end, &dh6) != 0) |
| return (B_TRUE); |
| |
| /* ip_nospoof/allowed-ips and DHCP are mutually exclusive by default */ |
| if (allowed_ips_set(mrp, IPV6_VERSION)) |
| return (B_FALSE); |
| |
| /* |
| * We want to act on packets that result in DHCPv6 Reply messages, or |
| * on packets that give up an IPv6 address. For example, a Request or |
| * Solicit (w/ the Rapid Commit option) will cause the server to send a |
| * Reply, ending the transaction. |
| */ |
| mtype = dh6->d6m_msg_type; |
| if (mtype != DHCPV6_MSG_SOLICIT && mtype != DHCPV6_MSG_REQUEST && |
| mtype != DHCPV6_MSG_RENEW && mtype != DHCPV6_MSG_REBIND && |
| mtype != DHCPV6_MSG_RELEASE) |
| return (B_TRUE); |
| |
| if ((cid = create_dhcpv6_cid(dh6, end)) == NULL) |
| return (B_TRUE); |
| |
| 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 = gethrtime(); |
| 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); |
| return (B_TRUE); |
| } |
| |
| /* |
| * Core logic for intercepting inbound DHCPv6 packets. |
| */ |
| static void |
| intercept_dhcpv6_inbound(mac_client_impl_t *mcip, uchar_t *end, |
| dhcpv6_message_t *dh6) |
| { |
| dhcpv6_txn_t *txn; |
| uint32_t xid; |
| uint8_t mtype; |
| uint16_t status; |
| |
| 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); |
| } |
| |
| /* |
| * Check whether an IP address is in the SLAAC table. |
| */ |
| static boolean_t |
| check_slaac_ip(mac_client_impl_t *mcip, in6_addr_t *addr) |
| { |
| slaac_addr_t tmp_addr, *a; |
| |
| mutex_enter(&mcip->mci_protect_lock); |
| bcopy(addr, &tmp_addr.sla_addr, sizeof (in6_addr_t)); |
| a = avl_find(&mcip->mci_v6_slaac_ip, &tmp_addr, NULL); |
| mutex_exit(&mcip->mci_protect_lock); |
| return (a != NULL); |
| } |
| |
| static boolean_t |
| insert_slaac_ip(avl_tree_t *tree, in6_addr_t *token, slaac_addr_t *addr) |
| { |
| uint_t i; |
| avl_index_t where; |
| in6_addr_t *prefix = &addr->sla_prefix; |
| in6_addr_t *in6p = &addr->sla_addr; |
| |
| bcopy(prefix, in6p, sizeof (struct in6_addr)); |
| |
| for (i = 0; i < 4; i++) { |
| in6p->s6_addr32[i] = token->s6_addr32[i] | |
| in6p->s6_addr32[i]; |
| } |
| |
| DTRACE_PROBE1(generated__addr, in6_addr_t *, in6p); |
| |
| if (avl_find(tree, addr, &where) != NULL) |
| return (B_FALSE); |
| |
| avl_insert(tree, addr, where); |
| return (B_TRUE); |
| } |
| |
| static void |
| insert_slaac_prefix(mac_client_impl_t *mcip, nd_opt_prefix_info_t *po) |
| { |
| slaac_addr_t *addr = NULL; |
| in6_addr_t *token = &mcip->mci_v6_mac_token; |
| |
| ASSERT(MUTEX_HELD(&mcip->mci_protect_lock)); |
| |
| if (avl_numnodes(&mcip->mci_v6_slaac_ip) >= slaac_max_allowed) { |
| DTRACE_PROBE(limit__reached); |
| return; |
| } |
| |
| if ((addr = kmem_zalloc(sizeof (slaac_addr_t), |
| KM_NOSLEEP | KM_NORMALPRI)) == NULL) |
| return; |
| |
| bcopy(&po->nd_opt_pi_prefix, &addr->sla_prefix, |
| sizeof (struct in6_addr)); |
| |
| if (!insert_slaac_ip(&mcip->mci_v6_slaac_ip, token, addr)) { |
| kmem_free(addr, sizeof (slaac_addr_t)); |
| } |
| } |
| |
| static void |
| intercept_prefix_info(mac_client_impl_t *mcip, nd_opt_prefix_info_t *po) |
| { |
| if (8 * po->nd_opt_pi_len != sizeof (nd_opt_prefix_info_t)) { |
| DTRACE_PROBE(invalid__length); |
| return; |
| } |
| |
| if (po->nd_opt_pi_prefix_len > 128) { |
| DTRACE_PROBE(invalid__plen); |
| return; |
| } |
| |
| if (IN6_IS_ADDR_LINKLOCAL(&po->nd_opt_pi_prefix)) { |
| DTRACE_PROBE(link__local); |
| return; |
| } |
| |
| if ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) == 0) |
| return; |
| |
| mutex_enter(&mcip->mci_protect_lock); |
| insert_slaac_prefix(mcip, po); |
| mutex_exit(&mcip->mci_protect_lock); |
| } |
| |
| /* |
| * If we receive a Router Advertisement carrying prefix information and |
| * indicating that SLAAC should be performed, then track the prefix. |
| */ |
| static void |
| intercept_ra_inbound(mac_client_impl_t *mcip, ip6_t *ip6h, uchar_t *end, |
| nd_router_advert_t *ra) |
| { |
| struct nd_opt_hdr *opt; |
| int len, optlen; |
| |
| if (ip6h->ip6_hlim != 255) { |
| DTRACE_PROBE1(invalid__hoplimit, uint8_t, ip6h->ip6_hlim); |
| return; |
| } |
| |
| len = ip6h->ip6_plen - sizeof (nd_router_advert_t); |
| opt = (struct nd_opt_hdr *)&ra[1]; |
| while (len >= sizeof (struct nd_opt_hdr) && |
| ((uchar_t *)opt + sizeof (struct nd_opt_hdr)) <= end) { |
| optlen = opt->nd_opt_len * 8; |
| |
| if (optlen < sizeof (struct nd_opt_hdr) || |
| ((uchar_t *)opt + optlen) > end) { |
| DTRACE_PROBE(invalid__length); |
| return; |
| } |
| |
| if (opt->nd_opt_type == ND_OPT_PREFIX_INFORMATION) { |
| intercept_prefix_info(mcip, |
| (nd_opt_prefix_info_t *)opt); |
| } |
| |
| opt = (struct nd_opt_hdr *)((char *)opt + optlen); |
| len -= optlen; |
| } |
| } |
| |
| /* |
| * 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 / (NANOSEC / MICROSEC))); |
| } |
| 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 / (NANOSEC / MICROSEC))); |
| } |
| } |
| |
| 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) { |
| /* |
| * Temporarily adjust mp->b_rptr to ensure proper |
| * alignment of IP header in newmp. |
| */ |
| 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_dynamic_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: { |
| struct dhcp *dh4; |
| ipha_t *ipha = (ipha_t *)start; |
| |
| if (start + sizeof (ipha_t) > end) |
| return; |
| |
| if (get_dhcpv4_info(ipha, end, &dh4) == 0) { |
| intercept_dhcpv4_inbound(mcip, end, dh4); |
| } |
| break; |
| } |
| case ETHERTYPE_IPV6: { |
| dhcpv6_message_t *dh6; |
| nd_router_advert_t *ra; |
| ip6_t *ip6h = (ip6_t *)start; |
| |
| if (start + sizeof (ip6_t) > end) |
| return; |
| |
| if (get_dhcpv6_info(ip6h, end, &dh6) == 0) { |
| intercept_dhcpv6_inbound(mcip, end, dh6); |
| } else if (get_ra_info(ip6h, end, &ra) == 0) { |
| intercept_ra_inbound(mcip, ip6h, end, ra); |
| } |
| |
| break; |
| } |
| } |
| freemsg(nmp); |
| } |
| |
| void |
| mac_protect_intercept_dynamic(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_dynamic_one(mcip, mp); |
| } |
| |
| void |
| mac_protect_flush_dynamic(mac_client_impl_t *mcip) |
| { |
| mutex_enter(&mcip->mci_protect_lock); |
| flush_dhcpv4(mcip); |
| flush_dhcpv6(mcip); |
| flush_slaac(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) { |
| uint32_t mask; |
| |
| /* LINTED E_SUSPICIOUS_COMPARISON */ |
| ASSERT(v4addr->ip_netmask >= 0 && |
| v4addr->ip_netmask <= 32); |
| mask = 0xFFFFFFFFu << (32 - v4addr->ip_netmask); |
| /* |
| * Since we have a netmask we know this entry |
| * signifies the entire subnet. Check if the |
| * given address is on the subnet. |
| */ |
| if (htonl(V4_PART_OF_V6(v4addr->ip_addr)) == |
| (htonl(*addr) & mask)) |
| return (B_TRUE); |
| } |
| } |
| return (protect->mp_ipaddrcnt == 0 ? |
| check_dhcpv4_dyn_ip(mcip, *addr) : B_FALSE); |
| } |
| |
| 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 && |
| /* LINTED E_SUSPICIOUS_COMPARISON */ |
| IN6_ARE_PREFIXEDADDR_EQUAL(&v6addr->ip_addr, addr, |
| v6addr->ip_netmask)) |
| return (B_TRUE); |
| } |
| |
| if (protect->mp_ipaddrcnt == 0) { |
| return (check_slaac_ip(mcip, addr) || |
| check_dhcpv6_dyn_ip(mcip, addr)); |
| } else { |
| return (B_FALSE); |
| } |
| } |
| |
| /* |
| * 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; |
| } |
| |
| 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) > end) |
| goto fail; |
| |
| if (!ipnospoof_check_v4(mcip, protect, &ipha->ipha_src)) |
| goto fail; |
| |
| if (!intercept_dhcpv4_outbound(mcip, ipha, end)) |
| goto fail; |
| break; |
| } |
| case ETHERTYPE_ARP: { |
| arh_t *arh = (arh_t *)start; |
| uint32_t maclen, hlen, plen, arplen; |
| ipaddr_t spaddr; |
| uchar_t *shaddr; |
| |
| if (start + sizeof (arh_t) > end) |
| goto fail; |
| |
| maclen = mcip->mci_mip->mi_info.mi_addr_length; |
| hlen = arh->arh_hlen; |
| plen = arh->arh_plen; |
| if ((hlen != 0 && hlen != maclen) || |
| plen != sizeof (ipaddr_t)) |
| goto fail; |
| |
| arplen = sizeof (arh_t) + 2 * hlen + 2 * plen; |
| if (start + arplen > end) |
| goto fail; |
| |
| shaddr = start + sizeof (arh_t); |
| if (hlen != 0 && |
| bcmp(mcip->mci_unicast->ma_addr, shaddr, maclen) != 0) |
| goto fail; |
| |
| bcopy(shaddr + hlen, &spaddr, sizeof (spaddr)); |
| if (!ipnospoof_check_v4(mcip, protect, &spaddr)) |
| goto fail; |
| break; |
| } |
| 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; |
| |
| if (!intercept_dhcpv6_outbound(mcip, ip6h, end)) |
| goto fail; |
| break; |
| } |
| } |
| freemsg(nmp); |
| return (0); |
| |
| fail: |
| 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 is called whenever the mac client's mac address changes, to make sure |
| * we allow use of the new link-local address. |
| */ |
| static void |
| mac_protect_update_v6_local_addr(mac_client_impl_t *mcip) |
| { |
| uint_t i; |
| in6_addr_t *token = &mcip->mci_v6_mac_token; |
| in6_addr_t *v6addr = &mcip->mci_v6_local_addr; |
| in6_addr_t ll_template = {(uint32_t)V6_LINKLOCAL, 0x0, 0x0, 0x0}; |
| |
| 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; |
| } |
| |
| /* |
| * This is called whenever the mac client's mac address changes, to make sure |
| * that any existing addresses gained via SLAAC are appropriately updated. |
| */ |
| static void |
| mac_protect_update_v6_slaac_addr(mac_client_impl_t *mcip) |
| { |
| void *cookie = NULL; |
| avl_tree_t temp_tree; |
| avl_tree_t *ttp = &temp_tree, *sip = &mcip->mci_v6_slaac_ip; |
| in6_addr_t *token = &mcip->mci_v6_mac_token; |
| slaac_addr_t *addr = NULL; |
| |
| avl_create(ttp, compare_slaac_ip, sizeof (slaac_addr_t), |
| offsetof(slaac_addr_t, sla_node)); |
| |
| /* Copy everything over to the temporary tree, and fix the IP address */ |
| while ((addr = avl_destroy_nodes(sip, &cookie)) != NULL) { |
| VERIFY(insert_slaac_ip(ttp, token, addr) == B_TRUE); |
| } |
| |
| /* |
| * Now that the tempory tree has all of the modified addresses, we can |
| * swap them over to the original tree once it's reset. |
| */ |
| avl_destroy(sip); |
| avl_create(sip, compare_slaac_ip, sizeof (slaac_addr_t), |
| offsetof(slaac_addr_t, sla_node)); |
| avl_swap(ttp, sip); |
| } |
| |
| /* |
| * After the unicast MAC address changes, we need to update the derived token, |
| * and update the IPv6 addresses that use the token. |
| */ |
| void |
| mac_protect_update_mac_token(mac_client_impl_t *mcip) |
| { |
| uint_t media = mcip->mci_mip->mi_info.mi_media; |
| uint8_t *p, *macaddr = mcip->mci_unicast->ma_addr; |
| in6_addr_t *token = &mcip->mci_v6_mac_token; |
| |
| bzero(token, sizeof (in6_addr_t)); |
| 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; |
| } |
| |
| mac_protect_update_v6_local_addr(mcip); |
| mac_protect_update_v6_slaac_addr(mcip); |
| } |
| |
| |
| |
| /* |
| * Enforce link protection on one packet. |
| */ |
| static int |
| mac_protect_check_one(mac_client_impl_t *mcip, mblk_t *mp) |
| { |
| mac_impl_t *mip = mcip->mci_mip; |
| mac_resource_props_t *mrp = MCIP_RESOURCE_PROPS(mcip); |
| mac_protect_t *protect; |
| mac_header_info_t mhi; |
| uint32_t types; |
| int err; |
| |
| ASSERT(mp->b_next == NULL); |
| ASSERT(mrp != NULL); |
| |
| 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); |
| } |
| protect = &mrp->mrp_protect; |
| types = protect->mp_types; |
| |
| if ((types & MPT_MACNOSPOOF) != 0) { |
| 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; |
| |
| /* |
| * ETHERTYPE_VLAN packets are allowed through, provided that |
| * 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); |
| } |
| |
| 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) { |
| 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); |
| } |
| |
| /* |
| * Enforce link protection on a packet chain. |
| * Packets that pass the checks are returned back to the caller. |
| */ |
| mblk_t * |
| mac_protect_check(mac_client_handle_t mch, mblk_t *mp) |
| { |
| mac_client_impl_t *mcip = (mac_client_impl_t *)mch; |
| mblk_t *ret_mp = NULL, **tailp = &ret_mp, *next; |
| |
| /* |
| * Skip checks if we are part of an aggr. |
| */ |
| if ((mcip->mci_state_flags & MCIS_IS_AGGR_PORT) != 0) |
| return (mp); |
| |
| for (; mp != NULL; mp = next) { |
| next = mp->b_next; |
| mp->b_next = NULL; |
| |
| if (mac_protect_check_one(mcip, mp) == 0) { |
| *tailp = mp; |
| tailp = &mp->b_next; |
| } else { |
| freemsg(mp); |
| } |
| } |
| return (ret_mp); |
| } |
| |
| /* |
| * Check if a particular protection type is enabled. |
| */ |
| boolean_t |
| mac_protect_enabled(mac_client_handle_t mch, uint32_t type) |
| { |
| return (MAC_PROTECT_ENABLED((mac_client_impl_t *)mch, type)); |
| } |
| |
| 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. Also, validate that the netmask, |
| * if any, is sane for the specific version of IP. A mask of |
| * some kind is always required. |
| */ |
| if (addr->ip_netmask == 0) |
| return (EINVAL); |
| |
| if (addr->ip_version == IPV4_VERSION) { |
| if (V4_PART_OF_V6(addr->ip_addr) == INADDR_ANY) |
| return (EINVAL); |
| if (addr->ip_netmask > 32) |
| return (EINVAL); |
| } else if (addr->ip_version == IPV6_VERSION) { |
| if (IN6_IS_ADDR_UNSPECIFIED(&addr->ip_addr)) |
| return (EINVAL); |
| |
| if (IN6_IS_ADDR_V4MAPPED_ANY(&addr->ip_addr)) |
| return (EINVAL); |
| |
| if (addr->ip_netmask > 128) |
| 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); |
| } |
| |
| /* |
| * Sanity-checks parameters given by userland. |
| */ |
| 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 ((err = validate_ips(p)) != 0) |
| return (err); |
| |
| if ((err = validate_cids(p)) != 0) |
| return (err); |
| |
| return (0); |
| } |
| |
| /* |
| * Enable/disable link protection. |
| */ |
| int |
| mac_protect_set(mac_client_handle_t mch, mac_resource_props_t *mrp) |
| { |
| mac_client_impl_t *mcip = (mac_client_impl_t *)mch; |
| mac_impl_t *mip = mcip->mci_mip; |
| uint_t media = mip->mi_info.mi_nativemedia; |
| int err; |
| |
| ASSERT(MAC_PERIM_HELD((mac_handle_t)mip)); |
| |
| /* tunnels are not supported */ |
| if (media == DL_IPV4 || media == DL_IPV6 || media == DL_6TO4) |
| return (ENOTSUP); |
| |
| if ((err = mac_protect_validate(mrp)) != 0) |
| return (err); |
| |
| if (err != 0) |
| return (err); |
| |
| mac_update_resources(mrp, MCIP_RESOURCE_PROPS(mcip), B_FALSE); |
| i_mac_notify(((mcip->mci_state_flags & MCIS_IS_VNIC) != 0 ? |
| mcip->mci_upper_mip : mip), MAC_NOTE_ALLOWED_IPS); |
| return (0); |
| } |
| |
| void |
| mac_protect_update(mac_resource_props_t *new, mac_resource_props_t *curr) |
| { |
| mac_protect_t *np = &new->mrp_protect; |
| mac_protect_t *cp = &curr->mrp_protect; |
| uint32_t types = np->mp_types; |
| |
| if (types == MPT_RESET) { |
| cp->mp_types = 0; |
| curr->mrp_mask &= ~MRP_PROTECT; |
| } else { |
| if (types != 0) { |
| cp->mp_types = types; |
| curr->mrp_mask |= MRP_PROTECT; |
| } |
| } |
| if (np->mp_ipaddrcnt != 0) { |
| if (np->mp_ipaddrcnt <= MPT_MAXIPADDR) { |
| bcopy(np->mp_ipaddrs, cp->mp_ipaddrs, |
| sizeof (cp->mp_ipaddrs)); |
| cp->mp_ipaddrcnt = np->mp_ipaddrcnt; |
| } else if (np->mp_ipaddrcnt == MPT_RESET) { |
| bzero(cp->mp_ipaddrs, sizeof (cp->mp_ipaddrs)); |
| 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)); |
| avl_create(&mcip->mci_v6_slaac_ip, compare_slaac_ip, |
| sizeof (slaac_addr_t), offsetof(slaac_addr_t, sla_node)); |
| |
| if (mcip->mci_state_flags & MCIS_IS_VNIC) |
| mcip->mci_protect_flags |= MPT_FLAG_PROMISC_FILTERED; |
| } |
| |
| 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); |
| avl_destroy(&mcip->mci_v6_slaac_ip); |
| mcip->mci_txn_cleanup_tid = 0; |
| mcip->mci_protect_flags = 0; |
| mutex_destroy(&mcip->mci_protect_lock); |
| } |
| |
| static boolean_t |
| allowed_ips_set(mac_resource_props_t *mrp, uint32_t af) |
| { |
| int i; |
| |
| for (i = 0; i < mrp->mrp_protect.mp_ipaddrcnt; i++) { |
| if (mrp->mrp_protect.mp_ipaddrs[i].ip_version == af) |
| return (B_TRUE); |
| } |
| return (B_FALSE); |
| } |
| |
| mac_protect_t * |
| mac_protect_get(mac_handle_t mh) |
| { |
| mac_impl_t *mip = (mac_impl_t *)mh; |
| |
| return (&mip->mi_resource_props.mrp_protect); |
| } |