root/usr/src/uts/common/inet/ip/tn_ipopt.c
/*
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#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>

/*
 * This routine takes a sensitivity label as input and creates a CIPSO
 * option in the specified buffer.  It returns the size of the CIPSO option.
 * If the sensitivity label is too large for the CIPSO option, then 0
 * is returned.
 *
 * tsol2cipso_tt1 returns 0 for failure and greater than 0 for success
 * (more accurately, success means a return value between 10 and 40).
 */

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);

        /* check for Admin High sensitivity label */
        if (blequal(sl, label2bslabel(l_admin_high)))
                return (0);

        /* check whether classification will fit in one octet */
        bsl = (const _bslabel_impl_t *)sl;
        if (LCLASS(bsl) & 0xFF00)
                return (0);

        /*
         * Check whether compartments will fit in 30 octets.
         * Compartments 241 - 256 are not allowed.
         */
        if (ntohl(bsl->compartments.c8) & 0x0000FFFF)
                return (0);

        /*
         * Compute option length and tag length.
         * 'p' points to the last two bytes in the Sensitivity Label's
         * compartments; these cannot be mapped into CIPSO compartments.
         */
        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]);
}

/*
 * The following routine searches for a security label in an IPv4 datagram.
 * It returns label_type of:
 *    OPT_CIPSO if a CIPSO IP option is found.
 *    OPT_NONE if no security label is found.
 *
 * If OPT_CIPSO, a pointer to the CIPSO IP option will be returned in
 * the buffer parameter.
 *
 * The function will return with B_FALSE if an IP format error
 * is encountered.
 */

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;

        /*
         * Get length (in 4 byte octets) of IP header options.
         * If header doesn't contain options, then return a label_type
         * of 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);

        /*
         * Search for CIPSO option.
         * If no such option is present, then return OPT_NONE.
         */
        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);
                /*
                 * Copy pointer to option into '*buffer' and
                 * return the option type.
                 */
                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);
}

/*
 * The following routine searches for a security label in an IPv6 datagram.
 * It returns label_type of:
 *    OPT_CIPSO if a CIPSO IP option is found.
 *    OPT_NONE if no security label is found.
 *
 * If OPT_CIPSO, a pointer to the IPv4 portion of the CIPSO IP option will
 * be returned in the buffer parameter.
 *
 * The function will return with B_FALSE if an IP format error
 * or an unexpected label content error is encountered.
 */

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);
        /* tsol_find_secopt_v6 guarantees some sanity */
        if (opt_ptr != NULL) {
                /*
                 * IPv6 Option
                 *   opt_ptr[0]: Option type
                 *   opt_ptr[1]: Length of option data in bytes
                 *   opt_ptr[2]: First byte of option data
                 */
                if ((optlen = opt_ptr[1]) < 8)
                        return (B_FALSE);
                opt_ptr += 2;
                /*
                 * From "Generalized Labeled Security Option for IPv6" draft
                 *   opt_ptr[0] - opt_ptr[4]: DOI = IP6LS_DOI_V4
                 *   opt_ptr[4]: Tag type = IP6LS_TT_V4
                 *   opt_ptr[5]: Tag length in bytes starting at Tag type field
                 * IPv4 CIPSO Option
                 *   opt_ptr[6]: option type
                 *   opt_ptr[7]: option length in bytes starting at type field
                 */
                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);
}

