root/usr/src/uts/common/inet/sctp/sctp_error.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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/types.h>
#include <sys/systm.h>
#include <sys/stream.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/strsubr.h>
#include <sys/tsol/tnet.h>

#include <netinet/in.h>
#include <netinet/ip6.h>

#include <inet/ipsec_impl.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <inet/ipsec_impl.h>
#include <inet/mib2.h>
#include <inet/sctp_ip.h>
#include <inet/ipclassifier.h>
#include <inet/ip_ire.h>
#include "sctp_impl.h"
#include "sctp_asconf.h"

ssize_t
sctp_link_abort(mblk_t *mp, uint16_t serror, char *details, size_t len,
    int iserror, boolean_t tbit)
{
        size_t alen;
        mblk_t *amp;
        sctp_chunk_hdr_t *acp;
        sctp_parm_hdr_t *eph;

        ASSERT(mp != NULL && mp->b_cont == NULL);

        alen = sizeof (*acp) + (serror != 0 ? (sizeof (*eph) + len) : 0);

        amp = allocb(alen, BPRI_MED);
        if (amp == NULL) {
                return (-1);
        }

        amp->b_wptr = amp->b_rptr + alen;

        /* Chunk header */
        acp = (sctp_chunk_hdr_t *)amp->b_rptr;
        acp->sch_id = iserror ? CHUNK_ERROR : CHUNK_ABORT;
        acp->sch_flags = 0;
        acp->sch_len = htons(alen);
        if (tbit)
                SCTP_SET_TBIT(acp);

        linkb(mp, amp);

        if (serror == 0) {
                return (alen);
        }

        eph = (sctp_parm_hdr_t *)(acp + 1);
        eph->sph_type = htons(serror);
        eph->sph_len = htons(len + sizeof (*eph));

        if (len > 0) {
                bcopy(details, eph + 1, len);
        }

        /* XXX pad */

        return (alen);
}

void
sctp_user_abort(sctp_t *sctp, mblk_t *data)
{
        mblk_t *mp;
        int len, hdrlen;
        char *cause;
        sctp_faddr_t *fp = sctp->sctp_current;
        ip_xmit_attr_t  *ixa = fp->sf_ixa;
        sctp_stack_t    *sctps = sctp->sctp_sctps;

        /*
         * Don't need notification if connection is not yet setup,
         * call sctp_clean_death() to reclaim resources.
         * Any pending connect call(s) will error out.
         */
        if (sctp->sctp_state < SCTPS_COOKIE_WAIT) {
                sctp_clean_death(sctp, ECONNABORTED);
                return;
        }

        mp = sctp_make_mp(sctp, fp, 0);
        if (mp == NULL) {
                SCTP_KSTAT(sctps, sctp_send_user_abort_failed);
                return;
        }

        /*
         * Create abort chunk.
         */
        if (data) {
                if (fp->sf_isv4) {
                        hdrlen = sctp->sctp_hdr_len;
                } else {
                        hdrlen = sctp->sctp_hdr6_len;
                }
                hdrlen += sizeof (sctp_chunk_hdr_t) + sizeof (sctp_parm_hdr_t);
                cause = (char *)data->b_rptr;
                len = data->b_wptr - data->b_rptr;

                if (len + hdrlen > fp->sf_pmss) {
                        len = fp->sf_pmss - hdrlen;
                }
        } else {
                cause = NULL;
                len = 0;
        }
        /*
         * Since it is a user abort, we should have the sctp_t and hence
         * the correct verification tag.  So we should not set the T-bit
         * in the ABORT.
         */
        if ((len = sctp_link_abort(mp, SCTP_ERR_USER_ABORT, cause, len, 0,
            B_FALSE)) < 0) {
                freemsg(mp);
                return;
        }
        SCTPS_BUMP_MIB(sctps, sctpAborted);
        BUMP_LOCAL(sctp->sctp_opkts);
        BUMP_LOCAL(sctp->sctp_obchunks);

        sctp_set_iplen(sctp, mp, ixa);
        ASSERT(ixa->ixa_ire != NULL);
        ASSERT(ixa->ixa_cred != NULL);

        (void) conn_ip_output(mp, ixa);

        sctp_assoc_event(sctp, SCTP_COMM_LOST, 0, NULL);
        sctp_clean_death(sctp, ECONNABORTED);
}

