root/usr/src/uts/common/inet/sctp/sctp_init.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/stream.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

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

#include <inet/common.h>
#include <inet/ipclassifier.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <inet/mib2.h>
#include <inet/nd.h>
#include <inet/optcom.h>
#include <inet/ipclassifier.h>
#include "sctp_impl.h"
#include "sctp_addr.h"

/*ARGSUSED*/
size_t
sctp_supaddr_param_len(sctp_t *sctp)
{
        return (sizeof (sctp_parm_hdr_t) + sizeof (int32_t));
}

size_t
sctp_supaddr_param(sctp_t *sctp, uchar_t *p)
{
        sctp_parm_hdr_t *sph;
        uint16_t *addrtype;
        conn_t          *connp = sctp->sctp_connp;

        sph = (sctp_parm_hdr_t *)p;
        sph->sph_type = htons(PARM_SUPP_ADDRS);
        addrtype = (uint16_t *)(sph + 1);
        switch (connp->conn_family) {
        case AF_INET:
                *addrtype++ = htons(PARM_ADDR4);
                *addrtype = 0;
                sph->sph_len = htons(sizeof (*sph) + sizeof (*addrtype));
                break;
        case AF_INET6:
                *addrtype++ = htons(PARM_ADDR6);
                if (!sctp->sctp_connp->conn_ipv6_v6only) {
                        *addrtype = htons(PARM_ADDR4);
                        sph->sph_len = htons(sizeof (*sph) +
                            sizeof (*addrtype) * 2);
                } else {
                        *addrtype = 0;
                        sph->sph_len = htons(sizeof (*sph) +
                            sizeof (*addrtype));
                }
                break;
        default:
                break;
        }
        return (sizeof (*sph) + (sizeof (*addrtype) * 2));
}

/*
 * Currently, we support on PRSCTP option, there is more to come.
 */
/*ARGSUSED*/
size_t
sctp_options_param_len(const sctp_t *sctp, int option)
{
        size_t  optlen;

        switch (option) {
        case SCTP_PRSCTP_OPTION:
                optlen = sizeof (sctp_parm_hdr_t);
                break;
        default:
                ASSERT(0);
        }

        return (optlen);
}

/*ARGSUSED*/
size_t
sctp_options_param(const sctp_t *sctp, void *p, int option)
{
        sctp_parm_hdr_t *sph = (sctp_parm_hdr_t *)p;

        switch (option) {
        case SCTP_PRSCTP_OPTION:
                sph->sph_type = htons(PARM_FORWARD_TSN);
                sph->sph_len = htons(sizeof (*sph));
                break;
        default:
                ASSERT(0);
        }

        return (sizeof (*sph));

}

size_t
sctp_adaptation_code_param(sctp_t *sctp, uchar_t *p)
{
        sctp_parm_hdr_t *sph;

        if (!sctp->sctp_send_adaptation) {
                return (0);
        }
        sph = (sctp_parm_hdr_t *)p;
        sph->sph_type = htons(PARM_ADAPT_LAYER_IND);
        sph->sph_len = htons(sizeof (*sph) + sizeof (uint32_t));
        *(uint32_t *)(sph + 1) = htonl(sctp->sctp_tx_adaptation_code);

        return (sizeof (*sph) + sizeof (uint32_t));
}

