#include <sys/types.h>
#include <sys/inttypes.h>
#include <sys/systm.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/debug.h>
#include <sys/ddi.h>
#include <sys/vtrace.h>
#include <inet/sctp_crc32.h>
#include <inet/ip.h>
#include <inet/ip6.h>
extern unsigned int ip_ocsum(ushort_t *, int, unsigned int);
#define mp_len(mp) ((mp)->b_wptr - (mp)->b_rptr)
#define is_odd(p) (((uintptr_t)(p) & 0x1) != 0)
#define is_even(p) (!is_odd(p))
#ifdef ZC_TEST
int noswcksum = 0;
#endif
unsigned int
ip_cksum(mblk_t *mp, int offset, uint_t sum)
{
ushort_t *w;
ssize_t mlen;
int pmlen;
mblk_t *pmp;
dblk_t *dp = mp->b_datap;
ushort_t psum = 0;
#ifdef ZC_TEST
if (noswcksum)
return (0xffff);
#endif
ASSERT(dp);
if (mp->b_cont == NULL) {
w = (ushort_t *)(mp->b_rptr + offset);
if (dp->db_struioflag & STRUIO_IP) {
if ((offset > dp->db_cksumstart) ||
mp->b_wptr != (uchar_t *)(mp->b_rptr +
dp->db_cksumend)) {
dp->db_struioflag &= ~STRUIO_IP;
goto norm;
}
ASSERT(mp->b_wptr == (mp->b_rptr + dp->db_cksumend));
psum = *(ushort_t *)dp->db_struioun.data;
if ((mlen = dp->db_cksumstart - offset) < 0)
mlen = 0;
if (is_odd(mlen))
goto slow;
if (mlen && dp->db_cksumstart != dp->db_cksumstuff &&
dp->db_cksumend != dp->db_cksumstuff) {
sum = ip_ocsum(w, mlen >> 1, sum);
w = (ushort_t *)(mp->b_rptr +
dp->db_cksumstuff);
if (is_odd(w)) {
pmp = mp;
goto slow1;
}
mlen = dp->db_cksumend - dp->db_cksumstuff;
} else if (dp->db_cksumend != dp->db_cksumstuff) {
if (mlen)
mlen += dp->db_cksumend
- dp->db_cksumstuff;
else {
w = (ushort_t *)(mp->b_rptr +
dp->db_cksumstuff);
if (is_odd(w))
goto slow;
mlen = dp->db_cksumend
- dp->db_cksumstuff;
}
} else if (mlen == 0)
return (psum);
if (is_odd(mlen))
goto slow;
sum += psum;
} else {
norm:
mlen = mp->b_wptr - (uchar_t *)w;
if (is_odd(mlen))
goto slow;
}
ASSERT(is_even(w));
ASSERT(is_even(mlen));
return (ip_ocsum(w, mlen >> 1, sum));
}
if (dp->db_struioflag & STRUIO_IP)
psum = *(ushort_t *)dp->db_struioun.data;
slow:
DTRACE_PROBE(ip_cksum_slow);
pmp = 0;
slow1:
mlen = 0;
pmlen = 0;
for (; ; ) {
w = (ushort_t *)(mp->b_rptr + offset);
if (pmp) {
pmp = 0;
mlen = 0;
goto douio;
} else if (dp->db_struioflag & STRUIO_IP) {
if ((offset > dp->db_cksumstart) ||
mp->b_wptr != (uchar_t *)(mp->b_rptr +
dp->db_cksumend)) {
dp->db_struioflag &= ~STRUIO_IP;
goto snorm;
}
ASSERT(mp->b_wptr == (mp->b_rptr + dp->db_cksumend));
if ((mlen = dp->db_cksumstart - offset) < 0)
mlen = 0;
if (mlen && dp->db_cksumstart != dp->db_cksumstuff) {
pmp = mp;
} else {
int odd;
douio:
odd = is_odd(dp->db_cksumstuff -
dp->db_cksumstart);
if (pmlen == -1) {
sum += ((psum << 8) & 0xffff)
| (psum >> 8);
if (odd)
pmlen = 0;
} else {
sum += psum;
if (odd)
pmlen = -1;
}
if (dp->db_cksumend != dp->db_cksumstuff) {
if (mlen)
mlen += dp->db_cksumend
- dp->db_cksumstuff;
else {
w = (ushort_t *)(mp->b_rptr +
dp->db_cksumstuff);
mlen = dp->db_cksumend -
dp->db_cksumstuff;
}
}
}
} else {
snorm:
mlen = mp->b_wptr - (uchar_t *)w;
}
mp = mp->b_cont;
if (mlen > 0 && pmlen == -1) {
#ifdef _LITTLE_ENDIAN
sum += *(uchar_t *)w << 8;
#else
sum += *(uchar_t *)w;
#endif
w = (ushort_t *)((char *)w + 1);
mlen--;
pmlen = 0;
}
if (mlen > 0) {
if (is_even(w)) {
sum = ip_ocsum(w, mlen>>1, sum);
w += mlen>>1;
if (is_odd(mlen)) {
#ifdef _LITTLE_ENDIAN
sum += *(uchar_t *)w;
#else
sum += *(uchar_t *)w << 8;
#endif
pmlen = -1;
}
} else {
ushort_t swsum;
#ifdef _LITTLE_ENDIAN
sum += *(uchar_t *)w;
#else
sum += *(uchar_t *)w << 8;
#endif
mlen--;
w = (ushort_t *)(1 + (uintptr_t)w);
swsum = ip_ocsum(w, mlen>>1, 0);
sum += ((swsum << 8) & 0xffff) | (swsum >> 8);
w += mlen>>1;
if (is_odd(mlen)) {
#ifdef _LITTLE_ENDIAN
sum += *(uchar_t *)w << 8;
#else
sum += *(uchar_t *)w;
#endif
}
else
pmlen = -1;
}
}
offset = 0;
if (! pmp) {
for (; ; ) {
if (mp == 0) {
goto done;
}
if (mp_len(mp))
break;
mp = mp->b_cont;
}
dp = mp->b_datap;
if (dp->db_struioflag & STRUIO_IP)
psum = *(ushort_t *)dp->db_struioun.data;
} else
mp = pmp;
}
done:
sum = (sum & 0xFFFF) + (sum >> 16);
sum = (sum & 0xFFFF) + (sum >> 16);
TRACE_3(TR_FAC_IP, TR_IP_CKSUM_END,
"ip_cksum_end:(%S) type %d (%X)", "ip_cksum", 1, sum);
return (sum);
}
uint32_t
sctp_cksum(mblk_t *mp, int offset)
{
uint32_t crc32;
uchar_t *p = NULL;
crc32 = 0xFFFFFFFF;
p = mp->b_rptr + offset;
crc32 = sctp_crc32(crc32, p, mp->b_wptr - p);
for (mp = mp->b_cont; mp != NULL; mp = mp->b_cont) {
crc32 = sctp_crc32(crc32, mp->b_rptr, MBLKL(mp));
}
crc32 = ~crc32;
return (crc32);
}
uint16_t
ip_csum_hdr(ipha_t *ipha)
{
uint16_t *uph;
uint32_t sum;
int opt_len;
opt_len = (ipha->ipha_version_and_hdr_length & 0xF) -
IP_SIMPLE_HDR_LENGTH_IN_WORDS;
uph = (uint16_t *)ipha;
sum = uph[0] + uph[1] + uph[2] + uph[3] + uph[4] +
uph[5] + uph[6] + uph[7] + uph[8] + uph[9];
if (opt_len > 0) {
do {
sum += uph[10];
sum += uph[11];
uph += 2;
} while (--opt_len);
}
sum = (sum & 0xFFFF) + (sum >> 16);
sum = ~(sum + (sum >> 16)) & 0xFFFF;
if (sum == 0xffff)
sum = 0;
return ((uint16_t)sum);
}
boolean_t
ip_hdr_length_nexthdr_v6(mblk_t *mp, ip6_t *ip6h, uint16_t *hdr_length_ptr,
uint8_t **nexthdrpp)
{
uint16_t length;
uint_t ehdrlen;
uint8_t *nexthdrp;
uint8_t *whereptr;
uint8_t *endptr;
ip6_dest_t *desthdr;
ip6_rthdr_t *rthdr;
ip6_frag_t *fraghdr;
if (IPH_HDR_VERSION(ip6h) != IPV6_VERSION)
return (B_FALSE);
length = IPV6_HDR_LEN;
whereptr = ((uint8_t *)&ip6h[1]);
endptr = mp->b_wptr;
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;
break;
case IPPROTO_NONE:
default:
*hdr_length_ptr = length;
if (nexthdrpp != NULL)
*nexthdrpp = nexthdrp;
return (B_TRUE);
}
length += ehdrlen;
whereptr += ehdrlen;
*hdr_length_ptr = length;
if (nexthdrpp != NULL)
*nexthdrpp = nexthdrp;
}
switch (*nexthdrp) {
case IPPROTO_HOPOPTS:
case IPPROTO_DSTOPTS:
case IPPROTO_ROUTING:
case IPPROTO_FRAGMENT:
return (B_FALSE);
default:
*hdr_length_ptr = length;
if (nexthdrpp != NULL)
*nexthdrpp = nexthdrp;
return (B_TRUE);
}
}