/*
 * If iserror == 0, sends an abort. If iserror != 0, sends an error.
 */
void
sctp_send_abort(sctp_t *sctp, uint32_t vtag, uint16_t serror, char *details,
    size_t len, mblk_t *inmp, int iserror, boolean_t tbit, ip_recv_attr_t *ira)
{

        mblk_t          *hmp;
        uint32_t        ip_hdr_len;
        ipha_t          *iniph;
        ipha_t          *ahiph = NULL;
        ip6_t           *inip6h;
        ip6_t           *ahip6h = NULL;
        sctp_hdr_t      *sh;
        sctp_hdr_t      *insh;
        size_t          ahlen;
        uchar_t         *p;
        ssize_t         alen;
        int             isv4;
        conn_t          *connp = sctp->sctp_connp;
        sctp_stack_t    *sctps = sctp->sctp_sctps;
        ip_xmit_attr_t  *ixa;

        isv4 = (IPH_HDR_VERSION(inmp->b_rptr) == IPV4_VERSION);
        if (isv4) {
                ahlen = sctp->sctp_hdr_len;
        } else {
                ahlen = sctp->sctp_hdr6_len;
        }

        /*
         * If this is a labeled system, then check to see if we're allowed to
         * send a response to this particular sender.  If not, then just drop.
         */
        if (is_system_labeled() && !tsol_can_reply_error(inmp, ira))
                return;

        hmp = allocb(sctps->sctps_wroff_xtra + ahlen, BPRI_MED);
        if (hmp == NULL) {
                /* XXX no resources */
                return;
        }

        /* copy in the IP / SCTP header */
        p = hmp->b_rptr + sctps->sctps_wroff_xtra;
        hmp->b_rptr = p;
        hmp->b_wptr = p + ahlen;
        if (isv4) {
                bcopy(sctp->sctp_iphc, p, sctp->sctp_hdr_len);
                /*
                 * Composite is likely incomplete at this point, so pull
                 * info from the incoming IP / SCTP headers.
                 */
                ahiph = (ipha_t *)p;
                iniph = (ipha_t *)inmp->b_rptr;
                ip_hdr_len = IPH_HDR_LENGTH(inmp->b_rptr);

                sh = (sctp_hdr_t *)(p + sctp->sctp_ip_hdr_len);
                ASSERT(OK_32PTR(sh));

                insh = (sctp_hdr_t *)((uchar_t *)iniph + ip_hdr_len);
                ASSERT(OK_32PTR(insh));

                /* Copy in the peer's IP addr */
                ahiph->ipha_dst = iniph->ipha_src;
                ahiph->ipha_src = iniph->ipha_dst;
        } else {
                bcopy(sctp->sctp_iphc6, p, sctp->sctp_hdr6_len);
                ahip6h = (ip6_t *)p;
                inip6h = (ip6_t *)inmp->b_rptr;
                ip_hdr_len = ip_hdr_length_v6(inmp, inip6h);

                sh = (sctp_hdr_t *)(p + sctp->sctp_ip_hdr6_len);
                ASSERT(OK_32PTR(sh));

                insh = (sctp_hdr_t *)((uchar_t *)inip6h + ip_hdr_len);
                ASSERT(OK_32PTR(insh));

                /* Copy in the peer's IP addr */
                ahip6h->ip6_dst = inip6h->ip6_src;
                ahip6h->ip6_src = inip6h->ip6_dst;
        }

        /* Fill in the holes in the SCTP common header */
        sh->sh_sport = insh->sh_dport;
        sh->sh_dport = insh->sh_sport;
        sh->sh_verf = vtag;

        /* Link in the abort chunk */
        if ((alen = sctp_link_abort(hmp, serror, details, len, iserror, tbit))
            < 0) {
                freemsg(hmp);
                return;
        }

        /*
         * Base the transmission on any routing-related socket options
         * that have been set on the listener/connection.
         */
        ixa = conn_get_ixa_exclusive(connp);
        if (ixa == NULL) {
                freemsg(hmp);
                return;
        }
        ixa->ixa_flags &= ~IXAF_VERIFY_PMTU;

        ixa->ixa_pktlen = ahlen + alen;
        if (isv4) {
                ixa->ixa_flags |= IXAF_IS_IPV4;
                ahiph->ipha_length = htons(ixa->ixa_pktlen);
                ixa->ixa_ip_hdr_length = sctp->sctp_ip_hdr_len;
        } else {
                ixa->ixa_flags &= ~IXAF_IS_IPV4;
                ahip6h->ip6_plen = htons(ixa->ixa_pktlen - IPV6_HDR_LEN);
                ixa->ixa_ip_hdr_length = sctp->sctp_ip_hdr6_len;
        }

        SCTPS_BUMP_MIB(sctps, sctpAborted);
        BUMP_LOCAL(sctp->sctp_obchunks);

        if (is_system_labeled() && ixa->ixa_tsl != NULL) {
                ASSERT(ira->ira_tsl != NULL);

                ixa->ixa_tsl = ira->ira_tsl;    /* A multi-level responder */
        }

        if (ira->ira_flags & IRAF_IPSEC_SECURE) {
                /*
                 * Apply IPsec based on how IPsec was applied to
                 * the packet that caused the abort.
                 */
                if (!ipsec_in_to_out(ira, ixa, hmp, ahiph, ahip6h)) {
                        ip_stack_t *ipst = sctps->sctps_netstack->netstack_ip;

                        BUMP_MIB(&ipst->ips_ip_mib, ipIfStatsOutDiscards);
                        /* Note: mp already consumed and ip_drop_packet done */
                        ixa_refrele(ixa);
                        return;
                }
        } else {
                ixa->ixa_flags |= IXAF_NO_IPSEC;
        }

        BUMP_LOCAL(sctp->sctp_opkts);
        BUMP_LOCAL(sctp->sctp_obchunks);

        (void) ip_output_simple(hmp, ixa);
        ixa_refrele(ixa);
}