/*
 * tsol_check_dest()
 *
 * This routine verifies if a destination is allowed to recieve messages
 * based on the security label. If any adjustments to the label are needed
 * due to the connection's MAC mode or the destination's ability
 * to receive labels, an "effective label" will be returned.
 *
 * zone_is_global is set if the actual zoneid is global. That is, it is
 * not set for an exclusive-IP zone.
 *
 * On successful return, effective_tsl will point to the new label needed
 * or will be NULL if a new label isn't needed. On error, effective_tsl will
 * point to NULL.
 *
 * Returns:
 *      0               Label (was|is now) correct
 *      EHOSTUNREACH    The label failed the remote host accreditation
 *      ENOMEM          Memory allocation failure
 */
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)));

        /* Always pass kernel level communication (NULL label) */
        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);
        }

        /* Always pass multicast */
        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);
        }

        /* Never pass an undefined destination */
        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:
                /*
                 * Can talk to unlabeled hosts if
                 * (1) zone's label matches the default label, or
                 * (2) SO_MAC_EXEMPT is on and we
                 * dominate the peer's label, or
                 * (3) SO_MAC_EXEMPT is on and
                 * this is the global zone
                 */
                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);
                        }
                        /*
                         * This is a downlabel MAC-exempt exchange.
                         * Use the remote destination's default label
                         * as the label of the message data.
                         */
                        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)) {
                        /*
                         * The security labels are the same but we need
                         * to flag that the remote node is unlabeled.
                         */
                        if ((newtsl = labeldup(tsl, KM_NOSLEEP)) == NULL) {
                                TPC_RELE(dst_rhtp);
                                return (ENOMEM);
                        }
                        newtsl->tsl_flags |= TSLF_UNLABELED;
                }
                break;

        case SUN_CIPSO:
                /*
                 * Can talk to labeled hosts if zone's label is within target's
                 * label range or set.
                 */
                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)) {
                        /*
                         * Copy label so we can modify the flags
                         */
                        if ((newtsl = labeldup(tsl, KM_NOSLEEP)) == NULL) {
                                TPC_RELE(dst_rhtp);
                                return (ENOMEM);
                        }
                        /*
                         * The security label is a match but we need to
                         * clear the unlabeled flag for this remote node.
                         */
                        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);
        }

        /*
         * Return the new label.
         */
        if (newtsl != NULL) {
                if (effective_tsl != NULL)
                        *effective_tsl = newtsl;
                else
                        label_rele(newtsl);
        }
        TPC_RELE(dst_rhtp);
        return (0);
}

/*
 * tsol_compute_label_v4()
 *
 * This routine computes the IP label that should be on a packet based on the
 * connection and destination information.
 *
 * The zoneid is the IP zoneid (i.e., GLOBAL_ZONEID for exlusive-IP zones).
 *
 * Returns:
 *      0               Fetched label
 *      EHOSTUNREACH    No route to destination
 *      EINVAL          Label cannot be computed
 */
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);

        /* always pass multicast */
        if (CLASSD(dst))
                return (0);

        if (tsl->tsl_flags & TSLF_IMPLICIT_OUT)
                return (0);

        if (tsl->tsl_flags & TSLF_UNLABELED) {
                /*
                 * The destination is unlabeled. Only add a label if the
                 * destination is not a broadcast/local/loopback address,
                 * the destination is not on the same subnet, and the
                 * next-hop gateway is labeled.
                 */
                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)) {
                        /* no route to destination */
                        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);
                }

                /*
                 * ire_route_recursive gives us the first attrp it finds
                 * in the recursive lookup.
                 */
                /*
                 * Return now if next hop gateway is unlabeled. There is
                 * no need to generate a CIPSO option for this message.
                 */
                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);
        }

        /* compute the CIPSO option */
        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);
}

/*
 * Remove any existing security option (CIPSO) from the given IP
 * header, move the 'buflen' bytes back to fill the gap, and return the number
 * of bytes removed (as zero or negative number).  Assumes that the headers are
 * sane.
 *
 * Note that tsol_remove_secopt does not adjust ipha_length but
 * tsol_remove_secopt_v6 does adjust ip6_plen.
 */
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];

                /* terminate on end of list */
                if (oval == IPOPT_EOL)
                        break;

                /*
                 * Delete any no-ops following a deleted option, at least up
                 * to a 4 octet alignment; copy others.
                 */
                if (oval == IPOPT_NOP) {
                        if (((fptr - (uchar_t *)ipha) & 3) == 0)
                                noop_keep = B_TRUE;
                        if (noop_keep)
                                *tptr++ = oval;
                        fptr++;
                        remlen--;
                        continue;
                }

                /* stop on corrupted list; just do nothing. */
                if (remlen < 2)
                        return (0);
                olen = fptr[IPOPT_OLEN];
                if (olen < 2 || olen > remlen)
                        return (0);

                /* skip over security options to delete them */
                if (oval == IPOPT_COMSEC || oval == IPOPT_SECURITY) {
                        noop_keep = B_FALSE;
                        fptr += olen;
                        remlen -= olen;
                        continue;
                }

                /* copy the rest */
                noop_keep = B_TRUE;
                if (tptr != fptr)
                        ovbcopy(fptr, tptr, olen);
                fptr += olen;
                tptr += olen;
                remlen -= olen;
        }

        fptr += remlen;

        /* figure how much padding we'll need for header alignment */
        olen = (tptr - (uchar_t *)ipha) & 3;
        if (olen > 0) {
                olen = 4 - olen;
                /* pad with end-of-list */
                bzero(tptr, olen);
                tptr += olen;
        }

        /* slide back the headers that follow and update the IP header */
        delta = fptr - tptr;
        if (delta != 0) {
                ovbcopy(fptr, tptr, ((uchar_t *)ipha + buflen) - fptr);
                ipha->ipha_version_and_hdr_length -= delta / 4;
        }
        return (-delta);
}

