mac_protect.c revision 25ec3e3dd27cc1038c10efa18ed08f064eab5fbe
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#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 <inet/ip.h>
#include <inet/ip6.h>
#include <inet/arp.h>
/*
* Check if ipaddr is in the 'allowed-ips' list.
*/
static boolean_t
ipnospoof_check_ips(mac_protect_t *protect, ipaddr_t ipaddr)
{
uint_t i;
/*
* unspecified addresses are harmless and are used by ARP,DHCP..etc.
*/
if (ipaddr == INADDR_ANY)
return (B_TRUE);
for (i = 0; i < protect->mp_ipaddrcnt; i++) {
if (protect->mp_ipaddrs[i] == ipaddr)
return (B_TRUE);
}
return (B_FALSE);
}
/*
* Enforce ip-nospoof protection. Only IPv4 is supported for now.
*/
static int
ipnospoof_check(mac_client_impl_t *mcip, mac_protect_t *protect,
mblk_t *mp, mac_header_info_t *mhip)
{
uint32_t sap = mhip->mhi_bindsap;
uchar_t *start = mp->b_rptr + mhip->mhi_hdrsize;
int err = EINVAL;
/*
* This handles the case where the mac header is not in
* the same mblk as the IP header.
*/
if (start == mp->b_wptr) {
mp = mp->b_cont;
/*
* IP header missing. Let the packet through.
*/
if (mp == NULL)
return (0);
start = mp->b_rptr;
}
switch (sap) {
case ETHERTYPE_IP: {
ipha_t *ipha = (ipha_t *)start;
if (start + sizeof (ipha_t) > mp->b_wptr || !OK_32PTR(start))
goto fail;
if (!ipnospoof_check_ips(protect, ipha->ipha_src))
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) > mp->b_wptr)
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 > mp->b_wptr)
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_ips(protect, spaddr))
goto fail;
break;
}
default:
break;
}
return (0);
fail:
/* increment ipnospoof stat here */
return (err);
}
/*
* 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) {
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)) {
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) {
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) {
DTRACE_PROBE2(ip__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)
{
mac_client_impl_t *mcip = (mac_client_impl_t *)mch;
mac_resource_props_t *mrp = MCIP_RESOURCE_PROPS(mcip);
ASSERT(mrp != NULL);
return ((mrp->mrp_protect.mp_types & type) != 0);
}
/*
* Sanity-checks parameters given by userland.
*/
int
mac_protect_validate(mac_resource_props_t *mrp)
{
mac_protect_t *p = &mrp->mrp_protect;
/* 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);
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);
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);
}
/*
* 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);
mac_update_resources(mrp, MCIP_RESOURCE_PROPS(mcip), B_FALSE);
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;
}
}
}