/*
 * OOTB version of the above.
 * If iserror == 0, sends an abort. If iserror != 0, sends an error.
 */
void
sctp_ootb_send_abort(uint32_t vtag, uint16_t serror, char *details,
    size_t len, const mblk_t *inmp, int iserror, boolean_t tbit,
    ip_recv_attr_t *ira, ip_stack_t *ipst)
{
        uint32_t        ip_hdr_len;
        size_t          ahlen;
        ipha_t          *ipha = NULL;
        ip6_t           *ip6h = NULL;
        sctp_hdr_t      *insctph;
        int             i;
        uint16_t        port;
        ssize_t         alen;
        int             isv4;
        mblk_t          *mp;
        netstack_t      *ns = ipst->ips_netstack;
        sctp_stack_t    *sctps = ns->netstack_sctp;
        ip_xmit_attr_t  ixas;

        bzero(&ixas, sizeof (ixas));

        isv4 = (IPH_HDR_VERSION(inmp->b_rptr) == IPV4_VERSION);
        ip_hdr_len = ira->ira_ip_hdr_length;
        ahlen = ip_hdr_len + sizeof (sctp_hdr_t);

        /*
         * If this is a labeled system, then check to see if we're allowed to
         * send a response to this particular sender.  If not, then just drop.
         */
        if (is_system_labeled() && !tsol_can_reply_error(inmp, ira))
                return;

        mp = allocb(ahlen + sctps->sctps_wroff_xtra, BPRI_MED);
        if (mp == NULL) {
                return;
        }
        mp->b_rptr += sctps->sctps_wroff_xtra;
        mp->b_wptr = mp->b_rptr + ahlen;
        bcopy(inmp->b_rptr, mp->b_rptr, ahlen);

        /*
         * We follow the logic in tcp_xmit_early_reset() in that we skip
         * reversing source route (i.e. replace all IP options with EOL).
         */
        if (isv4) {
                ipaddr_t        v4addr;

                ipha = (ipha_t *)mp->b_rptr;
                for (i = IP_SIMPLE_HDR_LENGTH; i < (int)ip_hdr_len; i++)
                        mp->b_rptr[i] = IPOPT_EOL;
                /* Swap addresses */
                ipha->ipha_length = htons(ahlen);
                v4addr = ipha->ipha_src;
                ipha->ipha_src = ipha->ipha_dst;
                ipha->ipha_dst = v4addr;
                ipha->ipha_ident = 0;
                ipha->ipha_ttl = (uchar_t)sctps->sctps_ipv4_ttl;

                ixas.ixa_flags = IXAF_BASIC_SIMPLE_V4;
        } else {
                in6_addr_t      v6addr;

                ip6h = (ip6_t *)mp->b_rptr;
                /* Remove any extension headers assuming partial overlay */
                if (ip_hdr_len > IPV6_HDR_LEN) {
                        uint8_t *to;

                        to = mp->b_rptr + ip_hdr_len - IPV6_HDR_LEN;
                        ovbcopy(ip6h, to, IPV6_HDR_LEN);
                        mp->b_rptr += ip_hdr_len - IPV6_HDR_LEN;
                        ip_hdr_len = IPV6_HDR_LEN;
                        ip6h = (ip6_t *)mp->b_rptr;
                        ip6h->ip6_nxt = IPPROTO_SCTP;
                        ahlen = ip_hdr_len + sizeof (sctp_hdr_t);
                }
                ip6h->ip6_plen = htons(ahlen - IPV6_HDR_LEN);
                v6addr = ip6h->ip6_src;
                ip6h->ip6_src = ip6h->ip6_dst;
                ip6h->ip6_dst = v6addr;
                ip6h->ip6_hops = (uchar_t)sctps->sctps_ipv6_hoplimit;

                ixas.ixa_flags = IXAF_BASIC_SIMPLE_V6;
                if (IN6_IS_ADDR_LINKSCOPE(&ip6h->ip6_dst)) {
                        ixas.ixa_flags |= IXAF_SCOPEID_SET;
                        ixas.ixa_scopeid = ira->ira_ruifindex;
                }
        }
        insctph = (sctp_hdr_t *)(mp->b_rptr + ip_hdr_len);

        /* Swap ports.  Verification tag is reused. */
        port = insctph->sh_sport;
        insctph->sh_sport = insctph->sh_dport;
        insctph->sh_dport = port;
        insctph->sh_verf = vtag;

        /* Link in the abort chunk */
        if ((alen = sctp_link_abort(mp, serror, details, len, iserror, tbit))
            < 0) {
                freemsg(mp);
                return;
        }

        ixas.ixa_pktlen = ahlen + alen;
        ixas.ixa_ip_hdr_length = ip_hdr_len;

        if (isv4) {
                ipha->ipha_length = htons(ixas.ixa_pktlen);
        } else {
                ip6h->ip6_plen = htons(ixas.ixa_pktlen - IPV6_HDR_LEN);
        }

        ixas.ixa_protocol = IPPROTO_SCTP;
        ixas.ixa_zoneid = ira->ira_zoneid;
        ixas.ixa_ipst = ipst;
        ixas.ixa_ifindex = 0;

        SCTPS_BUMP_MIB(sctps, sctpAborted);

        if (is_system_labeled()) {
                ASSERT(ira->ira_tsl != NULL);

                ixas.ixa_tsl = ira->ira_tsl;    /* A multi-level responder */
        }

        if (ira->ira_flags & IRAF_IPSEC_SECURE) {
                /*
                 * Apply IPsec based on how IPsec was applied to
                 * the packet that was out of the blue.
                 */
                if (!ipsec_in_to_out(ira, &ixas, mp, ipha, ip6h)) {
                        BUMP_MIB(&ipst->ips_ip_mib, ipIfStatsOutDiscards);
                        /* Note: mp already consumed and ip_drop_packet done */
                        return;
                }
        } else {
                /*
                 * This is in clear. The abort message we are building
                 * here should go out in clear, independent of our policy.
                 */
                ixas.ixa_flags |= IXAF_NO_IPSEC;
        }

        (void) ip_output_simple(mp, &ixas);
        ixa_cleanup(&ixas);
}