mblk_t *
sctp_init_mp(sctp_t *sctp, sctp_faddr_t *fp)
{
        mblk_t                  *mp;
        uchar_t                 *p;
        size_t                  initlen;
        sctp_init_chunk_t       *icp;
        sctp_chunk_hdr_t        *chp;
        uint16_t                schlen;
        int                     supp_af;
        sctp_stack_t            *sctps = sctp->sctp_sctps;
        conn_t                  *connp = sctp->sctp_connp;

        if (connp->conn_family == AF_INET) {
                supp_af = PARM_SUPP_V4;
        } else {
                if (sctp->sctp_connp->conn_ipv6_v6only)
                        supp_af = PARM_SUPP_V6;
                else
                        supp_af = PARM_SUPP_V6 | PARM_SUPP_V4;
        }
        initlen = sizeof (*chp) + sizeof (*icp);
        if (sctp->sctp_send_adaptation) {
                initlen += (sizeof (sctp_parm_hdr_t) + sizeof (uint32_t));
        }
        initlen += sctp_supaddr_param_len(sctp);
        initlen += sctp_addr_params(sctp, supp_af, NULL, B_TRUE);
        if (sctp->sctp_prsctp_aware && sctps->sctps_prsctp_enabled)
                initlen += sctp_options_param_len(sctp, SCTP_PRSCTP_OPTION);

        /*
         * This could be a INIT retransmission in which case sh_verf may
         * be non-zero, zero it out just to be sure.
         */
        sctp->sctp_sctph->sh_verf = 0;
        sctp->sctp_sctph6->sh_verf = 0;

        mp = sctp_make_mp(sctp, fp, initlen);
        if (mp == NULL) {
                SCTP_KSTAT(sctps, sctp_send_init_failed);
                return (NULL);
        }
        /* sctp_make_mp could have discovered we have no usable sources */
        if (sctp->sctp_nsaddrs == 0) {
                freemsg(mp);
                SCTP_KSTAT(sctps, sctp_send_init_failed);
                return (NULL);
        }

        /* Lay in a new INIT chunk, starting with the chunk header */
        chp = (sctp_chunk_hdr_t *)mp->b_wptr;
        chp->sch_id = CHUNK_INIT;
        chp->sch_flags = 0;
        schlen = (uint16_t)initlen;
        U16_TO_ABE16(schlen, &(chp->sch_len));

        mp->b_wptr += initlen;

        icp = (sctp_init_chunk_t *)(chp + 1);
        icp->sic_inittag = sctp->sctp_lvtag;
        U32_TO_ABE32(sctp->sctp_rwnd, &(icp->sic_a_rwnd));
        U16_TO_ABE16(sctp->sctp_num_ostr, &(icp->sic_outstr));
        U16_TO_ABE16(sctp->sctp_num_istr, &(icp->sic_instr));
        U32_TO_ABE32(sctp->sctp_ltsn, &(icp->sic_inittsn));

        p = (uchar_t *)(icp + 1);

        /* Adaptation layer param */
        p += sctp_adaptation_code_param(sctp, p);

        /* Add supported address types parameter */
        p += sctp_supaddr_param(sctp, p);

        /* Add address parameters */
        p += sctp_addr_params(sctp, supp_af, p, B_FALSE);

        /* Add Forward-TSN-Supported param */
        if (sctp->sctp_prsctp_aware && sctps->sctps_prsctp_enabled)
                p += sctp_options_param(sctp, p, SCTP_PRSCTP_OPTION);

        BUMP_LOCAL(sctp->sctp_obchunks);

        sctp_set_iplen(sctp, mp, fp->sf_ixa);

        return (mp);
}

/*
 * Extracts the verification tag from an INIT chunk. If the INIT
 * chunk is truncated or malformed, returns 0.
 */
uint32_t
sctp_init2vtag(sctp_chunk_hdr_t *initch)
{
        sctp_init_chunk_t *init;

        init = (sctp_init_chunk_t *)(initch + 1);
        return (init->sic_inittag);
}

size_t
sctp_addr_params(sctp_t *sctp, int af, uchar_t *p, boolean_t modify)
{
        size_t  param_len;

        ASSERT(sctp->sctp_nsaddrs > 0);

        /*
         * If we have only one local address or it is a loopback or linklocal
         * association, we let the peer pull the address from the IP header.
         */
        if ((!modify && sctp->sctp_nsaddrs == 1) || sctp->sctp_loopback ||
            sctp->sctp_linklocal) {
                return (0);
        }

        param_len = sctp_saddr_info(sctp, af, p, modify);
        return ((sctp->sctp_nsaddrs == 1) ? 0 : param_len);
}