/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #define _SUN_TPI_VERSION 2 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sctp_impl.h" #include "sctp_asconf.h" #include "sctp_addr.h" static int sctp_getpeeraddrs(sctp_t *, void *, int *); /* * Copy the standard header into its new location, * lay in the new options and then update the relevant * fields in both sctp_t and the standard header. * Returns 0 on success, errno otherwise. */ static int sctp_opt_set_header(sctp_t *sctp, const void *ptr, uint_t len) { uint8_t *ip_optp; sctp_hdr_t *new_sctph; if ((len > SCTP_MAX_IP_OPTIONS_LENGTH) || (len & 0x3)) return (EINVAL); if (len > IP_MAX_OPT_LENGTH - sctp->sctp_v4label_len) return (EINVAL); ip_optp = (uint8_t *)sctp->sctp_ipha + IP_SIMPLE_HDR_LENGTH; if (sctp->sctp_v4label_len > 0) { int padlen; uint8_t opt; /* convert list termination to no-ops as needed */ padlen = sctp->sctp_v4label_len - ip_optp[IPOPT_OLEN]; ip_optp += ip_optp[IPOPT_OLEN]; opt = len > 0 ? IPOPT_NOP : IPOPT_EOL; while (--padlen >= 0) *ip_optp++ = opt; ASSERT(ip_optp == (uint8_t *)sctp->sctp_ipha + IP_SIMPLE_HDR_LENGTH + sctp->sctp_v4label_len); } /* * Move the existing SCTP header out where it belongs. */ new_sctph = (sctp_hdr_t *)(ip_optp + len); ovbcopy(sctp->sctp_sctph, new_sctph, sizeof (sctp_hdr_t)); sctp->sctp_sctph = new_sctph; /* * Insert the new user-supplied IP options. */ if (len > 0) bcopy(ptr, ip_optp, len); len += sctp->sctp_v4label_len; sctp->sctp_ip_hdr_len = len; sctp->sctp_ipha->ipha_version_and_hdr_length = (IP_VERSION << 4) | (len >> 2); sctp->sctp_hdr_len = len + sizeof (sctp_hdr_t); if (sctp->sctp_current) { /* * Could be setting options before setting up connection. */ sctp_set_ulp_prop(sctp); } return (0); } static int sctp_get_status(sctp_t *sctp, void *ptr) { struct sctp_status *sstat = ptr; sctp_faddr_t *fp; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct sctp_paddrinfo *sp; mblk_t *meta, *mp; int i; sstat->sstat_state = sctp->sctp_state; sstat->sstat_rwnd = sctp->sctp_frwnd; sp = &sstat->sstat_primary; if (!sctp->sctp_primary) { bzero(sp, sizeof (*sp)); goto noprim; } fp = sctp->sctp_primary; if (fp->isv4) { sin = (struct sockaddr_in *)&sp->spinfo_address; sin->sin_family = AF_INET; sin->sin_port = sctp->sctp_fport; IN6_V4MAPPED_TO_INADDR(&fp->faddr, &sin->sin_addr); sp->spinfo_mtu = sctp->sctp_hdr_len; } else { sin6 = (struct sockaddr_in6 *)&sp->spinfo_address; sin6->sin6_family = AF_INET6; sin6->sin6_port = sctp->sctp_fport; sin6->sin6_addr = fp->faddr; sp->spinfo_mtu = sctp->sctp_hdr6_len; } sp->spinfo_state = fp->state == SCTP_FADDRS_ALIVE ? SCTP_ACTIVE : SCTP_INACTIVE; sp->spinfo_cwnd = fp->cwnd; sp->spinfo_srtt = fp->srtt; sp->spinfo_rto = fp->rto; sp->spinfo_mtu += fp->sfa_pmss; noprim: sstat->sstat_unackdata = 0; sstat->sstat_penddata = 0; sstat->sstat_instrms = sctp->sctp_num_istr; sstat->sstat_outstrms = sctp->sctp_num_ostr; sstat->sstat_fragmentation_point = sctp->sctp_mss - sizeof (sctp_data_hdr_t); /* count unack'd */ for (meta = sctp->sctp_xmit_head; meta; meta = meta->b_next) { for (mp = meta->b_cont; mp; mp = mp->b_next) { if (!SCTP_CHUNK_ISSENT(mp)) { break; } if (!SCTP_CHUNK_ISACKED(mp)) { sstat->sstat_unackdata++; } } } /* * Count penddata chunks. We can only count chunks in SCTP (not * data already delivered to socket layer). */ if (sctp->sctp_instr != NULL) { for (i = 0; i < sctp->sctp_num_istr; i++) { for (meta = sctp->sctp_instr[i].istr_reass; meta != NULL; meta = meta->b_next) { for (mp = meta->b_cont; mp; mp = mp->b_cont) { if (DB_TYPE(mp) != M_CTL) { sstat->sstat_penddata++; } } } } } /* Un-Ordered Frag list */ for (meta = sctp->sctp_uo_frags; meta != NULL; meta = meta->b_next) sstat->sstat_penddata++; return (sizeof (*sstat)); } /* * SCTP_GET_PEER_ADDR_INFO */ static int sctp_get_paddrinfo(sctp_t *sctp, void *ptr, socklen_t *optlen) { struct sctp_paddrinfo *infop = ptr; struct sockaddr_in *sin4; struct sockaddr_in6 *sin6; in6_addr_t faddr; sctp_faddr_t *fp; switch (infop->spinfo_address.ss_family) { case AF_INET: sin4 = (struct sockaddr_in *)&infop->spinfo_address; IN6_INADDR_TO_V4MAPPED(&sin4->sin_addr, &faddr); break; case AF_INET6: sin6 = (struct sockaddr_in6 *)&infop->spinfo_address; faddr = sin6->sin6_addr; break; default: return (EAFNOSUPPORT); } if ((fp = sctp_lookup_faddr(sctp, &faddr)) == NULL) return (EINVAL); infop->spinfo_state = (fp->state == SCTP_FADDRS_ALIVE) ? SCTP_ACTIVE : SCTP_INACTIVE; infop->spinfo_cwnd = fp->cwnd; infop->spinfo_srtt = TICK_TO_MSEC(fp->srtt); infop->spinfo_rto = TICK_TO_MSEC(fp->rto); infop->spinfo_mtu = fp->sfa_pmss; *optlen = sizeof (struct sctp_paddrinfo); return (0); } /* * SCTP_RTOINFO */ static int sctp_get_rtoinfo(sctp_t *sctp, void *ptr) { struct sctp_rtoinfo *srto = ptr; srto->srto_initial = TICK_TO_MSEC(sctp->sctp_rto_initial); srto->srto_max = TICK_TO_MSEC(sctp->sctp_rto_max); srto->srto_min = TICK_TO_MSEC(sctp->sctp_rto_min); return (sizeof (*srto)); } static int sctp_set_rtoinfo(sctp_t *sctp, const void *invalp, uint_t inlen) { const struct sctp_rtoinfo *srto; boolean_t ispriv; sctp_stack_t *sctps = sctp->sctp_sctps; if (inlen < sizeof (*srto)) { return (EINVAL); } srto = invalp; ispriv = secpolicy_ip_config(sctp->sctp_credp, B_TRUE) == 0; /* * Bounds checking. Priviledged user can set the RTO initial * outside the ndd boundary. */ if (srto->srto_initial != 0 && (!ispriv && (srto->srto_initial < sctps->sctps_rto_initialg_low || srto->srto_initial > sctps->sctps_rto_initialg_high))) { return (EINVAL); } if (srto->srto_max != 0 && (!ispriv && (srto->srto_max < sctps->sctps_rto_maxg_low || srto->srto_max > sctps->sctps_rto_maxg_high))) { return (EINVAL); } if (srto->srto_min != 0 && (!ispriv && (srto->srto_min < sctps->sctps_rto_ming_low || srto->srto_min > sctps->sctps_rto_ming_high))) { return (EINVAL); } if (srto->srto_initial != 0) { sctp->sctp_rto_initial = MSEC_TO_TICK(srto->srto_initial); } if (srto->srto_max != 0) { sctp->sctp_rto_max = MSEC_TO_TICK(srto->srto_max); } if (srto->srto_min != 0) { sctp->sctp_rto_min = MSEC_TO_TICK(srto->srto_min); } return (0); } /* * SCTP_ASSOCINFO */ static int sctp_get_assocparams(sctp_t *sctp, void *ptr) { struct sctp_assocparams *sap = ptr; sctp_faddr_t *fp; uint16_t i; sap->sasoc_asocmaxrxt = sctp->sctp_pa_max_rxt; /* * Count the number of peer addresses */ for (i = 0, fp = sctp->sctp_faddrs; fp != NULL; fp = fp->next) { i++; } sap->sasoc_number_peer_destinations = i; sap->sasoc_peer_rwnd = sctp->sctp_frwnd; sap->sasoc_local_rwnd = sctp->sctp_rwnd; sap->sasoc_cookie_life = TICK_TO_MSEC(sctp->sctp_cookie_lifetime); return (sizeof (*sap)); } static int sctp_set_assocparams(sctp_t *sctp, const void *invalp, uint_t inlen) { const struct sctp_assocparams *sap = invalp; uint32_t sum = 0; sctp_faddr_t *fp; sctp_stack_t *sctps = sctp->sctp_sctps; if (inlen < sizeof (*sap)) { return (EINVAL); } if (sap->sasoc_asocmaxrxt) { if (sctp->sctp_faddrs) { /* * Bounds check: as per rfc2960, assoc max retr cannot * exceed the sum of all individual path max retr's. */ for (fp = sctp->sctp_faddrs; fp; fp = fp->next) { sum += fp->max_retr; } if (sap->sasoc_asocmaxrxt > sum) { return (EINVAL); } } if (sap->sasoc_asocmaxrxt < sctps->sctps_pa_max_retr_low || sap->sasoc_asocmaxrxt > sctps->sctps_pa_max_retr_high) { /* * Out of bounds. */ return (EINVAL); } } if (sap->sasoc_cookie_life != 0 && (sap->sasoc_cookie_life < sctps->sctps_cookie_life_low || sap->sasoc_cookie_life > sctps->sctps_cookie_life_high)) { return (EINVAL); } if (sap->sasoc_asocmaxrxt > 0) { sctp->sctp_pa_max_rxt = sap->sasoc_asocmaxrxt; } if (sap->sasoc_cookie_life > 0) { sctp->sctp_cookie_lifetime = MSEC_TO_TICK( sap->sasoc_cookie_life); } return (0); } /* * SCTP_INITMSG */ static int sctp_get_initmsg(sctp_t *sctp, void *ptr) { struct sctp_initmsg *si = ptr; si->sinit_num_ostreams = sctp->sctp_num_ostr; si->sinit_max_instreams = sctp->sctp_num_istr; si->sinit_max_attempts = sctp->sctp_max_init_rxt; si->sinit_max_init_timeo = TICK_TO_MSEC(sctp->sctp_init_rto_max); return (sizeof (*si)); } static int sctp_set_initmsg(sctp_t *sctp, const void *invalp, uint_t inlen) { const struct sctp_initmsg *si = invalp; sctp_stack_t *sctps = sctp->sctp_sctps; if (sctp->sctp_state > SCTPS_LISTEN) { return (EINVAL); } if (inlen < sizeof (*si)) { return (EINVAL); } if (si->sinit_num_ostreams != 0 && (si->sinit_num_ostreams < sctps->sctps_initial_out_streams_low || si->sinit_num_ostreams > sctps->sctps_initial_out_streams_high)) { /* * Out of bounds. */ return (EINVAL); } if (si->sinit_max_instreams != 0 && (si->sinit_max_instreams < sctps->sctps_max_in_streams_low || si->sinit_max_instreams > sctps->sctps_max_in_streams_high)) { return (EINVAL); } if (si->sinit_max_attempts != 0 && (si->sinit_max_attempts < sctps->sctps_max_init_retr_low || si->sinit_max_attempts > sctps->sctps_max_init_retr_high)) { return (EINVAL); } if (si->sinit_max_init_timeo != 0 && (secpolicy_ip_config(sctp->sctp_credp, B_TRUE) != 0 && (si->sinit_max_init_timeo < sctps->sctps_rto_maxg_low || si->sinit_max_init_timeo > sctps->sctps_rto_maxg_high))) { return (EINVAL); } if (si->sinit_num_ostreams != 0) sctp->sctp_num_ostr = si->sinit_num_ostreams; if (si->sinit_max_instreams != 0) sctp->sctp_num_istr = si->sinit_max_instreams; if (si->sinit_max_attempts != 0) sctp->sctp_max_init_rxt = si->sinit_max_attempts; if (si->sinit_max_init_timeo != 0) { sctp->sctp_init_rto_max = MSEC_TO_TICK(si->sinit_max_init_timeo); } return (0); } /* * SCTP_PEER_ADDR_PARAMS */ static int sctp_find_peer_fp(sctp_t *sctp, const struct sockaddr_storage *ss, sctp_faddr_t **fpp) { struct sockaddr_in *sin; struct sockaddr_in6 *sin6; in6_addr_t addr; if (ss->ss_family == AF_INET) { sin = (struct sockaddr_in *)ss; IN6_IPADDR_TO_V4MAPPED(sin->sin_addr.s_addr, &addr); } else if (ss->ss_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)ss; addr = sin6->sin6_addr; } else if (ss->ss_family) { return (EAFNOSUPPORT); } if (!ss->ss_family || SCTP_IS_ADDR_UNSPEC(IN6_IS_ADDR_V4MAPPED(&addr), addr)) { *fpp = NULL; } else { *fpp = sctp_lookup_faddr(sctp, &addr); if (*fpp == NULL) { return (EINVAL); } } return (0); } static int sctp_get_peer_addr_params(sctp_t *sctp, void *ptr) { struct sctp_paddrparams *spp = ptr; sctp_faddr_t *fp; int retval; retval = sctp_find_peer_fp(sctp, &spp->spp_address, &fp); if (retval) { return (retval); } if (fp) { spp->spp_hbinterval = TICK_TO_MSEC(fp->hb_interval); spp->spp_pathmaxrxt = fp->max_retr; } else { spp->spp_hbinterval = TICK_TO_MSEC(sctp->sctp_hb_interval); spp->spp_pathmaxrxt = sctp->sctp_pp_max_rxt; } return (sizeof (*spp)); } static int sctp_set_peer_addr_params(sctp_t *sctp, const void *invalp, uint_t inlen) { const struct sctp_paddrparams *spp = invalp; sctp_faddr_t *fp, *fp2; int retval; uint32_t sum = 0; int64_t now; sctp_stack_t *sctps = sctp->sctp_sctps; if (inlen < sizeof (*spp)) { return (EINVAL); } retval = sctp_find_peer_fp(sctp, &spp->spp_address, &fp); if (retval != 0) { return (retval); } if (spp->spp_hbinterval && spp->spp_hbinterval != UINT32_MAX && (spp->spp_hbinterval < sctps->sctps_heartbeat_interval_low || spp->spp_hbinterval > sctps->sctps_heartbeat_interval_high)) { return (EINVAL); } if (spp->spp_pathmaxrxt && (spp->spp_pathmaxrxt < sctps->sctps_pp_max_retr_low || spp->spp_pathmaxrxt > sctps->sctps_pp_max_retr_high)) { return (EINVAL); } if (spp->spp_pathmaxrxt && sctp->sctp_faddrs) { for (fp2 = sctp->sctp_faddrs; fp2; fp2 = fp2->next) { if (!fp || fp2 == fp) { sum += spp->spp_pathmaxrxt; } else { sum += fp2->max_retr; } } if (sctp->sctp_pa_max_rxt > sum) { return (EINVAL); } } now = lbolt64; if (fp != NULL) { if (spp->spp_hbinterval == UINT32_MAX) { /* * Send heartbeat immediatelly, don't modify the * current setting. */ sctp_send_heartbeat(sctp, fp); } else { fp->hb_interval = MSEC_TO_TICK(spp->spp_hbinterval); fp->hb_expiry = now + SET_HB_INTVL(fp); /* * Restart the heartbeat timer using the new intrvl. * We need to call sctp_heartbeat_timer() to set * the earliest heartbeat expiry time. */ sctp_heartbeat_timer(sctp); } if (spp->spp_pathmaxrxt) { fp->max_retr = spp->spp_pathmaxrxt; } } else { for (fp2 = sctp->sctp_faddrs; fp2 != NULL; fp2 = fp2->next) { if (spp->spp_hbinterval == UINT32_MAX) { /* * Send heartbeat immediatelly, don't modify * the current setting. */ sctp_send_heartbeat(sctp, fp2); } else { fp2->hb_interval = MSEC_TO_TICK( spp->spp_hbinterval); fp2->hb_expiry = now + SET_HB_INTVL(fp2); } if (spp->spp_pathmaxrxt) { fp2->max_retr = spp->spp_pathmaxrxt; } } if (spp->spp_hbinterval != UINT32_MAX) { sctp->sctp_hb_interval = MSEC_TO_TICK( spp->spp_hbinterval); /* Restart the heartbeat timer using the new intrvl. */ sctp_timer(sctp, sctp->sctp_heartbeat_mp, sctp->sctp_hb_interval); } if (spp->spp_pathmaxrxt) { sctp->sctp_pp_max_rxt = spp->spp_pathmaxrxt; } } return (0); } /* * SCTP_DEFAULT_SEND_PARAM */ static int sctp_get_def_send_params(sctp_t *sctp, void *ptr) { struct sctp_sndrcvinfo *sinfo = ptr; sinfo->sinfo_stream = sctp->sctp_def_stream; sinfo->sinfo_ssn = 0; sinfo->sinfo_flags = sctp->sctp_def_flags; sinfo->sinfo_ppid = sctp->sctp_def_ppid; sinfo->sinfo_context = sctp->sctp_def_context; sinfo->sinfo_timetolive = sctp->sctp_def_timetolive; sinfo->sinfo_tsn = 0; sinfo->sinfo_cumtsn = 0; return (sizeof (*sinfo)); } static int sctp_set_def_send_params(sctp_t *sctp, const void *invalp, uint_t inlen) { const struct sctp_sndrcvinfo *sinfo = invalp; if (inlen < sizeof (*sinfo)) { return (EINVAL); } if (sinfo->sinfo_stream >= sctp->sctp_num_ostr) { return (EINVAL); } sctp->sctp_def_stream = sinfo->sinfo_stream; sctp->sctp_def_flags = sinfo->sinfo_flags; sctp->sctp_def_ppid = sinfo->sinfo_ppid; sctp->sctp_def_context = sinfo->sinfo_context; sctp->sctp_def_timetolive = sinfo->sinfo_timetolive; return (0); } static int sctp_set_prim(sctp_t *sctp, const void *invalp, uint_t inlen) { const struct sctp_setpeerprim *pp = invalp; int retval; sctp_faddr_t *fp; if (inlen < sizeof (*pp)) { return (EINVAL); } retval = sctp_find_peer_fp(sctp, &pp->sspp_addr, &fp); if (retval) return (retval); if (fp == NULL) return (EINVAL); if (fp == sctp->sctp_primary) return (0); sctp->sctp_primary = fp; /* Only switch current if fp is alive */ if (fp->state != SCTP_FADDRS_ALIVE || fp == sctp->sctp_current) { return (0); } sctp_set_faddr_current(sctp, fp); return (0); } /* Handy on off switch for socket option processing. */ #define ONOFF(x) ((x) == 0 ? 0 : 1) /* * SCTP routine to get the values of options. */ int sctp_get_opt(sctp_t *sctp, int level, int name, void *ptr, socklen_t *optlen) { int *i1 = (int *)ptr; int retval = 0; int buflen = *optlen; conn_t *connp = sctp->sctp_connp; ip6_pkt_t *ipp = &sctp->sctp_sticky_ipp; /* In most cases, the return buffer is just an int */ *optlen = sizeof (int32_t); RUN_SCTP(sctp); if (connp->conn_state_flags & CONN_CLOSING) { WAKE_SCTP(sctp); return (EINVAL); } switch (level) { case SOL_SOCKET: switch (name) { case SO_LINGER: { struct linger *lgr = (struct linger *)ptr; lgr->l_onoff = sctp->sctp_linger ? SO_LINGER : 0; lgr->l_linger = TICK_TO_MSEC(sctp->sctp_lingertime); *optlen = sizeof (struct linger); break; } case SO_DEBUG: *i1 = sctp->sctp_debug ? SO_DEBUG : 0; break; case SO_DONTROUTE: *i1 = connp->conn_dontroute ? SO_DONTROUTE : 0; break; case SO_USELOOPBACK: *i1 = connp->conn_loopback ? SO_USELOOPBACK : 0; break; case SO_BROADCAST: *i1 = connp->conn_broadcast ? SO_BROADCAST : 0; break; case SO_REUSEADDR: *i1 = connp->conn_reuseaddr ? SO_REUSEADDR : 0; break; case SO_DGRAM_ERRIND: *i1 = sctp->sctp_dgram_errind ? SO_DGRAM_ERRIND : 0; break; case SO_SNDBUF: *i1 = sctp->sctp_xmit_hiwater; break; case SO_RCVBUF: *i1 = sctp->sctp_rwnd; break; case SO_ALLZONES: *i1 = connp->conn_allzones; break; case SO_MAC_EXEMPT: *i1 = connp->conn_mac_exempt; break; case SO_PROTOTYPE: *i1 = IPPROTO_SCTP; break; case SO_DOMAIN: *i1 = sctp->sctp_family; break; default: retval = ENOPROTOOPT; break; } break; case IPPROTO_SCTP: switch (name) { case SCTP_RTOINFO: if (buflen < sizeof (struct sctp_rtoinfo)) { retval = EINVAL; break; } *optlen = sctp_get_rtoinfo(sctp, ptr); break; case SCTP_ASSOCINFO: if (buflen < sizeof (struct sctp_assocparams)) { retval = EINVAL; break; } *optlen = sctp_get_assocparams(sctp, ptr); break; case SCTP_INITMSG: if (buflen < sizeof (struct sctp_initmsg)) { retval = EINVAL; break; } *optlen = sctp_get_initmsg(sctp, ptr); break; case SCTP_NODELAY: *i1 = sctp->sctp_ndelay; break; case SCTP_AUTOCLOSE: *i1 = TICK_TO_SEC(sctp->sctp_autoclose); break; case SCTP_ADAPTATION_LAYER: if (buflen < sizeof (struct sctp_setadaptation)) { retval = EINVAL; break; } ((struct sctp_setadaptation *)ptr)->ssb_adaptation_ind = sctp->sctp_tx_adaptation_code; break; case SCTP_PEER_ADDR_PARAMS: if (buflen < sizeof (struct sctp_paddrparams)) { retval = EINVAL; break; } *optlen = sctp_get_peer_addr_params(sctp, ptr); break; case SCTP_DEFAULT_SEND_PARAM: if (buflen < sizeof (struct sctp_sndrcvinfo)) { retval = EINVAL; break; } *optlen = sctp_get_def_send_params(sctp, ptr); break; case SCTP_EVENTS: { struct sctp_event_subscribe *ev; if (buflen < sizeof (struct sctp_event_subscribe)) { retval = EINVAL; break; } ev = (struct sctp_event_subscribe *)ptr; ev->sctp_data_io_event = ONOFF(sctp->sctp_recvsndrcvinfo); ev->sctp_association_event = ONOFF(sctp->sctp_recvassocevnt); ev->sctp_address_event = ONOFF(sctp->sctp_recvpathevnt); ev->sctp_send_failure_event = ONOFF(sctp->sctp_recvsendfailevnt); ev->sctp_peer_error_event = ONOFF(sctp->sctp_recvpeererr); ev->sctp_shutdown_event = ONOFF(sctp->sctp_recvshutdownevnt); ev->sctp_partial_delivery_event = ONOFF(sctp->sctp_recvpdevnt); ev->sctp_adaptation_layer_event = ONOFF(sctp->sctp_recvalevnt); *optlen = sizeof (struct sctp_event_subscribe); break; } case SCTP_STATUS: if (buflen < sizeof (struct sctp_status)) { retval = EINVAL; break; } *optlen = sctp_get_status(sctp, ptr); break; case SCTP_GET_PEER_ADDR_INFO: if (buflen < sizeof (struct sctp_paddrinfo)) { retval = EINVAL; break; } retval = sctp_get_paddrinfo(sctp, ptr, optlen); break; case SCTP_GET_NLADDRS: *(int32_t *)ptr = sctp->sctp_nsaddrs; break; case SCTP_GET_LADDRS: { int addr_cnt; int addr_size; if (sctp->sctp_family == AF_INET) addr_size = sizeof (struct sockaddr_in); else addr_size = sizeof (struct sockaddr_in6); addr_cnt = buflen / addr_size; retval = sctp_getmyaddrs(sctp, ptr, &addr_cnt); if (retval == 0) *optlen = addr_cnt * addr_size; break; } case SCTP_GET_NPADDRS: { int i; sctp_faddr_t *fp; for (i = 0, fp = sctp->sctp_faddrs; fp != NULL; i++, fp = fp->next) ; *(int32_t *)ptr = i; break; } case SCTP_GET_PADDRS: { int addr_cnt; int addr_size; if (sctp->sctp_family == AF_INET) addr_size = sizeof (struct sockaddr_in); else addr_size = sizeof (struct sockaddr_in6); addr_cnt = buflen / addr_size; retval = sctp_getpeeraddrs(sctp, ptr, &addr_cnt); if (retval == 0) *optlen = addr_cnt * addr_size; break; } case SCTP_PRSCTP: *i1 = sctp->sctp_prsctp_aware ? 1 : 0; break; case SCTP_GET_ASSOC_STATS: { sctp_assoc_stats_t *sas; if (buflen < sizeof (sctp_assoc_stats_t)) { retval = EINVAL; break; } sas = (sctp_assoc_stats_t *)ptr; /* * Copy the current stats to the stats struct. */ sas->sas_rtxchunks = sctp->sctp_rxtchunks; sas->sas_gapcnt = sctp->sctp_gapcnt; sas->sas_outseqtsns = sctp->sctp_outseqtsns; sas->sas_osacks = sctp->sctp_osacks; sas->sas_isacks = sctp->sctp_isacks; sas->sas_octrlchunks = sctp->sctp_obchunks; sas->sas_ictrlchunks = sctp->sctp_ibchunks; sas->sas_oodchunks = sctp->sctp_odchunks; sas->sas_iodchunks = sctp->sctp_idchunks; sas->sas_ouodchunks = sctp->sctp_oudchunks; sas->sas_iuodchunks = sctp->sctp_iudchunks; sas->sas_idupchunks = sctp->sctp_idupchunks; /* * Copy out the maximum observed RTO since the * time this data was last requested */ if (sctp->sctp_maxrto == 0) { /* unchanged during obervation period */ sas->sas_maxrto = sctp->sctp_prev_maxrto; } else { /* record new period maximum */ sas->sas_maxrto = sctp->sctp_maxrto; } /* Record the value sent to the user this period */ sctp->sctp_prev_maxrto = sas->sas_maxrto; /* Mark beginning of a new observation period */ sctp->sctp_maxrto = 0; *optlen = sizeof (sctp_assoc_stats_t); break; } case SCTP_I_WANT_MAPPED_V4_ADDR: case SCTP_MAXSEG: case SCTP_DISABLE_FRAGMENTS: /* Not yet supported. */ default: retval = ENOPROTOOPT; break; } break; case IPPROTO_IP: if (sctp->sctp_family != AF_INET) { retval = EINVAL; break; } switch (name) { case IP_OPTIONS: case T_IP_OPTIONS: { /* * This is compatible with BSD in that in only return * the reverse source route with the final destination * as the last entry. The first 4 bytes of the option * will contain the final destination. Allocate a * buffer large enough to hold all the options, we * add IP_ADDR_LEN to SCTP_MAX_IP_OPTIONS_LENGTH since * ip_opt_get_user() adds the final destination * at the start. */ char *opt_ptr; int opt_len; uchar_t obuf[SCTP_MAX_IP_OPTIONS_LENGTH + IP_ADDR_LEN]; opt_ptr = (char *)sctp->sctp_ipha + IP_SIMPLE_HDR_LENGTH; opt_len = (char *)sctp->sctp_sctph - opt_ptr; /* Caller ensures enough space */ if (opt_len > 0) { /* * TODO: Do we have to handle getsockopt on an * initiator as well? */ opt_len = ip_opt_get_user(sctp->sctp_ipha, obuf); ASSERT(opt_len <= sizeof (obuf)); } else { opt_len = 0; } if (buflen < opt_len) { /* Silently truncate */ opt_len = buflen; } *optlen = opt_len; bcopy(obuf, ptr, opt_len); break; } case IP_TOS: case T_IP_TOS: *i1 = (int)sctp->sctp_ipha->ipha_type_of_service; break; case IP_TTL: *i1 = (int)sctp->sctp_ipha->ipha_ttl; break; case IP_NEXTHOP: if (connp->conn_nexthop_set) { *(ipaddr_t *)ptr = connp->conn_nexthop_v4; *optlen = sizeof (ipaddr_t); } else { *optlen = 0; } break; default: retval = ENOPROTOOPT; break; } break; case IPPROTO_IPV6: if (sctp->sctp_family != AF_INET6) { retval = EINVAL; break; } switch (name) { case IPV6_UNICAST_HOPS: *i1 = (unsigned int) sctp->sctp_ip6h->ip6_hops; break; /* goto sizeof (int) option return */ case IPV6_RECVPKTINFO: if (sctp->sctp_ipv6_recvancillary & SCTP_IPV6_RECVPKTINFO) { *i1 = 1; } else { *i1 = 0; } break; /* goto sizeof (int) option return */ case IPV6_RECVHOPLIMIT: if (sctp->sctp_ipv6_recvancillary & SCTP_IPV6_RECVHOPLIMIT) { *i1 = 1; } else { *i1 = 0; } break; /* goto sizeof (int) option return */ case IPV6_RECVHOPOPTS: if (sctp->sctp_ipv6_recvancillary & SCTP_IPV6_RECVHOPOPTS) { *i1 = 1; } else { *i1 = 0; } break; /* goto sizeof (int) option return */ case IPV6_RECVDSTOPTS: if (sctp->sctp_ipv6_recvancillary & SCTP_IPV6_RECVDSTOPTS) { *i1 = 1; } else { *i1 = 0; } break; /* goto sizeof (int) option return */ case IPV6_RECVRTHDR: if (sctp->sctp_ipv6_recvancillary & SCTP_IPV6_RECVRTHDR) { *i1 = 1; } else { *i1 = 0; } break; /* goto sizeof (int) option return */ case IPV6_RECVRTHDRDSTOPTS: if (sctp->sctp_ipv6_recvancillary & SCTP_IPV6_RECVRTDSTOPTS) { *i1 = 1; } else { *i1 = 0; } break; /* goto sizeof (int) option return */ case IPV6_PKTINFO: { struct in6_pktinfo *pkti; if (buflen < sizeof (struct in6_pktinfo)) { retval = EINVAL; break; } pkti = (struct in6_pktinfo *)ptr; if (ipp->ipp_fields & IPPF_IFINDEX) pkti->ipi6_ifindex = ipp->ipp_ifindex; else pkti->ipi6_ifindex = 0; if (ipp->ipp_fields & IPPF_ADDR) pkti->ipi6_addr = ipp->ipp_addr; else pkti->ipi6_addr = ipv6_all_zeros; *optlen = sizeof (struct in6_pktinfo); break; } case IPV6_NEXTHOP: { sin6_t *sin6; if (buflen < sizeof (sin6_t)) { retval = EINVAL; break; } sin6 = (sin6_t *)ptr; if (!(ipp->ipp_fields & IPPF_NEXTHOP)) break; *sin6 = sctp_sin6_null; sin6->sin6_family = AF_INET6; sin6->sin6_addr = ipp->ipp_nexthop; *optlen = sizeof (sin6_t); break; } case IPV6_HOPOPTS: { int len; if (!(ipp->ipp_fields & IPPF_HOPOPTS)) break; len = ipp->ipp_hopoptslen - sctp->sctp_v6label_len; if (len <= 0) break; if (buflen < len) { retval = EINVAL; break; } bcopy((char *)ipp->ipp_hopopts + sctp->sctp_v6label_len, ptr, len); if (sctp->sctp_v6label_len > 0) { char *cptr = ptr; /* * If the label length is greater than zero, * then we need to hide the label from user. * Make it look as though a normal Hop-By-Hop * Options Header is present here. */ cptr[0] = ((char *)ipp->ipp_hopopts)[0]; cptr[1] = (len + 7) / 8 - 1; } *optlen = len; break; } case IPV6_RTHDRDSTOPTS: if (!(ipp->ipp_fields & IPPF_RTDSTOPTS)) break; if (buflen < ipp->ipp_rtdstoptslen) { retval = EINVAL; break; } bcopy(ipp->ipp_rtdstopts, ptr, ipp->ipp_rtdstoptslen); *optlen = ipp->ipp_rtdstoptslen; break; case IPV6_RTHDR: if (!(ipp->ipp_fields & IPPF_RTHDR)) break; if (buflen < ipp->ipp_rthdrlen) { retval = EINVAL; break; } bcopy(ipp->ipp_rthdr, ptr, ipp->ipp_rthdrlen); *optlen = ipp->ipp_rthdrlen; break; case IPV6_DSTOPTS: if (!(ipp->ipp_fields & IPPF_DSTOPTS)) break; if (buflen < ipp->ipp_dstoptslen) { retval = EINVAL; break; } bcopy(ipp->ipp_dstopts, ptr, ipp->ipp_dstoptslen); *optlen = ipp->ipp_dstoptslen; break; case IPV6_V6ONLY: *i1 = sctp->sctp_connp->conn_ipv6_v6only; break; default: retval = ENOPROTOOPT; break; } break; default: retval = ENOPROTOOPT; break; } WAKE_SCTP(sctp); return (retval); } int sctp_set_opt(sctp_t *sctp, int level, int name, const void *invalp, socklen_t inlen) { ip6_pkt_t *ipp = &sctp->sctp_sticky_ipp; int *i1 = (int *)invalp; boolean_t onoff; int retval = 0, addrcnt; conn_t *connp = sctp->sctp_connp; sctp_stack_t *sctps = sctp->sctp_sctps; /* In all cases, the size of the option must be bigger than int */ if (inlen >= sizeof (int32_t)) { onoff = ONOFF(*i1); } retval = 0; RUN_SCTP(sctp); if (connp->conn_state_flags & CONN_CLOSING) { WAKE_SCTP(sctp); return (EINVAL); } switch (level) { case SOL_SOCKET: if (inlen < sizeof (int32_t)) { retval = EINVAL; break; } switch (name) { case SO_LINGER: { struct linger *lgr; if (inlen != sizeof (struct linger)) { retval = EINVAL; break; } lgr = (struct linger *)invalp; if (lgr->l_onoff != 0) { sctp->sctp_linger = 1; sctp->sctp_lingertime = MSEC_TO_TICK( lgr->l_linger); } else { sctp->sctp_linger = 0; sctp->sctp_lingertime = 0; } break; } case SO_DEBUG: sctp->sctp_debug = onoff; break; case SO_KEEPALIVE: break; case SO_DONTROUTE: /* * SO_DONTROUTE, SO_USELOOPBACK and SO_BROADCAST are * only of interest to IP. */ connp->conn_dontroute = onoff; break; case SO_USELOOPBACK: connp->conn_loopback = onoff; break; case SO_BROADCAST: connp->conn_broadcast = onoff; break; case SO_REUSEADDR: connp->conn_reuseaddr = onoff; break; case SO_DGRAM_ERRIND: sctp->sctp_dgram_errind = onoff; break; case SO_SNDBUF: if (*i1 > sctps->sctps_max_buf) { retval = ENOBUFS; break; } if (*i1 < 0) { retval = EINVAL; break; } sctp->sctp_xmit_hiwater = *i1; if (sctps->sctps_snd_lowat_fraction != 0) sctp->sctp_xmit_lowater = sctp->sctp_xmit_hiwater / sctps->sctps_snd_lowat_fraction; break; case SO_RCVBUF: if (*i1 > sctps->sctps_max_buf) { retval = ENOBUFS; break; } /* Silently ignore zero */ if (*i1 != 0) { struct sock_proto_props sopp; /* * Insist on a receive window that is at least * sctp_recv_hiwat_minmss * MSS (default 4*MSS) * to avoid funny interactions of Nagle * algorithm, SWS avoidance and delayed * acknowledgement. */ *i1 = MAX(*i1, sctps->sctps_recv_hiwat_minmss * sctp->sctp_mss); sctp->sctp_rwnd = *i1; sctp->sctp_irwnd = sctp->sctp_rwnd; sctp->sctp_pd_point = sctp->sctp_rwnd; sopp.sopp_flags = SOCKOPT_RCVHIWAT; sopp.sopp_rxhiwat = *i1; sctp->sctp_ulp_prop(sctp->sctp_ulpd, &sopp); } /* * XXX should we return the rwnd here * and sctp_opt_get ? */ break; case SO_ALLZONES: if (secpolicy_ip(sctp->sctp_credp, OP_CONFIG, B_TRUE)) { retval = EACCES; break; } if (sctp->sctp_state >= SCTPS_BOUND) { retval = EINVAL; break; } sctp->sctp_allzones = onoff; break; case SO_MAC_EXEMPT: if (secpolicy_net_mac_aware(sctp->sctp_credp) != 0) { retval = EACCES; break; } if (sctp->sctp_state >= SCTPS_BOUND) { retval = EINVAL; break; } connp->conn_mac_exempt = onoff; break; default: retval = ENOPROTOOPT; break; } break; case IPPROTO_SCTP: if (inlen < sizeof (int32_t)) { retval = EINVAL; break; } switch (name) { case SCTP_RTOINFO: retval = sctp_set_rtoinfo(sctp, invalp, inlen); break; case SCTP_ASSOCINFO: retval = sctp_set_assocparams(sctp, invalp, inlen); break; case SCTP_INITMSG: retval = sctp_set_initmsg(sctp, invalp, inlen); break; case SCTP_NODELAY: sctp->sctp_ndelay = ONOFF(*i1); break; case SCTP_AUTOCLOSE: if (SEC_TO_TICK(*i1) < 0) { retval = EINVAL; break; } /* Convert the number of seconds to ticks. */ sctp->sctp_autoclose = SEC_TO_TICK(*i1); sctp_heartbeat_timer(sctp); break; case SCTP_SET_PEER_PRIMARY_ADDR: retval = sctp_set_peerprim(sctp, invalp, inlen); break; case SCTP_PRIMARY_ADDR: retval = sctp_set_prim(sctp, invalp, inlen); break; case SCTP_ADAPTATION_LAYER: { struct sctp_setadaptation *ssb; if (inlen < sizeof (struct sctp_setadaptation)) { retval = EINVAL; break; } ssb = (struct sctp_setadaptation *)invalp; sctp->sctp_send_adaptation = 1; sctp->sctp_tx_adaptation_code = ssb->ssb_adaptation_ind; break; } case SCTP_PEER_ADDR_PARAMS: retval = sctp_set_peer_addr_params(sctp, invalp, inlen); break; case SCTP_DEFAULT_SEND_PARAM: retval = sctp_set_def_send_params(sctp, invalp, inlen); break; case SCTP_EVENTS: { struct sctp_event_subscribe *ev; if (inlen < sizeof (struct sctp_event_subscribe)) { retval = EINVAL; break; } ev = (struct sctp_event_subscribe *)invalp; sctp->sctp_recvsndrcvinfo = ONOFF(ev->sctp_data_io_event); sctp->sctp_recvassocevnt = ONOFF(ev->sctp_association_event); sctp->sctp_recvpathevnt = ONOFF(ev->sctp_address_event); sctp->sctp_recvsendfailevnt = ONOFF(ev->sctp_send_failure_event); sctp->sctp_recvpeererr = ONOFF(ev->sctp_peer_error_event); sctp->sctp_recvshutdownevnt = ONOFF(ev->sctp_shutdown_event); sctp->sctp_recvpdevnt = ONOFF(ev->sctp_partial_delivery_event); sctp->sctp_recvalevnt = ONOFF(ev->sctp_adaptation_layer_event); break; } case SCTP_ADD_ADDR: case SCTP_REM_ADDR: /* * The sctp_t has to be bound first before * the address list can be changed. */ if (sctp->sctp_state < SCTPS_BOUND) { retval = EINVAL; break; } if (sctp->sctp_family == AF_INET) { addrcnt = inlen / sizeof (struct sockaddr_in); } else { ASSERT(sctp->sctp_family == AF_INET6); addrcnt = inlen / sizeof (struct sockaddr_in6); } if (name == SCTP_ADD_ADDR) { retval = sctp_bind_add(sctp, invalp, addrcnt, B_TRUE, sctp->sctp_lport); } else { retval = sctp_bind_del(sctp, invalp, addrcnt, B_TRUE); } break; case SCTP_UC_SWAP: { struct sctp_uc_swap *us; /* * Change handle & upcalls. */ if (inlen < sizeof (*us)) { retval = EINVAL; break; } us = (struct sctp_uc_swap *)invalp; sctp->sctp_ulpd = us->sus_handle; sctp->sctp_upcalls = us->sus_upcalls; break; } case SCTP_PRSCTP: sctp->sctp_prsctp_aware = onoff; break; case SCTP_I_WANT_MAPPED_V4_ADDR: case SCTP_MAXSEG: case SCTP_DISABLE_FRAGMENTS: /* Not yet supported. */ default: retval = ENOPROTOOPT; break; } break; case IPPROTO_IP: if (sctp->sctp_family != AF_INET) { retval = ENOPROTOOPT; break; } if ((name != IP_OPTIONS) && (inlen < sizeof (int32_t))) { retval = EINVAL; break; } switch (name) { case IP_OPTIONS: case T_IP_OPTIONS: retval = sctp_opt_set_header(sctp, invalp, inlen); break; case IP_TOS: case T_IP_TOS: sctp->sctp_ipha->ipha_type_of_service = (uchar_t)*i1; break; case IP_TTL: sctp->sctp_ipha->ipha_ttl = (uchar_t)*i1; break; case IP_SEC_OPT: /* * We should not allow policy setting after * we start listening for connections. */ if (sctp->sctp_state >= SCTPS_LISTEN) { retval = EINVAL; } else { retval = ipsec_set_req(sctp->sctp_credp, sctp->sctp_connp, (ipsec_req_t *)invalp); } break; /* IP level options */ case IP_UNSPEC_SRC: connp->conn_unspec_src = onoff; break; case IP_NEXTHOP: { ipaddr_t addr = *i1; ipif_t *ipif = NULL; ill_t *ill; ip_stack_t *ipst = sctps->sctps_netstack->netstack_ip; if (secpolicy_ip(sctp->sctp_credp, OP_CONFIG, B_TRUE) == 0) { ipif = ipif_lookup_onlink_addr(addr, connp->conn_zoneid, ipst); if (ipif == NULL) { retval = EHOSTUNREACH; break; } ill = ipif->ipif_ill; mutex_enter(&ill->ill_lock); if ((ill->ill_state_flags & ILL_CONDEMNED) || (ipif->ipif_state_flags & IPIF_CONDEMNED)) { mutex_exit(&ill->ill_lock); ipif_refrele(ipif); retval = EHOSTUNREACH; break; } mutex_exit(&ill->ill_lock); ipif_refrele(ipif); mutex_enter(&connp->conn_lock); connp->conn_nexthop_v4 = addr; connp->conn_nexthop_set = B_TRUE; mutex_exit(&connp->conn_lock); } break; } default: retval = ENOPROTOOPT; break; } break; case IPPROTO_IPV6: { if (sctp->sctp_family != AF_INET6) { retval = ENOPROTOOPT; break; } switch (name) { case IPV6_UNICAST_HOPS: if (inlen < sizeof (int32_t)) { retval = EINVAL; break; } if (*i1 < -1 || *i1 > IPV6_MAX_HOPS) { retval = EINVAL; break; } if (*i1 == -1) { ipp->ipp_unicast_hops = sctps->sctps_ipv6_hoplimit; ipp->ipp_fields &= ~IPPF_UNICAST_HOPS; } else { ipp->ipp_unicast_hops = (uint8_t)*i1; ipp->ipp_fields |= IPPF_UNICAST_HOPS; } retval = sctp_build_hdrs(sctp); break; case IPV6_UNSPEC_SRC: if (inlen < sizeof (int32_t)) { retval = EINVAL; break; } connp->conn_unspec_src = onoff; break; case IPV6_RECVPKTINFO: if (inlen < sizeof (int32_t)) { retval = EINVAL; break; } if (onoff) sctp->sctp_ipv6_recvancillary |= SCTP_IPV6_RECVPKTINFO; else sctp->sctp_ipv6_recvancillary &= ~SCTP_IPV6_RECVPKTINFO; /* Send it with the next msg */ sctp->sctp_recvifindex = 0; connp->conn_ip_recvpktinfo = onoff; break; case IPV6_RECVHOPLIMIT: if (inlen < sizeof (int32_t)) { retval = EINVAL; break; } if (onoff) sctp->sctp_ipv6_recvancillary |= SCTP_IPV6_RECVHOPLIMIT; else sctp->sctp_ipv6_recvancillary &= ~SCTP_IPV6_RECVHOPLIMIT; sctp->sctp_recvhops = 0xffffffffU; connp->conn_ipv6_recvhoplimit = onoff; break; case IPV6_RECVHOPOPTS: if (inlen < sizeof (int32_t)) { retval = EINVAL; break; } if (onoff) sctp->sctp_ipv6_recvancillary |= SCTP_IPV6_RECVHOPOPTS; else sctp->sctp_ipv6_recvancillary &= ~SCTP_IPV6_RECVHOPOPTS; connp->conn_ipv6_recvhopopts = onoff; break; case IPV6_RECVDSTOPTS: if (inlen < sizeof (int32_t)) { retval = EINVAL; break; } if (onoff) sctp->sctp_ipv6_recvancillary |= SCTP_IPV6_RECVDSTOPTS; else sctp->sctp_ipv6_recvancillary &= ~SCTP_IPV6_RECVDSTOPTS; connp->conn_ipv6_recvdstopts = onoff; break; case IPV6_RECVRTHDR: if (inlen < sizeof (int32_t)) { retval = EINVAL; break; } if (onoff) sctp->sctp_ipv6_recvancillary |= SCTP_IPV6_RECVRTHDR; else sctp->sctp_ipv6_recvancillary &= ~SCTP_IPV6_RECVRTHDR; connp->conn_ipv6_recvrthdr = onoff; break; case IPV6_RECVRTHDRDSTOPTS: if (inlen < sizeof (int32_t)) { retval = EINVAL; break; } if (onoff) sctp->sctp_ipv6_recvancillary |= SCTP_IPV6_RECVRTDSTOPTS; else sctp->sctp_ipv6_recvancillary &= ~SCTP_IPV6_RECVRTDSTOPTS; connp->conn_ipv6_recvrtdstopts = onoff; break; case IPV6_PKTINFO: if (inlen != 0 && inlen != sizeof (struct in6_pktinfo)) { retval = EINVAL; break; } if (inlen == 0) { ipp->ipp_fields &= ~(IPPF_IFINDEX |IPPF_ADDR); } else { struct in6_pktinfo *pkti; pkti = (struct in6_pktinfo *)invalp; /* XXX Need to check if the index exists */ ipp->ipp_ifindex = pkti->ipi6_ifindex; ipp->ipp_addr = pkti->ipi6_addr; if (ipp->ipp_ifindex != 0) ipp->ipp_fields |= IPPF_IFINDEX; else ipp->ipp_fields &= ~IPPF_IFINDEX; if (!IN6_IS_ADDR_UNSPECIFIED(&ipp->ipp_addr)) ipp->ipp_fields |= IPPF_ADDR; else ipp->ipp_fields &= ~IPPF_ADDR; } retval = sctp_build_hdrs(sctp); break; case IPV6_NEXTHOP: { struct sockaddr_in6 *sin6; ip_stack_t *ipst = sctps->sctps_netstack->netstack_ip; if (inlen != 0 && inlen != sizeof (sin6_t)) { retval = EINVAL; break; } if (inlen == 0) { ipp->ipp_fields &= ~IPPF_NEXTHOP; } else { sin6 = (struct sockaddr_in6 *)invalp; if (sin6->sin6_family != AF_INET6) { retval = EAFNOSUPPORT; break; } if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { retval = EADDRNOTAVAIL; break; } ipp->ipp_nexthop = sin6->sin6_addr; if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { ipp->ipp_fields &= ~IPPF_NEXTHOP; } else { ire_t *ire; ire = ire_route_lookup_v6( &sin6->sin6_addr, NULL, NULL, 0, NULL, NULL, ALL_ZONES, NULL, MATCH_IRE_DEFAULT, ipst); if (ire == NULL) { retval = EHOSTUNREACH; break; } ire_refrele(ire); ipp->ipp_fields |= IPPF_NEXTHOP; } } retval = sctp_build_hdrs(sctp); break; } case IPV6_HOPOPTS: { ip6_hbh_t *hopts = (ip6_hbh_t *)invalp; if (inlen != 0 && inlen != (8 * (hopts->ip6h_len + 1))) { retval = EINVAL; break; } retval = optcom_pkt_set((uchar_t *)invalp, inlen, B_TRUE, (uchar_t **)&ipp->ipp_hopopts, &ipp->ipp_hopoptslen, sctp->sctp_v6label_len); if (retval != 0) break; if (ipp->ipp_hopoptslen == 0) ipp->ipp_fields &= ~IPPF_HOPOPTS; else ipp->ipp_fields |= IPPF_HOPOPTS; retval = sctp_build_hdrs(sctp); break; } case IPV6_RTHDRDSTOPTS: { ip6_dest_t *dopts = (ip6_dest_t *)invalp; if (inlen != 0 && inlen != (8 * (dopts->ip6d_len + 1))) { retval = EINVAL; break; } retval = optcom_pkt_set((uchar_t *)invalp, inlen, B_TRUE, (uchar_t **)&ipp->ipp_rtdstopts, &ipp->ipp_rtdstoptslen, 0); if (retval != 0) break; if (ipp->ipp_rtdstoptslen == 0) ipp->ipp_fields &= ~IPPF_RTDSTOPTS; else ipp->ipp_fields |= IPPF_RTDSTOPTS; retval = sctp_build_hdrs(sctp); break; } case IPV6_DSTOPTS: { ip6_dest_t *dopts = (ip6_dest_t *)invalp; if (inlen != 0 && inlen != (8 * (dopts->ip6d_len + 1))) { retval = EINVAL; break; } retval = optcom_pkt_set((uchar_t *)invalp, inlen, B_TRUE, (uchar_t **)&ipp->ipp_dstopts, &ipp->ipp_dstoptslen, 0); if (retval != 0) break; if (ipp->ipp_dstoptslen == 0) ipp->ipp_fields &= ~IPPF_DSTOPTS; else ipp->ipp_fields |= IPPF_DSTOPTS; retval = sctp_build_hdrs(sctp); break; } case IPV6_RTHDR: { ip6_rthdr_t *rt = (ip6_rthdr_t *)invalp; if (inlen != 0 && inlen != (8 * (rt->ip6r_len + 1))) { retval = EINVAL; break; } retval = optcom_pkt_set((uchar_t *)invalp, inlen, B_TRUE, (uchar_t **)&ipp->ipp_rthdr, &ipp->ipp_rthdrlen, 0); if (retval != 0) break; if (ipp->ipp_rthdrlen == 0) ipp->ipp_fields &= ~IPPF_RTHDR; else ipp->ipp_fields |= IPPF_RTHDR; retval = sctp_build_hdrs(sctp); break; } case IPV6_SEC_OPT: /* * We should not allow policy setting after * we start listening for connections. */ if (sctp->sctp_state >= SCTPS_LISTEN) { retval = EINVAL; } else { retval = ipsec_set_req(sctp->sctp_credp, sctp->sctp_connp, (ipsec_req_t *)invalp); } break; case IPV6_V6ONLY: /* * After the bound state, setting the v6only option * is too late. */ if (sctp->sctp_state >= SCTPS_BOUND) { retval = EINVAL; } else { sctp->sctp_connp->conn_ipv6_v6only = onoff; } break; default: retval = ENOPROTOOPT; break; } break; } default: retval = ENOPROTOOPT; break; } WAKE_SCTP(sctp); return (retval); } /* * SCTP exported kernel interface for geting the first source address of * a sctp_t. The parameter addr is assumed to have enough space to hold * one socket address. */ int sctp_getsockname(sctp_t *sctp, struct sockaddr *addr, socklen_t *addrlen) { int err = 0; int addrcnt = 1; sin_t *sin4; sin6_t *sin6; ASSERT(sctp != NULL); RUN_SCTP(sctp); addr->sa_family = sctp->sctp_family; switch (sctp->sctp_family) { case AF_INET: sin4 = (sin_t *)addr; if ((sctp->sctp_state <= SCTPS_LISTEN) && sctp->sctp_bound_to_all) { sin4->sin_addr.s_addr = INADDR_ANY; sin4->sin_port = sctp->sctp_lport; } else { err = sctp_getmyaddrs(sctp, sin4, &addrcnt); if (err != 0) { *addrlen = 0; break; } } *addrlen = sizeof (struct sockaddr_in); break; case AF_INET6: sin6 = (sin6_t *)addr; if ((sctp->sctp_state <= SCTPS_LISTEN) && sctp->sctp_bound_to_all) { bzero(&sin6->sin6_addr, sizeof (sin6->sin6_addr)); sin6->sin6_port = sctp->sctp_lport; } else { err = sctp_getmyaddrs(sctp, sin6, &addrcnt); if (err != 0) { *addrlen = 0; break; } } *addrlen = sizeof (struct sockaddr_in6); sin6->sin6_flowinfo = sctp->sctp_ip6h->ip6_vcf & ~IPV6_VERS_AND_FLOW_MASK; sin6->sin6_scope_id = 0; sin6->__sin6_src_id = 0; break; } WAKE_SCTP(sctp); return (err); } /* * SCTP exported kernel interface for geting the primary peer address of * a sctp_t. The parameter addr is assumed to have enough space to hold * one socket address. */ int sctp_getpeername(sctp_t *sctp, struct sockaddr *addr, socklen_t *addrlen) { int err = 0; int addrcnt = 1; sin6_t *sin6; ASSERT(sctp != NULL); RUN_SCTP(sctp); addr->sa_family = sctp->sctp_family; switch (sctp->sctp_family) { case AF_INET: err = sctp_getpeeraddrs(sctp, addr, &addrcnt); if (err != 0) { *addrlen = 0; break; } *addrlen = sizeof (struct sockaddr_in); break; case AF_INET6: sin6 = (sin6_t *)addr; err = sctp_getpeeraddrs(sctp, sin6, &addrcnt); if (err != 0) { *addrlen = 0; break; } *addrlen = sizeof (struct sockaddr_in6); sin6->sin6_flowinfo = 0; sin6->sin6_scope_id = 0; sin6->__sin6_src_id = 0; break; } WAKE_SCTP(sctp); return (err); } /* * Return a list of IP addresses of the peer endpoint of this sctp_t. * The parameter paddrs is supposed to be either (struct sockaddr_in *) or * (struct sockaddr_in6 *) depending on the address family of the sctp_t. */ int sctp_getpeeraddrs(sctp_t *sctp, void *paddrs, int *addrcnt) { int family; struct sockaddr_in *sin4; struct sockaddr_in6 *sin6; int max; int cnt; sctp_faddr_t *fp = sctp->sctp_faddrs; in6_addr_t addr; ASSERT(sctp != NULL); if (sctp->sctp_faddrs == NULL) return (ENOTCONN); family = sctp->sctp_family; max = *addrcnt; /* If we want only one, give the primary */ if (max == 1) { addr = sctp->sctp_primary->faddr; switch (family) { case AF_INET: sin4 = paddrs; IN6_V4MAPPED_TO_INADDR(&addr, &sin4->sin_addr); sin4->sin_port = sctp->sctp_fport; sin4->sin_family = AF_INET; break; case AF_INET6: sin6 = paddrs; sin6->sin6_addr = addr; sin6->sin6_port = sctp->sctp_fport; sin6->sin6_family = AF_INET6; break; } return (0); } for (cnt = 0; cnt < max && fp != NULL; cnt++, fp = fp->next) { addr = fp->faddr; switch (family) { case AF_INET: ASSERT(IN6_IS_ADDR_V4MAPPED(&addr)); sin4 = (struct sockaddr_in *)paddrs + cnt; IN6_V4MAPPED_TO_INADDR(&addr, &sin4->sin_addr); sin4->sin_port = sctp->sctp_fport; sin4->sin_family = AF_INET; break; case AF_INET6: sin6 = (struct sockaddr_in6 *)paddrs + cnt; sin6->sin6_addr = addr; sin6->sin6_port = sctp->sctp_fport; sin6->sin6_family = AF_INET6; break; } } *addrcnt = cnt; return (0); }