summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/mac/mac_protect.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/io/mac/mac_protect.c')
-rw-r--r--usr/src/uts/common/io/mac/mac_protect.c2049
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);
}