/*ARGSUSED*/
mblk_t *
sctp_make_err(sctp_t *sctp, uint16_t serror, void *details, size_t len)
{

        mblk_t *emp;
        size_t elen;
        sctp_chunk_hdr_t *ecp;
        sctp_parm_hdr_t *eph;
        int pad;

        if ((pad = len % SCTP_ALIGN) != 0) {
                pad = SCTP_ALIGN - pad;
        }

        elen = sizeof (*ecp) + sizeof (*eph) + len;
        emp = allocb(elen + pad, BPRI_MED);
        if (emp == NULL) {
                return (NULL);
        }

        emp->b_wptr = emp->b_rptr + elen + pad;

        /* Chunk header */
        ecp = (sctp_chunk_hdr_t *)emp->b_rptr;
        ecp->sch_id = CHUNK_ERROR;
        ecp->sch_flags = 0;
        ecp->sch_len = htons(elen);

        eph = (sctp_parm_hdr_t *)(ecp + 1);
        eph->sph_type = htons(serror);
        eph->sph_len = htons(len + sizeof (*eph));

        if (len > 0) {
                bcopy(details, eph + 1, len);
        }

        if (pad != 0) {
                bzero((uchar_t *)(eph + 1) + len, pad);
        }

        return (emp);
}

/*
 * Called from sctp_input_data() to add one error chunk to the error
 * chunks list.  The error chunks list will be processed at the end
 * of sctp_input_data() by calling sctp_process_err().
 */