/*
 * Insert the option in 'optbuf' into the IP header pointed to by 'ipha', and
 * move the data following the IP header (up to buflen) to accomodate the new
 * option.  Assumes that up to IP_MAX_OPT_LENGTH bytes are available (in total)
 * for IP options.  Returns the number of bytes actually inserted, or -1 if the
 * option cannot be inserted.  (Note that negative return values are possible
 * when noops must be compressed, and that only -1 indicates error.  Successful
 * return value is always evenly divisible by 4, by definition.)
 *
 * Note that tsol_prepend_option does not adjust ipha_length but
 * tsol_prepend_option_v6 does adjust ip6_plen.
 */
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);

        /* first find the real (unpadded) length of the existing options */
        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];

                /* stop at end of list */
                if (oval == IPOPT_EOL)
                        break;

                /* skip no-ops, noting that length byte isn't present */
                if (oval == IPOPT_NOP) {
                        optr++;
                        padding++;
                        lastpad++;
                        totlen++;
                        remlen--;
                        continue;
                }

                /* give up on a corrupted list; report failure */
                if (remlen < 2)
                        return (-1);
                olen = optr[IPOPT_OLEN];
                if (olen < 2 || olen > remlen)
                        return (-1);

                lastpad = 0;
                optr += olen;
                totlen += olen;
                remlen -= olen;
        }

        /* completely ignore any trailing padding */
        totlen -= lastpad;
        padding -= lastpad;

        /*
         * If some sort of inter-option alignment was present, try to preserve
         * that alignment.  If alignment pushes us out past the maximum, then
         * discard it and try to compress to fit.  (We just "assume" that any
         * padding added was attempting to get 32 bit alignment.  If that's
         * wrong, that's just too bad.)
         */
        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;
                }
        }

        /*
         * Since we may need to compress or expand the option list, we write to
         * a temporary buffer and then copy the results back to the IP header.
         */
        toptr = tempopt;

        /* compute actual option to insert */
        olen = optbuf[IPOPT_OLEN];
        bcopy(optbuf, toptr, olen);
        toptr += olen;
        if (padding > 0) {
                while ((olen & 3) != 0) {
                        *toptr++ = IPOPT_NOP;
                        olen++;
                }
        }

        /* copy over the existing options */
        optr = (uchar_t *)(ipha + 1);
        while (totlen > 0) {
                oval = optr[IPOPT_OPTVAL];

                /* totlen doesn't include end-of-list marker */
                ASSERT(oval != IPOPT_EOL);

                /* handle no-ops; copy if desired, ignore otherwise */
                if (oval == IPOPT_NOP) {
                        if (padding > 0) {
                                /* note: cannot overflow due to checks above */
                                ASSERT(toptr < tempopt + IP_MAX_OPT_LENGTH);
                                *toptr++ = oval;
                        }
                        optr++;
                        totlen--;
                        continue;
                }

                /* list cannot be corrupt at this point */
                ASSERT(totlen >= 2);
                olen = optr[IPOPT_OLEN];
                ASSERT(olen >= 2 && olen <= totlen);

                /* cannot run out of room due to tests above */
                ASSERT(toptr + olen <= tempopt + IP_MAX_OPT_LENGTH);

                bcopy(optr, toptr, olen);
                optr += olen;
                toptr += olen;
                totlen -= olen;
        }

        /* figure how much padding we'll need for header alignment */
        olen = (toptr - tempopt) & 3;
        if (olen > 0) {
                olen = 4 - olen;
                ASSERT(toptr + olen <= tempopt + IP_MAX_OPT_LENGTH);
                /* pad with end-of-list value */
                bzero(toptr, olen);
                toptr += olen;
        }

        /* move the headers as needed and update IP header */
        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;
        }

        /* slap in the new options */
        bcopy(tempopt, ipha + 1, olen - IP_SIMPLE_HDR_LENGTH);

        return (delta);
}

