#include <sys/types.h>
#include <sys/mac.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/vlan.h>
#include <inet/ip.h>
#include <inet/ip_impl.h>
#include <inet/ip6.h>
#include <sys/dlpi.h>
#include <sys/sunndi.h>
#include <inet/ipsec_impl.h>
#include <inet/sadb.h>
#include <inet/ipsecesp.h>
#include <inet/ipsecah.h>
#include <inet/inet_hash.h>
static boolean_t
inet_pkthash_ip_hdr_length_v6(ip6_t *ip6h, uint8_t *endptr,
uint16_t *hdr_length, uint8_t *next_hdr, ip6_frag_t **fragp)
{
uint16_t length;
uint_t ehdrlen;
uint8_t *whereptr;
uint8_t *nexthdrp;
ip6_dest_t *desthdr;
ip6_rthdr_t *rthdr;
ip6_frag_t *fraghdr;
if (((uchar_t *)ip6h + IPV6_HDR_LEN) > endptr)
return (B_FALSE);
ASSERT(IPH_HDR_VERSION(ip6h) == IPV6_VERSION);
length = IPV6_HDR_LEN;
whereptr = ((uint8_t *)&ip6h[1]);
if (fragp != NULL)
*fragp = NULL;
nexthdrp = &ip6h->ip6_nxt;
while (whereptr < endptr) {
if (whereptr + MIN_EHDR_LEN > endptr)
break;
switch (*nexthdrp) {
case IPPROTO_HOPOPTS:
case IPPROTO_DSTOPTS:
desthdr = (ip6_dest_t *)whereptr;
ehdrlen = 8 * (desthdr->ip6d_len + 1);
if ((uchar_t *)desthdr + ehdrlen > endptr)
return (B_FALSE);
nexthdrp = &desthdr->ip6d_nxt;
break;
case IPPROTO_ROUTING:
rthdr = (ip6_rthdr_t *)whereptr;
ehdrlen = 8 * (rthdr->ip6r_len + 1);
if ((uchar_t *)rthdr + ehdrlen > endptr)
return (B_FALSE);
nexthdrp = &rthdr->ip6r_nxt;
break;
case IPPROTO_FRAGMENT:
fraghdr = (ip6_frag_t *)whereptr;
ehdrlen = sizeof (ip6_frag_t);
if ((uchar_t *)&fraghdr[1] > endptr)
return (B_FALSE);
nexthdrp = &fraghdr->ip6f_nxt;
if (fragp != NULL)
*fragp = fraghdr;
break;
case IPPROTO_NONE:
default:
*hdr_length = length;
*next_hdr = *nexthdrp;
return (B_TRUE);
}
length += ehdrlen;
whereptr += ehdrlen;
*hdr_length = length;
*next_hdr = *nexthdrp;
}
switch (*nexthdrp) {
case IPPROTO_HOPOPTS:
case IPPROTO_DSTOPTS:
case IPPROTO_ROUTING:
case IPPROTO_FRAGMENT:
return (B_FALSE);
default:
*hdr_length = length;
*next_hdr = *nexthdrp;
return (B_TRUE);
}
}
#define PKT_HASH_2BYTES(x) ((x)[0] ^ (x)[1])
#define PKT_HASH_4BYTES(x) ((x)[0] ^ (x)[1] ^ (x)[2] ^ (x)[3])
#define PKT_HASH_MAC(x) ((x)[0] ^ (x)[1] ^ (x)[2] ^ (x)[3] ^ (x)[4] ^ (x)[5])
uint64_t
inet_pkt_hash(uint_t media, mblk_t *mp, uint8_t policy)
{
struct ether_header *ehp;
uint64_t hash = 0;
uint16_t sap;
uint_t skip_len;
uint8_t proto;
boolean_t ip_fragmented;
if (media != DL_ETHER)
return (0L);
ASSERT(IS_P2ALIGNED(mp->b_rptr, sizeof (uint16_t)));
ASSERT(MBLKL(mp) >= sizeof (struct ether_header));
ehp = (struct ether_header *)mp->b_rptr;
if ((policy & INET_PKT_HASH_L2) != 0) {
uchar_t *mac_src = ehp->ether_shost.ether_addr_octet;
uchar_t *mac_dst = ehp->ether_dhost.ether_addr_octet;
hash = PKT_HASH_MAC(mac_src) ^ PKT_HASH_MAC(mac_dst);
policy &= ~INET_PKT_HASH_L2;
}
if (policy == 0)
goto done;
sap = ntohs(ehp->ether_type);
if (sap == ETHERTYPE_VLAN) {
struct ether_vlan_header *evhp;
mblk_t *newmp = NULL;
skip_len = sizeof (struct ether_vlan_header);
if (MBLKL(mp) < skip_len) {
newmp = msgpullup(mp, -1);
if ((newmp == NULL) || (MBLKL(newmp) < skip_len)) {
goto done;
}
evhp = (struct ether_vlan_header *)newmp->b_rptr;
} else {
evhp = (struct ether_vlan_header *)mp->b_rptr;
}
sap = ntohs(evhp->ether_type);
freemsg(newmp);
} else {
skip_len = sizeof (struct ether_header);
}
if (MBLKL(mp) <= skip_len) {
skip_len -= MBLKL(mp);
mp = mp->b_cont;
if (mp == NULL)
goto done;
}
sap = (sap < ETHERTYPE_802_MIN) ? 0 : sap;
switch (sap) {
case ETHERTYPE_IP: {
ipha_t *iphp;
iphp = (ipha_t *)(mp->b_rptr + skip_len);
if (((unsigned char *)iphp + sizeof (ipha_t) > mp->b_wptr) ||
!OK_32PTR((char *)iphp))
goto done;
proto = iphp->ipha_protocol;
skip_len += IPH_HDR_LENGTH(iphp);
ip_fragmented = ntohs(iphp->ipha_fragment_offset_and_flags) &
IPH_OFFSET;
if (ip_fragmented || (policy & INET_PKT_HASH_L3) != 0) {
uint8_t *ip_src = (uint8_t *)&(iphp->ipha_src);
uint8_t *ip_dst = (uint8_t *)&(iphp->ipha_dst);
hash ^= (PKT_HASH_4BYTES(ip_src) ^
PKT_HASH_4BYTES(ip_dst));
policy &= ~INET_PKT_HASH_L3;
}
if (ip_fragmented) {
uint8_t *identp = (uint8_t *)&iphp->ipha_ident;
hash ^= PKT_HASH_2BYTES(identp);
goto done;
}
break;
}
case ETHERTYPE_IPV6: {
ip6_t *ip6hp;
ip6_frag_t *frag = NULL;
uint16_t hdr_length;
ip6hp = (ip6_t *)(mp->b_rptr + skip_len);
if (((unsigned char *)ip6hp + IPV6_HDR_LEN > mp->b_wptr) ||
!OK_32PTR((char *)ip6hp))
goto done;
if (!inet_pkthash_ip_hdr_length_v6(ip6hp, mp->b_wptr,
&hdr_length, &proto, &frag))
goto done;
skip_len += hdr_length;
if (frag != NULL || (policy & INET_PKT_HASH_L3) != 0) {
uint8_t *ip_src = &(ip6hp->ip6_src.s6_addr8[12]);
uint8_t *ip_dst = &(ip6hp->ip6_dst.s6_addr8[12]);
hash ^= (PKT_HASH_4BYTES(ip_src) ^
PKT_HASH_4BYTES(ip_dst));
policy &= ~INET_PKT_HASH_L3;
}
if (frag != NULL) {
uint8_t *identp = (uint8_t *)&frag->ip6f_ident;
hash ^= PKT_HASH_4BYTES(identp);
goto done;
}
break;
}
default:
goto done;
}
if (policy == 0)
goto done;
if (MBLKL(mp) <= skip_len) {
skip_len -= MBLKL(mp);
mp = mp->b_cont;
if (mp == NULL)
goto done;
}
again:
switch (proto) {
case IPPROTO_TCP:
case IPPROTO_UDP:
case IPPROTO_ESP:
case IPPROTO_SCTP:
if (mp->b_rptr + skip_len + 4 > mp->b_wptr)
goto done;
hash ^= PKT_HASH_4BYTES((mp->b_rptr + skip_len));
break;
case IPPROTO_AH: {
ah_t *ah = (ah_t *)(mp->b_rptr + skip_len);
uint_t ah_length = AH_TOTAL_LEN(ah);
if ((unsigned char *)ah + sizeof (ah_t) > mp->b_wptr)
goto done;
proto = ah->ah_nexthdr;
skip_len += ah_length;
if (MBLKL(mp) <= skip_len) {
skip_len -= MBLKL(mp);
mp = mp->b_cont;
if (mp == NULL)
goto done;
}
goto again;
}
}
done:
return (hash);
}