void
sctp_add_err(sctp_t *sctp, uint16_t serror, void *details, size_t len,
    sctp_faddr_t *dest)
{
        sctp_stack_t *sctps = sctp->sctp_sctps;
        mblk_t *emp;
        uint32_t emp_len;
        uint32_t mss;
        mblk_t *sendmp;
        sctp_faddr_t *fp;

        emp = sctp_make_err(sctp, serror, details, len);
        if (emp == NULL)
                return;
        emp_len = MBLKL(emp);
        if (sctp->sctp_err_chunks != NULL) {
                fp = SCTP_CHUNK_DEST(sctp->sctp_err_chunks);
        } else {
                fp = dest;
                SCTP_SET_CHUNK_DEST(emp, dest);
        }
        mss = fp->sf_pmss;

        /*
         * If the current output packet cannot include the new error chunk,
         * send out the current packet and then add the new error chunk
         * to the new output packet.
         */
        if (sctp->sctp_err_len + emp_len > mss) {
                if ((sendmp = sctp_make_mp(sctp, fp, 0)) == NULL) {
                        SCTP_KSTAT(sctps, sctp_send_err_failed);
                        /* Just free the latest error chunk. */
                        freeb(emp);
                        return;
                }
                sendmp->b_cont = sctp->sctp_err_chunks;
                sctp_set_iplen(sctp, sendmp, fp->sf_ixa);
                (void) conn_ip_output(sendmp, fp->sf_ixa);
                BUMP_LOCAL(sctp->sctp_opkts);

                sctp->sctp_err_chunks = emp;
                sctp->sctp_err_len = emp_len;
                SCTP_SET_CHUNK_DEST(emp, dest);
        } else {
                if (sctp->sctp_err_chunks != NULL)
                        linkb(sctp->sctp_err_chunks, emp);
                else
                        sctp->sctp_err_chunks = emp;
                sctp->sctp_err_len += emp_len;
        }
        /* Assume that we will send it out... */
        BUMP_LOCAL(sctp->sctp_obchunks);
}