/*
 * tsol_check_label_v4()
 *
 * This routine computes the IP label that should be on the packet based on the
 * connection and destination information.  It's called by the IP forwarding
 * logic and by ip_output_simple. The ULPs generate the labels before calling
 * conn_ip_output. If any adjustments to
 * the label are needed due to the connection's MAC-exempt status or
 * the destination's ability to receive labels, an "effective label"
 * will be returned.
 *
 * The packet's header is clear before entering IPsec's engine.
 *
 * The zoneid is the IP zoneid (i.e., GLOBAL_ZONEID for exlusive-IP zones).
 * zone_is_global is set if the actual zoneid is global.
 *
 * On successful return, effective_tslp will point to the new label needed
 * or will be NULL if a new label isn't needed. On error, effective_tsl will
 * point to NULL.
 *
 * Returns:
 *      0               Label (was|is now) correct
 *      EACCES          The packet failed the remote host accreditation.
 *      ENOMEM          Memory allocation failure.
 *      EINVAL          Label cannot be computed
 */
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;

        /*
         * Verify the destination is allowed to receive packets at
         * the security label of the message data. tsol_check_dest()
         * may create a new effective label or label flags.
         */
        retv = tsol_check_dest(tsl, &ipha->ipha_dst, IPV4_VERSION,
            mac_mode, zone_is_global, &effective_tsl);
        if (retv != 0)
                return (retv);

        /*
         * Calculate the security label to be placed in the text
         * of the message (if any).
         */
        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 no option is supposed to be there, make sure it's not */
                if (sec_opt_len == 0 && hlen > 0 &&
                    optr[IPOPT_OPTVAL] != IPOPT_COMSEC &&
                    optr[IPOPT_OPTVAL] != IPOPT_SECURITY)
                        return (0);
                /* if the option is there, it's always first */
                if (sec_opt_len != 0 &&
                    bcmp(opt_storage, optr, sec_opt_len) == 0)
                        return (0);
        }

        /*
         * If there is an option there, then it must be the wrong one; delete.
         */
        if (hlen > 0) {
                delta_remove = tsol_remove_secopt(ipha, MBLKL(mp));
                mp->b_wptr += delta_remove;
        }

        /* Make sure we have room for the worst-case addition */
        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;

                /* allocate enough to be meaningful, but not *too* much */
                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);
                }

                /* keep the bias */
                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);
}

