#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/disp.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/policy.h>
#include <sys/tsol/label_macro.h>
#include <sys/tsol/tndb.h>
#include <sys/tsol/tnet.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <inet/tcp.h>
#include <inet/ipclassifier.h>
#include <inet/ip_ire.h>
#include <inet/ip_ftable.h>
static int
tsol2cipso_tt1(const bslabel_t *sl, unsigned char *cop, uint32_t doi)
{
struct cipso_tag_type_1 *tt1;
const _bslabel_impl_t *bsl;
const uchar_t *ucp;
int i;
if (doi == 0)
return (0);
if (blequal(sl, label2bslabel(l_admin_high)))
return (0);
bsl = (const _bslabel_impl_t *)sl;
if (LCLASS(bsl) & 0xFF00)
return (0);
if (ntohl(bsl->compartments.c8) & 0x0000FFFF)
return (0);
ucp = (const uchar_t *)&bsl->compartments.c8 + 2;
while (--ucp >= (const uchar_t *)&bsl->compartments.c1)
if (*ucp != 0)
break;
i = ucp - (const uchar_t *)&bsl->compartments.c1 + 1;
if (cop == NULL)
return (10 + i);
doi = htonl(doi);
ucp = (const uchar_t *)&doi;
cop[IPOPT_OPTVAL] = IPOPT_COMSEC;
cop[IPOPT_OLEN] = 10 + i;
cop[IPOPT_OLEN+1] = ucp[0];
cop[IPOPT_OLEN+2] = ucp[1];
cop[IPOPT_OLEN+3] = ucp[2];
cop[IPOPT_OLEN+4] = ucp[3];
tt1 = (struct cipso_tag_type_1 *)&cop[IPOPT_OLEN + 5];
tt1->tag_type = 1;
tt1->tag_align = 0;
tt1->tag_sl = LCLASS(bsl);
tt1->tag_length = 4 + i;
bcopy(&bsl->compartments.c1, tt1->tag_cat, i);
return (cop[IPOPT_OLEN]);
}
boolean_t
tsol_get_option_v4(mblk_t *mp, tsol_ip_label_t *label_type, uchar_t **buffer)
{
ipha_t *ipha;
uchar_t *opt;
uint32_t totallen;
uint32_t optval;
uint32_t optlen;
*label_type = OPT_NONE;
ipha = (ipha_t *)mp->b_rptr;
totallen = ipha->ipha_version_and_hdr_length -
(uint8_t)((IP_VERSION << 4));
totallen <<= 2;
if (totallen < IP_SIMPLE_HDR_LENGTH || totallen > MBLKL(mp))
return (B_FALSE);
totallen -= IP_SIMPLE_HDR_LENGTH;
if (totallen == 0)
return (B_TRUE);
opt = (uchar_t *)&ipha[1];
while (totallen != 0) {
switch (optval = opt[IPOPT_OPTVAL]) {
case IPOPT_EOL:
return (B_TRUE);
case IPOPT_NOP:
optlen = 1;
break;
default:
if (totallen <= IPOPT_OLEN)
return (B_FALSE);
optlen = opt[IPOPT_OLEN];
if (optlen < 2)
return (B_FALSE);
}
if (optlen > totallen)
return (B_FALSE);
switch (optval) {
case IPOPT_COMSEC:
if (TSOL_CIPSO_TAG_OFFSET < optlen &&
opt[TSOL_CIPSO_TAG_OFFSET] == 1) {
*label_type = OPT_CIPSO;
*buffer = opt;
return (B_TRUE);
}
return (B_FALSE);
}
totallen -= optlen;
opt += optlen;
}
return (B_TRUE);
}
boolean_t
tsol_get_option_v6(mblk_t *mp, tsol_ip_label_t *label_type, uchar_t **buffer)
{
uchar_t *opt_ptr = NULL;
uchar_t *after_secopt;
boolean_t hbh_needed;
const uchar_t *ip6hbh;
size_t optlen;
uint32_t doi;
const ip6_t *ip6h;
*label_type = OPT_NONE;
*buffer = NULL;
ip6h = (const ip6_t *)mp->b_rptr;
if (ip6h->ip6_nxt != IPPROTO_HOPOPTS)
return (B_TRUE);
ip6hbh = (const uchar_t *)&ip6h[1];
if (ip6hbh + MIN_EHDR_LEN > mp->b_wptr)
return (B_FALSE);
optlen = (ip6hbh[1] + 1) << 3;
if (ip6hbh + optlen > mp->b_wptr)
return (B_FALSE);
if (!tsol_find_secopt_v6(ip6hbh, optlen,
&opt_ptr, &after_secopt, &hbh_needed))
return (B_FALSE);
if (opt_ptr != NULL) {
if ((optlen = opt_ptr[1]) < 8)
return (B_FALSE);
opt_ptr += 2;
bcopy(opt_ptr, &doi, sizeof (doi));
doi = ntohl(doi);
if (doi == IP6LS_DOI_V4 &&
opt_ptr[4] == IP6LS_TT_V4 &&
opt_ptr[5] <= optlen - 4 &&
opt_ptr[7] <= optlen - 6 &&
opt_ptr[7] <= opt_ptr[5] - 2) {
opt_ptr += sizeof (doi) + 2;
*label_type = OPT_CIPSO;
*buffer = opt_ptr;
return (B_TRUE);
}
return (B_FALSE);
}
return (B_TRUE);
}
int
tsol_check_dest(const ts_label_t *tsl, const void *dst,
uchar_t version, uint_t mac_mode, boolean_t zone_is_global,
ts_label_t **effective_tsl)
{
ts_label_t *newtsl = NULL;
tsol_tpc_t *dst_rhtp;
if (effective_tsl != NULL)
*effective_tsl = NULL;
ASSERT(version == IPV4_VERSION ||
(version == IPV6_VERSION &&
!IN6_IS_ADDR_V4MAPPED((in6_addr_t *)dst)));
if (tsl == NULL) {
DTRACE_PROBE2(tx__tnopt__log__info__labeling__mac__allownull,
char *, "destination ip(1) with null label was passed",
ipaddr_t, dst);
return (0);
}
if (tsl->tsl_flags & TSLF_IMPLICIT_IN) {
DTRACE_PROBE3(tx__tnopt__log__info__labeling__unresolved__label,
char *,
"implicit-in packet to ip(1) reached tsol_check_dest "
"with implied security label sl(2)",
ipaddr_t, dst, ts_label_t *, tsl);
}
if (version == IPV4_VERSION &&
CLASSD(*(ipaddr_t *)dst)) {
DTRACE_PROBE2(tx__tnopt__log__info__labeling__mac__allowmult,
char *, "destination ip(1) with multicast dest was passed",
ipaddr_t, dst);
return (0);
} else if (version == IPV6_VERSION &&
IN6_IS_ADDR_MULTICAST((in6_addr_t *)dst)) {
DTRACE_PROBE2(tx__tnopt__log__info__labeling__mac__allowmult_v6,
char *, "destination ip(1) with multicast dest was passed",
in6_addr_t *, dst);
return (0);
}
if ((dst_rhtp = find_tpc(dst, version, B_FALSE)) == NULL) {
DTRACE_PROBE2(tx__tnopt__log__info__labeling__lookupdst,
char *, "destination ip(1) not in tn database.",
void *, dst);
return (EHOSTUNREACH);
}
switch (dst_rhtp->tpc_tp.host_type) {
case UNLABELED:
if (dst_rhtp->tpc_tp.tp_doi != tsl->tsl_doi) {
DTRACE_PROBE4(tx__tnopt__log__info__labeling__doi,
char *, "unlabeled dest ip(1)/tpc(2) doi does "
"not match msg label(3) doi.", void *, dst,
tsol_tpc_t *, dst_rhtp, ts_label_t *, tsl);
TPC_RELE(dst_rhtp);
return (EHOSTUNREACH);
}
if (!blequal(&dst_rhtp->tpc_tp.tp_def_label,
&tsl->tsl_label)) {
if (mac_mode != CONN_MAC_AWARE ||
!(zone_is_global ||
bldominates(&tsl->tsl_label,
&dst_rhtp->tpc_tp.tp_def_label))) {
DTRACE_PROBE4(
tx__tnopt__log__info__labeling__mac,
char *, "unlabeled dest ip(1)/tpc(2) does "
"not match msg label(3).", void *, dst,
tsol_tpc_t *, dst_rhtp, ts_label_t *, tsl);
TPC_RELE(dst_rhtp);
return (EHOSTUNREACH);
}
if ((newtsl = labelalloc(&dst_rhtp->tpc_tp.tp_def_label,
dst_rhtp->tpc_tp.tp_doi, KM_NOSLEEP)) == NULL) {
TPC_RELE(dst_rhtp);
return (ENOMEM);
}
newtsl->tsl_flags |= TSLF_UNLABELED;
} else if (!(tsl->tsl_flags & TSLF_UNLABELED)) {
if ((newtsl = labeldup(tsl, KM_NOSLEEP)) == NULL) {
TPC_RELE(dst_rhtp);
return (ENOMEM);
}
newtsl->tsl_flags |= TSLF_UNLABELED;
}
break;
case SUN_CIPSO:
if (dst_rhtp->tpc_tp.tp_cipso_doi_cipso != tsl->tsl_doi ||
(!_blinrange(&tsl->tsl_label,
&dst_rhtp->tpc_tp.tp_sl_range_cipso) &&
!blinlset(&tsl->tsl_label,
dst_rhtp->tpc_tp.tp_sl_set_cipso))) {
DTRACE_PROBE4(tx__tnopt__log__info__labeling__mac,
char *, "labeled dest ip(1)/tpc(2) does not "
"match msg label(3).", void *, dst,
tsol_tpc_t *, dst_rhtp, ts_label_t *, tsl);
TPC_RELE(dst_rhtp);
return (EHOSTUNREACH);
}
if ((tsl->tsl_flags & TSLF_UNLABELED) ||
(mac_mode == CONN_MAC_IMPLICIT)) {
if ((newtsl = labeldup(tsl, KM_NOSLEEP)) == NULL) {
TPC_RELE(dst_rhtp);
return (ENOMEM);
}
newtsl->tsl_flags &= ~TSLF_UNLABELED;
if (mac_mode == CONN_MAC_IMPLICIT)
newtsl->tsl_flags |= TSLF_IMPLICIT_OUT;
}
break;
default:
TPC_RELE(dst_rhtp);
return (EHOSTUNREACH);
}
if (newtsl != NULL) {
if (effective_tsl != NULL)
*effective_tsl = newtsl;
else
label_rele(newtsl);
}
TPC_RELE(dst_rhtp);
return (0);
}
int
tsol_compute_label_v4(const ts_label_t *tsl, zoneid_t zoneid, ipaddr_t dst,
uchar_t *opt_storage, ip_stack_t *ipst)
{
uint_t sec_opt_len;
ire_t *ire;
tsol_ire_gw_secattr_t *attrp = NULL;
if (opt_storage != NULL)
opt_storage[IPOPT_OLEN] = 0;
if (tsl == NULL)
return (0);
if (CLASSD(dst))
return (0);
if (tsl->tsl_flags & TSLF_IMPLICIT_OUT)
return (0);
if (tsl->tsl_flags & TSLF_UNLABELED) {
ire = ire_route_recursive_v4(dst, 0, NULL, zoneid, tsl,
MATCH_IRE_SECATTR, IRR_ALLOCATE, 0, ipst, NULL, &attrp,
NULL);
ASSERT(ire != NULL);
if (ire->ire_flags & (RTF_REJECT|RTF_BLACKHOLE)) {
ire_refrele(ire);
DTRACE_PROBE3(
tx__tnopt__log__info__labeling__routedst__v4,
char *, "No route to unlabeled dest ip(1) with "
"with label(2).", ipaddr_t, dst, ts_label_t *, tsl);
return (EHOSTUNREACH);
}
if (ire->ire_type & (IRE_BROADCAST | IRE_LOCAL | IRE_LOOPBACK |
IRE_INTERFACE)) {
ire_refrele(ire);
return (0);
}
if (attrp == NULL || attrp->igsa_rhc == NULL ||
attrp->igsa_rhc->rhc_tpc->tpc_tp.host_type == UNLABELED) {
ire_refrele(ire);
return (0);
}
ire_refrele(ire);
}
sec_opt_len = tsol2cipso_tt1(&tsl->tsl_label, opt_storage,
tsl->tsl_doi);
if (sec_opt_len == 0) {
DTRACE_PROBE3(tx__tnopt__log__error__labeling__lostops__v4,
char *, "options lack length for dest ip(1) with label(2).",
ipaddr_t, dst, ts_label_t *, tsl);
return (EINVAL);
}
return (0);
}
int
tsol_remove_secopt(ipha_t *ipha, int buflen)
{
int remlen, olen, oval, delta;
uchar_t *fptr, *tptr;
boolean_t noop_keep;
remlen = IPH_HDR_LENGTH(ipha) - IP_SIMPLE_HDR_LENGTH;
fptr = tptr = (uchar_t *)(ipha + 1);
noop_keep = B_TRUE;
while (remlen > 0) {
oval = fptr[IPOPT_OPTVAL];
if (oval == IPOPT_EOL)
break;
if (oval == IPOPT_NOP) {
if (((fptr - (uchar_t *)ipha) & 3) == 0)
noop_keep = B_TRUE;
if (noop_keep)
*tptr++ = oval;
fptr++;
remlen--;
continue;
}
if (remlen < 2)
return (0);
olen = fptr[IPOPT_OLEN];
if (olen < 2 || olen > remlen)
return (0);
if (oval == IPOPT_COMSEC || oval == IPOPT_SECURITY) {
noop_keep = B_FALSE;
fptr += olen;
remlen -= olen;
continue;
}
noop_keep = B_TRUE;
if (tptr != fptr)
ovbcopy(fptr, tptr, olen);
fptr += olen;
tptr += olen;
remlen -= olen;
}
fptr += remlen;
olen = (tptr - (uchar_t *)ipha) & 3;
if (olen > 0) {
olen = 4 - olen;
bzero(tptr, olen);
tptr += olen;
}
delta = fptr - tptr;
if (delta != 0) {
ovbcopy(fptr, tptr, ((uchar_t *)ipha + buflen) - fptr);
ipha->ipha_version_and_hdr_length -= delta / 4;
}
return (-delta);
}
int
tsol_prepend_option(uchar_t *optbuf, ipha_t *ipha, int buflen)
{
int remlen, padding, lastpad, totlen;
int oval, olen;
int delta;
uchar_t *optr;
uchar_t tempopt[IP_MAX_OPT_LENGTH], *toptr;
if (optbuf[IPOPT_OPTVAL] == IPOPT_EOL ||
optbuf[IPOPT_OPTVAL] == IPOPT_NOP ||
optbuf[IPOPT_OLEN] == 0)
return (0);
ASSERT(optbuf[IPOPT_OLEN] >= 2 &&
optbuf[IPOPT_OLEN] <= IP_MAX_OPT_LENGTH);
remlen = IPH_HDR_LENGTH(ipha) - IP_SIMPLE_HDR_LENGTH;
padding = totlen = lastpad = 0;
optr = (uchar_t *)(ipha + 1);
while (remlen > 0) {
oval = optr[IPOPT_OPTVAL];
if (oval == IPOPT_EOL)
break;
if (oval == IPOPT_NOP) {
optr++;
padding++;
lastpad++;
totlen++;
remlen--;
continue;
}
if (remlen < 2)
return (-1);
olen = optr[IPOPT_OLEN];
if (olen < 2 || olen > remlen)
return (-1);
lastpad = 0;
optr += olen;
totlen += olen;
remlen -= olen;
}
totlen -= lastpad;
padding -= lastpad;
if (padding > 0) {
olen = (optbuf[IPOPT_OLEN] + 3) & ~3;
if (olen + totlen > IP_MAX_OPT_LENGTH) {
totlen -= padding;
if (olen + totlen > IP_MAX_OPT_LENGTH)
return (-1);
padding = 0;
}
}
toptr = tempopt;
olen = optbuf[IPOPT_OLEN];
bcopy(optbuf, toptr, olen);
toptr += olen;
if (padding > 0) {
while ((olen & 3) != 0) {
*toptr++ = IPOPT_NOP;
olen++;
}
}
optr = (uchar_t *)(ipha + 1);
while (totlen > 0) {
oval = optr[IPOPT_OPTVAL];
ASSERT(oval != IPOPT_EOL);
if (oval == IPOPT_NOP) {
if (padding > 0) {
ASSERT(toptr < tempopt + IP_MAX_OPT_LENGTH);
*toptr++ = oval;
}
optr++;
totlen--;
continue;
}
ASSERT(totlen >= 2);
olen = optr[IPOPT_OLEN];
ASSERT(olen >= 2 && olen <= totlen);
ASSERT(toptr + olen <= tempopt + IP_MAX_OPT_LENGTH);
bcopy(optr, toptr, olen);
optr += olen;
toptr += olen;
totlen -= olen;
}
olen = (toptr - tempopt) & 3;
if (olen > 0) {
olen = 4 - olen;
ASSERT(toptr + olen <= tempopt + IP_MAX_OPT_LENGTH);
bzero(toptr, olen);
toptr += olen;
}
olen = (toptr - tempopt) + IP_SIMPLE_HDR_LENGTH;
remlen = IPH_HDR_LENGTH(ipha);
delta = olen - remlen;
if (delta != 0) {
ovbcopy((uchar_t *)ipha + remlen, (uchar_t *)ipha + olen,
buflen - remlen);
ipha->ipha_version_and_hdr_length += delta / 4;
}
bcopy(tempopt, ipha + 1, olen - IP_SIMPLE_HDR_LENGTH);
return (delta);
}
int
tsol_check_label_v4(const ts_label_t *tsl, zoneid_t zoneid, mblk_t **mpp,
uint_t mac_mode, boolean_t zone_is_global, ip_stack_t *ipst,
ts_label_t **effective_tslp)
{
mblk_t *mp = *mpp;
ipha_t *ipha;
ts_label_t *effective_tsl = NULL;
uchar_t opt_storage[IP_MAX_OPT_LENGTH];
uint_t hlen;
uint_t sec_opt_len;
uchar_t *optr;
int delta_remove = 0, delta_add, adjust;
int retv;
*effective_tslp = NULL;
opt_storage[IPOPT_OPTVAL] = 0;
ipha = (ipha_t *)mp->b_rptr;
retv = tsol_check_dest(tsl, &ipha->ipha_dst, IPV4_VERSION,
mac_mode, zone_is_global, &effective_tsl);
if (retv != 0)
return (retv);
if (effective_tsl != NULL) {
if ((retv = tsol_compute_label_v4(effective_tsl, zoneid,
ipha->ipha_dst, opt_storage, ipst)) != 0) {
label_rele(effective_tsl);
return (retv);
}
*effective_tslp = effective_tsl;
} else {
if ((retv = tsol_compute_label_v4(tsl, zoneid,
ipha->ipha_dst, opt_storage, ipst)) != 0) {
return (retv);
}
}
optr = (uchar_t *)(ipha + 1);
hlen = IPH_HDR_LENGTH(ipha) - IP_SIMPLE_HDR_LENGTH;
sec_opt_len = opt_storage[IPOPT_OLEN];
if (hlen >= sec_opt_len) {
if (sec_opt_len == 0 && hlen > 0 &&
optr[IPOPT_OPTVAL] != IPOPT_COMSEC &&
optr[IPOPT_OPTVAL] != IPOPT_SECURITY)
return (0);
if (sec_opt_len != 0 &&
bcmp(opt_storage, optr, sec_opt_len) == 0)
return (0);
}
if (hlen > 0) {
delta_remove = tsol_remove_secopt(ipha, MBLKL(mp));
mp->b_wptr += delta_remove;
}
hlen = IPH_HDR_LENGTH(ipha) + opt_storage[IPOPT_OLEN];
hlen = (hlen + 3) & ~3;
if (hlen > IP_MAX_HDR_LENGTH)
hlen = IP_MAX_HDR_LENGTH;
hlen -= IPH_HDR_LENGTH(ipha);
if (mp->b_wptr + hlen > mp->b_datap->db_lim) {
int copylen;
mblk_t *new_mp;
copylen = MBLKL(mp);
if (copylen > 256)
copylen = 256;
new_mp = allocb_tmpl(hlen + copylen +
(mp->b_rptr - mp->b_datap->db_base), mp);
if (new_mp == NULL) {
if (effective_tsl != NULL) {
label_rele(effective_tsl);
*effective_tslp = NULL;
}
return (ENOMEM);
}
new_mp->b_rptr += mp->b_rptr - mp->b_datap->db_base;
new_mp->b_wptr = new_mp->b_rptr + copylen;
bcopy(mp->b_rptr, new_mp->b_rptr, copylen);
new_mp->b_cont = mp;
if ((mp->b_rptr += copylen) >= mp->b_wptr) {
new_mp->b_cont = mp->b_cont;
freeb(mp);
}
*mpp = mp = new_mp;
ipha = (ipha_t *)mp->b_rptr;
}
delta_add = tsol_prepend_option(opt_storage, ipha, MBLKL(mp));
if (delta_add == -1)
goto param_prob;
ASSERT((mp->b_wptr + delta_add) <= DB_LIM(mp));
mp->b_wptr += delta_add;
adjust = delta_remove + delta_add;
adjust += ntohs(ipha->ipha_length);
ipha->ipha_length = htons(adjust);
return (0);
param_prob:
if (effective_tsl != NULL) {
label_rele(effective_tsl);
*effective_tslp = NULL;
}
return (EINVAL);
}
int
tsol_compute_label_v6(const ts_label_t *tsl, zoneid_t zoneid,
const in6_addr_t *dst, uchar_t *opt_storage, ip_stack_t *ipst)
{
uint_t sec_opt_len;
uint32_t doi;
ire_t *ire;
tsol_ire_gw_secattr_t *attrp = NULL;
if (ip6opt_ls == 0)
return (EINVAL);
if (opt_storage != NULL)
opt_storage[IPOPT_OLEN] = 0;
if (tsl == NULL)
return (0);
if (IN6_IS_ADDR_MULTICAST(dst))
return (0);
if (tsl->tsl_flags & TSLF_IMPLICIT_OUT)
return (0);
if (tsl->tsl_flags & TSLF_UNLABELED) {
ire = ire_route_recursive_v6(dst, 0, NULL, zoneid, tsl,
MATCH_IRE_SECATTR, IRR_ALLOCATE, 0, ipst, NULL, &attrp,
NULL);
ASSERT(ire != NULL);
if (ire->ire_flags & (RTF_REJECT|RTF_BLACKHOLE)) {
ire_refrele(ire);
DTRACE_PROBE3(
tx__tnopt__log__info__labeling__routedst__v6,
char *, "No route to unlabeled dest ip6(1) with "
"label(2).", in6_addr_t *, dst, ts_label_t *, tsl);
return (EHOSTUNREACH);
}
if (ire->ire_type & (IRE_LOCAL | IRE_LOOPBACK |
IRE_INTERFACE)) {
ire_refrele(ire);
return (0);
}
if (attrp == NULL || attrp->igsa_rhc == NULL ||
attrp->igsa_rhc->rhc_tpc->tpc_tp.host_type == UNLABELED) {
ire_refrele(ire);
return (0);
}
ire_refrele(ire);
}
if (opt_storage != NULL)
opt_storage += 8;
sec_opt_len = tsol2cipso_tt1(&tsl->tsl_label, opt_storage,
tsl->tsl_doi);
if (sec_opt_len == 0) {
DTRACE_PROBE3(tx__tnopt__log__error__labeling__lostops__v6,
char *, "options lack length for dest ip6(1) with "
"label(2).", in6_addr_t *, dst, ts_label_t *, tsl);
return (EINVAL);
}
if (opt_storage == NULL)
return (0);
if (sec_opt_len < IP_MAX_OPT_LENGTH)
opt_storage[sec_opt_len] = IPOPT_EOL;
opt_storage[-2] = IP6LS_TT_V4;
opt_storage[-1] = (sec_opt_len + 2 + 1) & ~1;
opt_storage[-8] = ip6opt_ls;
opt_storage[-7] = opt_storage[-1] + 4;
doi = htons(IP6LS_DOI_V4);
bcopy(&doi, opt_storage - 6, 4);
return (0);
}
boolean_t
tsol_find_secopt_v6(
const uchar_t *ip6hbh,
uint_t hbhlen,
uchar_t **secoptp,
uchar_t **after_secoptp,
boolean_t *hbh_needed)
{
uint_t optlen;
uint_t optused;
const uchar_t *optptr;
uchar_t opt_type;
*secoptp = NULL;
*hbh_needed = B_FALSE;
*after_secoptp = NULL;
optlen = hbhlen - 2;
optptr = ip6hbh + 2;
while (optlen != 0) {
opt_type = *optptr;
if (opt_type == IP6OPT_PAD1) {
optptr++;
optlen--;
continue;
}
if (optlen == 1)
return (B_FALSE);
optused = 2 + optptr[1];
if (optused > optlen)
return (B_FALSE);
if (opt_type == ip6opt_ls) {
if (*secoptp != NULL)
return (B_FALSE);
*secoptp = (uchar_t *)optptr;
} else switch (opt_type) {
case IP6OPT_PADN:
break;
default:
*hbh_needed = B_TRUE;
if (*secoptp != NULL) {
*after_secoptp = (uchar_t *)optptr;
return (B_TRUE);
}
break;
}
optlen -= optused;
optptr += optused;
}
return (B_TRUE);
}
int
tsol_remove_secopt_v6(ip6_t *ip6h, int buflen)
{
uchar_t *ip6hbh;
uint_t hbhlen;
uchar_t *secopt = NULL;
uchar_t *after_secopt;
uint_t pad;
uint_t delta;
boolean_t hbh_needed;
if (ip6h->ip6_nxt != IPPROTO_HOPOPTS)
return (0);
ip6hbh = (uchar_t *)&ip6h[1];
hbhlen = (ip6hbh[1] + 1) << 3;
if (!tsol_find_secopt_v6(ip6hbh, hbhlen, &secopt, &after_secopt,
&hbh_needed)) {
ASSERT(0);
return (0);
}
if (secopt == NULL)
return (0);
if (!hbh_needed) {
uchar_t next_hdr;
next_hdr = ip6hbh[0];
ovbcopy(ip6hbh + hbhlen, ip6hbh,
buflen - (IPV6_HDR_LEN + hbhlen));
ip6h->ip6_plen = htons(ntohs(ip6h->ip6_plen) - hbhlen);
ip6h->ip6_nxt = next_hdr;
return (-hbhlen);
}
if (after_secopt == NULL) {
after_secopt = ip6hbh + hbhlen;
}
delta = after_secopt - secopt;
pad = delta % 8;
if (pad == 1) {
secopt[0] = IP6OPT_PAD1;
} else if (pad > 1) {
secopt[0] = IP6OPT_PADN;
secopt[1] = pad - 2;
if (pad > 2)
bzero(&secopt[2], pad - 2);
}
secopt += pad;
delta -= pad;
ovbcopy(after_secopt, secopt,
(uchar_t *)ip6h + buflen - after_secopt);
ip6hbh[1] -= delta/8;
ip6h->ip6_plen = htons(ntohs(ip6h->ip6_plen) - delta);
return (-delta);
}
int
tsol_prepend_option_v6(uchar_t *optbuf, ip6_t *ip6h, int buflen)
{
uint_t rawlen;
uint_t optlen;
uchar_t *ip6hbh;
uint_t hbhlen;
uint_t pad_len;
uchar_t *pad_position;
int delta;
rawlen = optbuf[1] + 2;
ip6hbh = (uchar_t *)&ip6h[1];
if (ip6h->ip6_nxt == IPPROTO_HOPOPTS) {
delta = optlen = (rawlen + 7) & ~7;
pad_len = optlen - rawlen;
pad_position = ip6hbh + 2 + rawlen;
ovbcopy(ip6hbh + 2, ip6hbh + 2 + optlen,
buflen - (IPV6_HDR_LEN + 2));
optlen >>= 3;
if (ip6hbh[1] + optlen > 255)
return (-1);
ip6hbh[1] += optlen;
} else {
delta = hbhlen = (2 + rawlen + 7) & ~7;
pad_len = hbhlen - (2 + rawlen);
pad_position = ip6hbh + 2 + rawlen;
ovbcopy(ip6hbh, ip6hbh + hbhlen, buflen - IPV6_HDR_LEN);
ip6hbh[0] = ip6h->ip6_nxt;
ip6hbh[1] = (hbhlen >> 3) - 1;
ip6h->ip6_nxt = IPPROTO_HOPOPTS;
}
bcopy(optbuf, ip6hbh + 2, rawlen);
if (pad_len == 1) {
pad_position[0] = IP6OPT_PAD1;
} else if (pad_len > 1) {
pad_position[0] = IP6OPT_PADN;
pad_position[1] = pad_len - 2;
if (pad_len > 2)
bzero(pad_position + 2, pad_len - 2);
}
ip6h->ip6_plen = htons(ntohs(ip6h->ip6_plen) + delta);
return (delta);
}
int
tsol_check_label_v6(const ts_label_t *tsl, zoneid_t zoneid, mblk_t **mpp,
uint_t mac_mode, boolean_t zone_is_global, ip_stack_t *ipst,
ts_label_t **effective_tslp)
{
mblk_t *mp = *mpp;
ip6_t *ip6h;
ts_label_t *effective_tsl = NULL;
uchar_t opt_storage[TSOL_MAX_IPV6_OPTION];
uint_t hlen;
uint_t sec_opt_len;
int delta_remove = 0, delta_add;
int retv;
uchar_t *after_secopt;
uchar_t *secopt = NULL;
uchar_t *ip6hbh;
uint_t hbhlen;
boolean_t hbh_needed;
*effective_tslp = NULL;
ip6h = (ip6_t *)mp->b_rptr;
retv = tsol_check_dest(tsl, &ip6h->ip6_dst, IPV6_VERSION,
mac_mode, zone_is_global, &effective_tsl);
if (retv != 0)
return (retv);
if (effective_tsl != NULL) {
if ((retv = tsol_compute_label_v6(effective_tsl, zoneid,
&ip6h->ip6_dst, opt_storage, ipst)) != 0) {
label_rele(effective_tsl);
return (retv);
}
*effective_tslp = effective_tsl;
} else {
if ((retv = tsol_compute_label_v6(tsl, zoneid,
&ip6h->ip6_dst, opt_storage, ipst)) != 0)
return (retv);
}
sec_opt_len = opt_storage[1];
if (ip6h->ip6_nxt == IPPROTO_HOPOPTS) {
ip6hbh = (uchar_t *)&ip6h[1];
hbhlen = (ip6hbh[1] + 1) << 3;
if (!tsol_find_secopt_v6(ip6hbh, hbhlen, &secopt,
&after_secopt, &hbh_needed)) {
ASSERT(0);
return (EACCES);
}
}
if (sec_opt_len == 0 && secopt == NULL) {
return (0);
}
if (secopt != NULL && sec_opt_len != 0 &&
(bcmp(opt_storage, secopt, sec_opt_len + 2) == 0)) {
return (0);
}
if (secopt != NULL) {
delta_remove = tsol_remove_secopt_v6(ip6h, MBLKL(mp));
mp->b_wptr += delta_remove;
}
hlen = (4 + sec_opt_len + 7) & ~7;
if (mp->b_wptr + hlen > mp->b_datap->db_lim) {
int copylen;
mblk_t *new_mp;
uint16_t hdr_len;
hdr_len = ip_hdr_length_v6(mp, ip6h);
copylen = MBLKL(mp);
if (copylen > 256)
copylen = 256;
if (copylen < hdr_len)
copylen = hdr_len;
new_mp = allocb_tmpl(hlen + copylen +
(mp->b_rptr - mp->b_datap->db_base), mp);
if (new_mp == NULL) {
if (effective_tsl != NULL) {
label_rele(effective_tsl);
*effective_tslp = NULL;
}
return (ENOMEM);
}
new_mp->b_rptr += mp->b_rptr - mp->b_datap->db_base;
new_mp->b_wptr = new_mp->b_rptr + copylen;
bcopy(mp->b_rptr, new_mp->b_rptr, copylen);
new_mp->b_cont = mp;
if ((mp->b_rptr += copylen) >= mp->b_wptr) {
new_mp->b_cont = mp->b_cont;
freeb(mp);
}
*mpp = mp = new_mp;
ip6h = (ip6_t *)mp->b_rptr;
}
delta_add = tsol_prepend_option_v6(opt_storage, ip6h, MBLKL(mp));
if (delta_add == -1)
goto param_prob;
ASSERT(mp->b_wptr + delta_add <= DB_LIM(mp));
mp->b_wptr += delta_add;
return (0);
param_prob:
if (effective_tsl != NULL) {
label_rele(effective_tsl);
*effective_tslp = NULL;
}
return (EINVAL);
}