/*
 * Called from sctp_input_data() to send out error chunks created during
 * the processing of all the chunks in an incoming packet.
 */
void
sctp_process_err(sctp_t *sctp)
{
        sctp_stack_t *sctps = sctp->sctp_sctps;
        mblk_t *errmp;
        mblk_t *sendmp;
        sctp_faddr_t *fp;

        ASSERT(sctp->sctp_err_chunks != NULL);
        errmp = sctp->sctp_err_chunks;
        fp = SCTP_CHUNK_DEST(errmp);
        if ((sendmp = sctp_make_mp(sctp, fp, 0)) == NULL) {
                SCTP_KSTAT(sctps, sctp_send_err_failed);
                freemsg(errmp);
                goto done;
        }
        sendmp->b_cont = errmp;
        sctp_set_iplen(sctp, sendmp, fp->sf_ixa);
        (void) conn_ip_output(sendmp, fp->sf_ixa);
        BUMP_LOCAL(sctp->sctp_opkts);
done:
        sctp->sctp_err_chunks = NULL;
        sctp->sctp_err_len = 0;
}

/*
 * Returns 0 on non-fatal error, otherwise a system error on fatal
 * error.
 */
int
sctp_handle_error(sctp_t *sctp, sctp_hdr_t *sctph, sctp_chunk_hdr_t *ch,
    mblk_t *mp, ip_recv_attr_t *ira)
{
        sctp_parm_hdr_t *errh;
        sctp_chunk_hdr_t *uch;

        if (ch->sch_len == htons(sizeof (*ch))) {
                /* no error cause given */
                return (0);
        }
        errh = (sctp_parm_hdr_t *)(ch + 1);
        sctp_error_event(sctp, ch, B_FALSE);

        switch (errh->sph_type) {
        /*
         * Both BAD_SID and NO_USR_DATA errors
         * indicate a serious bug in our stack,
         * so complain and abort the association.
         */
        case SCTP_ERR_BAD_SID:
                cmn_err(CE_WARN, "BUG! send to invalid SID");
                sctp_send_abort(sctp, sctph->sh_verf, 0, NULL, 0, mp, 0, 0,
                    ira);
                return (ECONNABORTED);
        case SCTP_ERR_NO_USR_DATA:
                cmn_err(CE_WARN, "BUG! no usr data");
                sctp_send_abort(sctp, sctph->sh_verf, 0, NULL, 0, mp, 0, 0,
                    ira);
                return (ECONNABORTED);
        case SCTP_ERR_UNREC_CHUNK:
                /* Pull out the unrecognized chunk type */
                if (ntohs(errh->sph_len) < (sizeof (*errh) + sizeof (*uch))) {
                        /* Not enough to process */
                        return (0);
                }
                uch = (sctp_chunk_hdr_t *)(errh + 1);
                if (uch->sch_id == CHUNK_ASCONF) {
                        /* Turn on ASCONF sending */
                        sctp->sctp_understands_asconf = B_FALSE;
                        /*
                         * Hand off to asconf to clear out the unacked
                         * asconf chunk.
                         */
                        if (ntohs(uch->sch_len) !=
                            (ntohs(errh->sph_len) - sizeof (*errh))) {
                                /* malformed */
                                dprint(0, ("Malformed Unrec Chunk error\n"));
                                return (0);
                        }
                        sctp_asconf_free_cxmit(sctp, uch);
                        return (0);
                }
                /* Else drop it */
                break;
        default:
                break;
        }

        return (0);
}