/*
 * IPv6 HopOpt extension header for the label option layout:
 *      - One octet giving the type of the 'next extension header'
 *      - Header extension length in 8-byte words, not including the
 *        1st 8 bytes, but including any pad bytes at the end.
 *        Eg. A value of 2 means 16 bytes not including the 1st 8 bytes.
 *      - Followed by TLV encoded IPv6 label option. Option layout is
 *              * One octet, IP6OPT_LS
 *              * One octet option length in bytes of the option data following
 *                the length, but not including any pad bytes at the end.
 *              * Four-octet DOI (IP6LS_DOI_V4)
 *              * One octet suboption, IP6LS_TT_V4
 *              * One octet suboption length in bytes of the suboption
 *                following the suboption length, including the suboption
 *                header length, but not including any pad bytes at the end.
 *      - Pad to make the extension header a multiple of 8 bytes.
 *
 * This function returns the contents of 'IPv6 option structure' in the above.
 * i.e starting from the IP6OPT_LS but not including the pad at the end.
 * The user must prepend two octets (either padding or next header / length)
 * and append padding out to the next 8 octet boundary.
 *
 * The zoneid is the IP zoneid (i.e., GLOBAL_ZONEID for exlusive-IP zones).
 */
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);

        /* Always pass multicast */
        if (IN6_IS_ADDR_MULTICAST(dst))
                return (0);

        /*
         * Fill in a V6 label.  If a new format is added here, make certain
         * that the maximum size of this label is reflected in sys/tsol/tnet.h
         * as TSOL_MAX_IPV6_OPTION.
         */
        if (tsl->tsl_flags & TSLF_IMPLICIT_OUT)
                return (0);

        if (tsl->tsl_flags & TSLF_UNLABELED) {
                /*
                 * The destination is unlabeled. Only add a label if the
                 * destination is not a broadcast/local/loopback address,
                 * the destination is not on the same subnet, and the
                 * next-hop gateway is labeled.
                 */
                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)) {
                        /* no route to destination */
                        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);
                }
                /*
                 * ire_route_recursive gives us the first attrp it finds
                 * in the recursive lookup.
                 */
                /*
                 * Return now if next hop gateway is unlabeled. There is
                 * no need to generate a CIPSO option for this message.
                 */
                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);
        }

        /* compute the CIPSO option */
        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;

        /*
         * Just in case the option length is odd, round it up to the next even
         * multiple.  The IPv6 option definition doesn't like odd numbers for
         * some reason.
         *
         * Length in the overall option header (IP6OPT_LS) does not include the
         * option header itself, but the length in the suboption does include
         * the suboption header.  Thus, when there's just one suboption, the
         * length in the option header is the suboption length plus 4 (for the
         * DOI value).
         */
        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);
}

/*
 * Locate the start of the IP6OPT_LS label option and return it.
 * Also return the start of the next non-pad option in after_secoptp.
 * Usually the label option is the first option at least when packets
 * are generated, but for generality we don't assume that on received packets.
 *
 * The function will return with B_FALSE if an IP format error
 * or an unexpected label content error is encountered.
 */
boolean_t
tsol_find_secopt_v6(
    const uchar_t *ip6hbh,      /* Start of the hop-by-hop extension header */
    uint_t hbhlen,              /* Length of the hop-by-hop extension header */
    uchar_t **secoptp,          /* Location of IP6OPT_LS label option */
    uchar_t **after_secoptp,    /* Non-pad option following the label option */
    boolean_t *hbh_needed)      /* Is hop-by-hop hdr needed w/o label */
{
        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 we get here, ip6opt_ls can
                 * not be 0 because it will always
                 * match the IP6OPT_PAD1 above.
                 * Therefore ip6opt_ls == 0 forces
                 * this test to always fail here.
                 */
                if (opt_type == ip6opt_ls) {
                        if (*secoptp != NULL)
                                /* More than one security option found */
                                return (B_FALSE);
                        *secoptp = (uchar_t *)optptr;
                } else switch (opt_type) {
                case IP6OPT_PADN:
                        break;
                default:
                        /*
                         * There is at least 1 option other than
                         * the label option. So the hop-by-hop header is needed
                         */
                        *hbh_needed = B_TRUE;
                        if (*secoptp != NULL) {
                                *after_secoptp = (uchar_t *)optptr;
                                return (B_TRUE);
                        }
                        break;
                }
                optlen -= optused;
                optptr += optused;
        }
        return (B_TRUE);
}

/*
 * Remove the label option from the hop-by-hop options header if it exists.
 * 'buflen' is the total length of the packet typically b_wptr - b_rptr.
 * Header and data following the label option that is deleted are copied
 * (i.e. slid backward) to the right position, and returns the number
 * of bytes removed (as zero or negative number.)
 *
 * Note that tsol_remove_secopt does not adjust ipha_length but
 * tsol_remove_secopt_v6 does adjust ip6_plen.
 */
int
tsol_remove_secopt_v6(ip6_t *ip6h, int buflen)
{
        uchar_t *ip6hbh;        /* hop-by-hop header */
        uint_t  hbhlen;         /* hop-by-hop extension header length */
        uchar_t *secopt = NULL;
        uchar_t *after_secopt;
        uint_t  pad;
        uint_t  delta;
        boolean_t hbh_needed;

        /*
         * hop-by-hop extension header must appear first, if it does not
         * exist, there is no label option.
         */
        if (ip6h->ip6_nxt != IPPROTO_HOPOPTS)
                return (0);

        ip6hbh = (uchar_t *)&ip6h[1];
        hbhlen = (ip6hbh[1] + 1) << 3;
        /*
         * Locate the start of the label option if it exists and the end
         * of the label option including pads if any.
         */
        if (!tsol_find_secopt_v6(ip6hbh, hbhlen, &secopt, &after_secopt,
            &hbh_needed)) {
                /*
                 * This function should not see invalid messages.
                 * If one occurs, it would indicate either an
                 * option previously verified in the forwarding
                 * path has been corrupted or an option was
                 * incorrectly generated locally.
                 */
                ASSERT(0);
                return (0);
        }
        if (secopt == NULL)
                return (0);
        if (!hbh_needed) {
                uchar_t next_hdr;
                /*
                 * The label option was the only option in the hop-by-hop
                 * header. We don't need the hop-by-hop header itself any
                 * longer.
                 */
                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) {
                /* There is no option following the label option */
                after_secopt = ip6hbh + hbhlen;
        }

        /*
         * After deleting the label option, we need to slide the headers
         * and data back, while still maintaining the same alignment (module 8)
         * for the other options. So we slide the headers and data back only
         * by an integral multiple of 8 bytes, and fill the remaining bytes
         * with pads.
         */
        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);
}

/*
 * 'optbuf' contains a CIPSO label embedded in an IPv6 hop-by-hop option,
 * starting with the IP6OPT_LS option type. The format of this hop-by-hop
 * option is described in the block comment above tsol_compute_label_v6.
 * This function prepends this hop-by-hop option before any other hop-by-hop
 * options in the hop-by-hop header if one already exists, else a new
 * hop-by-hop header is created and stuffed into the packet following
 * the IPv6 header. 'buflen' is the total length of the packet i.e.
 * b_wptr - b_rptr. The caller ensures that there is enough space for the
 * extra option being added. Header and data following the position where
 * the label option is inserted are copied (i.e. slid forward) to the right
 * position.
 *
 * Note that tsol_prepend_option does not adjust ipha_length but
 * tsol_prepend_option_v6 does adjust ip6_plen.
 */
int
tsol_prepend_option_v6(uchar_t *optbuf, ip6_t *ip6h, int buflen)
{
        /*
         * rawlen is the length of the label option in bytes, not including
         * any pads, starting from the IP6OPT_LS (option type) byte.
         */
        uint_t  rawlen;

        uint_t  optlen;         /* rawlen rounded to an 8 byte multiple */
        uchar_t *ip6hbh;        /* start of the hop-by-hop extension header */
        uint_t  hbhlen;         /* Length of the hop-by-hop extension header */
        uint_t  pad_len;
        uchar_t *pad_position;
        int     delta;          /* Actual number of bytes inserted */

        rawlen = optbuf[1] + 2; /* Add 2 for the option type, option length */
        ip6hbh = (uchar_t *)&ip6h[1];
        if (ip6h->ip6_nxt == IPPROTO_HOPOPTS) {
                /*
                 * There is a hop-by-hop header present already. In order to
                 * preserve the alignment of the other options at the existing
                 * value (modulo 8) we need to pad the label option to a
                 * multiple of 8 bytes before prepending it to the other
                 * options. Slide the extension headers and data forward to
                 * accomodate the label option at the start of the hop-by-hop
                 * header
                 */
                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));
                /*
                 * Bump up the hop-by-hop extension header length by
                 * the number of 8-byte words added
                 */
                optlen >>= 3;
                if (ip6hbh[1] + optlen > 255)
                        return (-1);
                ip6hbh[1] += optlen;
        } else {
                /*
                 * There is no hop-by-hop header in the packet. Construct a
                 * new Hop-by-hop extension header (a multiple of 8 bytes).
                 * Slide any other extension headers and data forward to
                 * accomodate this hop-by-hop header
                 */
                delta = hbhlen = (2 + rawlen + 7) & ~7; /* +2 for nxthdr, len */
                pad_len = hbhlen - (2 + rawlen);
                pad_position = ip6hbh + 2 + rawlen;
                ovbcopy(ip6hbh, ip6hbh + hbhlen, buflen - IPV6_HDR_LEN);
                ip6hbh[0] = ip6h->ip6_nxt;
                /*
                 * hop-by-hop extension header length in 8-byte words, not
                 * including the 1st 8 bytes of the hop-by-hop header.
                 */
                ip6hbh[1] = (hbhlen >> 3) - 1;
                ip6h->ip6_nxt = IPPROTO_HOPOPTS;
        }
        /*
         * Copy the label option into the hop-by-hop header and insert any
         * needed pads
         */
        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);
}

/*
 * tsol_check_label_v6()
 *
 * This routine computes the IP label that should be on the packet based on the
 * connection and destination information.  It's called by the IP forwarding
 * logic and by ip_output_simple. The ULPs generate the labels before calling
 * conn_ip_output. If any adjustments to
 * the label are needed due to the connection's MAC-exempt status or
 * the destination's ability to receive labels, an "effective label"
 * will be returned.
 *
 * The packet's header is clear before entering IPsec's engine.
 *
 * The zoneid is the IP zoneid (i.e., GLOBAL_ZONEID for exlusive-IP zones).
 * zone_is_global is set if the actual zoneid is global.
 *
 * On successful return, effective_tslp will point to the new label needed
 * or will be NULL if a new label isn't needed. On error, effective_tsl will
 * point to NULL.
 *
 * Returns:
 *      0               Label (was|is now) correct
 *      EACCES          The packet failed the remote host accreditation.
 *      ENOMEM          Memory allocation failure.
 *      EINVAL          Label cannot be computed
 */
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;
        /*
         * Label option length is limited to IP_MAX_OPT_LENGTH for
         * symmetry with IPv4. Can be relaxed if needed
         */
        uchar_t opt_storage[TSOL_MAX_IPV6_OPTION];
        uint_t hlen;
        uint_t sec_opt_len; /* label option length not including type, 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;

        /*
         * Verify the destination is allowed to receive packets at
         * the security label of the message data. tsol_check_dest()
         * may create a new effective label or label flags.
         */
        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);

        /*
         * Calculate the security label to be placed in the text
         * of the message (if any).
         */
        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)) {
                        /*
                         * This function should not see invalid messages.
                         * If one occurs, it would indicate either an
                         * option previously verified in the forwarding
                         * path has been corrupted or an option was
                         * incorrectly generated locally.
                         */
                        ASSERT(0);
                        return (EACCES);
                }
        }

        if (sec_opt_len == 0 && secopt == NULL) {
                /*
                 * The packet is not supposed to have a label, and it
                 * does not have one currently
                 */
                return (0);
        }

        if (secopt != NULL && sec_opt_len != 0 &&
            (bcmp(opt_storage, secopt, sec_opt_len + 2) == 0)) {
                /* The packet has the correct label already */
                return (0);
        }

        /*
         * If there is an option there, then it must be the wrong one; delete.
         */
        if (secopt != NULL) {
                delta_remove = tsol_remove_secopt_v6(ip6h, MBLKL(mp));
                mp->b_wptr += delta_remove;
        }

        /*
         * Make sure we have room for the worst-case addition. Add 2 bytes for
         * the hop-by-hop ext header's next header and length fields. Add
         * another 2 bytes for the label option type, len and then round
         * up to the next 8-byte multiple.
         */
        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);
                /*
                 * Allocate enough to be meaningful, but not *too* much.
                 * Also all the IPv6 extension headers must be in the same mblk
                 */
                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);
                }

                /* keep the bias */
                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;

        /* tsol_prepend_option_v6 has adjusted ip6_plen */
        return (0);

param_prob:
        if (effective_tsl != NULL) {
                label_rele(effective_tsl);
                *effective_tslp = NULL;
        }
        return (EINVAL);
}